Browse Source

blending in linear colour space before converting to sRGB space

Inderjit Gill 1 month ago
parent
commit
73483b1b31

+ 1
- 6
cli/src/main.rs View File

@@ -139,12 +139,7 @@ fn load_bitmap(asset_prefix: &String, filename: &String, context: &mut Context)
139 139
         data.push(rgba.data[3]);
140 140
     }
141 141
 
142
-    let bitmap_info = BitmapInfo {
143
-        width,
144
-        height,
145
-        data,
146
-        ..Default::default()
147
-    };
142
+    let bitmap_info = BitmapInfo::new(width, height, data);
148 143
 
149 144
     context.bitmap_cache.insert(&filename, bitmap_info)?;
150 145
 

+ 1
- 6
client/src/lib.rs View File

@@ -192,12 +192,7 @@ impl Bridge {
192 192
     }
193 193
 
194 194
     pub fn add_rgba_bitmap(&mut self, name: &str, width: usize, height: usize, data: Vec<u8>) {
195
-        let bitmap_info = BitmapInfo {
196
-            width,
197
-            height,
198
-            data,
199
-            ..Default::default()
200
-        };
195
+        let bitmap_info = BitmapInfo::new(width, height, data);
201 196
 
202 197
         self.context.bitmap_cache.insert(name, bitmap_info).unwrap();
203 198
     }

+ 332
- 111
client/www/index.js View File

@@ -109,19 +109,21 @@ function compileShader(gl, type, src) {
109 109
 
110 110
   if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
111 111
     //alert(gl.getShaderInfoLog(shader));
112
+    gl.deleteShader(shader);
112 113
     return null;
113 114
   }
114 115
   return shader;
115 116
 }
116 117
 
117
-function setupShaders(gl) {
118
-  const shaderProgram = gl.createProgram();
118
+function setupPieceShaders(gl) {
119
+  const shader = {};
119 120
 
121
+  shader.program = gl.createProgram();
120 122
 
121 123
   // pre-multiply the alpha in the shader
122 124
   // see http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/
123 125
   const fragmentSrc = `
124
-  precision mediump float;
126
+  precision highp float;
125 127
   varying vec4 vColor;
126 128
   varying highp vec2 vTextureCoord;
127 129
 
@@ -134,7 +136,6 @@ function setupShaders(gl) {
134 136
     gl_FragColor.g = tex.r * vColor.g * vColor.a;
135 137
     gl_FragColor.b = tex.r * vColor.b * vColor.a;
136 138
     gl_FragColor.a = tex.r * vColor.a;
137
-
138 139
   }
139 140
   `;
140 141
 
@@ -159,36 +160,108 @@ function setupShaders(gl) {
159 160
   const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc);
160 161
   const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc);
161 162
 
162
-  gl.attachShader(shaderProgram, vertexShader);
163
-  gl.attachShader(shaderProgram, fragmentShader);
163
+  gl.attachShader(shader.program, vertexShader);
164
+  gl.attachShader(shader.program, fragmentShader);
165
+
166
+  gl.linkProgram(shader.program);
167
+
168
+  if (!gl.getProgramParameter(shader.program, gl.LINK_STATUS)) {
169
+    let lastError = gl.getProgramInfoLog(shader.program);
170
+
171
+    alert(`Could not initialise shaders: ${lastError}`);;
172
+    gl.deleteProgram(shader.program);
173
+    return null;
174
+  }
175
+
176
+  shader.positionAttribute = gl.getAttribLocation(shader.program, 'aVertexPosition');
177
+  shader.colourAttribute = gl.getAttribLocation(shader.program, 'aVertexColor');
178
+  shader.textureAttribute = gl.getAttribLocation(shader.program, 'aVertexTexture');
179
+
180
+  shader.pMatrixUniform = gl.getUniformLocation(shader.program, 'uPMatrix');
181
+  shader.mvMatrixUniform = gl.getUniformLocation(shader.program, 'uMVMatrix');
182
+  shader.textureUniform  = gl.getUniformLocation(shader.program, 'uSampler');
183
+
184
+  return shader;
185
+}
186
+
187
+function setupBlitShaders(gl) {
188
+  const shader = {};
164 189
 
165
-  gl.linkProgram(shaderProgram);
190
+  shader.program = gl.createProgram();
166 191
 
167
-  // commented out because of jshint
168
-  //  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
169
-  //alert('Could not initialise shaders');
170
-  //  }
192
+  const fragmentSrc = `
193
+  precision highp float;
194
+
195
+  varying vec2 vTextureCoord;
171 196
 
172
-  gl.useProgram(shaderProgram);
197
+  uniform sampler2D uSampler;
173 198
 
174
-  shaderProgram.positionAttribute =
175
-    gl.getAttribLocation(shaderProgram, 'aVertexPosition');
176
-  gl.enableVertexAttribArray(shaderProgram.positionAttribute);
199
+  // https:en.wikipedia.org/wiki/SRGB
200
+  vec3 linear_to_srgb(vec3 linear) {
201
+      float a = 0.055;
202
+      float b = 0.0031308;
203
+      vec3 srgb_lo = 12.92 * linear;
204
+      vec3 srgb_hi = (1.0 + a) * pow(linear, vec3(1.0/2.4)) - vec3(a);
205
+      return vec3(
206
+          linear.r > b ? srgb_hi.r : srgb_lo.r,
207
+          linear.g > b ? srgb_hi.g : srgb_lo.g,
208
+          linear.b > b ? srgb_hi.b : srgb_lo.b);
209
+  }
177 210
 
178
-  shaderProgram.colourAttribute =
179
-    gl.getAttribLocation(shaderProgram, 'aVertexColor');
180
-  gl.enableVertexAttribArray(shaderProgram.colourAttribute);
211
+  // https:twitter.com/jimhejl/status/633777619998130176
212
+  vec3 ToneMapFilmic_Hejl2015(vec3 hdr, float whitePt) {
213
+      vec4 vh = vec4(hdr, whitePt);
214
+      vec4 va = 1.425 * vh + 0.05;
215
+      vec4 vf = (vh * va + 0.004) / (vh * (va + 0.55) + 0.0491) - 0.0821;
216
+      return vf.rgb / vf.www;
217
+  }
181 218
 
182
-  shaderProgram.textureAttribute =
183
-    gl.getAttribLocation(shaderProgram, 'aVertexTexture');
184
-  gl.enableVertexAttribArray(shaderProgram.textureAttribute);
219
+  void main()
220
+  {
221
+     vec4 col = texture2D( uSampler, vTextureCoord );
222
+     gl_FragColor = vec4(linear_to_srgb(col.rgb), 1.0);
223
+  }
224
+  `;
185 225
 
186
-  shaderProgram.pMatrixUniform =
187
-    gl.getUniformLocation(shaderProgram, 'uPMatrix');
188
-  shaderProgram.mvMatrixUniform =
189
-    gl.getUniformLocation(shaderProgram, 'uMVMatrix');
226
+  const vertexSrc = `
227
+  attribute vec2 aVertexPosition;
228
+  attribute vec2 aVertexTexture;
190 229
 
191
-  return shaderProgram;
230
+  uniform mat4 uMVMatrix;
231
+  uniform mat4 uPMatrix;
232
+
233
+  varying highp vec2 vTextureCoord;
234
+
235
+  void main(void) {
236
+    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 0.0, 1.0);
237
+    vTextureCoord = aVertexTexture;
238
+  }
239
+  `;
240
+
241
+  const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc);
242
+  const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc);
243
+
244
+  gl.attachShader(shader.program, vertexShader);
245
+  gl.attachShader(shader.program, fragmentShader);
246
+
247
+  gl.linkProgram(shader.program);
248
+
249
+  if (!gl.getProgramParameter(shader.program, gl.LINK_STATUS)) {
250
+    let lastError = gl.getProgramInfoLog(shader.program);
251
+
252
+    alert(`Could not initialise shaders: ${lastError}`);;
253
+    gl.deleteProgram(shader.program);
254
+    return null;
255
+  }
256
+
257
+  shader.positionAttribute = gl.getAttribLocation(shader.program, 'aVertexPosition');
258
+  shader.textureAttribute = gl.getAttribLocation(shader.program, 'aVertexTexture');
259
+
260
+  shader.pMatrixUniform = gl.getUniformLocation(shader.program, 'uPMatrix');
261
+  shader.mvMatrixUniform = gl.getUniformLocation(shader.program, 'uMVMatrix');
262
+  shader.textureUniform  = gl.getUniformLocation(shader.program, 'uSampler');
263
+
264
+  return shader;
192 265
 }
193 266
 
194 267
 function setupGLState(gl) {
@@ -205,7 +278,7 @@ function setupGLState(gl) {
205 278
 }
206 279
 
207 280
 
208
-function handleTextureLoaded(gl, image, texture, shaderProgram) {
281
+function handleTextureLoaded(gl, image, texture) {
209 282
   gl.bindTexture(gl.TEXTURE_2D, texture);
210 283
   gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
211 284
   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
@@ -217,9 +290,70 @@ function handleTextureLoaded(gl, image, texture, shaderProgram) {
217 290
 
218 291
   gl.activeTexture(gl.TEXTURE0);
219 292
   gl.bindTexture(gl.TEXTURE_2D, texture);
220
-  gl.uniform1i(gl.getUniformLocation(shaderProgram, 'uSampler'), 0);
221 293
 }
222 294
 
295
+function createRenderTexture(gl, config) {
296
+  // create to render to
297
+  const targetTextureWidth = config.render_texture_width;
298
+  const targetTextureHeight = config.render_texture_height;
299
+
300
+  const targetTexture = gl.createTexture();
301
+  gl.bindTexture(gl.TEXTURE_2D, targetTexture);
302
+
303
+  {
304
+    // define size and format of level 0
305
+    const level = 0;
306
+    const internalFormat = gl.RGBA;
307
+    const border = 0;
308
+    const format = gl.RGBA;
309
+    const type = gl.UNSIGNED_BYTE;
310
+    const data = null;
311
+    gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
312
+                  targetTextureWidth, targetTextureHeight, border,
313
+                  format, type, data);
314
+
315
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
316
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
317
+    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
318
+    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
319
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
320
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
321
+  }
322
+
323
+  return targetTexture;
324
+}
325
+
326
+function createFrameBuffer(gl, targetTexture) {
327
+  // Create and bind the framebuffer
328
+  const fb = gl.createFramebuffer();
329
+  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
330
+
331
+  // attach the texture as the first color attachment
332
+  const attachmentPoint = gl.COLOR_ATTACHMENT0;
333
+  const level = 0;
334
+  gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);
335
+
336
+  return fb;
337
+}
338
+
339
+function checkFramebufferStatus(gl) {
340
+  let res = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
341
+  switch(res) {
342
+  case gl.FRAMEBUFFER_COMPLETE: console.log("gl.FRAMEBUFFER_COMPLETE"); break;
343
+  case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT: console.log("gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); break;
344
+  case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: console.log("gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); break;
345
+  case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS: console.log("gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS"); break;
346
+  case gl.FRAMEBUFFER_UNSUPPORTED: console.log("gl.FRAMEBUFFER_UNSUPPORTED"); break;
347
+  case gl.FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: console.log("gl.FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"); break;
348
+  case gl.RENDERBUFFER_SAMPLES: console.log("gl.RENDERBUFFER_SAMPLE"); break;
349
+  }
350
+}
351
+
352
+const gConfig = {
353
+  render_texture_width: 1024,
354
+  render_texture_height: 1024,
355
+};
356
+
223 357
 class GLRenderer {
224 358
   constructor(canvasElement) {
225 359
     this.glDomElement = canvasElement;
@@ -228,7 +362,9 @@ class GLRenderer {
228 362
     const gl = initGL(this.glDomElement);
229 363
     this.gl = gl;
230 364
 
231
-    this.shaderProgram = setupShaders(gl);
365
+    this.pieceShader = setupPieceShaders(gl);
366
+    this.blitShader = setupBlitShaders(gl);
367
+
232 368
     setupGLState(gl);
233 369
 
234 370
     this.glVertexBuffer = gl.createBuffer();
@@ -237,7 +373,12 @@ class GLRenderer {
237 373
 
238 374
     this.mvMatrix = Matrix.create();
239 375
     this.pMatrix = Matrix.create();
240
-    Matrix.ortho(this.pMatrix, 0, 1000, 0, 1000, 10, -10);
376
+
377
+    this.renderTexture = createRenderTexture(gl, gConfig);
378
+    this.framebuffer = createFrameBuffer(gl, this.renderTexture);
379
+
380
+    // render to the canvas
381
+    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
241 382
   }
242 383
 
243 384
   loadTexture(src) {
@@ -250,7 +391,7 @@ class GLRenderer {
250 391
       const image = new Image();
251 392
 
252 393
       image.addEventListener('load', () => {
253
-        handleTextureLoaded(that.gl, image, that.texture, that.shaderProgram);
394
+        handleTextureLoaded(that.gl, image, that.texture);
254 395
         resolve();
255 396
       });
256 397
 
@@ -262,59 +403,28 @@ class GLRenderer {
262 403
     });
263 404
   }
264 405
 
265
-  copyImageDataTo(elem) {
266
-    return new Promise((resolve, reject) => {
267
-      try {
268
-        this.glDomElement.toBlob(blob => {
269
-          elem.src = window.URL.createObjectURL(blob);
270
-          return resolve();
271
-        });
272
-      } catch (error) {
273
-        return reject(error);
274
-      }
275
-    });
276
-  }
277 406
 
278
-  localDownload(filename) {
279
-    this.glDomElement.toBlob(function(blob) {
407
+  renderGeometryToTexture(destTextureWidth, destTextureHeight, memoryF32, buffers, section) {
408
+    const gl = this.gl;
280 409
 
281
-      const url = window.URL.createObjectURL(blob);
410
+    let shader = this.pieceShader;
282 411
 
283
-      let element = document.createElement('a');
284
-      element.setAttribute('href', url);
285
-      // element.setAttribute('target', '_blank');
286
-      element.setAttribute('download', filename);
412
+    // render to texture attached to framebuffer
287 413
 
288
-      element.style.display = 'none';
289
-      document.body.appendChild(element);
414
+    gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
415
+    //gl.bindFramebuffer(gl.FRAMEBUFFER, null);
416
+    gl.bindTexture(gl.TEXTURE_2D, this.texture);
417
+    gl.viewport(0, 0, destTextureWidth, destTextureHeight);
290 418
 
291
-      element.click();
419
+    gl.useProgram(shader.program);
292 420
 
293
-      document.body.removeChild(element);
421
+    gl.enableVertexAttribArray(shader.positionAttribute);
422
+    gl.enableVertexAttribArray(shader.colourAttribute);
423
+    gl.enableVertexAttribArray(shader.textureAttribute);
294 424
 
295
-      downloadDialogHide();
296
-    });
297
-  }
298
-
299
-  preDrawScene(destWidth, destHeight, section) {
300
-    const gl = this.gl;
301
-    const domElement = this.glDomElement;
302
-
303
-    if (domElement.width !== destWidth) {
304
-      //log('GL width from', domElement.width, 'to', destWidth);
305
-      domElement.width = destWidth;
306
-    }
307
-    if (this.glDomElement.height !== destHeight) {
308
-      //log('GL height from', domElement.height, 'to', destHeight);
309
-      domElement.height = destHeight;
310
-    }
311
-    // gl.drawingBufferWidth, gl.drawingBufferHeight hold the actual
312
-    // size of the rendering element
313
-
314
-    gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
425
+    // gl.clearColor(0.0, 0.0, 1.0, 1.0);
315 426
     gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
316 427
 
317
-
318 428
     if (section === undefined) {
319 429
       // render the entirety of the scene
320 430
       Matrix.ortho(this.pMatrix, 0, 1000, 0, 1000, 10, -10);
@@ -331,18 +441,16 @@ class GLRenderer {
331 441
       }
332 442
     }
333 443
 
334
-    gl.uniformMatrix4fv(this.shaderProgram.pMatrixUniform,
444
+    gl.uniformMatrix4fv(shader.pMatrixUniform,
335 445
                         false,
336 446
                         this.pMatrix);
337 447
 
338
-    gl.uniformMatrix4fv(this.shaderProgram.mvMatrixUniform,
448
+    gl.uniformMatrix4fv(shader.mvMatrixUniform,
339 449
                         false,
340 450
                         this.mvMatrix);
341
-  }
342 451
 
343
-  drawBuffer(memory, buffer) {
344
-    const gl = this.gl;
345
-    const shaderProgram = this.shaderProgram;
452
+    gl.uniform1i(shader.textureUniform, 0);
453
+
346 454
 
347 455
     const glVertexBuffer = this.glVertexBuffer;
348 456
     const glColourBuffer = this.glColourBuffer;
@@ -353,37 +461,156 @@ class GLRenderer {
353 461
     const vertexItemSize = 2;
354 462
     const colourItemSize = 4;
355 463
     const textureItemSize = 2;
356
-
357 464
     const totalSize = (vertexItemSize + colourItemSize + textureItemSize);
358 465
 
359
-    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray#Syntax
360
-    // a new typed array view is created that views the specified ArrayBuffer
361
-    const gbuf = new Float32Array(memory, buffer.geo_ptr, buffer.geo_len);
466
+
467
+    buffers.forEach(buffer => {
468
+      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray#Syntax
469
+      // a new typed array view is created that views the specified ArrayBuffer
470
+      const gbuf = new Float32Array(memoryF32, buffer.geo_ptr, buffer.geo_len);
471
+
472
+      //const gbuf = memorySubArray(memoryF32, geo_ptr, geo_len);
473
+
474
+      gl.bindBuffer(gl.ARRAY_BUFFER, glVertexBuffer);
475
+      gl.bufferData(gl.ARRAY_BUFFER, gbuf, gl.STATIC_DRAW);
476
+      gl.vertexAttribPointer(shader.positionAttribute,
477
+                             vertexItemSize,
478
+                             gl.FLOAT, false, totalSize * bytesin32bit,
479
+                             0);
480
+
481
+      gl.bindBuffer(gl.ARRAY_BUFFER, glColourBuffer);
482
+      gl.bufferData(gl.ARRAY_BUFFER, gbuf, gl.STATIC_DRAW);
483
+      gl.vertexAttribPointer(shader.colourAttribute,
484
+                             colourItemSize,
485
+                             gl.FLOAT, false, totalSize * bytesin32bit,
486
+                             vertexItemSize * bytesin32bit);
487
+
488
+      gl.bindBuffer(gl.ARRAY_BUFFER, glTextureBuffer);
489
+      gl.bufferData(gl.ARRAY_BUFFER, gbuf, gl.STATIC_DRAW);
490
+      gl.vertexAttribPointer(shader.textureAttribute,
491
+                             textureItemSize,
492
+                             gl.FLOAT, false, totalSize * bytesin32bit,
493
+                             (vertexItemSize + colourItemSize) * bytesin32bit);
494
+
495
+      gl.drawArrays(gl.TRIANGLE_STRIP, 0, buffer.geo_len / totalSize);
496
+
497
+    });
498
+
499
+  }
500
+
501
+  renderTextureToScreen(canvasWidth, canvasHeight) {
502
+    const gl = this.gl;
503
+    const domElement = this.glDomElement;
504
+
505
+
506
+    if (domElement.width !== canvasWidth) {
507
+      domElement.width = canvasWidth;
508
+    }
509
+    if (domElement.height !== canvasHeight) {
510
+      domElement.height = canvasHeight;
511
+    }
512
+
513
+    let shader = this.blitShader;
514
+
515
+    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
516
+    gl.bindTexture(gl.TEXTURE_2D, this.renderTexture);
517
+    gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
518
+
519
+    gl.useProgram(shader.program);
520
+
521
+    // console.log(shader);
522
+    gl.enableVertexAttribArray(shader.positionAttribute);
523
+    gl.enableVertexAttribArray(shader.textureAttribute);
524
+
525
+    // gl.clearColor(0.0, 0.0, 1.0, 1.0);
526
+    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
527
+
528
+    // render the entirety of the scene
529
+    Matrix.ortho(this.pMatrix, 0, canvasWidth, 0, canvasHeight, 10, -10);
530
+
531
+    // add some uniforms for canvas width and height
532
+
533
+    gl.uniformMatrix4fv(shader.pMatrixUniform,
534
+                        false,
535
+                        this.pMatrix);
536
+
537
+    gl.uniformMatrix4fv(shader.mvMatrixUniform,
538
+                        false,
539
+                        this.mvMatrix);
540
+
541
+    gl.uniform1i(shader.textureUniform, 0);
542
+
543
+    const glVertexBuffer = this.glVertexBuffer;
544
+    const glColourBuffer = this.glColourBuffer;
545
+    const glTextureBuffer = this.glTextureBuffer;
546
+
547
+    // x, y, u, v
548
+    const jsData = [
549
+      0.0, 0.0, 0.0, 0.0,
550
+      canvasWidth, 0.0, 1.0, 0.0,
551
+      0.0, canvasHeight, 0.0, 1.0,
552
+      canvasWidth, canvasHeight, 1.0, 1.0
553
+    ];
554
+    const data = Float32Array.from(jsData);
555
+
556
+
557
+    const bytesin32bit = 4;
558
+
559
+    const vertexItemSize = 2;
560
+    const textureItemSize = 2;
561
+    const totalSize = (vertexItemSize + textureItemSize);
362 562
 
363 563
     gl.bindBuffer(gl.ARRAY_BUFFER, glVertexBuffer);
364
-    gl.bufferData(gl.ARRAY_BUFFER, gbuf, gl.STATIC_DRAW);
365
-    gl.vertexAttribPointer(shaderProgram.positionAttribute,
564
+    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
565
+    gl.vertexAttribPointer(shader.positionAttribute,
366 566
                            vertexItemSize,
367
-                           gl.FLOAT, false, totalSize * bytesin32bit,
368
-                           0 * bytesin32bit);
369
-
370
-    gl.bindBuffer(gl.ARRAY_BUFFER, glColourBuffer);
371
-    gl.bufferData(gl.ARRAY_BUFFER, gbuf, gl.STATIC_DRAW);
372
-    gl.vertexAttribPointer(shaderProgram.colourAttribute,
373
-                           colourItemSize,
374
-                           gl.FLOAT, false, totalSize * bytesin32bit,
375
-                           vertexItemSize * bytesin32bit);
567
+                           gl.FLOAT, false, totalSize * 4,
568
+                           0);
376 569
 
377 570
     gl.bindBuffer(gl.ARRAY_BUFFER, glTextureBuffer);
378
-    gl.bufferData(gl.ARRAY_BUFFER, gbuf, gl.STATIC_DRAW);
379
-    gl.vertexAttribPointer(shaderProgram.textureAttribute,
571
+    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
572
+    gl.vertexAttribPointer(shader.textureAttribute,
380 573
                            textureItemSize,
381
-                           gl.FLOAT, false, totalSize * bytesin32bit,
382
-                           (vertexItemSize + colourItemSize) * bytesin32bit);
574
+                           gl.FLOAT, false, totalSize * 4,
575
+                           (vertexItemSize) * 4);
576
+
577
+    gl.drawArrays(gl.TRIANGLE_STRIP, 0, jsData.length / totalSize);
578
+  }
579
+
580
+  copyImageDataTo(elem) {
581
+    return new Promise((resolve, reject) => {
582
+      try {
583
+        this.glDomElement.toBlob(blob => {
584
+          elem.src = window.URL.createObjectURL(blob);
585
+          return resolve();
586
+        });
587
+      } catch (error) {
588
+        return reject(error);
589
+      }
590
+    });
591
+  }
383 592
 
384
-    gl.drawArrays(gl.TRIANGLE_STRIP, 0, buffer.geo_len / totalSize);
593
+  localDownload(filename) {
594
+    this.glDomElement.toBlob(function(blob) {
595
+
596
+      const url = window.URL.createObjectURL(blob);
597
+
598
+      let element = document.createElement('a');
599
+      element.setAttribute('href', url);
600
+      // element.setAttribute('target', '_blank');
601
+      element.setAttribute('download', filename);
602
+
603
+      element.style.display = 'none';
604
+      document.body.appendChild(element);
385 605
 
606
+      element.click();
607
+
608
+      document.body.removeChild(element);
609
+
610
+      downloadDialogHide();
611
+    });
386 612
   }
613
+
387 614
 }
388 615
 
389 616
 // --------------------------------------------------------------------------------
@@ -1447,11 +1674,8 @@ async function renderGeometryBuffers(memory, buffers, imageElement, w, h) {
1447 1674
 
1448 1675
   const stopFn = startTiming();
1449 1676
 
1450
-  gGLRenderer.preDrawScene(destWidth, destHeight);
1451
-
1452
-  buffers.forEach(buffer => {
1453
-    gGLRenderer.drawBuffer(memory, buffer);
1454
-  });
1677
+  gGLRenderer.renderGeometryToTexture(gConfig.render_texture_width, gConfig.render_texture_height, memory, buffers);
1678
+  gGLRenderer.renderTextureToScreen(destWidth, destHeight);
1455 1679
 
1456 1680
   await gGLRenderer.copyImageDataTo(imageElement);
1457 1681
 
@@ -1471,11 +1695,8 @@ async function renderGeometryBuffersSection(memory, buffers, imageElement, w, h,
1471 1695
 
1472 1696
   const stopFn = startTiming();
1473 1697
 
1474
-  gGLRenderer.preDrawScene(destWidth, destHeight, section);
1475
-
1476
-  buffers.forEach(buffer => {
1477
-    gGLRenderer.drawBuffer(memory, buffer);
1478
-  });
1698
+  gGLRenderer.renderGeometryToTexture(gConfig.render_texture_width, gConfig.render_texture_height, memory, buffers, section);
1699
+  gGLRenderer.renderTextureToScreen(destWidth, destHeight);
1479 1700
 
1480 1701
   await gGLRenderer.copyImageDataTo(imageElement);
1481 1702
 

+ 8
- 8
core/src/bitmap.rs View File

@@ -42,10 +42,10 @@ fn invoke_function(
42 42
     let fn_info = &program.fn_info[fun];
43 43
     let colour = Colour::new(
44 44
         ColourFormat::Rgb,
45
-        f32::from(bitmap_info.data[index]) / 255.0,
46
-        f32::from(bitmap_info.data[index + 1]) / 255.0,
47
-        f32::from(bitmap_info.data[index + 2]) / 255.0,
48
-        f32::from(bitmap_info.data[index + 3]) / 255.0,
45
+        bitmap_info.data[index],
46
+        bitmap_info.data[index + 1],
47
+        bitmap_info.data[index + 2],
48
+        bitmap_info.data[index + 3],
49 49
     );
50 50
 
51 51
     vm.function_call_default_arguments(context, program, fn_info)?;
@@ -209,10 +209,10 @@ pub fn value(
209 209
 
210 210
     let colour = Colour::new(
211 211
         ColourFormat::Rgb,
212
-        f32::from(bitmap_info.data[index]) / 255.0,
213
-        f32::from(bitmap_info.data[index + 1]) / 255.0,
214
-        f32::from(bitmap_info.data[index + 2]) / 255.0,
215
-        f32::from(bitmap_info.data[index + 3]) / 255.0,
212
+        bitmap_info.data[index],
213
+        bitmap_info.data[index + 1],
214
+        bitmap_info.data[index + 2],
215
+        bitmap_info.data[index + 3],
216 216
     );
217 217
 
218 218
     Ok(colour)

+ 24
- 1
core/src/bitmap_cache.rs View File

@@ -62,9 +62,32 @@ impl BitmapCache {
62 62
     }
63 63
 }
64 64
 
65
+// given pixel data as u8 in sRGB colour space
66
+// this converts to in internal linear colour space representation in f32 (range 0..1)
65 67
 #[derive(Default)]
66 68
 pub struct BitmapInfo {
67 69
     pub width: usize,
68 70
     pub height: usize,
69
-    pub data: Vec<u8>,
71
+    pub data: Vec<f32>,
72
+}
73
+
74
+// https://en.wikipedia.org/wiki/SRGB
75
+fn into_linear_space(c: u8) -> f32 {
76
+    let v = f32::from(c) / 255.0;
77
+
78
+    if v > 0.04045 {
79
+        ((v + 0.055) / 1.055).powf(2.4)
80
+    } else {
81
+        v / 12.92
82
+    }
83
+}
84
+
85
+impl BitmapInfo {
86
+    pub fn new(width: usize, height: usize, data: Vec<u8>) -> Self {
87
+        BitmapInfo {
88
+            width,
89
+            height,
90
+            data: data.into_iter().map(into_linear_space).collect(),
91
+        }
92
+    }
70 93
 }

+ 35
- 1
gui/assets/shaders/blit.frag View File

@@ -9,7 +9,41 @@ uniform sampler2D myTextureSampler;
9 9
 
10 10
 out vec4 Colour;
11 11
 
12
+
13
+vec3 linear_to_srgb(vec3 linear) {
14
+    float a = 0.055;
15
+    float b = 0.0031308;
16
+    vec3 srgb_lo = 12.92 * linear;
17
+    vec3 srgb_hi = (1.0 + a) * pow(linear, vec3(1.0/2.4)) - vec3(a);
18
+    return vec3(
19
+        linear.r > b ? srgb_hi.r : srgb_lo.r,
20
+        linear.g > b ? srgb_hi.g : srgb_lo.g,
21
+        linear.b > b ? srgb_hi.b : srgb_lo.b);
22
+}
23
+
24
+// https://twitter.com/jimhejl/status/633777619998130176
25
+vec3 ToneMapFilmic_Hejl2015(vec3 hdr, float whitePt) {
26
+    vec4 vh = vec4(hdr, whitePt);
27
+    vec4 va = 1.425 * vh + 0.05;
28
+    vec4 vf = (vh * va + 0.004) / (vh * (va + 0.55) + 0.0491) - 0.0821;
29
+    return vf.rgb / vf.www;
30
+}
31
+
32
+
33
+
12 34
 void main()
13 35
 {
14
-  Colour = texture( myTextureSampler, IN.TextureCoord );
36
+  // Colour = texture( myTextureSampler, IN.TextureCoord );
37
+
38
+  // Colour = texture( myTextureSampler, IN.TextureCoord );
39
+  // Colour = vec4(clamp(Colour.r, 0.0, 1.0), clamp(Colour.g, 0.0, 1.0), clamp(Colour.b, 0.0, 1.0), 1.0);
40
+
41
+  // vec4 pieceColour = texture( myTextureSampler, IN.TextureCoord );
42
+  // Colour = vec4(linear_to_srgb(ToneMapFilmic_Hejl2015(pieceColour.rgb, 1.0)), 1.0);
43
+
44
+  vec4 pieceColour = texture( myTextureSampler, IN.TextureCoord );
45
+  Colour = vec4(linear_to_srgb(pieceColour.rgb), 1.0);
46
+
47
+  // vec4 pieceColour = texture( myTextureSampler, IN.TextureCoord );
48
+  // Colour = vec4(ToneMapFilmic_Hejl2015(pieceColour.rgb, 1.0), 1.0);
15 49
 }

+ 11
- 3
gui/src/gl_util.rs View File

@@ -17,13 +17,21 @@ use std::mem;
17 17
 
18 18
 use std::path::Path;
19 19
 
20
-use core::BitmapInfo;
21 20
 use gl;
22 21
 use image::GenericImageView;
23 22
 use log::info;
24 23
 
25 24
 use crate::error::{Error, Result};
26 25
 
26
+
27
+
28
+#[derive(Default)]
29
+pub struct BitmapU8Info {
30
+    pub width: usize,
31
+    pub height: usize,
32
+    pub data: Vec<u8>,
33
+}
34
+
27 35
 #[macro_export]
28 36
 macro_rules! c_str {
29 37
     ($literal:expr) => {
@@ -40,7 +48,7 @@ where
40 48
     val
41 49
 }
42 50
 
43
-pub fn load_texture(ppath: &Path, name: &str) -> Result<BitmapInfo> {
51
+pub fn load_texture(ppath: &Path, name: &str) -> Result<BitmapU8Info> {
44 52
     let path = ppath.join(name);
45 53
 
46 54
     info!("load_bitmap: {:?}", path);
@@ -71,7 +79,7 @@ pub fn load_texture(ppath: &Path, name: &str) -> Result<BitmapInfo> {
71 79
         }
72 80
     }
73 81
 
74
-    let bitmap_info = BitmapInfo {
82
+    let bitmap_info = BitmapU8Info {
75 83
         width,
76 84
         height,
77 85
         data: data_flipped,

+ 18
- 18
gui/src/main.rs View File

@@ -381,26 +381,26 @@ fn run(config: &config::Config) -> Result<()> {
381 381
         // ui.show_demo_window(&mut true);
382 382
 
383 383
         // ~/repos/rust/imgui-rs/imgui-glium-examples/examples/test_window_impl.rs
384
-        ui.window(im_str!("Seni"))
385
-            .position((0.0, 0.0), imgui::ImGuiCond::FirstUseEver)
386
-            .size((800.0, 600.0), imgui::ImGuiCond::FirstUseEver)
387
-            .build(|| {
388
-                if ui.button(im_str!("Load.."), (0.0, 0.0)) {
389
-                    ui.open_popup(im_str!("modal"));
390
-                }
384
+        // ui.window(im_str!("Seni"))
385
+        //     .position((0.0, 0.0), imgui::ImGuiCond::FirstUseEver)
386
+        //     .size((800.0, 600.0), imgui::ImGuiCond::FirstUseEver)
387
+        //     .build(|| {
388
+        //         if ui.button(im_str!("Load.."), (0.0, 0.0)) {
389
+        //             ui.open_popup(im_str!("modal"));
390
+        //         }
391 391
 
392
-                ui.popup_modal(im_str!("modal")).build(|| {
393
-                    ui.text("Content of my modal");
394
-                    if ui.button(im_str!("OK"), (0.0, 0.0)) {
395
-                        ui.close_current_popup();
396
-                    }
397
-                });
392
+        //         ui.popup_modal(im_str!("modal")).build(|| {
393
+        //             ui.text("Content of my modal");
394
+        //             if ui.button(im_str!("OK"), (0.0, 0.0)) {
395
+        //                 ui.close_current_popup();
396
+        //             }
397
+        //         });
398 398
 
399
-                ui.separator();
400
-                if ui.collapsing_header(im_str!("script")).build() {
401
-                    ui.text(im_str!("{}", seni_source));
402
-                }
403
-            });
399
+        //         ui.separator();
400
+        //         if ui.collapsing_header(im_str!("script")).build() {
401
+        //             ui.text(im_str!("{}", seni_source));
402
+        //         }
403
+        //     });
404 404
 
405 405
         unsafe {
406 406
             gl.Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);

+ 1
- 6
gui/src/seni.rs View File

@@ -48,12 +48,7 @@ fn load_bitmap(asset_prefix: &String, filename: &String, context: &mut Context)
48 48
         data.push(rgba.data[3]);
49 49
     }
50 50
 
51
-    let bitmap_info = BitmapInfo {
52
-        width,
53
-        height,
54
-        data,
55
-        ..Default::default()
56
-    };
51
+    let bitmap_info = BitmapInfo::new(width, height, data);
57 52
 
58 53
     context.bitmap_cache.insert(&filename, bitmap_info)?;
59 54
 

+ 67
- 0
server/static/seni/sketch/1925-einstein.seni View File

@@ -0,0 +1,67 @@
1
+(define texture "einstein.png")
2
+(define per-pixel-funcs [(address-of pass-0)
3
+                         (address-of pass-1)
4
+                         (address-of pass-2)
5
+                         (address-of pass-3)])
6
+(define rng (prng/build seed: {343 (gen/scalar min: 42 max: 666)}
7
+                        min: 0
8
+                        max: 1))
9
+
10
+(each (per-pixel-func from: per-pixel-funcs)
11
+      (bitmap/each from: texture
12
+                   position: [500 500]
13
+                   width: 1000
14
+                   height: 1000
15
+                   fn: per-pixel-func))
16
+
17
+(fn (pass-0 colour: (col/rgb r: 0 g: 0 b: 0 alpha: 0) position: [100 100])
18
+  (per-pixel a: (col/get-r colour: colour)
19
+             alpha: 0.2
20
+             width: 1.0
21
+             from: [0 0]
22
+             to: [1 1]))
23
+
24
+(fn (pass-1 colour: (col/rgb r: 0 g: 0 b: 0 alpha: 0) position: [100 100])
25
+  (per-pixel a: (col/get-r colour: colour)
26
+             alpha: 0.1
27
+             width: {14.3 (gen/scalar min: 0.1 max: 15)}
28
+             from: [{10 (gen/scalar min: -15 max: 15)}
29
+                    {5 (gen/scalar min: -15 max: 15)}]
30
+             to: [{-9 (gen/scalar min: -15 max: 15)}
31
+                  {-1 (gen/scalar min: -15 max: 15)}]))
32
+
33
+(fn (pass-2 colour: (col/rgb r: 0 g: 0 b: 0 alpha: 0) position: [100 100])
34
+  (per-pixel a: (col/get-r colour: colour)
35
+             alpha: 0.05
36
+             width: {2.4 (gen/scalar min: 0.1 max: 15)}
37
+             from: [{15 (gen/scalar min: -15 max: 15)}
38
+                    {-5 (gen/scalar min: -15 max: 15)}]
39
+             to: [{0 (gen/scalar min: -15 max: 15)}
40
+                  {-2 (gen/scalar min: -15 max: 15)}]))
41
+
42
+(fn (pass-3 colour: (col/rgb r: 0 g: 0 b: 0 alpha: 0) position: [100 100])
43
+  (per-pixel a: (col/get-r colour: colour)
44
+             alpha: 0.025
45
+             width: {4.8 (gen/scalar min: 0.1 max: 15)}
46
+             from: [{-5 (gen/scalar min: -15 max: 15)}
47
+                    {-2 (gen/scalar min: -15 max: 15)}]
48
+             to: [{-7 (gen/scalar min: -15 max: 15)}
49
+                  {-3 (gen/scalar min: -15 max: 15)}]))
50
+
51
+(fn (per-pixel a: 0 alpha: 1 width: 0 from: [0 0] to: [0 0])
52
+  (line from: [(* (nth from: from n: 0) (math/cos angle: (* a math/PI)))
53
+               (* (nth from: from n: 1) (math/sin angle: (* a math/PI)))]
54
+        to: [(* (nth from: to n: 0) (math/cos angle: (* a math/PI)))
55
+             (* (nth from: to n: 1) (math/sin angle: (* a math/PI)))]
56
+        width: (+ 0 (- width a))
57
+        brush: brush/b
58
+        colour: (col/rgb r: a g: a b: a alpha: alpha)))
59
+
60
+(fn (per-pixel-rng a: 0 alpha: 1 width: 0 from: [0 0] to: [0 0])
61
+  (line from: [(* (prng/value from: rng) (nth from: from n: 0) (math/cos angle: (* a math/PI)))
62
+               (* (prng/value from: rng) (nth from: from n: 1) (math/sin angle: (* a math/PI)))]
63
+        to: [(* (prng/value from: rng) (nth from: to n: 0) (math/cos angle: (* a math/PI)))
64
+             (* (prng/value from: rng) (nth from: to n: 1) (math/sin angle: (* a math/PI)))]
65
+        width: (+ 0 (- width a))
66
+        brush: brush/b
67
+        colour: (col/rgb r: a g: a b: a alpha: alpha)))

+ 67
- 0
server/static/seni/sketch/1925-einstein2.seni View File

@@ -0,0 +1,67 @@
1
+(define texture "einstein.png")
2
+(define per-pixel-funcs [(address-of pass-0)
3
+                         (address-of pass-1)
4
+                         (address-of pass-2)
5
+                         (address-of pass-3)])
6
+(define rng (prng/build seed: {226 (gen/scalar min: 42 max: 666)}
7
+                        min: 0
8
+                        max: 1))
9
+
10
+(each (per-pixel-func from: per-pixel-funcs)
11
+      (bitmap/each from: texture
12
+                   position: [500 500]
13
+                   width: 1000
14
+                   height: 1000
15
+                   fn: per-pixel-func))
16
+
17
+(fn (pass-0 colour: (col/rgb r: 0 g: 0 b: 0 alpha: 0) position: [100 100])
18
+  (per-pixel a: (col/get-r colour: colour)
19
+             alpha: 0.2
20
+             width: 1.0
21
+             from: [0 0]
22
+             to: [1 1]))
23
+
24
+(fn (pass-1 colour: (col/rgb r: 0 g: 0 b: 0 alpha: 0) position: [100 100])
25
+  (per-pixel a: (col/get-r colour: colour)
26
+             alpha: 0.1
27
+             width: {1.5 (gen/scalar min: 0.1 max: 15)}
28
+             from: [{-9 (gen/scalar min: -15 max: 15)}
29
+                    {10 (gen/scalar min: -15 max: 15)}]
30
+             to: [{7 (gen/scalar min: -15 max: 15)}
31
+                  {-14 (gen/scalar min: -15 max: 15)}]))
32
+
33
+(fn (pass-2 colour: (col/rgb r: 0 g: 0 b: 0 alpha: 0) position: [100 100])
34
+  (per-pixel-rng a: (col/get-r colour: colour)
35
+             alpha: 0.05
36
+             width: {2.9 (gen/scalar min: 0.1 max: 15)}
37
+             from: [{12 (gen/scalar min: -15 max: 15)}
38
+                    {-15 (gen/scalar min: -15 max: 15)}]
39
+             to: [{-11 (gen/scalar min: -15 max: 15)}
40
+                  {9 (gen/scalar min: -15 max: 15)}]))
41
+
42
+(fn (pass-3 colour: (col/rgb r: 0 g: 0 b: 0 alpha: 0) position: [100 100])
43
+  (per-pixel-rng a: (col/get-r colour: colour)
44
+             alpha: 0.025
45
+             width: {1.1 (gen/scalar min: 0.1 max: 15)}
46
+             from: [{-4 (gen/scalar min: -15 max: 15)}
47
+                    {-5 (gen/scalar min: -15 max: 15)}]
48
+             to: [{11 (gen/scalar min: -15 max: 15)}
49
+                  {4 (gen/scalar min: -15 max: 15)}]))
50
+
51
+(fn (per-pixel a: 0 alpha: 1 width: 0 from: [0 0] to: [0 0])
52
+  (line from: [(* (nth from: from n: 0) (math/cos angle: (* a math/PI)))
53
+               (* (nth from: from n: 1) (math/sin angle: (* a math/PI)))]
54
+        to: [(* (nth from: to n: 0) (math/cos angle: (* a math/PI)))
55
+             (* (nth from: to n: 1) (math/sin angle: (* a math/PI)))]
56
+        width: (+ 0 (- width a))
57
+        brush: brush/b
58
+        colour: (col/rgb r: a g: a b: a alpha: alpha)))
59
+
60
+(fn (per-pixel-rng a: 0 alpha: 1 width: 0 from: [0 0] to: [0 0])
61
+  (line from: [(* (prng/value from: rng) (nth from: from n: 0) (math/cos angle: (* a math/PI)))
62
+               (* (prng/value from: rng) (nth from: from n: 1) (math/sin angle: (* a math/PI)))]
63
+        to: [(* (prng/value from: rng) (nth from: to n: 0) (math/cos angle: (* a math/PI)))
64
+             (* (prng/value from: rng) (nth from: to n: 1) (math/sin angle: (* a math/PI)))]
65
+        width: (+ 0 (- width a))
66
+        brush: brush/b
67
+        colour: (col/rgb r: a g: a b: a alpha: alpha)))

+ 48
- 0
server/static/seni/sketch/1925-shell.seni View File

@@ -0,0 +1,48 @@
1
+(define
2
+  coords1 [{[-7.987 -70.872] (gen/stray-2d from: [-3.718 -69.162] by: [5 5])}
3
+           {[459.069 -55.678] (gen/stray-2d from: [463.301 -57.804] by: [5 5])}
4
+           {[453.886 -313.444] (gen/stray-2d from: [456.097 -315.570] by: [5 5])}
5
+           {[318.958 -379.619] (gen/stray-2d from: [318.683 -384.297] by: [5 5])}]
6
+
7
+  coords2 [{[422.457 21.162] (gen/stray-2d from: [424.112 19.779] by: [5 5])}
8
+           {[-0.227 248.540] (gen/stray-2d from: [2.641 246.678] by: [5 5])}
9
+           {[-444.511 -82.728] (gen/stray-2d from: [-449.001 -79.842] by: [5 5])}
10
+           {[38.476 209.605] (gen/stray-2d from: [37.301 206.818] by: [5 5])}]
11
+
12
+  coords3 [{[82.956 -286.186] (gen/stray-2d from: [83.331 -282.954] by: [5 5])}
13
+           {[88.479 -396.479] (gen/stray-2d from: [92.716 -393.120] by: [5 5])}
14
+           {[423.226 -421.275] (gen/stray-2d from: [426.686 -420.284] by: [5 5])}
15
+           {[-32.486 338.664] (gen/stray-2d from: [-29.734 335.671] by: [5 5])}]
16
+
17
+  col-fn-1 (col/build-procedural preset: {transformers (gen/select from: col/procedural-fn-presets)}
18
+                                 alpha: 0.08)
19
+  col-fn-2 (col/build-procedural preset: {chrome (gen/select from: col/procedural-fn-presets)}
20
+                                 alpha: 0.08)
21
+  col-fn-3 (col/build-procedural preset: {knight-rider (gen/select from: col/procedural-fn-presets)}
22
+                                 alpha: 0.08)
23
+
24
+  num-copies {25 (gen/int min: 25 max: 30)}
25
+  squish (interp/build from: [0 (- num-copies 1)] to: [{1.2 (gen/scalar min: 1.0 max: 1.5)} {0.79 (gen/scalar min: 0.3 max: 1)}]))
26
+
27
+(fn (draw angle: 0 copy: 0)
28
+  (scale vector: [(interp/value from: squish t: copy) (interp/value from: squish t: copy)])
29
+  (fence (t num: 200)
30
+    (poly coords: [(interp/bezier t: t coords: coords1)
31
+                   (interp/bezier t: t coords: coords2)
32
+                   (interp/bezier t: t coords: coords3)]
33
+          colours: [(col/value from: col-fn-1 t: t)
34
+                    (col/value from: col-fn-2 t: t)
35
+                    (col/value from: col-fn-3 t: t)])))
36
+
37
+(fn (render)
38
+  (rect position: [500 500]
39
+        width: 1000
40
+        height: 1000
41
+        colour: (col/value from: col-fn-1 t: 0.5))
42
+  (on-matrix-stack
43
+    (translate vector: [(/ canvas/width 2) (/ canvas/height 2)])
44
+    (scale vector: [0.8 0.8])
45
+    (repeat/rotate-mirrored fn: (address-of draw)
46
+                            copies: num-copies)))
47
+
48
+(render)

Loading…
Cancel
Save