@chromatic-coherence/generative-charts-3d 0.12.2 → 0.13.1
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 +11 -0
- package/dist/index.js +61 -24
- 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,11 @@ export declare class World {
|
|
|
158
164
|
constructor(o: WorldOpts);
|
|
159
165
|
private wireControls;
|
|
160
166
|
private resize;
|
|
167
|
+
private col;
|
|
168
|
+
private parseCol;
|
|
169
|
+
private inkCss;
|
|
170
|
+
/** Remap a recipe-supplied css colour for the ink edition's overlay labels (identity when dark). */
|
|
171
|
+
private themedCss;
|
|
161
172
|
private pushFace;
|
|
162
173
|
/** v0.2 — pass per-vertex normals (na, nb, nc) and shading interpolates SMOOTHLY across
|
|
163
174
|
* 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 = [];
|
|
@@ -295,11 +300,13 @@ export class World {
|
|
|
295
300
|
this.fw = v3();
|
|
296
301
|
this.focal = 0;
|
|
297
302
|
this.vp = new Float32Array(16);
|
|
303
|
+
this.inkCss = new Map();
|
|
298
304
|
this.canvas = o.canvas;
|
|
299
305
|
const gl = o.canvas.getContext("webgl", { alpha: true, antialias: true, premultipliedAlpha: true });
|
|
300
306
|
if (!gl)
|
|
301
307
|
throw new Error("dark-math-3d: no webgl context");
|
|
302
308
|
this.gl = gl;
|
|
309
|
+
this.ink = o.theme === "light";
|
|
303
310
|
this.opts = {
|
|
304
311
|
maxDpr: o.maxDpr ?? 2,
|
|
305
312
|
drift: o.drift ?? 0.045,
|
|
@@ -416,10 +423,30 @@ export class World {
|
|
|
416
423
|
this.og.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
417
424
|
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
418
425
|
}
|
|
426
|
+
// ── the ink edition's one rule: lightness inverts; recipe colours pass through unchanged when dark ──
|
|
427
|
+
col(h, s, l) {
|
|
428
|
+
return hsl(h, s, this.ink ? 100 - l : l);
|
|
429
|
+
}
|
|
430
|
+
parseCol(c) {
|
|
431
|
+
const m = /hsl\(\s*([\d.]+)[deg\s]*[, ]\s*([\d.]+)%[, ]\s*([\d.]+)%/.exec(c);
|
|
432
|
+
return m ? this.col(+m[1], +m[2], +m[3]) : (this.ink ? [0.18, 0.18, 0.28] : [0.9, 0.9, 1]);
|
|
433
|
+
}
|
|
434
|
+
/** Remap a recipe-supplied css colour for the ink edition's overlay labels (identity when dark). */
|
|
435
|
+
themedCss(color) {
|
|
436
|
+
if (!this.ink)
|
|
437
|
+
return color;
|
|
438
|
+
let c = this.inkCss.get(color);
|
|
439
|
+
if (!c) {
|
|
440
|
+
const m = /hsl\(\s*([\d.]+)(?:deg)?[\s,]+([\d.]+)%[\s,]+([\d.]+)%\s*\)/.exec(color);
|
|
441
|
+
c = m ? `hsl(${m[1]} ${m[2]}% ${clamp(100 - +m[3], 0, 100)}%)` : color;
|
|
442
|
+
this.inkCss.set(color, c);
|
|
443
|
+
}
|
|
444
|
+
return c;
|
|
445
|
+
}
|
|
419
446
|
// ── building the static world (same verbs as 2d) ──
|
|
420
447
|
pushFace(store, a, b, c, o, na, nb, nc) {
|
|
421
448
|
const n = na ? undefined : norm(cross(sub(b, a), sub(c, a)));
|
|
422
|
-
const [r, g, bl] =
|
|
449
|
+
const [r, g, bl] = this.col(o.hue, o.sat ?? 45, o.light ?? 30);
|
|
423
450
|
const al = clamp(o.alpha ?? 1, 0, 1);
|
|
424
451
|
const gi = this.idxFor(o.group);
|
|
425
452
|
// faces are opaque (blend off), so the colour's alpha slot carries the GLOSS — documented
|
|
@@ -437,18 +464,21 @@ export class World {
|
|
|
437
464
|
this.staticDirty = true;
|
|
438
465
|
}
|
|
439
466
|
line(a, b, o) {
|
|
440
|
-
const [r, g, bl] =
|
|
467
|
+
const [r, g, bl] = this.col(o.hue, o.sat ?? 80, o.light ?? 55);
|
|
441
468
|
const al = clamp(o.alpha, 0, 1), gi = this.idxFor(o.group);
|
|
442
469
|
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
470
|
this.staticDirty = true;
|
|
444
471
|
}
|
|
445
472
|
glow(p, o) {
|
|
446
|
-
const [r, g, bl] = o.white ? [1, 1, 1] :
|
|
473
|
+
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
474
|
this.sPt.push(p.x, p.y, p.z, o.size, r, g, bl, clamp(o.alpha, 0, 1), this.idxFor(o.group));
|
|
448
475
|
this.staticDirty = true;
|
|
449
476
|
}
|
|
450
477
|
dust(p, alpha) {
|
|
451
|
-
|
|
478
|
+
if (this.ink)
|
|
479
|
+
this.sPt.push(p.x, p.y, p.z, 1.6, 0.29, 0.31, 0.45, clamp(alpha * 0.6, 0, 1), 0);
|
|
480
|
+
else
|
|
481
|
+
this.sPt.push(p.x, p.y, p.z, 1.6, 0.87, 0.89, 1, clamp(alpha * 0.6, 0, 1), 0);
|
|
452
482
|
this.staticDirty = true;
|
|
453
483
|
}
|
|
454
484
|
grow(fn) { this.hooks.push(fn); }
|
|
@@ -529,15 +559,15 @@ export class World {
|
|
|
529
559
|
continue;
|
|
530
560
|
}
|
|
531
561
|
if (rec.k === "glow") {
|
|
532
|
-
const [r, g, b] = rec.hue == null ? [1, 1, 1] :
|
|
562
|
+
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
563
|
this.dPtA.push(rec.p.x, rec.p.y, rec.p.z, rec.size, r, g, b, clamp(rec.alpha, 0, 1), 0);
|
|
534
564
|
}
|
|
535
565
|
else if (rec.k === "line") {
|
|
536
|
-
const [r, g, b] =
|
|
566
|
+
const [r, g, b] = this.parseCol(rec.color);
|
|
537
567
|
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
568
|
}
|
|
539
569
|
else if (rec.k === "poly") {
|
|
540
|
-
const [r, g, b] =
|
|
570
|
+
const [r, g, b] = this.parseCol(rec.color);
|
|
541
571
|
for (let i = 1; i < rec.pts.length; i++) {
|
|
542
572
|
const a = rec.pts[i - 1].p, q = rec.pts[i].p;
|
|
543
573
|
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 +624,13 @@ export class World {
|
|
|
594
624
|
if (this.dFaceA.length)
|
|
595
625
|
this.drawFaces(this.bFaceD, this.dFaceA.length / 12, null);
|
|
596
626
|
// the light — additive, depth-TESTED but not written: the dark occludes the light, exactly
|
|
627
|
+
// (ink edition: normal alpha blending — ink layers over paper instead of light adding up)
|
|
597
628
|
gl.depthMask(false);
|
|
598
629
|
gl.enable(gl.BLEND);
|
|
599
|
-
|
|
630
|
+
if (this.ink)
|
|
631
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
632
|
+
else
|
|
633
|
+
gl.blendFunc(gl.ONE, gl.ONE);
|
|
600
634
|
gl.useProgram(this.pLine);
|
|
601
635
|
this.setVP(this.pLine);
|
|
602
636
|
this.setFog(this.pLine, fogK);
|
|
@@ -622,7 +656,7 @@ export class World {
|
|
|
622
656
|
if (!q)
|
|
623
657
|
continue;
|
|
624
658
|
this.og.globalAlpha = clamp(l.alpha, 0, 1);
|
|
625
|
-
this.og.fillStyle = l.color;
|
|
659
|
+
this.og.fillStyle = this.themedCss(l.color);
|
|
626
660
|
this.og.fillText(l.text, q.x, q.y + l.dy);
|
|
627
661
|
}
|
|
628
662
|
this.og.globalAlpha = 1;
|
|
@@ -630,7 +664,7 @@ export class World {
|
|
|
630
664
|
const g = this.og, W = this.W, H = this.H;
|
|
631
665
|
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
666
|
grd.addColorStop(0, "rgba(0,0,0,0)");
|
|
633
|
-
grd.addColorStop(1, `rgba(6,4,14,${this.vignette})`);
|
|
667
|
+
grd.addColorStop(1, this.ink ? `rgba(52,48,76,${this.vignette * 0.3})` : `rgba(6,4,14,${this.vignette})`);
|
|
634
668
|
g.fillStyle = grd;
|
|
635
669
|
g.fillRect(0, 0, W, H);
|
|
636
670
|
}
|
|
@@ -641,12 +675,14 @@ export class World {
|
|
|
641
675
|
}
|
|
642
676
|
setFog(p, fogK) {
|
|
643
677
|
this.gl.uniform1f(this.gl.getUniformLocation(p, "uFogK"), fogK);
|
|
678
|
+
this.gl.uniform1f(this.gl.getUniformLocation(p, "uInk"), this.ink ? 1 : 0);
|
|
644
679
|
}
|
|
645
680
|
setLights(p, fogK) {
|
|
646
681
|
const gl = this.gl, L = norm(this.light.dir);
|
|
647
682
|
gl.uniform3f(gl.getUniformLocation(p, "uLdir"), L.x, L.y, L.z);
|
|
648
|
-
|
|
649
|
-
gl.uniform1f(gl.getUniformLocation(p, "
|
|
683
|
+
// ink edition: paper wants gentler shading — lift the ambient, soften the diffuse swing
|
|
684
|
+
gl.uniform1f(gl.getUniformLocation(p, "uAmb"), this.ink ? Math.min(1, this.light.ambient + 0.22) : this.light.ambient);
|
|
685
|
+
gl.uniform1f(gl.getUniformLocation(p, "uDif"), this.ink ? this.light.diffuse * 0.55 : this.light.diffuse);
|
|
650
686
|
gl.uniform1f(gl.getUniformLocation(p, "uSigned"), this.light.signed ? 1 : 0);
|
|
651
687
|
gl.uniform3f(gl.getUniformLocation(p, "uCam"), this.cx.x, this.cx.y, this.cx.z);
|
|
652
688
|
gl.uniform1f(gl.getUniformLocation(p, "uSpec"), this.specular);
|
|
@@ -654,7 +690,8 @@ export class World {
|
|
|
654
690
|
gl.uniform1f(gl.getUniformLocation(p, "uGrade"), this.grade ? 1 : 0);
|
|
655
691
|
gl.uniform3f(gl.getUniformLocation(p, "uSkyC"), this.ambientSky.x, this.ambientSky.y, this.ambientSky.z);
|
|
656
692
|
gl.uniform3f(gl.getUniformLocation(p, "uGroundC"), this.ambientGround.x, this.ambientGround.y, this.ambientGround.z);
|
|
657
|
-
|
|
693
|
+
// ink edition: the filmic curve would grey the paper itself — tone mapping is a night look
|
|
694
|
+
gl.uniform1f(gl.getUniformLocation(p, "uFilm"), this.filmic && !this.ink ? 1 : 0);
|
|
658
695
|
gl.uniformMatrix4fv(gl.getUniformLocation(p, "uSVP"), false, this.svp);
|
|
659
696
|
gl.uniform1f(gl.getUniformLocation(p, "uShadowOn"), this.shadowsOn && this.shadowTex && this.lights.length ? 1 : 0);
|
|
660
697
|
if (this.shadowTex) {
|
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.1",
|
|
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",
|