three-cad-viewer 4.3.5 → 4.3.7
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/dist/three-cad-viewer.esm.js +8 -5
- package/dist/three-cad-viewer.esm.js.map +1 -1
- package/dist/three-cad-viewer.esm.min.js +1 -1
- package/dist/three-cad-viewer.js +8 -5
- package/dist/three-cad-viewer.min.js +1 -1
- package/package.json +2 -3
- package/src/_version.ts +0 -1
- package/src/camera/camera.ts +0 -445
- package/src/camera/controls/CADOrbitControls.ts +0 -241
- package/src/camera/controls/CADTrackballControls.ts +0 -598
- package/src/camera/controls.ts +0 -380
- package/src/core/patches.ts +0 -16
- package/src/core/studio-manager.ts +0 -652
- package/src/core/types.ts +0 -892
- package/src/core/viewer-state.ts +0 -784
- package/src/core/viewer.ts +0 -4821
- package/src/index.ts +0 -151
- package/src/rendering/environment.ts +0 -840
- package/src/rendering/light-detection.ts +0 -327
- package/src/rendering/material-factory.ts +0 -735
- package/src/rendering/material-presets.ts +0 -289
- package/src/rendering/raycast.ts +0 -291
- package/src/rendering/room-environment.ts +0 -192
- package/src/rendering/studio-composer.ts +0 -577
- package/src/rendering/studio-floor.ts +0 -108
- package/src/rendering/texture-cache.ts +0 -324
- package/src/rendering/tree-model.ts +0 -542
- package/src/rendering/triplanar.ts +0 -329
- package/src/scene/animation.ts +0 -343
- package/src/scene/axes.ts +0 -108
- package/src/scene/bbox.ts +0 -223
- package/src/scene/clipping.ts +0 -650
- package/src/scene/grid.ts +0 -864
- package/src/scene/nestedgroup.ts +0 -1448
- package/src/scene/objectgroup.ts +0 -866
- package/src/scene/orientation.ts +0 -259
- package/src/scene/render-shape.ts +0 -634
- package/src/tools/cad_tools/measure.ts +0 -811
- package/src/tools/cad_tools/select.ts +0 -100
- package/src/tools/cad_tools/tools.ts +0 -231
- package/src/tools/cad_tools/ui.ts +0 -454
- package/src/tools/cad_tools/zebra.ts +0 -369
- package/src/types/html.d.ts +0 -5
- package/src/types/n8ao.d.ts +0 -28
- package/src/types/three-augmentation.d.ts +0 -60
- package/src/ui/display.ts +0 -3295
- package/src/ui/index.html +0 -505
- package/src/ui/info.ts +0 -177
- package/src/ui/slider.ts +0 -206
- package/src/ui/toolbar.ts +0 -347
- package/src/ui/treeview.ts +0 -945
- package/src/utils/decode-instances.ts +0 -233
- package/src/utils/font.ts +0 -60
- package/src/utils/gpu-tracker.ts +0 -265
- package/src/utils/logger.ts +0 -92
- package/src/utils/sizeof.ts +0 -116
- package/src/utils/timer.ts +0 -69
- package/src/utils/utils.ts +0 -446
|
@@ -1,634 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shape tessellation and decomposition for rendering CAD objects.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as THREE from "three";
|
|
6
|
-
import { NestedGroup } from "./nestedgroup.js";
|
|
7
|
-
import { BoundingBox } from "./bbox.js";
|
|
8
|
-
import { flatten } from "../utils/utils.js";
|
|
9
|
-
import { hasTrianglesPerFace, hasSegmentsPerEdge } from "../core/types.js";
|
|
10
|
-
import type { Shapes, Shape, VisibilityState } from "../core/types.js";
|
|
11
|
-
|
|
12
|
-
// =============================================================================
|
|
13
|
-
// Types
|
|
14
|
-
// =============================================================================
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Tree data structure for shape visibility navigation.
|
|
18
|
-
* Maps object names to either nested tree data or visibility state.
|
|
19
|
-
*/
|
|
20
|
-
interface ShapeTreeData {
|
|
21
|
-
[key: string]: ShapeTreeData | VisibilityState;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Result from rendering tessellated shapes.
|
|
26
|
-
*/
|
|
27
|
-
interface RenderResult {
|
|
28
|
-
group: NestedGroup;
|
|
29
|
-
tree: ShapeTreeData;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Configuration options for shape rendering.
|
|
34
|
-
*/
|
|
35
|
-
interface ShapeRenderConfig {
|
|
36
|
-
cadWidth: number;
|
|
37
|
-
height: number;
|
|
38
|
-
edgeColor: number;
|
|
39
|
-
transparent: boolean;
|
|
40
|
-
defaultOpacity: number;
|
|
41
|
-
metalness: number;
|
|
42
|
-
roughness: number;
|
|
43
|
-
normalLen: number;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// =============================================================================
|
|
47
|
-
// Helper Functions
|
|
48
|
-
// =============================================================================
|
|
49
|
-
|
|
50
|
-
/** Convert a hex color number to CSS hex string */
|
|
51
|
-
function hexToColorString(hex: number | string): string {
|
|
52
|
-
// If already a string with #, return as-is
|
|
53
|
-
if (typeof hex === "string") {
|
|
54
|
-
return hex.startsWith("#") ? hex : `#${hex}`;
|
|
55
|
-
}
|
|
56
|
-
return `#${hex.toString(16).padStart(6, "0")}`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// =============================================================================
|
|
60
|
-
// ShapeRenderer Class
|
|
61
|
-
// =============================================================================
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Handles tessellation and decomposition of CAD shapes for rendering.
|
|
65
|
-
*/
|
|
66
|
-
class ShapeRenderer {
|
|
67
|
-
private config: ShapeRenderConfig;
|
|
68
|
-
private _bbox: BoundingBox | null = null;
|
|
69
|
-
|
|
70
|
-
constructor(config: ShapeRenderConfig) {
|
|
71
|
-
this.config = config;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Get the computed bounding box (set after rendering if shapes.bb is defined).
|
|
76
|
-
*/
|
|
77
|
-
get bbox(): BoundingBox | null {
|
|
78
|
-
return this._bbox;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Update configuration (e.g., when state changes).
|
|
83
|
-
*/
|
|
84
|
-
updateConfig(config: Partial<ShapeRenderConfig>): void {
|
|
85
|
-
this.config = { ...this.config, ...config };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Render tessellated shapes of a CAD object.
|
|
90
|
-
* @param shapes - The Shapes object representing the tessellated CAD object.
|
|
91
|
-
* @returns A nested THREE.Group object.
|
|
92
|
-
*/
|
|
93
|
-
private _renderTessellatedShapes(shapes: Shapes): NestedGroup {
|
|
94
|
-
const nestedGroup = new NestedGroup(
|
|
95
|
-
shapes,
|
|
96
|
-
this.config.cadWidth,
|
|
97
|
-
this.config.height,
|
|
98
|
-
this.config.edgeColor,
|
|
99
|
-
this.config.transparent,
|
|
100
|
-
this.config.defaultOpacity,
|
|
101
|
-
this.config.metalness,
|
|
102
|
-
this.config.roughness,
|
|
103
|
-
this.config.normalLen,
|
|
104
|
-
);
|
|
105
|
-
if (shapes.bb) {
|
|
106
|
-
this._bbox = new BoundingBox(
|
|
107
|
-
new THREE.Vector3(shapes.bb.xmin, shapes.bb.ymin, shapes.bb.zmin),
|
|
108
|
-
new THREE.Vector3(shapes.bb.xmax, shapes.bb.ymax, shapes.bb.zmax),
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
nestedGroup.render();
|
|
112
|
-
return nestedGroup;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Retrieve the navigation tree from a Shapes object.
|
|
117
|
-
* @param shapes - The Shapes object.
|
|
118
|
-
* @returns The navigation tree object.
|
|
119
|
-
*/
|
|
120
|
-
private _getTree(shapes: Shapes): ShapeTreeData {
|
|
121
|
-
const _getTree = (parts: Shapes[]): ShapeTreeData => {
|
|
122
|
-
const result: ShapeTreeData = {};
|
|
123
|
-
for (const part of parts) {
|
|
124
|
-
if (part.parts != null) {
|
|
125
|
-
result[part.name] = _getTree(part.parts);
|
|
126
|
-
} else {
|
|
127
|
-
result[part.name] = part.state as VisibilityState;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return result;
|
|
131
|
-
};
|
|
132
|
-
const tree: ShapeTreeData = {};
|
|
133
|
-
tree[shapes.name] = _getTree(shapes.parts ?? []);
|
|
134
|
-
return tree;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Decompose a CAD object into faces, edges and vertices.
|
|
139
|
-
* @param part - The part to decompose.
|
|
140
|
-
* @returns A decomposed part object.
|
|
141
|
-
*/
|
|
142
|
-
private _decompose(part: Shapes): Shapes {
|
|
143
|
-
const shape = part.shape!;
|
|
144
|
-
let j: number;
|
|
145
|
-
|
|
146
|
-
part.parts = [];
|
|
147
|
-
|
|
148
|
-
if (part.type === "shapes") {
|
|
149
|
-
// decompose faces
|
|
150
|
-
const new_part: Shapes = {
|
|
151
|
-
version: 2,
|
|
152
|
-
name: "faces",
|
|
153
|
-
id: `${part.id}/faces`,
|
|
154
|
-
parts: [],
|
|
155
|
-
loc: [
|
|
156
|
-
[0, 0, 0],
|
|
157
|
-
[0, 0, 0, 1],
|
|
158
|
-
],
|
|
159
|
-
};
|
|
160
|
-
let triangles: Uint32Array | number[];
|
|
161
|
-
// _convertArrays must be called before _decompose to ensure TypedArrays
|
|
162
|
-
if (!(shape.vertices instanceof Float32Array)) {
|
|
163
|
-
throw new Error("_decompose requires shape.vertices to be Float32Array (call _convertArrays first)");
|
|
164
|
-
}
|
|
165
|
-
if (!(shape.normals instanceof Float32Array)) {
|
|
166
|
-
throw new Error("_decompose requires shape.normals to be Float32Array (call _convertArrays first)");
|
|
167
|
-
}
|
|
168
|
-
const vertices = shape.vertices;
|
|
169
|
-
const normals = shape.normals;
|
|
170
|
-
const uvs = shape.uvs instanceof Float32Array ? shape.uvs : null;
|
|
171
|
-
// Determine format and validate
|
|
172
|
-
let current = 0;
|
|
173
|
-
if (hasTrianglesPerFace(shape)) {
|
|
174
|
-
// Binary format: flat Uint32Array with per-face counts
|
|
175
|
-
if (!(shape.triangles instanceof Uint32Array)) {
|
|
176
|
-
throw new Error("Expected Uint32Array for triangles in binary format");
|
|
177
|
-
}
|
|
178
|
-
const trianglesArray = shape.triangles;
|
|
179
|
-
const perFace = shape.triangles_per_face;
|
|
180
|
-
const num = perFace.length;
|
|
181
|
-
for (j = 0; j < num; j++) {
|
|
182
|
-
triangles = trianglesArray.subarray(
|
|
183
|
-
current,
|
|
184
|
-
current + 3 * perFace[j],
|
|
185
|
-
);
|
|
186
|
-
current += 3 * perFace[j];
|
|
187
|
-
|
|
188
|
-
const vecs = new Float32Array(triangles.length * 3);
|
|
189
|
-
const norms = new Float32Array(triangles.length * 3);
|
|
190
|
-
const uvArr = uvs ? new Float32Array(triangles.length * 2) : null;
|
|
191
|
-
for (let i = 0; i < triangles.length; i++) {
|
|
192
|
-
const s = triangles[i];
|
|
193
|
-
vecs[3 * i] = vertices[3 * s];
|
|
194
|
-
vecs[3 * i + 1] = vertices[3 * s + 1];
|
|
195
|
-
vecs[3 * i + 2] = vertices[3 * s + 2];
|
|
196
|
-
norms[3 * i] = normals[3 * s];
|
|
197
|
-
norms[3 * i + 1] = normals[3 * s + 1];
|
|
198
|
-
norms[3 * i + 2] = normals[3 * s + 2];
|
|
199
|
-
if (uvs && uvArr) {
|
|
200
|
-
uvArr[2 * i] = uvs[2 * s];
|
|
201
|
-
uvArr[2 * i + 1] = uvs[2 * s + 1];
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
const newShapeObj: Shape = {
|
|
205
|
-
triangles: [...Array(triangles.length).keys()],
|
|
206
|
-
vertices: Array.from(vecs),
|
|
207
|
-
normals: Array.from(norms),
|
|
208
|
-
edges: [],
|
|
209
|
-
obj_vertices: [],
|
|
210
|
-
edge_types: [],
|
|
211
|
-
face_types: [shape.face_types[j]],
|
|
212
|
-
};
|
|
213
|
-
if (uvArr) {
|
|
214
|
-
newShapeObj.uvs = Array.from(uvArr);
|
|
215
|
-
}
|
|
216
|
-
const new_shape: Shapes = {
|
|
217
|
-
version: 2,
|
|
218
|
-
loc: [
|
|
219
|
-
[0, 0, 0],
|
|
220
|
-
[0, 0, 0, 1],
|
|
221
|
-
],
|
|
222
|
-
name: `faces_${j}`,
|
|
223
|
-
id: `${part.id}/faces/faces_${j}`,
|
|
224
|
-
type: "shapes",
|
|
225
|
-
color: part.color,
|
|
226
|
-
alpha: part.alpha,
|
|
227
|
-
renderback: part.subtype !== "solid",
|
|
228
|
-
state: [1, 3],
|
|
229
|
-
accuracy: part.accuracy,
|
|
230
|
-
bb: null,
|
|
231
|
-
shape: newShapeObj,
|
|
232
|
-
};
|
|
233
|
-
if (part.texture) {
|
|
234
|
-
new_shape.texture = part.texture;
|
|
235
|
-
}
|
|
236
|
-
if (part.material) {
|
|
237
|
-
new_shape.material = part.material;
|
|
238
|
-
}
|
|
239
|
-
new_shape.geomtype = shape.face_types[j];
|
|
240
|
-
new_shape.subtype = part.subtype;
|
|
241
|
-
new_shape.exploded = true;
|
|
242
|
-
new_part.parts!.push(new_shape);
|
|
243
|
-
}
|
|
244
|
-
} else {
|
|
245
|
-
// Non-binary format: nested number[][] arrays
|
|
246
|
-
if (!Array.isArray(shape.triangles) || !Array.isArray(shape.triangles[0])) {
|
|
247
|
-
throw new Error("Expected nested array for triangles in non-binary format");
|
|
248
|
-
}
|
|
249
|
-
// After validation, we know shape.triangles is number[][] (TypeScript can't infer this)
|
|
250
|
-
const trianglesNested = shape.triangles as number[][];
|
|
251
|
-
const num = trianglesNested.length;
|
|
252
|
-
for (j = 0; j < num; j++) {
|
|
253
|
-
triangles = trianglesNested[j];
|
|
254
|
-
|
|
255
|
-
const vecs = new Float32Array(triangles.length * 3);
|
|
256
|
-
const norms = new Float32Array(triangles.length * 3);
|
|
257
|
-
const uvArr = uvs ? new Float32Array(triangles.length * 2) : null;
|
|
258
|
-
for (let i = 0; i < triangles.length; i++) {
|
|
259
|
-
const s = triangles[i];
|
|
260
|
-
vecs[3 * i] = vertices[3 * s];
|
|
261
|
-
vecs[3 * i + 1] = vertices[3 * s + 1];
|
|
262
|
-
vecs[3 * i + 2] = vertices[3 * s + 2];
|
|
263
|
-
norms[3 * i] = normals[3 * s];
|
|
264
|
-
norms[3 * i + 1] = normals[3 * s + 1];
|
|
265
|
-
norms[3 * i + 2] = normals[3 * s + 2];
|
|
266
|
-
if (uvs && uvArr) {
|
|
267
|
-
uvArr[2 * i] = uvs[2 * s];
|
|
268
|
-
uvArr[2 * i + 1] = uvs[2 * s + 1];
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
const newShapeObj2: Shape = {
|
|
272
|
-
triangles: [...Array(triangles.length).keys()],
|
|
273
|
-
vertices: Array.from(vecs),
|
|
274
|
-
normals: Array.from(norms),
|
|
275
|
-
edges: [],
|
|
276
|
-
obj_vertices: [],
|
|
277
|
-
edge_types: [],
|
|
278
|
-
face_types: [shape.face_types[j]],
|
|
279
|
-
};
|
|
280
|
-
if (uvArr) {
|
|
281
|
-
newShapeObj2.uvs = Array.from(uvArr);
|
|
282
|
-
}
|
|
283
|
-
const new_shape: Shapes = {
|
|
284
|
-
version: 2,
|
|
285
|
-
loc: [
|
|
286
|
-
[0, 0, 0],
|
|
287
|
-
[0, 0, 0, 1],
|
|
288
|
-
],
|
|
289
|
-
name: `faces_${j}`,
|
|
290
|
-
id: `${part.id}/faces/faces_${j}`,
|
|
291
|
-
type: "shapes",
|
|
292
|
-
color: part.color,
|
|
293
|
-
alpha: part.alpha,
|
|
294
|
-
renderback: part.subtype !== "solid",
|
|
295
|
-
state: [1, 3],
|
|
296
|
-
accuracy: part.accuracy,
|
|
297
|
-
bb: null,
|
|
298
|
-
shape: newShapeObj2,
|
|
299
|
-
};
|
|
300
|
-
if (part.texture) {
|
|
301
|
-
new_shape.texture = part.texture;
|
|
302
|
-
}
|
|
303
|
-
if (part.material) {
|
|
304
|
-
new_shape.material = part.material;
|
|
305
|
-
}
|
|
306
|
-
new_shape.geomtype = shape.face_types[j];
|
|
307
|
-
new_shape.subtype = part.subtype;
|
|
308
|
-
new_shape.exploded = true;
|
|
309
|
-
new_part.parts!.push(new_shape);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
part.parts.push(new_part);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (part.type === "shapes" || part.type === "edges") {
|
|
317
|
-
// decompose edges
|
|
318
|
-
const new_part: Shapes = {
|
|
319
|
-
version: 2,
|
|
320
|
-
parts: [],
|
|
321
|
-
loc: [
|
|
322
|
-
[0, 0, 0],
|
|
323
|
-
[0, 0, 0, 1],
|
|
324
|
-
],
|
|
325
|
-
name: "edges",
|
|
326
|
-
id: `${part.id}/edges`,
|
|
327
|
-
};
|
|
328
|
-
// Check if multiColor (array of colors per edge)
|
|
329
|
-
const multiColorArray = Array.isArray(part.color) ? part.color : null;
|
|
330
|
-
let color: string | string[] | undefined;
|
|
331
|
-
let edge: Float32Array | number[];
|
|
332
|
-
let current = 0;
|
|
333
|
-
|
|
334
|
-
if (hasSegmentsPerEdge(shape)) {
|
|
335
|
-
// Binary format: flat Float32Array with per-edge counts
|
|
336
|
-
if (!(shape.edges instanceof Float32Array)) {
|
|
337
|
-
throw new Error("Expected Float32Array for edges in binary format");
|
|
338
|
-
}
|
|
339
|
-
const edgesArray = shape.edges;
|
|
340
|
-
const perEdge = shape.segments_per_edge;
|
|
341
|
-
const num = perEdge.length;
|
|
342
|
-
for (j = 0; j < num; j++) {
|
|
343
|
-
edge = edgesArray.subarray(current, current + 6 * perEdge[j]);
|
|
344
|
-
current += 6 * perEdge[j];
|
|
345
|
-
color = multiColorArray ? multiColorArray[j] : part.color;
|
|
346
|
-
const new_shape: Shapes = {
|
|
347
|
-
version: 2,
|
|
348
|
-
loc: [
|
|
349
|
-
[0, 0, 0],
|
|
350
|
-
[0, 0, 0, 1],
|
|
351
|
-
],
|
|
352
|
-
name: `edges_${j}`,
|
|
353
|
-
id: `${part.id}/edges/edges_${j}`,
|
|
354
|
-
type: "edges",
|
|
355
|
-
color:
|
|
356
|
-
part.type === "shapes"
|
|
357
|
-
? hexToColorString(this.config.edgeColor)
|
|
358
|
-
: color,
|
|
359
|
-
state: [3, 1],
|
|
360
|
-
bb: null,
|
|
361
|
-
shape: {
|
|
362
|
-
edges: Array.from(edge),
|
|
363
|
-
vertices: [],
|
|
364
|
-
normals: [],
|
|
365
|
-
triangles: [],
|
|
366
|
-
obj_vertices: [],
|
|
367
|
-
edge_types: [shape.edge_types[j]],
|
|
368
|
-
face_types: [],
|
|
369
|
-
},
|
|
370
|
-
};
|
|
371
|
-
new_shape.width = part.type === "shapes" ? 1 : part.width;
|
|
372
|
-
new_shape.geomtype = shape.edge_types[j];
|
|
373
|
-
new_part.parts!.push(new_shape);
|
|
374
|
-
}
|
|
375
|
-
} else {
|
|
376
|
-
// Non-binary format: nested number[][] arrays
|
|
377
|
-
const edgesRaw = shape.edges;
|
|
378
|
-
if (!Array.isArray(edgesRaw) || (edgesRaw.length > 0 && !Array.isArray(edgesRaw[0]))) {
|
|
379
|
-
throw new Error("Expected nested array for edges in non-binary format");
|
|
380
|
-
}
|
|
381
|
-
// After validation, we know this is number[][] (TypeScript can't infer from the check)
|
|
382
|
-
const edgesNested = edgesRaw as number[][];
|
|
383
|
-
const num = edgesNested.length;
|
|
384
|
-
for (j = 0; j < num; j++) {
|
|
385
|
-
edge = edgesNested[j];
|
|
386
|
-
color = multiColorArray ? multiColorArray[j] : part.color;
|
|
387
|
-
const new_shape: Shapes = {
|
|
388
|
-
version: 2,
|
|
389
|
-
loc: [
|
|
390
|
-
[0, 0, 0],
|
|
391
|
-
[0, 0, 0, 1],
|
|
392
|
-
],
|
|
393
|
-
name: `edges_${j}`,
|
|
394
|
-
id: `${part.id}/edges/edges_${j}`,
|
|
395
|
-
type: "edges",
|
|
396
|
-
color:
|
|
397
|
-
part.type === "shapes"
|
|
398
|
-
? hexToColorString(this.config.edgeColor)
|
|
399
|
-
: color,
|
|
400
|
-
state: [3, 1],
|
|
401
|
-
bb: null,
|
|
402
|
-
shape: {
|
|
403
|
-
edges: edge,
|
|
404
|
-
vertices: [],
|
|
405
|
-
normals: [],
|
|
406
|
-
triangles: [],
|
|
407
|
-
obj_vertices: [],
|
|
408
|
-
edge_types: [shape.edge_types[j]],
|
|
409
|
-
face_types: [],
|
|
410
|
-
},
|
|
411
|
-
};
|
|
412
|
-
new_shape.width = part.type === "shapes" ? 1 : part.width;
|
|
413
|
-
new_shape.geomtype = shape.edge_types[j];
|
|
414
|
-
new_part.parts!.push(new_shape);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
if (new_part.parts!.length > 0) {
|
|
418
|
-
part.parts.push(new_part);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// decompose vertices
|
|
423
|
-
const new_part: Shapes = {
|
|
424
|
-
version: 2,
|
|
425
|
-
parts: [],
|
|
426
|
-
loc: [
|
|
427
|
-
[0, 0, 0],
|
|
428
|
-
[0, 0, 0, 1],
|
|
429
|
-
],
|
|
430
|
-
name: "vertices",
|
|
431
|
-
id: `${part.id}/vertices`,
|
|
432
|
-
};
|
|
433
|
-
const vertices = shape.obj_vertices;
|
|
434
|
-
for (j = 0; j < vertices.length / 3; j++) {
|
|
435
|
-
const new_shape: Shapes = {
|
|
436
|
-
version: 2,
|
|
437
|
-
loc: [
|
|
438
|
-
[0, 0, 0],
|
|
439
|
-
[0, 0, 0, 1],
|
|
440
|
-
],
|
|
441
|
-
name: `vertices_${j}`,
|
|
442
|
-
id: `${part.id}/vertices/vertices_${j}`,
|
|
443
|
-
type: "vertices",
|
|
444
|
-
color:
|
|
445
|
-
part.type === "shapes" || part.type === "edges"
|
|
446
|
-
? hexToColorString(this.config.edgeColor)
|
|
447
|
-
: part.color,
|
|
448
|
-
state: [3, 1],
|
|
449
|
-
bb: null,
|
|
450
|
-
shape: {
|
|
451
|
-
obj_vertices: [
|
|
452
|
-
vertices[3 * j],
|
|
453
|
-
vertices[3 * j + 1],
|
|
454
|
-
vertices[3 * j + 2],
|
|
455
|
-
],
|
|
456
|
-
vertices: [],
|
|
457
|
-
normals: [],
|
|
458
|
-
triangles: [],
|
|
459
|
-
edges: [],
|
|
460
|
-
edge_types: [],
|
|
461
|
-
face_types: [],
|
|
462
|
-
},
|
|
463
|
-
};
|
|
464
|
-
new_shape.size =
|
|
465
|
-
part.type === "shapes" || part.type === "edges" ? 4 : part.size;
|
|
466
|
-
new_part.parts!.push(new_shape);
|
|
467
|
-
}
|
|
468
|
-
if (new_part.parts!.length > 0) {
|
|
469
|
-
part.parts.push(new_part);
|
|
470
|
-
}
|
|
471
|
-
delete part.shape;
|
|
472
|
-
delete part.color;
|
|
473
|
-
delete part.alpha;
|
|
474
|
-
delete part.accuracy;
|
|
475
|
-
delete part.renderback;
|
|
476
|
-
|
|
477
|
-
return part;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Convert Shape arrays to TypedArrays for efficient rendering.
|
|
482
|
-
* Note: This mutates the shape in place.
|
|
483
|
-
*/
|
|
484
|
-
private _convertArrays(shape: Shape): void {
|
|
485
|
-
// Mutable interface to allow TypedArray assignment
|
|
486
|
-
interface MutableShape {
|
|
487
|
-
triangles: number[] | number[][] | Uint32Array;
|
|
488
|
-
edges: number[] | number[][] | Float32Array;
|
|
489
|
-
vertices: number[] | Float32Array;
|
|
490
|
-
normals: number[] | number[][] | Float32Array;
|
|
491
|
-
obj_vertices: number[] | Float32Array;
|
|
492
|
-
face_types: number[] | Uint32Array;
|
|
493
|
-
edge_types: number[] | Uint8Array | Uint32Array;
|
|
494
|
-
triangles_per_face?: number[] | Uint32Array;
|
|
495
|
-
segments_per_edge?: number[] | Uint32Array;
|
|
496
|
-
uvs?: number[] | Float32Array;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Shape interface matches MutableShape - we cast to allow reassignment
|
|
500
|
-
const s: MutableShape = shape;
|
|
501
|
-
|
|
502
|
-
// triangles: flat array or nested array -> Uint32Array
|
|
503
|
-
// Note: Only flat triangles (with triangles_per_face) are converted here.
|
|
504
|
-
// Nested triangles (number[][]) are kept as-is for _decompose to handle.
|
|
505
|
-
if (s.triangles != null && !(s.triangles instanceof Uint32Array)) {
|
|
506
|
-
// Only convert if it's a flat number[] (has triangles_per_face)
|
|
507
|
-
if (s.triangles_per_face !== undefined) {
|
|
508
|
-
if (!Array.isArray(s.triangles[0])) {
|
|
509
|
-
s.triangles = new Uint32Array(s.triangles as number[]);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
// If no triangles_per_face, leave as number[][] for _decompose
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// edges: nested number[][] -> Float32Array (flattened)
|
|
516
|
-
// Only flatten if it's actually nested (no segments_per_edge means nested format)
|
|
517
|
-
if (s.edges != null && !(s.edges instanceof Float32Array)) {
|
|
518
|
-
if (s.segments_per_edge !== undefined) {
|
|
519
|
-
if (!Array.isArray(s.edges[0])) {
|
|
520
|
-
// Flat number[] — convert directly
|
|
521
|
-
s.edges = new Float32Array(s.edges as number[]);
|
|
522
|
-
} else {
|
|
523
|
-
// Nested number[][] with segments_per_edge — flatten then convert
|
|
524
|
-
s.edges = new Float32Array(flatten(s.edges as number[][], 1));
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
// If no segments_per_edge, leave as number[][] for _decompose
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// vertices: always flat number[] -> Float32Array
|
|
531
|
-
if (s.vertices != null && !(s.vertices instanceof Float32Array)) {
|
|
532
|
-
s.vertices = new Float32Array(s.vertices as number[]);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// normals: flat or nested -> Float32Array
|
|
536
|
-
// Only process if there are normals (non-empty array)
|
|
537
|
-
if (s.normals != null && !(s.normals instanceof Float32Array)) {
|
|
538
|
-
if (Array.isArray(s.normals) && s.normals.length > 0) {
|
|
539
|
-
if (Array.isArray(s.normals[0])) {
|
|
540
|
-
// Nested format: flatten first
|
|
541
|
-
s.normals = new Float32Array(flatten(s.normals as number[][], 2));
|
|
542
|
-
} else {
|
|
543
|
-
// Already flat
|
|
544
|
-
s.normals = new Float32Array(s.normals as number[]);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// obj_vertices: always flat number[] -> Float32Array
|
|
550
|
-
if (s.obj_vertices != null && !(s.obj_vertices instanceof Float32Array)) {
|
|
551
|
-
s.obj_vertices = new Float32Array(s.obj_vertices as number[]);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// uvs: flat number[] -> Float32Array (2 floats per vertex)
|
|
555
|
-
if (s.uvs != null && !(s.uvs instanceof Float32Array)) {
|
|
556
|
-
s.uvs = new Float32Array(s.uvs as number[]);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// face_types: number[] -> Uint32Array
|
|
560
|
-
if (s.face_types != null && !(s.face_types instanceof Uint32Array)) {
|
|
561
|
-
s.face_types = new Uint32Array(s.face_types);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// edge_types: number[] or Uint8Array -> Uint32Array
|
|
565
|
-
if (s.edge_types != null && !(s.edge_types instanceof Uint32Array)) {
|
|
566
|
-
if (s.edge_types instanceof Uint8Array) {
|
|
567
|
-
s.edge_types = new Uint32Array(s.edge_types);
|
|
568
|
-
} else {
|
|
569
|
-
s.edge_types = new Uint32Array(s.edge_types);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
// triangles_per_face: number[] -> Uint32Array
|
|
574
|
-
if (
|
|
575
|
-
s.triangles_per_face != null &&
|
|
576
|
-
!(s.triangles_per_face instanceof Uint32Array)
|
|
577
|
-
) {
|
|
578
|
-
s.triangles_per_face = new Uint32Array(s.triangles_per_face);
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// segments_per_edge: number[] -> Uint32Array
|
|
582
|
-
if (
|
|
583
|
-
s.segments_per_edge != null &&
|
|
584
|
-
!(s.segments_per_edge instanceof Uint32Array)
|
|
585
|
-
) {
|
|
586
|
-
s.segments_per_edge = new Uint32Array(s.segments_per_edge);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
* Recursively process shapes, converting arrays and decomposing parts.
|
|
592
|
-
*/
|
|
593
|
-
private _processShapes(shapes: Shapes): Shapes {
|
|
594
|
-
if (shapes.version === 2 || shapes.version === 3) {
|
|
595
|
-
const parts: Shapes[] = [];
|
|
596
|
-
for (let i = 0; i < (shapes.parts?.length ?? 0); i++) {
|
|
597
|
-
const part = shapes.parts![i];
|
|
598
|
-
if (part.shape != null) {
|
|
599
|
-
this._convertArrays(part.shape);
|
|
600
|
-
}
|
|
601
|
-
if (part.parts != null) {
|
|
602
|
-
const tmp = this._processShapes(part);
|
|
603
|
-
parts.push(tmp);
|
|
604
|
-
} else {
|
|
605
|
-
parts.push(this._decompose(part));
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
shapes.parts = parts;
|
|
609
|
-
}
|
|
610
|
-
return shapes;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Render the shapes of the CAD object.
|
|
615
|
-
* @param exploded - Whether to render the compact or exploded version
|
|
616
|
-
* @param shapes - The Shapes object.
|
|
617
|
-
* @returns A nested THREE.Group object and navigation tree.
|
|
618
|
-
*/
|
|
619
|
-
render(exploded: boolean, shapes: Shapes): RenderResult {
|
|
620
|
-
let processedShapes: Shapes;
|
|
621
|
-
if (exploded) {
|
|
622
|
-
processedShapes = this._processShapes(structuredClone(shapes));
|
|
623
|
-
} else {
|
|
624
|
-
processedShapes = structuredClone(shapes);
|
|
625
|
-
}
|
|
626
|
-
const group = this._renderTessellatedShapes(processedShapes);
|
|
627
|
-
const tree = this._getTree(processedShapes);
|
|
628
|
-
|
|
629
|
-
return { group, tree };
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
export { ShapeRenderer };
|
|
634
|
-
export type { ShapeTreeData, RenderResult, ShapeRenderConfig };
|