@chromatic-coherence/generative-engine 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/world.d.ts CHANGED
@@ -251,7 +251,14 @@ export declare class World {
251
251
  private drawPts;
252
252
  private drawRibbons;
253
253
  private renderFrame;
254
+ private frameFn;
255
+ private pausedByUser;
254
256
  start(): void;
257
+ /** Freeze the frame loop, keeping every GPU resource warm — the cheap way to park an
258
+ * off-screen world. resume() picks the motion back up instantly; nothing rebuilds. */
259
+ pause(): void;
260
+ /** Wake a pause()d world — the loop continues from where the clock left off. */
261
+ resume(): void;
255
262
  /** Render one deterministic frame at time t (no loop) — stills, tests, screenshots. */
256
263
  settle(t: number): void;
257
264
  /** Stop the world. `releaseContext: true` also LOSES the GL context immediately —
package/dist/world.js CHANGED
@@ -109,6 +109,8 @@ export class World {
109
109
  this.zNear = 24;
110
110
  this.zFar = 6000;
111
111
  this.inkCss = new Map();
112
+ this.frameFn = null;
113
+ this.pausedByUser = false;
112
114
  this.canvas = o.canvas;
113
115
  const gl = o.canvas.getContext("webgl2", { alpha: true, antialias: false, premultipliedAlpha: true });
114
116
  if (!gl)
@@ -161,17 +163,23 @@ export class World {
161
163
  this.shadowSoft = o.shadows.softness ?? 1.6;
162
164
  this.shadowBias = o.shadows.bias ?? 0.0035;
163
165
  }
164
- if (this.shadowsOn) {
165
- // a REAL depth texture with hardware comparison LINEAR gives free 2x2 PCF per tap
166
+ {
167
+ // the shadow sampler must ALWAYS have a complete depth-comparable texture bound:
168
+ // an unbound sampler2DShadow invalidates the face program on some drivers and
169
+ // every solid silently vanishes. Shadows off => a 1x1 dummy depth texture.
170
+ // (Shadows on: a REAL depth texture — LINEAR gives free 2x2 hardware PCF per tap.)
171
+ const size = this.shadowsOn ? this.shadowSize : 1;
166
172
  this.shadowTex = gl.createTexture();
167
173
  gl.bindTexture(gl.TEXTURE_2D, this.shadowTex);
168
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, this.shadowSize, this.shadowSize, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
174
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, size, size, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
169
175
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
170
176
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
171
177
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
172
178
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
173
179
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE);
174
180
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_COMPARE_FUNC, gl.LEQUAL);
181
+ }
182
+ if (this.shadowsOn) {
175
183
  this.shadowFBO = gl.createFramebuffer();
176
184
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.shadowFBO);
177
185
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this.shadowTex, 0);
@@ -732,9 +740,10 @@ export class World {
732
740
  this.renderFrame(t, dt);
733
741
  this.raf = !document.hidden ? requestAnimationFrame(frame) : 0;
734
742
  };
743
+ this.frameFn = frame;
735
744
  this.raf = requestAnimationFrame(frame);
736
745
  const onVis = () => {
737
- if (!document.hidden && !this.raf && !this.disposed) {
746
+ if (!document.hidden && !this.raf && !this.disposed && !this.pausedByUser) {
738
747
  this.tPrev = performance.now();
739
748
  this.raf = requestAnimationFrame(frame);
740
749
  }
@@ -742,6 +751,25 @@ export class World {
742
751
  document.addEventListener("visibilitychange", onVis);
743
752
  this.cleanup.push(() => document.removeEventListener("visibilitychange", onVis));
744
753
  }
754
+ /** Freeze the frame loop, keeping every GPU resource warm — the cheap way to park an
755
+ * off-screen world. resume() picks the motion back up instantly; nothing rebuilds. */
756
+ pause() {
757
+ this.pausedByUser = true;
758
+ if (this.raf) {
759
+ cancelAnimationFrame(this.raf);
760
+ this.raf = 0;
761
+ }
762
+ }
763
+ /** Wake a pause()d world — the loop continues from where the clock left off. */
764
+ resume() {
765
+ if (this.disposed || !this.pausedByUser)
766
+ return;
767
+ this.pausedByUser = false;
768
+ if (this.frameFn && !this.raf && !document.hidden) {
769
+ this.tPrev = performance.now();
770
+ this.raf = requestAnimationFrame(this.frameFn);
771
+ }
772
+ }
745
773
  /** Render one deterministic frame at time t (no loop) — stills, tests, screenshots. */
746
774
  settle(t) { this.renderFrame(t, 0); }
747
775
  /** Stop the world. `releaseContext: true` also LOSES the GL context immediately —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chromatic-coherence/generative-engine",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "THE THIRD ENGINE — a zero-dependency WebGL2 graphics engine grown from generative-charts-3d: HDR + bloom + SSAO post chain, hardware-PCF shadow maps, a geometry stdlib, ribbon strokes, group transforms and morph targets. The charts API rides on top unchanged.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",