@driftengine/assets 4.2.1 → 4.4.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/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
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Model readers for seven formats, and the streaming loader that uploads them.
|
|
4
4
|
|
|
5
|
-
**Cost:
|
|
5
|
+
**Cost: 15.8 KB gzipped on top of core.** Measured by `scripts/size-gate.test.mjs`, which fails if it drifts more than
|
|
6
6
|
3% — the number is a fact about the build rather than a claim in a document.
|
|
7
7
|
|
|
8
8
|
Part of [DriftEngine](../../README.md). See [`docs/ARCHITECTURE.md`](../../docs/ARCHITECTURE.md)
|
|
@@ -0,0 +1,24 @@
|
|
|
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 declare class CollapseHeap {
|
|
10
|
+
private costs;
|
|
11
|
+
private us;
|
|
12
|
+
private vs;
|
|
13
|
+
private versions;
|
|
14
|
+
size: number;
|
|
15
|
+
push(cost: number, u: number, v: number, version: number): void;
|
|
16
|
+
topCost(): number;
|
|
17
|
+
pop(): {
|
|
18
|
+
u: number;
|
|
19
|
+
v: number;
|
|
20
|
+
version: number;
|
|
21
|
+
};
|
|
22
|
+
private move;
|
|
23
|
+
private grow;
|
|
24
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
costs = new Float64Array(1024);
|
|
11
|
+
us = new Int32Array(1024);
|
|
12
|
+
vs = new Int32Array(1024);
|
|
13
|
+
versions = new Uint32Array(1024);
|
|
14
|
+
size = 0;
|
|
15
|
+
push(cost, u, v, version) {
|
|
16
|
+
if (this.size === this.costs.length)
|
|
17
|
+
this.grow();
|
|
18
|
+
let i = this.size++;
|
|
19
|
+
while (i > 0) {
|
|
20
|
+
const parent = (i - 1) >> 1;
|
|
21
|
+
if (this.costs[parent] <= cost)
|
|
22
|
+
break;
|
|
23
|
+
this.move(parent, i);
|
|
24
|
+
i = parent;
|
|
25
|
+
}
|
|
26
|
+
this.costs[i] = cost;
|
|
27
|
+
this.us[i] = u;
|
|
28
|
+
this.vs[i] = v;
|
|
29
|
+
this.versions[i] = version;
|
|
30
|
+
}
|
|
31
|
+
topCost() {
|
|
32
|
+
return this.costs[0];
|
|
33
|
+
}
|
|
34
|
+
pop() {
|
|
35
|
+
const top = { u: this.us[0], v: this.vs[0], version: this.versions[0] };
|
|
36
|
+
const last = --this.size;
|
|
37
|
+
const cost = this.costs[last];
|
|
38
|
+
const u = this.us[last];
|
|
39
|
+
const v = this.vs[last];
|
|
40
|
+
const version = this.versions[last];
|
|
41
|
+
let i = 0;
|
|
42
|
+
for (;;) {
|
|
43
|
+
const left = i * 2 + 1;
|
|
44
|
+
if (left >= last)
|
|
45
|
+
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)
|
|
49
|
+
break;
|
|
50
|
+
this.move(child, i);
|
|
51
|
+
i = child;
|
|
52
|
+
}
|
|
53
|
+
this.costs[i] = cost;
|
|
54
|
+
this.us[i] = u;
|
|
55
|
+
this.vs[i] = v;
|
|
56
|
+
this.versions[i] = version;
|
|
57
|
+
return top;
|
|
58
|
+
}
|
|
59
|
+
move(from, to) {
|
|
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
|
+
grow() {
|
|
66
|
+
const size = this.costs.length * 2;
|
|
67
|
+
const costs = new Float64Array(size);
|
|
68
|
+
costs.set(this.costs);
|
|
69
|
+
const us = new Int32Array(size);
|
|
70
|
+
us.set(this.us);
|
|
71
|
+
const vs = new Int32Array(size);
|
|
72
|
+
vs.set(this.vs);
|
|
73
|
+
const versions = new Uint32Array(size);
|
|
74
|
+
versions.set(this.versions);
|
|
75
|
+
this.costs = costs;
|
|
76
|
+
this.us = us;
|
|
77
|
+
this.vs = vs;
|
|
78
|
+
this.versions = versions;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
/** A key for the undirected edge `a`–`b` among `v` vertices. */
|
|
11
|
+
export declare function edgeKey(a: number, b: number, v: number): number;
|
|
12
|
+
/** The unit normal of a triangle into `out`, returning twice its area, or 0 for none. */
|
|
13
|
+
export declare function triangleNormal(p: Float64Array, a: number, b: number, c: number, out: number[]): number;
|
|
14
|
+
/** Adds the plane `ax + by + cz + d = 0`, weighted by `w`, to vertex `vert`'s quadric. */
|
|
15
|
+
export declare function addPlane(q: Float64Array, vert: number, a: number, b: number, c: number, d: number, w: number): void;
|
|
16
|
+
/** Vertex `vert`'s quadric at the point `x, y, z`: its planes' weighted squared distance from it. */
|
|
17
|
+
export declare function quadricAt(q: Float64Array, vert: number, x: number, y: number, z: number): number;
|
|
18
|
+
/** Every face's plane on its three corners, weighted by its area, and each corner's area summed. */
|
|
19
|
+
export declare function addFacePlanes(p: Float64Array, faces: Uint32Array, quadric: Float64Array, area: Float64Array): void;
|
|
20
|
+
/**
|
|
21
|
+
* A plane standing on every open edge, perpendicular to its face, so a border holds its line.
|
|
22
|
+
* `edges` counts the faces on each edge, keyed by `edgeKey`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function addBorderPlanes(p: Float64Array, faces: Uint32Array, edges: ReadonlyMap<number, number>, quadric: Float64Array): void;
|
|
25
|
+
/** A triangle's signed area in uv. */
|
|
26
|
+
export declare function uvArea(uvs: Float32Array, a: number, b: number, c: number): number;
|
|
27
|
+
/**
|
|
28
|
+
* How far `sample`'s own uv is from what the triangles in `corners` interpolate where it lies: the
|
|
29
|
+
* triangle it projects into, or the nearest one where it projects into none.
|
|
30
|
+
*/
|
|
31
|
+
export declare function uvMiss(p: Float64Array, uvs: Float32Array, corners: readonly number[], sample: number): number;
|
|
@@ -0,0 +1,183 @@
|
|
|
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
|
+
/** How much a border's own planes weigh against the faces', per unit of edge length squared. */
|
|
11
|
+
const BORDER_WEIGHT = 4;
|
|
12
|
+
/**
|
|
13
|
+
* How far past a triangle's edge a sample may lie, in barycentric units, and still be measured
|
|
14
|
+
* against it: a vertex on a border that curves, which the straightened border no longer quite
|
|
15
|
+
* reaches. Further out, the surface the sample was painted on has gone from under it.
|
|
16
|
+
*/
|
|
17
|
+
const NEAR_EDGE = 0.05;
|
|
18
|
+
/** A key for the undirected edge `a`–`b` among `v` vertices. */
|
|
19
|
+
export function edgeKey(a, b, v) {
|
|
20
|
+
return a < b ? a * v + b : b * v + a;
|
|
21
|
+
}
|
|
22
|
+
/** The unit normal of a triangle into `out`, returning twice its area, or 0 for none. */
|
|
23
|
+
export function triangleNormal(p, a, b, c, out) {
|
|
24
|
+
const ux = p[b * 3] - p[a * 3];
|
|
25
|
+
const uy = p[b * 3 + 1] - p[a * 3 + 1];
|
|
26
|
+
const uz = p[b * 3 + 2] - p[a * 3 + 2];
|
|
27
|
+
const vx = p[c * 3] - p[a * 3];
|
|
28
|
+
const vy = p[c * 3 + 1] - p[a * 3 + 1];
|
|
29
|
+
const vz = p[c * 3 + 2] - p[a * 3 + 2];
|
|
30
|
+
const nx = uy * vz - uz * vy;
|
|
31
|
+
const ny = uz * vx - ux * vz;
|
|
32
|
+
const nz = ux * vy - uy * vx;
|
|
33
|
+
const length = Math.hypot(nx, ny, nz);
|
|
34
|
+
if (length <= 1e-30)
|
|
35
|
+
return 0;
|
|
36
|
+
out[0] = nx / length;
|
|
37
|
+
out[1] = ny / length;
|
|
38
|
+
out[2] = nz / length;
|
|
39
|
+
return length;
|
|
40
|
+
}
|
|
41
|
+
/** Adds the plane `ax + by + cz + d = 0`, weighted by `w`, to vertex `vert`'s quadric. */
|
|
42
|
+
export function addPlane(q, vert, a, b, c, d, w) {
|
|
43
|
+
const at = vert * 10;
|
|
44
|
+
q[at] = q[at] + w * a * a;
|
|
45
|
+
q[at + 1] = q[at + 1] + w * a * b;
|
|
46
|
+
q[at + 2] = q[at + 2] + w * a * c;
|
|
47
|
+
q[at + 3] = q[at + 3] + w * a * d;
|
|
48
|
+
q[at + 4] = q[at + 4] + w * b * b;
|
|
49
|
+
q[at + 5] = q[at + 5] + w * b * c;
|
|
50
|
+
q[at + 6] = q[at + 6] + w * b * d;
|
|
51
|
+
q[at + 7] = q[at + 7] + w * c * c;
|
|
52
|
+
q[at + 8] = q[at + 8] + w * c * d;
|
|
53
|
+
q[at + 9] = q[at + 9] + w * d * d;
|
|
54
|
+
}
|
|
55
|
+
/** Vertex `vert`'s quadric at the point `x, y, z`: its planes' weighted squared distance from it. */
|
|
56
|
+
export function quadricAt(q, vert, x, y, z) {
|
|
57
|
+
const at = vert * 10;
|
|
58
|
+
return (q[at] * x * x +
|
|
59
|
+
2 * q[at + 1] * x * y +
|
|
60
|
+
2 * q[at + 2] * x * z +
|
|
61
|
+
2 * q[at + 3] * x +
|
|
62
|
+
q[at + 4] * y * y +
|
|
63
|
+
2 * q[at + 5] * y * z +
|
|
64
|
+
2 * q[at + 6] * y +
|
|
65
|
+
q[at + 7] * z * z +
|
|
66
|
+
2 * q[at + 8] * z +
|
|
67
|
+
q[at + 9]);
|
|
68
|
+
}
|
|
69
|
+
/** Every face's plane on its three corners, weighted by its area, and each corner's area summed. */
|
|
70
|
+
export function addFacePlanes(p, faces, quadric, area) {
|
|
71
|
+
const f = faces.length / 3;
|
|
72
|
+
const n = [0, 0, 0];
|
|
73
|
+
for (let face = 0; face < f; face++) {
|
|
74
|
+
const a = faces[face * 3];
|
|
75
|
+
const b = faces[face * 3 + 1];
|
|
76
|
+
const c = faces[face * 3 + 2];
|
|
77
|
+
const twice = triangleNormal(p, a, b, c, n);
|
|
78
|
+
if (twice <= 0)
|
|
79
|
+
continue;
|
|
80
|
+
const w = twice / 2;
|
|
81
|
+
const d = -(n[0] * p[a * 3] + n[1] * p[a * 3 + 1] + n[2] * p[a * 3 + 2]);
|
|
82
|
+
for (const vert of [a, b, c]) {
|
|
83
|
+
addPlane(quadric, vert, n[0], n[1], n[2], d, w);
|
|
84
|
+
area[vert] = area[vert] + w;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A plane standing on every open edge, perpendicular to its face, so a border holds its line.
|
|
90
|
+
* `edges` counts the faces on each edge, keyed by `edgeKey`.
|
|
91
|
+
*/
|
|
92
|
+
export function addBorderPlanes(p, faces, edges, quadric) {
|
|
93
|
+
const v = p.length / 3;
|
|
94
|
+
const f = faces.length / 3;
|
|
95
|
+
const n = [0, 0, 0];
|
|
96
|
+
for (let face = 0; face < f; face++) {
|
|
97
|
+
const a0 = faces[face * 3];
|
|
98
|
+
const b0 = faces[face * 3 + 1];
|
|
99
|
+
const c0 = faces[face * 3 + 2];
|
|
100
|
+
if (triangleNormal(p, a0, b0, c0, n) <= 0)
|
|
101
|
+
continue;
|
|
102
|
+
for (let k = 0; k < 3; k++) {
|
|
103
|
+
const a = faces[face * 3 + k];
|
|
104
|
+
const b = faces[face * 3 + ((k + 1) % 3)];
|
|
105
|
+
if (edges.get(edgeKey(a, b, v)) !== 1)
|
|
106
|
+
continue;
|
|
107
|
+
const ex = p[b * 3] - p[a * 3];
|
|
108
|
+
const ey = p[b * 3 + 1] - p[a * 3 + 1];
|
|
109
|
+
const ez = p[b * 3 + 2] - p[a * 3 + 2];
|
|
110
|
+
let px = ey * n[2] - ez * n[1];
|
|
111
|
+
let py = ez * n[0] - ex * n[2];
|
|
112
|
+
let pz = ex * n[1] - ey * n[0];
|
|
113
|
+
const length = Math.hypot(px, py, pz);
|
|
114
|
+
if (length <= 0)
|
|
115
|
+
continue;
|
|
116
|
+
px /= length;
|
|
117
|
+
py /= length;
|
|
118
|
+
pz /= length;
|
|
119
|
+
const d = -(px * p[a * 3] + py * p[a * 3 + 1] + pz * p[a * 3 + 2]);
|
|
120
|
+
const w = BORDER_WEIGHT * (ex * ex + ey * ey + ez * ez);
|
|
121
|
+
addPlane(quadric, a, px, py, pz, d, w);
|
|
122
|
+
addPlane(quadric, b, px, py, pz, d, w);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** A triangle's signed area in uv. */
|
|
127
|
+
export function uvArea(uvs, a, b, c) {
|
|
128
|
+
const ux = uvs[b * 2] - uvs[a * 2];
|
|
129
|
+
const uy = uvs[b * 2 + 1] - uvs[a * 2 + 1];
|
|
130
|
+
const vx = uvs[c * 2] - uvs[a * 2];
|
|
131
|
+
const vy = uvs[c * 2 + 1] - uvs[a * 2 + 1];
|
|
132
|
+
return (ux * vy - uy * vx) / 2;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* How far `sample`'s own uv is from what the triangles in `corners` interpolate where it lies: the
|
|
136
|
+
* triangle it projects into, or the nearest one where it projects into none.
|
|
137
|
+
*/
|
|
138
|
+
export function uvMiss(p, uvs, corners, sample) {
|
|
139
|
+
let inside = Infinity;
|
|
140
|
+
let nearest = Infinity;
|
|
141
|
+
let nearestMiss = Infinity;
|
|
142
|
+
const px = p[sample * 3];
|
|
143
|
+
const py = p[sample * 3 + 1];
|
|
144
|
+
const pz = p[sample * 3 + 2];
|
|
145
|
+
for (let t = 0; t < corners.length; t += 3) {
|
|
146
|
+
const a = corners[t];
|
|
147
|
+
const b = corners[t + 1];
|
|
148
|
+
const c = corners[t + 2];
|
|
149
|
+
const e0x = p[b * 3] - p[a * 3];
|
|
150
|
+
const e0y = p[b * 3 + 1] - p[a * 3 + 1];
|
|
151
|
+
const e0z = p[b * 3 + 2] - p[a * 3 + 2];
|
|
152
|
+
const e1x = p[c * 3] - p[a * 3];
|
|
153
|
+
const e1y = p[c * 3 + 1] - p[a * 3 + 1];
|
|
154
|
+
const e1z = p[c * 3 + 2] - p[a * 3 + 2];
|
|
155
|
+
const qx = px - p[a * 3];
|
|
156
|
+
const qy = py - p[a * 3 + 1];
|
|
157
|
+
const qz = pz - p[a * 3 + 2];
|
|
158
|
+
const d00 = e0x * e0x + e0y * e0y + e0z * e0z;
|
|
159
|
+
const d01 = e0x * e1x + e0y * e1y + e0z * e1z;
|
|
160
|
+
const d11 = e1x * e1x + e1y * e1y + e1z * e1z;
|
|
161
|
+
const d20 = qx * e0x + qy * e0y + qz * e0z;
|
|
162
|
+
const d21 = qx * e1x + qy * e1y + qz * e1z;
|
|
163
|
+
const denominator = d00 * d11 - d01 * d01;
|
|
164
|
+
if (denominator <= 1e-30)
|
|
165
|
+
continue;
|
|
166
|
+
const beta = (d11 * d20 - d01 * d21) / denominator;
|
|
167
|
+
const gamma = (d00 * d21 - d01 * d20) / denominator;
|
|
168
|
+
const alpha = 1 - beta - gamma;
|
|
169
|
+
const outside = Math.max(0, -alpha, -beta, -gamma);
|
|
170
|
+
const u = alpha * uvs[a * 2] + beta * uvs[b * 2] + gamma * uvs[c * 2];
|
|
171
|
+
const w = alpha * uvs[a * 2 + 1] + beta * uvs[b * 2 + 1] + gamma * uvs[c * 2 + 1];
|
|
172
|
+
const miss = Math.hypot(u - uvs[sample * 2], w - uvs[sample * 2 + 1]);
|
|
173
|
+
if (outside < 1e-6)
|
|
174
|
+
inside = Math.min(inside, miss);
|
|
175
|
+
else if (outside < nearest) {
|
|
176
|
+
nearest = outside;
|
|
177
|
+
nearestMiss = miss;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (inside !== Infinity)
|
|
181
|
+
return inside;
|
|
182
|
+
return nearest < NEAR_EDGE ? nearestMiss : Infinity;
|
|
183
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DrftSdfvEntry, MeshData } from '@driftengine/drft';
|
|
2
|
+
/**
|
|
3
|
+
* The field of every mesh `include` admits, merged, at `voxelM` spacing, as the file's
|
|
4
|
+
* whole-geometry entry. Null when nothing is admitted.
|
|
5
|
+
*/
|
|
6
|
+
export declare function bakeSceneField(meshes: readonly MeshData[], voxelM: number, include: (index: number) => boolean): DrftSdfvEntry | null;
|
|
@@ -0,0 +1,48 @@
|
|
|
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 { bakeObjectSdf } from './sdf.js';
|
|
18
|
+
/**
|
|
19
|
+
* The field of every mesh `include` admits, merged, at `voxelM` spacing, as the file's
|
|
20
|
+
* whole-geometry entry. Null when nothing is admitted.
|
|
21
|
+
*/
|
|
22
|
+
export function bakeSceneField(meshes, voxelM, include) {
|
|
23
|
+
const chosen = [];
|
|
24
|
+
for (let index = 0; index < meshes.length; index++) {
|
|
25
|
+
if (include(index))
|
|
26
|
+
chosen.push(meshes[index]);
|
|
27
|
+
}
|
|
28
|
+
if (chosen.length === 0)
|
|
29
|
+
return null;
|
|
30
|
+
const merged = concatMeshes(chosen);
|
|
31
|
+
const positions = merged.positions;
|
|
32
|
+
let longest = 0;
|
|
33
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
34
|
+
let lo = Number.POSITIVE_INFINITY;
|
|
35
|
+
let hi = Number.NEGATIVE_INFINITY;
|
|
36
|
+
for (let at = axis; at < positions.length; at += 3) {
|
|
37
|
+
const value = positions[at];
|
|
38
|
+
if (value < lo)
|
|
39
|
+
lo = value;
|
|
40
|
+
if (value > hi)
|
|
41
|
+
hi = value;
|
|
42
|
+
}
|
|
43
|
+
longest = Math.max(longest, hi - lo);
|
|
44
|
+
}
|
|
45
|
+
const resolution = Math.max(2, Math.ceil(longest / voxelM) + 1);
|
|
46
|
+
const baked = bakeObjectSdf(merged, resolution);
|
|
47
|
+
return { mesh: SDFV_WHOLE_FILE, dims: baked.dims, bounds: baked.bounds, field: baked.field };
|
|
48
|
+
}
|
package/dist/bake/sdf.d.ts
CHANGED
|
@@ -30,11 +30,11 @@ export interface ObjectSdf {
|
|
|
30
30
|
* has a gradient whose length varies with direction, which is the one property a sphere trace
|
|
31
31
|
* depends on, and the failure is a march that overshoots along the coarse axis.
|
|
32
32
|
*
|
|
33
|
-
* **The sign comes from
|
|
34
|
-
* angle-weighted pseudonormal and a generalised winding number all flip when a
|
|
35
|
-
* with its triangles the other way round, and importers disagree about that
|
|
36
|
-
*
|
|
37
|
-
*
|
|
33
|
+
* **The sign comes from how many surfaces separate a point from open air, not from the winding.**
|
|
34
|
+
* A face normal, an angle-weighted pseudonormal and a generalised winding number all flip when a
|
|
35
|
+
* mesh is exported with its triangles the other way round, and importers disagree about that
|
|
36
|
+
* constantly. A walk over the grid from its own faces does not care which way a triangle faces, and
|
|
37
|
+
* unlike crossing parity along one axis it does not need the mesh to be closed. See `surfaceDepth.ts`.
|
|
38
38
|
*
|
|
39
39
|
* **The magnitude comes from exact point-to-triangle distance, not from voxelisation.** Marking
|
|
40
40
|
* the cells a triangle passes through and flooding outward loses any wall thinner than a cell,
|
package/dist/bake/sdf.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** A signed distance field per object, baked offline, for tracing light through a world. */
|
|
2
|
+
import { surfaceDepth } from './surfaceDepth.js';
|
|
2
3
|
/**
|
|
3
4
|
* Voxels of clearance around the mesh, on every axis.
|
|
4
5
|
*
|
|
@@ -28,11 +29,11 @@ const UNREACHED = 1e30;
|
|
|
28
29
|
* has a gradient whose length varies with direction, which is the one property a sphere trace
|
|
29
30
|
* depends on, and the failure is a march that overshoots along the coarse axis.
|
|
30
31
|
*
|
|
31
|
-
* **The sign comes from
|
|
32
|
-
* angle-weighted pseudonormal and a generalised winding number all flip when a
|
|
33
|
-
* with its triangles the other way round, and importers disagree about that
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* **The sign comes from how many surfaces separate a point from open air, not from the winding.**
|
|
33
|
+
* A face normal, an angle-weighted pseudonormal and a generalised winding number all flip when a
|
|
34
|
+
* mesh is exported with its triangles the other way round, and importers disagree about that
|
|
35
|
+
* constantly. A walk over the grid from its own faces does not care which way a triangle faces, and
|
|
36
|
+
* unlike crossing parity along one axis it does not need the mesh to be closed. See `surfaceDepth.ts`.
|
|
36
37
|
*
|
|
37
38
|
* **The magnitude comes from exact point-to-triangle distance, not from voxelisation.** Marking
|
|
38
39
|
* the cells a triangle passes through and flooding outward loses any wall thinner than a cell,
|
|
@@ -93,8 +94,6 @@ export function bakeObjectSdf(mesh, resolution) {
|
|
|
93
94
|
const field = new Float32Array(count).fill(UNREACHED);
|
|
94
95
|
/** The triangle each grid point's distance was measured to, and -1 where none has reached. */
|
|
95
96
|
const closest = new Int32Array(count).fill(-1);
|
|
96
|
-
/** How many times the surface is crossed between grid point `i - 1` and `i` along `x`. */
|
|
97
|
-
const crossings = new Int32Array(count);
|
|
98
97
|
const ox = origin[0];
|
|
99
98
|
const oy = origin[1];
|
|
100
99
|
const oz = origin[2];
|
|
@@ -132,37 +131,6 @@ export function bakeObjectSdf(mesh, resolution) {
|
|
|
132
131
|
}
|
|
133
132
|
}
|
|
134
133
|
}
|
|
135
|
-
/*
|
|
136
|
-
* Where a ray along `x` through each grid column crosses this triangle. Recorded at the first
|
|
137
|
-
* grid point past the crossing, so a scan along `x` accumulating these counts knows, at every
|
|
138
|
-
* point, how many surfaces stand between it and the grid's `-x` face.
|
|
139
|
-
*/
|
|
140
|
-
for (let k = clampIndex(Math.ceil((Math.min(az, bz, cz) - oz) / step), nz); k < nz; k++) {
|
|
141
|
-
const gz = oz + k * step;
|
|
142
|
-
if (gz > Math.max(az, bz, cz))
|
|
143
|
-
break;
|
|
144
|
-
for (let j = clampIndex(Math.ceil((Math.min(ay, by, cy) - oy) / step), ny); j < ny; j++) {
|
|
145
|
-
const gy = oy + j * step;
|
|
146
|
-
if (gy > Math.max(ay, by, cy))
|
|
147
|
-
break;
|
|
148
|
-
const bary = barycentric2d(gy, gz, ay, az, by, bz, cy, cz);
|
|
149
|
-
if (bary === null)
|
|
150
|
-
continue;
|
|
151
|
-
const x = bary[0] * ax + bary[1] * bx + bary[2] * cx;
|
|
152
|
-
const i = Math.ceil((x - ox) / step);
|
|
153
|
-
/*
|
|
154
|
-
* **Unreachable while `PAD_VOXELS` is at least one, and kept because it is a constant.**
|
|
155
|
-
* The grid reaches two voxels past the mesh on every axis, so no crossing can land past
|
|
156
|
-
* the `+x` face; with no padding one could, and clamping it would mark the last grid point
|
|
157
|
-
* as having one more surface behind it than it does — flipping the parity of a point that
|
|
158
|
-
* is outside. A crossing before the `-x` face is clamped to zero, where it is true: the
|
|
159
|
-
* grid's first point really does have that surface behind it.
|
|
160
|
-
*/
|
|
161
|
-
if (i >= nx)
|
|
162
|
-
continue;
|
|
163
|
-
crossings[Math.max(0, i) + nx * (j + ny * k)]++;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
134
|
}
|
|
167
135
|
/*
|
|
168
136
|
* Carry the closest triangle outward and recompute against it, eight directions twice. Two
|
|
@@ -181,16 +149,15 @@ export function bakeObjectSdf(mesh, resolution) {
|
|
|
181
149
|
if (field[index] >= UNREACHED)
|
|
182
150
|
field[index] = reach;
|
|
183
151
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
152
|
+
/*
|
|
153
|
+
* **The sign is how many surfaces stand between a point and the open air**: odd is inside. A walk
|
|
154
|
+
* over the grid answers that for every point, where crossing parity along one axis answered it
|
|
155
|
+
* only for a closed solid and striped the air of anything built from sheets. See `surfaceDepth.ts`.
|
|
156
|
+
*/
|
|
157
|
+
const depth = surfaceDepth(field, dims, step);
|
|
158
|
+
for (let index = 0; index < count; index++) {
|
|
159
|
+
if (depth[index] % 2 === 1)
|
|
160
|
+
field[index] = -field[index];
|
|
194
161
|
}
|
|
195
162
|
return { field, dims, bounds };
|
|
196
163
|
}
|
|
@@ -264,53 +231,6 @@ const NEIGHBOURS = [
|
|
|
264
231
|
function clampIndex(value, count) {
|
|
265
232
|
return value < 0 ? 0 : value > count - 1 ? count - 1 : value;
|
|
266
233
|
}
|
|
267
|
-
/**
|
|
268
|
-
* Where a point stands in a triangle projected onto one plane, or null if it stands outside.
|
|
269
|
-
*
|
|
270
|
-
* **The tie-break is what stops a shared edge being counted twice or not at all.** A grid column
|
|
271
|
-
* passing exactly along the edge two triangles share is inside both or neither depending on the
|
|
272
|
-
* sign of a zero, and either answer flips the parity of an entire row. Breaking the tie on the
|
|
273
|
-
* coordinates themselves makes the choice consistent between the two triangles: whichever way it
|
|
274
|
-
* goes, exactly one of them claims the column.
|
|
275
|
-
*/
|
|
276
|
-
function barycentric2d(px, py, ax, ay, bx, by, cx, cy) {
|
|
277
|
-
const bxr = bx - px;
|
|
278
|
-
const byr = by - py;
|
|
279
|
-
const cxr = cx - px;
|
|
280
|
-
const cyr = cy - py;
|
|
281
|
-
const axr = ax - px;
|
|
282
|
-
const ayr = ay - py;
|
|
283
|
-
const areaA = orientation(bxr, byr, cxr, cyr);
|
|
284
|
-
if (areaA.sign === 0)
|
|
285
|
-
return null;
|
|
286
|
-
const areaB = orientation(cxr, cyr, axr, ayr);
|
|
287
|
-
if (areaB.sign !== areaA.sign)
|
|
288
|
-
return null;
|
|
289
|
-
const areaC = orientation(axr, ayr, bxr, byr);
|
|
290
|
-
if (areaC.sign !== areaA.sign)
|
|
291
|
-
return null;
|
|
292
|
-
const sum = areaA.area + areaB.area + areaC.area;
|
|
293
|
-
if (sum === 0)
|
|
294
|
-
return null;
|
|
295
|
-
return [areaA.area / sum, areaB.area / sum, areaC.area / sum];
|
|
296
|
-
}
|
|
297
|
-
/** Twice the signed area of a triangle with the origin, with zero broken deterministically. */
|
|
298
|
-
function orientation(x1, y1, x2, y2) {
|
|
299
|
-
const area = y1 * x2 - x1 * y2;
|
|
300
|
-
if (area > 0)
|
|
301
|
-
return { sign: 1, area };
|
|
302
|
-
if (area < 0)
|
|
303
|
-
return { sign: -1, area };
|
|
304
|
-
if (y2 > y1)
|
|
305
|
-
return { sign: 1, area };
|
|
306
|
-
if (y2 < y1)
|
|
307
|
-
return { sign: -1, area };
|
|
308
|
-
if (x1 > x2)
|
|
309
|
-
return { sign: 1, area };
|
|
310
|
-
if (x1 < x2)
|
|
311
|
-
return { sign: -1, area };
|
|
312
|
-
return { sign: 0, area };
|
|
313
|
-
}
|
|
314
234
|
/**
|
|
315
235
|
* The distance from a point to the nearest point of a triangle.
|
|
316
236
|
*
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A textured mesh brought down to the triangles its shape needs, at bake time, within a distance.
|
|
3
|
+
*
|
|
4
|
+
* **A bought scene is modelled for a renderer that is not in a hurry.** A courtyard measured here
|
|
5
|
+
* holds two million triangles of curtain, five million of ivy leaf and fifteen hundred a candle: at
|
|
6
|
+
* 4K a triangle of that cloth covers two pixels, and a GPU shades whole 2 × 2 quads, so most of the
|
|
7
|
+
* work is shading pixels nobody sees, in every shadow pass and every probe face as well as the frame.
|
|
8
|
+
*
|
|
9
|
+
* **Quadric error, half-edge collapses, and a hard bound.** Each vertex carries the planes of the
|
|
10
|
+
* faces around it, and a collapse is costed by the mean squared distance the moving vertex's planes
|
|
11
|
+
* are from where it lands, so a flat stretch goes first and a fold holds (Garland and Heckbert). The
|
|
12
|
+
* vertex moves onto a neighbour rather than to an optimal point, so every vertex that is left is one
|
|
13
|
+
* the author placed, with the normal, uv, colour and tangent they gave it: nothing is interpolated,
|
|
14
|
+
* and a texture stays where it was painted. Nothing collapses once the cheapest collapse would move
|
|
15
|
+
* the surface further than `maxError`.
|
|
16
|
+
*
|
|
17
|
+
* **What is held.** A vertex that shares its place with another is on a uv seam or a hard edge, and
|
|
18
|
+
* moving either copy opens the seam, so both stay. An open border simplifies only along itself,
|
|
19
|
+
* costed by planes standing on its edges, so a leaf keeps its outline to the same bound. A collapse
|
|
20
|
+
* that turns a face more than about seventy degrees, folds the texture over, stretches it more than
|
|
21
|
+
* half again, or joins two vertices whose normals part by more than twenty-five degrees is refused,
|
|
22
|
+
* and so is one after which a vertex taken in, by it or any collapse before, no longer finds its own
|
|
23
|
+
* uv on the surface that is left (`collapseMeasures.ts` measures both). A mesh with joints or morph
|
|
24
|
+
* targets comes back as it went in: a vertex there is a promise to a rig.
|
|
25
|
+
*
|
|
26
|
+
* **What it gives up** is a better placed vertex at the same count, which a full-edge collapse to
|
|
27
|
+
* the quadric's minimum would find, at the price of interpolating every attribute. What would make
|
|
28
|
+
* it wrong is a model whose detail is in its geometry rather than its maps at the scale of the bound.
|
|
29
|
+
*/
|
|
30
|
+
import type { MeshData } from '@driftengine/drft';
|
|
31
|
+
export interface SimplifyOptions {
|
|
32
|
+
/** The furthest the surface may move, in the mesh's units: metres, for a baked scene. */
|
|
33
|
+
readonly maxError: number;
|
|
34
|
+
/**
|
|
35
|
+
* The furthest a texture may slide under the surface, in uv units: a texel of a 1,024 map by
|
|
36
|
+
* default. Checked at every vertex a collapse has removed, so it cannot accumulate.
|
|
37
|
+
*/
|
|
38
|
+
readonly maxUvError?: number;
|
|
39
|
+
/** Stop once this share of the triangles is left, whatever the error. 0 by default. */
|
|
40
|
+
readonly minRatio?: number;
|
|
41
|
+
}
|
|
42
|
+
export declare function simplifyMesh(mesh: MeshData, options: SimplifyOptions): MeshData;
|