three-cad-viewer 4.3.9 → 5.0.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 → Readme.md} +7 -5
- package/dist/core/picking-controller.d.ts +125 -0
- package/dist/core/types.d.ts +13 -6
- package/dist/core/viewer-state.d.ts +1 -1
- package/dist/core/viewer.d.ts +44 -48
- package/dist/rendering/highlight.d.ts +209 -0
- package/dist/rendering/id-picking.d.ts +464 -0
- package/dist/rendering/light-detection.d.ts +3 -3
- package/dist/rendering/material-factory.d.ts +3 -1
- package/dist/rendering/picked.d.ts +55 -0
- package/dist/rendering/studio-composer.d.ts +2 -2
- package/dist/scene/clipping.d.ts +53 -0
- package/dist/scene/nestedgroup.d.ts +46 -4
- package/dist/scene/objectgroup.d.ts +17 -0
- package/dist/scene/render-shape.d.ts +3 -19
- package/dist/three-cad-viewer.css +54 -20
- package/dist/three-cad-viewer.esm.js +4095 -1381
- package/dist/three-cad-viewer.esm.js.map +1 -1
- package/dist/three-cad-viewer.esm.min.js +3 -3
- package/dist/three-cad-viewer.js +4095 -1381
- package/dist/three-cad-viewer.min.js +4 -4
- package/dist/tools/cad_tools/measure.d.ts +8 -9
- package/dist/tools/cad_tools/mesh-measure.d.ts +264 -0
- package/dist/tools/cad_tools/select.d.ts +4 -3
- package/dist/tools/cad_tools/tools.d.ts +16 -4
- package/dist/tools/cad_tools/ui.d.ts +8 -6
- package/dist/ui/display.d.ts +25 -2
- package/package.json +3 -2
- package/dist/rendering/raycast.d.ts +0 -111
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import type { Camera } from "../camera/camera.js";
|
|
3
|
+
/**
|
|
4
|
+
* Topology type of a pickable component. Mirrors the values of `TopoFilter`
|
|
5
|
+
* below (minus the `none` sentinel).
|
|
6
|
+
*/
|
|
7
|
+
export type TopoType = "face" | "edge" | "vertex" | "solid";
|
|
8
|
+
/**
|
|
9
|
+
* Filter types for topology-based picking. `none` is the "no filter" sentinel
|
|
10
|
+
* (all topos eligible).
|
|
11
|
+
*/
|
|
12
|
+
export declare const TopoFilter: {
|
|
13
|
+
none: null;
|
|
14
|
+
vertex: "vertex";
|
|
15
|
+
edge: "edge";
|
|
16
|
+
face: "face";
|
|
17
|
+
solid: "solid";
|
|
18
|
+
};
|
|
19
|
+
export type TopoFilterType = (typeof TopoFilter)[keyof typeof TopoFilter];
|
|
20
|
+
/** Reserved component id meaning "nothing under the cursor". */
|
|
21
|
+
export declare const BACKGROUND_ID = 0;
|
|
22
|
+
/**
|
|
23
|
+
* Name of the per-vertex integer buffer attribute carrying the component id on
|
|
24
|
+
* every pickable geometry (faces, edges and vertices). The geometry attaches a
|
|
25
|
+
* `Uint32BufferAttribute` under this name; the pick pass reads it as an **integer**
|
|
26
|
+
* attribute (`gpuType = THREE.IntType`, GLSL3 `in uint`) so ids stay exact past
|
|
27
|
+
* 2^24. The geometry side must set `gpuType = THREE.IntType` on the attribute for
|
|
28
|
+
* the pick shader to read it.
|
|
29
|
+
*/
|
|
30
|
+
export declare const COMPONENT_ID_ATTRIBUTE = "componentId";
|
|
31
|
+
/**
|
|
32
|
+
* three.js camera/object layers reserved for the pick passes.
|
|
33
|
+
*
|
|
34
|
+
* A pickable object lives on visual layer 0 (so the main camera draws it) **and**
|
|
35
|
+
* its topo's pick layer. The pick pass sets the (reused) main camera to a single
|
|
36
|
+
* pick layer so `overrideMaterial` only touches one topo at a time — a mesh shader
|
|
37
|
+
* can't render lines/points. `obj_vertices` points live on `VERTEX` *only* (off the
|
|
38
|
+
* visual pass).
|
|
39
|
+
*/
|
|
40
|
+
export declare const PICK_LAYER: {
|
|
41
|
+
readonly FACE: 1;
|
|
42
|
+
readonly EDGE: 2;
|
|
43
|
+
readonly VERTEX: 3;
|
|
44
|
+
};
|
|
45
|
+
export type PickLayer = (typeof PICK_LAYER)[keyof typeof PICK_LAYER];
|
|
46
|
+
/**
|
|
47
|
+
* Attach the per-vertex `componentId` integer attribute to a pickable geometry: the
|
|
48
|
+
* canonical way to bind {@link COMPONENT_ID_ATTRIBUTE} so the GLSL3 pick shader reads
|
|
49
|
+
* it as `in uint` (`gpuType = IntType` — without it three uploads floats and the bits
|
|
50
|
+
* are wrong). Use `instanced` for fat-line (per-segment) edge geometry.
|
|
51
|
+
*/
|
|
52
|
+
export declare function applyComponentIds(geometry: THREE.BufferGeometry, componentId: Uint32Array, instanced?: boolean): void;
|
|
53
|
+
/**
|
|
54
|
+
* Add `object` to its topo's pick layer additively — it stays on visual layer 0 (the
|
|
55
|
+
* main render pass) AND becomes pickable. For face/edge geometry and visible vertices.
|
|
56
|
+
*/
|
|
57
|
+
export declare function enablePickLayer(object: THREE.Object3D, topo: "face" | "edge" | "vertex"): void;
|
|
58
|
+
/**
|
|
59
|
+
* Put `object` on its topo's pick layer ONLY (off visual layer 0), so the main camera
|
|
60
|
+
* never draws it — for the pick-only `obj_vertices` Points cloud.
|
|
61
|
+
*/
|
|
62
|
+
export declare function setPickLayerExclusive(object: THREE.Object3D, topo: "face" | "edge" | "vertex"): void;
|
|
63
|
+
/**
|
|
64
|
+
* Metadata for one pickable component (face, edge, vertex, or solid). The id-buffer
|
|
65
|
+
* pass reads back an id, and this is what it resolves to.
|
|
66
|
+
*/
|
|
67
|
+
export interface ComponentInfo {
|
|
68
|
+
/** Unique id within the current model; encoded into the pick buffer. Never 0. */
|
|
69
|
+
id: number;
|
|
70
|
+
/** Full path, e.g. "/Assembly/Part1/faces/faces_3". */
|
|
71
|
+
path: string;
|
|
72
|
+
/** Leaf name, e.g. "faces_3". */
|
|
73
|
+
name: string;
|
|
74
|
+
/** Topology type. */
|
|
75
|
+
topo: TopoType;
|
|
76
|
+
/** Subtype of the owning shape (e.g. "solid"), or null. */
|
|
77
|
+
subtype: string | null;
|
|
78
|
+
/**
|
|
79
|
+
* Path of the owning solid when this component is a face/edge/vertex *inside* a
|
|
80
|
+
* solid — pick-only, no tree node. `null` for standalone faces/edges/vertices
|
|
81
|
+
* that are tree leaves in their own right.
|
|
82
|
+
*/
|
|
83
|
+
solidPath: string | null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Map a dropdown topo filter to the picker's `TopoType[]`. `none`/`[null]` (the
|
|
87
|
+
* "no filter" sentinel) → `undefined` (all topos eligible).
|
|
88
|
+
*/
|
|
89
|
+
export declare function pickerTopoFilter(filter: TopoFilterType[]): TopoType[] | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Tree-leaf path that owns a picked component: the owning solid's path for a
|
|
92
|
+
* sub-component, else the component's own path with the topo suffix stripped. This
|
|
93
|
+
* is the leaf the double-click pick and the visibility gate operate on.
|
|
94
|
+
*/
|
|
95
|
+
export declare function leafPath(info: ComponentInfo): string;
|
|
96
|
+
/**
|
|
97
|
+
* Signature of the live clip state, to detect actual clip changes for the picker
|
|
98
|
+
* (used by the render loop's dirty cadence).
|
|
99
|
+
*/
|
|
100
|
+
export declare function clipSignature(planes: THREE.Plane[] | null, intersection: boolean): string;
|
|
101
|
+
/**
|
|
102
|
+
* Flat `id -> ComponentInfo` registry. Replaces the `NestedGroup.groups[id]`
|
|
103
|
+
* explosion for pick lookups. Ids are allocated monotonically starting at 1;
|
|
104
|
+
* id 0 ({@link BACKGROUND_ID}) means "nothing".
|
|
105
|
+
*/
|
|
106
|
+
export declare class ComponentRegistry {
|
|
107
|
+
private byId;
|
|
108
|
+
private nextId;
|
|
109
|
+
constructor();
|
|
110
|
+
/**
|
|
111
|
+
* Register a component and return its freshly allocated id. The returned id is
|
|
112
|
+
* also written onto the stored record's `id` field.
|
|
113
|
+
*/
|
|
114
|
+
register(info: Omit<ComponentInfo, "id">): number;
|
|
115
|
+
/** Look up a component by id; `undefined` for unknown ids or background (0). */
|
|
116
|
+
get(id: number): ComponentInfo | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* Iterate every registered component in registration (id-ascending) order.
|
|
119
|
+
* Used by the highlight controller to resolve solid selection (all components
|
|
120
|
+
* sharing a `solidPath`) without a scene-graph walk.
|
|
121
|
+
*/
|
|
122
|
+
entries(): IterableIterator<ComponentInfo>;
|
|
123
|
+
/**
|
|
124
|
+
* Largest allocated id so far (0 when empty). Ids are allocated contiguously from
|
|
125
|
+
* 1 and NEVER recycled ({@link removeByPathPrefix} deletes records but keeps
|
|
126
|
+
* `nextId`), so this is the high-water mark used as the upper bound for sizing the
|
|
127
|
+
* highlight-state texture; it is `>=` {@link size} (equal until the first removal).
|
|
128
|
+
*/
|
|
129
|
+
get maxId(): number;
|
|
130
|
+
/** Number of registered components. */
|
|
131
|
+
get size(): number;
|
|
132
|
+
/**
|
|
133
|
+
* Drop every component whose path is `prefix` or lies under it (`prefix + "/"`),
|
|
134
|
+
* for {@link Viewer.removePart}. Does NOT recycle ids — surviving components keep
|
|
135
|
+
* their ids (and thus their highlight-state texel), and {@link maxId} stays put.
|
|
136
|
+
*/
|
|
137
|
+
removeByPathPrefix(prefix: string): void;
|
|
138
|
+
/** Drop all entries and reset id allocation. */
|
|
139
|
+
clear(): void;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Pack a component id into an RGBA8 byte tuple (full 32-bit range, low byte in R).
|
|
143
|
+
* The pick fragment shader must write the same layout. Inverse of {@link unpackId}.
|
|
144
|
+
*/
|
|
145
|
+
export declare function packId(id: number): [number, number, number, number];
|
|
146
|
+
/** Decode an RGBA8 tuple (as written by {@link packId}) back into a component id. */
|
|
147
|
+
export declare function unpackId(r: number, g: number, b: number, a: number): number;
|
|
148
|
+
/**
|
|
149
|
+
* Build the per-vertex face component-id attribute for a solid/standalone-face
|
|
150
|
+
* node's EXISTING indexed tessellation, registering one face component per face
|
|
151
|
+
* under `{path}/faces/faces_{i}` in array (backend-enumeration) order.
|
|
152
|
+
*
|
|
153
|
+
* No de-indexing: vertices are pooled per face, so each vertex maps to exactly one
|
|
154
|
+
* face. `collisions` counts vertices referenced by more than one face — it must be
|
|
155
|
+
* 0 (a non-zero value means cross-face sharing, i.e. de-indexing would be required).
|
|
156
|
+
*
|
|
157
|
+
* @param vertexCount - number of vertices in the position pool (`positions.length / 3`)
|
|
158
|
+
* @param triangles - flat index array (with `trianglesPerFace`) OR nested
|
|
159
|
+
* `number[][]` (one face's flat index list per entry)
|
|
160
|
+
* @param trianglesPerFace - per-face triangle counts when `triangles` is flat;
|
|
161
|
+
* `undefined` for the nested format
|
|
162
|
+
*/
|
|
163
|
+
export declare function buildFaceComponentIds(vertexCount: number, triangles: Uint32Array | number[] | number[][], trianglesPerFace: Uint32Array | number[] | undefined, path: string, subtype: string | null, registry: ComponentRegistry): {
|
|
164
|
+
componentId: Uint32Array;
|
|
165
|
+
collisions: number;
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Build the per-segment (instanced) edge component-id attribute, registering one
|
|
169
|
+
* edge component per edge under `{path}/edges/edges_{i}` in array order. Produces
|
|
170
|
+
* one id per line segment (one `LineSegments2` instance).
|
|
171
|
+
*
|
|
172
|
+
* @param edges - flat segment endpoints (with `segmentsPerEdge`) OR nested
|
|
173
|
+
* `number[][]` (one edge's flat points per entry, 6 floats per segment)
|
|
174
|
+
* @param segmentsPerEdge - per-edge segment counts when `edges` is flat;
|
|
175
|
+
* `undefined` for the nested format
|
|
176
|
+
*/
|
|
177
|
+
export declare function buildEdgeComponentIds(edges: Float32Array | number[] | number[][], segmentsPerEdge: Uint32Array | number[] | undefined, path: string, subtype: string | null, registry: ComponentRegistry): {
|
|
178
|
+
componentId: Uint32Array;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Build the per-point vertex component-id attribute for a node's `obj_vertices`
|
|
182
|
+
* (the real B-rep corners — the *selectable* vertices, NOT the triangle-mesh
|
|
183
|
+
* vertices), registering one vertex component per point under
|
|
184
|
+
* `{path}/vertices/vertices_{i}` in array (backend `get_vertices`) order.
|
|
185
|
+
*
|
|
186
|
+
* @param objVertices - flat xyz triples (3 floats per vertex)
|
|
187
|
+
*/
|
|
188
|
+
export declare function buildVertexComponentIds(objVertices: Float32Array | number[], path: string, subtype: string | null, registry: ComponentRegistry): {
|
|
189
|
+
componentId: Uint32Array;
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Result of a successful pick: the component id read from the id buffer, its
|
|
193
|
+
* registry metadata, and the world-space hit point.
|
|
194
|
+
*/
|
|
195
|
+
export interface PickResult {
|
|
196
|
+
/** Picked component id (never {@link BACKGROUND_ID}). */
|
|
197
|
+
id: number;
|
|
198
|
+
/** Registry metadata for the picked component. */
|
|
199
|
+
info: ComponentInfo;
|
|
200
|
+
/**
|
|
201
|
+
* World-space hit point, sourced from the position-MRT attachment. `null` when
|
|
202
|
+
* the RGBA32F position attachment is unsupported (id-only pick).
|
|
203
|
+
*/
|
|
204
|
+
point: THREE.Vector3 | null;
|
|
205
|
+
}
|
|
206
|
+
/** Options for {@link IdPicker.pickAt}. */
|
|
207
|
+
export interface PickAtOptions {
|
|
208
|
+
/**
|
|
209
|
+
* Restrict which topo types may be resolved (mirrors the existing
|
|
210
|
+
* `topoFilter`). `undefined`/empty = all (priority vertex>edge>face applies).
|
|
211
|
+
* `"solid"` makes faces eligible (caller maps the face to its `solidPath`).
|
|
212
|
+
*/
|
|
213
|
+
topoFilter?: TopoType[];
|
|
214
|
+
/**
|
|
215
|
+
* Odd N for the N×N readback window (touch radius for thin edges / tiny
|
|
216
|
+
* vertices). Defaults to {@link IdPicker}'s window size (5).
|
|
217
|
+
*/
|
|
218
|
+
windowSize?: number;
|
|
219
|
+
}
|
|
220
|
+
/** Options for the per-topo pick materials. */
|
|
221
|
+
export interface PickMaterialOptions {
|
|
222
|
+
/** Live clipping planes the pick pass must honor (so clipped geometry is not picked). */
|
|
223
|
+
clippingPlanes?: THREE.Plane[] | null;
|
|
224
|
+
/** Intersection clipping (mirror the visual material's `clipIntersection`). */
|
|
225
|
+
clipIntersection?: boolean;
|
|
226
|
+
/**
|
|
227
|
+
* Forward depth bias in **view-space world units** (a positive nudge toward the
|
|
228
|
+
* camera) so a corner's higher-priority topo wins the shared-depth pick test over
|
|
229
|
+
* its coincident lower-priority topo. Applied to `mvPosition.z` BEFORE projection —
|
|
230
|
+
* i.e. a constant world distance, independent of the camera's near/far spread.
|
|
231
|
+
* (An earlier build biased a constant in NDC z, which under an orthographic camera
|
|
232
|
+
* on a large scene became a multi-mm world window that let geometry behind an
|
|
233
|
+
* occluding face get picked "through" it.) Set per-scene from the bounding radius.
|
|
234
|
+
*/
|
|
235
|
+
depthBias?: number;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* View-space depth-bias factors (× scene bounding radius) for the edge and vertex
|
|
239
|
+
* pick passes. Face = 0. Kept monotonic with the vertex > edge > face hover priority
|
|
240
|
+
* so a corner's vertex wins over its edges and its edges over their faces. The factor
|
|
241
|
+
* is a fraction of the bounding radius: large enough to clear 24-bit depth-buffer
|
|
242
|
+
* quantization (≈ (far−near)/2^24 with far/near ∝ radius), small enough that the
|
|
243
|
+
* world window stays sub-mm on typical models so geometry a real distance behind an
|
|
244
|
+
* occluder is NOT picked through it.
|
|
245
|
+
*/
|
|
246
|
+
export declare const EDGE_DEPTH_BIAS_FACTOR = 0.0001;
|
|
247
|
+
export declare const VERTEX_DEPTH_BIAS_FACTOR = 0.0002;
|
|
248
|
+
/**
|
|
249
|
+
* Pick size (framebuffer px in the half-res target) for vertex pick points. Kept
|
|
250
|
+
* SMALL: the N×N readback window provides the "touch radius" by reading neighbours,
|
|
251
|
+
* so fat rendering is unnecessary and would only overwrite neighbouring face pixels
|
|
252
|
+
* in the shared id buffer (hurting faces-only picks near corners). A few px keeps
|
|
253
|
+
* the corner reliably present without eating faces.
|
|
254
|
+
*/
|
|
255
|
+
export declare const PICK_POINT_SIZE = 3;
|
|
256
|
+
/**
|
|
257
|
+
* Build the **face** pick override-material: a GLSL3 `ShaderMaterial` that reads the
|
|
258
|
+
* integer {@link COMPONENT_ID_ATTRIBUTE} and writes `packId(componentId)` +
|
|
259
|
+
* world position to the MRT pick target. Honors clipping (planes + intersection).
|
|
260
|
+
*/
|
|
261
|
+
export declare function createFacePickMaterial(options?: PickMaterialOptions): THREE.ShaderMaterial;
|
|
262
|
+
/**
|
|
263
|
+
* Build the **vertex** pick override-material for an `obj_vertices` `THREE.Points`
|
|
264
|
+
* cloud: a GLSL3 `ShaderMaterial` that draws each point **fat** (`gl_PointSize` =
|
|
265
|
+
* {@link PICK_POINT_SIZE}, a touch radius independent of the visual point size),
|
|
266
|
+
* reads the integer `componentId`, and writes id + world position to the MRT. A
|
|
267
|
+
* tiny forward depth bias lets a corner vertex win the depth test over its
|
|
268
|
+
* coincident face (so it can be picked), while occluded vertices stay hidden.
|
|
269
|
+
* Honors clipping.
|
|
270
|
+
*/
|
|
271
|
+
export declare function createVertexPickMaterial(options?: PickMaterialOptions): THREE.ShaderMaterial;
|
|
272
|
+
/**
|
|
273
|
+
* Pick width (px in the half-res target) for edge pick lines. Kept THIN: edges run
|
|
274
|
+
* along face boundaries, so a fat band would overwrite face pixels on both sides and
|
|
275
|
+
* wreck faces-only picks. The N×N readback window supplies the touch radius instead.
|
|
276
|
+
*/
|
|
277
|
+
export declare const PICK_EDGE_WIDTH = 2;
|
|
278
|
+
/**
|
|
279
|
+
* Build the **edge** pick override-material for a `LineSegments2` /
|
|
280
|
+
* `LineSegmentsGeometry` fat line: a GLSL3 `ShaderMaterial` that ports three
|
|
281
|
+
* `LineMaterial`'s screen-space instanced fat-line expansion (instanceStart/End +
|
|
282
|
+
* the `position` quad template, `linewidth`/`resolution` uniforms), reads the
|
|
283
|
+
* **instanced** integer `componentId`, and writes id + world position to the MRT.
|
|
284
|
+
* Drawn fat ({@link PICK_EDGE_WIDTH}) for a touch radius independent of the visual
|
|
285
|
+
* line width. `resolution` MUST be set to the pick target size by the caller.
|
|
286
|
+
* Honors clipping. (World-units + dash + color paths are dropped — pick only.)
|
|
287
|
+
*/
|
|
288
|
+
export declare function createEdgePickMaterial(options?: PickMaterialOptions): THREE.ShaderMaterial;
|
|
289
|
+
/** MRT attachment index of the packed-id texture. */
|
|
290
|
+
export declare const PICK_ID_ATTACHMENT = 0;
|
|
291
|
+
/** MRT attachment index of the world-position texture. */
|
|
292
|
+
export declare const PICK_POS_ATTACHMENT = 1;
|
|
293
|
+
/**
|
|
294
|
+
* Create the MRT pick render target: a single `WebGLRenderTarget` with two color
|
|
295
|
+
* attachments —
|
|
296
|
+
* - {@link PICK_ID_ATTACHMENT} (0): RGBA8/UnsignedByte packed `componentId`
|
|
297
|
+
* (`packId`, low byte in R), and
|
|
298
|
+
* - {@link PICK_POS_ATTACHMENT} (1): RGBA32F world-space hit position,
|
|
299
|
+
* `xyz` = point, `w = 1` where a fragment was written (0 = background).
|
|
300
|
+
*
|
|
301
|
+
* `NearestFilter` on both — interpolated ids/positions are meaningless. A depth
|
|
302
|
+
* buffer keeps the nearest fragment per pixel; no depth *texture* is read.
|
|
303
|
+
* `NoColorSpace` on the id texture stops any sRGB transform corrupting the bytes.
|
|
304
|
+
*
|
|
305
|
+
* Plain factory: the caller ({@link IdPicker.pickAt}) owns sizing/resize/disposal.
|
|
306
|
+
* `width`/`height` are already the pick-buffer pixel size (caller applies half-DPR).
|
|
307
|
+
*
|
|
308
|
+
* @param withPosition - allocate the RGBA32F world-position attachment. Requires
|
|
309
|
+
* `EXT_color_buffer_float` (probed by {@link IdPicker}); when `false` the target
|
|
310
|
+
* carries the id attachment only and `pickAt` returns `point = null` (the fat
|
|
311
|
+
* `out` at location 1 in the pick shaders is simply dropped — writing to an
|
|
312
|
+
* unbound draw buffer is well-defined and discarded in WebGL2).
|
|
313
|
+
*/
|
|
314
|
+
export declare function createPickTargets(width: number, height: number, withPosition?: boolean): THREE.WebGLRenderTarget;
|
|
315
|
+
/**
|
|
316
|
+
* GPU id-based picker: cursor → component id → registry → backend path, plus the
|
|
317
|
+
* world-space hit point.
|
|
318
|
+
*
|
|
319
|
+
* Lifecycle the host (viewer) drives:
|
|
320
|
+
* - {@link attach} once the scene + camera exist (re-attach after `clear()`),
|
|
321
|
+
* - {@link setSize} on canvas resize, {@link setClippingPlanes} on clip change,
|
|
322
|
+
* - {@link setDirty} whenever the view/geometry changes (camera move, geometry
|
|
323
|
+
* rebuild) so the pick buffer is re-rendered lazily on the next {@link pickAt},
|
|
324
|
+
* - {@link pickAt} on hover/click to resolve the component under the cursor.
|
|
325
|
+
*
|
|
326
|
+
* Per-topo passes (FACE, then fat EDGE lines, then fat VERTEX points) accumulate
|
|
327
|
+
* into one MRT target; `pickAt` reads an N×N window and resolves **vertex > edge >
|
|
328
|
+
* face** priority via the registry's topo, honoring `topoFilter`, then reads the
|
|
329
|
+
* position attachment → `point`.
|
|
330
|
+
*/
|
|
331
|
+
export declare class IdPicker {
|
|
332
|
+
readonly registry: ComponentRegistry;
|
|
333
|
+
/**
|
|
334
|
+
* Default N for the N×N readback window — the pick "touch radius" applied to the
|
|
335
|
+
* (thin) geometry. 3 is odd, so the window is symmetric about the cursor pixel
|
|
336
|
+
* (half = 1 → cursor ±1 on each axis); an even size is asymmetric. Tuned
|
|
337
|
+
* interactively (pick-explore.html): 2 and 3 feel identical, much larger (e.g. 7)
|
|
338
|
+
* pulls edge/vertex priority too far off the actual line/point; 1 is too unforgiving.
|
|
339
|
+
*/
|
|
340
|
+
windowSize: number;
|
|
341
|
+
private renderer;
|
|
342
|
+
/** Render source — the scene + camera the pick pass renders. */
|
|
343
|
+
private scene;
|
|
344
|
+
private camera;
|
|
345
|
+
/** Live clipping planes mirrored onto the pick materials (`null` = clipping off). */
|
|
346
|
+
private clippingPlanes;
|
|
347
|
+
/** Canvas size in CSS px (the pick target is sized at half-DPR internally). */
|
|
348
|
+
private width;
|
|
349
|
+
private height;
|
|
350
|
+
/** Intersection clipping mode mirrored onto the pick materials. */
|
|
351
|
+
private clipIntersection;
|
|
352
|
+
/**
|
|
353
|
+
* Scene bounding radius, used to scale the edge/vertex view-space depth bias to a
|
|
354
|
+
* small constant world distance (see {@link setSceneRadius}). 0 until the host sets
|
|
355
|
+
* it — with 0, coincident corner topo would z-fight and be unpickable, so the host
|
|
356
|
+
* MUST call {@link setSceneRadius} once the model bounds are known.
|
|
357
|
+
*/
|
|
358
|
+
private sceneRadius;
|
|
359
|
+
/** Whether the pick buffer must be re-rendered before the next read. */
|
|
360
|
+
private dirty;
|
|
361
|
+
/** MRT pick target (id + world-position attachments). Allocated lazily in `pickAt`. */
|
|
362
|
+
private pickTarget;
|
|
363
|
+
/** Per-topo pick override-materials. Allocated lazily in `pickAt`. */
|
|
364
|
+
private faceMaterial;
|
|
365
|
+
private edgeMaterial;
|
|
366
|
+
private vertexMaterial;
|
|
367
|
+
/** Clip-plane count the pick materials were last compiled for (program key). */
|
|
368
|
+
private compiledPlaneCount;
|
|
369
|
+
/** Intersection mode the pick materials were last compiled for (program key). */
|
|
370
|
+
private compiledIntersection;
|
|
371
|
+
/** Scratch to save/restore the renderer clear color across a pick pass. */
|
|
372
|
+
private readonly savedClearColor;
|
|
373
|
+
/** Scratch to save/restore the renderer viewport across a pick pass. */
|
|
374
|
+
private readonly savedViewport;
|
|
375
|
+
/** Scratch readback buffer for the position attachment (one RGBA32F pixel). */
|
|
376
|
+
private readonly posPixel;
|
|
377
|
+
/** Scratch readback buffer for the N×N id window (grown as needed). */
|
|
378
|
+
private idWindow;
|
|
379
|
+
/**
|
|
380
|
+
* Whether the GPU can render to a float color attachment (`EXT_color_buffer_float`,
|
|
381
|
+
* probed at construction). When `false`, the pick target carries the id attachment
|
|
382
|
+
* only and {@link pickAt} returns `point = null` — id picking still works, just
|
|
383
|
+
* without the world-space hit point (degrades hover coords / double-click pivot to
|
|
384
|
+
* the bbox-center fallback). Universal in real WebGL2; the probe guards exotic
|
|
385
|
+
* contexts.
|
|
386
|
+
*/
|
|
387
|
+
readonly positionSupported: boolean;
|
|
388
|
+
constructor(renderer: THREE.WebGLRenderer, registry: ComponentRegistry);
|
|
389
|
+
/**
|
|
390
|
+
* Probe `EXT_color_buffer_float` — the capability needed to *render into* the
|
|
391
|
+
* RGBA32F world-position attachment. Getting the extension is the canonical
|
|
392
|
+
* feature test (no side effects) and is WebGL2-only: it does not exist on WebGL1
|
|
393
|
+
* (whose float-color extension is `WEBGL_color_buffer_float`), so a non-null result
|
|
394
|
+
* already implies a WebGL2 context. Near-universal on WebGL2 but spec-optional.
|
|
395
|
+
*/
|
|
396
|
+
private static _probeFloatColor;
|
|
397
|
+
/**
|
|
398
|
+
* Point the picker at the scene + camera it renders for picking. Call once
|
|
399
|
+
* the scene exists and again after the scene/camera are recreated (`clear()`).
|
|
400
|
+
*/
|
|
401
|
+
attach(scene: THREE.Scene, camera: Camera): void;
|
|
402
|
+
/**
|
|
403
|
+
* Mark the pick buffer stale so the next {@link pickAt} re-renders it. Call on any
|
|
404
|
+
* view change (camera move/zoom, geometry rebuild) — NOT on mouse-move.
|
|
405
|
+
*/
|
|
406
|
+
setDirty(): void;
|
|
407
|
+
/**
|
|
408
|
+
* Set the scene bounding radius so the edge/vertex pick passes bias depth by a
|
|
409
|
+
* small constant WORLD distance (radius × {@link EDGE_DEPTH_BIAS_FACTOR} /
|
|
410
|
+
* {@link VERTEX_DEPTH_BIAS_FACTOR}) rather than a constant in NDC z. The world
|
|
411
|
+
* window is then independent of the camera near/far spread — the fix for geometry
|
|
412
|
+
* behind an occluding face getting picked "through" it under an orthographic camera
|
|
413
|
+
* on a large scene. Call whenever the model bounds change (initial build, add/remove
|
|
414
|
+
* part). Updates existing materials in place and seeds lazily-created ones.
|
|
415
|
+
*/
|
|
416
|
+
setSceneRadius(radius: number): void;
|
|
417
|
+
/** View-space edge depth bias (world units) for the current scene radius. */
|
|
418
|
+
private _edgeDepthBias;
|
|
419
|
+
/** View-space vertex depth bias (world units) for the current scene radius. */
|
|
420
|
+
private _vertexDepthBias;
|
|
421
|
+
/** Resize the pick render target to match the canvas (CSS px; half-DPR internally). */
|
|
422
|
+
setSize(width: number, height: number): void;
|
|
423
|
+
/**
|
|
424
|
+
* Update the clipping the pick materials must honor — planes (`null` when the
|
|
425
|
+
* viewer's clipping is off) and intersection mode (mirror the visual material's
|
|
426
|
+
* `clipIntersection`). Recompiles the materials only when the plane *count* or the
|
|
427
|
+
* intersection *mode* changes (three keys the program on `NUM_CLIPPING_PLANES` /
|
|
428
|
+
* `UNION_CLIPPING_PLANES`); a value-only change just re-renders. Always
|
|
429
|
+
* re-renders next {@link pickAt}.
|
|
430
|
+
*/
|
|
431
|
+
setClippingPlanes(planes: THREE.Plane[] | null, intersection?: boolean): void;
|
|
432
|
+
/**
|
|
433
|
+
* Resolve the component under the cursor, with its world-space hit point.
|
|
434
|
+
*
|
|
435
|
+
* @param x - canvas-relative x in CSS px (`getBoundingClientRect`)
|
|
436
|
+
* @param y - canvas-relative y in CSS px (top-left origin; WebGL Y-flip is internal)
|
|
437
|
+
* @returns the picked component (`point` = world hit position), or `null` for
|
|
438
|
+
* background / no hit.
|
|
439
|
+
*
|
|
440
|
+
* Re-renders the MRT pick target if {@link dirty} (per-topo passes on the pick
|
|
441
|
+
* layers, honoring clipping), then reads an N×N window around the cursor pixel
|
|
442
|
+
* (canvas px → ×dpr×0.5 → Y-flip) and resolves **vertex > edge > face** priority
|
|
443
|
+
* via the registry's topo (nearest-to-center tie-break), restricted to
|
|
444
|
+
* `options.topoFilter`. The position attachment at the chosen pixel → `point`.
|
|
445
|
+
*/
|
|
446
|
+
pickAt(x: number, y: number, options?: PickAtOptions): PickResult | null;
|
|
447
|
+
/** Lazily allocate / resize the MRT target and the per-topo pick materials. */
|
|
448
|
+
private _ensureResources;
|
|
449
|
+
/**
|
|
450
|
+
* Render the per-topo pick passes into the MRT target, reusing the main camera.
|
|
451
|
+
* Clears once, then accumulates **FACE → EDGE → VERTEX** (each drawn fat
|
|
452
|
+
* and later, so higher-priority topos overwrite at coincident pixels). All
|
|
453
|
+
* renderer state touched here is saved and restored.
|
|
454
|
+
*/
|
|
455
|
+
private _renderPickBuffer;
|
|
456
|
+
/** One pick pass: render only `layer`'s objects with `material` (no clear).
|
|
457
|
+
* Clipping planes are kept in sync solely by {@link setClippingPlanes} (and the
|
|
458
|
+
* lazy material creation in {@link _ensureResources}); re-assigning them here per
|
|
459
|
+
* pass would mutate `clippingPlanes` without `needsUpdate`, risking a stale
|
|
460
|
+
* `NUM_CLIPPING_PLANES` if the count ever changed off that path. */
|
|
461
|
+
private _pass;
|
|
462
|
+
/** Release GPU resources (pick render target + materials). */
|
|
463
|
+
dispose(): void;
|
|
464
|
+
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Analyzes equirectangular HDR pixel data to find dominant light sources
|
|
5
5
|
* (softboxes in studio HDRs, sun in outdoor HDRs). Returns direction,
|
|
6
|
-
* intensity, and color for up to
|
|
7
|
-
*
|
|
6
|
+
* intensity, and color for up to MAX_LIGHTS (currently 1), used to create the
|
|
7
|
+
* shadow-casting DirectionalLight in Studio mode.
|
|
8
8
|
*
|
|
9
9
|
* Algorithm: downsample to 128x64 luminance grid → threshold at 10x median
|
|
10
10
|
* → flood-fill cluster → convert centroids to 3D direction vectors.
|
|
@@ -32,7 +32,7 @@ export interface LightDetectionResult {
|
|
|
32
32
|
* @param data - Raw pixel data (Uint16Array for HalfFloat, or Float32Array)
|
|
33
33
|
* @param width - HDR image width in pixels
|
|
34
34
|
* @param height - HDR image height in pixels
|
|
35
|
-
* @returns Detection result with up to
|
|
35
|
+
* @returns Detection result with up to MAX_LIGHTS (currently 1) lights
|
|
36
36
|
*/
|
|
37
37
|
export declare function detectDominantLights(data: Uint16Array | Float32Array, width: number, height: number): LightDetectionResult;
|
|
38
38
|
/**
|
|
@@ -162,7 +162,9 @@ declare class MaterialFactory {
|
|
|
162
162
|
*
|
|
163
163
|
* threejs-materials `properties` uses simplified property names (e.g., "color",
|
|
164
164
|
* "roughness", "normal") where each entry has an optional `value` (scalar or
|
|
165
|
-
* [r,g,b] array
|
|
165
|
+
* [r,g,b] color array) and/or `texture` (inline data URI). Color arrays follow
|
|
166
|
+
* a per-key color-space convention: `color` is sRGB, the other color keys are
|
|
167
|
+
* linear (see the COLOR_ARRAY_KEYS handling in the values loop below).
|
|
166
168
|
*
|
|
167
169
|
* @param values - Scalar PBR values from threejs-materials
|
|
168
170
|
* @param textures - Texture map references from threejs-materials
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type * as THREE from "three";
|
|
2
|
+
import type { ComponentInfo, TopoType } from "./id-picking.js";
|
|
3
|
+
import type { HighlightController } from "./highlight.js";
|
|
4
|
+
/**
|
|
5
|
+
* A picked component, so the viewer's selection state and the measure/select
|
|
6
|
+
* tools consume ONE interface. {@link IdPicked} wraps a registry `ComponentInfo`
|
|
7
|
+
* + the shader `HighlightController` (the GPU id-pick path) — NO dependency on the
|
|
8
|
+
* exploded scene graph.
|
|
9
|
+
*/
|
|
10
|
+
export interface PickedComponent {
|
|
11
|
+
/** Canonical "/"-path sent to the backend (component path, or solid path when
|
|
12
|
+
* {@link fromSolid}). Replaces measure's `getId` + tree-sync name munging. */
|
|
13
|
+
readonly backendId: string;
|
|
14
|
+
/** Leaf name, e.g. "faces_3" — source for select.ts's numeric sub-index. */
|
|
15
|
+
readonly name: string;
|
|
16
|
+
/** Topology type of the resolved component. */
|
|
17
|
+
readonly topo: TopoType;
|
|
18
|
+
/** Solid-selection mode (the topo filter is "solid"). */
|
|
19
|
+
readonly fromSolid: boolean;
|
|
20
|
+
/** World-space hit point, when the backend provides one (else null). */
|
|
21
|
+
readonly point: THREE.Vector3 | null;
|
|
22
|
+
/** Show the hover highlight (`true`) — selection is via {@link toggleSelection}. */
|
|
23
|
+
highlight(asHover: boolean): void;
|
|
24
|
+
/** Remove the hover highlight; `keepSelected` preserves the selected state. */
|
|
25
|
+
unhighlight(keepSelected: boolean): void;
|
|
26
|
+
/** Toggle the persistent selected highlight. */
|
|
27
|
+
toggleSelection(): void;
|
|
28
|
+
/** Clear all highlight (hover + selected) for this component. */
|
|
29
|
+
clearHighlights(): void;
|
|
30
|
+
/** Identity equality (same component / same solid), for hover + isNewObject dedup. */
|
|
31
|
+
equals(other: PickedComponent | null): boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* `PickedComponent` over a GPU id-pick result. Drives the shader
|
|
35
|
+
* `HighlightController` by `componentId` (faces of a solid via `*Solid`), with no
|
|
36
|
+
* per-component `ObjectGroup` and no exploded graph.
|
|
37
|
+
*/
|
|
38
|
+
export declare class IdPicked implements PickedComponent {
|
|
39
|
+
readonly info: ComponentInfo;
|
|
40
|
+
readonly fromSolid: boolean;
|
|
41
|
+
readonly point: THREE.Vector3 | null;
|
|
42
|
+
private readonly highlightCtl;
|
|
43
|
+
constructor(info: ComponentInfo, fromSolid: boolean, point: THREE.Vector3 | null, highlightCtl: HighlightController);
|
|
44
|
+
/** Whether to act on the whole solid (faces) rather than the single component. */
|
|
45
|
+
private get asSolid();
|
|
46
|
+
get backendId(): string;
|
|
47
|
+
get name(): string;
|
|
48
|
+
get topo(): TopoType;
|
|
49
|
+
highlight(asHover: boolean): void;
|
|
50
|
+
unhighlight(keepSelected: boolean): void;
|
|
51
|
+
toggleSelection(): void;
|
|
52
|
+
clearHighlights(): void;
|
|
53
|
+
private _setSelected;
|
|
54
|
+
equals(other: PickedComponent | null): boolean;
|
|
55
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Wraps the pmndrs EffectComposer to provide:
|
|
5
5
|
* - Scene rendering (RenderPass)
|
|
6
6
|
* - Screen-space ambient occlusion (N8AOPostPass)
|
|
7
|
-
* - Screen-space shadow mask (
|
|
7
|
+
* - Screen-space shadow mask (PCFShadowMap + KawaseBlurPass)
|
|
8
8
|
* - Tone mapping + sRGB output + antialiasing (ToneMappingEffect + SMAAEffect)
|
|
9
9
|
*
|
|
10
10
|
* Tone mapping is handled by the postprocessing ToneMappingEffect, which uses
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* transparent, and the EffectPass alpha-blends its output onto a pre-cleared
|
|
19
19
|
* canvas that already has the correct background color.
|
|
20
20
|
*
|
|
21
|
-
* Shadow mask:
|
|
21
|
+
* Shadow mask: PCFShadowMap produces sharp shadow boundaries at 4096×4096.
|
|
22
22
|
* A half-resolution ShadowMaterial override pass captures the mask, which is
|
|
23
23
|
* then blurred via KawaseBlurPass and composited by ShadowMaskEffect before
|
|
24
24
|
* tone mapping. The floor keeps its own ShadowMaterial reading the shadow map
|
package/dist/scene/clipping.d.ts
CHANGED
|
@@ -2,6 +2,18 @@ import * as THREE from "three";
|
|
|
2
2
|
import type { Vector3Tuple } from "three";
|
|
3
3
|
import { ObjectGroup } from "./nestedgroup.js";
|
|
4
4
|
import type { Theme, ClipIndex } from "../core/types";
|
|
5
|
+
/**
|
|
6
|
+
* Cap-culling thresholds (see {@link Clipping.cull}). The per-solid stencil + cap
|
|
7
|
+
* meshes are correct but O(N) in draw calls; on a large assembly (~1300 solids ×
|
|
8
|
+
* 3 planes × 3 meshes ≈ 12000 draws/frame) a zoomed-out clip+rotate overruns the
|
|
9
|
+
* GPU watchdog → context loss. Culling bounds the per-frame work:
|
|
10
|
+
* - `CAP_CULL_MIN_PX`: skip a solid's caps when its projected screen radius is
|
|
11
|
+
* below this (sub-pixel solids contribute nothing visible zoomed out).
|
|
12
|
+
* - `CAP_CULL_BUDGET`: hard cap on the number of capped solids (largest-first);
|
|
13
|
+
* the real crash guard for pathologically dense views. Tune on a real GPU.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CAP_CULL_MIN_PX = 3;
|
|
16
|
+
export declare const CAP_CULL_BUDGET = 400;
|
|
5
17
|
/**
|
|
6
18
|
* A THREE.Plane that maintains a constant relative to a center point.
|
|
7
19
|
*/
|
|
@@ -103,6 +115,17 @@ declare class Clipping extends THREE.Group {
|
|
|
103
115
|
objectColorCaps: boolean;
|
|
104
116
|
planeHelpers: PlaneMeshGroup | null;
|
|
105
117
|
private _planeMeshGroup;
|
|
118
|
+
/** Per-solid stencil/cap units, the unit of screen-size culling. */
|
|
119
|
+
private _capUnits;
|
|
120
|
+
/**
|
|
121
|
+
* Whether {@link cull} last ran with clipping active. Lets the inactive path
|
|
122
|
+
* gate stencils/caps off exactly once, then early-return on later still frames.
|
|
123
|
+
* Starts `true` so the first inactive call performs the initial gate-off (the
|
|
124
|
+
* meshes default to `visible:true`).
|
|
125
|
+
*/
|
|
126
|
+
private _cullActive;
|
|
127
|
+
/** Reused buffer for the budget threshold sort (no per-frame alloc). */
|
|
128
|
+
private _radiiScratch;
|
|
106
129
|
/**
|
|
107
130
|
* Create a Clipping instance.
|
|
108
131
|
* @param center - The center point [x, y, z].
|
|
@@ -168,6 +191,36 @@ declare class Clipping extends THREE.Group {
|
|
|
168
191
|
* @param flag - True to show, false to hide.
|
|
169
192
|
*/
|
|
170
193
|
setVisible: (flag: boolean) => void;
|
|
194
|
+
/**
|
|
195
|
+
* Bound the per-frame stencil/cap draw work to keep large assemblies from
|
|
196
|
+
* overrunning the GPU watchdog on clip+rotate (see {@link CAP_CULL_MIN_PX}).
|
|
197
|
+
*
|
|
198
|
+
* Toggles each unit's `Object3D.visible` (NOT material.visible), which composes
|
|
199
|
+
* by AND with the existing material-level toggles — `setShapeVisible` (per-solid
|
|
200
|
+
* hide) and `setVisible` (clip-tab on/off) — so a unit renders only when it is
|
|
201
|
+
* un-culled AND its solid is shown AND the clip tab is active. Render order and
|
|
202
|
+
* the per-solid `clearStencil` isolation are untouched (removing a whole solid's
|
|
203
|
+
* units never affects the remaining solids' cap correctness).
|
|
204
|
+
*
|
|
205
|
+
* @param camera - The active camera (ortho or perspective).
|
|
206
|
+
* @param width - Canvas width in CSS px.
|
|
207
|
+
* @param height - Canvas height in CSS px.
|
|
208
|
+
* @param clipActive - `renderer.localClippingEnabled` (clip tab selected).
|
|
209
|
+
*/
|
|
210
|
+
cull(camera: THREE.Camera, width: number, height: number, clipActive: boolean): void;
|
|
211
|
+
/**
|
|
212
|
+
* World-space AABB of a solid (local bounding box transformed by the front
|
|
213
|
+
* mesh's world matrix, which folds in GDS z-scale). Returns a shared scratch
|
|
214
|
+
* Box3 (valid only until the next call) or `null` when geometry is missing.
|
|
215
|
+
*/
|
|
216
|
+
private _solidWorldBox;
|
|
217
|
+
/**
|
|
218
|
+
* Projected screen radius (px) of a solid's bounding sphere. Projects the world
|
|
219
|
+
* sphere center and a point one world-radius along the camera-right axis, and
|
|
220
|
+
* measures their screen-space separation — correct for both ortho and
|
|
221
|
+
* perspective. Returns `Infinity` (never cull) when geometry is missing.
|
|
222
|
+
*/
|
|
223
|
+
private _solidScreenRadius;
|
|
171
224
|
/**
|
|
172
225
|
* Save the current clipping state for later restoration.
|
|
173
226
|
* Captures plane positions, helper visibility, and stencil plane visibility.
|