phoria2 1.2.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 (69) hide show
  1. package/README.md +11 -9
  2. package/dist/bloom.js +4 -4
  3. package/dist/bloom.js.map +1 -1
  4. package/dist/charts.js +527 -510
  5. package/dist/charts.js.map +1 -1
  6. package/dist/core.js +1760 -1170
  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 +60 -60
  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 +155 -155
  15. package/dist/gltf.js.map +1 -1
  16. package/dist/keyboard.js +188 -0
  17. package/dist/keyboard.js.map +1 -0
  18. package/dist/motion.js +35 -35
  19. package/dist/motion.js.map +1 -1
  20. package/dist/particles.js +187 -114
  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 +95 -62
  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 +27 -0
  30. package/dist/types/area.d.ts +18 -0
  31. package/dist/types/debug.d.ts +74 -0
  32. package/dist/types/explain.d.ts +4 -0
  33. package/dist/types/frame.d.ts +5 -0
  34. package/dist/types/geometry/Geometry.d.ts +16 -0
  35. package/dist/types/index.d.ts +2 -0
  36. package/dist/types/material/Material.d.ts +67 -0
  37. package/dist/types/material/Texture.d.ts +18 -2
  38. package/dist/types/particles/Emitter.d.ts +39 -2
  39. package/dist/types/scene/drawables.d.ts +23 -2
  40. package/dist/types/watch.d.ts +8 -1
  41. package/dist/types/webgl/hooks.d.ts +48 -0
  42. package/dist/types/webgl/passes.d.ts +4 -0
  43. package/dist/types/webgl/programs.d.ts +15 -5
  44. package/dist/types/webgl/resources.d.ts +12 -2
  45. package/dist/types/webgl/shaders.d.ts +41 -5
  46. package/llms-full.txt +275 -1
  47. package/llms.txt +1 -1
  48. package/package.json +1 -1
  49. package/schema/scene.schema.json +75 -0
  50. package/src/View.js +121 -7
  51. package/src/area.js +131 -0
  52. package/src/charts/marks.js +29 -2
  53. package/src/debug.js +283 -0
  54. package/src/explain.js +19 -0
  55. package/src/frame.js +7 -2
  56. package/src/geometry/Geometry.js +16 -1
  57. package/src/index.js +1 -0
  58. package/src/material/Material.js +130 -4
  59. package/src/material/Texture.js +55 -2
  60. package/src/particles/Emitter.js +121 -10
  61. package/src/scene/drawables.js +89 -8
  62. package/src/watch.js +26 -3
  63. package/src/webgl/WebGLRenderer.js +55 -7
  64. package/src/webgl/hooks.js +121 -0
  65. package/src/webgl/passes.js +17 -2
  66. package/src/webgl/picking.js +45 -3
  67. package/src/webgl/programs.js +20 -9
  68. package/src/webgl/resources.js +169 -15
  69. package/src/webgl/shaders.js +545 -38
@@ -64,9 +64,15 @@ export type TextureOptions = {
64
64
  */
65
65
  flipY?: boolean;
66
66
  };
67
+ export type TextureRegion = {
68
+ x: number;
69
+ y: number;
70
+ width: number;
71
+ height: number;
72
+ };
67
73
  /**
68
74
  * An image for a material map. A video's frames upload by themselves as it plays; after drawing on
69
- * a canvas, call `needsUpdate()`.
75
+ * a canvas, call `needsUpdate()` - with the rectangle drawn in, to send only that.
70
76
  */
71
77
  export declare class Texture {
72
78
  #private;
@@ -114,7 +120,17 @@ export declare class Texture {
114
120
  /** 'srgb' for colour (decoded to linear by the GPU), 'linear' for data such as normals and roughness. Call needsUpdate() after changing it. @returns {'srgb' | 'linear'} */
115
121
  get colorSpace(): 'srgb' | 'linear';
116
122
  set colorSpace(v: 'srgb' | 'linear');
117
- needsUpdate(): this;
123
+ /**
124
+ * Upload the image again, after drawing on it (a canvas) or changing its pixels. Give the
125
+ * rectangle that changed - in the image's pixels, from its top left - and only that is sent: for a
126
+ * big canvas drawn on a little at a time, far less to send each frame.
127
+ *
128
+ * ctx.fillRect(x, y, 20, 20)
129
+ * texture.needsUpdate({ x, y, width: 20, height: 20 })
130
+ * @param {TextureRegion} [region]
131
+ * @returns {this}
132
+ */
133
+ needsUpdate(region?: TextureRegion): this;
118
134
  /** Release the GPU copy. @returns {void} */
119
135
  dispose(): void;
120
136
  /** A description (url plus options that differ from the defaults), or null for an in-memory image. */
@@ -61,6 +61,28 @@ export type EmitterOptions = NodeOptions & SpawnOptions & {
61
61
  frustumCulled?: boolean;
62
62
  renderOrder?: number;
63
63
  onParticle?: ((p: Particle) => boolean | void) | null;
64
+ sort?: boolean;
65
+ } & LookOptions;
66
+ export type LookOptions = {
67
+ /**
68
+ * a multiple of the material's size, or several each particle goes
69
+ * through over its life - [0.5, 3] for smoke that billows out (default 1)
70
+ */
71
+ size?: number | number[];
72
+ /**
73
+ * each particle's size varies by up to this share either way, 0-1 (default 0)
74
+ */
75
+ sizeSpread?: number;
76
+ /**
77
+ * 0-1, or several each particle goes through - [0, 1, 0] fades in
78
+ * and out (default 1)
79
+ */
80
+ opacity?: number | number[];
81
+ /**
82
+ * radians per second: a number turns each particle up to that
83
+ * fast either way, [min, max] a range. Spinning particles start at a random angle (default 0)
84
+ */
85
+ spin?: number | [number, number];
64
86
  };
65
87
  /**
66
88
  * Particles drawn as points. Add it to a scene and it runs itself.
@@ -102,11 +124,11 @@ export declare class Emitter extends Points {
102
124
  /**
103
125
  * Change several settings at once - a preset, say. Vectors are copied into the emitter's own,
104
126
  * so anything bound to `emitter.gravity` and friends stays bound. Live particles carry on.
105
- * @param {SpawnOptions & { rate?: number, gravity?: Vec3Input, drag?: number, follow?: Node | null,
127
+ * @param {SpawnOptions & LookOptions & { rate?: number, gravity?: Vec3Input, drag?: number, follow?: Node | null,
106
128
  * onParticle?: ((p: Particle) => boolean | void) | null }} options
107
129
  * @returns {this}
108
130
  */
109
- set(options: SpawnOptions & {
131
+ set(options: SpawnOptions & LookOptions & {
110
132
  rate?: number;
111
133
  gravity?: Vec3Input;
112
134
  drag?: number;
@@ -121,6 +143,21 @@ export declare class Emitter extends Points {
121
143
  set color(v: ColorInput | ColorInput[]);
122
144
  /** @returns {[number, number, number][]} */
123
145
  get color(): [number, number, number][];
146
+ /** Each particle's size over its life, as multiples of the material's. @param {number | number[]} v */
147
+ set size(v: number | number[]);
148
+ /** @returns {number[]} */
149
+ get size(): number[];
150
+ /** How much each particle's size varies, 0-1 either way. @param {number} v */
151
+ set sizeSpread(v: number);
152
+ get sizeSpread(): number;
153
+ /** Each particle's opacity over its life. @param {number | number[]} v */
154
+ set opacity(v: number | number[]);
155
+ /** @returns {number[]} */
156
+ get opacity(): number[];
157
+ /** How fast particles turn, radians per second, as a [min, max] range. @param {number | [number, number]} v */
158
+ set spin(v: number | [number, number]);
159
+ /** @returns {[number, number]} */
160
+ get spin(): [number, number];
124
161
  /**
125
162
  * Birth `count` particles at once - explosions, splashes. Options override the emitter's own
126
163
  * spawn settings for this burst only.
@@ -238,19 +238,40 @@ export declare class Points extends Drawable {
238
238
  static get OPTIONS(): string[];
239
239
  /** A label per point from its row of data - or a list of them, several per point. @type {ReturnType<typeof instanceLabels>} */
240
240
  labels: ReturnType<typeof instanceLabels>;
241
- /** @param {DrawableOptions & { labels?: InstanceLabels | InstanceLabels[] | null }} options */
241
+ /**
242
+ * Draw the points back to front, furthest first, each frame - for soft points that cover what is
243
+ * behind them (smoke, with blending 'normal'), which then layer the right way round. Drawn as squares.
244
+ */
245
+ sort: boolean;
246
+ /** @param {DrawableOptions & { labels?: InstanceLabels | InstanceLabels[] | null, sort?: boolean }} options */
242
247
  constructor(options: DrawableOptions & {
243
248
  labels?: InstanceLabels | InstanceLabels[] | null;
249
+ sort?: boolean;
244
250
  });
245
251
  /** @returns {'points'} */
246
252
  get drawMode(): 'points';
247
253
  }
254
+ export type LinesOptions = DrawableOptions & {
255
+ arrows?: 'none' | 'start' | 'end' | 'both';
256
+ arrowSize?: number;
257
+ };
258
+ /**
259
+ * @typedef {DrawableOptions & { arrows?: 'none' | 'start' | 'end' | 'both', arrowSize?: number }} LinesOptions
260
+ */
248
261
  /**
249
262
  * Line segments: indices (or consecutive vertices) in pairs. Width is in pixels and works on
250
- * every platform. edges(geometry) makes a wireframe of any geometry.
263
+ * every platform. edges(geometry) makes a wireframe of any geometry. Segments each starting where
264
+ * the one before ended make a chain - a polyline - which dashes run on along and arrowheads finish.
251
265
  */
252
266
  export declare class Lines extends Drawable {
253
267
  static type: string;
268
+ static get OPTIONS(): string[];
269
+ /** Arrowheads at the ends of each chain: at its 'start', its 'end', 'both' or 'none'. @type {'none' | 'start' | 'end' | 'both'} */
270
+ arrows: 'none' | 'start' | 'end' | 'both';
271
+ /** Arrowhead length in pixels; 0 sizes them from the material's width. */
272
+ arrowSize: number;
273
+ /** @param {LinesOptions} options */
274
+ constructor(options: LinesOptions);
254
275
  /** @returns {'lines'} */
255
276
  get drawMode(): 'lines';
256
277
  }
@@ -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.
@@ -101,8 +101,8 @@ export declare class Resources {
101
101
  count: number;
102
102
  } | null, withTangents?: boolean): number;
103
103
  /**
104
- * Vertex array for Lines: one instance per segment with its two endpoints (and colours),
105
- * expanded to a screen-space quad in the vertex shader.
104
+ * Vertex array for Lines: one instance per segment with its two endpoints (and colours) and where
105
+ * it is along its chain, expanded to a screen-space quad - and arrowheads - in the vertex shader.
106
106
  * @param {Geometry} geometry
107
107
  * @returns {MeshBuffers}
108
108
  */
@@ -119,6 +119,16 @@ export declare class Resources {
119
119
  * @param {object} owner @param {Scene} scene
120
120
  */
121
121
  release(owner: object, scene: Scene): void;
122
+ /**
123
+ * Vertex array for points drawn as squares: one instance a point - its position, colour, size,
124
+ * opacity, angle and index - over the corner strip lines use. Given a model-view matrix, the points
125
+ * go back to front (furthest first) - re-sorted every call, as the camera may have moved; without
126
+ * one, in their own order; with undefined, in whatever order they were last sent (picking, which
127
+ * reads each point's index and doesn't mind).
128
+ * @param {Geometry} geometry @param {ArrayLike<number> | null} [modelView]
129
+ * @returns {MeshBuffers}
130
+ */
131
+ sprites(geometry: Geometry, modelView?: ArrayLike<number> | null): MeshBuffers;
122
132
  /** Forget everything - after a lost context, when every GL object is already gone. */
123
133
  reset(): void;
124
134
  }
@@ -8,11 +8,16 @@ export declare const ATTRIBUTES: {
8
8
  instanceColor: number;
9
9
  tangent: number;
10
10
  uv2: number;
11
+ pointSize: number;
12
+ pointOpacity: number;
13
+ pointAngle: number;
14
+ pointIndex: number;
11
15
  segmentStart: number;
12
16
  segmentEnd: number;
13
17
  segmentColorStart: number;
14
18
  segmentColorEnd: number;
15
19
  corner: number;
20
+ segmentInfo: number;
16
21
  };
17
22
  export declare const CLIP = "\n#ifdef CLIP\nuniform vec4 uClipPlane; // world space, unit normal: cut away where dot(n, p) + d > 0\nvoid clipAway(vec3 p) {\n if (dot(uClipPlane.xyz, p) + uClipPlane.w > 0.0) discard;\n}\n#else\nvoid clipAway(vec3 p) {}\n#endif\n";
18
23
  export declare const FOG = "\n#ifdef FOG\nuniform vec3 uFogColor; // linear\nuniform vec2 uFogRange; // linear fog: near, far\nuniform float uFogDensity; // exponential fog\nin float vFogDepth;\nvec3 fogged(vec3 c) {\n#ifdef FOG_LINEAR\n float f = clamp((vFogDepth - uFogRange.x) / max(uFogRange.y - uFogRange.x, 1e-4), 0.0, 1.0);\n#else\n float d = uFogDensity * vFogDepth;\n float f = 1.0 - exp(-d * d);\n#endif\n#ifdef FOG_ADD\n return c * (1.0 - f);\n#elif defined(FOG_MULTIPLY)\n // a filter in the fog fades to white, which leaves what is behind it as it was\n return mix(c, vec3(1.0), f);\n#else\n return mix(c, uFogColor, f);\n#endif\n}\n#else\nvec3 fogged(vec3 c) { return c; }\n#endif\n";
@@ -21,14 +26,45 @@ export declare const COMMON = "\nvec3 srgbToLinear(vec3 c) {\n return mix(c / 1
21
26
  export declare const EQUIRECT = "\nvec2 equirectUv(vec3 d) {\n return vec2(atan(d.z, d.x) * 0.15915494 + 0.5, acos(clamp(d.y, -1.0, 1.0)) * 0.31830989);\n}\nvec3 equirectDirection(vec2 uv) {\n float phi = (uv.x - 0.5) * 6.28318531, theta = uv.y * 3.14159265;\n return vec3(cos(phi) * sin(theta), cos(theta), sin(phi) * sin(theta));\n}\n";
22
27
  export declare const FULLSCREEN_VERTEX = "\nout vec2 vUv;\nvoid main() {\n vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));\n vUv = p;\n gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);\n}\n";
23
28
  export declare const TONE_MAP = "\nuniform float uExposure;\nuniform int uToneMapping; // 0 none, 1 aces, 2 neutral\n\nvec3 toneMap(vec3 c) {\n c *= uExposure;\n if (uToneMapping == 1) {\n // ACES filmic, Narkowicz's fit\n c = clamp((c * (2.51 * c + 0.03)) / (c * (2.43 * c + 0.59) + 0.14), 0.0, 1.0);\n } else if (uToneMapping == 2) {\n // Khronos PBR Neutral\n float x = min(c.r, min(c.g, c.b));\n float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n c -= offset;\n float peak = max(c.r, max(c.g, c.b));\n if (peak > 0.8) {\n float d = 0.2;\n float newPeak = 1.0 - d * d / (peak + d - 0.8);\n c *= newPeak / peak;\n float g = 1.0 - 1.0 / (0.15 * (peak - newPeak) + 1.0);\n c = mix(c, vec3(newPeak), g);\n }\n }\n return c;\n}\n";
24
- export declare const MESH_VERTEX = "\nlayout(location = 0) in vec3 position;\nlayout(location = 1) in vec3 normal;\nlayout(location = 2) in vec2 uv;\nlayout(location = 3) in vec3 color;\n#ifdef INSTANCED\nlayout(location = 4) in mat4 instanceMatrix;\nlayout(location = 8) in vec3 instanceColor;\n#endif\n#ifdef ANISOTROPY\nlayout(location = 9) in vec4 tangent;\nout vec4 vTangent;\n#endif\n#ifdef UV2\nlayout(location = 10) in vec2 uv2;\nout vec2 vUv2;\n#endif\n\nuniform mat4 uModel;\nuniform mat3 uNormalMatrix;\nuniform mat4 uViewProjection;\nuniform int uHighlightIndex; // -2 none, -1 the whole node, otherwise the instance\n#ifdef FOG\nuniform mat4 uView;\nout float vFogDepth;\n#endif\n\nout vec3 vWorldPosition;\nout vec3 vNormal;\nout vec2 vUv;\nout vec3 vColor;\nflat out float vHighlight;\n#ifdef TRIPLANAR\n// the mesh's own space, so projected maps stay on it as it moves; and how its normals turn into the world\nout vec3 vObjectPosition;\nout vec3 vObjectNormal;\nout mat3 vObjectToWorld;\n#endif\n#ifdef PICK\nflat out uint vPickElement; // which instance, point or segment, for GPU picking\n#endif\n\n\nvec3 srgbToLinear(vec3 c) {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));\n}\nvec3 linearToSrgb(vec3 c) {\n c = clamp(c, 0.0, 1.0);\n return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));\n}\n\n\n// Matrices times vectors only - a matrix times a matrix per vertex is four times the work, and\n// with 100,000 instances the vertex stage is most of the frame.\nvoid main() {\n vColor = vec3(1.0);\n vHighlight = uHighlightIndex == -1 ? 1.0 : 0.0;\n#ifdef INSTANCED\n vec4 local = instanceMatrix * vec4(position, 1.0);\n // the instance's normal matrix as its cofactor: the inverse transpose up to scale, from three\n // cross products, flipped back for mirroring transforms\n mat3 m = mat3(instanceMatrix);\n mat3 instanceNormal = mat3(cross(m[1], m[2]), cross(m[2], m[0]), cross(m[0], m[1])) * sign(dot(m[0], cross(m[1], m[2])));\n vec3 n = instanceNormal * normal;\n vColor *= instanceColor;\n if (gl_InstanceID == uHighlightIndex) vHighlight = 1.0;\n#else\n vec4 local = vec4(position, 1.0);\n vec3 n = normal;\n#endif\n#ifdef VERTEX_COLORS\n vColor *= srgbToLinear(color);\n#endif\n vec4 world = uModel * local;\n vWorldPosition = world.xyz;\n vNormal = uNormalMatrix * n;\n vUv = uv;\n#ifdef UV2\n vUv2 = uv2;\n#endif\n#ifdef ANISOTROPY\n // a direction along the surface, so it turns as the model does\n#ifdef INSTANCED\n vTangent = vec4(mat3(uModel) * (m * tangent.xyz), tangent.w);\n#else\n vTangent = vec4(mat3(uModel) * tangent.xyz, tangent.w);\n#endif\n#endif\n#ifdef TRIPLANAR\n vObjectPosition = position;\n vObjectNormal = normal;\n#ifdef INSTANCED\n vObjectToWorld = uNormalMatrix * instanceNormal;\n#else\n vObjectToWorld = uNormalMatrix;\n#endif\n#endif\n gl_Position = uViewProjection * world;\n#ifdef FOG\n vFogDepth = -(uView * world).z;\n#endif\n#ifdef PICK\n vPickElement = uint(gl_InstanceID);\n#endif\n}\n";
25
- export declare const POINTS_VERTEX = "\nlayout(location = 0) in vec3 position;\nlayout(location = 3) in vec3 color;\n\nuniform mat4 uModel;\nuniform mat4 uView;\nuniform mat4 uProjection;\nuniform float uSize; // pixels (with the pixel ratio applied), or world units when attenuated\nuniform float uScale; // projection focal term x half the buffer height: world size -> pixels at depth 1\nuniform int uHighlightIndex; // -2 none, -1 every point, otherwise the point\n\nout vec3 vColor;\nflat out float vHighlight;\n#ifdef PICK\nflat out uint vPickElement; // which instance, point or segment, for GPU picking\n#endif\n#ifdef FOG\nout float vFogDepth;\n#endif\n\n\nvec3 srgbToLinear(vec3 c) {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));\n}\nvec3 linearToSrgb(vec3 c) {\n c = clamp(c, 0.0, 1.0);\n return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));\n}\n\n\nvoid main() {\n vec4 viewPosition = uView * (uModel * vec4(position, 1.0));\n gl_Position = uProjection * viewPosition;\n#ifdef FOG\n vFogDepth = -viewPosition.z;\n#endif\n#ifdef SIZE_ATTENUATION\n // perspective shrinks with distance; an orthographic projection (w row 0, 0, 0, 1) doesn't\n float depth = uProjection[3][3] == 1.0 ? 1.0 : max(-viewPosition.z, 1e-4);\n gl_PointSize = uSize * uScale / depth;\n#else\n gl_PointSize = uSize;\n#endif\n vColor = vec3(1.0);\n#ifdef VERTEX_COLORS\n vColor = srgbToLinear(color);\n#endif\n vHighlight = uHighlightIndex == -1 || gl_VertexID == uHighlightIndex ? 1.0 : 0.0;\n#ifdef PICK\n vPickElement = uint(gl_VertexID);\n#endif\n}\n";
26
- export declare const LINES_VERTEX = "\nlayout(location = 0) in vec3 segmentStart;\nlayout(location = 1) in vec3 segmentEnd;\nlayout(location = 2) in vec3 segmentColorStart;\nlayout(location = 3) in vec3 segmentColorEnd;\nlayout(location = 4) in vec2 corner; // x: 0 at start, 1 at end; y: -1 or 1 across\n\nuniform mat4 uModel;\nuniform mat4 uView;\nuniform mat4 uProjection;\nuniform vec2 uResolution; // drawing buffer size in pixels\nuniform float uWidth; // pixels, already scaled by the pixel ratio\nuniform float uNear;\n\nout vec3 vColor;\n#ifdef PICK\nflat out uint vPickElement; // which instance, point or segment, for GPU picking\n#endif\n#ifdef FOG\nout float vFogDepth;\n#endif\n\n\nvec3 srgbToLinear(vec3 c) {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));\n}\nvec3 linearToSrgb(vec3 c) {\n c = clamp(c, 0.0, 1.0);\n return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));\n}\n\n\nvoid main() {\n vec4 a = uView * (uModel * vec4(segmentStart, 1.0));\n vec4 b = uView * (uModel * vec4(segmentEnd, 1.0));\n // clip the segment to the near plane so lines passing behind the camera stay correct\n float clipZ = -uNear;\n if (a.z > clipZ && b.z > clipZ) {\n gl_Position = vec4(2.0, 2.0, 2.0, 1.0); // wholly behind: off screen\n return;\n }\n if (a.z > clipZ) a = mix(a, b, (clipZ - a.z) / (b.z - a.z));\n if (b.z > clipZ) b = mix(b, a, (clipZ - b.z) / (a.z - b.z));\n\n vec4 ca = uProjection * a;\n vec4 cb = uProjection * b;\n vec2 sa = ca.xy / ca.w * uResolution * 0.5;\n vec2 sb = cb.xy / cb.w * uResolution * 0.5;\n vec2 dir = sb - sa;\n float len = length(dir);\n dir = len > 0.0 ? dir / len : vec2(1.0, 0.0);\n vec2 normal = vec2(-dir.y, dir.x);\n\n vec4 clip = mix(ca, cb, corner.x);\n#ifdef FOG\n vFogDepth = -mix(a.z, b.z, corner.x);\n#endif\n // extend past each end by half the width so joins look continuous\n vec2 offset = normal * corner.y * uWidth * 0.5 + dir * (corner.x * 2.0 - 1.0) * uWidth * 0.5;\n clip.xy += offset / (uResolution * 0.5) * clip.w;\n gl_Position = clip;\n#ifdef PICK\n vPickElement = uint(gl_InstanceID);\n#endif\n\n vColor = vec3(1.0);\n#ifdef VERTEX_COLORS\n vColor = srgbToLinear(mix(segmentColorStart, segmentColorEnd, corner.x));\n#endif\n}\n";
29
+ export declare const MESH_VERTEX = "\nlayout(location = 0) in vec3 position;\nlayout(location = 1) in vec3 normal;\nlayout(location = 2) in vec2 uv;\nlayout(location = 3) in vec3 color;\n#ifdef INSTANCED\nlayout(location = 4) in mat4 instanceMatrix;\nlayout(location = 8) in vec3 instanceColor;\n#endif\n#ifdef ANISOTROPY\nlayout(location = 9) in vec4 tangent;\nout vec4 vTangent;\n#endif\n#ifdef UV2\nlayout(location = 10) in vec2 uv2;\nout vec2 vUv2;\n#endif\n\nuniform mat4 uModel;\nuniform mat3 uNormalMatrix;\nuniform mat4 uViewProjection;\nuniform int uHighlightIndex; // -2 none, -1 the whole node, otherwise the instance\n#ifdef FOG\nuniform mat4 uView;\nout float vFogDepth;\n#endif\n//@hook uniforms\n\nout vec3 vWorldPosition;\nout vec3 vNormal;\nout vec2 vUv;\nout vec3 vColor;\nflat out float vHighlight;\n#ifdef TRIPLANAR\n// the mesh's own space, so projected maps stay on it as it moves; and how its normals turn into the world\nout vec3 vObjectPosition;\nout vec3 vObjectNormal;\nout mat3 vObjectToWorld;\n#endif\n#ifdef PICK\nflat out uint vPickElement; // which instance, point or segment, for GPU picking\n#endif\n\n\nvec3 srgbToLinear(vec3 c) {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));\n}\nvec3 linearToSrgb(vec3 c) {\n c = clamp(c, 0.0, 1.0);\n return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));\n}\n\n\n// Matrices times vectors only - a matrix times a matrix per vertex is four times the work, and\n// with 100,000 instances the vertex stage is most of the frame.\nvoid main() {\n vColor = vec3(1.0);\n vHighlight = uHighlightIndex == -1 ? 1.0 : 0.0;\n // the vertex in the geometry's own space - where a shader hook moves it\n vec3 local = position;\n vec3 n = normal;\n#ifdef HOOK_VERTEX\n {\n int index = gl_InstanceID;\n//@hook vertex\n }\n#endif\n#ifdef INSTANCED\n vec4 placed = instanceMatrix * vec4(local, 1.0);\n // the instance's normal matrix as its cofactor: the inverse transpose up to scale, from three\n // cross products, flipped back for mirroring transforms\n mat3 m = mat3(instanceMatrix);\n mat3 instanceNormal = mat3(cross(m[1], m[2]), cross(m[2], m[0]), cross(m[0], m[1])) * sign(dot(m[0], cross(m[1], m[2])));\n vec3 placedNormal = instanceNormal * n;\n vColor *= instanceColor;\n if (gl_InstanceID == uHighlightIndex) vHighlight = 1.0;\n#else\n vec4 placed = vec4(local, 1.0);\n vec3 placedNormal = n;\n#endif\n#ifdef VERTEX_COLORS\n vColor *= srgbToLinear(color);\n#endif\n vec4 world = uModel * placed;\n vWorldPosition = world.xyz;\n vNormal = uNormalMatrix * placedNormal;\n vUv = uv;\n#ifdef UV2\n vUv2 = uv2;\n#endif\n#ifdef ANISOTROPY\n // a direction along the surface, so it turns as the model does\n#ifdef INSTANCED\n vTangent = vec4(mat3(uModel) * (m * tangent.xyz), tangent.w);\n#else\n vTangent = vec4(mat3(uModel) * tangent.xyz, tangent.w);\n#endif\n#endif\n#ifdef TRIPLANAR\n vObjectPosition = local;\n vObjectNormal = n;\n#ifdef INSTANCED\n vObjectToWorld = uNormalMatrix * instanceNormal;\n#else\n vObjectToWorld = uNormalMatrix;\n#endif\n#endif\n gl_Position = uViewProjection * world;\n#ifdef FOG\n vFogDepth = -(uView * world).z;\n#endif\n#ifdef PICK\n vPickElement = uint(gl_InstanceID);\n#endif\n}\n";
30
+ export declare const POINTS_VERTEX = "\nlayout(location = 0) in vec3 position;\nlayout(location = 3) in vec3 color;\n#ifdef SPRITES\n// each point a square: one instance a point, drawn as a strip over the corners (along 0-1, across -1-1)\nlayout(location = 4) in vec2 corner;\nlayout(location = 11) in float pointSize;\nlayout(location = 12) in float pointOpacity;\nlayout(location = 13) in float pointAngle;\nlayout(location = 14) in float pointIndex;\nuniform vec2 uResolution; // drawing buffer size in pixels\nout vec2 vUv;\nout float vOpacity;\n#endif\n\nuniform mat4 uModel;\nuniform mat4 uView;\nuniform mat4 uProjection;\nuniform float uSize; // pixels (with the pixel ratio applied), or world units when attenuated\nuniform float uScale; // projection focal term x half the buffer height: world size -> pixels at depth 1\nuniform int uHighlightIndex; // -2 none, -1 every point, otherwise the point\n//@hook uniforms\n\nout vec3 vColor;\nflat out float vHighlight;\n#ifdef HOOK_FRAGMENT\nflat out float vPointIndex;\n#endif\n#ifdef PICK\nflat out uint vPickElement; // which instance, point or segment, for GPU picking\n#endif\n#ifdef FOG\nout float vFogDepth;\n#endif\n\n\nvec3 srgbToLinear(vec3 c) {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));\n}\nvec3 linearToSrgb(vec3 c) {\n c = clamp(c, 0.0, 1.0);\n return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));\n}\n\n\nvoid main() {\n // the point in the geometry's own space, and its size as a multiple of the material's - where a\n // shader hook changes them\n vec3 local = position;\n#ifdef SPRITES\n int self = int(pointIndex + 0.5);\n float size = pointSize;\n#else\n int self = gl_VertexID;\n float size = 1.0;\n#endif\n#ifdef HOOK_VERTEX\n {\n int index = self;\n//@hook vertex\n }\n#endif\n#ifdef HOOK_FRAGMENT\n vPointIndex = float(self);\n#endif\n vec4 viewPosition = uView * (uModel * vec4(local, 1.0));\n#ifdef SPRITES\n // the square's corner, turned by the point's angle: the texture turns with it\n vec2 c = vec2(corner.x * 2.0 - 1.0, corner.y);\n float s = sin(pointAngle), k = cos(pointAngle);\n vec2 turned = vec2(c.x * k - c.y * s, c.x * s + c.y * k);\n float across = uSize * size;\n#ifdef SIZE_ATTENUATION\n // world units: the square sits in the view, facing the camera\n viewPosition.xy += turned * across * 0.5;\n gl_Position = uProjection * viewPosition;\n#else\n // pixels: the square is laid out on the screen\n gl_Position = uProjection * viewPosition;\n gl_Position.xy += turned * across / uResolution * gl_Position.w;\n#endif\n // the image upright on the square, its top at the top, as a point's is\n vUv = vec2(c.x * 0.5 + 0.5, 0.5 - c.y * 0.5);\n vOpacity = pointOpacity;\n#else\n gl_Position = uProjection * viewPosition;\n#endif\n#ifdef FOG\n vFogDepth = -viewPosition.z;\n#endif\n#ifdef SPRITES\n#elif defined(SIZE_ATTENUATION)\n // perspective shrinks with distance; an orthographic projection (w row 0, 0, 0, 1) doesn't\n float depth = uProjection[3][3] == 1.0 ? 1.0 : max(-viewPosition.z, 1e-4);\n gl_PointSize = uSize * size * uScale / depth;\n#else\n gl_PointSize = uSize * size;\n#endif\n vColor = vec3(1.0);\n#ifdef VERTEX_COLORS\n vColor = srgbToLinear(color);\n#endif\n vHighlight = uHighlightIndex == -1 || self == uHighlightIndex ? 1.0 : 0.0;\n#ifdef PICK\n vPickElement = uint(self);\n#endif\n}\n";
31
+ export declare const LINES_VERTEX = "\nlayout(location = 0) in vec3 segmentStart;\nlayout(location = 1) in vec3 segmentEnd;\nlayout(location = 2) in vec3 segmentColorStart;\nlayout(location = 3) in vec3 segmentColorEnd;\n// the distance along its chain (segments joined end to start) where it starts, in the model's\n// units; 1 if it starts a chain; 1 if it ends one\nlayout(location = 5) in vec3 segmentInfo;\n\nuniform mat4 uModel;\nuniform mat4 uView;\nuniform mat4 uProjection;\nuniform vec2 uResolution; // drawing buffer size in pixels\nuniform float uWidth; // pixels, already scaled by the pixel ratio\nuniform float uNear;\nuniform int uArrows; // arrowheads: 1 at the start of each chain, 2 at its end, 3 at both\nuniform float uArrowSize; // their length in pixels, scaled by the pixel ratio\n//@hook uniforms\n\n// six vertices an instance make the segment's quad, as two triangles - x: 0 at its start, 1 at its\n// end; y: -1 or 1 across - and six more its two arrowheads, drawn only when asked for\nconst vec2 QUAD[6] = vec2[6](vec2(0.0, -1.0), vec2(0.0, 1.0), vec2(1.0, -1.0), vec2(1.0, -1.0), vec2(0.0, 1.0), vec2(1.0, 1.0));\n\nout vec3 vColor;\n#if defined(DASHED) || defined(HOOK_FRAGMENT)\nout float vDistance; // along the chain, in the model's units\n#endif\n#ifdef HOOK_FRAGMENT\nflat out float vSegment; // which segment, for a fragment hook's index\n#endif\n#ifdef DASHED\nflat out float vArrow; // 1 on an arrowhead, which is never dashed\n#endif\n#ifdef PICK\nflat out uint vPickElement; // which instance, point or segment, for GPU picking\n#endif\n#ifdef FOG\nout float vFogDepth;\n#endif\n\n\nvec3 srgbToLinear(vec3 c) {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));\n}\nvec3 linearToSrgb(vec3 c) {\n c = clamp(c, 0.0, 1.0);\n return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));\n}\n\n\n#ifdef HOOK_VERTEX\n// a shader hook moves each end of each segment: local is the end, along 0 at the start and 1 at the end\nvec3 hookVertex(vec3 local, float along, int index) {\n//@hook vertex\n return local;\n}\n#endif\n\nvoid main() {\n vec3 start = segmentStart, end = segmentEnd;\n#ifdef HOOK_VERTEX\n start = hookVertex(segmentStart, 0.0, gl_InstanceID);\n end = hookVertex(segmentEnd, 1.0, gl_InstanceID);\n#endif\n vec4 a = uView * (uModel * vec4(start, 1.0));\n vec4 b = uView * (uModel * vec4(end, 1.0));\n // clip the segment to the near plane so lines passing behind the camera stay correct\n float clipZ = -uNear;\n if (a.z > clipZ && b.z > clipZ) {\n gl_Position = vec4(2.0, 2.0, 2.0, 1.0); // wholly behind: off screen\n return;\n }\n if (a.z > clipZ) a = mix(a, b, (clipZ - a.z) / (b.z - a.z));\n if (b.z > clipZ) b = mix(b, a, (clipZ - b.z) / (a.z - b.z));\n\n vec4 ca = uProjection * a;\n vec4 cb = uProjection * b;\n vec2 sa = ca.xy / ca.w * uResolution * 0.5;\n vec2 sb = cb.xy / cb.w * uResolution * 0.5;\n vec2 dir = sb - sa;\n float len = length(dir);\n dir = len > 0.0 ? dir / len : vec2(1.0, 0.0);\n vec2 normal = vec2(-dir.y, dir.x);\n\n bool arrowStart = (uArrows & 1) != 0 && segmentInfo.y > 0.5;\n bool arrowEnd = (uArrows & 2) != 0 && segmentInfo.z > 0.5;\n float along, arrow = 0.0;\n vec2 offset;\n if (gl_VertexID < 6) {\n vec2 c = QUAD[gl_VertexID];\n along = c.x;\n // past each end by half the width so joins look continuous - or short of it, under an arrowhead\n float startOut = arrowStart ? -uArrowSize * 0.8 : uWidth * 0.5;\n float endOut = arrowEnd ? -uArrowSize * 0.8 : uWidth * 0.5;\n offset = normal * c.y * uWidth * 0.5 + dir * (along > 0.5 ? endOut : -startOut);\n } else {\n // an arrowhead: its tip on the chain's end, pointing out along the segment\n int which = (gl_VertexID - 6) / 3, k = gl_VertexID - 6 - which * 3;\n if (which == 0 ? !arrowStart : !arrowEnd) {\n gl_Position = vec4(2.0, 2.0, 2.0, 1.0);\n return;\n }\n along = float(which);\n vec2 d = which == 1 ? dir : -dir;\n float wing = uArrowSize * 0.45;\n offset = k == 0 ? vec2(0.0) : -d * uArrowSize + normal * (k == 1 ? wing : -wing);\n arrow = 1.0;\n }\n\n vec4 clip = mix(ca, cb, along);\n#ifdef FOG\n vFogDepth = -mix(a.z, b.z, along);\n#endif\n clip.xy += offset / (uResolution * 0.5) * clip.w;\n gl_Position = clip;\n#if defined(DASHED) || defined(HOOK_FRAGMENT)\n vDistance = segmentInfo.x + length(end - start) * along;\n#endif\n#ifdef HOOK_FRAGMENT\n vSegment = float(gl_InstanceID);\n#endif\n#ifdef DASHED\n vArrow = arrow;\n#endif\n#ifdef PICK\n vPickElement = uint(gl_InstanceID);\n#endif\n\n vColor = vec3(1.0);\n#ifdef VERTEX_COLORS\n vColor = srgbToLinear(mix(segmentColorStart, segmentColorEnd, along));\n#endif\n}\n";
27
32
  export type ShaderKind = 'mesh' | 'points' | 'lines';
33
+ export type HookSource = {
34
+ /**
35
+ * declarations
36
+ */
37
+ uniforms: string;
38
+ /**
39
+ * functions every hook can call - hash, noise, fbm
40
+ */
41
+ helpers?: string;
42
+ vertex: string;
43
+ fragment: string;
44
+ lit: string;
45
+ };
46
+ /**
47
+ * GLSL of a material's own, spliced in at the shaders' `//@hook` lines: uniform declarations, and
48
+ * code run on each vertex, before lighting and after it.
49
+ * @typedef {object} HookSource
50
+ * @property {string} uniforms declarations
51
+ * @property {string} [helpers] functions every hook can call - hash, noise, fbm
52
+ * @property {string} vertex
53
+ * @property {string} fragment
54
+ * @property {string} lit
55
+ */
56
+ export declare const HOOK_HELPERS = "\nhighp uint phoriaMix(highp uint h) {\n h ^= h >> 16u; h *= 0x7feb352du; h ^= h >> 15u; h *= 0x846ca68bu; h ^= h >> 16u;\n return h;\n}\nfloat phoriaUnit(highp uint h) { return float(h >> 8u) * (1.0 / 16777216.0); }\nfloat hash(float x) { return phoriaUnit(phoriaMix(floatBitsToUint(x))); }\nfloat hash(vec2 p) { highp uvec2 u = floatBitsToUint(p); return phoriaUnit(phoriaMix(u.x ^ phoriaMix(u.y + 0x9e3779b9u))); }\nfloat hash(vec3 p) { highp uvec3 u = floatBitsToUint(p); return phoriaUnit(phoriaMix(u.x ^ phoriaMix(u.y ^ phoriaMix(u.z + 0x9e3779b9u)))); }\n// a lattice point's bits: each coordinate times its own large odd number, mixed once\nhighp uint phoriaCell(highp ivec3 c) {\n highp uvec3 u = uvec3(c) * uvec3(0x8da6b343u, 0xd8163841u, 0xcb1ab31fu);\n return phoriaMix(u.x ^ u.y ^ u.z);\n}\n\nfloat vnoise(vec3 p) {\n vec3 i = floor(p), f = p - i;\n vec3 s = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n highp ivec3 c = ivec3(i);\n float a = mix(phoriaUnit(phoriaCell(c)), phoriaUnit(phoriaCell(c + ivec3(1, 0, 0))), s.x);\n float b = mix(phoriaUnit(phoriaCell(c + ivec3(0, 1, 0))), phoriaUnit(phoriaCell(c + ivec3(1, 1, 0))), s.x);\n float d = mix(phoriaUnit(phoriaCell(c + ivec3(0, 0, 1))), phoriaUnit(phoriaCell(c + ivec3(1, 0, 1))), s.x);\n float e = mix(phoriaUnit(phoriaCell(c + ivec3(0, 1, 1))), phoriaUnit(phoriaCell(c + ivec3(1, 1, 1))), s.x);\n return mix(mix(a, b, s.y), mix(d, e, s.y), s.z);\n}\nfloat vnoise(vec2 p) {\n vec2 i = floor(p), f = p - i;\n vec2 s = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n highp ivec3 c = ivec3(ivec2(i), 0);\n return mix(mix(phoriaUnit(phoriaCell(c)), phoriaUnit(phoriaCell(c + ivec3(1, 0, 0))), s.x),\n mix(phoriaUnit(phoriaCell(c + ivec3(0, 1, 0))), phoriaUnit(phoriaCell(c + ivec3(1, 1, 0))), s.x), s.y);\n}\n\n// simplex: a corner's share of the value and the gradient - t^4 times the gradient's ramp, falling\n// to nothing at the corner's reach\nvec2 phoriaGrad2(highp ivec2 c) {\n float a = phoriaUnit(phoriaCell(ivec3(c, 7))) * 6.28318530718;\n return vec2(cos(a), sin(a));\n}\nvoid phoriaCorner2(vec2 x, highp ivec2 c, inout float n, inout vec2 d) {\n float t = 0.5 - dot(x, x);\n if (t <= 0.0) return;\n vec2 g = phoriaGrad2(c);\n float t2 = t * t, gx = dot(g, x);\n n += t2 * t2 * gx;\n d += t2 * t2 * g - 8.0 * t2 * t * gx * x;\n}\nvec3 snoised(vec2 p) {\n const float F = 0.366025403784, G = 0.211324865405;\n vec2 i = floor(p + (p.x + p.y) * F);\n vec2 x0 = p - i + (i.x + i.y) * G;\n vec2 o = x0.x > x0.y ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n highp ivec2 c = ivec2(i);\n float n = 0.0;\n vec2 d = vec2(0.0);\n phoriaCorner2(x0, c, n, d);\n phoriaCorner2(x0 - o + G, c + ivec2(o), n, d);\n phoriaCorner2(x0 - 1.0 + 2.0 * G, c + 1, n, d);\n // unit gradients peak near 1/70: scaled to -1-1, then to 0-1\n return vec3(0.5 + 35.0 * n, 35.0 * d);\n}\nfloat snoise(vec2 p) { return snoised(p).x; }\n\nconst vec3 PHORIA_EDGES[12] = vec3[12](vec3(1, 1, 0), vec3(-1, 1, 0), vec3(1, -1, 0), vec3(-1, -1, 0),\n vec3(1, 0, 1), vec3(-1, 0, 1), vec3(1, 0, -1), vec3(-1, 0, -1), vec3(0, 1, 1), vec3(0, -1, 1), vec3(0, 1, -1), vec3(0, -1, -1));\nvoid phoriaCorner3(vec3 x, highp ivec3 c, inout float n, inout vec3 d) {\n float t = 0.6 - dot(x, x);\n if (t <= 0.0) return;\n vec3 g = PHORIA_EDGES[phoriaCell(c) % 12u];\n float t2 = t * t, gx = dot(g, x);\n n += t2 * t2 * gx;\n d += t2 * t2 * g - 8.0 * t2 * t * gx * x;\n}\nvec4 snoised(vec3 p) {\n const float F = 1.0 / 3.0, G = 1.0 / 6.0;\n vec3 i = floor(p + dot(p, vec3(F)));\n vec3 x0 = p - i + dot(i, vec3(G));\n vec3 g = step(x0.yzx, x0.xyz), l = 1.0 - g;\n vec3 i1 = min(g, l.zxy), i2 = max(g, l.zxy);\n highp ivec3 c = ivec3(i);\n float n = 0.0;\n vec3 d = vec3(0.0);\n phoriaCorner3(x0, c, n, d);\n phoriaCorner3(x0 - i1 + G, c + ivec3(i1), n, d);\n phoriaCorner3(x0 - i2 + 2.0 * G, c + ivec3(i2), n, d);\n phoriaCorner3(x0 - 1.0 + 3.0 * G, c + 1, n, d);\n // edge gradients peak near 1/32: scaled to -1-1, then to 0-1\n return vec4(0.5 + 16.0 * n, 16.0 * d);\n}\nfloat snoise(vec3 p) { return snoised(p).x; }\n\n// fbm: each octave turned (so grids don't line up into a visible lattice), scaled by 2.03 and moved\n// on, its amplitude halved; the sum divided by the amplitudes, so 0-1 like the noise\nconst mat2 PHORIA_TURN2 = mat2(0.8, 0.6, -0.6, 0.8);\nconst mat3 PHORIA_TURN3 = mat3(0.00, 0.80, 0.60, -0.80, 0.36, -0.48, -0.60, -0.48, 0.64);\nfloat vfbm(vec2 p, int octaves) {\n float v = 0.0, a = 0.5, sum = 0.0;\n for (int i = 0; i < clamp(octaves, 1, 12); i++) { v += a * vnoise(p); sum += a; p = PHORIA_TURN2 * p * 2.03 + 17.0; a *= 0.5; }\n return v / sum;\n}\nfloat vfbm(vec3 p, int octaves) {\n float v = 0.0, a = 0.5, sum = 0.0;\n for (int i = 0; i < clamp(octaves, 1, 12); i++) { v += a * vnoise(p); sum += a; p = PHORIA_TURN3 * p * 2.03 + 17.0; a *= 0.5; }\n return v / sum;\n}\nfloat sfbm(vec2 p, int octaves) {\n float v = 0.0, a = 0.5, sum = 0.0;\n for (int i = 0; i < clamp(octaves, 1, 12); i++) { v += a * snoise(p); sum += a; p = PHORIA_TURN2 * p * 2.03 + 17.0; a *= 0.5; }\n return v / sum;\n}\nfloat sfbm(vec3 p, int octaves) {\n float v = 0.0, a = 0.5, sum = 0.0;\n for (int i = 0; i < clamp(octaves, 1, 12); i++) { v += a * snoise(p); sum += a; p = PHORIA_TURN3 * p * 2.03 + 17.0; a *= 0.5; }\n return v / sum;\n}\n// with the gradient: each octave's, carried back through the turns and scalings so far\nvec3 sfbmd(vec2 p, int octaves) {\n float v = 0.0, a = 0.5, sum = 0.0;\n vec2 d = vec2(0.0);\n mat2 m = mat2(1.0);\n for (int i = 0; i < clamp(octaves, 1, 12); i++) {\n vec3 n = snoised(p);\n v += a * n.x;\n d += a * (transpose(m) * n.yz);\n sum += a;\n p = PHORIA_TURN2 * p * 2.03 + 17.0;\n m = PHORIA_TURN2 * m * 2.03;\n a *= 0.5;\n }\n return vec3(v, d) / sum;\n}\nvec4 sfbmd(vec3 p, int octaves) {\n float v = 0.0, a = 0.5, sum = 0.0;\n vec3 d = vec3(0.0);\n mat3 m = mat3(1.0);\n for (int i = 0; i < clamp(octaves, 1, 12); i++) {\n vec4 n = snoised(p);\n v += a * n.x;\n d += a * (transpose(m) * n.yzw);\n sum += a;\n p = PHORIA_TURN3 * p * 2.03 + 17.0;\n m = PHORIA_TURN3 * m * 2.03;\n a *= 0.5;\n }\n return vec4(v, d) / sum;\n}\nfloat vfbm(vec2 p) { return vfbm(p, 5); }\nfloat vfbm(vec3 p) { return vfbm(p, 5); }\nfloat sfbm(vec2 p) { return sfbm(p, 5); }\nfloat sfbm(vec3 p) { return sfbm(p, 5); }\nvec3 sfbmd(vec2 p) { return sfbmd(p, 5); }\nvec4 sfbmd(vec3 p) { return sfbmd(p, 5); }\n\n// cellular noise: each unit cell's own point, from its hash; the nearest and second nearest of the\n// 3 x 3 (or 3 x 3 x 3) cells round p - F1, F2 - and the nearest cell's own random number\nvec3 voronoi(vec2 p) {\n vec2 i = floor(p), f = p - i;\n highp ivec2 c = ivec2(i);\n float f1 = 8.0, f2 = 8.0, id = 0.0;\n for (int y = -1; y <= 1; y++) {\n for (int x = -1; x <= 1; x++) {\n highp uint h = phoriaCell(ivec3(c + ivec2(x, y), 11));\n vec2 o = vec2(float(x), float(y)) + vec2(phoriaUnit(h), phoriaUnit(phoriaMix(h ^ 0x68e31da4u))) - f;\n float d = dot(o, o);\n if (d < f1) { f2 = f1; f1 = d; id = phoriaUnit(phoriaMix(h + 0x1b56c4e9u)); }\n else if (d < f2) f2 = d;\n }\n }\n return vec3(sqrt(f1), sqrt(f2), id);\n}\nvec3 voronoi(vec3 p) {\n vec3 i = floor(p), f = p - i;\n highp ivec3 c = ivec3(i);\n float f1 = 8.0, f2 = 8.0, id = 0.0;\n for (int z = -1; z <= 1; z++) {\n for (int y = -1; y <= 1; y++) {\n for (int x = -1; x <= 1; x++) {\n highp uint h = phoriaCell(c + ivec3(x, y, z)) ^ 0x2545f491u;\n h = phoriaMix(h);\n vec3 o = vec3(float(x), float(y), float(z)) - f\n + vec3(phoriaUnit(h), phoriaUnit(phoriaMix(h ^ 0x68e31da4u)), phoriaUnit(phoriaMix(h ^ 0xb5297a4du)));\n float d = dot(o, o);\n if (d < f1) { f2 = f1; f1 = d; id = phoriaUnit(phoriaMix(h + 0x1b56c4e9u)); }\n else if (d < f2) f2 = d;\n }\n }\n }\n return vec3(sqrt(f1), sqrt(f2), id);\n}\n\n// a colour as picked (sRGB) to the linear colour hooks work in\nvec3 srgb(vec3 c) { return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(vec3(0.04045), c)); }\n// hue, saturation and value 0-1 to a linear colour - the colour a colour picker shows\nvec3 hsv2rgb(vec3 hsv) {\n vec3 k = clamp(abs(mod(hsv.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);\n return srgb(hsv.z * mix(vec3(1.0), k, hsv.y));\n}\nvec3 hsv2rgb(float h, float s, float v) { return hsv2rgb(vec3(h, s, v)); }\n// a cosine palette (Quilez): a + b cos(2 pi (c t + d)), its four vectors as picked; a linear colour\nvec3 palette(float t, vec3 a, vec3 b, vec3 c, vec3 d) { return srgb(clamp(a + b * cos(6.28318530718 * (c * t + d)), 0.0, 1.0)); }\n// how edge-on a surface is seen: 0 facing the camera, 1 side on, sharpened by the power - rim light\nfloat fresnel(vec3 normal, vec3 position, float power) {\n vec3 v = normalize(cameraPosition - position);\n return pow(1.0 - clamp(abs(dot(normalize(normal), v)), 0.0, 1.0), power);\n}\n// turning: rotate2(a) * v anticlockwise by a radians; rotate3(axis, a) * v about an axis\nmat2 rotate2(float a) { float s = sin(a), c = cos(a); return mat2(c, s, -s, c); }\nmat3 rotate3(vec3 axis, float a) {\n vec3 u = normalize(axis);\n float s = sin(a), c = cos(a), t = 1.0 - c;\n return mat3(t * u.x * u.x + c, t * u.x * u.y + s * u.z, t * u.x * u.z - s * u.y,\n t * u.x * u.y - s * u.z, t * u.y * u.y + c, t * u.y * u.z + s * u.x,\n t * u.x * u.z + s * u.y, t * u.y * u.z - s * u.x, t * u.z * u.z + c);\n}\n// x from one range to another, not clamped\nfloat remap(float x, float a, float b, float c, float d) { return c + (x - a) * (d - c) / (b - a); }\nvec2 remap(vec2 x, float a, float b, float c, float d) { return c + (x - a) * (d - c) / (b - a); }\nvec3 remap(vec3 x, float a, float b, float c, float d) { return c + (x - a) * (d - c) / (b - a); }\n// clamped to 0-1, as HLSL has it\nfloat saturate(float x) { return clamp(x, 0.0, 1.0); }\nvec2 saturate(vec2 x) { return clamp(x, 0.0, 1.0); }\nvec3 saturate(vec3 x) { return clamp(x, 0.0, 1.0); }\nvec4 saturate(vec4 x) { return clamp(x, 0.0, 1.0); }\n";
57
+ export declare const HOOK_SOURCE_NUMBERS: {
58
+ vertex: number;
59
+ fragment: number;
60
+ lit: number;
61
+ };
28
62
  /**
29
- * Vertex and fragment source for a kind of drawable, or a pass's own pair, with a set of feature defines.
63
+ * Vertex and fragment source for a kind of drawable, or a pass's own pair, with a set of feature
64
+ * defines - and a material's shader hooks, when it has them.
30
65
  * @param {ShaderKind | [string, string]} kind
31
66
  * @param {string[]} defines e.g. ['LIT', 'MAP']
67
+ * @param {HookSource | null} [hooks]
32
68
  * @returns {[string, string]}
33
69
  */
34
- export declare function shaderSource(kind: ShaderKind | [string, string], defines: string[]): [string, string];
70
+ export declare function shaderSource(kind: ShaderKind | [string, string], defines: string[], hooks?: HookSource | null): [string, string];