@chromatic-coherence/generative-charts-3d 0.12.1 → 0.13.0
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/index.d.ts +8 -0
- package/dist/index.js +52 -23
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -61,6 +61,11 @@ export type WorldOpts = {
|
|
|
61
61
|
parallax?: number;
|
|
62
62
|
controls?: boolean;
|
|
63
63
|
shadows?: boolean;
|
|
64
|
+
/** v0.13 — THE INK EDITION: theme "light" renders the same recipe on paper instead of night.
|
|
65
|
+
* One rule, applied everywhere: lightness inverts (the dark ground becomes paper, bright
|
|
66
|
+
* light becomes ink), the light pass blends normally instead of additively, and fog fades
|
|
67
|
+
* toward the page instead of toward black. Recipes need no changes. Default "dark". */
|
|
68
|
+
theme?: "dark" | "light";
|
|
64
69
|
camera?: {
|
|
65
70
|
target?: V3;
|
|
66
71
|
dist?: number;
|
|
@@ -104,6 +109,7 @@ export declare class World {
|
|
|
104
109
|
private disposed;
|
|
105
110
|
private cleanup;
|
|
106
111
|
private fLight;
|
|
112
|
+
private ink;
|
|
107
113
|
cam: {
|
|
108
114
|
target: V3;
|
|
109
115
|
dist: number;
|
|
@@ -158,6 +164,8 @@ export declare class World {
|
|
|
158
164
|
constructor(o: WorldOpts);
|
|
159
165
|
private wireControls;
|
|
160
166
|
private resize;
|
|
167
|
+
private col;
|
|
168
|
+
private parseCol;
|
|
161
169
|
private pushFace;
|
|
162
170
|
/** v0.2 — pass per-vertex normals (na, nb, nc) and shading interpolates SMOOTHLY across
|
|
163
171
|
* the face: the facets vanish. Omit them for flat shading, exactly as before. */
|
package/dist/index.js
CHANGED
|
@@ -101,7 +101,7 @@ uniform vec3 uLdir; uniform float uAmb; uniform float uDif; uniform float uSigne
|
|
|
101
101
|
uniform vec3 uLp[4]; uniform float uLi[4]; uniform float uLf[4]; uniform int uLn;
|
|
102
102
|
uniform vec3 uCam; uniform float uFogK; uniform float uSpec;
|
|
103
103
|
uniform vec3 uSkyC; uniform vec3 uGroundC; uniform float uFilm; uniform float uGrade;
|
|
104
|
-
uniform float uShadowOn; uniform sampler2D uShadow; uniform float uPulse;
|
|
104
|
+
uniform float uShadowOn; uniform sampler2D uShadow; uniform float uPulse; uniform float uInk;
|
|
105
105
|
varying vec4 vSh; varying float vGA; varying float vBlood;
|
|
106
106
|
float unpack(vec4 e){ return dot(e, vec4(1., 1./255., 1./65025., 1./16581375.)); }
|
|
107
107
|
float h3(vec3 p){ return fract(sin(dot(p, vec3(12.9898, 78.233, 37.719))) * 43758.5453); }
|
|
@@ -174,7 +174,10 @@ void main(){
|
|
|
174
174
|
float fres = pow(1.0 - clamp(dot(n, V), 0.0, 1.0), 3.0);
|
|
175
175
|
lit += vec3(0.55, 0.09, 0.10) * fres * skin * (0.28 + 0.5 * b);
|
|
176
176
|
float fog = clamp(uFogK / vZ, 0.08, 1.0);
|
|
177
|
-
vec3 outC =
|
|
177
|
+
vec3 outC = lit + vec3(1.0) * spec * uSpec * vCol.a;
|
|
178
|
+
// ink edition: fog fades toward the paper, not toward black (the page shows through)
|
|
179
|
+
if (uInk > 0.5) outC = mix(vec3(0.90, 0.92, 0.95), outC, fog * vGA);
|
|
180
|
+
else outC = outC * fog * vGA;
|
|
178
181
|
if (uFilm > 0.5) { // v0.7 — the film look: highlights roll off instead of clipping
|
|
179
182
|
outC = (outC * (2.51 * outC + 0.03)) / (outC * (2.43 * outC + 0.59) + 0.14);
|
|
180
183
|
}
|
|
@@ -191,8 +194,11 @@ uniform mat4 uVP; uniform float uGA[8];
|
|
|
191
194
|
varying vec4 vCol; varying float vZ; varying float vGA;
|
|
192
195
|
void main(){ vCol=aCol; vGA=uGA[int(aGrp+0.5)]; vec4 p=uVP*vec4(aPos,1.0); vZ=p.w; gl_Position=p; }`;
|
|
193
196
|
const LINE_FS = `
|
|
194
|
-
precision mediump float; varying vec4 vCol; varying float vZ; varying float vGA; uniform float uFogK;
|
|
195
|
-
void main(){ float fog = clamp(uFogK / vZ, 0.08, 1.0);
|
|
197
|
+
precision mediump float; varying vec4 vCol; varying float vZ; varying float vGA; uniform float uFogK; uniform float uInk;
|
|
198
|
+
void main(){ float fog = clamp(uFogK / vZ, 0.08, 1.0);
|
|
199
|
+
// ink edition: colour holds, alpha fades — the line washes toward the paper behind it
|
|
200
|
+
if (uInk > 0.5) gl_FragColor = vec4(vCol.rgb, vCol.a * fog * vGA);
|
|
201
|
+
else gl_FragColor = vec4(vCol.rgb * fog, vCol.a * fog * vGA); }`;
|
|
196
202
|
const PT_VS = `
|
|
197
203
|
attribute vec3 aPos; attribute float aSize; attribute vec4 aCol; attribute float aGrp;
|
|
198
204
|
uniform mat4 uVP; uniform float uFocal; uniform float uGA[8];
|
|
@@ -200,12 +206,15 @@ varying vec4 vCol; varying float vZ; varying float vGA;
|
|
|
200
206
|
void main(){ vCol=aCol; vGA=uGA[int(aGrp+0.5)]; vec4 p=uVP*vec4(aPos,1.0); vZ=p.w;
|
|
201
207
|
gl_PointSize = clamp(aSize * uFocal / p.w, 1.0, 256.0); gl_Position=p; }`;
|
|
202
208
|
const PT_FS = `
|
|
203
|
-
precision mediump float; varying vec4 vCol; varying float vZ; varying float vGA; uniform float uFogK;
|
|
209
|
+
precision mediump float; varying vec4 vCol; varying float vZ; varying float vGA; uniform float uFogK; uniform float uInk;
|
|
204
210
|
void main(){
|
|
205
211
|
float r = length(gl_PointCoord - 0.5) * 2.0;
|
|
206
212
|
float a = exp(-3.2 * r * r) * (1.0 - smoothstep(0.85, 1.0, r));
|
|
207
213
|
float fog = clamp(uFogK / vZ, 0.08, 1.0);
|
|
208
|
-
|
|
214
|
+
float A = vCol.a * a * fog * vGA;
|
|
215
|
+
// ink edition: an ink blot blended normally, not a premultiplied additive glow
|
|
216
|
+
if (uInk > 0.5) gl_FragColor = vec4(vCol.rgb, A);
|
|
217
|
+
else gl_FragColor = vec4(vCol.rgb, 1.0) * A;
|
|
209
218
|
}`;
|
|
210
219
|
function makeProg(gl, vs, fs) {
|
|
211
220
|
const sh = (type, src) => {
|
|
@@ -224,11 +233,6 @@ function makeProg(gl, vs, fs) {
|
|
|
224
233
|
throw new Error("dark-math-3d link: " + gl.getProgramInfoLog(p));
|
|
225
234
|
return p;
|
|
226
235
|
}
|
|
227
|
-
/** Parse a css hsl(...) string the way the 2d examples write them. */
|
|
228
|
-
function parseHsl(c) {
|
|
229
|
-
const m = /hsl\(\s*([\d.]+)[deg\s]*[, ]\s*([\d.]+)%[, ]\s*([\d.]+)%/.exec(c);
|
|
230
|
-
return m ? hsl(+m[1], +m[2], +m[3]) : [0.9, 0.9, 1];
|
|
231
|
-
}
|
|
232
236
|
export class World {
|
|
233
237
|
idxFor(name) {
|
|
234
238
|
const n = name ?? "default";
|
|
@@ -260,6 +264,7 @@ export class World {
|
|
|
260
264
|
this.disposed = false;
|
|
261
265
|
this.cleanup = [];
|
|
262
266
|
this.fLight = [];
|
|
267
|
+
this.ink = false; // v0.13 — theme "light": ink on paper instead of light on black
|
|
263
268
|
this.cam = { target: v3(0, 0, 0), dist: 700, pitch: 0.5, yawBase: 0, yawUser: 0, pitchUser: 0, distT: 700, fov: 1.15 };
|
|
264
269
|
this.light = { dir: v3(0, 1, 0), ambient: 0.55, diffuse: 0.45, signed: false };
|
|
265
270
|
this.lights = [];
|
|
@@ -300,6 +305,7 @@ export class World {
|
|
|
300
305
|
if (!gl)
|
|
301
306
|
throw new Error("dark-math-3d: no webgl context");
|
|
302
307
|
this.gl = gl;
|
|
308
|
+
this.ink = o.theme === "light";
|
|
303
309
|
this.opts = {
|
|
304
310
|
maxDpr: o.maxDpr ?? 2,
|
|
305
311
|
drift: o.drift ?? 0.045,
|
|
@@ -416,10 +422,18 @@ export class World {
|
|
|
416
422
|
this.og.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
417
423
|
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
418
424
|
}
|
|
425
|
+
// ── the ink edition's one rule: lightness inverts; recipe colours pass through unchanged when dark ──
|
|
426
|
+
col(h, s, l) {
|
|
427
|
+
return hsl(h, s, this.ink ? 100 - l : l);
|
|
428
|
+
}
|
|
429
|
+
parseCol(c) {
|
|
430
|
+
const m = /hsl\(\s*([\d.]+)[deg\s]*[, ]\s*([\d.]+)%[, ]\s*([\d.]+)%/.exec(c);
|
|
431
|
+
return m ? this.col(+m[1], +m[2], +m[3]) : (this.ink ? [0.18, 0.18, 0.28] : [0.9, 0.9, 1]);
|
|
432
|
+
}
|
|
419
433
|
// ── building the static world (same verbs as 2d) ──
|
|
420
434
|
pushFace(store, a, b, c, o, na, nb, nc) {
|
|
421
435
|
const n = na ? undefined : norm(cross(sub(b, a), sub(c, a)));
|
|
422
|
-
const [r, g, bl] =
|
|
436
|
+
const [r, g, bl] = this.col(o.hue, o.sat ?? 45, o.light ?? 30);
|
|
423
437
|
const al = clamp(o.alpha ?? 1, 0, 1);
|
|
424
438
|
const gi = this.idxFor(o.group);
|
|
425
439
|
// faces are opaque (blend off), so the colour's alpha slot carries the GLOSS — documented
|
|
@@ -437,18 +451,21 @@ export class World {
|
|
|
437
451
|
this.staticDirty = true;
|
|
438
452
|
}
|
|
439
453
|
line(a, b, o) {
|
|
440
|
-
const [r, g, bl] =
|
|
454
|
+
const [r, g, bl] = this.col(o.hue, o.sat ?? 80, o.light ?? 55);
|
|
441
455
|
const al = clamp(o.alpha, 0, 1), gi = this.idxFor(o.group);
|
|
442
456
|
this.sLine.push(a.x, a.y, a.z, r, g, bl, al, gi, b.x, b.y, b.z, r, g, bl, al, gi);
|
|
443
457
|
this.staticDirty = true;
|
|
444
458
|
}
|
|
445
459
|
glow(p, o) {
|
|
446
|
-
const [r, g, bl] = o.white ? [1, 1, 1] :
|
|
460
|
+
const [r, g, bl] = o.white ? (this.ink ? [0.16, 0.15, 0.24] : [1, 1, 1]) : this.col(o.hue ?? 0, 85, 62);
|
|
447
461
|
this.sPt.push(p.x, p.y, p.z, o.size, r, g, bl, clamp(o.alpha, 0, 1), this.idxFor(o.group));
|
|
448
462
|
this.staticDirty = true;
|
|
449
463
|
}
|
|
450
464
|
dust(p, alpha) {
|
|
451
|
-
|
|
465
|
+
if (this.ink)
|
|
466
|
+
this.sPt.push(p.x, p.y, p.z, 1.6, 0.29, 0.31, 0.45, clamp(alpha * 0.6, 0, 1), 0);
|
|
467
|
+
else
|
|
468
|
+
this.sPt.push(p.x, p.y, p.z, 1.6, 0.87, 0.89, 1, clamp(alpha * 0.6, 0, 1), 0);
|
|
452
469
|
this.staticDirty = true;
|
|
453
470
|
}
|
|
454
471
|
grow(fn) { this.hooks.push(fn); }
|
|
@@ -529,15 +546,15 @@ export class World {
|
|
|
529
546
|
continue;
|
|
530
547
|
}
|
|
531
548
|
if (rec.k === "glow") {
|
|
532
|
-
const [r, g, b] = rec.hue == null ? [1, 1, 1] :
|
|
549
|
+
const [r, g, b] = rec.hue == null ? (this.ink ? [0.16, 0.15, 0.24] : [1, 1, 1]) : this.col(rec.hue, 85, 62);
|
|
533
550
|
this.dPtA.push(rec.p.x, rec.p.y, rec.p.z, rec.size, r, g, b, clamp(rec.alpha, 0, 1), 0);
|
|
534
551
|
}
|
|
535
552
|
else if (rec.k === "line") {
|
|
536
|
-
const [r, g, b] =
|
|
553
|
+
const [r, g, b] = this.parseCol(rec.color);
|
|
537
554
|
this.dLineA.push(rec.a.x, rec.a.y, rec.a.z, r, g, b, rec.alpha, 0, rec.b.x, rec.b.y, rec.b.z, r, g, b, rec.alpha, 0);
|
|
538
555
|
}
|
|
539
556
|
else if (rec.k === "poly") {
|
|
540
|
-
const [r, g, b] =
|
|
557
|
+
const [r, g, b] = this.parseCol(rec.color);
|
|
541
558
|
for (let i = 1; i < rec.pts.length; i++) {
|
|
542
559
|
const a = rec.pts[i - 1].p, q = rec.pts[i].p;
|
|
543
560
|
this.dLineA.push(a.x, a.y, a.z, r, g, b, rec.alpha, 0, q.x, q.y, q.z, r, g, b, rec.alpha, 0);
|
|
@@ -594,9 +611,13 @@ export class World {
|
|
|
594
611
|
if (this.dFaceA.length)
|
|
595
612
|
this.drawFaces(this.bFaceD, this.dFaceA.length / 12, null);
|
|
596
613
|
// the light — additive, depth-TESTED but not written: the dark occludes the light, exactly
|
|
614
|
+
// (ink edition: normal alpha blending — ink layers over paper instead of light adding up)
|
|
597
615
|
gl.depthMask(false);
|
|
598
616
|
gl.enable(gl.BLEND);
|
|
599
|
-
|
|
617
|
+
if (this.ink)
|
|
618
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
619
|
+
else
|
|
620
|
+
gl.blendFunc(gl.ONE, gl.ONE);
|
|
600
621
|
gl.useProgram(this.pLine);
|
|
601
622
|
this.setVP(this.pLine);
|
|
602
623
|
this.setFog(this.pLine, fogK);
|
|
@@ -630,7 +651,7 @@ export class World {
|
|
|
630
651
|
const g = this.og, W = this.W, H = this.H;
|
|
631
652
|
const grd = g.createRadialGradient(W / 2, H * 0.46, Math.min(W, H) * 0.28, W / 2, H / 2, Math.max(W, H) * 0.72);
|
|
632
653
|
grd.addColorStop(0, "rgba(0,0,0,0)");
|
|
633
|
-
grd.addColorStop(1, `rgba(6,4,14,${this.vignette})`);
|
|
654
|
+
grd.addColorStop(1, this.ink ? `rgba(52,48,76,${this.vignette * 0.3})` : `rgba(6,4,14,${this.vignette})`);
|
|
634
655
|
g.fillStyle = grd;
|
|
635
656
|
g.fillRect(0, 0, W, H);
|
|
636
657
|
}
|
|
@@ -641,12 +662,14 @@ export class World {
|
|
|
641
662
|
}
|
|
642
663
|
setFog(p, fogK) {
|
|
643
664
|
this.gl.uniform1f(this.gl.getUniformLocation(p, "uFogK"), fogK);
|
|
665
|
+
this.gl.uniform1f(this.gl.getUniformLocation(p, "uInk"), this.ink ? 1 : 0);
|
|
644
666
|
}
|
|
645
667
|
setLights(p, fogK) {
|
|
646
668
|
const gl = this.gl, L = norm(this.light.dir);
|
|
647
669
|
gl.uniform3f(gl.getUniformLocation(p, "uLdir"), L.x, L.y, L.z);
|
|
648
|
-
|
|
649
|
-
gl.uniform1f(gl.getUniformLocation(p, "
|
|
670
|
+
// ink edition: paper wants gentler shading — lift the ambient, soften the diffuse swing
|
|
671
|
+
gl.uniform1f(gl.getUniformLocation(p, "uAmb"), this.ink ? Math.min(1, this.light.ambient + 0.22) : this.light.ambient);
|
|
672
|
+
gl.uniform1f(gl.getUniformLocation(p, "uDif"), this.ink ? this.light.diffuse * 0.55 : this.light.diffuse);
|
|
650
673
|
gl.uniform1f(gl.getUniformLocation(p, "uSigned"), this.light.signed ? 1 : 0);
|
|
651
674
|
gl.uniform3f(gl.getUniformLocation(p, "uCam"), this.cx.x, this.cx.y, this.cx.z);
|
|
652
675
|
gl.uniform1f(gl.getUniformLocation(p, "uSpec"), this.specular);
|
|
@@ -654,7 +677,8 @@ export class World {
|
|
|
654
677
|
gl.uniform1f(gl.getUniformLocation(p, "uGrade"), this.grade ? 1 : 0);
|
|
655
678
|
gl.uniform3f(gl.getUniformLocation(p, "uSkyC"), this.ambientSky.x, this.ambientSky.y, this.ambientSky.z);
|
|
656
679
|
gl.uniform3f(gl.getUniformLocation(p, "uGroundC"), this.ambientGround.x, this.ambientGround.y, this.ambientGround.z);
|
|
657
|
-
|
|
680
|
+
// ink edition: the filmic curve would grey the paper itself — tone mapping is a night look
|
|
681
|
+
gl.uniform1f(gl.getUniformLocation(p, "uFilm"), this.filmic && !this.ink ? 1 : 0);
|
|
658
682
|
gl.uniformMatrix4fv(gl.getUniformLocation(p, "uSVP"), false, this.svp);
|
|
659
683
|
gl.uniform1f(gl.getUniformLocation(p, "uShadowOn"), this.shadowsOn && this.shadowTex && this.lights.length ? 1 : 0);
|
|
660
684
|
if (this.shadowTex) {
|
|
@@ -766,6 +790,11 @@ export class World {
|
|
|
766
790
|
c();
|
|
767
791
|
this.cleanup = [];
|
|
768
792
|
this.overlay.remove();
|
|
793
|
+
// Release the GL context NOW — browsers cap live WebGL contexts per page (~16) and only
|
|
794
|
+
// reclaim them at GC, so a page that builds and disposes many worlds otherwise exhausts
|
|
795
|
+
// the pool and every later createWorld fails. A lost context is permanently dead on its
|
|
796
|
+
// canvas: mount a fresh canvas to build again.
|
|
797
|
+
this.gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
769
798
|
}
|
|
770
799
|
}
|
|
771
800
|
export function createWorld(o) { return new World(o); }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chromatic-coherence/generative-charts-3d",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "The second generative-charts engine — the same API as generative-charts-2d, rendered on WebGL: a real depth buffer (occlusion exact, always on, for solids and light) and per-pixel Lambert + point lights. Zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|