phoria2 1.3.0 → 1.4.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.
Files changed (56) hide show
  1. package/README.md +9 -6
  2. package/dist/bloom.js +4 -4
  3. package/dist/bloom.js.map +1 -1
  4. package/dist/charts.js +436 -436
  5. package/dist/charts.js.map +1 -1
  6. package/dist/core.js +1240 -862
  7. package/dist/core.js.map +1 -1
  8. package/dist/debug.js +297 -0
  9. package/dist/debug.js.map +1 -0
  10. package/dist/edit.js +40 -40
  11. package/dist/edit.js.map +1 -1
  12. package/dist/explain.js +32 -13
  13. package/dist/explain.js.map +1 -1
  14. package/dist/gltf.js +128 -128
  15. package/dist/gltf.js.map +1 -1
  16. package/dist/keyboard.js +19 -19
  17. package/dist/keyboard.js.map +1 -1
  18. package/dist/motion.js +35 -35
  19. package/dist/motion.js.map +1 -1
  20. package/dist/particles.js +36 -36
  21. package/dist/particles.js.map +1 -1
  22. package/dist/passes.js +116 -104
  23. package/dist/passes.js.map +1 -1
  24. package/dist/phoria2.js +1 -1
  25. package/dist/picking.js +90 -71
  26. package/dist/picking.js.map +1 -1
  27. package/dist/snapshot.js +1 -1
  28. package/dist/tooltip.js +1 -1
  29. package/dist/types/View.d.ts +16 -0
  30. package/dist/types/debug.d.ts +74 -0
  31. package/dist/types/explain.d.ts +4 -0
  32. package/dist/types/frame.d.ts +5 -0
  33. package/dist/types/material/Material.d.ts +52 -0
  34. package/dist/types/watch.d.ts +8 -1
  35. package/dist/types/webgl/hooks.d.ts +48 -0
  36. package/dist/types/webgl/passes.d.ts +4 -0
  37. package/dist/types/webgl/programs.d.ts +15 -5
  38. package/dist/types/webgl/shaders.d.ts +36 -5
  39. package/llms-full.txt +227 -0
  40. package/llms.txt +1 -1
  41. package/package.json +1 -1
  42. package/schema/scene.schema.json +27 -0
  43. package/src/View.js +105 -7
  44. package/src/debug.js +283 -0
  45. package/src/explain.js +19 -0
  46. package/src/frame.js +7 -2
  47. package/src/material/Material.js +121 -2
  48. package/src/scene/drawables.js +21 -4
  49. package/src/watch.js +18 -2
  50. package/src/webgl/WebGLRenderer.js +19 -2
  51. package/src/webgl/hooks.js +121 -0
  52. package/src/webgl/passes.js +17 -2
  53. package/src/webgl/picking.js +23 -2
  54. package/src/webgl/programs.js +20 -9
  55. package/src/webgl/resources.js +1 -1
  56. package/src/webgl/shaders.js +443 -35
package/dist/picking.js CHANGED
@@ -1,22 +1,37 @@
1
- import { D as e, Ft as t, Lt as n, O as r, S as i, k as a, w as o } from "./core.js";
1
+ import { A as e, C as t, D as n, M as r, S as i, T as a, Vt as o, j as s, w as c, zt as l } from "./core.js";
2
2
  // src/webgl/picking.js
3
- var s = 33, c = 4, l = `precision highp float;
3
+ var u = 33, d = 4, f = `precision highp float;
4
4
  precision highp int;
5
5
  flat in uint vPickElement;
6
6
  #ifdef SPRITES
7
7
  in vec2 vUv;
8
8
  #endif
9
- #ifdef CLIP
9
+ #if defined(CLIP) || defined(HOOK_FRAGMENT)
10
10
  in vec3 vWorldPosition;
11
11
  #endif
12
+ #ifdef HOOK_FRAGMENT
13
+ in vec3 vNormal;
14
+ in vec2 vUv;
15
+ in vec3 vColor;
16
+ #endif
17
+ //@hook uniforms
12
18
  uniform uint uPickId;
13
19
  layout(location = 0) out uvec4 pickOut;
14
- ${o}
15
- ${i}
20
+ ${n}
21
+ ${a}
16
22
  void main() {
17
23
  #ifdef CLIP
18
24
  clipAway(vWorldPosition);
19
25
  #endif
26
+ #ifdef HOOK_FRAGMENT
27
+ {
28
+ vec4 base = vec4(vColor, 1.0);
29
+ vec3 emissive = vec3(0.0);
30
+ vec3 normal = normalize(vNormal);
31
+ //@hook fragment
32
+ if (base.a <= 0.0) discard;
33
+ }
34
+ #endif
20
35
  #ifdef ROUND
21
36
  #ifdef SPRITES
22
37
  vec2 p = vUv * 2.0 - 1.0;
@@ -27,11 +42,11 @@ if (dot(p, p) > 1.0) discard;
27
42
  #endif
28
43
  pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
29
44
  }
30
- `, u = {
31
- mesh: [r, l],
32
- points: [a, l],
33
- lines: [e, l]
34
- }, d = class {
45
+ `, p = {
46
+ mesh: [s, f],
47
+ points: [r, f],
48
+ lines: [e, f]
49
+ }, m = class {
35
50
  #e = null;
36
51
  #t = /* @__PURE__ */ new Uint32Array(4356);
37
52
  #n = /* @__PURE__ */ new Float32Array(16);
@@ -46,79 +61,79 @@ pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
46
61
  this.#e = null, this.#a = null, this.#o = !1;
47
62
  }
48
63
  pick(e, t, n, r, i, a) {
49
- let o = this.gl, c = this.#s(e, t, n, r, i, a);
50
- return c ? (o.readPixels(0, 0, s, s, o.RGBA_INTEGER, o.UNSIGNED_INT, this.#t), o.bindFramebuffer(o.FRAMEBUFFER, null), this.#c(c)) : null;
64
+ let o = this.gl, s = this.#s(e, t, n, r, i, a);
65
+ return s ? (o.readPixels(0, 0, u, u, o.RGBA_INTEGER, o.UNSIGNED_INT, this.#t), o.bindFramebuffer(o.FRAMEBUFFER, null), this.#c(s)) : null;
51
66
  }
52
67
  pickAsync(e, t, n, r, i, a) {
53
68
  if (this.#o) return null;
54
- let o = this.gl, c = this.#s(e, t, n, r, i, a);
55
- if (!c) return Promise.resolve(null);
56
- let l = this.#l();
57
- o.bindBuffer(o.PIXEL_PACK_BUFFER, l), o.readPixels(0, 0, s, s, o.RGBA_INTEGER, o.UNSIGNED_INT, 0), o.bindBuffer(o.PIXEL_PACK_BUFFER, null), o.bindFramebuffer(o.FRAMEBUFFER, null);
58
- let u = o.fenceSync(o.SYNC_GPU_COMMANDS_COMPLETE, 0);
69
+ let o = this.gl, s = this.#s(e, t, n, r, i, a);
70
+ if (!s) return Promise.resolve(null);
71
+ let c = this.#l();
72
+ o.bindBuffer(o.PIXEL_PACK_BUFFER, c), o.readPixels(0, 0, u, u, o.RGBA_INTEGER, o.UNSIGNED_INT, 0), o.bindBuffer(o.PIXEL_PACK_BUFFER, null), o.bindFramebuffer(o.FRAMEBUFFER, null);
73
+ let l = o.fenceSync(o.SYNC_GPU_COMMANDS_COMPLETE, 0);
59
74
  return o.flush(), this.#o = !0, new Promise((e) => {
60
75
  let t = () => {
61
- o.isContextLost() ? (this.#o = !1, e(null)) : o.clientWaitSync(u, 0, 0) === o.TIMEOUT_EXPIRED ? setTimeout(t, 1) : (o.deleteSync(u), o.bindBuffer(o.PIXEL_PACK_BUFFER, l), o.getBufferSubData(o.PIXEL_PACK_BUFFER, 0, this.#t), o.bindBuffer(o.PIXEL_PACK_BUFFER, null), this.#o = !1, e(this.#c(c)));
76
+ o.isContextLost() ? (this.#o = !1, e(null)) : o.clientWaitSync(l, 0, 0) === o.TIMEOUT_EXPIRED ? setTimeout(t, 1) : (o.deleteSync(l), o.bindBuffer(o.PIXEL_PACK_BUFFER, c), o.getBufferSubData(o.PIXEL_PACK_BUFFER, 0, this.#t), o.bindBuffer(o.PIXEL_PACK_BUFFER, null), this.#o = !1, e(this.#c(s)));
62
77
  };
63
78
  setTimeout(t, 0);
64
79
  });
65
80
  }
66
- #s(e, t, r, i, a, o) {
67
- let c = this.gl, l = f(e);
68
- if (!l.length) return null;
69
- let u = this.#d();
70
- c.bindFramebuffer(c.FRAMEBUFFER, u.framebuffer), c.viewport(0, 0, s, s), c.disable(c.BLEND), c.disable(c.SCISSOR_TEST), c.enable(c.DEPTH_TEST), c.depthFunc(c.LEQUAL), c.depthMask(!0), c.clearBufferuiv(c.COLOR, 0, [
81
+ #s(e, t, n, r, i, a) {
82
+ let s = this.gl, c = h(e);
83
+ if (!c.length) return null;
84
+ let l = this.#d();
85
+ s.bindFramebuffer(s.FRAMEBUFFER, l.framebuffer), s.viewport(0, 0, u, u), s.disable(s.BLEND), s.disable(s.SCISSOR_TEST), s.enable(s.DEPTH_TEST), s.depthFunc(s.LEQUAL), s.depthMask(!0), s.clearBufferuiv(s.COLOR, 0, [
71
86
  0,
72
87
  0,
73
88
  0,
74
89
  0
75
- ]), c.clearBufferfv(c.DEPTH, 0, [1]);
76
- let d = s, p = t / i * 2 - 1, m = 1 - r / a * 2, h = new Float32Array([
77
- i / d,
90
+ ]), s.clearBufferfv(s.DEPTH, 0, [1]);
91
+ let d = u, f = t / r * 2 - 1, p = 1 - n / i * 2, m = new Float32Array([
92
+ r / d,
78
93
  0,
79
94
  0,
80
95
  0,
81
96
  0,
82
- a / d,
97
+ i / d,
83
98
  0,
84
99
  0,
85
100
  0,
86
101
  0,
87
102
  1,
88
103
  0,
104
+ -f * r / d,
89
105
  -p * i / d,
90
- -m * a / d,
91
106
  0,
92
107
  1
93
108
  ]);
94
- return n(this.#n, h, e.camera.projection), n(this.#r, this.#n, e.camera.view), l.forEach((t, n) => this.#u(t, n + 1, e, a, o)), c.bindVertexArray(null), {
95
- items: l,
96
- cx: p,
97
- cy: m,
98
- width: i,
99
- height: a
109
+ return o(this.#n, m, e.camera.projection), o(this.#r, this.#n, e.camera.view), c.forEach((t, n) => this.#u(t, n + 1, e, i, a)), s.bindVertexArray(null), {
110
+ items: c,
111
+ cx: f,
112
+ cy: p,
113
+ width: r,
114
+ height: i
100
115
  };
101
116
  }
102
117
  #c({ items: e, cx: t, cy: n, width: r, height: i }) {
103
- let a = this.#t, o = (e, t) => (t * s + e) * 4, l = a[o(16, 16)] ? o(16, 16) : -1, u = 0, d = 0;
104
- if (l < 0) {
118
+ let a = this.#t, o = (e, t) => (t * u + e) * 4, s = a[o(16, 16)] ? o(16, 16) : -1, c = 0, l = 0;
119
+ if (s < 0) {
105
120
  let t = Infinity;
106
- for (let n = -4; n <= c; n++) for (let r = -4; r <= c; r++) {
121
+ for (let n = -4; n <= d; n++) for (let r = -4; r <= d; r++) {
107
122
  let i = r * r + n * n;
108
123
  if (i > 16 || i >= t) continue;
109
- let s = o(16 + r, 16 + n), c = a[s];
110
- c && e[c - 1].node.drawMode !== "triangles" && (l = s, t = i, u = r, d = n);
124
+ let u = o(16 + r, 16 + n), d = a[u];
125
+ d && e[d - 1].node.drawMode !== "triangles" && (s = u, t = i, c = r, l = n);
111
126
  }
112
127
  }
113
- if (l < 0) return null;
114
- let f = new Float32Array(new Uint32Array([a[l + 2]]).buffer)[0], p = [
115
- t + u * 2 / r,
116
- n + d * 2 / i,
128
+ if (s < 0) return null;
129
+ let f = new Float32Array(new Uint32Array([a[s + 2]]).buffer)[0], p = [
130
+ t + c * 2 / r,
131
+ n + l * 2 / i,
117
132
  f * 2 - 1
118
133
  ];
119
134
  return {
120
- item: e[a[l] - 1],
121
- element: a[l + 1],
135
+ item: e[a[s] - 1],
136
+ element: a[s + 1],
122
137
  ndc: p
123
138
  };
124
139
  }
@@ -127,35 +142,39 @@ pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
127
142
  let e = this.gl;
128
143
  return this.#a = e.createBuffer(), e.bindBuffer(e.PIXEL_PACK_BUFFER, this.#a), e.bufferData(e.PIXEL_PACK_BUFFER, this.#t.byteLength, e.STREAM_READ), e.bindBuffer(e.PIXEL_PACK_BUFFER, null), this.#a;
129
144
  }
130
- #u(e, n, r, i, a) {
131
- let o = this.gl, { node: c, geometry: l, material: d, worldMatrix: f, instances: p } = e, m = c.drawMode, h = m === "points" ? "points" : m === "lines" ? "lines" : "mesh", g = ["PICK"];
132
- p && g.push("INSTANCED");
133
- let _ = h === "points" && c.drawsSprites;
134
- h === "points" && (d.sizeAttenuation && g.push("SIZE_ATTENUATION"), d.map || g.push("ROUND"), _ && g.push("SPRITES")), h === "mesh" && e.clip && g.push("CLIP");
135
- let v = this.programs.ready(`pick-${h}`, g, u[h]);
136
- if (o.useProgram(v.program), v.set("uView", r.camera.view), v.set("uProjection", this.#n), v.set("uViewProjection", this.#r), v.set("uModel", f), v.set("uPickId", n), v.set("uHighlightIndex", -2), e.clip && v.set("uClipPlane", e.clip.plane), h === "lines") {
137
- o.disable(o.CULL_FACE), v.set("uResolution", [s, s]), v.set("uWidth", d.width * a), v.set("uNear", r.camera.near);
138
- let e = c.arrowBits ?? 0;
139
- v.set("uArrows", e), v.set("uArrowSize", c.arrowPixels?.(d.width) * a || 0);
140
- let t = this.resources.lines(l);
141
- o.bindVertexArray(t.vao), o.drawArraysInstanced(o.TRIANGLES, 0, e ? 12 : 6, Math.min(t.count, Math.floor(l.drawnCount / 2)));
142
- } else if (h === "points") {
143
- if (o.disable(o.CULL_FACE), v.set("uSize", d.sizeAttenuation ? d.size : d.size * a), v.set("uScale", r.camera.projection[5] * i / 2), _) {
144
- v.set("uResolution", [s, s]);
145
- let e = this.resources.sprites(l);
146
- o.bindVertexArray(e.vao), o.drawArraysInstanced(o.TRIANGLE_STRIP, 0, 4, e.count);
147
- return;
148
- }
149
- let e = this.resources.mesh(l, null, !1);
150
- o.bindVertexArray(e.vao), o.drawArrays(o.POINTS, 0, l.drawnVertices);
151
- } else v.set("uNormalMatrix", this.#i), d.depthTest ? o.enable(o.DEPTH_TEST) : o.disable(o.DEPTH_TEST), d.doubleSided || e.clip?.cap ? o.disable(o.CULL_FACE) : (o.enable(o.CULL_FACE), o.cullFace(o.BACK), o.frontFace(t(f) < 0 ? o.CW : o.CCW)), this.resources.drawTriangles(l, p);
145
+ #u(e, n, r, a, o) {
146
+ let s = this.gl, { node: d, geometry: f, material: m, worldMatrix: h, instances: g } = e, _ = d.drawMode, v = _ === "points" ? "points" : _ === "lines" ? "lines" : "mesh", y = ["PICK"];
147
+ g && y.push("INSTANCED");
148
+ let b = v === "points" && d.drawsSprites;
149
+ v === "points" && (m.sizeAttenuation && y.push("SIZE_ATTENUATION"), m.map || y.push("ROUND"), b && y.push("SPRITES")), v === "mesh" && e.clip && y.push("CLIP");
150
+ let x = i(m);
151
+ x && y.push(...t(x, v === "mesh"));
152
+ let S = this.programs.ready(`pick-${v}`, y, p[v], x?.source);
153
+ if (!S.failed) {
154
+ if (s.useProgram(S.program), S.set("uView", r.camera.view), S.set("uProjection", this.#n), S.set("uViewProjection", this.#r), S.set("uModel", h), S.set("uPickId", n), S.set("uHighlightIndex", -2), e.clip && S.set("uClipPlane", e.clip.plane), x && c(S, m, r.time ?? 0, r.camera.position, (e) => this.resources.texture(e)), v === "lines") {
155
+ s.disable(s.CULL_FACE), S.set("uResolution", [u, u]), S.set("uWidth", m.width * o), S.set("uNear", r.camera.near);
156
+ let e = d.arrowBits ?? 0;
157
+ S.set("uArrows", e), S.set("uArrowSize", d.arrowPixels?.(m.width) * o || 0);
158
+ let t = this.resources.lines(f);
159
+ s.bindVertexArray(t.vao), s.drawArraysInstanced(s.TRIANGLES, 0, e ? 12 : 6, Math.min(t.count, Math.floor(f.drawnCount / 2)));
160
+ } else if (v === "points") {
161
+ if (s.disable(s.CULL_FACE), S.set("uSize", m.sizeAttenuation ? m.size : m.size * o), S.set("uScale", r.camera.projection[5] * a / 2), b) {
162
+ S.set("uResolution", [u, u]);
163
+ let e = this.resources.sprites(f);
164
+ s.bindVertexArray(e.vao), s.drawArraysInstanced(s.TRIANGLE_STRIP, 0, 4, e.count);
165
+ return;
166
+ }
167
+ let e = this.resources.mesh(f, null, !1);
168
+ s.bindVertexArray(e.vao), s.drawArrays(s.POINTS, 0, f.drawnVertices);
169
+ } else S.set("uNormalMatrix", this.#i), m.depthTest ? s.enable(s.DEPTH_TEST) : s.disable(s.DEPTH_TEST), m.doubleSided || e.clip?.cap ? s.disable(s.CULL_FACE) : (s.enable(s.CULL_FACE), s.cullFace(s.BACK), s.frontFace(l(h) < 0 ? s.CW : s.CCW)), this.resources.drawTriangles(f, g);
170
+ }
152
171
  }
153
172
  #d() {
154
173
  if (this.#e) return this.#e;
155
174
  let e = this.gl, t = e.createTexture();
156
- e.activeTexture(e.TEXTURE15), e.bindTexture(e.TEXTURE_2D, t), e.texStorage2D(e.TEXTURE_2D, 1, e.RGBA32UI, s, s);
175
+ e.activeTexture(e.TEXTURE15), e.bindTexture(e.TEXTURE_2D, t), e.texStorage2D(e.TEXTURE_2D, 1, e.RGBA32UI, u, u);
157
176
  let n = e.createRenderbuffer();
158
- e.bindRenderbuffer(e.RENDERBUFFER, n), e.renderbufferStorage(e.RENDERBUFFER, e.DEPTH_COMPONENT24, s, s);
177
+ e.bindRenderbuffer(e.RENDERBUFFER, n), e.renderbufferStorage(e.RENDERBUFFER, e.DEPTH_COMPONENT24, u, u);
159
178
  let r = e.createFramebuffer();
160
179
  return e.bindFramebuffer(e.FRAMEBUFFER, r), e.framebufferTexture2D(e.FRAMEBUFFER, e.COLOR_ATTACHMENT0, e.TEXTURE_2D, t, 0), e.framebufferRenderbuffer(e.FRAMEBUFFER, e.DEPTH_ATTACHMENT, e.RENDERBUFFER, n), e.bindFramebuffer(e.FRAMEBUFFER, null), this.#e = {
161
180
  framebuffer: r,
@@ -164,7 +183,7 @@ pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
164
183
  }, this.#e;
165
184
  }
166
185
  };
167
- function f(e) {
186
+ function h(e) {
168
187
  let t = /* @__PURE__ */ new Map(), n = (e) => {
169
188
  if (!e) return !0;
170
189
  let r = t.get(e);
@@ -172,6 +191,6 @@ function f(e) {
172
191
  }, r = [...e.opaque, ...e.transparent].filter((e) => n(e.node));
173
192
  return [...r.filter((e) => e.material.depthTest), ...r.filter((e) => !e.material.depthTest)];
174
193
  }
175
- export { d as Picker };
194
+ export { m as Picker };
176
195
 
177
196
  //# sourceMappingURL=picking.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"picking.js","names":["mat4.determinant","LINES_VERTEX","determinant","multiply","MESH_VERTEX","CLIP","POINTS_VERTEX","COMMON","REGION","TOLERANCE","PICK_FRAGMENT","SOURCES","Picker","gl","programs","resources","#target","#pack","#waiting","region","#drawRegion","frame","x","y","width","height","pixelRatio","#pixels","#decode","buffer","#readBuffer","fence","poll","resolve","items","pickable","target","#ensureTarget","s","cx","cy","zoom","#projection","#viewProjection","#draw","item","i","p","at","py","px","best","bestX","bestY","bestD","dy","dx","d","o","id","depth","ndc","mode","node","kind","defines","instances","sprites","material","program","worldMatrix","arrows","buffers","geometry","#normal","color","framebuffer","seen","ok","n","v"],"sources":[],"mappings":""}
1
+ {"version":3,"file":"picking.js","names":["mat4.determinant","LINES_VERTEX","passDefines","COMMON","POINTS_VERTEX","hookFor","CLIP","multiply","MESH_VERTEX","setHookUniforms","determinant","REGION","TOLERANCE","PICK_FRAGMENT","SOURCES","Picker","gl","programs","resources","#target","#pack","#waiting","region","#drawRegion","frame","x","y","width","height","pixelRatio","#pixels","#decode","buffer","#readBuffer","fence","poll","resolve","items","pickable","target","#ensureTarget","s","cx","cy","zoom","#projection","#viewProjection","#draw","item","i","p","at","py","px","best","bestX","bestY","bestD","dy","dx","d","o","id","depth","ndc","mode","node","kind","defines","instances","sprites","material","hook","program","worldMatrix","t","arrows","buffers","geometry","#normal","color","framebuffer","seen","ok","n","v"],"sources":[],"mappings":""}
package/dist/snapshot.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Tt as e } from "./core.js";
1
+ import { kt as e } from "./core.js";
2
2
  // src/snapshot.js
3
3
  function t(t, n, { type: a = "image/png", quality: o, labels: s = !0 } = {}, c = []) {
4
4
  let l = t;
package/dist/tooltip.js CHANGED
@@ -1,4 +1,4 @@
1
- import { jt as e, kt as t } from "./core.js";
1
+ import { Ft as e, Nt as t } from "./core.js";
2
2
  // src/tooltip.js
3
3
  var n = "\n:where(.phoria-tooltip) { position: fixed; left: 0; top: 0; z-index: 1000; pointer-events: none; max-width: 320px;\n padding: 6px 10px; border-radius: 6px; background: rgba(20, 20, 24, 0.92); color: #fff;\n font: 12px/1.45 system-ui, sans-serif; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); white-space: pre-line; }\n:where(.phoria-tooltip-title) { font-weight: 600; }\n:where(.phoria-tooltip-rows) { display: grid; grid-template-columns: auto auto; column-gap: 12px; }\n:where(.phoria-tooltip-rows > :nth-child(odd)) { opacity: 0.7; }\n:where(.phoria-tooltip-rows > :nth-child(even)) { text-align: right; font-variant-numeric: tabular-nums; }\n", r = 14, i = class {
4
4
  el;
@@ -17,6 +17,7 @@ import type { CameraOptions } from './scene/cameras.js';
17
17
  import type { ColorInput, Vec3Input } from './types.js';
18
18
  import type { Node } from './scene/Node.js';
19
19
  import type { KeyboardOptions } from './keyboard.js';
20
+ import type { DebugOptions } from './debug.js';
20
21
  export type RenderMode = 'auto' | 'always' | 'manual';
21
22
  export type ViewOptions = {
22
23
  /**
@@ -125,6 +126,11 @@ export type ViewOptions = {
125
126
  * on and no description, one is made from the scene (charts describe themselves)
126
127
  */
127
128
  description?: string | null;
129
+ /**
130
+ * pictures for checking a scene: lights, shadow cameras, bounds,
131
+ * normals, axes, the orbit target and a stats readout. true shows all but normals. Default false
132
+ */
133
+ debug?: boolean | DebugOptions;
128
134
  };
129
135
  export type ViewEvent = 'click' | 'hover';
130
136
  export type ViewHandler = (hit: Hit | null, event: PointerEvent | MouseEvent | null) => void;
@@ -195,6 +201,16 @@ export declare class View {
195
201
  get reducedMotion(): boolean;
196
202
  /** @param {'auto' | boolean} v */
197
203
  set reducedMotion(v: 'auto' | boolean);
204
+ /**
205
+ * Debug drawing, or false: what it shows, as DebugOptions. Assign true for everything but normals,
206
+ * options for some, or false. Its code loads the first time it's turned on.
207
+ *
208
+ * view.debug = { lights: true, shadows: true, bounds: 'hovered', stats: 'bottom-right' }
209
+ * @returns {boolean | DebugOptions} the options in force, every one given, or false
210
+ */
211
+ get debug(): boolean | DebugOptions;
212
+ /** @param {boolean | DebugOptions} v */
213
+ set debug(v: boolean | DebugOptions);
198
214
  /**
199
215
  * What the view shows, for screen readers - the canvas's label - or null. With keyboard on and no
200
216
  * description, one is made from the scene.
@@ -0,0 +1,74 @@
1
+ import { Box3 } from './value/Box3.js';
2
+ import type { FrameDescription } from './frame.js';
3
+ import type { Hit } from './Raycaster.js';
4
+ import type { Camera } from './scene/cameras.js';
5
+ export type DebugOptions = {
6
+ /**
7
+ * each light: an arrow for directional ones, a small sphere and its range
8
+ * for point lights, a cone for spot lights - in the light's colour
9
+ */
10
+ lights?: boolean;
11
+ /**
12
+ * the box or cone each shadow is drawn from - the usual reason a shadow
13
+ * is cut off or soft
14
+ */
15
+ shadows?: boolean;
16
+ /**
17
+ * world bounding boxes - what culling and view.frame() see
18
+ */
19
+ bounds?: boolean | 'hovered';
20
+ /**
21
+ * short lines along vertex normals (meshes up to 5,000
22
+ * vertices each when all are shown)
23
+ */
24
+ normals?: boolean | 'hovered';
25
+ /**
26
+ * x, y and z at the origin, red, green and blue
27
+ */
28
+ axes?: boolean;
29
+ /**
30
+ * the point the camera orbits round
31
+ */
32
+ camera?: boolean;
33
+ /**
34
+ * a readout of
35
+ * frame time, draw calls, triangles, programs, textures and geometries, in a corner of the canvas
36
+ */
37
+ stats?: boolean | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
38
+ };
39
+ export declare class DebugDraw {
40
+ #private;
41
+ overlay: HTMLElement;
42
+ /** @param {HTMLElement | null} overlay where the stats readout goes - the layer over the canvas */
43
+ constructor(overlay: HTMLElement | null);
44
+ /**
45
+ * Add the helpers to a frame about to be drawn, after everything in it. Returns how many items were
46
+ * added to each list, to take out again once it is drawn.
47
+ * @param {FrameDescription} frame @param {DebugOptions} o
48
+ * @param {{ camera: Camera, hovered: Hit | null, bounds: Box3 }} at
49
+ * @returns {{ opaque: number, transparent: number }}
50
+ */
51
+ add(frame: FrameDescription, o: DebugOptions, at: {
52
+ camera: Camera;
53
+ hovered: Hit | null;
54
+ bounds: Box3;
55
+ }): {
56
+ opaque: number;
57
+ transparent: number;
58
+ };
59
+ /**
60
+ * The corner readout, refreshed a few times a second.
61
+ * @param {DebugOptions['stats']} where
62
+ * @param {{ drawCalls: number, triangles: number, programs: number, textures: number, geometries: number, compiling: number }} info
63
+ * @param {number} frameMs time between drawn frames @param {number} cpuMs time the last frame took to draw
64
+ */
65
+ stats(where: DebugOptions['stats'], info: {
66
+ drawCalls: number;
67
+ triangles: number;
68
+ programs: number;
69
+ textures: number;
70
+ geometries: number;
71
+ compiling: number;
72
+ }, frameMs: number, cpuMs: number): void;
73
+ dispose(): void;
74
+ }
@@ -44,6 +44,10 @@ export type ExplainInfo = {
44
44
  * lines from nodes that describe themselves, such as charts
45
45
  */
46
46
  notes?: string[];
47
+ /**
48
+ * what debug drawing shows, when it is on
49
+ */
50
+ debug?: Record<string, unknown> | null;
47
51
  };
48
52
  /** @param {ExplainInfo} info @returns {string} */
49
53
  export declare function explain(info: ExplainInfo): string;
@@ -272,6 +272,10 @@ export type FrameDescription = {
272
272
  drawn: number;
273
273
  culled: number;
274
274
  };
275
+ /**
276
+ * the View's clock in seconds, for shader hooks that read uTime
277
+ */
278
+ time?: number;
275
279
  /**
276
280
  * set by the View for hover highlighting
277
281
  */
@@ -414,6 +418,7 @@ export type FrameDescription = {
414
418
  * @property {PointShadowFrame | null} pointShadow a point light's shadow, all round it
415
419
  * @property {FrameCap[]} caps section planes' caps to fill, after the opaque pass
416
420
  * @property {{ drawn: number, culled: number }} stats
421
+ * @property {number} [time] the View's clock in seconds, for shader hooks that read uTime
417
422
  * @property {FrameHighlight | null} highlight set by the View for hover highlighting
418
423
  * @property {any[]} overlays HTML elements carried by the nodes drawn (node.overlay)
419
424
  * @property {boolean} interactive some node has a tooltip, highlight or pointer handler
@@ -3,6 +3,39 @@ import { Texture } from './Texture.js';
3
3
  import type { ColorInput } from '../types.js';
4
4
  import type { TextureOptions } from './Texture.js';
5
5
  export type MapInput = Texture | TextureOptions | string;
6
+ export type ShaderUniform = number | number[] | ColorInput | Texture;
7
+ export type ShaderHooks = {
8
+ /**
9
+ * runs on each vertex before it is placed: change `local` (vec3, the
10
+ * geometry's own space) - and for meshes `n`, its normal; reads `uv` (meshes), `index` (the
11
+ * instance, point or segment), `size` (points: change it too) and `along` (lines: 0 at a
12
+ * segment's start, 1 at its end)
13
+ */
14
+ vertex?: string;
15
+ /**
16
+ * runs on each pixel before lighting: change `base` (vec4: linear
17
+ * colour and alpha) and, on meshes, `emissive` and `normal` (the shading normal, world space - bend it for
18
+ * relief lit per pixel); `discard` cuts the pixel away - shadows and
19
+ * picking too. Reads `vUv`, `vWorldPosition`, `vNormal` (meshes), `pointUv` and `index`
20
+ * (points), `distance` along the line and `index` the segment (lines)
21
+ */
22
+ fragment?: string;
23
+ /**
24
+ * runs after lighting: change `color` (vec3, linear) and `base.a` - glows,
25
+ * scanlines and fades the light doesn't touch
26
+ */
27
+ lit?: string;
28
+ /**
29
+ * values the hooks read, by name; change them on
30
+ * `material.shader.uniforms` and the View redraws
31
+ */
32
+ uniforms?: Record<string, ShaderUniform>;
33
+ /**
34
+ * how far, in world units, the vertex hook can move a vertex: bounds are
35
+ * padded by it for culling, framing and shadows. Without it a vertex hook turns frustum culling off
36
+ */
37
+ bounds?: number;
38
+ };
6
39
  export type MaterialOptions = {
7
40
  name?: string;
8
41
  /**
@@ -207,6 +240,11 @@ export type MaterialOptions = {
207
240
  * Points: sized in the world, so smaller with distance (default true)
208
241
  */
209
242
  sizeAttenuation?: boolean;
243
+ /**
244
+ * GLSL of your own, run at fixed points in the shader - waves,
245
+ * dissolves, glows (default none)
246
+ */
247
+ shader?: ShaderHooks | null;
210
248
  /**
211
249
  * Lines: width in pixels (default 1)
212
250
  */
@@ -252,6 +290,10 @@ export declare class Material {
252
290
  colour: string;
253
291
  emissiveColor: string;
254
292
  alpha: string;
293
+ onBeforeCompile: string;
294
+ vertexShader: string;
295
+ fragmentShader: string;
296
+ uniforms: string;
255
297
  };
256
298
  /** @type {string} */ name: string;
257
299
  /** @type {number} */ opacity: number;
@@ -292,6 +334,16 @@ export declare class Material {
292
334
  /** Lines: how far the dashes have moved along. @type {number} */ dashOffset: number;
293
335
  /** @param {MaterialOptions} [options] */
294
336
  constructor(options?: MaterialOptions);
337
+ /**
338
+ * GLSL hooks run inside the shader, or null. Change its uniforms in place
339
+ * (`material.shader.uniforms.uAmount = 2`); assign a new object to change the code.
340
+ * @returns {Required<Omit<ShaderHooks, 'bounds'>> & { bounds: number | null } | null}
341
+ */
342
+ get shader(): (Required<Omit<ShaderHooks, 'bounds'>> & {
343
+ bounds: number | null;
344
+ }) | null;
345
+ /** @param {ShaderHooks | null} v */
346
+ set shader(v: ShaderHooks | null);
295
347
  /** Base colour; assign any colour input. @returns {Color} */
296
348
  get color(): Color;
297
349
  /** @param {ColorInput} v */
@@ -1,14 +1,21 @@
1
1
  import type { Scene } from './scene/Scene.js';
2
2
  import type { Camera } from './scene/cameras.js';
3
+ /** Does a material's shader hook read the clock? @param {{ vertex: string, fragment: string, lit: string }} h */
4
+ export declare const usesTime: (h: {
5
+ vertex: string;
6
+ fragment: string;
7
+ lit: string;
8
+ }) => boolean;
3
9
  export declare class ChangeWatch {
4
10
  #private;
5
11
  /**
6
12
  * Has anything that affects the picture changed since the last call? The first call says yes.
7
13
  * @param {Scene} scene
8
14
  * @param {Camera} camera
15
+ * @param {number} [time] the View's clock, which shader hooks reading uTime follow
9
16
  * @returns {boolean}
10
17
  */
11
- changed(scene: Scene, camera: Camera): boolean;
18
+ changed(scene: Scene, camera: Camera, time?: number): boolean;
12
19
  /** Forget the last signature, so the next check reports a change. */
13
20
  reset(): void;
14
21
  }
@@ -0,0 +1,48 @@
1
+ import { Texture } from '../material/Texture.js';
2
+ import type { Material } from '../material/Material.js';
3
+ import type { HookSource } from './shaders.js';
4
+ import type { Program } from './programs.js';
5
+ export type Hook = {
6
+ /**
7
+ * its code and uniform types - one program per signature
8
+ */
9
+ signature: string;
10
+ /**
11
+ * HOOK_VERTEX, HOOK_FRAGMENT, HOOK_LIT for the parts it has, and its id
12
+ */
13
+ defines: string[];
14
+ /**
15
+ * what the shaders splice in
16
+ */
17
+ source: HookSource;
18
+ /**
19
+ * it has a vertex hook
20
+ */
21
+ moves: boolean;
22
+ };
23
+ /**
24
+ * The hooks a material draws with, or null: none, or ones that failed to compile.
25
+ * @param {Material} material
26
+ * @returns {Hook | null}
27
+ */
28
+ export declare function hookFor(material: Material): Hook | null;
29
+ /**
30
+ * Defines for a pass that isn't the colour pass: the vertex hook always - so a shadow, an outline or
31
+ * a pick is of the moved shape - and the fragment hook only where its discards should cut (meshes'
32
+ * shadows and picks); never the lit hook.
33
+ * @param {Hook} hook @param {boolean} cuts
34
+ */
35
+ export declare function passDefines(hook: Hook, cuts: boolean): string[];
36
+ /**
37
+ * Set the hook's uniforms: the clock, where the camera is, and the material's own values.
38
+ * @param {Program} program @param {Material} material @param {number} time
39
+ * @param {ArrayLike<number>} camera the camera's world position
40
+ * @param {(t: Texture) => WebGLTexture} texture
41
+ */
42
+ export declare function setHookUniforms(program: Program, material: Material, time: number, camera: ArrayLike<number>, texture: (t: Texture) => WebGLTexture): void;
43
+ /**
44
+ * A program built with a material's hooks failed: say why, once, in the hook's own terms, and draw
45
+ * the material without them from now on.
46
+ * @param {Material} material @param {Hook} hook @param {string} log the compiler's
47
+ */
48
+ export declare function hookFailed(material: Material, hook: Hook, log: string): void;
@@ -73,6 +73,10 @@ export declare class Passes {
73
73
  gl: WebGL2RenderingContext;
74
74
  programs: Programs;
75
75
  resources: Resources;
76
+ /** The View's clock, for shader hooks that read uTime - set by the renderer each frame. */
77
+ time: number;
78
+ /** The camera's world position, for shader hooks' fresnel and cameraPosition - set each frame too. @type {ArrayLike<number>} */
79
+ cameraPosition: ArrayLike<number>;
76
80
  /** Rendering to float textures, which environments need. */
77
81
  floatTargets: boolean;
78
82
  /** @param {WebGL2RenderingContext} gl @param {Programs} programs @param {Resources} resources */
@@ -1,4 +1,4 @@
1
- import type { ShaderKind } from './shaders.js';
1
+ import type { HookSource, ShaderKind } from './shaders.js';
2
2
  export type Program = {
3
3
  program: WebGLProgram;
4
4
  key: string;
@@ -14,12 +14,18 @@ export type Program = {
14
14
  * compiled, linked and its uniforms found
15
15
  */
16
16
  ready: boolean;
17
+ /**
18
+ * the compiler's log, for a program with a material's shader hooks that
19
+ * failed - those are reported and drawn without, rather than thrown
20
+ */
21
+ failed?: string;
17
22
  };
18
23
  export type Building = Program & {
19
24
  shaders: [WebGLShader, WebGLShader];
20
25
  sources: [string, string];
26
+ hooked: boolean;
21
27
  };
22
- /** @import { ShaderKind } from './shaders.js' */
28
+ /** @import { HookSource, ShaderKind } from './shaders.js' */
23
29
  /**
24
30
  * @typedef {object} Program
25
31
  * @property {WebGLProgram} program
@@ -27,9 +33,11 @@ export type Building = Program & {
27
33
  * @property {(name: string, value: any) => void} set set a uniform by name (ignored if the shader doesn't use it)
28
34
  * @property {number} frame the frame its per-frame uniforms were last set for
29
35
  * @property {boolean} ready compiled, linked and its uniforms found
36
+ * @property {string} [failed] the compiler's log, for a program with a material's shader hooks that
37
+ * failed - those are reported and drawn without, rather than thrown
30
38
  */
31
39
  /**
32
- * @typedef {Program & { shaders: [WebGLShader, WebGLShader], sources: [string, string] }} Building
40
+ * @typedef {Program & { shaders: [WebGLShader, WebGLShader], sources: [string, string], hooked: boolean }} Building
33
41
  */
34
42
  export declare class Programs {
35
43
  #private;
@@ -45,16 +53,18 @@ export declare class Programs {
45
53
  * @param {ShaderKind | string} kind a drawable kind, or a pass's name when it brings its sources
46
54
  * @param {string[]} defines
47
55
  * @param {[string, string]} [sources] a pass's vertex and fragment shaders
56
+ * @param {HookSource | null} [hooks] a material's shader hooks, spliced in (its id is in the defines)
48
57
  * @returns {Program}
49
58
  */
50
- get(kind: ShaderKind | string, defines: string[], sources?: [string, string]): Program;
59
+ get(kind: ShaderKind | string, defines: string[], sources?: [string, string], hooks?: HookSource | null): Program;
51
60
  /**
52
61
  * A program, waiting for it to finish compiling if it hasn't - for passes and picking, which
53
62
  * need their programs now.
54
63
  * @param {ShaderKind | string} kind @param {string[]} defines @param {[string, string]} [sources]
64
+ * @param {HookSource | null} [hooks]
55
65
  * @returns {Program}
56
66
  */
57
- ready(kind: ShaderKind | string, defines: string[], sources?: [string, string]): Program;
67
+ ready(kind: ShaderKind | string, defines: string[], sources?: [string, string], hooks?: HookSource | null): Program;
58
68
  /**
59
69
  * Has it finished compiling? Asks without waiting, and finishes it (finding its uniforms) once
60
70
  * it has.