@ifc-lite/renderer 1.47.0 → 1.48.1
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/clash-solid-pipeline.d.ts +34 -0
- package/dist/clash-solid-pipeline.d.ts.map +1 -0
- package/dist/clash-solid-pipeline.js +193 -0
- package/dist/clash-solid-pipeline.js.map +1 -0
- package/dist/deviation/deviation-computer.d.ts +87 -0
- package/dist/deviation/deviation-computer.d.ts.map +1 -0
- package/dist/deviation/deviation-computer.js +201 -0
- package/dist/deviation/deviation-computer.js.map +1 -0
- package/dist/index.d.ts +22 -46
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -141
- package/dist/index.js.map +1 -1
- package/dist/renderer-overlays.d.ts +4 -0
- package/dist/renderer-overlays.d.ts.map +1 -1
- package/dist/renderer-overlays.js +19 -0
- package/dist/renderer-overlays.js.map +1 -1
- package/dist/shaders/sky.wgsl.d.ts +1 -1
- package/dist/shaders/sky.wgsl.d.ts.map +1 -1
- package/dist/shaders/sky.wgsl.js +7 -1
- package/dist/shaders/sky.wgsl.js.map +1 -1
- package/dist/snap-corner.d.ts +44 -0
- package/dist/snap-corner.d.ts.map +1 -0
- package/dist/snap-corner.js +111 -0
- package/dist/snap-corner.js.map +1 -0
- package/dist/snap-detector.d.ts +8 -4
- package/dist/snap-detector.d.ts.map +1 -1
- package/dist/snap-detector.js +60 -58
- package/dist/snap-detector.js.map +1 -1
- package/dist/snap-edge-runs.d.ts +75 -0
- package/dist/snap-edge-runs.d.ts.map +1 -0
- package/dist/snap-edge-runs.js +233 -0
- package/dist/snap-edge-runs.js.map +1 -0
- package/dist/snap-geometry-cache.d.ts +17 -11
- package/dist/snap-geometry-cache.d.ts.map +1 -1
- package/dist/snap-geometry-cache.js +115 -141
- package/dist/snap-geometry-cache.js.map +1 -1
- package/dist/snap-weld.d.ts +66 -0
- package/dist/snap-weld.d.ts.map +1 -0
- package/dist/snap-weld.js +150 -0
- package/dist/snap-weld.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* Minimum number of distinct edge lines meeting at a vertex for it to count as
|
|
6
|
+
* a corner. Two lines meeting is the weakest thing that still looks like a
|
|
7
|
+
* corner; one line means the vertex is an interior split of a straight run.
|
|
8
|
+
*/
|
|
9
|
+
export const MIN_CORNER_VALENCE = 2;
|
|
10
|
+
/** Lexicographic order on a point — the geometric tiebreak used for determinism. */
|
|
11
|
+
export function compareVec3(a, b) {
|
|
12
|
+
if (a.x !== b.x)
|
|
13
|
+
return a.x < b.x ? -1 : 1;
|
|
14
|
+
if (a.y !== b.y)
|
|
15
|
+
return a.y < b.y ? -1 : 1;
|
|
16
|
+
if (a.z !== b.z)
|
|
17
|
+
return a.z < b.z ? -1 : 1;
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
/** Perpendicular distance from `p` to the line through `origin` with unit direction `dir`. */
|
|
21
|
+
function perpDistance(p, origin, dir) {
|
|
22
|
+
const rx = p.x - origin.x, ry = p.y - origin.y, rz = p.z - origin.z;
|
|
23
|
+
const along = rx * dir.x + ry * dir.y + rz * dir.z;
|
|
24
|
+
const px = rx - along * dir.x, py = ry - along * dir.y, pz = rz - along * dir.z;
|
|
25
|
+
return Math.sqrt(px * px + py * py + pz * pz);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Distinct edge lines meeting at each vertex.
|
|
29
|
+
*
|
|
30
|
+
* Counting incident SEGMENTS (what the cache used to do) scores an interior
|
|
31
|
+
* split of a straight run as valence 2 and declares it a corner, which froze the
|
|
32
|
+
* snap point there for centimetres of cursor travel and painted corner rings on
|
|
33
|
+
* nothing. Counting distinct LINES scores that same vertex 1.
|
|
34
|
+
*
|
|
35
|
+
* MUST be fed the canonically ordered segment list (see `mergeCollinearRuns`),
|
|
36
|
+
* never the raw input: the grouping below is greedy, so the representative set
|
|
37
|
+
* - and with it the valence, the `isCorner` gate and the corner confidence - is
|
|
38
|
+
* a function of segment order. Fed the raw array, that order is wasm triangle
|
|
39
|
+
* emission order, and the valence at a junction moved when the mesher's output
|
|
40
|
+
* order did (the #2388 failure class, measured as valence 3 vs 4 at one vertex
|
|
41
|
+
* for the same geometry).
|
|
42
|
+
*
|
|
43
|
+
* The same-line test is symmetric in the pair. Comparing one segment's far
|
|
44
|
+
* POINT against the other's line (the previous test) scales with whichever
|
|
45
|
+
* segment happens to be the candidate, so which of two near-parallel segments
|
|
46
|
+
* was seen first decided whether they merged. Instead the angle between the
|
|
47
|
+
* two lines is scaled by the SHORTER length: a segment of length L built from
|
|
48
|
+
* welded points only pins its direction to within ~tol/L, so two directions
|
|
49
|
+
* are indistinguishable when their angle is inside the shorter segment's own
|
|
50
|
+
* uncertainty - an order-free statement about the pair. (Scaling by the longer
|
|
51
|
+
* length would be symmetric too, but it splits a genuinely straight run at its
|
|
52
|
+
* interior weld vertex whenever the two half-segments are lopsided, because
|
|
53
|
+
* the vertex's f32 noise is amplified by the length ratio.)
|
|
54
|
+
*/
|
|
55
|
+
function computeValences(segments, points, tol) {
|
|
56
|
+
const incident = new Map();
|
|
57
|
+
const add = (v, other) => {
|
|
58
|
+
const list = incident.get(v);
|
|
59
|
+
if (list)
|
|
60
|
+
list.push(other);
|
|
61
|
+
else
|
|
62
|
+
incident.set(v, [other]);
|
|
63
|
+
};
|
|
64
|
+
for (const s of segments) {
|
|
65
|
+
add(s.a, s.b);
|
|
66
|
+
add(s.b, s.a);
|
|
67
|
+
}
|
|
68
|
+
const valence = new Map();
|
|
69
|
+
for (const [v, others] of incident) {
|
|
70
|
+
const origin = points[v];
|
|
71
|
+
const reps = [];
|
|
72
|
+
for (const o of others) {
|
|
73
|
+
const p = points[o];
|
|
74
|
+
const dx = p.x - origin.x, dy = p.y - origin.y, dz = p.z - origin.z;
|
|
75
|
+
const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
76
|
+
if (len <= tol)
|
|
77
|
+
continue;
|
|
78
|
+
const dir = { x: dx / len, y: dy / len, z: dz / len };
|
|
79
|
+
let matched = false;
|
|
80
|
+
for (const rep of reps) {
|
|
81
|
+
// Sine of the angle between the two LINES: the cross product magnitude
|
|
82
|
+
// of the unit directions, which is identical for opposite orientations,
|
|
83
|
+
// so a straight run passing through the vertex still counts as one line.
|
|
84
|
+
const cx = dir.y * rep.dir.z - dir.z * rep.dir.y;
|
|
85
|
+
const cy = dir.z * rep.dir.x - dir.x * rep.dir.z;
|
|
86
|
+
const cz = dir.x * rep.dir.y - dir.y * rep.dir.x;
|
|
87
|
+
const sin = Math.sqrt(cx * cx + cy * cy + cz * cz);
|
|
88
|
+
if (sin * Math.min(len, rep.len) <= tol) {
|
|
89
|
+
matched = true;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!matched)
|
|
94
|
+
reps.push({ dir, len });
|
|
95
|
+
}
|
|
96
|
+
valence.set(v, reps.length);
|
|
97
|
+
}
|
|
98
|
+
return valence;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Merge the mesh's edge segments into model-edge runs.
|
|
102
|
+
*
|
|
103
|
+
* Deterministic by construction: segments are processed in a canonical
|
|
104
|
+
* GEOMETRIC order (lexicographic on their endpoints), never in array order, so
|
|
105
|
+
* the output is identical under any permutation of the input.
|
|
106
|
+
*/
|
|
107
|
+
export function mergeCollinearRuns(segments, points, tol) {
|
|
108
|
+
// Orient each segment lexicographically, then sort the whole set the same way.
|
|
109
|
+
const oriented = segments
|
|
110
|
+
.filter((s) => s.a !== s.b && points[s.a] !== undefined && points[s.b] !== undefined)
|
|
111
|
+
.map((s) => (compareVec3(points[s.a], points[s.b]) <= 0 ? { a: s.a, b: s.b } : { a: s.b, b: s.a }));
|
|
112
|
+
oriented.sort((s1, s2) => {
|
|
113
|
+
const byA = compareVec3(points[s1.a], points[s2.a]);
|
|
114
|
+
return byA !== 0 ? byA : compareVec3(points[s1.b], points[s2.b]);
|
|
115
|
+
});
|
|
116
|
+
// Valences are computed on the CANONICAL order, after the sort: they are the
|
|
117
|
+
// one channel that survives into user-visible behaviour (corner detection and
|
|
118
|
+
// confidence), so they must be as order-free as the runs themselves.
|
|
119
|
+
const valence = computeValences(oriented, points, tol);
|
|
120
|
+
const adjacency = new Map();
|
|
121
|
+
oriented.forEach((s, i) => {
|
|
122
|
+
for (const v of [s.a, s.b]) {
|
|
123
|
+
const list = adjacency.get(v);
|
|
124
|
+
if (list)
|
|
125
|
+
list.push(i);
|
|
126
|
+
else
|
|
127
|
+
adjacency.set(v, [i]);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
const visited = new Uint8Array(oriented.length);
|
|
131
|
+
const edges = [];
|
|
132
|
+
for (let seed = 0; seed < oriented.length; seed++) {
|
|
133
|
+
if (visited[seed])
|
|
134
|
+
continue;
|
|
135
|
+
const origin = points[oriented[seed].a];
|
|
136
|
+
const far = points[oriented[seed].b];
|
|
137
|
+
const dx = far.x - origin.x, dy = far.y - origin.y, dz = far.z - origin.z;
|
|
138
|
+
const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
139
|
+
if (len <= tol) {
|
|
140
|
+
visited[seed] = 1;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const dir = { x: dx / len, y: dy / len, z: dz / len };
|
|
144
|
+
// Grow the run over segments that share a vertex with it AND whose BOTH
|
|
145
|
+
// endpoints lie on the SEED's line. Testing against the seed line rather
|
|
146
|
+
// than the previous member is what keeps a tessellated arc unfused: the
|
|
147
|
+
// error is measured globally, so it cannot accumulate chord by chord.
|
|
148
|
+
visited[seed] = 1;
|
|
149
|
+
const members = [seed];
|
|
150
|
+
const queue = [seed];
|
|
151
|
+
while (queue.length > 0) {
|
|
152
|
+
const cur = queue.pop();
|
|
153
|
+
for (const v of [oriented[cur].a, oriented[cur].b]) {
|
|
154
|
+
for (const nb of adjacency.get(v) ?? []) {
|
|
155
|
+
if (visited[nb])
|
|
156
|
+
continue;
|
|
157
|
+
if (perpDistance(points[oriented[nb].a], origin, dir) > tol)
|
|
158
|
+
continue;
|
|
159
|
+
if (perpDistance(points[oriented[nb].b], origin, dir) > tol)
|
|
160
|
+
continue;
|
|
161
|
+
visited[nb] = 1;
|
|
162
|
+
members.push(nb);
|
|
163
|
+
queue.push(nb);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const project = (v) => {
|
|
168
|
+
const p = points[v];
|
|
169
|
+
return (p.x - origin.x) * dir.x + (p.y - origin.y) * dir.y + (p.z - origin.z) * dir.z;
|
|
170
|
+
};
|
|
171
|
+
const intervals = [];
|
|
172
|
+
const seen = new Map();
|
|
173
|
+
for (const m of members) {
|
|
174
|
+
const { a, b } = oriented[m];
|
|
175
|
+
const ta = project(a), tb = project(b);
|
|
176
|
+
seen.set(a, ta);
|
|
177
|
+
seen.set(b, tb);
|
|
178
|
+
intervals.push(ta <= tb ? { t0: ta, t1: tb, v0: a, v1: b } : { t0: tb, t1: ta, v0: b, v1: a });
|
|
179
|
+
}
|
|
180
|
+
intervals.sort((i1, i2) => i1.t0 - i2.t0 || i1.t1 - i2.t1);
|
|
181
|
+
// 1D union. The gap tolerance is the SAME ~15 um as collinearity, never a
|
|
182
|
+
// round number: two openings aligned on one line in a slab are distinct
|
|
183
|
+
// model edges separated by a real gap, and a centimetre-scale gap tolerance
|
|
184
|
+
// would bridge them and report a length no edge has.
|
|
185
|
+
const runs = [];
|
|
186
|
+
let cur = intervals[0];
|
|
187
|
+
for (let i = 1; i < intervals.length; i++) {
|
|
188
|
+
const next = intervals[i];
|
|
189
|
+
if (next.t0 <= cur.t1 + tol) {
|
|
190
|
+
if (next.t1 > cur.t1)
|
|
191
|
+
cur = { t0: cur.t0, t1: next.t1, v0: cur.v0, v1: next.v1 };
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
runs.push(cur);
|
|
195
|
+
cur = next;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
runs.push(cur);
|
|
199
|
+
for (const run of runs) {
|
|
200
|
+
let p0 = points[run.v0], p1 = points[run.v1];
|
|
201
|
+
let val0 = valence.get(run.v0) ?? 0, val1 = valence.get(run.v1) ?? 0;
|
|
202
|
+
const reversed = compareVec3(p0, p1) > 0;
|
|
203
|
+
if (reversed) {
|
|
204
|
+
[p0, p1] = [p1, p0];
|
|
205
|
+
[val0, val1] = [val1, val0];
|
|
206
|
+
}
|
|
207
|
+
const span = run.t1 - run.t0;
|
|
208
|
+
const junctions = [];
|
|
209
|
+
for (const [v, t] of seen) {
|
|
210
|
+
if (t <= run.t0 + tol || t >= run.t1 - tol)
|
|
211
|
+
continue;
|
|
212
|
+
const val = valence.get(v) ?? 0;
|
|
213
|
+
if (val < MIN_CORNER_VALENCE)
|
|
214
|
+
continue;
|
|
215
|
+
const along = span > 0 ? (t - run.t0) / span : 0;
|
|
216
|
+
junctions.push({ point: points[v], valence: val, t: reversed ? 1 - along : along });
|
|
217
|
+
}
|
|
218
|
+
junctions.sort((j1, j2) => j1.t - j2.t);
|
|
219
|
+
const ex = p1.x - p0.x, ey = p1.y - p0.y, ez = p1.z - p0.z;
|
|
220
|
+
edges.push({
|
|
221
|
+
v0: p0,
|
|
222
|
+
v1: p1,
|
|
223
|
+
index: edges.length,
|
|
224
|
+
length: Math.sqrt(ex * ex + ey * ey + ez * ez),
|
|
225
|
+
v0Valence: val0,
|
|
226
|
+
v1Valence: val1,
|
|
227
|
+
junctions,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return edges;
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=snap-edge-runs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snap-edge-runs.js","sourceRoot":"","sources":["../src/snap-edge-runs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Geometry cache building for snap detection.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Three stages, in order, because each one is what makes the next answerable:
|
|
5
|
+
*
|
|
6
|
+
* 1. WELD by position (`snap-weld.ts`). The wasm mesh is unwelded across
|
|
7
|
+
* creases, so classifying edges by vertex INDEX made every physical edge
|
|
8
|
+
* arrive twice with one adjacent triangle each — every edge looked like a
|
|
9
|
+
* boundary edge, the coplanarity test below could never fire, and the cache
|
|
10
|
+
* filled with triangulation diagonals (911 of 1755 distinct edges on
|
|
11
|
+
* `building-architecture.ifc`) that a user can snap to and measure.
|
|
12
|
+
* 2. CLASSIFY: keep boundary and crease edges, drop the diagonals shared by two
|
|
13
|
+
* coplanar triangles.
|
|
14
|
+
* 3. MERGE collinear runs (`snap-edge-runs.ts`), so a straight model edge that
|
|
15
|
+
* several triangles cross becomes ONE edge of the length the user sees.
|
|
6
16
|
*/
|
|
7
17
|
import type { MeshData } from '@ifc-lite/geometry';
|
|
8
18
|
import type { Vec3 } from './raycaster.js';
|
|
19
|
+
import { type SnapEdge } from './snap-edge-runs.js';
|
|
20
|
+
export type { SnapEdge } from './snap-edge-runs.js';
|
|
9
21
|
export interface MeshGeometryCache {
|
|
10
22
|
vertices: Vec3[];
|
|
11
|
-
edges:
|
|
12
|
-
v0: Vec3;
|
|
13
|
-
v1: Vec3;
|
|
14
|
-
index: number;
|
|
15
|
-
}>;
|
|
16
|
-
vertexValence: Map<string, number>;
|
|
17
|
-
vertexEdges: Map<string, number[]>;
|
|
23
|
+
edges: SnapEdge[];
|
|
18
24
|
}
|
|
19
25
|
/**
|
|
20
|
-
* Build a geometry cache for a mesh:
|
|
21
|
-
*
|
|
26
|
+
* Build a geometry cache for a mesh: welded vertices plus reconstructed model
|
|
27
|
+
* edges with their corner valences.
|
|
22
28
|
*/
|
|
23
29
|
export declare function buildGeometryCache(mesh: MeshData): MeshGeometryCache;
|
|
24
30
|
//# sourceMappingURL=snap-geometry-cache.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snap-geometry-cache.d.ts","sourceRoot":"","sources":["../src/snap-geometry-cache.ts"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"snap-geometry-cache.d.ts","sourceRoot":"","sources":["../src/snap-geometry-cache.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAsB,KAAK,QAAQ,EAAsB,MAAM,qBAAqB,CAAC;AAE5F,YAAY,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,IAAI,EAAE,CAAC;IACjB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAuCD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,CA2DpE"}
|
|
@@ -1,163 +1,137 @@
|
|
|
1
1
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { snapToleranceFor, weldVertices } from './snap-weld.js';
|
|
5
|
+
import { mergeCollinearRuns } from './snap-edge-runs.js';
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
+
* Dot-product threshold above which two triangles sharing an edge count as
|
|
8
|
+
* coplanar, so their shared edge is an internal triangulation diagonal rather
|
|
9
|
+
* than a model edge.
|
|
10
|
+
*
|
|
11
|
+
* NEAR-EXACT on purpose. Measured over every two-manifold welded edge of the
|
|
12
|
+
* three committed samples (the `pnpm test:snap-edges` pipeline), the two
|
|
13
|
+
* populations this constant separates do not overlap for five decades:
|
|
14
|
+
*
|
|
15
|
+
* - triangulation diagonals of flat faces sit at dot >= 1 - 8.1e-8, pure f32
|
|
16
|
+
* position noise (10k+ edges on building-architecture.ifc and
|
|
17
|
+
* infra-bridge.ifc);
|
|
18
|
+
* - real creases sit at dot <= 1 - 3.66e-5 (0.49 degrees and steeper).
|
|
19
|
+
*
|
|
20
|
+
* 1 - 1e-6 sits between them: 12x above the diagonal noise floor, 36x below
|
|
21
|
+
* the flattest measured crease (both in 1-dot terms). As an angle it is a
|
|
22
|
+
* 0.081 degree cutoff, flatter than any slope BIM geometry encodes on purpose
|
|
23
|
+
* (a 2% drainage fall is 1.15 degrees, a 0.5% minimum interior fall 0.29
|
|
24
|
+
* degrees), so shallow real creases stay snappable. An earlier 0.98 cutoff
|
|
25
|
+
* (~11.5 degrees) deleted them wholesale: 1093 real creases on
|
|
26
|
+
* infra-bridge.ifc alone, among them a 3.500 m bridge-deck edge whose faces
|
|
27
|
+
* meet at 3.617 degrees (IfcBuildingElementProxy #723) - and it cleared the
|
|
28
|
+
* coarsest shipped circle facet (32 segments = 11.25 degrees, dot 0.98079) by
|
|
29
|
+
* only 0.0008, so tessellated arcs kept their edges by luck.
|
|
30
|
+
*
|
|
31
|
+
* The trade is asymmetric and this errs on the KEEP side deliberately: a
|
|
32
|
+
* sliver triangle noisier than the measured floor slips its diagonal under
|
|
33
|
+
* the cutoff and adds a benign extra snap edge on a flat face, while a cutoff
|
|
34
|
+
* low enough to chase such noise deletes real edges a user has to be able to
|
|
35
|
+
* measure. Keeping more edges on curved geometry is the conservative
|
|
36
|
+
* direction for snapping. Pinned from both sides by
|
|
37
|
+
* `snap-geometry-cache.test.ts`.
|
|
38
|
+
*/
|
|
39
|
+
const COPLANAR_THRESHOLD = 1 - 1e-6;
|
|
40
|
+
const EMPTY_CACHE = { vertices: [], edges: [] };
|
|
41
|
+
/**
|
|
42
|
+
* Build a geometry cache for a mesh: welded vertices plus reconstructed model
|
|
43
|
+
* edges with their corner valences.
|
|
7
44
|
*/
|
|
8
45
|
export function buildGeometryCache(mesh) {
|
|
9
46
|
const positions = mesh.positions;
|
|
10
|
-
// Validate input
|
|
11
47
|
if (!positions || positions.length === 0) {
|
|
12
|
-
return {
|
|
13
|
-
vertices: [],
|
|
14
|
-
edges: [],
|
|
15
|
-
vertexValence: new Map(),
|
|
16
|
-
vertexEdges: new Map(),
|
|
17
|
-
};
|
|
48
|
+
return { ...EMPTY_CACHE };
|
|
18
49
|
}
|
|
19
50
|
// Positions are stored in the element's local frame (world = origin + local).
|
|
20
51
|
// Snap targets are compared against world-space raycast hit points, so the
|
|
21
52
|
// cache is built in WORLD space by lifting every absolute vertex read by the
|
|
22
53
|
// per-mesh origin. (Triangle-normal math below uses position *differences*,
|
|
23
|
-
// where the origin cancels — those reads are left untouched
|
|
54
|
+
// where the origin cancels — those reads are left untouched, and the
|
|
55
|
+
// tolerance is likewise derived from the LOCAL magnitudes.)
|
|
24
56
|
const ox = mesh.origin ? mesh.origin[0] : 0;
|
|
25
57
|
const oy = mesh.origin ? mesh.origin[1] : 0;
|
|
26
58
|
const oz = mesh.origin ? mesh.origin[2] : 0;
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
59
|
+
const tolerance = snapToleranceFor(positions);
|
|
60
|
+
const { ids, points } = weldVertices(positions, ox, oy, oz, tolerance);
|
|
61
|
+
const indices = mesh.indices;
|
|
62
|
+
if (!indices) {
|
|
63
|
+
return { vertices: points, edges: [] };
|
|
64
|
+
}
|
|
65
|
+
// Collect each welded edge with the normals of the triangles that use it.
|
|
66
|
+
const edgeData = new Map();
|
|
67
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
68
|
+
const normal = triangleNormal(positions, indices, i);
|
|
69
|
+
// A degenerate triangle has no meaningful normal and its edges are junk;
|
|
70
|
+
// letting one through would make a real crease read as coplanar.
|
|
71
|
+
if (!normal)
|
|
36
72
|
continue;
|
|
73
|
+
const a = ids[indices[i]];
|
|
74
|
+
const b = ids[indices[i + 1]];
|
|
75
|
+
const c = ids[indices[i + 2]];
|
|
76
|
+
if (a < 0 || b < 0 || c < 0)
|
|
77
|
+
continue;
|
|
78
|
+
// Welding can collapse a triangle even when its PRE-weld positions gave a
|
|
79
|
+
// valid normal. Skipping only the collapsed edge is not enough: if a === b
|
|
80
|
+
// then (b,c) and (c,a) are the SAME pair, so the survivor is registered
|
|
81
|
+
// twice and receives two normals from one degenerate triangle. It then
|
|
82
|
+
// reads as a two-manifold edge whose crease angle is computed against a
|
|
83
|
+
// duplicate of itself, which can turn a boundary edge into a false crease.
|
|
84
|
+
if (a === b || b === c || c === a)
|
|
85
|
+
continue;
|
|
86
|
+
for (const [idx0, idx1] of [[a, b], [b, c], [c, a]]) {
|
|
87
|
+
const key = idx0 < idx1 ? `${idx0}_${idx1}` : `${idx1}_${idx0}`;
|
|
88
|
+
const existing = edgeData.get(key);
|
|
89
|
+
if (existing)
|
|
90
|
+
existing.normals.push(normal);
|
|
91
|
+
else
|
|
92
|
+
edgeData.set(key, { segment: { a: idx0, b: idx1 }, normals: [normal] });
|
|
37
93
|
}
|
|
38
|
-
// Use reduced precision for deduplication
|
|
39
|
-
const key = `${vertex.x.toFixed(4)}_${vertex.y.toFixed(4)}_${vertex.z.toFixed(4)}`;
|
|
40
|
-
vertexMap.set(key, vertex);
|
|
41
94
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const vertexValence = new Map();
|
|
47
|
-
const vertexEdges = new Map();
|
|
48
|
-
const indices = mesh.indices;
|
|
49
|
-
if (indices) {
|
|
50
|
-
// First pass: collect edges and their adjacent triangle normals
|
|
51
|
-
const edgeData = new Map();
|
|
52
|
-
// Helper to compute triangle normal
|
|
53
|
-
const computeTriangleNormal = (i) => {
|
|
54
|
-
const i0 = indices[i] * 3;
|
|
55
|
-
const i1 = indices[i + 1] * 3;
|
|
56
|
-
const i2 = indices[i + 2] * 3;
|
|
57
|
-
const ax = positions[i1] - positions[i0];
|
|
58
|
-
const ay = positions[i1 + 1] - positions[i0 + 1];
|
|
59
|
-
const az = positions[i1 + 2] - positions[i0 + 2];
|
|
60
|
-
const bx = positions[i2] - positions[i0];
|
|
61
|
-
const by = positions[i2 + 1] - positions[i0 + 1];
|
|
62
|
-
const bz = positions[i2 + 2] - positions[i0 + 2];
|
|
63
|
-
// Cross product
|
|
64
|
-
const nx = ay * bz - az * by;
|
|
65
|
-
const ny = az * bx - ax * bz;
|
|
66
|
-
const nz = ax * by - ay * bx;
|
|
67
|
-
// Normalize
|
|
68
|
-
const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
69
|
-
return len > 0 ? { x: nx / len, y: ny / len, z: nz / len } : { x: 0, y: 1, z: 0 };
|
|
70
|
-
};
|
|
71
|
-
for (let i = 0; i < indices.length; i += 3) {
|
|
72
|
-
const triNormal = computeTriangleNormal(i);
|
|
73
|
-
const triangleEdges = [
|
|
74
|
-
[indices[i], indices[i + 1]],
|
|
75
|
-
[indices[i + 1], indices[i + 2]],
|
|
76
|
-
[indices[i + 2], indices[i]],
|
|
77
|
-
];
|
|
78
|
-
for (const [idx0, idx1] of triangleEdges) {
|
|
79
|
-
const i0 = idx0 * 3;
|
|
80
|
-
const i1 = idx1 * 3;
|
|
81
|
-
const v0 = {
|
|
82
|
-
x: positions[i0] + ox,
|
|
83
|
-
y: positions[i0 + 1] + oy,
|
|
84
|
-
z: positions[i0 + 2] + oz,
|
|
85
|
-
};
|
|
86
|
-
const v1 = {
|
|
87
|
-
x: positions[i1] + ox,
|
|
88
|
-
y: positions[i1 + 1] + oy,
|
|
89
|
-
z: positions[i1 + 2] + oz,
|
|
90
|
-
};
|
|
91
|
-
// Create canonical edge key (smaller index first)
|
|
92
|
-
const key = idx0 < idx1 ? `${idx0}_${idx1}` : `${idx1}_${idx0}`;
|
|
93
|
-
if (!edgeData.has(key)) {
|
|
94
|
-
edgeData.set(key, { v0, v1, idx0, idx1, normals: [triNormal] });
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
const existing = edgeData.get(key);
|
|
98
|
-
if (existing) {
|
|
99
|
-
existing.normals.push(triNormal);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// Second pass: filter to only real edges (boundary or crease edges)
|
|
105
|
-
// Skip internal triangulation edges (shared by coplanar triangles)
|
|
106
|
-
const COPLANAR_THRESHOLD = 0.98; // Dot product threshold for coplanar check
|
|
107
|
-
for (const [, data] of edgeData) {
|
|
108
|
-
const { v0, v1, normals } = data;
|
|
109
|
-
// Boundary edge: only one triangle uses it - always a real edge
|
|
110
|
-
if (normals.length === 1) {
|
|
111
|
-
const edgeIndex = edges.length;
|
|
112
|
-
edges.push({ v0, v1, index: edgeIndex });
|
|
113
|
-
// Track vertex valence
|
|
114
|
-
const v0Key = `${v0.x.toFixed(4)}_${v0.y.toFixed(4)}_${v0.z.toFixed(4)}`;
|
|
115
|
-
const v1Key = `${v1.x.toFixed(4)}_${v1.y.toFixed(4)}_${v1.z.toFixed(4)}`;
|
|
116
|
-
vertexValence.set(v0Key, (vertexValence.get(v0Key) || 0) + 1);
|
|
117
|
-
vertexValence.set(v1Key, (vertexValence.get(v1Key) || 0) + 1);
|
|
118
|
-
if (!vertexEdges.has(v0Key))
|
|
119
|
-
vertexEdges.set(v0Key, []);
|
|
120
|
-
if (!vertexEdges.has(v1Key))
|
|
121
|
-
vertexEdges.set(v1Key, []);
|
|
122
|
-
const v0Edges = vertexEdges.get(v0Key);
|
|
123
|
-
const v1Edges = vertexEdges.get(v1Key);
|
|
124
|
-
if (v0Edges)
|
|
125
|
-
v0Edges.push(edgeIndex);
|
|
126
|
-
if (v1Edges)
|
|
127
|
-
v1Edges.push(edgeIndex);
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
// Shared edge: check if triangles are coplanar (internal triangulation edge)
|
|
131
|
-
if (normals.length >= 2) {
|
|
132
|
-
const n1 = normals[0];
|
|
133
|
-
const n2 = normals[1];
|
|
134
|
-
const dot = Math.abs(n1.x * n2.x + n1.y * n2.y + n1.z * n2.z);
|
|
135
|
-
// If normals are nearly parallel, triangles are coplanar - skip this edge
|
|
136
|
-
// (it's an internal triangulation diagonal, not a real model edge)
|
|
137
|
-
if (dot > COPLANAR_THRESHOLD) {
|
|
138
|
-
continue; // Skip internal edge
|
|
139
|
-
}
|
|
140
|
-
// Crease edge: triangles meet at an angle - this is a real edge
|
|
141
|
-
const edgeIndex = edges.length;
|
|
142
|
-
edges.push({ v0, v1, index: edgeIndex });
|
|
143
|
-
// Track vertex valence
|
|
144
|
-
const v0Key = `${v0.x.toFixed(4)}_${v0.y.toFixed(4)}_${v0.z.toFixed(4)}`;
|
|
145
|
-
const v1Key = `${v1.x.toFixed(4)}_${v1.y.toFixed(4)}_${v1.z.toFixed(4)}`;
|
|
146
|
-
vertexValence.set(v0Key, (vertexValence.get(v0Key) || 0) + 1);
|
|
147
|
-
vertexValence.set(v1Key, (vertexValence.get(v1Key) || 0) + 1);
|
|
148
|
-
if (!vertexEdges.has(v0Key))
|
|
149
|
-
vertexEdges.set(v0Key, []);
|
|
150
|
-
if (!vertexEdges.has(v1Key))
|
|
151
|
-
vertexEdges.set(v1Key, []);
|
|
152
|
-
const v0CreaseEdges = vertexEdges.get(v0Key);
|
|
153
|
-
const v1CreaseEdges = vertexEdges.get(v1Key);
|
|
154
|
-
if (v0CreaseEdges)
|
|
155
|
-
v0CreaseEdges.push(edgeIndex);
|
|
156
|
-
if (v1CreaseEdges)
|
|
157
|
-
v1CreaseEdges.push(edgeIndex);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
95
|
+
const segments = [];
|
|
96
|
+
for (const [, data] of edgeData) {
|
|
97
|
+
if (isModelEdge(data.normals))
|
|
98
|
+
segments.push(data.segment);
|
|
160
99
|
}
|
|
161
|
-
return { vertices, edges,
|
|
100
|
+
return { vertices: points, edges: mergeCollinearRuns(segments, points, tolerance) };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A model edge is one that is not flat: a boundary edge (a single adjacent
|
|
104
|
+
* triangle), a non-manifold edge (three or more), or a crease where some pair
|
|
105
|
+
* of adjacent triangles meets at more than the coplanar cutoff.
|
|
106
|
+
*
|
|
107
|
+
* `Math.abs` on the dot is deliberate: a mesh with inconsistent winding gives
|
|
108
|
+
* coplanar neighbours a dot of -1, and without it every such diagonal would be
|
|
109
|
+
* kept as a "crease".
|
|
110
|
+
*/
|
|
111
|
+
function isModelEdge(normals) {
|
|
112
|
+
if (normals.length !== 2)
|
|
113
|
+
return true;
|
|
114
|
+
const [n1, n2] = normals;
|
|
115
|
+
const dot = Math.abs(n1.x * n2.x + n1.y * n2.y + n1.z * n2.z);
|
|
116
|
+
return dot <= COPLANAR_THRESHOLD;
|
|
117
|
+
}
|
|
118
|
+
/** Unit normal of triangle `i`, or null when the triangle is degenerate. */
|
|
119
|
+
function triangleNormal(positions, indices, i) {
|
|
120
|
+
const i0 = indices[i] * 3;
|
|
121
|
+
const i1 = indices[i + 1] * 3;
|
|
122
|
+
const i2 = indices[i + 2] * 3;
|
|
123
|
+
const ax = positions[i1] - positions[i0];
|
|
124
|
+
const ay = positions[i1 + 1] - positions[i0 + 1];
|
|
125
|
+
const az = positions[i1 + 2] - positions[i0 + 2];
|
|
126
|
+
const bx = positions[i2] - positions[i0];
|
|
127
|
+
const by = positions[i2 + 1] - positions[i0 + 1];
|
|
128
|
+
const bz = positions[i2 + 2] - positions[i0 + 2];
|
|
129
|
+
const nx = ay * bz - az * by;
|
|
130
|
+
const ny = az * bx - ax * bz;
|
|
131
|
+
const nz = ax * by - ay * bx;
|
|
132
|
+
const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
133
|
+
if (!(len > 0) || !isFinite(len))
|
|
134
|
+
return null;
|
|
135
|
+
return { x: nx / len, y: ny / len, z: nz / len };
|
|
162
136
|
}
|
|
163
137
|
//# sourceMappingURL=snap-geometry-cache.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snap-geometry-cache.js","sourceRoot":"","sources":["../src/snap-geometry-cache.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAqB/D;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAc;IAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"snap-geometry-cache.js","sourceRoot":"","sources":["../src/snap-geometry-cache.ts"],"names":[],"mappings":"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"}
|