phoria2 1.1.0 → 1.3.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.
- package/README.md +70 -13
- package/dist/bloom.js +4 -4
- package/dist/bloom.js.map +1 -1
- package/dist/charts.js +538 -508
- package/dist/charts.js.map +1 -1
- package/dist/core.js +4270 -3652
- package/dist/core.js.map +1 -1
- package/dist/edit.js +65 -65
- package/dist/edit.js.map +1 -1
- package/dist/explain.js +6 -2
- package/dist/explain.js.map +1 -1
- package/dist/gltf.js +116 -116
- package/dist/gltf.js.map +1 -1
- package/dist/keyboard.js +188 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/motion.js +35 -35
- package/dist/motion.js.map +1 -1
- package/dist/particles.js +188 -115
- package/dist/particles.js.map +1 -1
- package/dist/passes.js +29 -29
- package/dist/passes.js.map +1 -1
- package/dist/phoria2.js +1 -1
- package/dist/picking.js +32 -16
- package/dist/picking.js.map +1 -1
- package/dist/snapshot.js +1 -1
- package/dist/tooltip.js +1 -1
- package/dist/types/OrbitControl.d.ts +6 -2
- package/dist/types/Raycaster.d.ts +10 -0
- package/dist/types/View.d.ts +70 -1
- package/dist/types/area.d.ts +18 -0
- package/dist/types/charts/Chart.d.ts +7 -0
- package/dist/types/explain.d.ts +3 -0
- package/dist/types/frame.d.ts +7 -0
- package/dist/types/geometry/Geometry.d.ts +24 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/keyboard.d.ts +48 -0
- package/dist/types/labels.d.ts +4 -0
- package/dist/types/material/Material.d.ts +15 -0
- package/dist/types/material/Texture.d.ts +18 -2
- package/dist/types/particles/Emitter.d.ts +39 -2
- package/dist/types/scene/Node.d.ts +19 -14
- package/dist/types/scene/drawables.d.ts +54 -15
- package/dist/types/scene/lights.d.ts +16 -2
- package/dist/types/shadow.d.ts +2 -1
- package/dist/types/types.d.ts +14 -0
- package/dist/types/value/Color.d.ts +0 -4
- package/dist/types/value/Quat.d.ts +4 -2
- package/dist/types/webgl/resources.d.ts +12 -2
- package/dist/types/webgl/shaders.d.ts +7 -2
- package/llms-full.txt +106 -10
- package/package.json +4 -1
- package/schema/scene.schema.json +67 -1
- package/src/OrbitControl.js +7 -3
- package/src/Raycaster.js +58 -0
- package/src/View.js +220 -41
- package/src/area.js +131 -0
- package/src/charts/Chart.js +20 -2
- package/src/charts/marks.js +29 -2
- package/src/explain.js +6 -2
- package/src/frame.js +29 -9
- package/src/geometry/Geometry.js +32 -4
- package/src/index.js +1 -0
- package/src/keyboard.js +298 -0
- package/src/labels.js +4 -1
- package/src/material/Material.js +9 -2
- package/src/material/Texture.js +55 -2
- package/src/particles/Emitter.js +121 -10
- package/src/scene/Node.js +20 -9
- package/src/scene/Scene.js +2 -0
- package/src/scene/cameras.js +4 -1
- package/src/scene/drawables.js +115 -15
- package/src/scene/lights.js +40 -6
- package/src/shadow.js +27 -4
- package/src/types.js +6 -0
- package/src/value/Color.js +21 -0
- package/src/value/Quat.js +15 -6
- package/src/watch.js +14 -2
- package/src/webgl/WebGLRenderer.js +36 -5
- package/src/webgl/picking.js +22 -1
- package/src/webgl/resources.js +168 -14
- package/src/webgl/shaders.js +110 -11
|
@@ -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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
|
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,21 +236,42 @@ 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
|
|
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
|
-
/**
|
|
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 */
|
|
228
247
|
constructor(options: DrawableOptions & {
|
|
229
|
-
labels?: InstanceLabels | null;
|
|
248
|
+
labels?: InstanceLabels | InstanceLabels[] | null;
|
|
249
|
+
sort?: boolean;
|
|
230
250
|
});
|
|
231
251
|
/** @returns {'points'} */
|
|
232
252
|
get drawMode(): 'points';
|
|
233
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
|
+
*/
|
|
234
261
|
/**
|
|
235
262
|
* Line segments: indices (or consecutive vertices) in pairs. Width is in pixels and works on
|
|
236
|
-
* 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.
|
|
237
265
|
*/
|
|
238
266
|
export declare class Lines extends Drawable {
|
|
239
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);
|
|
240
275
|
/** @returns {'lines'} */
|
|
241
276
|
get drawMode(): 'lines';
|
|
242
277
|
}
|
|
@@ -261,6 +296,8 @@ export declare class Label extends Node {
|
|
|
261
296
|
pointer: string;
|
|
262
297
|
anchor: string;
|
|
263
298
|
zIndex: string;
|
|
299
|
+
minZoom: string;
|
|
300
|
+
lod: string;
|
|
264
301
|
collision: string;
|
|
265
302
|
};
|
|
266
303
|
text: string;
|
|
@@ -280,6 +317,8 @@ export declare class Label extends Node {
|
|
|
280
317
|
leader: boolean;
|
|
281
318
|
/** Set at the view's left or right edge, level with its point, or null for by its point. @type {'left' | 'right' | null} */
|
|
282
319
|
edge: 'left' | 'right' | null;
|
|
320
|
+
/** Shown only when zoomed in this far: CSS pixels to one world unit at its point; 0 for always. */
|
|
321
|
+
minScale: number;
|
|
283
322
|
/** @param {LabelOptions} options */
|
|
284
323
|
constructor(options: LabelOptions);
|
|
285
324
|
}
|
|
@@ -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 {
|
package/dist/types/shadow.d.ts
CHANGED
|
@@ -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;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -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 {
|
|
48
|
-
copy(q:
|
|
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} */
|
|
@@ -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";
|
|
@@ -22,8 +27,8 @@ export declare const EQUIRECT = "\nvec2 equirectUv(vec3 d) {\n return vec2(atan
|
|
|
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
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\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 ||
|
|
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;\
|
|
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\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#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 size = uSize * pointSize;\n#ifdef SIZE_ATTENUATION\n // world units: the square sits in the view, facing the camera\n viewPosition.xy += turned * size * 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 * size / 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 int self = int(pointIndex + 0.5);\n#else\n gl_Position = uProjection * viewPosition;\n int self = gl_VertexID;\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 * 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 || 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\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#ifdef DASHED\nout float vDistance; // along the chain, in the model's units\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\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 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#ifdef DASHED\n vDistance = segmentInfo.x + length(segmentEnd - segmentStart) * along;\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';
|
|
28
33
|
/**
|
|
29
34
|
* Vertex and fragment source for a kind of drawable, or a pass's own pair, with a set of feature defines.
|
package/llms-full.txt
CHANGED
|
@@ -114,9 +114,13 @@ const view = new View({ canvas: '#c', orbit: true })
|
|
|
114
114
|
| `autoLight` | `true` | light a scene that has no lights (a chart needs none of its own); `false` to leave it dark |
|
|
115
115
|
| `pixelRatio` | `min(devicePixelRatio, 2)` | |
|
|
116
116
|
| `context` | `'auto'` | WebGL context: own for the first four views on a page, then one shared context - so a page can hold any number of views |
|
|
117
|
+
| `keyboard` | `false` | `true` or `{ label: (hit) => text }`: use without a pointer, with a screen reader - see Interaction |
|
|
118
|
+
| `description` | none | what the view shows, for screen readers (the canvas's label); made from the scene when `keyboard` is on |
|
|
119
|
+
| `reducedMotion` | `'auto'` | as the system's reduced-motion setting: `frame()` jumps rather than glides, auto-rotation holds, charts change at once. `true` or `false` to choose |
|
|
117
120
|
|
|
118
121
|
Members: `view.scene`, `view.camera` (assign a camera or a preset name to switch), `view.controls`
|
|
119
|
-
(the orbit control), `view.renderer`, `view.width`, `view.height
|
|
122
|
+
(the orbit control), `view.renderer`, `view.width`, `view.height`, `view.description`, `view.reducedMotion`
|
|
123
|
+
(true or false as it stands - check it for motion of your own, such as a timeline; set `'auto'`, true or false).
|
|
120
124
|
|
|
121
125
|
Methods:
|
|
122
126
|
|
|
@@ -135,6 +139,8 @@ Methods:
|
|
|
135
139
|
- `on('click' | 'hover', (hit, event) => ...)` - see Interaction. Returns an unsubscribe function.
|
|
136
140
|
- `tooltip(fn)` - set the tooltip function later.
|
|
137
141
|
- `pick(x, y)` / `pick(event)` - the hit under a canvas position; `pickAll` for every hit.
|
|
142
|
+
- `pickArea(area)` - every hit inside a box `{ x, y, width, height }` or a lasso of `[x, y]` points
|
|
143
|
+
(CSS pixels), nearest first - see Interaction.
|
|
138
144
|
- `rayAt(x, y)` / `rayAt(event)` - the world `Ray` through a canvas position, for placing things:
|
|
139
145
|
`ray.at(ray.intersectPlane([0, 1, 0], [0, 0, 0]))` is the point on the floor under the pointer
|
|
140
146
|
(`intersectPlane` returns null when the ray runs along the plane or away from it).
|
|
@@ -164,7 +170,7 @@ something changes):
|
|
|
164
170
|
## Scene graph
|
|
165
171
|
|
|
166
172
|
Everything in a scene is a node. Node options (all optional): `name`, `position`, `rotation`
|
|
167
|
-
(Euler XYZ radians, `[x, y, z]`), `quaternion
|
|
173
|
+
(Euler XYZ radians, `[x, y, z]`), `quaternion` (`[x, y, z, w]` or `{ x, y, z, w }`), `scale` (number or `[x, y, z]`), `visible`,
|
|
168
174
|
`pickable` (default true), `userData`, `children`, `onUpdate`, `clip` (a section plane - see
|
|
169
175
|
Cutaways), plus the interaction options below.
|
|
170
176
|
|
|
@@ -218,6 +224,24 @@ new Lines({ geometry: edges(box(2)), material: { color: '#333', width: 2 } })
|
|
|
218
224
|
new Label({ text: 'Rack A1', position: [0, 2, 0], className: 'my-label', place: 'above' })
|
|
219
225
|
```
|
|
220
226
|
|
|
227
|
+
**Lines** dashed and with arrowheads - flow and direction in network diagrams. Dashes are material
|
|
228
|
+
options in the model's units: `dash` (0 is solid), `gap` (0 is the dash's length) and `dashOffset`,
|
|
229
|
+
which moves them along - animate it for traffic. Arrowheads are Lines options: `arrows: 'start' |
|
|
230
|
+
'end' | 'both'` puts them at the ends of each chain (segments each starting where the one before
|
|
231
|
+
ended - a polyline is one chain, separate links are one each), `arrowSize` in pixels (0, the default,
|
|
232
|
+
sizes them from the width). The dashes run on along a chain, and arrowheads are never dashed.
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
const flow = view.scene.add(new Lines({ geometry: links, arrows: 'end', material: { color: '#4cc9f0', width: 2, dash: 0.3, gap: 0.15 } }))
|
|
236
|
+
view.start((dt) => { flow.material.dashOffset += dt * 0.8 }) // traffic moving along each link
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Points** each their own size, opacity or angle: give the geometry `sizes` (multiples of the material's
|
|
240
|
+
`size`), `opacities` (0-1) or `angles` (radians, turning the point's `map`), one per vertex. Such points
|
|
241
|
+
are drawn as camera-facing squares, so any size works and they stay whole at the edge of the view.
|
|
242
|
+
`sort: true` draws them furthest first each frame - for soft points with `blending: 'normal'`
|
|
243
|
+
(smoke, cloud) that would otherwise layer wrongly; with additive blending order doesn't matter.
|
|
244
|
+
|
|
221
245
|
**InstancedMesh** - many copies of one geometry in one draw call, for bars, racks, nodes:
|
|
222
246
|
|
|
223
247
|
```js
|
|
@@ -229,7 +253,11 @@ bars.setColor(3, 'red')
|
|
|
229
253
|
`setInstance(i, { position, rotation, quaternion, scale, color })`. An instance's `color` multiplies the
|
|
230
254
|
material's colour, which is white unless you set one - so the instance colour shows as given. `count` defaults to the number
|
|
231
255
|
of rows in `data`. For very large data, write `bars.instanceMatrices` (16 numbers per instance,
|
|
232
|
-
column-major) directly and call `bars.needsUpdate()`.
|
|
256
|
+
column-major) directly and call `bars.needsUpdate()`. An instance scaled to 0 is hidden: it draws
|
|
257
|
+
nothing, and bounds, framing, shadows and labels leave it out - so a pool can keep spare instances.
|
|
258
|
+
Positions and quaternions also take `{ x, y, z }` and `{ x, y, z, w }`, as physics engines return
|
|
259
|
+
them, so a physics bridge is one line a body:
|
|
260
|
+
`bricks.setInstance(i, { position: body.translation(), quaternion: body.rotation() })`.
|
|
233
261
|
|
|
234
262
|
**Labels** are HTML elements pinned to 3D points (style them with CSS). Options: `text`, `position`,
|
|
235
263
|
`className`, `place` (`'center'` | `'above'` | `'below'` | `'left'` | `'right'`), `offset` ([x, y]
|
|
@@ -237,7 +265,8 @@ pixels), `priority` (higher wins when labels overlap), `overlap` (`'hide'` | `'d
|
|
|
237
265
|
a label does when covered), `occlude` (true: counts as covered when geometry is in front), `leader`
|
|
238
266
|
(true: a line from the point to the label, with a dot on the point - for callouts), `edge` (`'left'` |
|
|
239
267
|
`'right'`: set the label at that edge of the view, level with its point - callouts in a column, with
|
|
240
|
-
`leader: true`; `offset` then moves it in and up or down)
|
|
268
|
+
`leader: true`; `offset` then moves it in and up or down), `minScale` (show it only when zoomed in this
|
|
269
|
+
far - below).
|
|
241
270
|
|
|
242
271
|
A label answers the pointer when it has handlers: a Label with its own `onClick` or `onHover` is
|
|
243
272
|
clicked and hovered like a mesh (`new Label({ text: 'Mantle', onClick: () => show('mantle') })` - a
|
|
@@ -250,6 +279,21 @@ clicking that bar, with the same hit. Other labels let the pointer through to th
|
|
|
250
279
|
new InstancedMesh({ geometry: box(1), data: rows, labels: { text: (row) => row.name, place: 'above', priority: (row) => row.alert ? 2 : 0 } })
|
|
251
280
|
```
|
|
252
281
|
|
|
282
|
+
**Labels by zoom level**: `minScale` on any label shows it only when the view is zoomed in at least that
|
|
283
|
+
far - that many CSS pixels to one world unit, at the label's point (perspective or orthographic alike).
|
|
284
|
+
Until then it takes no part in the layout, so it can't push other labels out. `labels` can also be a
|
|
285
|
+
list, several labels per instance - the short one always, the details as you come closer:
|
|
286
|
+
|
|
287
|
+
```js
|
|
288
|
+
new InstancedMesh({ geometry: box(1), data: elements, labels: [
|
|
289
|
+
{ text: (e) => e.symbol, place: 'center', className: 'symbol' },
|
|
290
|
+
{ text: (e) => e.name, place: 'below', className: 'name', minScale: 45 },
|
|
291
|
+
] })
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Pick `minScale` by looking: at the zoom where the details should appear, a world unit spans that many
|
|
295
|
+
pixels (a 1-unit tile drawn 60 px wide is 60). `view.explain()` counts the labels still waiting.
|
|
296
|
+
|
|
253
297
|
## Geometry
|
|
254
298
|
|
|
255
299
|
Generators return a `Geometry`; each takes one options object (a number is the main size):
|
|
@@ -287,7 +331,9 @@ extrude({
|
|
|
287
331
|
|
|
288
332
|
Your own: `new Geometry({ positions, normals?, uvs?, uvs2?, colors?, indices? })` - flat arrays, xyz per
|
|
289
333
|
vertex; `colors` are rgb 0-1 per vertex (use `material: { vertexColors: true }`); `uvs2` is a second UV
|
|
290
|
-
set, for baked occlusion and light maps (below).
|
|
334
|
+
set, for baked occlusion and light maps (below). To animate one - cloth, a wave, a morph - write
|
|
335
|
+
`geometry.positions` in place and call `geometry.needsUpdate()` each frame; normals you didn't give are
|
|
336
|
+
worked out again to follow the shape.
|
|
291
337
|
|
|
292
338
|
## Materials
|
|
293
339
|
|
|
@@ -335,7 +381,10 @@ unless its `colorSpace` was set on purpose - `normalMap: await loadTexture('n.pn
|
|
|
335
381
|
|
|
336
382
|
**Canvas and video textures**: a texture's `image` can be a canvas - draw on it, then call
|
|
337
383
|
`texture.needsUpdate()` - or a `<video>`, whose frames upload by themselves while it plays, with
|
|
338
|
-
nothing to call.
|
|
384
|
+
nothing to call. For a big canvas drawn on a little at a time (paint, a map being revealed), say what
|
|
385
|
+
changed - `texture.needsUpdate({ x, y, width, height })`, in the canvas's pixels from its top left -
|
|
386
|
+
and only that rectangle is sent, rather than the whole canvas every frame (several calls before a
|
|
387
|
+
frame add up). `new Texture({ url: 'promo.mp4' })` (or .webm, .ogv, .mov, .m4v) makes the video
|
|
339
388
|
for you, muted and looping so browsers let it start by itself; `texture.image` is that `<video>` -
|
|
340
389
|
pause, seek or unmute it as any other. A browser that blocks autoplay leaves it on its first frame
|
|
341
390
|
until `texture.image.play()` from a click. Video textures make no mipmaps (they would be remade
|
|
@@ -425,7 +474,7 @@ on top, as with UVs (animate `offset` to scroll a pattern). 0 (the default) uses
|
|
|
425
474
|
|---|---|
|
|
426
475
|
| `AmbientLight` | `color`, `intensity` |
|
|
427
476
|
| `HemisphereLight` | `sky`, `ground`, `intensity` |
|
|
428
|
-
| `DirectionalLight` | `color`, `intensity`, `direction` (the way it travels, e.g. `[-1, -2, -1]`), `castShadow` |
|
|
477
|
+
| `DirectionalLight` | `color`, `intensity`, `direction` (the way it travels, e.g. `[-1, -2, -1]`), `castShadow`, `shadowBounds` |
|
|
429
478
|
| `PointLight` | `color`, `intensity`, `position`, `range` - inverse-square falloff, so intensity ~ distance squared (30 lights things ~5 m away); `castShadow` |
|
|
430
479
|
| `SpotLight` | point light options plus `direction`, `angle`, `penumbra`, `castShadow` |
|
|
431
480
|
|
|
@@ -443,7 +492,9 @@ meshes opt in with `castShadow` and `receiveShadow`. No shadow camera to set up.
|
|
|
443
492
|
lamp - anything whose bounds hold the light - doesn't block it, so a lamp's shade can cast shadows of
|
|
444
493
|
its own. A point light casts all round, and everything that casts blocks it, around it too: a candle in
|
|
445
494
|
a lantern or a carved pumpkin lights the room only through the openings. Give a bulb or flame mesh
|
|
446
|
-
drawn at the light `castShadow: false`.
|
|
495
|
+
drawn at the light `castShadow: false`. The directional shadow is fitted to whatever casts, every
|
|
496
|
+
frame; when things fly far off, give the light `shadowBounds: { min: [x, y, z], max: [x, y, z] }` -
|
|
497
|
+
the world area that gets shadows - so the map stays sharp there.
|
|
447
498
|
|
|
448
499
|
```js
|
|
449
500
|
scene.add(new PointLight({ position: [0, 0.1, 0], intensity: 0.5, range: 4, castShadow: true })) // a candle in a jack-o'-lantern
|
|
@@ -469,6 +520,9 @@ and `target` yourself; the orbit always turns about wherever the target is.
|
|
|
469
520
|
press or the wheel takes over.
|
|
470
521
|
- **Back to the start**: `view.frame(undefined, { direction: 'perspective', duration: 0.6 })`
|
|
471
522
|
(or the preset the view started with).
|
|
523
|
+
- **Where framing would go**: `view.framing(target, { padding, direction })` returns
|
|
524
|
+
`{ position, target, height? }` without moving the camera - for a camera that eases after
|
|
525
|
+
something that keeps changing size (`camera.position.lerp(aim.position, 0.1)`, the same for target).
|
|
472
526
|
- **Follow something that moves** - move the camera and its target by as much as it moved, so the
|
|
473
527
|
view keeps its angle and the user can still orbit round it:
|
|
474
528
|
|
|
@@ -506,6 +560,16 @@ view.on('hover', (hit) => show(hit?.datum)) // fires when the th
|
|
|
506
560
|
- The **nearest surface** under the pointer is the hit - a link drawn over a site's top wins the
|
|
507
561
|
hover there. To let one kind of thing through, give it `pickable: false`; to choose among
|
|
508
562
|
everything under the pointer, `view.pickAll(event)` lists every hit, nearest first.
|
|
563
|
+
- **Selecting an area** - `view.pickArea({ x, y, width, height })` (width and height may be negative,
|
|
564
|
+
as a drag up or left makes them) or `view.pickArea([[x, y], ...])` for a lasso: a hit for each point,
|
|
565
|
+
instance and line segment whose place on screen is inside, and each mesh whose middle is. Places, not
|
|
566
|
+
surfaces: what is hidden behind other things is found too, so a box over a scatter takes that whole
|
|
567
|
+
region. Hidden, unpickable and zero-scale instances are left out. Turn `view.controls.enabled` off
|
|
568
|
+
while the user drags out the area.
|
|
569
|
+
|
|
570
|
+
```js
|
|
571
|
+
const chosen = view.pickArea({ x: 40, y: 30, width: 200, height: 120 }).map((h) => h.datum)
|
|
572
|
+
```
|
|
509
573
|
- **Rays of your own** - line of sight, what's below a dropped object, picking in tests: a
|
|
510
574
|
`Raycaster` finds what any ray hits, on the CPU. Set `raycaster.ray.origin` and `.direction`, or aim
|
|
511
575
|
it with `setFromCamera([x, y], camera)` (-1 to 1, y up); `intersect(nodeOrNodes)` returns hits,
|
|
@@ -524,7 +588,8 @@ view.on('hover', (hit) => show(hit?.datum)) // fires when the th
|
|
|
524
588
|
children. A node's `highlight` is the colour for its subtree; it tints the mesh, instance or point
|
|
525
589
|
under the pointer. Add `highlightWhole: true` to tint the whole subtree as one thing (a piece of
|
|
526
590
|
furniture, a machine) whichever part is hovered. Without the View's `highlight`, only nodes with
|
|
527
|
-
their own highlight light up - leave it off to keep floors and walls plain
|
|
591
|
+
their own highlight light up - leave it off to keep floors and walls plain, or keep it and give
|
|
592
|
+
those `highlight: false` (`null` means "the View's").
|
|
528
593
|
- `onHover` and `onClick` are the app's own handler for a node; to add more without replacing it
|
|
529
594
|
(an add-on, a second concern), `node.on('hover' | 'click', fn)` - it returns a function that stops
|
|
530
595
|
listening. Both run. A drag has one handler, `onDrag`: one thing moves a node at a time.
|
|
@@ -549,6 +614,22 @@ chair.onDrag = (drag) => {
|
|
|
549
614
|
corners - as the canvas is; `outlineAntialias: false` on the View (or `view.renderer.outlineAntialias =
|
|
550
615
|
false`, any time) draws them in hard pixel steps instead.
|
|
551
616
|
- `view.controls.cancel()` lets go of the orbit gesture in progress, for apps that take over a press.
|
|
617
|
+
- **Keyboard and screen readers**: `keyboard: true` on the View makes the canvas a stop in the page's
|
|
618
|
+
tab order. The arrow keys (and Home, End, PageUp, PageDown) step through what can be hovered or
|
|
619
|
+
clicked, in scene order and each mesh's instances in order: each instance, point, surface grid
|
|
620
|
+
point (a mesh with a row per vertex) or row of data under a node with its own `tooltip`, `onClick`,
|
|
621
|
+
`onHover`, `onDrag` or `highlight` - and, when the View has a tooltip or handlers itself, each one
|
|
622
|
+
with data. A mesh with no rows is a stop when it has a name or a handler of its own. A
|
|
623
|
+
`highlightWhole` subtree is one stop. Lines with a row per segment (a network's links) are a stop
|
|
624
|
+
per segment, after everything else. The one stepped to is shown as hovering would show it - tint (`#ffcc00` when no highlight
|
|
625
|
+
is set), tooltip, hover handlers - and Enter or Space clicks it (the handler's event is null);
|
|
626
|
+
Escape leaves it, and moving the pointer takes over. A screen reader hears each one: the tooltip's
|
|
627
|
+
text, else its data, else its name, with "3 of 12" - or `keyboard: { label: (hit) => text }`. The
|
|
628
|
+
canvas is labelled with `description`, or from the scene: each chart's `summary()` ("Sales: bar
|
|
629
|
+
chart of 12 rows by month, sales"), else "3D view of 12 items".
|
|
630
|
+
- **Reduced motion**: `reducedMotion` follows the system's setting by default. When on, `frame()`
|
|
631
|
+
with a `duration` jumps, `autoRotate` holds, and chart transitions and flowing links settle at
|
|
632
|
+
once. Motion of your own (a `Timeline`, `onUpdate` spin) can check `view.reducedMotion`.
|
|
552
633
|
|
|
553
634
|
## Charts: `phoria2/charts`
|
|
554
635
|
|
|
@@ -600,7 +681,8 @@ A chart is a node with axes, ticks, grid walls, a legend and tooltips built in.
|
|
|
600
681
|
in it as a wrapping row, so give it no fixed height). Categories get swatches that click to hide
|
|
601
682
|
and show; a number colour gets a key - a ramp, highest at the top, with values marked up its side,
|
|
602
683
|
`ticks` as on an axis),
|
|
603
|
-
`theme: 'dark' | 'light'`, `title`, `
|
|
684
|
+
`theme: 'dark' | 'light'`, `title`, `description` (what a screen reader hears for it; else `chart.summary()`
|
|
685
|
+
makes one from the title and channels), `transition: false | { duration, ease }`,
|
|
604
686
|
`overlap: 'dim' | 'hide' | 'show'` (labels), `highlight` (a colour, false, or for networks
|
|
605
687
|
`'neighbours'` - the default, lighting the node's links and linked nodes - or `'node'`, the node alone), and node options (`position`, ...).
|
|
606
688
|
- **Surfaces**: one row per grid point - `x` and `z` are the grid (numbers or categories), `y` the
|
|
@@ -758,6 +840,20 @@ second), `follow` (a node whose position is the origin each frame - a trail), `o
|
|
|
758
840
|
default material is small additive points. `burst(count, spawnOptions)`, `clear()`, `set(options)`,
|
|
759
841
|
`count`.
|
|
760
842
|
|
|
843
|
+
How particles look over their lives: `size` (a multiple of the material's size, or a list each particle
|
|
844
|
+
goes through - `[0.5, 3]` billows), `sizeSpread` (each one's size varies by up to this share, 0-1),
|
|
845
|
+
`opacity` (0-1, or a list - `[0, 1, 0]` fades in and out) and `spin` (radians per second: a number
|
|
846
|
+
either way, or `[min, max]`; spinning particles start at a random angle). For smoke, a soft `map`,
|
|
847
|
+
`blending: 'normal'` and `sort: true`:
|
|
848
|
+
|
|
849
|
+
```js
|
|
850
|
+
const smoke = view.scene.add(new Emitter({
|
|
851
|
+
rate: 40, velocity: [0, 1.5, 0], velocitySpread: 0.4, lifetime: [3, 5], color: ['#777', '#333'],
|
|
852
|
+
size: [0.6, 4], sizeSpread: 0.3, opacity: [0, 0.6, 0], spin: 0.5, sort: true,
|
|
853
|
+
material: { map: puff, size: 1, blending: 'normal' },
|
|
854
|
+
}))
|
|
855
|
+
```
|
|
856
|
+
|
|
761
857
|
`Body` moves any node: `node.onUpdate(new Body({ velocity, gravity, drag }).update)`;
|
|
762
858
|
`body.impulse([x, y, z])` kicks it.
|
|
763
859
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phoria2",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Small, friendly 3D for the web - a WebGL 2 renderer with charts, labels, picking, particles and glTF",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"3d",
|
|
@@ -85,10 +85,13 @@
|
|
|
85
85
|
"plugin": "node scripts/plugin.mjs"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
|
+
"@dimforge/rapier3d-compat": "^0.21.0",
|
|
88
89
|
"@eslint/js": "^10.0.1",
|
|
89
90
|
"eslint": "^10.3.0",
|
|
90
91
|
"gl-matrix": "^3.4.4",
|
|
91
92
|
"globals": "^17.6.0",
|
|
93
|
+
"manifold-3d": "^3.5.4",
|
|
94
|
+
"recast-navigation": "^0.44.0",
|
|
92
95
|
"three": "0.186.1",
|
|
93
96
|
"typescript": "^7.0.2",
|
|
94
97
|
"vite": "^8.0.12",
|