release winning entries of IOCCC28 under the year 2024
Congratulations to: * 2024/anders - Prize in sound coding - layered music generator * 2024/burton - Prize in pentagrammatology - wordle hard mode solver * 2024/cable1 - Prize in bot talk - LLM inference engine * 2024/cable2 - Prize in murky waters - salmon recipe * 2024/carlini - Prize in perfect timing - Intel 4004 emulation * 2024/codemeow - Prize in tray planting - bonsai generator * 2024/endoh1 - Prize in patient pointillism - slow ray tracer in CPP * 2024/endoh2 - Prize in solid body physics - rotating rigid body simulation * 2024/ferguson1 - Prize in diabolical logistics - Oregon trail pact with the devil * 2024/ferguson2 - Prize in yil-tas - Navajo code talker translator * 2024/howe - Nice accent eh-ward - mini vi * 2024/kramer - Prize in linguistic arithmetic - natural language numerical calculator * 2024/kurdyukov1 - Prize in phased periodicity - phase of the Moon * 2024/kurdyukov2 - Prize in art restoration - JPEG artifact removal * 2024/kurdyukov3 - Prize in virtual quietus - gaming VM and doom * 2024/kurdyukov4 - Prize in embiggening - encode without literals * 2024/maffiodo - Prize in creative interpretation - Jav\*script interpreter * 2024/mills - Prize in ℤ₃ - Infocom v3 story interpreter * 2024/stedolan - Best one liner - icon from MD5 * 2024/straadt - Prize in imitative rebooting - OpenRISC CPU and Linux * 2024/tmarrec - Prize in cyclonic coding - 3D tornado simulation * 2024/tompng - Prize in quasi bijection - vortex encryptor decryptor * 2024/weaver - Sur prize - rickroll Ran `make update` to perform the above. new file: 2024/burton/.entry.json
This commit is contained in:
1 parent
10512e6c79
commit
09750f8899
443 files changed
+182938
-231
No files matched your search
@@ -0,0 +1,516 @@
|
||||
## To build:
|
||||
|
||||
``` <!---sh-->
|
||||
make all
|
||||
```
|
||||
|
||||
|
||||
## Try:
|
||||
|
||||
``` <!---sh-->
|
||||
./try.sh
|
||||
```
|
||||
|
||||
**HINT**: You have to be **VERY** patient while the above script runs to completion.
|
||||
|
||||
|
||||
## Judges' remarks:
|
||||
|
||||
When you `make all` you see what looks like a C program, `rt.c`.
|
||||
However if you try to compile `rt.c`, you will fail: even if you try to
|
||||
define the symbols `X`, `Y`, `W`, and `H`.
|
||||
|
||||
Well, the propose of the C program, `rt.c` is to generate the red, green, and blue colors of
|
||||
a single pixel, in the C Preprocessor!!!
|
||||
|
||||
For the patient, a image may be rendered a slow pixel at a time.
|
||||
Think of this as digital [Pointillism](https://en.wikipedia.org/wiki/Pointillism). :-)
|
||||
|
||||
For the truly impatient who wishes to get right to the point without trying to dot around
|
||||
waiting for the [try.sh](%%REPO_URL%%/2024/endoh1/try.sh) to finish, you may view these images:
|
||||
|
||||
* [tiny 8x8 image](ref.8.png)
|
||||
* [small 32x32 image](ref.32.png)
|
||||
* [medium 128x128 image](ref.128.png)
|
||||
* [large 512x512 image](ref.512.png)
|
||||
|
||||
**FUN FACT**: We were able to determine on an "_x86-64 processor_" running
|
||||
"_RHEL 9.6_" using "_gcc version 11.5.0_", one could theoretically render
|
||||
a **2147483647 x 2147483647** pixel image. At the rate it took to render
|
||||
the **512 x 512** pixel image, and without using any parallelism, such an
|
||||
image would take about **169 billion years** to fully render on a single
|
||||
CPU core! Fully rendering such large images is **NOT** recommended. :-)
|
||||
|
||||
|
||||
### Parallel make
|
||||
|
||||
**SUGGESTION**: The [2024/endoh1/Makefile](%%REPO_URL%%/2024/endoh1/Makefile),
|
||||
by default, uses a single CPU core, can make use of parallel operations.
|
||||
If you have access to a "_parallel make_", can achieve the above in a shorter
|
||||
around of time. If you have "_n_" CPU cores available, consider running
|
||||
a parallel make with "_n-1_" jobs that run simultaneously. For example:
|
||||
|
||||
``` <!---sh-->
|
||||
RESERVE="-2"
|
||||
NPROC=$(nproc)
|
||||
((JOBS=NPROC-RESERVE))
|
||||
SIZE=512
|
||||
make clobber all
|
||||
nohup time make -j "$JOBS" out.ppm H="$SIZE" W="$SIZE" > make.out 2>&1 < /dev/null &
|
||||
```
|
||||
|
||||
Where `SIZE` is the size of the image to generate.
|
||||
|
||||
Where `RESERVE` is the number of CPU cores to reserve for other things
|
||||
such as the operating system, daemons, terminal window, etc. You can even
|
||||
"_oversubscribe_" by setting RESERVE to a negative number (e.g., `RESERVE="-2"`).
|
||||
|
||||
**NOTE**: The [try.sh](%%REPO_URL%%/2024/endoh1/try.sh) script follows the above
|
||||
example for `SIZE` values of 8, 32, 128, and then 512.
|
||||
|
||||
The optimal `RESERVE` will depend on your hardware, operating system,
|
||||
a C compiler. Making `RESERVE` too small (i.e., too negative) may slow
|
||||
down the result. We recommend that experiment various values pf `RESERVE`
|
||||
with moderate `SIZE` of **32** in order to try to find an optimal `JOBS`
|
||||
value before attempting to try a larger size such as **512** and beyond.
|
||||
|
||||
|
||||
<div id="larger">
|
||||
### Be first to build images larger than 512x512
|
||||
</div>
|
||||
|
||||
**QUESTION**: Do you have the patience to build an even larger than a 512x512 image?
|
||||
If you do, **using the program as it is written of course**, please [send the IOCCC](../../judges.html)
|
||||
the following:
|
||||
|
||||
* Image Height x Width in pixels
|
||||
* URL of the fully rendered image (in PPM form or better yet converted to PNG)
|
||||
* How long it took (perhaps using the time(1) command or using the `time` shell builtin)
|
||||
* The starting and ending dates (using `date -u`) of your rendering
|
||||
* Number of CPU cores, Operating system, Compiler, Compiler version, and make version
|
||||
* The number of jobs you ran in parallel
|
||||
* Anything else you may have done to optimize
|
||||
* Your name, (GitHub handle (optional)), (Mastodon handle (optional)), (home page URL (optional)), Country (or XX to be anonymous)
|
||||
|
||||
**PLEASE** do not attempt to optimize the general algorithm used by this entry to build PPM images.
|
||||
Please remain "_faithful to the spirit_" of this wonderful entry when attempting to build larger PPM images.
|
||||
|
||||
The first people to render a new record sized image **using the general algorithm used by this entry**,
|
||||
will be given credit in the [thanks for the help](../../thanks-for-help.html) web page.
|
||||
|
||||
|
||||
## Author's remarks:
|
||||
|
||||
|
||||
### A Ray Tracer in the C Preprocessor
|
||||
|
||||
Ever since the iconic 1988 IOCCC winning entry [1988/applin](../../1988/applin/index.html),
|
||||
bending the C preprocessor to one's will has been a time-honored tradition in the IOCCC.
|
||||
This entry sets out to be the culmination of that legacy: a ray tracer implemented entirely with C macros!
|
||||
|
||||
|
||||
#### How to Run
|
||||
|
||||
The initial program, `./prog` (compiled from `prog.c`), outputs the main code, `rt.c`.
|
||||
To get started, compile and execute it:
|
||||
|
||||
``` <!---sh-->
|
||||
make all
|
||||
```
|
||||
|
||||
Note: `rt.c` is not a valid C program -- it's written for C preprocessor.
|
||||
|
||||
To get the color of a single pixel at position (16, 16) in a 32 x 32 image, run:
|
||||
|
||||
``` <!---sh-->
|
||||
cc -E -P -DX=16 -DY=16 -DW=32 -DH=32 rt.c
|
||||
```
|
||||
|
||||
This will produce:
|
||||
|
||||
```
|
||||
129
|
||||
62
|
||||
129
|
||||
```
|
||||
|
||||
These three values represent the red, green, and blue components respectively.
|
||||
|
||||
* Red = 129 (0x81)
|
||||
* Green = 62 (0x3e)
|
||||
* Blue = 129 (0x81)
|
||||
|
||||
In other words, this indicates that the pixel at position (16, 16) has the color #813e81.
|
||||
|
||||
|
||||
#### How to Generate the Full Image
|
||||
|
||||
To generate the entire image, you need to run the preprocessor for every pixel.
|
||||
For example, to form a 32x32 image:
|
||||
|
||||
``` <!---sh-->
|
||||
mkdir -p pixel
|
||||
cc -E -P -DX=0 -DY=0 -DW=32 -DH=32 rt.c > pixel/pixel-00-00.txt
|
||||
cc -E -P -DX=1 -DY=0 -DW=32 -DH=32 rt.c > pixel/pixel-00-01.txt
|
||||
cc -E -P -DX=2 -DY=0 -DW=32 -DH=32 rt.c > pixel/pixel-00-02.txt
|
||||
...
|
||||
cc -E -P -DX=30 -DY=0 -DW=32 -DH=32 rt.c > pixel/pixel-00-30.txt
|
||||
cc -E -P -DX=31 -DY=0 -DW=32 -DH=32 rt.c > pixel/pixel-00-31.txt
|
||||
cc -E -P -DX=0 -DY=1 -DW=32 -DH=32 rt.c > pixel/pixel-01-00.txt
|
||||
cc -E -P -DX=1 -DY=1 -DW=32 -DH=32 rt.c > pixel/pixel-01-01.txt
|
||||
cc -E -P -DX=2 -DY=1 -DW=32 -DH=32 rt.c > pixel/pixel-01-02.txt
|
||||
...
|
||||
cc -E -P -DX=30 -DY=31 -DW=32 -DH=32 rt.c > pixel/pixel-31-30.txt
|
||||
cc -E -P -DX=31 -DY=31 -DW=32 -DH=32 rt.c > pixel/pixel-31-31.txt
|
||||
```
|
||||
|
||||
Then, concatenate the results with a PPM header to create the image:
|
||||
|
||||
``` <!---sh-->
|
||||
echo P3 32 32 > out.ppm
|
||||
echo 255 >> out.ppm
|
||||
find pixel -type f -print | LANG=C sort | xargs cat >> out.ppm
|
||||
```
|
||||
|
||||
Now open `out.ppm` with any image viewer that supports the PPM format.
|
||||
|
||||
|
||||
#### Automating with Makefile
|
||||
|
||||
To form a 32x32 image:
|
||||
|
||||
``` <!---sh-->
|
||||
make clean targets out.ppm H=32 W=32
|
||||
```
|
||||
|
||||
Now open `out.ppm` with any image viewer that supports the PPM format.
|
||||
|
||||
|
||||
#### Generating a Larger Image
|
||||
|
||||
To form a 256x256 image:
|
||||
|
||||
``` <!---sh-->
|
||||
make clean targets out.ppm H=256 W=256
|
||||
```
|
||||
|
||||
Now open `out.ppm` with any image viewer that supports the PPM format.
|
||||
|
||||
For reference, the 512x512 image took about 10 hours to render.
|
||||
|
||||
|
||||
#### Technical Challenges
|
||||
|
||||
Below is a technical explanation of the implementation.
|
||||
|
||||
|
||||
##### Copying a Macro Definition
|
||||
|
||||
One core challenges was how to "copy" the value of a macro.
|
||||
|
||||
For example, in C macros:
|
||||
|
||||
``` <!---c-->
|
||||
#define X 10
|
||||
#define Y X
|
||||
|
||||
#undef X
|
||||
#define X 20
|
||||
|
||||
Y // expands to "20", not "10"
|
||||
```
|
||||
|
||||
The C preprocessor uses lazy evaluation, meaning macro values are always resolved at the time of use.
|
||||
With this observation, some people claim it's impossible to copy a macro's value.
|
||||
|
||||
One known workaround is encoding one bit by whether or not a macro is defined (as seen in `applin.c` from IOCCC 1988 and `vik2.c` from IOCCC 2004).
|
||||
However, this approach is too inefficient for our purposes -- ray tracing requires operations like multiplication, and even square root.
|
||||
|
||||
Instead, we discovered an efficient way to "copy" a macro's value.
|
||||
The key observation is that `#if` eagerly evaluates the definition of a macro at that point in preprocessing.
|
||||
For example:
|
||||
|
||||
``` <!---c-->
|
||||
#if X % 2
|
||||
# define X_b0 1 // The LSB of X is 1
|
||||
#else
|
||||
# define X_b0 0 // The LSB of X is 0
|
||||
#endif
|
||||
```
|
||||
|
||||
This technique copies the least significant bit of `X`.
|
||||
By applying this for each bit, we can reconstruct the full value:
|
||||
|
||||
``` <!---c-->
|
||||
#define X 10
|
||||
|
||||
#if X % 2
|
||||
# define X_b0 1
|
||||
#else
|
||||
# define X_b0 0
|
||||
#endif
|
||||
|
||||
#if X / 2 % 2
|
||||
# define X_b1 2
|
||||
#else
|
||||
# define X_b1 0
|
||||
#endif
|
||||
|
||||
#if X / 4 % 2
|
||||
# define X_b2 4
|
||||
#else
|
||||
# define X_b2 0
|
||||
#endif
|
||||
|
||||
...
|
||||
|
||||
#define Y (X_b0 + X_b1 + X_b2 + ...)
|
||||
|
||||
#undef X
|
||||
#define X 20
|
||||
|
||||
Y // replaced with "0 + 2 + 0 + 8 + ..." = 10
|
||||
```
|
||||
|
||||
Even if `X` is later redefined, `Y` remains unchanged.
|
||||
|
||||
This technique resembles ["information flow"][information-flow] in the information security.
|
||||
To our best knowledge, there has been no literature that mentions this technique in terms of the C preprocessor programming.
|
||||
|
||||
[information-flow]: https://en.wikipedia.org/wiki/Information_flow_(information_theory)
|
||||
|
||||
|
||||
##### Macro Expansion Explosion
|
||||
|
||||
Using macros for complex expressions often causes memory issues.
|
||||
|
||||
For instance, consider this Newton's method implementation for square roots:
|
||||
|
||||
``` <!---c-->
|
||||
#define sqrt0(x, guess) (guess ? (x/guess+guess)/2 : 0)
|
||||
#define sqrt(x) sqrt0(x,sqrt0(x,sqrt0(x,sqrt0(x,sqrt0(x,10000)))))
|
||||
```
|
||||
|
||||
The subroutine `sqrt0` is one step of Newton's method, and `sqrt` applies it five times.
|
||||
There are three occurences of the argument `guess` in the body of `sqrt0`.
|
||||
Thus, applying `sqrt` repeats its argument expression at least 5^3 = 243 times.
|
||||
This easily leads to the explosion of macro expansion.
|
||||
|
||||
We avoid this issue by using the earlier "copy" trick -- reducing the size of macro expansions by materializing their values.
|
||||
|
||||
|
||||
##### Encoding Vectors
|
||||
|
||||
In theory, it is trivial to encode a 3D vector in the C preprocessor: you can just define three macros for x, y, and z components.
|
||||
However, it creates bloated code -- unsuitable for IOCCC size limits.
|
||||
|
||||
Instead, we encode vectors as simple tuples:
|
||||
|
||||
``` <!---c-->
|
||||
#define vec 1,2,3
|
||||
```
|
||||
|
||||
The vector projection can be implemented as follows.
|
||||
|
||||
``` <!---c-->
|
||||
#define fst(x,y,z) x
|
||||
#define snd(x,y,z) y
|
||||
#define thd(x,y,z) z
|
||||
#define call(f,a) f(a)
|
||||
```
|
||||
|
||||
Example usage:
|
||||
|
||||
``` <!---c-->
|
||||
call(fst, vec) // expands to 1
|
||||
call(snd, vec) // expands to 2
|
||||
call(thd, vec) // expands to 3
|
||||
```
|
||||
|
||||
The auxiliary macro `call` is required.
|
||||
This is a hassle of the C preprocessor, but, `call(fst, vec)` is expanded to `fst(1,2,3)`, which results in `1`.
|
||||
|
||||
We can define a vector addition by using the projections.
|
||||
|
||||
``` <!---c-->
|
||||
#define vec3_add(a,b) fst(a)+fst(b),snd(a)+snd(b),thd(a)+thd(b)
|
||||
```
|
||||
|
||||
Now, we can simply write `vec3_add(v1, v2)` to add two vectors.
|
||||
One identifier represents one vector.
|
||||
It is also elegantly possible to nest expressions like `vec3_add(v1, vec3_add(v1, v2))`.
|
||||
|
||||
``` <!---c-->
|
||||
#define v1 0,1,2
|
||||
#define v2 2,1,0
|
||||
|
||||
int main() {
|
||||
printf("(%d %d %d)\n", vec3_add(v1, v2)); // prints "(2 2 2)"
|
||||
printf("(%d %d %d)\n", vec3_add(v1, vec3_add(v1, v2))); // prints "(2 3 4)"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
##### Ray Tracing via Self-Inclusion
|
||||
|
||||
Ray tracing involves recursive calls for reflection.
|
||||
In `rt.c`, this is done via:
|
||||
|
||||
``` <!---c-->
|
||||
#include __FILE__
|
||||
```
|
||||
|
||||
In the implementation, only spheres reflect rays, and only one sphere exists.
|
||||
Thus, the recursion depth is limited to one.
|
||||
|
||||
Theoretically, it is possible to support multiple reflections, but this would require implementing a "stack" to keep "local variables" in a stack frame.
|
||||
|
||||
|
||||
##### Outputting a Pixel
|
||||
|
||||
The preprocessed result of `rt.c` contains some preprocessor directives (starting with `#`) and three integers (red, green, and blue).
|
||||
|
||||
``` <!---sh-->
|
||||
cc -E -DX=16 -DY=16 -DW=32 -DH=32 rt.c
|
||||
```
|
||||
|
||||
This will produce:
|
||||
|
||||
``` <!---c-->
|
||||
# 0 "rt.c"
|
||||
# 0 "<built-in>"
|
||||
# 0 "<command-line>"
|
||||
# 1 "/usr/include/stdc-predef.h" 1 3 4
|
||||
# 0 "<command-line>" 2
|
||||
# 1 "rt.c"
|
||||
# 3481 "rt.c"
|
||||
# 1 "rt.c" 1
|
||||
# 2090 "rt.c"
|
||||
# 1 "rt.c" 1
|
||||
# 2091 "rt.c" 2
|
||||
# 3482 "rt.c" 2
|
||||
# 3582 "rt.c"
|
||||
129
|
||||
# 3684 "rt.c"
|
||||
62
|
||||
# 3786 "rt.c"
|
||||
129
|
||||
```
|
||||
|
||||
Interestingly, these are valid in the PPM format (lines starting with `#` are commments in PPM).
|
||||
Thus, just concatenating the results for all pixels (and prefixing a small header) will work to create a PPM image file.
|
||||
|
||||
When render a large image, we recommend use the `-E -P` flags as in:
|
||||
|
||||
``` <!---sh-->
|
||||
cc -E -P -DX=16 -DY=16 -DW=32 -DH=32 rt.c
|
||||
```
|
||||
|
||||
This will only produce:
|
||||
|
||||
```
|
||||
129
|
||||
62
|
||||
129
|
||||
```
|
||||
|
||||
Note that values appear as expressions like `(0 + 2 + 0 + 8 + ...)` because we use the "copy" trick.
|
||||
To print them as to ordinary decimal values, we use macro logic like:
|
||||
|
||||
``` <!---c-->
|
||||
// assume red is (0 + 2 + 0 + 8)
|
||||
|
||||
#if red / 100 % 10 == 0
|
||||
# define digit0(digits) 0##digits
|
||||
#elif red / 100 % 10 == 1
|
||||
# define digit0(digits) 1##digits
|
||||
#else
|
||||
# define digit0(digits) 2##digits
|
||||
#endif
|
||||
|
||||
#if red / 10 % 10 == 0
|
||||
# define digit1(digits) digits0(0##digits)
|
||||
#elif red / 10 % 10 == 1
|
||||
# define digit1(digits) digits0(1##digits)
|
||||
#elif ...
|
||||
...
|
||||
#endif
|
||||
|
||||
#if red % 10 == 0
|
||||
# define digits digit1(0)
|
||||
#elif red % 10 == 1
|
||||
# define digits digit1(1)
|
||||
#elif ...
|
||||
...
|
||||
#endif
|
||||
|
||||
digits // outputs "10"
|
||||
```
|
||||
|
||||
|
||||
#### Customizing the Scene
|
||||
|
||||
You can modify the scene by editing the top of `rt.c`.
|
||||
|
||||
|
||||
##### Example Defaults
|
||||
|
||||
``` <!---c-->
|
||||
#define p 0,500,4000 // sphere center position
|
||||
#define q 2000 // sphere radius
|
||||
#define r 300,300,300 // sphere color (default: light gray)
|
||||
#define s(x) 900,200+K*7100,900 // plane color (default: checkered colors of white for K=1 and purple for K=0)
|
||||
#define t -2000,4000,1000 // light position
|
||||
#define u 50000,50000,50000 // light color (default: white)
|
||||
#define v(x) 500-x,500-x,500 // background color (default: gradation from blue to white)
|
||||
```
|
||||
|
||||
|
||||
##### Variant Example
|
||||
|
||||
``` <!---c-->
|
||||
#define p 0,0,8000
|
||||
#define q 2000
|
||||
#define r 300,300,900
|
||||
#define s(x) 200,500-x*300,200+x*800
|
||||
#define t 2000,4000,3000
|
||||
#define u 30000,40000,30000
|
||||
#define v(x) 500,x,10
|
||||
```
|
||||
|
||||
Features:
|
||||
|
||||
* Grounded blue sphere
|
||||
* Blue and green checkered floor
|
||||
* Sunset-style lighting
|
||||
|
||||
See `ref.512-variant.png` for the rendered result.
|
||||
|
||||
|
||||
#### Limitations
|
||||
|
||||
Currently, this program supports:
|
||||
|
||||
* One sphere
|
||||
* One floor
|
||||
* One light
|
||||
|
||||
Adding more lights is relatively simple.
|
||||
Adding multiple spheres would require stack support for recursive ray tracing (see "Ray Tracing by Self-Inclusion" section).
|
||||
|
||||
Note: we recommend GCC for performance; Clang is slower for C preprocessor-heavy tasks like this one.
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
Copyright © 1984-2025 by Landon Curt Noll and Leonid A. Broukhis. All Rights Reserved.
|
||||
|
||||
You are free to share and adapt this file under the terms of this license:
|
||||
|
||||
Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
|
||||
|
||||
For more information, see:
|
||||
|
||||
https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
-->
|
||||
Reference in new issue
Block a user