phoria2 1.0.0 → 1.2.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 (76) hide show
  1. package/README.md +71 -13
  2. package/dist/bloom.js +1 -1
  3. package/dist/charts.js +397 -384
  4. package/dist/charts.js.map +1 -1
  5. package/dist/core.js +4451 -3568
  6. package/dist/core.js.map +1 -1
  7. package/dist/edit.js +67 -67
  8. package/dist/edit.js.map +1 -1
  9. package/dist/explain.js +6 -2
  10. package/dist/explain.js.map +1 -1
  11. package/dist/gltf.js +211 -166
  12. package/dist/gltf.js.map +1 -1
  13. package/dist/motion.js +26 -26
  14. package/dist/motion.js.map +1 -1
  15. package/dist/particles.js +49 -49
  16. package/dist/particles.js.map +1 -1
  17. package/dist/passes.js +188 -86
  18. package/dist/passes.js.map +1 -1
  19. package/dist/phoria2.js +1 -1
  20. package/dist/picking.js +8 -13
  21. package/dist/picking.js.map +1 -1
  22. package/dist/snapshot.js +1 -1
  23. package/dist/tooltip.js +3 -3
  24. package/dist/tooltip.js.map +1 -1
  25. package/dist/types/OrbitControl.d.ts +6 -2
  26. package/dist/types/Raycaster.d.ts +10 -0
  27. package/dist/types/View.d.ts +64 -1
  28. package/dist/types/charts/Chart.d.ts +7 -0
  29. package/dist/types/explain.d.ts +3 -0
  30. package/dist/types/frame.d.ts +7 -0
  31. package/dist/types/geometry/Geometry.d.ts +14 -2
  32. package/dist/types/keyboard.d.ts +48 -0
  33. package/dist/types/labels.d.ts +4 -0
  34. package/dist/types/material/Material.d.ts +125 -0
  35. package/dist/types/material/Texture.d.ts +37 -5
  36. package/dist/types/scene/Node.d.ts +19 -14
  37. package/dist/types/scene/drawables.d.ts +32 -14
  38. package/dist/types/scene/lights.d.ts +16 -2
  39. package/dist/types/shadow.d.ts +2 -1
  40. package/dist/types/types.d.ts +14 -0
  41. package/dist/types/value/Color.d.ts +0 -4
  42. package/dist/types/value/Quat.d.ts +4 -2
  43. package/dist/types/webgl/WebGLRenderer.d.ts +6 -0
  44. package/dist/types/webgl/passes.d.ts +37 -13
  45. package/dist/types/webgl/resources.d.ts +24 -2
  46. package/dist/types/webgl/shaders.d.ts +4 -2
  47. package/llms-full.txt +153 -15
  48. package/package.json +4 -1
  49. package/schema/scene.schema.json +146 -1
  50. package/src/OrbitControl.js +7 -3
  51. package/src/Raycaster.js +58 -0
  52. package/src/View.js +207 -42
  53. package/src/charts/Chart.js +20 -2
  54. package/src/explain.js +6 -2
  55. package/src/frame.js +29 -9
  56. package/src/geometry/Geometry.js +24 -4
  57. package/src/gltf/load.js +50 -6
  58. package/src/keyboard.js +298 -0
  59. package/src/labels.js +4 -1
  60. package/src/material/Material.js +104 -19
  61. package/src/material/Texture.js +116 -11
  62. package/src/scene/Node.js +20 -9
  63. package/src/scene/Scene.js +2 -0
  64. package/src/scene/cameras.js +4 -1
  65. package/src/scene/drawables.js +48 -12
  66. package/src/scene/lights.js +40 -6
  67. package/src/shadow.js +27 -4
  68. package/src/types.js +6 -0
  69. package/src/value/Color.js +21 -0
  70. package/src/value/Quat.js +15 -6
  71. package/src/watch.js +25 -1
  72. package/src/webgl/WebGLRenderer.js +51 -12
  73. package/src/webgl/passes.js +190 -32
  74. package/src/webgl/picking.js +0 -1
  75. package/src/webgl/resources.js +89 -8
  76. package/src/webgl/shaders.js +350 -48
@@ -4,7 +4,7 @@ import type { Geometry } from '../geometry/Geometry.js';
4
4
  import type { Material, MaterialOptions } from '../material/Material.js';
5
5
  import type { GeometryOptions } from '../geometry/Geometry.js';
6
6
  import type { NodeOptions } from './Node.js';
7
- import type { ColorInput, Vec3Input } from '../types.js';
7
+ import type { ColorInput, QuatInput, Vec3Input } from '../types.js';
8
8
  export type DrawableOptions = NodeOptions & {
9
9
  geometry: Geometry | GeometryOptions;
10
10
  material?: Material | MaterialOptions;
@@ -52,6 +52,11 @@ export type LabelPlacement = {
52
52
  * the edge and up or down (default null: by its point)
53
53
  */
54
54
  edge?: 'left' | 'right' | null;
55
+ /**
56
+ * show the label only when zoomed in this far: at least this many CSS pixels to
57
+ * one world unit, at the label's point - details that appear as you come closer (default 0: always)
58
+ */
59
+ minScale?: number;
55
60
  };
56
61
  export type LabelPlace = 'center' | 'above' | 'below' | 'left' | 'right';
57
62
  export type InstanceLabels = Omit<LabelPlacement, 'priority'> & {
@@ -64,7 +69,7 @@ export type InstanceLabels = Omit<LabelPlacement, 'priority'> & {
64
69
  /** @import { Material, MaterialOptions } from '../material/Material.js' */
65
70
  /** @import { GeometryOptions } from '../geometry/Geometry.js' */
66
71
  /** @import { NodeOptions } from './Node.js' */
67
- /** @import { ColorInput, Vec3Input } from '../types.js' */
72
+ /** @import { ColorInput, QuatInput, Vec3Input } from '../types.js' */
68
73
  /**
69
74
  * @typedef {NodeOptions & {
70
75
  * geometry: Geometry | GeometryOptions,
@@ -95,10 +100,13 @@ export type InstanceLabels = Omit<LabelPlacement, 'priority'> & {
95
100
  * @property {'left' | 'right' | null} [edge] set the label at the view's left or right edge, level with its
96
101
  * point - callouts in a column down the side of a diagram, with leader: true. offset then moves it in from
97
102
  * the edge and up or down (default null: by its point)
103
+ * @property {number} [minScale] show the label only when zoomed in this far: at least this many CSS pixels to
104
+ * one world unit, at the label's point - details that appear as you come closer (default 0: always)
98
105
  */
99
106
  /** @typedef {'center' | 'above' | 'below' | 'left' | 'right'} LabelPlace */
100
107
  /**
101
- * One label per instance or point, from its row of data.
108
+ * One label per instance or point, from its row of data - or a list of these, for several each (a symbol
109
+ * always, its details when zoomed in).
102
110
  * @typedef {Omit<LabelPlacement, 'priority'> & {
103
111
  * text: (datum: any, index: number) => string | number | null | undefined,
104
112
  * priority?: number | ((datum: any, index: number) => number),
@@ -109,10 +117,12 @@ export type InstanceLabels = Omit<LabelPlacement, 'priority'> & {
109
117
  * label the point itself.
110
118
  */
111
119
  export declare const LABEL_PLACES: LabelPlace[];
112
- /** @param {string} owner @param {unknown} v @returns {(InstanceLabels & Required<Omit<LabelPlacement, 'priority'>> & { priority: number | ((datum: any, index: number) => number) }) | null} */
113
- declare function instanceLabels(owner: string, v: unknown): (InstanceLabels & Required<Omit<LabelPlacement, 'priority'>> & {
120
+ export type CheckedLabels = InstanceLabels & Required<Omit<LabelPlacement, 'priority'>> & {
114
121
  priority: number | ((datum: any, index: number) => number);
115
- }) | null;
122
+ };
123
+ /** @typedef {InstanceLabels & Required<Omit<LabelPlacement, 'priority'>> & { priority: number | ((datum: any, index: number) => number) }} CheckedLabels */
124
+ /** @param {string} owner @param {unknown} v @returns {CheckedLabels | CheckedLabels[] | null} */
125
+ declare function instanceLabels(owner: string, v: unknown): CheckedLabels | CheckedLabels[] | null;
116
126
  /** Base for nodes with geometry and a material: Mesh, InstancedMesh, Points and Lines. */
117
127
  export declare class Drawable extends Node {
118
128
  #private;
@@ -157,7 +167,10 @@ export type InstanceOptions = {
157
167
  * Euler angles in radians, XYZ order
158
168
  */
159
169
  rotation?: Vec3Input | Euler;
160
- quaternion?: ArrayLike<number>;
170
+ /**
171
+ * [x, y, z, w] or { x, y, z, w } - as physics engines give them
172
+ */
173
+ quaternion?: QuatInput;
161
174
  scale?: Vec3Input | number;
162
175
  /**
163
176
  * multiplies the material colour
@@ -166,7 +179,8 @@ export type InstanceOptions = {
166
179
  };
167
180
  /**
168
181
  * Many copies of one geometry and material, drawn together. Set each copy with setInstance().
169
- * Picking reports which instance was hit.
182
+ * Picking reports which instance was hit. An instance scaled to 0 is hidden - it draws nothing, and
183
+ * framing, culling, shadows and labels leave it out - so a pool of instances can keep spares.
170
184
  */
171
185
  export declare class InstancedMesh extends Mesh {
172
186
  #private;
@@ -178,11 +192,11 @@ export declare class InstancedMesh extends Mesh {
178
192
  instanceMatrices: Float32Array;
179
193
  /** Linear rgb per instance, or null until a colour is set. @type {Float32Array | null} */
180
194
  instanceColors: Float32Array | null;
181
- /** A label per instance, from its row of data. @type {ReturnType<typeof instanceLabels>} */
195
+ /** A label per instance from its row of data - or a list of them, several per instance. @type {ReturnType<typeof instanceLabels>} */
182
196
  labels: ReturnType<typeof instanceLabels>;
183
197
  /**
184
198
  * @param {DrawableOptions & { count?: number, instances?: InstanceOptions[], instanceMatrices?: ArrayLike<number>,
185
- * instanceColors?: ArrayLike<number>, labels?: InstanceLabels | null }} options count defaults to the number
199
+ * instanceColors?: ArrayLike<number>, labels?: InstanceLabels | InstanceLabels[] | null }} options count defaults to the number
186
200
  * of instances or rows of data; instanceMatrices/instanceColors are raw data, as toJSON() writes
187
201
  */
188
202
  constructor(options: DrawableOptions & {
@@ -190,7 +204,7 @@ export declare class InstancedMesh extends Mesh {
190
204
  instances?: InstanceOptions[];
191
205
  instanceMatrices?: ArrayLike<number>;
192
206
  instanceColors?: ArrayLike<number>;
193
- labels?: InstanceLabels | null;
207
+ labels?: InstanceLabels | InstanceLabels[] | null;
194
208
  });
195
209
  /**
196
210
  * Set an instance's transform and optional colour. Unset transform parts default to identity.
@@ -222,11 +236,11 @@ export declare class InstancedMesh extends Mesh {
222
236
  export declare class Points extends Drawable {
223
237
  static type: string;
224
238
  static get OPTIONS(): string[];
225
- /** A label per point, from its row of data. @type {ReturnType<typeof instanceLabels>} */
239
+ /** A label per point from its row of data - or a list of them, several per point. @type {ReturnType<typeof instanceLabels>} */
226
240
  labels: ReturnType<typeof instanceLabels>;
227
- /** @param {DrawableOptions & { labels?: InstanceLabels | null }} options */
241
+ /** @param {DrawableOptions & { labels?: InstanceLabels | InstanceLabels[] | null }} options */
228
242
  constructor(options: DrawableOptions & {
229
- labels?: InstanceLabels | null;
243
+ labels?: InstanceLabels | InstanceLabels[] | null;
230
244
  });
231
245
  /** @returns {'points'} */
232
246
  get drawMode(): 'points';
@@ -261,6 +275,8 @@ export declare class Label extends Node {
261
275
  pointer: string;
262
276
  anchor: string;
263
277
  zIndex: string;
278
+ minZoom: string;
279
+ lod: string;
264
280
  collision: string;
265
281
  };
266
282
  text: string;
@@ -280,6 +296,8 @@ export declare class Label extends Node {
280
296
  leader: boolean;
281
297
  /** Set at the view's left or right edge, level with its point, or null for by its point. @type {'left' | 'right' | null} */
282
298
  edge: 'left' | 'right' | null;
299
+ /** Shown only when zoomed in this far: CSS pixels to one world unit at its point; 0 for always. */
300
+ minScale: number;
283
301
  /** @param {LabelOptions} options */
284
302
  constructor(options: LabelOptions);
285
303
  }
@@ -1,8 +1,9 @@
1
1
  import { Color } from '../value/Color.js';
2
2
  import { Vec3 } from '../value/Vec3.js';
3
+ import { Box3 } from '../value/Box3.js';
3
4
  import { Node } from './Node.js';
4
5
  import type { NodeOptions } from './Node.js';
5
- import type { ColorInput, Vec3Input } from '../types.js';
6
+ import type { Box3Input, ColorInput, Vec3Input } from '../types.js';
6
7
  export type LightOptions = NodeOptions & {
7
8
  color?: ColorInput;
8
9
  intensity?: number;
@@ -87,13 +88,26 @@ declare class AimedLight extends Light {
87
88
  }
88
89
  /** Parallel light from far away, like the sun. Position only matters for its shadow. */
89
90
  export declare class DirectionalLight extends AimedLight {
91
+ #private;
90
92
  static type: string;
91
93
  static get OPTIONS(): string[];
92
94
  castShadow: boolean;
93
- /** @param {AimedLightOptions & { castShadow?: boolean }} [options] */
95
+ /** @param {AimedLightOptions & { castShadow?: boolean, shadowBounds?: Box3Input | null }} [options] */
94
96
  constructor(options?: AimedLightOptions & {
95
97
  castShadow?: boolean;
98
+ shadowBounds?: Box3Input | null;
96
99
  });
100
+ /**
101
+ * The part of the world, in world units, that has shadows - or null (the default) to follow
102
+ * whatever casts them. The shadow map is fitted to what casts every frame, so one caster flung far
103
+ * off spreads its texels over everything and every shadow blurs; set the area where things happen
104
+ * and its shadows stay sharp. Casters outside it still shadow it, from wherever they stand.
105
+ * Assigning copies.
106
+ * @returns {Box3 | null}
107
+ */
108
+ get shadowBounds(): Box3 | null;
109
+ /** @param {Box3Input | null} v */
110
+ set shadowBounds(v: Box3Input | null);
97
111
  }
98
112
  /** Light from a point in all directions, falling off with the square of the distance. */
99
113
  export declare class PointLight extends Light {
@@ -25,9 +25,10 @@ export type ShadowFit = {
25
25
  * @param {Box3[]} casters world bounds
26
26
  * @param {Box3[]} receivers world bounds
27
27
  * @param {number} [size] map size in texels
28
+ * @param {Box3 | null} [area] the light's shadowBounds: the map covers this across, whatever casts
28
29
  * @returns {ShadowFit | null}
29
30
  */
30
- export declare function fitShadow(direction: ArrayLike<number>, casters: Box3[], receivers: Box3[], size?: number): ShadowFit | null;
31
+ export declare function fitShadow(direction: ArrayLike<number>, casters: Box3[], receivers: Box3[], size?: number, area?: Box3 | null): ShadowFit | null;
31
32
  /** Spot shadows' map: four tiles of half its size, one a light. */
32
33
  export declare const SPOT_ATLAS_SIZE = 2048;
33
34
  export declare const SPOT_TILES = 4;
@@ -7,12 +7,26 @@ export type XYZ = {
7
7
  z: number;
8
8
  };
9
9
  export type Vec3Input = Vec3Like | XYZ;
10
+ export type XYZW = {
11
+ x: number;
12
+ y: number;
13
+ z: number;
14
+ w: number;
15
+ };
16
+ export type QuatInput = ArrayLike<number> | XYZW;
17
+ export type Box3Input = import('./value/Box3.js').Box3 | {
18
+ min: Vec3Input;
19
+ max: Vec3Input;
20
+ };
10
21
  export type ColorInput = string | number | number[] | Float32Array | import('./value/Color.js').Color;
11
22
  /** @typedef {Float32Array | number[]} Vec2Like */
12
23
  /** @typedef {Float32Array | number[]} Vec3Like */
13
24
  /** @typedef {Float32Array | number[]} Vec4Like */
14
25
  /** A position or direction as an object literal. @typedef {{ x: number, y: number, z: number }} XYZ */
15
26
  /** A vector as accepted on input: [x, y, z], { x, y, z } or a Vec3. @typedef {Vec3Like | XYZ} Vec3Input */
27
+ /** A quaternion as an object literal - as physics engines such as Rapier return them. @typedef {{ x: number, y: number, z: number, w: number }} XYZW */
28
+ /** A rotation quaternion as accepted on input: [x, y, z, w], { x, y, z, w } or a Quat. @typedef {ArrayLike<number> | XYZW} QuatInput */
29
+ /** A box as accepted on input: a Box3, or its lowest and highest corners. @typedef {import('./value/Box3.js').Box3 | { min: Vec3Input, max: Vec3Input }} Box3Input */
16
30
  /**
17
31
  * A colour as accepted on input, in sRGB: '#ff8800', '#f80', 0xff8800, 'rgb(255 136 0)',
18
32
  * 'hsl(32 100% 50%)', a CSS colour name, or [r, g, b] with components 0-1.
@@ -1,8 +1,4 @@
1
1
  import type { ColorInput } from '../types.js';
2
- /**
3
- * An RGB colour. Input is sRGB, as everywhere in phoria2; the components are stored **linear**,
4
- * ready for lighting maths. `toJSON()` gives sRGB '#rrggbb' back.
5
- */
6
2
  export declare class Color extends Float32Array {
7
3
  #private;
8
4
  0: any;
@@ -1,3 +1,5 @@
1
+ import type { QuatInput } from '../types.js';
2
+ /** @import { QuatInput } from '../types.js' */
1
3
  /**
2
4
  * A rotation quaternion [x, y, z, w]: a Float32Array(4) with accessors and chainable methods.
3
5
  * A node's quaternion and its Euler `rotation` keep each other in sync through these setters;
@@ -44,8 +46,8 @@ export declare class Quat extends Float32Array {
44
46
  */
45
47
  /** @param {number | ArrayLike<number>} x @param {number} [y] @param {number} [z] @param {number} [w] */
46
48
  set(array: ArrayLike<number>, offset?: number): void;
47
- /** @param {ArrayLike<number>} q [x, y, z, w] @param {string} [owner] @returns {this} */
48
- copy(q: ArrayLike<number>, owner?: string): this;
49
+ /** @param {QuatInput} q [x, y, z, w], or { x, y, z, w } as physics engines give them @param {string} [owner] @returns {this} */
50
+ copy(q: QuatInput, owner?: string): this;
49
51
  /** @returns {Quat} */
50
52
  clone(): Quat;
51
53
  /** @returns {this} */
@@ -29,6 +29,10 @@ export type WebGLRendererOptions = {
29
29
  * multisampling (default true; a shared context always has it)
30
30
  */
31
31
  antialias?: boolean;
32
+ /**
33
+ * soft-edged outlines with round corners (default: as antialias)
34
+ */
35
+ outlineAntialias?: boolean;
32
36
  /**
33
37
  * how lit colours are mapped to the screen: 'neutral' keeps colours
34
38
  * true (Khronos PBR Neutral, the default), 'aces' is filmic, 'none' clips
@@ -80,6 +84,8 @@ export declare class WebGLRenderer {
80
84
  /** @type {ToneMapping} */ toneMapping: ToneMapping;
81
85
  exposure: number;
82
86
  pixelRatio: number;
87
+ /** Outlines with soft edges and round corners, or hard pixel steps; takes effect on the next frame. */
88
+ outlineAntialias: boolean;
83
89
  /** Counts from the last frame and the GPU caches. */
84
90
  info: {
85
91
  drawCalls: number;
@@ -24,6 +24,30 @@ export type ShadowMap = {
24
24
  sampler: string;
25
25
  texture: WebGLTexture;
26
26
  };
27
+ export type Target = {
28
+ texture: WebGLTexture;
29
+ framebuffer: WebGLFramebuffer;
30
+ };
31
+ export type OutlineTargets = {
32
+ width: number;
33
+ height: number;
34
+ smooth: boolean;
35
+ mask: Target;
36
+ wide: Target;
37
+ coverage: Target | null;
38
+ msaa: {
39
+ framebuffer: WebGLFramebuffer;
40
+ renderbuffers: WebGLRenderbuffer[];
41
+ } | null;
42
+ };
43
+ export type OutlineMask = {
44
+ mask: WebGLTexture;
45
+ wide: WebGLTexture;
46
+ coverage: WebGLTexture | null;
47
+ radius: number;
48
+ width: number;
49
+ height: number;
50
+ };
27
51
  /**
28
52
  * @typedef {object} Environment
29
53
  * @property {WebGLTexture} source the HDR image, mipmapped
@@ -38,6 +62,12 @@ export type ShadowMap = {
38
62
  * a pass that returns one of these.
39
63
  * @typedef {{ define: string, uniforms: Record<string, unknown>, sampler: string, texture: WebGLTexture }} ShadowMap
40
64
  */
65
+ /**
66
+ * @typedef {{ texture: WebGLTexture, framebuffer: WebGLFramebuffer }} Target
67
+ * @typedef {{ width: number, height: number, smooth: boolean, mask: Target, wide: Target, coverage: Target | null,
68
+ * msaa: { framebuffer: WebGLFramebuffer, renderbuffers: WebGLRenderbuffer[] } | null }} OutlineTargets
69
+ * @typedef {{ mask: WebGLTexture, wide: WebGLTexture, coverage: WebGLTexture | null, radius: number, width: number, height: number }} OutlineMask
70
+ */
41
71
  export declare class Passes {
42
72
  #private;
43
73
  gl: WebGL2RenderingContext;
@@ -88,25 +118,19 @@ export declare class Passes {
88
118
  * The first half of drawing outlines, before the scene: a mask of the outlined subtrees - each
89
119
  * pixel they cover in their colour, with the line's width in alpha - then that mask widened
90
120
  * sideways. outlineComposite() widens it up and down and draws the ring left round each mask.
121
+ * Smooth outlines draw the mask multisampled, with how much of each pixel it covers, and measure
122
+ * the ring from the shape's edge; otherwise each pixel is in or out.
91
123
  * @param {FrameOutline[]} outlines @param {FrameDescription} frame
92
124
  * @param {number} width @param {number} height drawing buffer pixels
93
125
  * @param {number} pixelRatio
94
- * @returns {{ mask: WebGLTexture, wide: WebGLTexture, radius: number }}
126
+ * @param {boolean} smooth antialiased: soft edges and round corners
127
+ * @returns {OutlineMask}
95
128
  */
96
- outlineMask(outlines: FrameOutline[], frame: FrameDescription, width: number, height: number, pixelRatio: number): {
97
- mask: WebGLTexture;
98
- wide: WebGLTexture;
99
- radius: number;
100
- };
129
+ outlineMask(outlines: FrameOutline[], frame: FrameDescription, width: number, height: number, pixelRatio: number, smooth: boolean): OutlineMask;
101
130
  /**
102
131
  * Draw the outlines over the finished scene, into whatever is bound: the sideways-widened mask
103
132
  * widened up and down too, less the mask itself, so a ring is left round each subtree.
104
- * @param {{ mask: WebGLTexture, wide: WebGLTexture, radius: number }} outline
105
- * @param {number} height drawing buffer pixels
133
+ * @param {OutlineMask} outline
106
134
  */
107
- outlineComposite(outline: {
108
- mask: WebGLTexture;
109
- wide: WebGLTexture;
110
- radius: number;
111
- }, height: number): void;
135
+ outlineComposite(outline: OutlineMask): void;
112
136
  }
@@ -41,6 +41,26 @@ export type MeshBuffers = {
41
41
  */
42
42
  layout?: string;
43
43
  };
44
+ /**
45
+ * Per-vertex tangents along the UVs' u, with the way v runs in w (Lengyel's method): smooth where
46
+ * the normals are. Without UVs, around the mesh's up axis.
47
+ * @param {Geometry} g
48
+ * @returns {Float32Array}
49
+ */
50
+ export declare function tangentsOf(g: Geometry): Float32Array;
51
+ /**
52
+ * A map's UV transform as a 3x3 (column-major): repeat, then rotation about its centre, then offset -
53
+ * glTF's KHR_texture_transform order, turning the way three.js and glTF viewers do. Written into one
54
+ * shared array, so set it as a uniform straight away.
55
+ * @param {{ repeat: number[], offset: number[], rotation: number, center: number[] }} t
56
+ * @returns {Float32Array}
57
+ */
58
+ export declare function uvMatrix(t: {
59
+ repeat: number[];
60
+ offset: number[];
61
+ rotation: number;
62
+ center: number[];
63
+ }): Float32Array;
44
64
  export declare class Resources {
45
65
  #private;
46
66
  /** @type {WebGL2RenderingContext} */ gl: WebGL2RenderingContext;
@@ -59,17 +79,19 @@ export declare class Resources {
59
79
  * @param {Geometry} geometry
60
80
  * @param {{ instanceMatrices: Float32Array, instanceColors: Float32Array | null, version: number } | null} instances
61
81
  * @param {boolean} withNormals
82
+ * @param {boolean} [withTangents] add tangents, for brushed metal - kept once added
62
83
  * @returns {MeshBuffers}
63
84
  */
64
85
  mesh(geometry: Geometry, instances: {
65
86
  instanceMatrices: Float32Array;
66
87
  instanceColors: Float32Array | null;
67
88
  version: number;
68
- } | null, withNormals: boolean): MeshBuffers;
89
+ } | null, withNormals: boolean, withTangents?: boolean): MeshBuffers;
69
90
  /**
70
91
  * Draw a mesh's triangles - instanced or not, indexed or not - with the program in use, as far as
71
92
  * its drawn count goes. Every pass that draws meshes draws them this way.
72
93
  * @param {Geometry} geometry @param {{ instanceMatrices: Float32Array, instanceColors: Float32Array | null, version: number, count: number } | null} instances
94
+ * @param {boolean} [withTangents] for brushed metal
73
95
  * @returns {number} triangles drawn
74
96
  */
75
97
  drawTriangles(geometry: Geometry, instances: {
@@ -77,7 +99,7 @@ export declare class Resources {
77
99
  instanceColors: Float32Array | null;
78
100
  version: number;
79
101
  count: number;
80
- } | null): number;
102
+ } | null, withTangents?: boolean): number;
81
103
  /**
82
104
  * Vertex array for Lines: one instance per segment with its two endpoints (and colours),
83
105
  * expanded to a screen-space quad in the vertex shader.
@@ -6,6 +6,8 @@ export declare const ATTRIBUTES: {
6
6
  color: number;
7
7
  instanceMatrix: number;
8
8
  instanceColor: number;
9
+ tangent: number;
10
+ uv2: number;
9
11
  segmentStart: number;
10
12
  segmentEnd: number;
11
13
  segmentColorStart: number;
@@ -14,12 +16,12 @@ export declare const ATTRIBUTES: {
14
16
  };
15
17
  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";
16
18
  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";
17
- export declare const CLIP_ONLY_FRAGMENT = "\nprecision highp float;\nin vec3 vWorldPosition;\nout vec4 fragColor;\n#ifdef ALPHA_TEST\n// a cut-out casts the shadow of what is left of it - leaves, fences, grilles - not of its rectangle\nin vec2 vUv;\nuniform float uOpacity;\nuniform float uAlphaTest;\n#ifdef MAP\nuniform sampler2D uMap;\n\n#ifdef TRIPLANAR\nin vec3 vObjectPosition;\nin vec3 vObjectNormal;\nin mat3 vObjectToWorld;\nuniform float uTriplanar; // repeats per unit of the mesh's own space\nuniform vec4 uUvTransform; // the map's repeat xy and offset zw, applied to each projection as to UVs\n\nvec2 projected(vec2 p) {\n return p * uUvTransform.xy + uUvTransform.zw;\n}\n\n// how much each axis's projection counts: mostly the one the surface faces, blended at the turns\nvec3 triplanarWeights(vec3 n) {\n vec3 w = pow(abs(n), vec3(4.0));\n return w / (w.x + w.y + w.z);\n}\n// a map seen from the three axes and blended - for meshes with no UVs, scans and CAD models\nvec4 triplanar(sampler2D s) {\n vec3 p = vObjectPosition * uTriplanar, w = triplanarWeights(normalize(vObjectNormal));\n return texture(s, projected(p.zy)) * w.x + texture(s, projected(p.xz)) * w.y + texture(s, projected(p.xy)) * w.z;\n}\n#define SAMPLE(s) triplanar(s)\n#else\n#define SAMPLE(s) texture(s, vUv)\n#endif\n\n#endif\n#endif\n\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\nvoid main() {\n clipAway(vWorldPosition);\n#ifdef ALPHA_TEST\n float alpha = uOpacity;\n#ifdef MAP\n alpha *= SAMPLE(uMap).a;\n#endif\n if (alpha < uAlphaTest) discard;\n#endif\n fragColor = vec4(1.0);\n}\n";
19
+ export declare const CLIP_ONLY_FRAGMENT: string;
18
20
  export declare const COMMON = "\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";
19
21
  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";
20
22
  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";
21
23
  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";
22
- 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\nuniform mat4 uModel;\nuniform mat3 uNormalMatrix;\nuniform mat4 uViewProjection;\nuniform vec4 uUvTransform; // repeat xy, offset zw\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 * uUvTransform.xy + uUvTransform.zw;\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";
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";
23
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";
24
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";
25
27
  export type ShaderKind = 'mesh' | 'points' | 'lines';