├── scene.ppm.in ├── final.png ├── final.png.license ├── scene.ppm.in.license ├── meson_options.txt ├── .reuse └── dep5 ├── LICENSES └── MIT.txt ├── README.md └── meson.build /scene.ppm.in: -------------------------------------------------------------------------------- 1 | P3 2 | @dim@ @dim@ 3 | 255 4 | # 5 | @scene@ 6 | -------------------------------------------------------------------------------- /final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annacrombie/meson-raytracer/HEAD/final.png -------------------------------------------------------------------------------- /final.png.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Stone Tickle 2 | SPDX-License-Identifier: MIT 3 | -------------------------------------------------------------------------------- /scene.ppm.in.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: Stone Tickle 2 | SPDX-License-Identifier: MIT 3 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Stone Tickle 2 | # SPDX-License-Identifier: MIT 3 | 4 | option('dim', type: 'integer', value: 64) 5 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: meson-raytracer 3 | Upstream-Contact: Stone Tickle 4 | Source: https://github.com/annacrombie/meson-raytracer 5 | 6 | # Sample paragraph, commented out: 7 | # 8 | # Files: src/* 9 | # Copyright: $YEAR $NAME <$CONTACT> 10 | # License: ... 11 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # meson ray tracer 7 | 8 | A simple ray tracer written in the meson.build language. 9 | 10 | ![render example](final.png) 11 | 12 | ## usage 13 | 14 | ``` 15 | $meson setup -Ddim=512 build 16 | ``` 17 | 18 | The output will be located at `build/output.ppm`. 19 | 20 | ## performance 21 | 22 | Numbers are for the example render above on an Intel(R) Core(TM) i7-2640M CPU @ 23 | 2.80GHz with 16G of memory. 24 | 25 | | implementation | time | 26 | | -------------- | --------- | 27 | | Meson | 20h57m19s | 28 | | muon | 15m13s | 29 | 30 | ## implementation 31 | 32 | This ray tracer is based on and inspired by Matt Taylor's 33 | [cmake-raytracer](https://github.com/64/cmake-raytracer/blob/master/README.md). 34 | In addition to not supporting floating point values like CMake, Meson does not 35 | allow user defined functions, and all objects are immutable. It also does not 36 | have while loops, only python-style for loops. 37 | 38 | There are a few ways to implement a ray tracer given these constraints. One way 39 | would be to take the algorithm, inline all the function calls (including 40 | recursively inlining the trace function) and wrapping it in a loop for each 41 | pixel. If I had taken this approach I likely would have written a code 42 | generator to make development easier. Another method (the one I chose) is to 43 | implement a tiny vm that interprets code somewhat resembling assembly. This not 44 | only seems more interesting to write, but also demonstrates that Meson 45 | technically *can* have functions, macros, and practical undecidability. 46 | 47 | The vm's instruction set mainly consists of arithmetic, vector operations, and 48 | jumping instructions. The latter being required for branching, loops, and 49 | recursion. Some other notable instructions are `push` and `pop` which are used 50 | to make recursion more convenient. Originally, there was also a proper return 51 | stack and the program was broken up into more small functions. However, due to 52 | meson's immutable objects, popping from the stack is relatively expensive since 53 | it requires the current stack to be copied. The current implementation, with 54 | just one function call, and a stack-less `ret` instruction, improves performance 55 | while still allowing recursion where needed. 56 | 57 | Because Meson has no while loop, the vm runs in a `foreach` loop. Luckily Meson 58 | does have a `range(x)` function, which can be used to create practically 59 | infinite loops. The current implementation supports 4294967295 cycles, which at 60 | roughly 381 cycles/s, would be exhausted after running for 130 days. If this 61 | isn't long enough for you, you can wrap the vm's loop inside another identical 62 | foreach loop and get about 1,535,281,934 years of runtime! muon, on the other 63 | hand, runs at about 26,685 cycles/s so its corresponding numbers would be 45 64 | hours with one foreach and 21,920,270 years with two. 65 | 66 | The program is written as an array, with four elements per instruction. The 67 | first element is the instruction name, and the remaining three are arguments. By 68 | convention, zero, aliased to \_, is passed for unused argument slots. For 69 | example, the function 'print' can be called like this: 70 | 71 | ``` 72 | ['print', 'argument', _, _] 73 | ``` 74 | 75 | Here is a while (true) loop: 76 | 77 | ``` 78 | ['set', 'message', 'hello world!', _, 79 | 'print', 'message', _, _ 80 | 'jmp', -2*ll, _, _, 81 | ] 82 | ``` 83 | 84 | The actual ray tracer part of this project is essentially a port of the cmake 85 | ray tracer to this assembly-like meta-language. 86 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Stone Tickle 2 | # SPDX-License-Identifier: MIT 3 | 4 | project('ray', version: 'tracer', meson_version: '>=0.60.0') 5 | 6 | dim = get_option('dim') 7 | 8 | scale = 10000 9 | dim_fp = dim * scale 10 | scale_div_2 = scale / 2 11 | three_halves = (scale / 2) + scale 12 | v_empty = [0, 0, 0] 13 | plane_y_fp = -2 * scale 14 | ray_epsilon = 100 15 | ray_dir = [0, 0, 0] 16 | plane_color_1 = [10*222, 10*125, 10*484] 17 | plane_color_2 = [(scale / 100)*45, (scale / 100)*45, (scale / 100)*45] 18 | sphere_r = 2 * scale 19 | sphere_r2 = (sphere_r * sphere_r) / scale 20 | sphere_center = [scale, 0 * scale, 4 * scale] 21 | sphere_color = [(scale / 100)*45, (scale / 100)*45, (scale / 100)*45] 22 | light_pos = [-2 * scale, 4 * scale, 1 * scale] 23 | light_color = [40*2851,40*7460,40*3984] 24 | bg = [10*222, 10*125, 10*484] 25 | retto = -1 26 | s = [] 27 | sl = 0 28 | pix = [] 29 | pc = 0 30 | 31 | cmp = 0 32 | ll = 4 33 | _ = 0 34 | 35 | p_plane_intersect = [ 36 | '!set', 'pb', 'pc', _, 37 | 'set', 'pl', 'l_plane_intersect', _, 38 | 'vget', 'ray_dir_y', 'ray_dir', 1, 39 | 'cmp', 'ray_dir_y', 0, _, 40 | 'jne', 2*ll, _, _, 41 | 'set', 'hit_t', -1, _, 42 | 'ret', _, _, _, 43 | 44 | 'set', 't', plane_y_fp, _, 45 | 'vget', 'ray_origin_y', 'ray_origin', 1, 46 | '!-', 't', 'ray_origin_y', _, 47 | '!/', 't', 'ray_dir_y', _, 48 | 49 | 'between', 't', 0, scale * 20, 50 | 'jnz', 2*ll, _, _, 51 | 'set', 'hit_t', -1, _, 52 | 'ret', _, _, _, 53 | 54 | '!set', 'hit_t', 't', _, 55 | '!vcopy', 'v1', 'ray_dir', _, 56 | '!v*', 'v1', 't', _, 57 | '!v+', 'v1', 'ray_origin', _, 58 | '!vcopy', 'hit_point', 'v1', _, 59 | 'vclear', 'hit_normal', _, _, 60 | 'vset', 'hit_normal', 1, scale, 61 | ] 62 | l_plane_intersect = p_plane_intersect.length() 63 | 64 | p_sphere_intersect = [ 65 | '!set', 'pb', 'pc', _, 66 | 'set', 'pl', 'l_sphere_intersect', _, 67 | 68 | '!vcopy', 'oc', 'ray_origin', _, 69 | 'v-', 'oc', sphere_center, _, 70 | '!v.v', 'rd_rd', 'ray_dir', 'ray_dir', 71 | '!v.v', 'half_b', 'oc', 'ray_dir', 72 | '!v.v', 'ac', 'oc', 'oc', 73 | '-', 'ac', sphere_r2, _, 74 | 75 | '!set', 'discrim', 'half_b', _, 76 | '!*', 'discrim', 'half_b', _, 77 | '!*', 'ac', 'rd_rd', _, 78 | '!-', 'discrim', 'ac', _, 79 | 80 | 'cmp', 'discrim', 0, _, 81 | 'jg', 2*ll, _, _, 82 | 'set', 'hit_t', -1, _, 83 | 'ret', _, _, 'sphere intersect 1', 84 | 85 | 'sqrt', 'discrim', _, _, 86 | 'set', 'minus_half_b', 0, _, 87 | '!-', 'minus_half_b', 'half_b', _, 88 | 89 | '!set', 't', 'minus_half_b', _, 90 | '!-', 't', 'discrim', _, 91 | '!/', 't', 'rd_rd', _, 92 | 93 | 'cmp', 't', 0, _, 94 | 'jle', 11*ll, _, _, 95 | 96 | '!set', 'hit_t', 't', _, 97 | '!vcopy', 'hit_point', 'ray_dir', _, 98 | '!v*', 'hit_point', 't', _, 99 | '!v+', 'hit_point', 'ray_origin', _, 100 | '!vcopy', 'hit_normal', 'hit_point', _, 101 | 'v-', 'hit_normal', sphere_center, _, 102 | 'v/', 'hit_normal', sphere_r, _, 103 | 'ret', _, _, 'sphere intersect', 104 | 105 | '!set', 't', 'minus_half_b', _, 106 | '!+', 't', 'discrim', _, 107 | '!/', 't', 'rd_rd', _, 108 | 109 | 'cmp', 't', 0, _, 110 | 'jg', -13*ll, _, _, 111 | 112 | 'set', 'hit_t', -1, _, 113 | ] 114 | l_sphere_intersect = p_sphere_intersect.length() 115 | 116 | p_light = [ 117 | 'vclear', 'lcol', _, _, 118 | '!vcopy', 'l', 'light_pos', _, 119 | '!v-', 'l', 'hit_point', _, 120 | '!vcopy', 'lnorm', 'l', _, 121 | 'vnorm', 'lnorm', _, _, 122 | '!v.v', 'ndotl', 'hit_normal', 'lnorm', 123 | 'cmp', 'ndotl', 0, _, 124 | 'jl', 4*ll, _, _, 125 | '!vcopy', 'lcol', 'light_color', _, 126 | '!v*', 'lcol', 'ndotl', _, 127 | '!v.v', 'l2', 'l', 'l', 128 | '!v/', 'lcol', 'l2', _, 129 | ] 130 | 131 | p_checker = [ 132 | 'vget', 'hit_point_x', 'hit_point', 0, 133 | 'vget', 'hit_point_z', 'hit_point', 2, 134 | 'fract', 'hit_point_x', _, _, 135 | 'fract', 'hit_point_z', _, _, 136 | 137 | ] + p_light + [ 138 | 139 | 'cmp', 'hit_point_x', scale_div_2, _, 140 | 'jl', 5*ll, _, _, 141 | 'cmp', 'hit_point_z', scale_div_2, _, 142 | 'jl', 3*ll, _, _, 143 | '!v+', 'rgb', 'plane_color_1', _, 144 | '!v*v', 'rgb', 'lcol', _, 145 | 'ret', _, _, _, 146 | 147 | 'cmp', 'hit_point_z', scale_div_2, _, 148 | 'jg', 5*ll, _, _, 149 | 'cmp', 'hit_point_x', scale_div_2, _, 150 | 'jg', 3*ll, _, _, 151 | '!v+', 'rgb', 'plane_color_1', _, 152 | '!v*v', 'rgb', 'lcol', _, 153 | 'ret', _, _, _, 154 | 155 | '!v+', 'rgb', 'plane_color_2', _, 156 | '!v*v', 'rgb', 'lcol', _, 157 | ] 158 | 159 | p_ssurf = [ 160 | '!vcopy', 'new_origin', 'hit_normal', _, 161 | 'v*', 'new_origin', ray_epsilon, _, 162 | '!v+', 'new_origin', 'hit_point', _, 163 | 164 | '!v.v', 'scalar', 'hit_normal', 'ray_dir', 165 | '2*', 'scalar', _, _, 166 | '!vcopy', 'refl_a', 'hit_normal', _, 167 | '!v*', 'refl_a', 'scalar', _, 168 | '!vcopy', 'new_dir', 'ray_dir', _, 169 | '!v-', 'new_dir', 'refl_a', _, 170 | 171 | '!vcopy', 'ray_dir', 'new_dir', _, 172 | '!vcopy', 'ray_origin', 'new_origin', _, 173 | 174 | 'push', 'hit_point', _, _, 175 | 'push', 'hit_normal', _, _, 176 | 'push', 'retto', _, _, 177 | '+', 'trace_depth', 1, _, 178 | '!set', 'retto', 'pc', _, 179 | '+', 'retto', 2*ll, _, 180 | '!jmp', 'pb_trace', _, _, 181 | 'pop', 'retto', _, _, 182 | 'pop', 'hit_normal', _, _, 183 | 'pop', 'hit_point', _, _, 184 | 185 | ] + p_light + [ 186 | '!v+', 'rgb', 'lcol', _, 187 | '!v*v', 'rgb', 'sphere_color', _, 188 | ] 189 | 190 | p_trace = [ 191 | '!set', 'pb_trace', 'pc', _, 192 | 'cmp', 'trace_depth', 3, _, 193 | 'jl', 1*ll, _, _, 194 | 'ret', _, _, _, 195 | 196 | '!vcopy', 'rgb', 'bg', _, 197 | 198 | ] + p_sphere_intersect + [ 199 | '!set', 'pb', 'pb_trace', _, 200 | 'set', 'pl', 'l_trace', _, 201 | 202 | 'cmp', 'hit_t', ray_epsilon, _, 203 | 'jl', p_ssurf.length()+1*ll, _, _, 204 | ] + p_ssurf + [ 205 | 'ret', _, _, _, 206 | 207 | ] + p_plane_intersect + [ 208 | '!set', 'pb', 'pb_trace', _, 209 | 'set', 'pl', 'l_trace', _, 210 | 211 | 'cmp', 'hit_t', ray_epsilon, _, 212 | 'jg', 1*ll, _, _, 213 | 'ret', _, _, _, 214 | ] + p_checker + [ 215 | 216 | 'ret', _, _, _, 217 | ] 218 | l_trace = p_trace.length() 219 | 220 | p_calc_dir_coord = [ 221 | '/', 'fp_coord', dim_fp, _, 222 | '*', 'fp_coord', 2 * scale, _, 223 | 'set', 'dir_coord', scale, _, 224 | ] 225 | 226 | p_l1 = [ 227 | 'set', 'x', 0, _, 228 | '!set', 'l1', 'pc', _, 229 | 230 | 'vclear', 'rgb', _, _, 231 | 'vclear', 'ray_origin', _, _, 232 | 233 | '!fp', 'fp_coord', 'x', _, 234 | ] + p_calc_dir_coord + [ 235 | '!-', 'fp_coord', 'dir_coord', _, 236 | '!vset', 'ray_dir', 0, 'fp_coord', 237 | '!fp', 'fp_coord', 'y', _, 238 | ] + p_calc_dir_coord + [ 239 | '!-', 'dir_coord', 'fp_coord', _, 240 | '!vset', 'ray_dir', 1, 'dir_coord', 241 | 'vset', 'ray_dir', 2, scale, 242 | 243 | 'set', 'trace_depth', 0, _, 244 | ] + p_trace + [ 245 | 246 | 'v*', 'rgb', 255 * scale, _, 247 | 'vtrunc', 'rgb', _, _, 248 | 'addpix', 'rgb', _, _, 249 | 250 | '+', 'x', 1, _, 251 | 'cmp', 'x', dim, _, 252 | '!jl', 'l1', _, _, 253 | ] 254 | 255 | p_m = [ 256 | 'set', 'y', 0, _, 257 | '!set', 'l0', 'pc', _, 258 | '!print', 'y', _, _, 259 | 260 | ] + p_l1 + [ 261 | 262 | '+', 'y', 1, _, 263 | 'cmp', 'y', dim, _, 264 | '!jl', 'l0', _, _, 265 | ] 266 | 267 | prog = p_m 268 | prog_length = prog.length() 269 | if prog_length / 4 * 4 != prog_length 270 | error('prog unaligned') 271 | endif 272 | 273 | # foreach inst : range(prog_length / 4) 274 | # inst = inst * 4 275 | # message([inst, prog[inst+0], prog[inst+1], prog[inst+2], prog[inst+3]]) 276 | # endforeach 277 | # error('') 278 | 279 | foreach _ : range(4294967295) 280 | f = prog[pc] 281 | pc += 1 282 | a = prog[pc] 283 | pc += 1 284 | b = prog[pc] 285 | pc += 1 286 | c = prog[pc] 287 | pc += 1 288 | 289 | # p({'pc': pc - 4, 'inst': [f, a, b, c]}) 290 | 291 | if f == '!print' 292 | message(get_variable(a)) 293 | elif f == 'vprint' 294 | va = get_variable(a) 295 | message('v: (@0@, @1@, @2@)'.format(va[0], va[1], va[2])) 296 | elif f == 'set' 297 | set_variable(a, b) 298 | elif f == '!set' 299 | set_variable(a, get_variable(b)) 300 | elif f == '!fp' 301 | set_variable(a, get_variable(b) * scale) 302 | elif f == 'between' 303 | v1 = get_variable(a) 304 | if b < v1 and v1 < c 305 | cmp = 1 306 | else 307 | cmp = 0 308 | endif 309 | elif f == 'cmp' 310 | v1 = get_variable(a) 311 | if v1 < b 312 | cmp = -1 313 | elif v1 > b 314 | cmp = 1 315 | else 316 | cmp = 0 317 | endif 318 | elif f == 'jne' 319 | if cmp != 0 320 | pc += a 321 | endif 322 | elif f == 'jl' 323 | if cmp == -1 324 | pc += a 325 | endif 326 | elif f == 'jle' 327 | if cmp != 1 328 | pc += a 329 | endif 330 | elif f == '!jl' 331 | if cmp == -1 332 | pc = get_variable(a) 333 | endif 334 | elif f == 'jg' or f == 'jnz' 335 | if cmp == 1 336 | pc += a 337 | endif 338 | elif f == '!jmp' 339 | pc = get_variable(a) 340 | elif f == 'ret' 341 | if retto >= 0 and get_variable('pl') == 'l_trace' 342 | pc = retto 343 | else 344 | pc = get_variable('pb') + get_variable(get_variable('pl')) - ll 345 | endif 346 | elif f == '+' 347 | set_variable(a, get_variable(a) + b) 348 | elif f == '!+' 349 | set_variable(a, get_variable(a) + get_variable(b)) 350 | elif f == '-' 351 | set_variable(a, get_variable(a) - b) 352 | elif f == '!-' 353 | set_variable(a, get_variable(a) - get_variable(b)) 354 | elif f == '*' 355 | set_variable(a, (get_variable(a) * b) / scale) 356 | elif f == '2*' 357 | set_variable(a, get_variable(a) * 2) 358 | elif f == '!*' 359 | set_variable(a, (get_variable(a) * get_variable(b)) / scale) 360 | elif f == '/' 361 | set_variable(a, (get_variable(a) * scale) / b) 362 | elif f == '!/' 363 | set_variable(a, (get_variable(a) * scale) / get_variable(b)) 364 | elif f == 'fract' 365 | nv = get_variable(a) % scale 366 | if nv < 0 367 | nv += scale 368 | endif 369 | 370 | set_variable(a, nv) 371 | elif f == 'trunc' 372 | set_variable(a, get_variable(a) / scale) 373 | elif f == '%' 374 | set_variable(a, get_variable(a) % b) 375 | elif f == 'sqrt' 376 | va = get_variable(a) 377 | if va < 0 378 | error('sqrt negative') 379 | endif 380 | 381 | guess = va / 2 382 | foreach _ : range(5) 383 | if guess == 0 384 | break 385 | endif 386 | 387 | sqrt_tmp = (va * scale) / guess 388 | sqrt_tmp += guess 389 | guess = sqrt_tmp / 2 390 | endforeach 391 | 392 | set_variable(a, guess) 393 | elif f == '!v+' 394 | va = get_variable(a) 395 | vb = get_variable(b) 396 | set_variable(a, [va[0] + vb[0], va[1] + vb[1], va[2] + vb[2]]) 397 | elif f == 'v-' 398 | va = get_variable(a) 399 | set_variable(a, [va[0] - b[0], va[1] - b[1], va[2] - b[2]]) 400 | elif f == '!v-' 401 | va = get_variable(a) 402 | vb = get_variable(b) 403 | set_variable(a, [va[0] - vb[0], va[1] - vb[1], va[2] - vb[2]]) 404 | elif f == 'v*' 405 | va = get_variable(a) 406 | set_variable(a, [(va[0] * b) / scale, (va[1] * b) / scale, (va[2] * b) / scale]) 407 | elif f == '!v*' 408 | va = get_variable(a) 409 | fb = get_variable(b) 410 | set_variable(a, [(va[0] * fb) / scale, (va[1] * fb) / scale, (va[2] * fb) / scale]) 411 | elif f == 'v/' 412 | va = get_variable(a) 413 | set_variable(a, [(va[0] * scale) / b, (va[1] * scale) / b, (va[2] * scale) / b]) 414 | elif f == '!v/' 415 | va = get_variable(a) 416 | fb = get_variable(b) 417 | set_variable(a, [(va[0] * scale) / fb, (va[1] * scale) / fb, (va[2] * scale) / fb]) 418 | elif f == '!v*v' 419 | va = get_variable(a) 420 | vb = get_variable(b) 421 | set_variable(a, [(va[0] * vb[0]) / scale, (va[1] * vb[1]) / scale, (va[2] * vb[2]) / scale]) 422 | elif f == '!v.v' 423 | vb = get_variable(b) 424 | vc = get_variable(c) 425 | set_variable(a, (vb[0] * vc[0]) / scale + (vb[1] * vc[1]) / scale + (vb[2] * vc[2]) / scale) 426 | elif f == 'vnorm' 427 | va = get_variable(a) 428 | dot = (va[0] * va[0]) / scale + (va[1] * va[1]) / scale + (va[2] * va[2]) / scale 429 | 430 | dot2 = dot / 2 431 | guess = (scale * scale) / dot 432 | foreach _ : range(6) 433 | rsq_tmp = (guess * guess) / scale 434 | rsq_tmp = (rsq_tmp * dot2) / scale 435 | rsq_tmp = three_halves - rsq_tmp 436 | guess = (rsq_tmp * guess) / scale 437 | endforeach 438 | 439 | fb = guess 440 | set_variable(a, [(va[0] * fb) / scale, (va[1] * fb) / scale, (va[2] * fb) / scale]) 441 | elif f == 'vtrunc' 442 | va = get_variable(a) 443 | set_variable(a, [va[0] / scale, va[1] / scale, va[2] / scale]) 444 | elif f == 'vclear' 445 | set_variable(a, v_empty) 446 | elif f == '!vcopy' 447 | set_variable(a, get_variable(b)) 448 | elif f == 'vset' 449 | old_v = get_variable(a) 450 | new_v = [] 451 | foreach vi : range(3) 452 | if vi == b 453 | new_v += c 454 | else 455 | new_v += old_v[vi] 456 | endif 457 | endforeach 458 | 459 | set_variable(a, new_v) 460 | elif f == '!vset' 461 | old_v = get_variable(a) 462 | new_v = [] 463 | foreach vi : range(3) 464 | if vi == b 465 | new_v += get_variable(c) 466 | else 467 | new_v += old_v[vi] 468 | endif 469 | endforeach 470 | 471 | set_variable(a, new_v) 472 | elif f == 'vget' 473 | set_variable(a, get_variable(b)[c]) 474 | elif f == 'addpix' 475 | va = get_variable(a) 476 | pix += [va[0].to_string(), va[1].to_string(), va[2].to_string()] 477 | elif f == 'push' 478 | s += [get_variable(a)] 479 | sl += 1 480 | elif f == 'pop' 481 | i = 0 482 | ns = [] 483 | nv = '?' 484 | foreach v : s 485 | i += 1 486 | if i == sl 487 | nv = v 488 | break 489 | endif 490 | 491 | ns += [v] 492 | endforeach 493 | s = ns 494 | sl = sl - 1 495 | set_variable(a, nv) 496 | else 497 | error(f'unknown function @f@') 498 | endif 499 | 500 | if pc >= prog_length 501 | break 502 | endif 503 | endforeach 504 | 505 | configure_file(input: 'scene.ppm.in', 506 | output: 'scene.ppm', 507 | configuration: { 508 | 'dim': dim, 509 | 'scene': ' '.join(pix) 510 | } 511 | ) 512 | --------------------------------------------------------------------------------