phoria2 1.3.0 → 1.4.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.
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 +1223 -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 +88 -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,35 @@
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
12
17
  uniform uint uPickId;
13
18
  layout(location = 0) out uvec4 pickOut;
14
- ${o}
15
- ${i}
19
+ ${n}
20
+ ${a}
16
21
  void main() {
17
22
  #ifdef CLIP
18
23
  clipAway(vWorldPosition);
19
24
  #endif
25
+ #ifdef HOOK_FRAGMENT
26
+ {
27
+ vec4 base = vec4(vColor, 1.0);
28
+ vec3 emissive = vec3(0.0);
29
+ vec3 normal = normalize(vNormal);
30
+ if (base.a <= 0.0) discard;
31
+ }
32
+ #endif
20
33
  #ifdef ROUND
21
34
  #ifdef SPRITES
22
35
  vec2 p = vUv * 2.0 - 1.0;
@@ -27,11 +40,11 @@ if (dot(p, p) > 1.0) discard;
27
40
  #endif
28
41
  pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
29
42
  }
30
- `, u = {
31
- mesh: [r, l],
32
- points: [a, l],
33
- lines: [e, l]
34
- }, d = class {
43
+ `, p = {
44
+ mesh: [s, f],
45
+ points: [r, f],
46
+ lines: [e, f]
47
+ }, m = class {
35
48
  #e = null;
36
49
  #t = /* @__PURE__ */ new Uint32Array(4356);
37
50
  #n = /* @__PURE__ */ new Float32Array(16);
@@ -46,79 +59,79 @@ pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
46
59
  this.#e = null, this.#a = null, this.#o = !1;
47
60
  }
48
61
  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;
62
+ let o = this.gl, s = this.#s(e, t, n, r, i, a);
63
+ 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
64
  }
52
65
  pickAsync(e, t, n, r, i, a) {
53
66
  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);
67
+ let o = this.gl, s = this.#s(e, t, n, r, i, a);
68
+ if (!s) return Promise.resolve(null);
69
+ let c = this.#l();
70
+ 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);
71
+ let l = o.fenceSync(o.SYNC_GPU_COMMANDS_COMPLETE, 0);
59
72
  return o.flush(), this.#o = !0, new Promise((e) => {
60
73
  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)));
74
+ 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
75
  };
63
76
  setTimeout(t, 0);
64
77
  });
65
78
  }
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, [
79
+ #s(e, t, n, r, i, a) {
80
+ let s = this.gl, c = h(e);
81
+ if (!c.length) return null;
82
+ let l = this.#d();
83
+ 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
84
  0,
72
85
  0,
73
86
  0,
74
87
  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,
88
+ ]), s.clearBufferfv(s.DEPTH, 0, [1]);
89
+ let d = u, f = t / r * 2 - 1, p = 1 - n / i * 2, m = new Float32Array([
90
+ r / d,
78
91
  0,
79
92
  0,
80
93
  0,
81
94
  0,
82
- a / d,
95
+ i / d,
83
96
  0,
84
97
  0,
85
98
  0,
86
99
  0,
87
100
  1,
88
101
  0,
102
+ -f * r / d,
89
103
  -p * i / d,
90
- -m * a / d,
91
104
  0,
92
105
  1
93
106
  ]);
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
107
+ 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), {
108
+ items: c,
109
+ cx: f,
110
+ cy: p,
111
+ width: r,
112
+ height: i
100
113
  };
101
114
  }
102
115
  #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) {
116
+ let a = this.#t, o = (e, t) => (t * u + e) * 4, s = a[o(16, 16)] ? o(16, 16) : -1, c = 0, l = 0;
117
+ if (s < 0) {
105
118
  let t = Infinity;
106
- for (let n = -4; n <= c; n++) for (let r = -4; r <= c; r++) {
119
+ for (let n = -4; n <= d; n++) for (let r = -4; r <= d; r++) {
107
120
  let i = r * r + n * n;
108
121
  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);
122
+ let u = o(16 + r, 16 + n), d = a[u];
123
+ d && e[d - 1].node.drawMode !== "triangles" && (s = u, t = i, c = r, l = n);
111
124
  }
112
125
  }
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,
126
+ if (s < 0) return null;
127
+ let f = new Float32Array(new Uint32Array([a[s + 2]]).buffer)[0], p = [
128
+ t + c * 2 / r,
129
+ n + l * 2 / i,
117
130
  f * 2 - 1
118
131
  ];
119
132
  return {
120
- item: e[a[l] - 1],
121
- element: a[l + 1],
133
+ item: e[a[s] - 1],
134
+ element: a[s + 1],
122
135
  ndc: p
123
136
  };
124
137
  }
@@ -127,35 +140,39 @@ pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
127
140
  let e = this.gl;
128
141
  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
142
  }
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);
143
+ #u(e, n, r, a, o) {
144
+ 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"];
145
+ g && y.push("INSTANCED");
146
+ let b = v === "points" && d.drawsSprites;
147
+ v === "points" && (m.sizeAttenuation && y.push("SIZE_ATTENUATION"), m.map || y.push("ROUND"), b && y.push("SPRITES")), v === "mesh" && e.clip && y.push("CLIP");
148
+ let x = i(m);
149
+ x && y.push(...t(x, v === "mesh"));
150
+ let S = this.programs.ready(`pick-${v}`, y, p[v], x?.source);
151
+ if (!S.failed) {
152
+ 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") {
153
+ s.disable(s.CULL_FACE), S.set("uResolution", [u, u]), S.set("uWidth", m.width * o), S.set("uNear", r.camera.near);
154
+ let e = d.arrowBits ?? 0;
155
+ S.set("uArrows", e), S.set("uArrowSize", d.arrowPixels?.(m.width) * o || 0);
156
+ let t = this.resources.lines(f);
157
+ s.bindVertexArray(t.vao), s.drawArraysInstanced(s.TRIANGLES, 0, e ? 12 : 6, Math.min(t.count, Math.floor(f.drawnCount / 2)));
158
+ } else if (v === "points") {
159
+ 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) {
160
+ S.set("uResolution", [u, u]);
161
+ let e = this.resources.sprites(f);
162
+ s.bindVertexArray(e.vao), s.drawArraysInstanced(s.TRIANGLE_STRIP, 0, 4, e.count);
163
+ return;
164
+ }
165
+ let e = this.resources.mesh(f, null, !1);
166
+ s.bindVertexArray(e.vao), s.drawArrays(s.POINTS, 0, f.drawnVertices);
167
+ } 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);
168
+ }
152
169
  }
153
170
  #d() {
154
171
  if (this.#e) return this.#e;
155
172
  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);
173
+ e.activeTexture(e.TEXTURE15), e.bindTexture(e.TEXTURE_2D, t), e.texStorage2D(e.TEXTURE_2D, 1, e.RGBA32UI, u, u);
157
174
  let n = e.createRenderbuffer();
158
- e.bindRenderbuffer(e.RENDERBUFFER, n), e.renderbufferStorage(e.RENDERBUFFER, e.DEPTH_COMPONENT24, s, s);
175
+ e.bindRenderbuffer(e.RENDERBUFFER, n), e.renderbufferStorage(e.RENDERBUFFER, e.DEPTH_COMPONENT24, u, u);
159
176
  let r = e.createFramebuffer();
160
177
  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
178
  framebuffer: r,
@@ -164,7 +181,7 @@ pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
164
181
  }, this.#e;
165
182
  }
166
183
  };
167
- function f(e) {
184
+ function h(e) {
168
185
  let t = /* @__PURE__ */ new Map(), n = (e) => {
169
186
  if (!e) return !0;
170
187
  let r = t.get(e);
@@ -172,6 +189,6 @@ function f(e) {
172
189
  }, r = [...e.opaque, ...e.transparent].filter((e) => n(e.node));
173
190
  return [...r.filter((e) => e.material.depthTest), ...r.filter((e) => !e.material.depthTest)];
174
191
  }
175
- export { d as Picker };
192
+ export { m as Picker };
176
193
 
177
194
  //# 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.