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
package/src/utils/sizeof.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Options for memory size calculation.
|
|
3
|
-
*/
|
|
4
|
-
interface MemorySizeOptions {
|
|
5
|
-
excludeAttributes?: string[];
|
|
6
|
-
visited?: WeakSet<object>;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function calculateObjectSize(obj: unknown, options: MemorySizeOptions = {}): number {
|
|
10
|
-
const {
|
|
11
|
-
excludeAttributes = ["parent", "context", "_parent", "__parent"],
|
|
12
|
-
visited = new WeakSet(),
|
|
13
|
-
} = options;
|
|
14
|
-
|
|
15
|
-
// Prevent circular reference
|
|
16
|
-
if (obj === null || obj === undefined) return 0;
|
|
17
|
-
|
|
18
|
-
// Check for primitive types
|
|
19
|
-
if (
|
|
20
|
-
["number", "string", "boolean", "symbol", "bigint"].includes(typeof obj)
|
|
21
|
-
) {
|
|
22
|
-
return estimatePrimitiveSize(obj as string | number | boolean | symbol | bigint);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Prevent circular references
|
|
26
|
-
if (typeof obj === "object" && visited.has(obj)) return 0;
|
|
27
|
-
if (typeof obj === "object") visited.add(obj);
|
|
28
|
-
|
|
29
|
-
// Handle Arrays
|
|
30
|
-
if (Array.isArray(obj)) {
|
|
31
|
-
return obj.reduce(
|
|
32
|
-
(total, item) =>
|
|
33
|
-
total +
|
|
34
|
-
calculateObjectSize(item, {
|
|
35
|
-
excludeAttributes,
|
|
36
|
-
visited,
|
|
37
|
-
}),
|
|
38
|
-
estimateArrayOverhead(obj),
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Handle TypedArrays
|
|
43
|
-
if (ArrayBuffer.isView(obj)) {
|
|
44
|
-
return obj.byteLength + estimateTypedArrayOverhead();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Handle Maps
|
|
48
|
-
if (obj instanceof Map) {
|
|
49
|
-
let mapSize = estimateMapOverhead(obj);
|
|
50
|
-
for (const [key, value] of obj) {
|
|
51
|
-
mapSize += calculateObjectSize(key, { excludeAttributes, visited });
|
|
52
|
-
mapSize += calculateObjectSize(value, { excludeAttributes, visited });
|
|
53
|
-
}
|
|
54
|
-
return mapSize;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Handle regular objects
|
|
58
|
-
if (typeof obj === "object") {
|
|
59
|
-
let objectSize = estimateObjectOverhead();
|
|
60
|
-
for (const key in obj) {
|
|
61
|
-
// Skip excluded attributes
|
|
62
|
-
if (
|
|
63
|
-
Object.prototype.hasOwnProperty.call(obj, key) &&
|
|
64
|
-
!excludeAttributes.includes(key)
|
|
65
|
-
) {
|
|
66
|
-
objectSize += calculateObjectSize(key, { excludeAttributes, visited });
|
|
67
|
-
objectSize += calculateObjectSize((obj as Record<string, unknown>)[key], {
|
|
68
|
-
excludeAttributes,
|
|
69
|
-
visited,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return objectSize;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// For functions or unknown types
|
|
77
|
-
return 0;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function estimatePrimitiveSize(primitive: string | number | boolean | symbol | bigint | null | undefined): number {
|
|
81
|
-
if (primitive === null || primitive === undefined) return 0;
|
|
82
|
-
switch (typeof primitive) {
|
|
83
|
-
case "number":
|
|
84
|
-
return 8; // 64-bit float
|
|
85
|
-
case "string":
|
|
86
|
-
return primitive.length * 2; // Assume UTF-16 encoding
|
|
87
|
-
case "boolean":
|
|
88
|
-
return 4;
|
|
89
|
-
case "symbol":
|
|
90
|
-
return 8;
|
|
91
|
-
case "bigint":
|
|
92
|
-
return 8;
|
|
93
|
-
default:
|
|
94
|
-
return 0;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function estimateArrayOverhead(arr: unknown[]): number {
|
|
99
|
-
return 24 + arr.length * 8; // Array header + pointer overhead
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function estimateTypedArrayOverhead(): number {
|
|
103
|
-
return 24; // Typical overhead for TypedArrays
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function estimateMapOverhead(map: Map<unknown, unknown>): number {
|
|
107
|
-
return 40 + map.size * 16; // Map header + entry overhead
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function estimateObjectOverhead(): number {
|
|
111
|
-
return 32; // Basic object overhead
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function sizeof(obj: unknown): number {
|
|
115
|
-
return calculateObjectSize(obj);
|
|
116
|
-
}
|
package/src/utils/timer.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Performance timing utility for measuring execution time.
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* ```typescript
|
|
6
|
-
* const timer = new Timer("render", true);
|
|
7
|
-
* // ... do some work ...
|
|
8
|
-
* timer.split("tessellation complete");
|
|
9
|
-
* // ... do more work ...
|
|
10
|
-
* timer.stop();
|
|
11
|
-
* ```
|
|
12
|
-
*
|
|
13
|
-
* @public
|
|
14
|
-
*/
|
|
15
|
-
class Timer {
|
|
16
|
-
private prefix: string;
|
|
17
|
-
private timeit: boolean;
|
|
18
|
-
private start: number;
|
|
19
|
-
private last: number;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Create a new Timer instance.
|
|
23
|
-
*
|
|
24
|
-
* @param prefix - Label prefix for log messages
|
|
25
|
-
* @param timeit - If false, all timing operations are no-ops
|
|
26
|
-
*/
|
|
27
|
-
constructor(prefix: string, timeit: boolean) {
|
|
28
|
-
this.prefix = prefix;
|
|
29
|
-
this.timeit = timeit;
|
|
30
|
-
this.start = performance.now();
|
|
31
|
-
this.last = this.start;
|
|
32
|
-
if (timeit) {
|
|
33
|
-
console.info(`three-cad-viewer: ${prefix}:timer start`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Log a split time (time since last split or start).
|
|
39
|
-
*
|
|
40
|
-
* @param msg - Message to include in the log output
|
|
41
|
-
*/
|
|
42
|
-
split(msg: string): void {
|
|
43
|
-
if (this.timeit) {
|
|
44
|
-
const t = performance.now();
|
|
45
|
-
console.info(
|
|
46
|
-
`three-cad-viewer: ${this.prefix}:${msg}:timer split ${(
|
|
47
|
-
t - this.last
|
|
48
|
-
).toFixed(1)} ms`,
|
|
49
|
-
);
|
|
50
|
-
this.last = t;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Log total elapsed time and stop the timer.
|
|
56
|
-
*/
|
|
57
|
-
stop(): void {
|
|
58
|
-
if (this.timeit) {
|
|
59
|
-
const t = performance.now();
|
|
60
|
-
console.info(
|
|
61
|
-
`three-cad-viewer: ${this.prefix}:timer stop ${(t - this.start).toFixed(
|
|
62
|
-
1,
|
|
63
|
-
)} ms:`,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export { Timer };
|
package/src/utils/utils.ts
DELETED
|
@@ -1,446 +0,0 @@
|
|
|
1
|
-
import * as THREE from "three";
|
|
2
|
-
import type { Vector3Tuple, QuaternionTuple } from "three";
|
|
3
|
-
import type { LineSegments2 } from "three/examples/jsm/lines/LineSegments2.js";
|
|
4
|
-
import type { Axis } from "../core/types.js";
|
|
5
|
-
import { gpuTracker } from "./gpu-tracker.js";
|
|
6
|
-
|
|
7
|
-
// =============================================================================
|
|
8
|
-
// Constants
|
|
9
|
-
// =============================================================================
|
|
10
|
-
|
|
11
|
-
/** Unit vectors for each axis */
|
|
12
|
-
export const AXIS_VECTORS: Readonly<Record<Axis, THREE.Vector3>> = {
|
|
13
|
-
x: new THREE.Vector3(1, 0, 0),
|
|
14
|
-
y: new THREE.Vector3(0, 1, 0),
|
|
15
|
-
z: new THREE.Vector3(0, 0, 1),
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// =============================================================================
|
|
19
|
-
// Utility Functions
|
|
20
|
-
// =============================================================================
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Flatten a nested array of numbers to a 1D array.
|
|
24
|
-
* The cast is necessary because TypeScript's flat() return type
|
|
25
|
-
* is a complex union that doesn't simplify to number[].
|
|
26
|
-
*/
|
|
27
|
-
function flatten(arr: number[][] | number[][][] | number[][][][], depth: number = 1): number[] {
|
|
28
|
-
return arr.flat(depth) as number[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Convert an array to Vector3Tuple with validation.
|
|
33
|
-
* Throws if the input is not a 3-element array.
|
|
34
|
-
*/
|
|
35
|
-
function toVector3Tuple(arr: number[]): Vector3Tuple {
|
|
36
|
-
if (!Array.isArray(arr) || arr.length !== 3) {
|
|
37
|
-
throw new Error(`Expected array of length 3, got ${Array.isArray(arr) ? arr.length : typeof arr}`);
|
|
38
|
-
}
|
|
39
|
-
return arr as Vector3Tuple;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Convert an array to QuaternionTuple with validation.
|
|
44
|
-
* Throws if the input is not a 4-element array.
|
|
45
|
-
*/
|
|
46
|
-
function toQuaternionTuple(arr: number[]): QuaternionTuple {
|
|
47
|
-
if (!Array.isArray(arr) || arr.length !== 4) {
|
|
48
|
-
throw new Error(`Expected array of length 4, got ${Array.isArray(arr) ? arr.length : typeof arr}`);
|
|
49
|
-
}
|
|
50
|
-
return arr as QuaternionTuple;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function isEqual(obj1: unknown, obj2: unknown, tol: number = 1e-9): boolean {
|
|
54
|
-
if (Array.isArray(obj1) && Array.isArray(obj2)) {
|
|
55
|
-
return (
|
|
56
|
-
obj1.length === obj2.length && obj1.every((v, i) => isEqual(v, obj2[i], tol))
|
|
57
|
-
);
|
|
58
|
-
} else if (obj1 !== null && obj2 !== null && typeof obj1 === "object" && typeof obj2 === "object") {
|
|
59
|
-
const rec1 = obj1 as Record<string, unknown>;
|
|
60
|
-
const rec2 = obj2 as Record<string, unknown>;
|
|
61
|
-
const keys1 = Object.keys(rec1);
|
|
62
|
-
const keys2 = Object.keys(rec2);
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
keys1.length === keys2.length &&
|
|
66
|
-
keys1.every((key) => Object.prototype.hasOwnProperty.call(rec2, key))
|
|
67
|
-
) {
|
|
68
|
-
return keys1.every((key) => isEqual(rec1[key], rec2[key], tol));
|
|
69
|
-
} else {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
} else {
|
|
73
|
-
if (typeof obj1 === "number" && typeof obj2 === "number") {
|
|
74
|
-
return Math.abs(obj1 - obj2) < tol;
|
|
75
|
-
}
|
|
76
|
-
return obj1 === obj2;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function sceneTraverse(obj: THREE.Object3D | null | undefined, fn: (obj: THREE.Object3D) => void): void {
|
|
81
|
-
if (!obj) return;
|
|
82
|
-
|
|
83
|
-
fn(obj);
|
|
84
|
-
|
|
85
|
-
if (obj.children && obj.children.length > 0) {
|
|
86
|
-
obj.children.forEach((o) => {
|
|
87
|
-
sceneTraverse(o, fn);
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
interface GeometryLike {
|
|
93
|
-
dispose: () => void;
|
|
94
|
-
attributes: Record<string, unknown>;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function disposeGeometry(geometry: GeometryLike | null | undefined): void {
|
|
98
|
-
if (geometry) {
|
|
99
|
-
gpuTracker.untrack("geometry", geometry);
|
|
100
|
-
geometry.dispose();
|
|
101
|
-
for (const attr of Object.values(geometry.attributes)) {
|
|
102
|
-
if (attr && typeof attr === "object" && "dispose" in attr && typeof attr.dispose === "function") {
|
|
103
|
-
attr.dispose();
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
interface Disposable {
|
|
110
|
-
dispose: () => void;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
interface MaterialLike {
|
|
114
|
-
dispose: () => void;
|
|
115
|
-
// MeshStandardMaterial texture maps
|
|
116
|
-
map?: Disposable | null;
|
|
117
|
-
normalMap?: Disposable | null;
|
|
118
|
-
roughnessMap?: Disposable | null;
|
|
119
|
-
metalnessMap?: Disposable | null;
|
|
120
|
-
aoMap?: Disposable | null;
|
|
121
|
-
emissiveMap?: Disposable | null;
|
|
122
|
-
alphaMap?: Disposable | null;
|
|
123
|
-
bumpMap?: Disposable | null;
|
|
124
|
-
// MeshPhysicalMaterial additional texture maps
|
|
125
|
-
transmissionMap?: Disposable | null;
|
|
126
|
-
clearcoatMap?: Disposable | null;
|
|
127
|
-
clearcoatRoughnessMap?: Disposable | null;
|
|
128
|
-
clearcoatNormalMap?: Disposable | null;
|
|
129
|
-
thicknessMap?: Disposable | null;
|
|
130
|
-
specularIntensityMap?: Disposable | null;
|
|
131
|
-
specularColorMap?: Disposable | null;
|
|
132
|
-
sheenColorMap?: Disposable | null;
|
|
133
|
-
sheenRoughnessMap?: Disposable | null;
|
|
134
|
-
anisotropyMap?: Disposable | null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** All texture map property names on MaterialLike (for iteration) */
|
|
138
|
-
const MATERIAL_TEXTURE_KEYS: readonly (keyof Omit<MaterialLike, "dispose">)[] = [
|
|
139
|
-
// MeshStandardMaterial
|
|
140
|
-
"map", "normalMap", "roughnessMap", "metalnessMap",
|
|
141
|
-
"aoMap", "emissiveMap", "alphaMap", "bumpMap",
|
|
142
|
-
// MeshPhysicalMaterial
|
|
143
|
-
"transmissionMap", "clearcoatMap", "clearcoatRoughnessMap", "clearcoatNormalMap",
|
|
144
|
-
"thicknessMap", "specularIntensityMap", "specularColorMap",
|
|
145
|
-
"sheenColorMap", "sheenRoughnessMap", "anisotropyMap",
|
|
146
|
-
];
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Dispose a material and detach its texture references.
|
|
150
|
-
*
|
|
151
|
-
* Texture GPU resources are NOT freed here -- TextureCache is the sole owner
|
|
152
|
-
* of loaded textures and is responsible for calling texture.dispose().
|
|
153
|
-
* This function only nulls out the material's texture map references to
|
|
154
|
-
* break the association, then disposes the material itself.
|
|
155
|
-
*/
|
|
156
|
-
function disposeMaterial(material: MaterialLike | null | undefined): void {
|
|
157
|
-
if (!material) return;
|
|
158
|
-
|
|
159
|
-
// Detach all texture references (do NOT dispose -- TextureCache owns them)
|
|
160
|
-
for (const key of MATERIAL_TEXTURE_KEYS) {
|
|
161
|
-
if (material[key]) {
|
|
162
|
-
(material as unknown as Record<string, unknown>)[key] = null;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
gpuTracker.untrack("material", material);
|
|
167
|
-
material.dispose();
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
interface MeshLike {
|
|
171
|
-
geometry?: GeometryLike | null;
|
|
172
|
-
material?: MaterialLike | MaterialLike[] | null;
|
|
173
|
-
isMesh?: boolean;
|
|
174
|
-
isLine?: boolean;
|
|
175
|
-
isPoints?: boolean;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function disposeMesh(mesh: MeshLike): void {
|
|
179
|
-
if (mesh.geometry) {
|
|
180
|
-
disposeGeometry(mesh.geometry);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (mesh.material) {
|
|
184
|
-
if (Array.isArray(mesh.material)) {
|
|
185
|
-
mesh.material.forEach(disposeMaterial);
|
|
186
|
-
} else {
|
|
187
|
-
disposeMaterial(mesh.material);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
interface DisposableTree extends MeshLike {
|
|
193
|
-
children?: DisposableTree[];
|
|
194
|
-
dispose?: () => void;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function deepDispose(tree: DisposableTree | DisposableTree[] | null | undefined): void {
|
|
198
|
-
if (!tree) {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
if (Array.isArray(tree)) {
|
|
202
|
-
tree.forEach(deepDispose);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (Array.isArray(tree.children)) {
|
|
206
|
-
tree.children.forEach(deepDispose);
|
|
207
|
-
}
|
|
208
|
-
if (tree.dispose) {
|
|
209
|
-
tree.dispose();
|
|
210
|
-
} else if (tree.isMesh || tree.isLine || tree.isPoints) {
|
|
211
|
-
disposeMesh(tree);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function format(v: number, b: number = 2, a: number = 2): string {
|
|
216
|
-
const s = Math.abs(v).toFixed(a);
|
|
217
|
-
let padding = "";
|
|
218
|
-
const int = s.split(".")[0];
|
|
219
|
-
for (let i = int.length; i < b; i++) padding += " ";
|
|
220
|
-
padding += v < 0 ? "-" : " ";
|
|
221
|
-
return padding + s;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function prettyPrintVector(v: number[], a: number, b: number): string {
|
|
225
|
-
return `${format(v[0], a, b)}, ${format(v[1], a, b)}, ${format(v[2], a, b)}`;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// KeyEventLike matches MouseEvent, PointerEvent, KeyboardEvent etc.
|
|
229
|
-
type KeyEventLike = {
|
|
230
|
-
ctrlKey: boolean;
|
|
231
|
-
shiftKey: boolean;
|
|
232
|
-
altKey: boolean;
|
|
233
|
-
metaKey: boolean;
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
type KeyEventKey = keyof KeyEventLike;
|
|
237
|
-
type MappedKey = "shift" | "ctrl" | "meta" | "alt";
|
|
238
|
-
|
|
239
|
-
interface KeyMappingConfig {
|
|
240
|
-
shift: KeyEventKey;
|
|
241
|
-
ctrl: KeyEventKey;
|
|
242
|
-
meta: KeyEventKey;
|
|
243
|
-
alt: KeyEventKey;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
class _KeyMapper {
|
|
247
|
-
private keyMapping: Record<MappedKey, KeyEventKey>;
|
|
248
|
-
private actionShortcuts: Record<string, string>;
|
|
249
|
-
private reverseActionShortcuts: Record<string, string>;
|
|
250
|
-
|
|
251
|
-
constructor() {
|
|
252
|
-
this.keyMapping = {
|
|
253
|
-
shift: "ctrlKey",
|
|
254
|
-
ctrl: "shiftKey",
|
|
255
|
-
meta: "altKey",
|
|
256
|
-
alt: "metaKey",
|
|
257
|
-
};
|
|
258
|
-
this.actionShortcuts = {};
|
|
259
|
-
this.reverseActionShortcuts = {};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
getshortcuts = (key: MappedKey): string => {
|
|
263
|
-
return this.keyMapping[key].replace("Key", "");
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
get_config(): Record<MappedKey, KeyEventKey> {
|
|
267
|
-
return Object.assign({}, this.keyMapping);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
get = (event: KeyEventLike, key: MappedKey): boolean => {
|
|
271
|
-
const prop = this.keyMapping[key];
|
|
272
|
-
return event[prop];
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
set = (config: Partial<KeyMappingConfig>): void => {
|
|
276
|
-
for (const key of Object.keys(config) as MappedKey[]) {
|
|
277
|
-
const value = config[key];
|
|
278
|
-
if (value !== undefined) {
|
|
279
|
-
this.keyMapping[key] = value;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
setActionShortcuts = (shortcuts: Record<string, string>): void => {
|
|
285
|
-
this.actionShortcuts = { ...shortcuts };
|
|
286
|
-
this.reverseActionShortcuts = {};
|
|
287
|
-
for (const [action, key] of Object.entries(shortcuts)) {
|
|
288
|
-
this.reverseActionShortcuts[key] = action;
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
getActionForKey = (key: string): string | undefined => {
|
|
293
|
-
return this.reverseActionShortcuts[key];
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
getShortcutForAction = (action: string): string | undefined => {
|
|
297
|
-
return this.actionShortcuts[action];
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
getActionShortcuts = (): Record<string, string> => {
|
|
301
|
-
return { ...this.actionShortcuts };
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// see https://discourse.threejs.org/t/updates-to-lighting-in-three-js-r155/53733
|
|
306
|
-
function scaleLight(intensity: number): number {
|
|
307
|
-
return Math.round(Math.PI * intensity);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// =============================================================================
|
|
311
|
-
// THREE.js Type Guards
|
|
312
|
-
// =============================================================================
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Type guard to check if an Object3D is a Mesh.
|
|
316
|
-
*/
|
|
317
|
-
function isMesh(obj: THREE.Object3D): obj is THREE.Mesh {
|
|
318
|
-
return "isMesh" in obj && obj.isMesh === true;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Type guard to check if an Object3D is a Line.
|
|
323
|
-
*/
|
|
324
|
-
function isLine(obj: THREE.Object3D): obj is THREE.Line {
|
|
325
|
-
return "isLine" in obj && obj.isLine === true;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Type guard to check if an Object3D is a Points.
|
|
330
|
-
*/
|
|
331
|
-
function isPoints(obj: THREE.Object3D): obj is THREE.Points {
|
|
332
|
-
return "isPoints" in obj && obj.isPoints === true;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Type guard to check if an object is an OrthographicCamera.
|
|
337
|
-
* Accepts Object3D to allow use in controls where camera type is broader.
|
|
338
|
-
*/
|
|
339
|
-
function isOrthographicCamera(obj: THREE.Object3D): obj is THREE.OrthographicCamera {
|
|
340
|
-
return "isOrthographicCamera" in obj && (obj as THREE.OrthographicCamera).isOrthographicCamera === true;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Type guard to check if an object is a PerspectiveCamera.
|
|
345
|
-
* Accepts Object3D to allow use in controls where camera type is broader.
|
|
346
|
-
*/
|
|
347
|
-
function isPerspectiveCamera(obj: THREE.Object3D): obj is THREE.PerspectiveCamera {
|
|
348
|
-
return "isPerspectiveCamera" in obj && (obj as THREE.PerspectiveCamera).isPerspectiveCamera === true;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Type guard to check if an Object3D is a LineSegments2 (fat line).
|
|
353
|
-
*/
|
|
354
|
-
function isLineSegments2(obj: THREE.Object3D): obj is LineSegments2 {
|
|
355
|
-
return obj.type === "LineSegments2";
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Type guard to check if a material has a color property.
|
|
360
|
-
*/
|
|
361
|
-
function hasColor(material: THREE.Material): material is THREE.Material & { color: THREE.Color } {
|
|
362
|
-
return "color" in material;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Type guard to check if a material has emissive property.
|
|
367
|
-
*/
|
|
368
|
-
function hasEmissive(material: THREE.Material): material is THREE.Material & { emissive: THREE.Color } {
|
|
369
|
-
return "emissive" in material;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Type guard to check if a material is a MeshStandardMaterial.
|
|
374
|
-
*/
|
|
375
|
-
function isMeshStandardMaterial(material: THREE.Material): material is THREE.MeshStandardMaterial {
|
|
376
|
-
return "isMeshStandardMaterial" in material && material.isMeshStandardMaterial === true;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const KeyMapper = new _KeyMapper();
|
|
380
|
-
|
|
381
|
-
interface EventListenerEntry {
|
|
382
|
-
target: EventTarget;
|
|
383
|
-
event: string;
|
|
384
|
-
handler: EventListenerOrEventListenerObject;
|
|
385
|
-
options: boolean | AddEventListenerOptions;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
class EventListenerManager {
|
|
389
|
-
private listeners: EventListenerEntry[];
|
|
390
|
-
|
|
391
|
-
constructor() {
|
|
392
|
-
this.listeners = [];
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
add = (
|
|
396
|
-
target: EventTarget,
|
|
397
|
-
event: string,
|
|
398
|
-
handler: EventListenerOrEventListenerObject,
|
|
399
|
-
options: boolean | AddEventListenerOptions = false
|
|
400
|
-
): void => {
|
|
401
|
-
target.addEventListener(event, handler, options);
|
|
402
|
-
this.listeners.push({
|
|
403
|
-
target: target,
|
|
404
|
-
event: event,
|
|
405
|
-
handler: handler,
|
|
406
|
-
options: options,
|
|
407
|
-
});
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
dispose(): void {
|
|
411
|
-
this.listeners.forEach(({ target, event, handler, options }) => {
|
|
412
|
-
target.removeEventListener(event, handler, options);
|
|
413
|
-
});
|
|
414
|
-
this.listeners = [];
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
export {
|
|
419
|
-
flatten,
|
|
420
|
-
isEqual,
|
|
421
|
-
sceneTraverse,
|
|
422
|
-
prettyPrintVector,
|
|
423
|
-
KeyMapper,
|
|
424
|
-
scaleLight,
|
|
425
|
-
deepDispose,
|
|
426
|
-
disposeGeometry,
|
|
427
|
-
EventListenerManager,
|
|
428
|
-
// Type guards
|
|
429
|
-
isMesh,
|
|
430
|
-
isLine,
|
|
431
|
-
isPoints,
|
|
432
|
-
isOrthographicCamera,
|
|
433
|
-
isPerspectiveCamera,
|
|
434
|
-
isLineSegments2,
|
|
435
|
-
hasColor,
|
|
436
|
-
hasEmissive,
|
|
437
|
-
isMeshStandardMaterial,
|
|
438
|
-
toVector3Tuple,
|
|
439
|
-
toQuaternionTuple,
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
export type {
|
|
443
|
-
KeyEventKey,
|
|
444
|
-
KeyMappingConfig,
|
|
445
|
-
DisposableTree,
|
|
446
|
-
};
|