phoria2 1.1.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.
- package/README.md +71 -13
- package/dist/bloom.js +4 -4
- package/dist/bloom.js.map +1 -1
- package/dist/charts.js +358 -345
- package/dist/charts.js.map +1 -1
- package/dist/core.js +3989 -3600
- package/dist/core.js.map +1 -1
- package/dist/edit.js +43 -43
- package/dist/edit.js.map +1 -1
- package/dist/explain.js +5 -1
- package/dist/explain.js.map +1 -1
- package/dist/gltf.js +96 -96
- package/dist/gltf.js.map +1 -1
- package/dist/motion.js +3 -3
- package/dist/motion.js.map +1 -1
- package/dist/particles.js +16 -16
- package/dist/particles.js.map +1 -1
- package/dist/passes.js +26 -26
- package/dist/passes.js.map +1 -1
- package/dist/phoria2.js +1 -1
- package/dist/picking.js +16 -16
- package/dist/picking.js.map +1 -1
- package/dist/types/OrbitControl.d.ts +6 -2
- package/dist/types/Raycaster.d.ts +10 -0
- package/dist/types/View.d.ts +59 -1
- 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 +8 -2
- package/dist/types/keyboard.d.ts +48 -0
- package/dist/types/labels.d.ts +4 -0
- package/dist/types/scene/Node.d.ts +19 -14
- package/dist/types/scene/drawables.d.ts +32 -14
- 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/llms-full.txt +58 -9
- package/package.json +4 -1
- package/schema/scene.schema.json +19 -1
- package/src/OrbitControl.js +7 -3
- package/src/Raycaster.js +58 -0
- package/src/View.js +204 -41
- package/src/charts/Chart.js +20 -2
- package/src/explain.js +6 -2
- package/src/frame.js +29 -9
- package/src/geometry/Geometry.js +16 -3
- package/src/keyboard.js +298 -0
- package/src/labels.js +4 -1
- 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 +48 -12
- 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 +6 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Hit } from './Raycaster.js';
|
|
2
|
+
import type { TooltipContent } from './tooltip.js';
|
|
3
|
+
import type { Node } from './scene/Node.js';
|
|
4
|
+
import type { View } from './View.js';
|
|
5
|
+
export type KeyboardOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* what a screen reader says for each thing stepped to (default: the
|
|
8
|
+
* tooltip's text, then its data, then the node's name)
|
|
9
|
+
*/
|
|
10
|
+
label?: (hit: Hit) => string;
|
|
11
|
+
};
|
|
12
|
+
export type KeyboardHooks = {
|
|
13
|
+
/**
|
|
14
|
+
* show this as hovered, or nothing
|
|
15
|
+
*/
|
|
16
|
+
focus: (hit: Hit | null) => void;
|
|
17
|
+
/**
|
|
18
|
+
* click it
|
|
19
|
+
*/
|
|
20
|
+
press: (hit: Hit) => void;
|
|
21
|
+
/**
|
|
22
|
+
* the tooltip's content for it, if there is a tooltip
|
|
23
|
+
*/
|
|
24
|
+
tooltip: (hit: Hit) => TooltipContent;
|
|
25
|
+
/**
|
|
26
|
+
* does the View itself answer hovers or clicks (a tooltip, handlers)?
|
|
27
|
+
*/
|
|
28
|
+
viewWide: () => boolean;
|
|
29
|
+
};
|
|
30
|
+
export type Item = {
|
|
31
|
+
node: Node;
|
|
32
|
+
index: number | null;
|
|
33
|
+
whole: Node | null;
|
|
34
|
+
};
|
|
35
|
+
export declare class Keyboard {
|
|
36
|
+
#private;
|
|
37
|
+
/** @param {View} view @param {KeyboardHooks} hooks @param {KeyboardOptions} options */
|
|
38
|
+
constructor(view: View, hooks: KeyboardHooks, options: KeyboardOptions);
|
|
39
|
+
/**
|
|
40
|
+
* The canvas's label for screen readers: the View's description, or - when it has none - one made
|
|
41
|
+
* from the scene: what its charts show, and how many things there are to step through.
|
|
42
|
+
* @param {string | null} text
|
|
43
|
+
*/
|
|
44
|
+
setDescription(text: string | null): void;
|
|
45
|
+
/** Step off whatever was stepped to: the pointer has taken over, or focus has gone. */
|
|
46
|
+
clear(): void;
|
|
47
|
+
dispose(): void;
|
|
48
|
+
}
|
package/dist/types/labels.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Mat4 } from '../value/Mat4.js';
|
|
|
5
5
|
import { Quat } from '../value/Quat.js';
|
|
6
6
|
import { Vec3 } from '../value/Vec3.js';
|
|
7
7
|
import { Clip } from '../clip.js';
|
|
8
|
-
import type { ColorInput, Vec3Input } from '../types.js';
|
|
8
|
+
import type { ColorInput, QuatInput, Vec3Input } from '../types.js';
|
|
9
9
|
import type { Hit } from '../Raycaster.js';
|
|
10
10
|
import type { Camera } from './cameras.js';
|
|
11
11
|
import type { ClipOptions } from '../clip.js';
|
|
@@ -59,9 +59,9 @@ export type NodeOptions = {
|
|
|
59
59
|
*/
|
|
60
60
|
rotation?: Vec3Input | Euler;
|
|
61
61
|
/**
|
|
62
|
-
* [x, y, z, w] - instead of rotation
|
|
62
|
+
* [x, y, z, w] or { x, y, z, w } - instead of rotation
|
|
63
63
|
*/
|
|
64
|
-
quaternion?:
|
|
64
|
+
quaternion?: QuatInput;
|
|
65
65
|
/**
|
|
66
66
|
* a number scales uniformly
|
|
67
67
|
*/
|
|
@@ -87,9 +87,10 @@ export type NodeOptions = {
|
|
|
87
87
|
*/
|
|
88
88
|
tooltip?: ((hit: Hit) => any) | null;
|
|
89
89
|
/**
|
|
90
|
-
* hover tint for this subtree, over the View's
|
|
90
|
+
* hover tint for this subtree, over the View's; false for none - a
|
|
91
|
+
* floor or wall left plain under a View that tints everything else (null: the View's)
|
|
91
92
|
*/
|
|
92
|
-
highlight?: ColorInput | null;
|
|
93
|
+
highlight?: ColorInput | null | false;
|
|
93
94
|
/**
|
|
94
95
|
* hovering any part of this subtree tints all of it, as one thing - a
|
|
95
96
|
* piece of furniture, a machine - rather than just the part under the pointer (default false)
|
|
@@ -127,7 +128,7 @@ export type NodeOptions = {
|
|
|
127
128
|
*/
|
|
128
129
|
clip?: ClipOptions | Clip | null;
|
|
129
130
|
};
|
|
130
|
-
/** @import { ColorInput, Vec3Input } from '../types.js' */
|
|
131
|
+
/** @import { ColorInput, QuatInput, Vec3Input } from '../types.js' */
|
|
131
132
|
/** @import { Hit } from '../Raycaster.js' */
|
|
132
133
|
/** @import { Camera } from './cameras.js' */
|
|
133
134
|
/** @import { ClipOptions } from '../clip.js' */
|
|
@@ -157,7 +158,7 @@ export type NodeOptions = {
|
|
|
157
158
|
* @property {string} [name]
|
|
158
159
|
* @property {Vec3Input} [position]
|
|
159
160
|
* @property {Vec3Input | Euler} [rotation] Euler angles in radians, XYZ order
|
|
160
|
-
* @property {
|
|
161
|
+
* @property {QuatInput} [quaternion] [x, y, z, w] or { x, y, z, w } - instead of rotation
|
|
161
162
|
* @property {Vec3Input | number} [scale] a number scales uniformly
|
|
162
163
|
* @property {boolean} [visible]
|
|
163
164
|
* @property {boolean} [pickable] include in view.pick() (default true)
|
|
@@ -167,7 +168,8 @@ export type NodeOptions = {
|
|
|
167
168
|
* @property {UpdateHandler | UpdateHandler[]} [onUpdate]
|
|
168
169
|
* @property {((hit: Hit) => any) | null} [tooltip] tooltip for anything picked in this subtree; nearer nodes win, and
|
|
169
170
|
* all win over the View's
|
|
170
|
-
* @property {ColorInput | null} [highlight] hover tint for this subtree, over the View's
|
|
171
|
+
* @property {ColorInput | null | false} [highlight] hover tint for this subtree, over the View's; false for none - a
|
|
172
|
+
* floor or wall left plain under a View that tints everything else (null: the View's)
|
|
171
173
|
* @property {boolean} [highlightWhole] hovering any part of this subtree tints all of it, as one thing - a
|
|
172
174
|
* piece of furniture, a machine - rather than just the part under the pointer (default false)
|
|
173
175
|
* @property {((hit: Hit | null) => void) | null} [onHover] called as the thing under the pointer in this subtree changes
|
|
@@ -244,14 +246,17 @@ export declare class Node {
|
|
|
244
246
|
set rotation(v: Vec3Input);
|
|
245
247
|
/** Kept in sync with rotation. Assigning copies, normalised. @returns {Quat} */
|
|
246
248
|
get quaternion(): Quat;
|
|
247
|
-
/** @param {
|
|
248
|
-
set quaternion(v:
|
|
249
|
+
/** @param {QuatInput} v */
|
|
250
|
+
set quaternion(v: QuatInput);
|
|
249
251
|
/** @param {NodeOptions} [options] */
|
|
250
252
|
constructor(options?: NodeOptions);
|
|
251
|
-
/**
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Hover tint for this subtree, over the View's: a colour, false for none, or null to use the View's.
|
|
255
|
+
* @returns {Color | null | false}
|
|
256
|
+
*/
|
|
257
|
+
get highlight(): Color | null | false;
|
|
258
|
+
/** @param {ColorInput | null | false} value */
|
|
259
|
+
set highlight(value: ColorInput | null | false);
|
|
255
260
|
/**
|
|
256
261
|
* The section plane cutting this subtree, or null. Set { normal, offset, cap }, a Clip or null;
|
|
257
262
|
* move the cut by changing its offset or normal in place.
|
|
@@ -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,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
|
|
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 {
|
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} */
|
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
|
|
|
@@ -164,7 +168,7 @@ something changes):
|
|
|
164
168
|
## Scene graph
|
|
165
169
|
|
|
166
170
|
Everything in a scene is a node. Node options (all optional): `name`, `position`, `rotation`
|
|
167
|
-
(Euler XYZ radians, `[x, y, z]`), `quaternion
|
|
171
|
+
(Euler XYZ radians, `[x, y, z]`), `quaternion` (`[x, y, z, w]` or `{ x, y, z, w }`), `scale` (number or `[x, y, z]`), `visible`,
|
|
168
172
|
`pickable` (default true), `userData`, `children`, `onUpdate`, `clip` (a section plane - see
|
|
169
173
|
Cutaways), plus the interaction options below.
|
|
170
174
|
|
|
@@ -229,7 +233,11 @@ bars.setColor(3, 'red')
|
|
|
229
233
|
`setInstance(i, { position, rotation, quaternion, scale, color })`. An instance's `color` multiplies the
|
|
230
234
|
material's colour, which is white unless you set one - so the instance colour shows as given. `count` defaults to the number
|
|
231
235
|
of rows in `data`. For very large data, write `bars.instanceMatrices` (16 numbers per instance,
|
|
232
|
-
column-major) directly and call `bars.needsUpdate()`.
|
|
236
|
+
column-major) directly and call `bars.needsUpdate()`. An instance scaled to 0 is hidden: it draws
|
|
237
|
+
nothing, and bounds, framing, shadows and labels leave it out - so a pool can keep spare instances.
|
|
238
|
+
Positions and quaternions also take `{ x, y, z }` and `{ x, y, z, w }`, as physics engines return
|
|
239
|
+
them, so a physics bridge is one line a body:
|
|
240
|
+
`bricks.setInstance(i, { position: body.translation(), quaternion: body.rotation() })`.
|
|
233
241
|
|
|
234
242
|
**Labels** are HTML elements pinned to 3D points (style them with CSS). Options: `text`, `position`,
|
|
235
243
|
`className`, `place` (`'center'` | `'above'` | `'below'` | `'left'` | `'right'`), `offset` ([x, y]
|
|
@@ -237,7 +245,8 @@ pixels), `priority` (higher wins when labels overlap), `overlap` (`'hide'` | `'d
|
|
|
237
245
|
a label does when covered), `occlude` (true: counts as covered when geometry is in front), `leader`
|
|
238
246
|
(true: a line from the point to the label, with a dot on the point - for callouts), `edge` (`'left'` |
|
|
239
247
|
`'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)
|
|
248
|
+
`leader: true`; `offset` then moves it in and up or down), `minScale` (show it only when zoomed in this
|
|
249
|
+
far - below).
|
|
241
250
|
|
|
242
251
|
A label answers the pointer when it has handlers: a Label with its own `onClick` or `onHover` is
|
|
243
252
|
clicked and hovered like a mesh (`new Label({ text: 'Mantle', onClick: () => show('mantle') })` - a
|
|
@@ -250,6 +259,21 @@ clicking that bar, with the same hit. Other labels let the pointer through to th
|
|
|
250
259
|
new InstancedMesh({ geometry: box(1), data: rows, labels: { text: (row) => row.name, place: 'above', priority: (row) => row.alert ? 2 : 0 } })
|
|
251
260
|
```
|
|
252
261
|
|
|
262
|
+
**Labels by zoom level**: `minScale` on any label shows it only when the view is zoomed in at least that
|
|
263
|
+
far - that many CSS pixels to one world unit, at the label's point (perspective or orthographic alike).
|
|
264
|
+
Until then it takes no part in the layout, so it can't push other labels out. `labels` can also be a
|
|
265
|
+
list, several labels per instance - the short one always, the details as you come closer:
|
|
266
|
+
|
|
267
|
+
```js
|
|
268
|
+
new InstancedMesh({ geometry: box(1), data: elements, labels: [
|
|
269
|
+
{ text: (e) => e.symbol, place: 'center', className: 'symbol' },
|
|
270
|
+
{ text: (e) => e.name, place: 'below', className: 'name', minScale: 45 },
|
|
271
|
+
] })
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Pick `minScale` by looking: at the zoom where the details should appear, a world unit spans that many
|
|
275
|
+
pixels (a 1-unit tile drawn 60 px wide is 60). `view.explain()` counts the labels still waiting.
|
|
276
|
+
|
|
253
277
|
## Geometry
|
|
254
278
|
|
|
255
279
|
Generators return a `Geometry`; each takes one options object (a number is the main size):
|
|
@@ -287,7 +311,9 @@ extrude({
|
|
|
287
311
|
|
|
288
312
|
Your own: `new Geometry({ positions, normals?, uvs?, uvs2?, colors?, indices? })` - flat arrays, xyz per
|
|
289
313
|
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).
|
|
314
|
+
set, for baked occlusion and light maps (below). To animate one - cloth, a wave, a morph - write
|
|
315
|
+
`geometry.positions` in place and call `geometry.needsUpdate()` each frame; normals you didn't give are
|
|
316
|
+
worked out again to follow the shape.
|
|
291
317
|
|
|
292
318
|
## Materials
|
|
293
319
|
|
|
@@ -425,7 +451,7 @@ on top, as with UVs (animate `offset` to scroll a pattern). 0 (the default) uses
|
|
|
425
451
|
|---|---|
|
|
426
452
|
| `AmbientLight` | `color`, `intensity` |
|
|
427
453
|
| `HemisphereLight` | `sky`, `ground`, `intensity` |
|
|
428
|
-
| `DirectionalLight` | `color`, `intensity`, `direction` (the way it travels, e.g. `[-1, -2, -1]`), `castShadow` |
|
|
454
|
+
| `DirectionalLight` | `color`, `intensity`, `direction` (the way it travels, e.g. `[-1, -2, -1]`), `castShadow`, `shadowBounds` |
|
|
429
455
|
| `PointLight` | `color`, `intensity`, `position`, `range` - inverse-square falloff, so intensity ~ distance squared (30 lights things ~5 m away); `castShadow` |
|
|
430
456
|
| `SpotLight` | point light options plus `direction`, `angle`, `penumbra`, `castShadow` |
|
|
431
457
|
|
|
@@ -443,7 +469,9 @@ meshes opt in with `castShadow` and `receiveShadow`. No shadow camera to set up.
|
|
|
443
469
|
lamp - anything whose bounds hold the light - doesn't block it, so a lamp's shade can cast shadows of
|
|
444
470
|
its own. A point light casts all round, and everything that casts blocks it, around it too: a candle in
|
|
445
471
|
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`.
|
|
472
|
+
drawn at the light `castShadow: false`. The directional shadow is fitted to whatever casts, every
|
|
473
|
+
frame; when things fly far off, give the light `shadowBounds: { min: [x, y, z], max: [x, y, z] }` -
|
|
474
|
+
the world area that gets shadows - so the map stays sharp there.
|
|
447
475
|
|
|
448
476
|
```js
|
|
449
477
|
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 +497,9 @@ and `target` yourself; the orbit always turns about wherever the target is.
|
|
|
469
497
|
press or the wheel takes over.
|
|
470
498
|
- **Back to the start**: `view.frame(undefined, { direction: 'perspective', duration: 0.6 })`
|
|
471
499
|
(or the preset the view started with).
|
|
500
|
+
- **Where framing would go**: `view.framing(target, { padding, direction })` returns
|
|
501
|
+
`{ position, target, height? }` without moving the camera - for a camera that eases after
|
|
502
|
+
something that keeps changing size (`camera.position.lerp(aim.position, 0.1)`, the same for target).
|
|
472
503
|
- **Follow something that moves** - move the camera and its target by as much as it moved, so the
|
|
473
504
|
view keeps its angle and the user can still orbit round it:
|
|
474
505
|
|
|
@@ -524,7 +555,8 @@ view.on('hover', (hit) => show(hit?.datum)) // fires when the th
|
|
|
524
555
|
children. A node's `highlight` is the colour for its subtree; it tints the mesh, instance or point
|
|
525
556
|
under the pointer. Add `highlightWhole: true` to tint the whole subtree as one thing (a piece of
|
|
526
557
|
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
|
|
558
|
+
their own highlight light up - leave it off to keep floors and walls plain, or keep it and give
|
|
559
|
+
those `highlight: false` (`null` means "the View's").
|
|
528
560
|
- `onHover` and `onClick` are the app's own handler for a node; to add more without replacing it
|
|
529
561
|
(an add-on, a second concern), `node.on('hover' | 'click', fn)` - it returns a function that stops
|
|
530
562
|
listening. Both run. A drag has one handler, `onDrag`: one thing moves a node at a time.
|
|
@@ -549,6 +581,22 @@ chair.onDrag = (drag) => {
|
|
|
549
581
|
corners - as the canvas is; `outlineAntialias: false` on the View (or `view.renderer.outlineAntialias =
|
|
550
582
|
false`, any time) draws them in hard pixel steps instead.
|
|
551
583
|
- `view.controls.cancel()` lets go of the orbit gesture in progress, for apps that take over a press.
|
|
584
|
+
- **Keyboard and screen readers**: `keyboard: true` on the View makes the canvas a stop in the page's
|
|
585
|
+
tab order. The arrow keys (and Home, End, PageUp, PageDown) step through what can be hovered or
|
|
586
|
+
clicked, in scene order and each mesh's instances in order: each instance, point, surface grid
|
|
587
|
+
point (a mesh with a row per vertex) or row of data under a node with its own `tooltip`, `onClick`,
|
|
588
|
+
`onHover`, `onDrag` or `highlight` - and, when the View has a tooltip or handlers itself, each one
|
|
589
|
+
with data. A mesh with no rows is a stop when it has a name or a handler of its own. A
|
|
590
|
+
`highlightWhole` subtree is one stop. Lines with a row per segment (a network's links) are a stop
|
|
591
|
+
per segment, after everything else. The one stepped to is shown as hovering would show it - tint (`#ffcc00` when no highlight
|
|
592
|
+
is set), tooltip, hover handlers - and Enter or Space clicks it (the handler's event is null);
|
|
593
|
+
Escape leaves it, and moving the pointer takes over. A screen reader hears each one: the tooltip's
|
|
594
|
+
text, else its data, else its name, with "3 of 12" - or `keyboard: { label: (hit) => text }`. The
|
|
595
|
+
canvas is labelled with `description`, or from the scene: each chart's `summary()` ("Sales: bar
|
|
596
|
+
chart of 12 rows by month, sales"), else "3D view of 12 items".
|
|
597
|
+
- **Reduced motion**: `reducedMotion` follows the system's setting by default. When on, `frame()`
|
|
598
|
+
with a `duration` jumps, `autoRotate` holds, and chart transitions and flowing links settle at
|
|
599
|
+
once. Motion of your own (a `Timeline`, `onUpdate` spin) can check `view.reducedMotion`.
|
|
552
600
|
|
|
553
601
|
## Charts: `phoria2/charts`
|
|
554
602
|
|
|
@@ -600,7 +648,8 @@ A chart is a node with axes, ticks, grid walls, a legend and tooltips built in.
|
|
|
600
648
|
in it as a wrapping row, so give it no fixed height). Categories get swatches that click to hide
|
|
601
649
|
and show; a number colour gets a key - a ramp, highest at the top, with values marked up its side,
|
|
602
650
|
`ticks` as on an axis),
|
|
603
|
-
`theme: 'dark' | 'light'`, `title`, `
|
|
651
|
+
`theme: 'dark' | 'light'`, `title`, `description` (what a screen reader hears for it; else `chart.summary()`
|
|
652
|
+
makes one from the title and channels), `transition: false | { duration, ease }`,
|
|
604
653
|
`overlap: 'dim' | 'hide' | 'show'` (labels), `highlight` (a colour, false, or for networks
|
|
605
654
|
`'neighbours'` - the default, lighting the node's links and linked nodes - or `'node'`, the node alone), and node options (`position`, ...).
|
|
606
655
|
- **Surfaces**: one row per grid point - `x` and `z` are the grid (numbers or categories), `y` the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phoria2",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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",
|
package/schema/scene.schema.json
CHANGED
|
@@ -202,6 +202,11 @@
|
|
|
202
202
|
"type": "null"
|
|
203
203
|
}
|
|
204
204
|
]
|
|
205
|
+
},
|
|
206
|
+
"minScale": {
|
|
207
|
+
"type": "number",
|
|
208
|
+
"description": "Show the label only when zoomed in this far: CSS pixels to one world unit at its point (0, always).",
|
|
209
|
+
"minimum": 0
|
|
205
210
|
}
|
|
206
211
|
},
|
|
207
212
|
"unevaluatedProperties": false
|
|
@@ -327,6 +332,16 @@
|
|
|
327
332
|
},
|
|
328
333
|
"castShadow": {
|
|
329
334
|
"type": "boolean"
|
|
335
|
+
},
|
|
336
|
+
"shadowBounds": {
|
|
337
|
+
"type": ["object", "null"],
|
|
338
|
+
"description": "The part of the world, in world units, that has shadows; null (the default) follows what casts.",
|
|
339
|
+
"required": ["min", "max"],
|
|
340
|
+
"properties": {
|
|
341
|
+
"min": { "$ref": "#/$defs/vec3" },
|
|
342
|
+
"max": { "$ref": "#/$defs/vec3" }
|
|
343
|
+
},
|
|
344
|
+
"additionalProperties": false
|
|
330
345
|
}
|
|
331
346
|
},
|
|
332
347
|
"unevaluatedProperties": false
|
|
@@ -462,13 +477,16 @@
|
|
|
462
477
|
"description": "Anything of your own."
|
|
463
478
|
},
|
|
464
479
|
"highlight": {
|
|
465
|
-
"description": "Hover tint for this subtree,
|
|
480
|
+
"description": "Hover tint for this subtree; false for none, null to use the View's.",
|
|
466
481
|
"anyOf": [
|
|
467
482
|
{
|
|
468
483
|
"$ref": "#/$defs/color"
|
|
469
484
|
},
|
|
470
485
|
{
|
|
471
486
|
"type": "null"
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
"const": false
|
|
472
490
|
}
|
|
473
491
|
]
|
|
474
492
|
},
|
package/src/OrbitControl.js
CHANGED
|
@@ -255,8 +255,12 @@ export class OrbitControl {
|
|
|
255
255
|
this.target.add(move)
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
/**
|
|
259
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Apply inertia and auto-rotate; the View calls this each frame.
|
|
260
|
+
* @param {number} dt seconds
|
|
261
|
+
* @param {boolean} [still] hold auto-rotation - the View passes its reducedMotion
|
|
262
|
+
*/
|
|
263
|
+
update(dt, still = false) {
|
|
260
264
|
if (!this.enabled) return
|
|
261
265
|
if (this.#pointers.size === 0) {
|
|
262
266
|
const v = this.#velocity
|
|
@@ -267,7 +271,7 @@ export class OrbitControl {
|
|
|
267
271
|
v.yaw *= keep
|
|
268
272
|
v.pitch *= keep
|
|
269
273
|
}
|
|
270
|
-
if (this.autoRotate) this.rotate(-(this.autoRotate === true ? 0.5 : this.autoRotate) * dt, 0)
|
|
274
|
+
if (this.autoRotate && !still) this.rotate(-(this.autoRotate === true ? 0.5 : this.autoRotate) * dt, 0)
|
|
271
275
|
}
|
|
272
276
|
}
|
|
273
277
|
|