@kupcad/ui 0.0.0-stage → 0.1.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/LICENSE +661 -0
- package/README.md +281 -2
- package/package.json +65 -3
- package/src/ConsolePanel.svelte +139 -0
- package/src/ExportMenu.svelte +36 -0
- package/src/ParamField.svelte +390 -0
- package/src/ParamsPanel.svelte +216 -0
- package/src/PreviewPanel.svelte +372 -0
- package/src/ShortcutsDialog.svelte +170 -0
- package/src/Splitter.svelte +148 -0
- package/src/Viewer.svelte +175 -0
- package/src/ViewerPane.svelte +745 -0
- package/src/base64.js +16 -0
- package/src/console.js +78 -0
- package/src/controls.css +183 -0
- package/src/index.js +40 -0
- package/src/measures.js +52 -0
- package/src/params.js +348 -0
- package/src/range.js +19 -0
- package/src/recent.js +44 -0
- package/src/run-status.js +78 -0
- package/src/settings.js +82 -0
- package/src/shortcuts.js +59 -0
- package/src/theme.js +131 -0
- package/src/tokens.css +70 -0
- package/src/values.svelte.js +13 -0
- package/src/view-settings.js +81 -0
- package/src/viewer/appearance.js +90 -0
- package/src/viewer/camera.js +100 -0
- package/src/viewer/dispose.js +30 -0
- package/src/viewer/environment.js +145 -0
- package/src/viewer/faces.js +311 -0
- package/src/viewer/hover-mark.js +125 -0
- package/src/viewer/index.js +7 -0
- package/src/viewer/model.js +293 -0
- package/src/viewer/motion.js +96 -0
- package/src/viewer/section.js +166 -0
- package/src/viewer/testing.js +90 -0
- package/src/viewer/theme.js +32 -0
- package/src/viewer/types.d.ts +136 -0
- package/src/viewer/view-cube.js +314 -0
- package/src/viewer/viewer.js +1027 -0
- package/src/viewer/wheel.js +55 -0
- package/src/viewport-socket.js +64 -0
- package/src/vscode.css +49 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
// A mesh's faces, as its feature edges bound them: the triangles joined across their edges where
|
|
2
|
+
// they meet at less than EDGE_ANGLE_DEG (model.js draws an edge where they meet at more). A face
|
|
3
|
+
// is flat when its triangles all face one way, curved otherwise (a cylinder's side); its area is
|
|
4
|
+
// in the scene's millimetres. A geometry's faces are found once, as the pointer first meets each;
|
|
5
|
+
// a face's triangles are what the viewer marks the face under the pointer with.
|
|
6
|
+
|
|
7
|
+
import { Vector3 } from 'three';
|
|
8
|
+
import { EDGE_ANGLE_DEG } from './model.js';
|
|
9
|
+
|
|
10
|
+
/** @import { BufferGeometry, Matrix4, Mesh } from 'three' */
|
|
11
|
+
/** @import { Face } from './types.js' */
|
|
12
|
+
|
|
13
|
+
/** Triangles that face ways this close (the cosine of their angle) are one flat face's. */
|
|
14
|
+
const FLAT_COS = Math.cos((0.5 * Math.PI) / 180);
|
|
15
|
+
const SMOOTH_COS = Math.cos((EDGE_ANGLE_DEG * Math.PI) / 180);
|
|
16
|
+
/** Two corners nearer than this, of the geometry's size, are one point: its triangles meet there. */
|
|
17
|
+
const SAME_POINT = 1e-6;
|
|
18
|
+
/** Slots in a hash table per key it is given, at least: it stays at most half full. */
|
|
19
|
+
const TABLE_ROOM = 2;
|
|
20
|
+
/** Odd numbers that spread a key's parts over a hash table's slots. */
|
|
21
|
+
const SPREAD = [0x9e3779b1, 0x85ebca77, 0xc2b2ae3d];
|
|
22
|
+
/** A face's way is an axis's when this close to it (the cosine). */
|
|
23
|
+
const AXIS_COS = Math.cos((1 * Math.PI) / 180);
|
|
24
|
+
/** The axes a face may face, and what the views call them. */
|
|
25
|
+
const AXES = /** @type {const} */ ([
|
|
26
|
+
{ axis: [0, 0, 1], name: '+Z (top)' },
|
|
27
|
+
{ axis: [0, 0, -1], name: '-Z (bottom)' },
|
|
28
|
+
{ axis: [0, -1, 0], name: '-Y (front)' },
|
|
29
|
+
{ axis: [0, 1, 0], name: '+Y (back)' },
|
|
30
|
+
{ axis: [1, 0, 0], name: '+X (right)' },
|
|
31
|
+
{ axis: [-1, 0, 0], name: '-X (left)' },
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A geometry's triangles, which meet which across an edge, and the face each was found in (-1
|
|
36
|
+
* for none yet). A triangle's sides are numbered `triangle * 3 + corner`, each from its corner
|
|
37
|
+
* to the next.
|
|
38
|
+
* @typedef {object} Topology
|
|
39
|
+
* @property {Float32Array} normals a unit normal per triangle, 3 numbers each
|
|
40
|
+
* @property {Int32Array} edgeOf the edge each side lies on, by its ends' points
|
|
41
|
+
* @property {Int32Array} start where each edge's sides begin in `sides` (one more, the end)
|
|
42
|
+
* @property {Int32Array} sides the sides on each edge, edge by edge
|
|
43
|
+
* @property {Int32Array} faceOf
|
|
44
|
+
* @property {FaceOf[]} faces
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A face's triangles, and its area in the scene as last found, with the mesh's matrix it was
|
|
49
|
+
* found for: a pointer moving over the face asks again on every frame.
|
|
50
|
+
* @typedef {object} FaceOf
|
|
51
|
+
* @property {number[]} triangles
|
|
52
|
+
* @property {boolean} flat
|
|
53
|
+
* @property {{ matrix: Matrix4, value: number } | null} area
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/** @type {WeakMap<BufferGeometry, Topology>} */
|
|
57
|
+
const topologies = new WeakMap();
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The face of `mesh` its triangle `triangle` is in, in the scene's terms.
|
|
61
|
+
* @param {Mesh} mesh
|
|
62
|
+
* @param {number} triangle the index a raycast's hit gives (`faceIndex`)
|
|
63
|
+
* @returns {Face}
|
|
64
|
+
*/
|
|
65
|
+
export function faceAt(mesh, triangle) {
|
|
66
|
+
const geometry = mesh.geometry;
|
|
67
|
+
const face = faceContaining(geometry, triangle);
|
|
68
|
+
const area = areaOf(face, geometry, mesh.matrixWorld);
|
|
69
|
+
if (!face.flat) return { flat: false, area, normal: null, facing: null };
|
|
70
|
+
const normal = worldNormal(geometry, triangle, mesh.matrixWorld);
|
|
71
|
+
return {
|
|
72
|
+
flat: true,
|
|
73
|
+
area,
|
|
74
|
+
normal: /** @type {[number, number, number]} */ (normal.toArray()),
|
|
75
|
+
facing: AXES.find(({ axis }) => normal.dot(new Vector3(...axis)) >= AXIS_COS)?.name ?? null,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The triangles of the face of `mesh` its triangle `triangle` is in: the same array for every
|
|
81
|
+
* triangle of the face (and every mesh of the geometry).
|
|
82
|
+
* @param {Mesh} mesh
|
|
83
|
+
* @param {number} triangle
|
|
84
|
+
* @returns {readonly number[]}
|
|
85
|
+
*/
|
|
86
|
+
export function faceTriangles(mesh, triangle) {
|
|
87
|
+
return faceContaining(mesh.geometry, triangle).triangles;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The face of `geometry` its triangle `triangle` is in, found the first time it is asked for.
|
|
92
|
+
* @param {BufferGeometry} geometry
|
|
93
|
+
* @param {number} triangle
|
|
94
|
+
*/
|
|
95
|
+
function faceContaining(geometry, triangle) {
|
|
96
|
+
let topology = topologies.get(geometry);
|
|
97
|
+
if (!topology) {
|
|
98
|
+
topology = topologyOf(geometry);
|
|
99
|
+
topologies.set(geometry, topology);
|
|
100
|
+
}
|
|
101
|
+
if (topology.faceOf[triangle] < 0) findFace(topology, triangle);
|
|
102
|
+
return topology.faces[topology.faceOf[triangle]];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The triangles of `geometry`, their normals, and which meet which: across an edge whose two
|
|
107
|
+
* ends each triangle has (by their points, as a mesh's faces each have corners of their own).
|
|
108
|
+
* Typed arrays throughout: a mesh of a million triangles took seconds, a string and an array
|
|
109
|
+
* made for each.
|
|
110
|
+
* @param {BufferGeometry} geometry
|
|
111
|
+
* @returns {Topology}
|
|
112
|
+
*/
|
|
113
|
+
function topologyOf(geometry) {
|
|
114
|
+
const position = geometry.getAttribute('position');
|
|
115
|
+
const index = geometry.getIndex();
|
|
116
|
+
const count = (index ? index.count : position.count) / 3;
|
|
117
|
+
const sideCount = count * 3;
|
|
118
|
+
/** @param {number} side */
|
|
119
|
+
const vertex = (side) => (index ? index.getX(side) : side);
|
|
120
|
+
const pointOf = pointsOf(geometry);
|
|
121
|
+
const normals = new Float32Array(count * 3);
|
|
122
|
+
const a = new Vector3();
|
|
123
|
+
const b = new Vector3();
|
|
124
|
+
const c = new Vector3();
|
|
125
|
+
for (let t = 0; t < count; t++) {
|
|
126
|
+
a.fromBufferAttribute(position, vertex(t * 3));
|
|
127
|
+
b.fromBufferAttribute(position, vertex(t * 3 + 1));
|
|
128
|
+
c.fromBufferAttribute(position, vertex(t * 3 + 2));
|
|
129
|
+
c.sub(b)
|
|
130
|
+
.cross(a.sub(b))
|
|
131
|
+
.normalize()
|
|
132
|
+
.toArray(normals, t * 3);
|
|
133
|
+
}
|
|
134
|
+
// Each side's ends, the lower point first, so a side and its twin are one key
|
|
135
|
+
const low = new Int32Array(sideCount);
|
|
136
|
+
const high = new Int32Array(sideCount);
|
|
137
|
+
for (let side = 0; side < sideCount; side++) {
|
|
138
|
+
const next = side % 3 === 2 ? side - 2 : side + 1;
|
|
139
|
+
const p = pointOf[vertex(side)];
|
|
140
|
+
const q = pointOf[vertex(next)];
|
|
141
|
+
low[side] = Math.min(p, q);
|
|
142
|
+
high[side] = Math.max(p, q);
|
|
143
|
+
}
|
|
144
|
+
const edges = intern(
|
|
145
|
+
sideCount,
|
|
146
|
+
(side) => Math.imul(low[side], SPREAD[0]) ^ Math.imul(high[side], SPREAD[1]),
|
|
147
|
+
(side, other) => low[side] === low[other] && high[side] === high[other],
|
|
148
|
+
);
|
|
149
|
+
const start = new Int32Array(edges.count + 1);
|
|
150
|
+
for (let side = 0; side < sideCount; side++) start[edges.ids[side] + 1]++;
|
|
151
|
+
for (let edge = 0; edge < edges.count; edge++) start[edge + 1] += start[edge];
|
|
152
|
+
const sides = new Int32Array(sideCount);
|
|
153
|
+
const filled = start.slice(0, edges.count);
|
|
154
|
+
for (let side = 0; side < sideCount; side++) sides[filled[edges.ids[side]]++] = side;
|
|
155
|
+
return {
|
|
156
|
+
normals,
|
|
157
|
+
edgeOf: edges.ids,
|
|
158
|
+
start,
|
|
159
|
+
sides,
|
|
160
|
+
faceOf: new Int32Array(count).fill(-1),
|
|
161
|
+
faces: [],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The point each vertex of `geometry` is at: vertices nearer than SAME_POINT of its size are at
|
|
167
|
+
* one.
|
|
168
|
+
* @param {BufferGeometry} geometry
|
|
169
|
+
*/
|
|
170
|
+
function pointsOf(geometry) {
|
|
171
|
+
const position = geometry.getAttribute('position');
|
|
172
|
+
geometry.computeBoundingBox();
|
|
173
|
+
const size = geometry.boundingBox?.getSize(new Vector3()).length() || 1;
|
|
174
|
+
const step = size * SAME_POINT;
|
|
175
|
+
const grid = new Float64Array(position.count * 3);
|
|
176
|
+
for (let v = 0; v < position.count; v++) {
|
|
177
|
+
grid[v * 3] = Math.round(position.getX(v) / step);
|
|
178
|
+
grid[v * 3 + 1] = Math.round(position.getY(v) / step);
|
|
179
|
+
grid[v * 3 + 2] = Math.round(position.getZ(v) / step);
|
|
180
|
+
}
|
|
181
|
+
return intern(
|
|
182
|
+
position.count,
|
|
183
|
+
(v) =>
|
|
184
|
+
Math.imul(grid[v * 3], SPREAD[0]) ^
|
|
185
|
+
Math.imul(grid[v * 3 + 1], SPREAD[1]) ^
|
|
186
|
+
Math.imul(grid[v * 3 + 2], SPREAD[2]),
|
|
187
|
+
(v, w) =>
|
|
188
|
+
grid[v * 3] === grid[w * 3] &&
|
|
189
|
+
grid[v * 3 + 1] === grid[w * 3 + 1] &&
|
|
190
|
+
grid[v * 3 + 2] === grid[w * 3 + 2],
|
|
191
|
+
).ids;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Ids for `count` keys, the same for equal keys and counted from 0 in the order they first
|
|
196
|
+
* come: by an open-addressing table of the first key of each id.
|
|
197
|
+
* @param {number} count
|
|
198
|
+
* @param {(key: number) => number} hash
|
|
199
|
+
* @param {(key: number, other: number) => boolean} same
|
|
200
|
+
* @returns {{ ids: Int32Array, count: number }}
|
|
201
|
+
*/
|
|
202
|
+
function intern(count, hash, same) {
|
|
203
|
+
let size = 1;
|
|
204
|
+
while (size < count * TABLE_ROOM) size *= 2;
|
|
205
|
+
const mask = size - 1;
|
|
206
|
+
const firsts = new Int32Array(size).fill(-1);
|
|
207
|
+
const ids = new Int32Array(count);
|
|
208
|
+
let distinct = 0;
|
|
209
|
+
for (let key = 0; key < count; key++) {
|
|
210
|
+
for (let slot = hash(key) & mask; ; slot = (slot + 1) & mask) {
|
|
211
|
+
const first = firsts[slot];
|
|
212
|
+
if (first < 0) {
|
|
213
|
+
firsts[slot] = key;
|
|
214
|
+
ids[key] = distinct++;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
if (same(key, first)) {
|
|
218
|
+
ids[key] = ids[first];
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { ids, count: distinct };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Finds the face `seed` is in: the triangles reached across edges that turn less than the
|
|
228
|
+
* feature edges' angle; flat when they all face the seed's way.
|
|
229
|
+
* @param {Topology} topology
|
|
230
|
+
* @param {number} seed
|
|
231
|
+
*/
|
|
232
|
+
function findFace(topology, seed) {
|
|
233
|
+
const { normals, edgeOf, start, sides, faceOf, faces } = topology;
|
|
234
|
+
const id = faces.length;
|
|
235
|
+
/** @param {number} t @param {number} u */
|
|
236
|
+
const cos = (t, u) =>
|
|
237
|
+
normals[t * 3] * normals[u * 3] +
|
|
238
|
+
normals[t * 3 + 1] * normals[u * 3 + 1] +
|
|
239
|
+
normals[t * 3 + 2] * normals[u * 3 + 2];
|
|
240
|
+
const triangles = [seed];
|
|
241
|
+
faceOf[seed] = id;
|
|
242
|
+
let flat = true;
|
|
243
|
+
for (let at = 0; at < triangles.length; at++) {
|
|
244
|
+
const t = triangles[at];
|
|
245
|
+
for (let side = t * 3; side < t * 3 + 3; side++) {
|
|
246
|
+
const edge = edgeOf[side];
|
|
247
|
+
for (let on = start[edge]; on < start[edge + 1]; on++) {
|
|
248
|
+
const u = Math.floor(sides[on] / 3);
|
|
249
|
+
if (faceOf[u] >= 0 || cos(t, u) < SMOOTH_COS) continue;
|
|
250
|
+
faceOf[u] = id;
|
|
251
|
+
triangles.push(u);
|
|
252
|
+
if (cos(seed, u) < FLAT_COS) flat = false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
faces.push({ triangles, flat, area: null });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* A triangle's corners, in the scene.
|
|
261
|
+
* @param {BufferGeometry} geometry
|
|
262
|
+
* @param {number} triangle
|
|
263
|
+
* @param {Matrix4} matrix the mesh's to the scene
|
|
264
|
+
* @returns {[Vector3, Vector3, Vector3]}
|
|
265
|
+
*/
|
|
266
|
+
function corners(geometry, triangle, matrix) {
|
|
267
|
+
const position = geometry.getAttribute('position');
|
|
268
|
+
const index = geometry.getIndex();
|
|
269
|
+
return /** @type {[Vector3, Vector3, Vector3]} */ (
|
|
270
|
+
[0, 1, 2].map((corner) => {
|
|
271
|
+
const i = index ? index.getX(triangle * 3 + corner) : triangle * 3 + corner;
|
|
272
|
+
return new Vector3().fromBufferAttribute(position, i).applyMatrix4(matrix);
|
|
273
|
+
})
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The area of `face` of `geometry` in the scene, by the mesh's `matrix` to it.
|
|
279
|
+
* @param {FaceOf} face
|
|
280
|
+
* @param {BufferGeometry} geometry
|
|
281
|
+
* @param {Matrix4} matrix
|
|
282
|
+
*/
|
|
283
|
+
function areaOf(face, geometry, matrix) {
|
|
284
|
+
if (face.area?.matrix.equals(matrix)) return face.area.value;
|
|
285
|
+
const position = geometry.getAttribute('position');
|
|
286
|
+
const index = geometry.getIndex();
|
|
287
|
+
/** @param {number} side */
|
|
288
|
+
const vertex = (side) => (index ? index.getX(side) : side);
|
|
289
|
+
const a = new Vector3();
|
|
290
|
+
const b = new Vector3();
|
|
291
|
+
const c = new Vector3();
|
|
292
|
+
let value = 0;
|
|
293
|
+
for (const t of face.triangles) {
|
|
294
|
+
a.fromBufferAttribute(position, vertex(t * 3)).applyMatrix4(matrix);
|
|
295
|
+
b.fromBufferAttribute(position, vertex(t * 3 + 1)).applyMatrix4(matrix);
|
|
296
|
+
c.fromBufferAttribute(position, vertex(t * 3 + 2)).applyMatrix4(matrix);
|
|
297
|
+
value += c.sub(b).cross(a.sub(b)).length() / 2;
|
|
298
|
+
}
|
|
299
|
+
face.area = { matrix: matrix.clone(), value };
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* @param {BufferGeometry} geometry
|
|
305
|
+
* @param {number} triangle
|
|
306
|
+
* @param {Matrix4} matrix
|
|
307
|
+
*/
|
|
308
|
+
function worldNormal(geometry, triangle, matrix) {
|
|
309
|
+
const [a, b, c] = corners(geometry, triangle, matrix);
|
|
310
|
+
return c.sub(b).cross(a.sub(b)).normalize();
|
|
311
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// What the pointer is over, marked: the copy under it glows a little in the selection's colour,
|
|
2
|
+
// and on a solid the face under it (its triangles, faces.js) more. Each is a mesh under the
|
|
3
|
+
// copy's, adding light to what is drawn there, so the mark shows on a part of any colour (a tint
|
|
4
|
+
// toward the selection's lost itself in a part of that colour), and a little in front of the
|
|
5
|
+
// faces, which are drawn behind their own depth (model.js). The viewer moves it as the pointer
|
|
6
|
+
// goes.
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
AdditiveBlending,
|
|
10
|
+
BufferGeometry,
|
|
11
|
+
Float32BufferAttribute,
|
|
12
|
+
Mesh,
|
|
13
|
+
MeshBasicMaterial,
|
|
14
|
+
} from 'three';
|
|
15
|
+
import { faceTriangles } from './faces.js';
|
|
16
|
+
|
|
17
|
+
/** @import { Material } from 'three' */
|
|
18
|
+
|
|
19
|
+
/** How much of the selection's colour the copy under the pointer takes on, and its face. */
|
|
20
|
+
export const COPY_GLOW = 0.2;
|
|
21
|
+
export const FACE_GLOW = 0.4;
|
|
22
|
+
/** What a mark draws while it marks nothing. */
|
|
23
|
+
const NOTHING = new BufferGeometry();
|
|
24
|
+
|
|
25
|
+
export class HoverMark {
|
|
26
|
+
#copy = new Mesh(NOTHING, glowMaterial(COPY_GLOW));
|
|
27
|
+
#face = new Mesh(NOTHING, glowMaterial(FACE_GLOW));
|
|
28
|
+
/** The copy marked, or null. @type {Mesh | null} */
|
|
29
|
+
#mesh = null;
|
|
30
|
+
/** Its face marked: faces.js's array of the face's triangles, or null. @type {readonly number[] | null} */
|
|
31
|
+
#triangles = null;
|
|
32
|
+
|
|
33
|
+
/** @param {string} color a CSS colour */
|
|
34
|
+
constructor(color) {
|
|
35
|
+
this.setColor(color);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Its materials: the viewer clips them at a section, as the model's. @returns {Material[]} */
|
|
39
|
+
get materials() {
|
|
40
|
+
return [
|
|
41
|
+
/** @type {Material} */ (this.#copy.material),
|
|
42
|
+
/** @type {Material} */ (this.#face.material),
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @param {string} color a CSS colour */
|
|
47
|
+
setColor(color) {
|
|
48
|
+
for (const material of this.materials)
|
|
49
|
+
/** @type {MeshBasicMaterial} */ (material).color.set(color);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Marks `mesh`, a copy, and the face its triangle `triangle` is in (none: null); nothing for
|
|
54
|
+
* a null mesh.
|
|
55
|
+
* @param {Mesh | null} mesh
|
|
56
|
+
* @param {number | null} triangle
|
|
57
|
+
* @returns {boolean} whether the mark moved
|
|
58
|
+
*/
|
|
59
|
+
mark(mesh, triangle) {
|
|
60
|
+
const triangles = mesh && triangle !== null ? faceTriangles(mesh, triangle) : null;
|
|
61
|
+
if (mesh === this.#mesh && triangles === this.#triangles) return false;
|
|
62
|
+
if (mesh !== this.#mesh) {
|
|
63
|
+
this.#copy.removeFromParent();
|
|
64
|
+
this.#mesh = mesh;
|
|
65
|
+
// The copy's own geometry, drawn on the side its faces are (a drawing's, both)
|
|
66
|
+
this.#copy.geometry = mesh ? mesh.geometry : NOTHING;
|
|
67
|
+
if (mesh) {
|
|
68
|
+
const material = /** @type {Material} */ (this.#copy.material);
|
|
69
|
+
const { side } = /** @type {Material} */ (mesh.material);
|
|
70
|
+
if (material.side !== side) {
|
|
71
|
+
material.side = side;
|
|
72
|
+
material.needsUpdate = true;
|
|
73
|
+
}
|
|
74
|
+
mesh.add(this.#copy);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
this.#face.removeFromParent();
|
|
78
|
+
if (this.#face.geometry !== NOTHING) this.#face.geometry.dispose();
|
|
79
|
+
this.#triangles = triangles;
|
|
80
|
+
this.#face.geometry = mesh && triangles ? trianglesOf(mesh, triangles) : NOTHING;
|
|
81
|
+
if (mesh && triangles) mesh.add(this.#face);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
dispose() {
|
|
86
|
+
this.mark(null, null);
|
|
87
|
+
for (const material of this.materials) material.dispose();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* What adds `glow` of its colour to what is drawn under it, a little in front of the faces.
|
|
93
|
+
* @param {number} glow
|
|
94
|
+
* @returns {MeshBasicMaterial}
|
|
95
|
+
*/
|
|
96
|
+
function glowMaterial(glow) {
|
|
97
|
+
return new MeshBasicMaterial({
|
|
98
|
+
transparent: true,
|
|
99
|
+
opacity: glow,
|
|
100
|
+
blending: AdditiveBlending,
|
|
101
|
+
depthWrite: false,
|
|
102
|
+
polygonOffset: true,
|
|
103
|
+
polygonOffsetFactor: -1,
|
|
104
|
+
polygonOffsetUnits: -1,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The triangles of `mesh` listed, as a geometry of their own in the mesh's space.
|
|
110
|
+
* @param {Mesh} mesh
|
|
111
|
+
* @param {readonly number[]} triangles
|
|
112
|
+
* @returns {BufferGeometry}
|
|
113
|
+
*/
|
|
114
|
+
function trianglesOf(mesh, triangles) {
|
|
115
|
+
const position = mesh.geometry.getAttribute('position');
|
|
116
|
+
const index = mesh.geometry.getIndex();
|
|
117
|
+
const positions = new Float32Array(triangles.length * 9);
|
|
118
|
+
triangles.forEach((triangle, at) => {
|
|
119
|
+
for (let corner = 0; corner < 3; corner++) {
|
|
120
|
+
const i = index ? index.getX(triangle * 3 + corner) : triangle * 3 + corner;
|
|
121
|
+
positions.set([position.getX(i), position.getY(i), position.getZ(i)], at * 9 + corner * 3);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
return new BufferGeometry().setAttribute('position', new Float32BufferAttribute(positions, 3));
|
|
125
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// @kupcad/ui/viewer: the scene of a model the core made, for any host, Svelte or not. It imports
|
|
2
|
+
// nothing of the rest of the package, nor Svelte (.oxlintrc.json holds it to that);
|
|
3
|
+
// the Svelte component around it is @kupcad/ui's Viewer.
|
|
4
|
+
|
|
5
|
+
export { ViewCube } from './view-cube.js';
|
|
6
|
+
export { RENDER_MODES, Viewer, ViewerError } from './viewer.js';
|
|
7
|
+
export { DARK, LIGHT } from './theme.js';
|