@driftengine/assets 4.3.0 → 4.4.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/README.md +1 -1
- package/dist/bake/collapseHeap.d.ts +24 -0
- package/dist/bake/collapseHeap.js +80 -0
- package/dist/bake/collapseMeasures.d.ts +31 -0
- package/dist/bake/collapseMeasures.js +183 -0
- package/dist/bake/sceneField.d.ts +6 -0
- package/dist/bake/sceneField.js +48 -0
- package/dist/bake/sdf.d.ts +5 -5
- package/dist/bake/sdf.js +15 -95
- package/dist/bake/simplify.d.ts +42 -0
- package/dist/bake/simplify.js +350 -0
- package/dist/bake/surfaceDepth.d.ts +29 -0
- package/dist/bake/surfaceDepth.js +101 -0
- package/dist/downscale.d.ts +25 -0
- package/dist/downscale.js +50 -0
- package/dist/drawKey.d.ts +13 -1
- package/dist/drawKey.js +5 -3
- package/dist/drftLoader.d.ts +38 -7
- package/dist/drftLoader.js +186 -30
- package/dist/fieldPlacement.d.ts +22 -0
- package/dist/fieldPlacement.js +49 -0
- package/dist/gltf.d.ts +46 -1
- package/dist/gltf.js +101 -2
- package/dist/index.d.ts +9 -1
- package/dist/index.js +15 -1
- package/dist/loadProgress.d.ts +22 -1
- package/dist/orient.d.ts +9 -1
- package/dist/orient.js +60 -2
- package/dist/partMaterial.d.ts +19 -0
- package/dist/partMaterial.js +16 -0
- package/dist/readModel.d.ts +3 -1
- package/dist/repeats.d.ts +39 -0
- package/dist/repeats.js +391 -0
- package/dist/weld.d.ts +8 -0
- package/dist/weld.js +155 -34
- package/package.json +4 -4
- package/src/bake/collapseHeap.ts +81 -0
- package/src/bake/collapseMeasures.ts +215 -0
- package/src/bake/sceneField.ts +50 -0
- package/src/bake/sdf.ts +14 -99
- package/src/bake/simplify.ts +395 -0
- package/src/bake/surfaceDepth.ts +102 -0
- package/src/downscale.ts +61 -0
- package/src/drawKey.ts +17 -3
- package/src/drftLoader.ts +224 -38
- package/src/fieldPlacement.ts +74 -0
- package/src/gltf.ts +139 -3
- package/src/index.ts +24 -1
- package/src/loadProgress.ts +23 -1
- package/src/orient.ts +62 -3
- package/src/partMaterial.ts +42 -0
- package/src/readModel.ts +3 -1
- package/src/repeats.ts +442 -0
- package/src/weld.ts +164 -34
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The simplifier's queue: candidate edge collapses, cheapest first.
|
|
3
|
+
*
|
|
4
|
+
* A binary min-heap in parallel typed arrays grown by doubling, because a scene's meshes queue
|
|
5
|
+
* millions of candidates and an object each would be most of the bake's memory. A candidate carries
|
|
6
|
+
* the version its vertex had when it was costed; the caller discards one whose vertex has moved on
|
|
7
|
+
* since, rather than the heap finding and removing it.
|
|
8
|
+
*/
|
|
9
|
+
export class CollapseHeap {
|
|
10
|
+
private costs = new Float64Array(1024);
|
|
11
|
+
private us = new Int32Array(1024);
|
|
12
|
+
private vs = new Int32Array(1024);
|
|
13
|
+
private versions = new Uint32Array(1024);
|
|
14
|
+
size = 0;
|
|
15
|
+
|
|
16
|
+
push(cost: number, u: number, v: number, version: number): void {
|
|
17
|
+
if (this.size === this.costs.length) this.grow();
|
|
18
|
+
let i = this.size++;
|
|
19
|
+
while (i > 0) {
|
|
20
|
+
const parent = (i - 1) >> 1;
|
|
21
|
+
if (this.costs[parent]! <= cost) break;
|
|
22
|
+
this.move(parent, i);
|
|
23
|
+
i = parent;
|
|
24
|
+
}
|
|
25
|
+
this.costs[i] = cost;
|
|
26
|
+
this.us[i] = u;
|
|
27
|
+
this.vs[i] = v;
|
|
28
|
+
this.versions[i] = version;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
topCost(): number {
|
|
32
|
+
return this.costs[0]!;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pop(): { u: number; v: number; version: number } {
|
|
36
|
+
const top = { u: this.us[0]!, v: this.vs[0]!, version: this.versions[0]! };
|
|
37
|
+
const last = --this.size;
|
|
38
|
+
const cost = this.costs[last]!;
|
|
39
|
+
const u = this.us[last]!;
|
|
40
|
+
const v = this.vs[last]!;
|
|
41
|
+
const version = this.versions[last]!;
|
|
42
|
+
let i = 0;
|
|
43
|
+
for (;;) {
|
|
44
|
+
const left = i * 2 + 1;
|
|
45
|
+
if (left >= last) break;
|
|
46
|
+
const right = left + 1;
|
|
47
|
+
const child = right < last && this.costs[right]! < this.costs[left]! ? right : left;
|
|
48
|
+
if (this.costs[child]! >= cost) break;
|
|
49
|
+
this.move(child, i);
|
|
50
|
+
i = child;
|
|
51
|
+
}
|
|
52
|
+
this.costs[i] = cost;
|
|
53
|
+
this.us[i] = u;
|
|
54
|
+
this.vs[i] = v;
|
|
55
|
+
this.versions[i] = version;
|
|
56
|
+
return top;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private move(from: number, to: number): void {
|
|
60
|
+
this.costs[to] = this.costs[from]!;
|
|
61
|
+
this.us[to] = this.us[from]!;
|
|
62
|
+
this.vs[to] = this.vs[from]!;
|
|
63
|
+
this.versions[to] = this.versions[from]!;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private grow(): void {
|
|
67
|
+
const size = this.costs.length * 2;
|
|
68
|
+
const costs = new Float64Array(size);
|
|
69
|
+
costs.set(this.costs);
|
|
70
|
+
const us = new Int32Array(size);
|
|
71
|
+
us.set(this.us);
|
|
72
|
+
const vs = new Int32Array(size);
|
|
73
|
+
vs.set(this.vs);
|
|
74
|
+
const versions = new Uint32Array(size);
|
|
75
|
+
versions.set(this.versions);
|
|
76
|
+
this.costs = costs;
|
|
77
|
+
this.us = us;
|
|
78
|
+
this.vs = vs;
|
|
79
|
+
this.versions = versions;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an edge collapse is judged by: how far it moves the surface, and how far it slides a texture.
|
|
3
|
+
*
|
|
4
|
+
* The surface is measured by quadrics (Garland and Heckbert): each vertex carries the planes of the
|
|
5
|
+
* faces around it as ten sums, weighted by area, and a point's cost is its squared distance from
|
|
6
|
+
* them. The texture is measured against the uv each vertex was painted with, read back off the
|
|
7
|
+
* triangles a collapse leaves. Positions are a flat `Float64Array` of xyz and uvs a flat
|
|
8
|
+
* `Float32Array` of uv, both indexed by vertex.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** How much a border's own planes weigh against the faces', per unit of edge length squared. */
|
|
12
|
+
const BORDER_WEIGHT = 4;
|
|
13
|
+
/**
|
|
14
|
+
* How far past a triangle's edge a sample may lie, in barycentric units, and still be measured
|
|
15
|
+
* against it: a vertex on a border that curves, which the straightened border no longer quite
|
|
16
|
+
* reaches. Further out, the surface the sample was painted on has gone from under it.
|
|
17
|
+
*/
|
|
18
|
+
const NEAR_EDGE = 0.05;
|
|
19
|
+
|
|
20
|
+
/** A key for the undirected edge `a`–`b` among `v` vertices. */
|
|
21
|
+
export function edgeKey(a: number, b: number, v: number): number {
|
|
22
|
+
return a < b ? a * v + b : b * v + a;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The unit normal of a triangle into `out`, returning twice its area, or 0 for none. */
|
|
26
|
+
export function triangleNormal(
|
|
27
|
+
p: Float64Array,
|
|
28
|
+
a: number,
|
|
29
|
+
b: number,
|
|
30
|
+
c: number,
|
|
31
|
+
out: number[],
|
|
32
|
+
): number {
|
|
33
|
+
const ux = p[b * 3]! - p[a * 3]!;
|
|
34
|
+
const uy = p[b * 3 + 1]! - p[a * 3 + 1]!;
|
|
35
|
+
const uz = p[b * 3 + 2]! - p[a * 3 + 2]!;
|
|
36
|
+
const vx = p[c * 3]! - p[a * 3]!;
|
|
37
|
+
const vy = p[c * 3 + 1]! - p[a * 3 + 1]!;
|
|
38
|
+
const vz = p[c * 3 + 2]! - p[a * 3 + 2]!;
|
|
39
|
+
const nx = uy * vz - uz * vy;
|
|
40
|
+
const ny = uz * vx - ux * vz;
|
|
41
|
+
const nz = ux * vy - uy * vx;
|
|
42
|
+
const length = Math.hypot(nx, ny, nz);
|
|
43
|
+
if (length <= 1e-30) return 0;
|
|
44
|
+
out[0] = nx / length;
|
|
45
|
+
out[1] = ny / length;
|
|
46
|
+
out[2] = nz / length;
|
|
47
|
+
return length;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Adds the plane `ax + by + cz + d = 0`, weighted by `w`, to vertex `vert`'s quadric. */
|
|
51
|
+
export function addPlane(
|
|
52
|
+
q: Float64Array,
|
|
53
|
+
vert: number,
|
|
54
|
+
a: number,
|
|
55
|
+
b: number,
|
|
56
|
+
c: number,
|
|
57
|
+
d: number,
|
|
58
|
+
w: number,
|
|
59
|
+
): void {
|
|
60
|
+
const at = vert * 10;
|
|
61
|
+
q[at] = q[at]! + w * a * a;
|
|
62
|
+
q[at + 1] = q[at + 1]! + w * a * b;
|
|
63
|
+
q[at + 2] = q[at + 2]! + w * a * c;
|
|
64
|
+
q[at + 3] = q[at + 3]! + w * a * d;
|
|
65
|
+
q[at + 4] = q[at + 4]! + w * b * b;
|
|
66
|
+
q[at + 5] = q[at + 5]! + w * b * c;
|
|
67
|
+
q[at + 6] = q[at + 6]! + w * b * d;
|
|
68
|
+
q[at + 7] = q[at + 7]! + w * c * c;
|
|
69
|
+
q[at + 8] = q[at + 8]! + w * c * d;
|
|
70
|
+
q[at + 9] = q[at + 9]! + w * d * d;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Vertex `vert`'s quadric at the point `x, y, z`: its planes' weighted squared distance from it. */
|
|
74
|
+
export function quadricAt(q: Float64Array, vert: number, x: number, y: number, z: number): number {
|
|
75
|
+
const at = vert * 10;
|
|
76
|
+
return (
|
|
77
|
+
q[at]! * x * x +
|
|
78
|
+
2 * q[at + 1]! * x * y +
|
|
79
|
+
2 * q[at + 2]! * x * z +
|
|
80
|
+
2 * q[at + 3]! * x +
|
|
81
|
+
q[at + 4]! * y * y +
|
|
82
|
+
2 * q[at + 5]! * y * z +
|
|
83
|
+
2 * q[at + 6]! * y +
|
|
84
|
+
q[at + 7]! * z * z +
|
|
85
|
+
2 * q[at + 8]! * z +
|
|
86
|
+
q[at + 9]!
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Every face's plane on its three corners, weighted by its area, and each corner's area summed. */
|
|
91
|
+
export function addFacePlanes(
|
|
92
|
+
p: Float64Array,
|
|
93
|
+
faces: Uint32Array,
|
|
94
|
+
quadric: Float64Array,
|
|
95
|
+
area: Float64Array,
|
|
96
|
+
): void {
|
|
97
|
+
const f = faces.length / 3;
|
|
98
|
+
const n = [0, 0, 0];
|
|
99
|
+
for (let face = 0; face < f; face++) {
|
|
100
|
+
const a = faces[face * 3] as number;
|
|
101
|
+
const b = faces[face * 3 + 1] as number;
|
|
102
|
+
const c = faces[face * 3 + 2] as number;
|
|
103
|
+
const twice = triangleNormal(p, a, b, c, n);
|
|
104
|
+
if (twice <= 0) continue;
|
|
105
|
+
const w = twice / 2;
|
|
106
|
+
const d = -(n[0]! * p[a * 3]! + n[1]! * p[a * 3 + 1]! + n[2]! * p[a * 3 + 2]!);
|
|
107
|
+
for (const vert of [a, b, c]) {
|
|
108
|
+
addPlane(quadric, vert, n[0]!, n[1]!, n[2]!, d, w);
|
|
109
|
+
area[vert] = area[vert]! + w;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A plane standing on every open edge, perpendicular to its face, so a border holds its line.
|
|
116
|
+
* `edges` counts the faces on each edge, keyed by `edgeKey`.
|
|
117
|
+
*/
|
|
118
|
+
export function addBorderPlanes(
|
|
119
|
+
p: Float64Array,
|
|
120
|
+
faces: Uint32Array,
|
|
121
|
+
edges: ReadonlyMap<number, number>,
|
|
122
|
+
quadric: Float64Array,
|
|
123
|
+
): void {
|
|
124
|
+
const v = p.length / 3;
|
|
125
|
+
const f = faces.length / 3;
|
|
126
|
+
const n = [0, 0, 0];
|
|
127
|
+
for (let face = 0; face < f; face++) {
|
|
128
|
+
const a0 = faces[face * 3] as number;
|
|
129
|
+
const b0 = faces[face * 3 + 1] as number;
|
|
130
|
+
const c0 = faces[face * 3 + 2] as number;
|
|
131
|
+
if (triangleNormal(p, a0, b0, c0, n) <= 0) continue;
|
|
132
|
+
for (let k = 0; k < 3; k++) {
|
|
133
|
+
const a = faces[face * 3 + k] as number;
|
|
134
|
+
const b = faces[face * 3 + ((k + 1) % 3)] as number;
|
|
135
|
+
if (edges.get(edgeKey(a, b, v)) !== 1) continue;
|
|
136
|
+
const ex = p[b * 3]! - p[a * 3]!;
|
|
137
|
+
const ey = p[b * 3 + 1]! - p[a * 3 + 1]!;
|
|
138
|
+
const ez = p[b * 3 + 2]! - p[a * 3 + 2]!;
|
|
139
|
+
let px = ey * n[2]! - ez * n[1]!;
|
|
140
|
+
let py = ez * n[0]! - ex * n[2]!;
|
|
141
|
+
let pz = ex * n[1]! - ey * n[0]!;
|
|
142
|
+
const length = Math.hypot(px, py, pz);
|
|
143
|
+
if (length <= 0) continue;
|
|
144
|
+
px /= length;
|
|
145
|
+
py /= length;
|
|
146
|
+
pz /= length;
|
|
147
|
+
const d = -(px * p[a * 3]! + py * p[a * 3 + 1]! + pz * p[a * 3 + 2]!);
|
|
148
|
+
const w = BORDER_WEIGHT * (ex * ex + ey * ey + ez * ez);
|
|
149
|
+
addPlane(quadric, a, px, py, pz, d, w);
|
|
150
|
+
addPlane(quadric, b, px, py, pz, d, w);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** A triangle's signed area in uv. */
|
|
156
|
+
export function uvArea(uvs: Float32Array, a: number, b: number, c: number): number {
|
|
157
|
+
const ux = uvs[b * 2]! - uvs[a * 2]!;
|
|
158
|
+
const uy = uvs[b * 2 + 1]! - uvs[a * 2 + 1]!;
|
|
159
|
+
const vx = uvs[c * 2]! - uvs[a * 2]!;
|
|
160
|
+
const vy = uvs[c * 2 + 1]! - uvs[a * 2 + 1]!;
|
|
161
|
+
return (ux * vy - uy * vx) / 2;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* How far `sample`'s own uv is from what the triangles in `corners` interpolate where it lies: the
|
|
166
|
+
* triangle it projects into, or the nearest one where it projects into none.
|
|
167
|
+
*/
|
|
168
|
+
export function uvMiss(
|
|
169
|
+
p: Float64Array,
|
|
170
|
+
uvs: Float32Array,
|
|
171
|
+
corners: readonly number[],
|
|
172
|
+
sample: number,
|
|
173
|
+
): number {
|
|
174
|
+
let inside = Infinity;
|
|
175
|
+
let nearest = Infinity;
|
|
176
|
+
let nearestMiss = Infinity;
|
|
177
|
+
const px = p[sample * 3]!;
|
|
178
|
+
const py = p[sample * 3 + 1]!;
|
|
179
|
+
const pz = p[sample * 3 + 2]!;
|
|
180
|
+
for (let t = 0; t < corners.length; t += 3) {
|
|
181
|
+
const a = corners[t] as number;
|
|
182
|
+
const b = corners[t + 1] as number;
|
|
183
|
+
const c = corners[t + 2] as number;
|
|
184
|
+
const e0x = p[b * 3]! - p[a * 3]!;
|
|
185
|
+
const e0y = p[b * 3 + 1]! - p[a * 3 + 1]!;
|
|
186
|
+
const e0z = p[b * 3 + 2]! - p[a * 3 + 2]!;
|
|
187
|
+
const e1x = p[c * 3]! - p[a * 3]!;
|
|
188
|
+
const e1y = p[c * 3 + 1]! - p[a * 3 + 1]!;
|
|
189
|
+
const e1z = p[c * 3 + 2]! - p[a * 3 + 2]!;
|
|
190
|
+
const qx = px - p[a * 3]!;
|
|
191
|
+
const qy = py - p[a * 3 + 1]!;
|
|
192
|
+
const qz = pz - p[a * 3 + 2]!;
|
|
193
|
+
const d00 = e0x * e0x + e0y * e0y + e0z * e0z;
|
|
194
|
+
const d01 = e0x * e1x + e0y * e1y + e0z * e1z;
|
|
195
|
+
const d11 = e1x * e1x + e1y * e1y + e1z * e1z;
|
|
196
|
+
const d20 = qx * e0x + qy * e0y + qz * e0z;
|
|
197
|
+
const d21 = qx * e1x + qy * e1y + qz * e1z;
|
|
198
|
+
const denominator = d00 * d11 - d01 * d01;
|
|
199
|
+
if (denominator <= 1e-30) continue;
|
|
200
|
+
const beta = (d11 * d20 - d01 * d21) / denominator;
|
|
201
|
+
const gamma = (d00 * d21 - d01 * d20) / denominator;
|
|
202
|
+
const alpha = 1 - beta - gamma;
|
|
203
|
+
const outside = Math.max(0, -alpha, -beta, -gamma);
|
|
204
|
+
const u = alpha * uvs[a * 2]! + beta * uvs[b * 2]! + gamma * uvs[c * 2]!;
|
|
205
|
+
const w = alpha * uvs[a * 2 + 1]! + beta * uvs[b * 2 + 1]! + gamma * uvs[c * 2 + 1]!;
|
|
206
|
+
const miss = Math.hypot(u - uvs[sample * 2]!, w - uvs[sample * 2 + 1]!);
|
|
207
|
+
if (outside < 1e-6) inside = Math.min(inside, miss);
|
|
208
|
+
else if (outside < nearest) {
|
|
209
|
+
nearest = outside;
|
|
210
|
+
nearestMiss = miss;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (inside !== Infinity) return inside;
|
|
214
|
+
return nearest < NEAR_EDGE ? nearestMiss : Infinity;
|
|
215
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One distance field over a file's static geometry, for tracing indirect light through a scene.
|
|
3
|
+
*
|
|
4
|
+
* **One field for the scene rather than one per mesh, because a bought scene is shaped against the
|
|
5
|
+
* per-mesh bake.** Its walls come as a few meshes spanning the whole of it, so a field sized to
|
|
6
|
+
* each mesh's longest side is metre-wide voxels on exactly the walls the light bounces off, and
|
|
7
|
+
* four hundred of them add more than the model's own geometry. Merged, the same scene is one grid
|
|
8
|
+
* at a voxel size the caller chooses: 25 cm resolves a column, and a courtyard is three megabytes.
|
|
9
|
+
*
|
|
10
|
+
* **What goes in is the caller's to say**, for the reason `addDistanceField` gives: light is traced
|
|
11
|
+
* against what it is safe to be blocked by. Walls and floors, not foliage, not cloth, not the copies
|
|
12
|
+
* of a candle drawn ten thousand times. What it gives up is movement: a merged field is static, so
|
|
13
|
+
* anything that moves wants a field of its own.
|
|
14
|
+
*/
|
|
15
|
+
import { concatMeshes } from '@driftengine/core';
|
|
16
|
+
import { SDFV_WHOLE_FILE } from '@driftengine/drft';
|
|
17
|
+
import type { DrftSdfvEntry, MeshData } from '@driftengine/drft';
|
|
18
|
+
import { bakeObjectSdf } from './sdf.ts';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The field of every mesh `include` admits, merged, at `voxelM` spacing, as the file's
|
|
22
|
+
* whole-geometry entry. Null when nothing is admitted.
|
|
23
|
+
*/
|
|
24
|
+
export function bakeSceneField(
|
|
25
|
+
meshes: readonly MeshData[],
|
|
26
|
+
voxelM: number,
|
|
27
|
+
include: (index: number) => boolean,
|
|
28
|
+
): DrftSdfvEntry | null {
|
|
29
|
+
const chosen: MeshData[] = [];
|
|
30
|
+
for (let index = 0; index < meshes.length; index++) {
|
|
31
|
+
if (include(index)) chosen.push(meshes[index] as MeshData);
|
|
32
|
+
}
|
|
33
|
+
if (chosen.length === 0) return null;
|
|
34
|
+
const merged = concatMeshes(chosen);
|
|
35
|
+
const positions = merged.positions;
|
|
36
|
+
let longest = 0;
|
|
37
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
38
|
+
let lo = Number.POSITIVE_INFINITY;
|
|
39
|
+
let hi = Number.NEGATIVE_INFINITY;
|
|
40
|
+
for (let at = axis; at < positions.length; at += 3) {
|
|
41
|
+
const value = positions[at] as number;
|
|
42
|
+
if (value < lo) lo = value;
|
|
43
|
+
if (value > hi) hi = value;
|
|
44
|
+
}
|
|
45
|
+
longest = Math.max(longest, hi - lo);
|
|
46
|
+
}
|
|
47
|
+
const resolution = Math.max(2, Math.ceil(longest / voxelM) + 1);
|
|
48
|
+
const baked = bakeObjectSdf(merged, resolution);
|
|
49
|
+
return { mesh: SDFV_WHOLE_FILE, dims: baked.dims, bounds: baked.bounds, field: baked.field };
|
|
50
|
+
}
|
package/src/bake/sdf.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** A signed distance field per object, baked offline, for tracing light through a world. */
|
|
2
2
|
|
|
3
|
+
import { surfaceDepth } from './surfaceDepth.ts';
|
|
3
4
|
import type { MeshData } from '@driftengine/drft';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -57,11 +58,11 @@ const UNREACHED = 1e30;
|
|
|
57
58
|
* has a gradient whose length varies with direction, which is the one property a sphere trace
|
|
58
59
|
* depends on, and the failure is a march that overshoots along the coarse axis.
|
|
59
60
|
*
|
|
60
|
-
* **The sign comes from
|
|
61
|
-
* angle-weighted pseudonormal and a generalised winding number all flip when a
|
|
62
|
-
* with its triangles the other way round, and importers disagree about that
|
|
63
|
-
*
|
|
64
|
-
*
|
|
61
|
+
* **The sign comes from how many surfaces separate a point from open air, not from the winding.**
|
|
62
|
+
* A face normal, an angle-weighted pseudonormal and a generalised winding number all flip when a
|
|
63
|
+
* mesh is exported with its triangles the other way round, and importers disagree about that
|
|
64
|
+
* constantly. A walk over the grid from its own faces does not care which way a triangle faces, and
|
|
65
|
+
* unlike crossing parity along one axis it does not need the mesh to be closed. See `surfaceDepth.ts`.
|
|
65
66
|
*
|
|
66
67
|
* **The magnitude comes from exact point-to-triangle distance, not from voxelisation.** Marking
|
|
67
68
|
* the cells a triangle passes through and flooding outward loses any wall thinner than a cell,
|
|
@@ -130,8 +131,6 @@ export function bakeObjectSdf(mesh: MeshData, resolution: number): ObjectSdf {
|
|
|
130
131
|
const field = new Float32Array(count).fill(UNREACHED);
|
|
131
132
|
/** The triangle each grid point's distance was measured to, and -1 where none has reached. */
|
|
132
133
|
const closest = new Int32Array(count).fill(-1);
|
|
133
|
-
/** How many times the surface is crossed between grid point `i - 1` and `i` along `x`. */
|
|
134
|
-
const crossings = new Int32Array(count);
|
|
135
134
|
|
|
136
135
|
const ox = origin[0] as number;
|
|
137
136
|
const oy = origin[1] as number;
|
|
@@ -173,34 +172,6 @@ export function bakeObjectSdf(mesh: MeshData, resolution: number): ObjectSdf {
|
|
|
173
172
|
}
|
|
174
173
|
}
|
|
175
174
|
}
|
|
176
|
-
|
|
177
|
-
/*
|
|
178
|
-
* Where a ray along `x` through each grid column crosses this triangle. Recorded at the first
|
|
179
|
-
* grid point past the crossing, so a scan along `x` accumulating these counts knows, at every
|
|
180
|
-
* point, how many surfaces stand between it and the grid's `-x` face.
|
|
181
|
-
*/
|
|
182
|
-
for (let k = clampIndex(Math.ceil((Math.min(az, bz, cz) - oz) / step), nz); k < nz; k++) {
|
|
183
|
-
const gz = oz + k * step;
|
|
184
|
-
if (gz > Math.max(az, bz, cz)) break;
|
|
185
|
-
for (let j = clampIndex(Math.ceil((Math.min(ay, by, cy) - oy) / step), ny); j < ny; j++) {
|
|
186
|
-
const gy = oy + j * step;
|
|
187
|
-
if (gy > Math.max(ay, by, cy)) break;
|
|
188
|
-
const bary = barycentric2d(gy, gz, ay, az, by, bz, cy, cz);
|
|
189
|
-
if (bary === null) continue;
|
|
190
|
-
const x = bary[0] * ax + bary[1] * bx + bary[2] * cx;
|
|
191
|
-
const i = Math.ceil((x - ox) / step);
|
|
192
|
-
/*
|
|
193
|
-
* **Unreachable while `PAD_VOXELS` is at least one, and kept because it is a constant.**
|
|
194
|
-
* The grid reaches two voxels past the mesh on every axis, so no crossing can land past
|
|
195
|
-
* the `+x` face; with no padding one could, and clamping it would mark the last grid point
|
|
196
|
-
* as having one more surface behind it than it does — flipping the parity of a point that
|
|
197
|
-
* is outside. A crossing before the `-x` face is clamped to zero, where it is true: the
|
|
198
|
-
* grid's first point really does have that surface behind it.
|
|
199
|
-
*/
|
|
200
|
-
if (i >= nx) continue;
|
|
201
|
-
crossings[Math.max(0, i) + nx * (j + ny * k)]++;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
175
|
}
|
|
205
176
|
|
|
206
177
|
/*
|
|
@@ -221,15 +192,14 @@ export function bakeObjectSdf(mesh: MeshData, resolution: number): ObjectSdf {
|
|
|
221
192
|
if ((field[index] as number) >= UNREACHED) field[index] = reach;
|
|
222
193
|
}
|
|
223
194
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
195
|
+
/*
|
|
196
|
+
* **The sign is how many surfaces stand between a point and the open air**: odd is inside. A walk
|
|
197
|
+
* over the grid answers that for every point, where crossing parity along one axis answered it
|
|
198
|
+
* only for a closed solid and striped the air of anything built from sheets. See `surfaceDepth.ts`.
|
|
199
|
+
*/
|
|
200
|
+
const depth = surfaceDepth(field, dims, step);
|
|
201
|
+
for (let index = 0; index < count; index++) {
|
|
202
|
+
if ((depth[index] as number) % 2 === 1) field[index] = -(field[index] as number);
|
|
233
203
|
}
|
|
234
204
|
|
|
235
205
|
return { field, dims, bounds };
|
|
@@ -333,61 +303,6 @@ function clampIndex(value: number, count: number): number {
|
|
|
333
303
|
return value < 0 ? 0 : value > count - 1 ? count - 1 : value;
|
|
334
304
|
}
|
|
335
305
|
|
|
336
|
-
/**
|
|
337
|
-
* Where a point stands in a triangle projected onto one plane, or null if it stands outside.
|
|
338
|
-
*
|
|
339
|
-
* **The tie-break is what stops a shared edge being counted twice or not at all.** A grid column
|
|
340
|
-
* passing exactly along the edge two triangles share is inside both or neither depending on the
|
|
341
|
-
* sign of a zero, and either answer flips the parity of an entire row. Breaking the tie on the
|
|
342
|
-
* coordinates themselves makes the choice consistent between the two triangles: whichever way it
|
|
343
|
-
* goes, exactly one of them claims the column.
|
|
344
|
-
*/
|
|
345
|
-
function barycentric2d(
|
|
346
|
-
px: number,
|
|
347
|
-
py: number,
|
|
348
|
-
ax: number,
|
|
349
|
-
ay: number,
|
|
350
|
-
bx: number,
|
|
351
|
-
by: number,
|
|
352
|
-
cx: number,
|
|
353
|
-
cy: number,
|
|
354
|
-
): [number, number, number] | null {
|
|
355
|
-
const bxr = bx - px;
|
|
356
|
-
const byr = by - py;
|
|
357
|
-
const cxr = cx - px;
|
|
358
|
-
const cyr = cy - py;
|
|
359
|
-
const axr = ax - px;
|
|
360
|
-
const ayr = ay - py;
|
|
361
|
-
|
|
362
|
-
const areaA = orientation(bxr, byr, cxr, cyr);
|
|
363
|
-
if (areaA.sign === 0) return null;
|
|
364
|
-
const areaB = orientation(cxr, cyr, axr, ayr);
|
|
365
|
-
if (areaB.sign !== areaA.sign) return null;
|
|
366
|
-
const areaC = orientation(axr, ayr, bxr, byr);
|
|
367
|
-
if (areaC.sign !== areaA.sign) return null;
|
|
368
|
-
|
|
369
|
-
const sum = areaA.area + areaB.area + areaC.area;
|
|
370
|
-
if (sum === 0) return null;
|
|
371
|
-
return [areaA.area / sum, areaB.area / sum, areaC.area / sum];
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/** Twice the signed area of a triangle with the origin, with zero broken deterministically. */
|
|
375
|
-
function orientation(
|
|
376
|
-
x1: number,
|
|
377
|
-
y1: number,
|
|
378
|
-
x2: number,
|
|
379
|
-
y2: number,
|
|
380
|
-
): { sign: number; area: number } {
|
|
381
|
-
const area = y1 * x2 - x1 * y2;
|
|
382
|
-
if (area > 0) return { sign: 1, area };
|
|
383
|
-
if (area < 0) return { sign: -1, area };
|
|
384
|
-
if (y2 > y1) return { sign: 1, area };
|
|
385
|
-
if (y2 < y1) return { sign: -1, area };
|
|
386
|
-
if (x1 > x2) return { sign: 1, area };
|
|
387
|
-
if (x1 < x2) return { sign: -1, area };
|
|
388
|
-
return { sign: 0, area };
|
|
389
|
-
}
|
|
390
|
-
|
|
391
306
|
/**
|
|
392
307
|
* The distance from a point to the nearest point of a triangle.
|
|
393
308
|
*
|