@driftengine/core 4.5.0 → 4.6.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/CHANGELOG.json +51 -0
- package/README.md +1 -1
- package/dist/geometry/boxCsg.d.ts +28 -0
- package/dist/geometry/boxCsg.js +139 -0
- package/dist/geometry/csg.d.ts +36 -0
- package/dist/geometry/csg.js +329 -0
- package/dist/geometry/meshBuilder.js +13 -0
- package/dist/geometry/meshConcat.js +1 -0
- package/dist/geometry/pixelFont.js +2 -0
- package/dist/geometry/solid.d.ts +61 -0
- package/dist/geometry/solid.js +152 -0
- package/dist/geometry/solidFlat.d.ts +35 -0
- package/dist/geometry/solidFlat.js +428 -0
- package/dist/geometry/solidHarness.d.ts +19 -0
- package/dist/geometry/solidHarness.js +66 -0
- package/dist/geometry/solidNormals.d.ts +17 -0
- package/dist/geometry/solidNormals.js +86 -0
- package/dist/geometry/solidRound.d.ts +41 -0
- package/dist/geometry/solidRound.js +323 -0
- package/dist/geometry/solidWriter.d.ts +16 -0
- package/dist/geometry/solidWriter.js +42 -0
- package/dist/index.d.ts +23 -2
- package/dist/index.js +13 -1
- package/dist/render/backend/api.d.ts +17 -3
- package/dist/render/backend/drawFog.d.ts +26 -0
- package/dist/render/backend/drawFog.js +30 -0
- package/dist/render/backend/materialChanges.d.ts +2 -1
- package/dist/render/backend/materialChanges.js +6 -1
- package/dist/render/backend/webgl2/instanced.d.ts +24 -5
- package/dist/render/backend/webgl2/instanced.js +72 -15
- package/dist/render/backend/webgl2/renderer.d.ts +122 -8
- package/dist/render/backend/webgl2/renderer.js +238 -121
- package/dist/render/backend/webgpu/buffers.d.ts +10 -0
- package/dist/render/backend/webgpu/buffers.js +34 -3
- package/dist/render/backend/webgpu/depthPass.js +5 -1
- package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +4 -1
- package/dist/render/backend/webgpu/flatPass.js +38 -9
- package/dist/render/backend/webgpu/glassTintPass.js +2 -1
- package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
- package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
- package/dist/render/backend/webgpu/instanced.d.ts +11 -1
- package/dist/render/backend/webgpu/instanced.js +21 -3
- package/dist/render/backend/webgpu/latePass.d.ts +6 -2
- package/dist/render/backend/webgpu/latePass.js +11 -4
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +100 -10
- package/dist/render/backend/webgpu/renderer.js +462 -201
- package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +17 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +77 -38
- package/dist/render/driftLight/bake.d.ts +7 -0
- package/dist/render/driftLight/bake.js +61 -56
- package/dist/render/driftLight/denseField.d.ts +46 -0
- package/dist/render/driftLight/denseField.js +109 -0
- package/dist/render/driftLight/lightField.d.ts +10 -5
- package/dist/render/driftLight/lightField.js +25 -19
- package/dist/render/driftLight/presence.d.ts +50 -0
- package/dist/render/driftLight/presence.js +43 -0
- package/dist/render/driftLight/uniforms.d.ts +3 -3
- package/dist/render/driftLight/uniforms.js +5 -6
- package/dist/render/driftLight/worldLightField.d.ts +41 -0
- package/dist/render/driftLight/worldLightField.js +66 -0
- package/dist/render/fixtureAtlas.d.ts +20 -0
- package/dist/render/fixtureAtlas.js +49 -0
- package/dist/render/instanceCull.d.ts +63 -0
- package/dist/render/instanceCull.js +160 -0
- package/dist/render/instances.d.ts +15 -0
- package/dist/render/lightBudget.d.ts +22 -33
- package/dist/render/lightBudget.js +24 -35
- package/dist/render/lightGrid.d.ts +42 -0
- package/dist/render/lightGrid.js +87 -0
- package/dist/render/mesh.d.ts +18 -24
- package/dist/render/mesh.js +51 -68
- package/dist/render/occlusion.d.ts +10 -0
- package/dist/render/occlusion.js +36 -7
- package/dist/render/pointLightSelection.d.ts +7 -0
- package/dist/render/pointLightSelection.js +29 -4
- package/dist/render/rainField.d.ts +13 -0
- package/dist/render/rainField.js +27 -5
- package/dist/render/recon/lateDepth.d.ts +11 -0
- package/dist/render/recon/lateDepth.js +70 -0
- package/dist/render/recon/resolve.d.ts +34 -4
- package/dist/render/recon/resolve.js +161 -20
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +2 -0
- package/dist/render/shaders/depth.js +12 -9
- package/dist/render/shaders/flat/driftLight.d.ts +1 -1
- package/dist/render/shaders/flat/driftLight.js +32 -7
- package/dist/render/shaders/flat/index.js +5 -3
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +75 -23
- package/dist/render/shaders/flat/preamble.js +75 -20
- package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
- package/dist/render/shaders/flat/surfaceEffects.js +152 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +8 -8
- package/dist/render/shaders/generated/depth.wgsl.js +8 -8
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1320 -1320
- package/dist/render/shaders/generated/flat.wgsl.js +1359 -1345
- package/dist/render/shaders/generated/lightVolume.wgsl.js +18 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
- package/dist/render/shaders/generated/rush.wgsl.js +21 -16
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
- package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
- package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/text.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
- package/dist/render/shaders/recon/lateDepth.wgsl.js +38 -13
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
- package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
- package/dist/render/shaders/rush.js +11 -1
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/sdfText.js +3 -2
- package/dist/render/shaders/text.d.ts +1 -1
- package/dist/render/shaders/text.js +18 -9
- package/dist/render/surfaceEffects.d.ts +83 -0
- package/dist/render/surfaceEffects.js +75 -0
- package/dist/render/surfaceScene.d.ts +8 -0
- package/dist/render/surfaceScene.js +8 -0
- package/dist/render/surfaceTexture.d.ts +18 -2
- package/dist/render/surfaceTexture.js +68 -15
- package/dist/render/temporalAa.d.ts +25 -6
- package/dist/render/temporalAa.js +45 -15
- package/dist/render/textureSource.d.ts +30 -0
- package/dist/render/textureSource.js +53 -0
- package/dist/render/vertexDefaults.js +2 -0
- package/dist/world/hlod.d.ts +104 -0
- package/dist/world/hlod.js +214 -0
- package/package.json +6 -6
- package/src/geometry/boxCsg.ts +186 -0
- package/src/geometry/csg.ts +384 -0
- package/src/geometry/meshBuilder.ts +15 -0
- package/src/geometry/meshConcat.ts +1 -0
- package/src/geometry/pixelFont.ts +2 -0
- package/src/geometry/solid.ts +189 -0
- package/src/geometry/solidFlat.ts +522 -0
- package/src/geometry/solidHarness.ts +83 -0
- package/src/geometry/solidNormals.ts +103 -0
- package/src/geometry/solidRound.ts +443 -0
- package/src/geometry/solidWriter.ts +58 -0
- package/src/index.ts +51 -1
- package/src/render/backend/api.ts +21 -2
- package/src/render/backend/drawFog.ts +31 -0
- package/src/render/backend/materialChanges.ts +9 -2
- package/src/render/backend/webgl2/instanced.ts +80 -14
- package/src/render/backend/webgl2/renderer.ts +300 -143
- package/src/render/backend/webgpu/buffers.ts +42 -3
- package/src/render/backend/webgpu/depthPass.ts +5 -1
- package/src/render/backend/webgpu/drawCommand.ts +6 -0
- package/src/render/backend/webgpu/flatPass.ts +41 -11
- package/src/render/backend/webgpu/glassTintPass.ts +2 -1
- package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
- package/src/render/backend/webgpu/instanced.ts +21 -3
- package/src/render/backend/webgpu/latePass.ts +12 -3
- package/src/render/backend/webgpu/renderer.ts +540 -226
- package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
- package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +90 -50
- package/src/render/driftLight/bake.ts +85 -62
- package/src/render/driftLight/denseField.ts +149 -0
- package/src/render/driftLight/lightField.ts +33 -18
- package/src/render/driftLight/presence.ts +69 -0
- package/src/render/driftLight/uniforms.ts +8 -9
- package/src/render/driftLight/worldLightField.ts +83 -0
- package/src/render/fixtureAtlas.ts +63 -0
- package/src/render/instanceCull.ts +205 -0
- package/src/render/instances.ts +16 -0
- package/src/render/lightBudget.ts +25 -36
- package/src/render/lightGrid.ts +130 -0
- package/src/render/mesh.ts +59 -72
- package/src/render/occlusion.ts +51 -7
- package/src/render/pointLightSelection.ts +72 -4
- package/src/render/rainField.ts +39 -5
- package/src/render/recon/lateDepth.ts +83 -0
- package/src/render/recon/resolve.ts +206 -20
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneTarget.ts +4 -0
- package/src/render/shaders/depth.ts +12 -9
- package/src/render/shaders/flat/driftLight.ts +32 -7
- package/src/render/shaders/flat/index.ts +5 -3
- package/src/render/shaders/flat/main.ts +75 -23
- package/src/render/shaders/flat/preamble.ts +75 -20
- package/src/render/shaders/flat/surfaceEffects.ts +152 -0
- package/src/render/shaders/generated/depth.wgsl.ts +8 -8
- package/src/render/shaders/generated/flat.wgsl.ts +1361 -1345
- package/src/render/shaders/generated/lightVolume.wgsl.ts +20 -4
- package/src/render/shaders/generated/rush.wgsl.ts +21 -16
- package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
- package/src/render/shaders/generated/text.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
- package/src/render/shaders/recon/lateDepth.wgsl.ts +38 -13
- package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
- package/src/render/shaders/rush.ts +11 -1
- package/src/render/shaders/sdfText.ts +3 -2
- package/src/render/shaders/text.ts +18 -9
- package/src/render/surfaceEffects.ts +132 -0
- package/src/render/surfaceScene.ts +16 -0
- package/src/render/surfaceTexture.ts +110 -19
- package/src/render/temporalAa.ts +78 -14
- package/src/render/textureSource.ts +72 -0
- package/src/render/vertexDefaults.ts +2 -0
- package/src/world/hlod.ts +290 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boolean operations on closed solids — union, subtraction, intersection — by binary space
|
|
3
|
+
* partitioning.
|
|
4
|
+
*
|
|
5
|
+
* The algorithm is the one Evan Wallace's csg.js made well known, implemented afresh here — named
|
|
6
|
+
* at the point it is used, as `CREDITS.md` asks of a published method: each operand becomes a BSP tree of its own polygons, each tree clips the
|
|
7
|
+
* other's polygons to the region it keeps, and the survivors are rebuilt into one tree. Polygons
|
|
8
|
+
* carry position, normal and texture coordinates, interpolated where a plane splits them, so a cut
|
|
9
|
+
* face keeps the texture of the face it was cut from.
|
|
10
|
+
*
|
|
11
|
+
* **Only what the other operand reaches is split.** Each tree is used for its planes alone: an
|
|
12
|
+
* operand's own polygons are clipped as they arrived, never as its tree's construction cut them,
|
|
13
|
+
* and a polygon whose bounds clear the other operand's is passed through whole — kept or dropped
|
|
14
|
+
* as the operation says of everything outside that operand. The result is the two clipped lists
|
|
15
|
+
* side by side, where the published method rebuilds one tree from them and splits the second list
|
|
16
|
+
* along every plane of the first on the way. Neither changes the surface; together they are what
|
|
17
|
+
* kept a wall cut five times with its end faces at two triangles each instead of eight, and a
|
|
18
|
+
* city's worth of cut towers out of the millions.
|
|
19
|
+
*
|
|
20
|
+
* **What it gives up.** A polygon the other operand does reach is still split along every plane of
|
|
21
|
+
* its tree that crosses it, and nothing is re-merged, so a heavily cut solid grows, and the pieces
|
|
22
|
+
* meet with T-junctions: closed as a surface — its volume exact — but not edge for edge. Welding
|
|
23
|
+
* and simplification are the caller's afterwards. The tree is built iteratively, so a large
|
|
24
|
+
* operand cannot overflow the stack; clipping recurses to the tree's depth, which a solid of a few
|
|
25
|
+
* thousand faces keeps shallow.
|
|
26
|
+
*
|
|
27
|
+
* **Empty operands are answered before any tree is built**, because clipping against an empty tree
|
|
28
|
+
* keeps everything — which is the right answer for a union and the wrong one for an intersection.
|
|
29
|
+
*/
|
|
30
|
+
import type { Solid } from './solid.ts';
|
|
31
|
+
import { emptySolid } from './solid.ts';
|
|
32
|
+
import { SolidWriter } from './solidWriter.ts';
|
|
33
|
+
|
|
34
|
+
/** Distance within which a point counts as on a plane. */
|
|
35
|
+
const EPSILON = 1e-5;
|
|
36
|
+
|
|
37
|
+
interface Vertex {
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
z: number;
|
|
41
|
+
nx: number;
|
|
42
|
+
ny: number;
|
|
43
|
+
nz: number;
|
|
44
|
+
u: number;
|
|
45
|
+
v: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface Plane {
|
|
49
|
+
nx: number;
|
|
50
|
+
ny: number;
|
|
51
|
+
nz: number;
|
|
52
|
+
w: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface Polygon {
|
|
56
|
+
vertices: Vertex[];
|
|
57
|
+
plane: Plane;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const COPLANAR = 0;
|
|
61
|
+
const FRONT = 1;
|
|
62
|
+
const BACK = 2;
|
|
63
|
+
const SPANNING = 3;
|
|
64
|
+
|
|
65
|
+
function planeFrom(a: Vertex, b: Vertex, c: Vertex): Plane | null {
|
|
66
|
+
const ux = b.x - a.x;
|
|
67
|
+
const uy = b.y - a.y;
|
|
68
|
+
const uz = b.z - a.z;
|
|
69
|
+
const vx = c.x - a.x;
|
|
70
|
+
const vy = c.y - a.y;
|
|
71
|
+
const vz = c.z - a.z;
|
|
72
|
+
let nx = uy * vz - uz * vy;
|
|
73
|
+
let ny = uz * vx - ux * vz;
|
|
74
|
+
let nz = ux * vy - uy * vx;
|
|
75
|
+
const len = Math.hypot(nx, ny, nz);
|
|
76
|
+
if (len < 1e-12) return null;
|
|
77
|
+
nx /= len;
|
|
78
|
+
ny /= len;
|
|
79
|
+
nz /= len;
|
|
80
|
+
return { nx, ny, nz, w: nx * a.x + ny * a.y + nz * a.z };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function flipPolygon(p: Polygon): Polygon {
|
|
84
|
+
const vertices = p.vertices
|
|
85
|
+
.slice()
|
|
86
|
+
.reverse()
|
|
87
|
+
.map((v) => ({ ...v, nx: -v.nx, ny: -v.ny, nz: -v.nz }));
|
|
88
|
+
return { vertices, plane: { nx: -p.plane.nx, ny: -p.plane.ny, nz: -p.plane.nz, w: -p.plane.w } };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function lerp(a: Vertex, b: Vertex, t: number): Vertex {
|
|
92
|
+
let nx = a.nx + (b.nx - a.nx) * t;
|
|
93
|
+
let ny = a.ny + (b.ny - a.ny) * t;
|
|
94
|
+
let nz = a.nz + (b.nz - a.nz) * t;
|
|
95
|
+
const len = Math.hypot(nx, ny, nz) || 1;
|
|
96
|
+
nx /= len;
|
|
97
|
+
ny /= len;
|
|
98
|
+
nz /= len;
|
|
99
|
+
return {
|
|
100
|
+
x: a.x + (b.x - a.x) * t,
|
|
101
|
+
y: a.y + (b.y - a.y) * t,
|
|
102
|
+
z: a.z + (b.z - a.z) * t,
|
|
103
|
+
nx,
|
|
104
|
+
ny,
|
|
105
|
+
nz,
|
|
106
|
+
u: a.u + (b.u - a.u) * t,
|
|
107
|
+
v: a.v + (b.v - a.v) * t,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Sort `polygon` against `plane` into the four lists, splitting it where it spans. A coplanar
|
|
113
|
+
* polygon goes to the front list when it faces the same way as the plane, the back one otherwise.
|
|
114
|
+
*/
|
|
115
|
+
function split(
|
|
116
|
+
plane: Plane,
|
|
117
|
+
polygon: Polygon,
|
|
118
|
+
coplanarFront: Polygon[],
|
|
119
|
+
coplanarBack: Polygon[],
|
|
120
|
+
front: Polygon[],
|
|
121
|
+
back: Polygon[],
|
|
122
|
+
): void {
|
|
123
|
+
let kind = 0;
|
|
124
|
+
const types: number[] = [];
|
|
125
|
+
for (const v of polygon.vertices) {
|
|
126
|
+
const t = plane.nx * v.x + plane.ny * v.y + plane.nz * v.z - plane.w;
|
|
127
|
+
const type = t < -EPSILON ? BACK : t > EPSILON ? FRONT : COPLANAR;
|
|
128
|
+
kind |= type;
|
|
129
|
+
types.push(type);
|
|
130
|
+
}
|
|
131
|
+
if (kind === COPLANAR) {
|
|
132
|
+
const facing =
|
|
133
|
+
plane.nx * polygon.plane.nx + plane.ny * polygon.plane.ny + plane.nz * polygon.plane.nz;
|
|
134
|
+
(facing > 0 ? coplanarFront : coplanarBack).push(polygon);
|
|
135
|
+
} else if (kind === FRONT) {
|
|
136
|
+
front.push(polygon);
|
|
137
|
+
} else if (kind === BACK) {
|
|
138
|
+
back.push(polygon);
|
|
139
|
+
} else if (kind === SPANNING) {
|
|
140
|
+
const f: Vertex[] = [];
|
|
141
|
+
const b: Vertex[] = [];
|
|
142
|
+
const n = polygon.vertices.length;
|
|
143
|
+
for (let i = 0; i < n; i++) {
|
|
144
|
+
const j = (i + 1) % n;
|
|
145
|
+
const ti = types[i] ?? COPLANAR;
|
|
146
|
+
const tj = types[j] ?? COPLANAR;
|
|
147
|
+
const vi = polygon.vertices[i];
|
|
148
|
+
const vj = polygon.vertices[j];
|
|
149
|
+
if (!vi || !vj) continue;
|
|
150
|
+
if (ti !== BACK) f.push(vi);
|
|
151
|
+
if (ti !== FRONT) b.push(vi);
|
|
152
|
+
if ((ti | tj) === SPANNING) {
|
|
153
|
+
const di = plane.nx * vi.x + plane.ny * vi.y + plane.nz * vi.z;
|
|
154
|
+
const dj = plane.nx * vj.x + plane.ny * vj.y + plane.nz * vj.z;
|
|
155
|
+
const t = (plane.w - di) / (dj - di);
|
|
156
|
+
const mid = lerp(vi, vj, t);
|
|
157
|
+
f.push(mid);
|
|
158
|
+
b.push({ ...mid });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (f.length >= 3) front.push({ vertices: f, plane: polygon.plane });
|
|
162
|
+
if (b.length >= 3) back.push({ vertices: b, plane: polygon.plane });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** A BSP tree of an operand's planes. Its polygons only choose the planes and are not kept. */
|
|
167
|
+
class Node {
|
|
168
|
+
plane: Plane | null = null;
|
|
169
|
+
front: Node | null = null;
|
|
170
|
+
back: Node | null = null;
|
|
171
|
+
|
|
172
|
+
/** Add `polygons`' planes to the tree, splitting down it, without recursion. */
|
|
173
|
+
build(polygons: Polygon[]): void {
|
|
174
|
+
const work: [Node, Polygon[]][] = [[this, polygons]];
|
|
175
|
+
const coplanar: Polygon[] = [];
|
|
176
|
+
while (work.length > 0) {
|
|
177
|
+
const item = work.pop();
|
|
178
|
+
if (!item) break;
|
|
179
|
+
const [node, list] = item;
|
|
180
|
+
if (list.length === 0) continue;
|
|
181
|
+
const first = list[0];
|
|
182
|
+
if (!node.plane && first) node.plane = { ...first.plane };
|
|
183
|
+
const plane = node.plane;
|
|
184
|
+
if (!plane) continue;
|
|
185
|
+
const f: Polygon[] = [];
|
|
186
|
+
const b: Polygon[] = [];
|
|
187
|
+
for (const p of list) split(plane, p, coplanar, coplanar, f, b);
|
|
188
|
+
coplanar.length = 0;
|
|
189
|
+
if (f.length > 0) {
|
|
190
|
+
node.front ??= new Node();
|
|
191
|
+
work.push([node.front, f]);
|
|
192
|
+
}
|
|
193
|
+
if (b.length > 0) {
|
|
194
|
+
node.back ??= new Node();
|
|
195
|
+
work.push([node.back, b]);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Turn the tree inside out: every plane reversed, front and back swapped. */
|
|
201
|
+
invert(): Node {
|
|
202
|
+
const stack: Node[] = [this];
|
|
203
|
+
while (stack.length > 0) {
|
|
204
|
+
const node = stack.pop();
|
|
205
|
+
if (!node) break;
|
|
206
|
+
if (node.plane) {
|
|
207
|
+
node.plane = {
|
|
208
|
+
nx: -node.plane.nx,
|
|
209
|
+
ny: -node.plane.ny,
|
|
210
|
+
nz: -node.plane.nz,
|
|
211
|
+
w: -node.plane.w,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const f = node.front;
|
|
215
|
+
node.front = node.back;
|
|
216
|
+
node.back = f;
|
|
217
|
+
if (node.front) stack.push(node.front);
|
|
218
|
+
if (node.back) stack.push(node.back);
|
|
219
|
+
}
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** The parts of `polygons` outside this tree's solid. */
|
|
224
|
+
clipPolygons(polygons: Polygon[]): Polygon[] {
|
|
225
|
+
if (!this.plane) return polygons.slice();
|
|
226
|
+
let f: Polygon[] = [];
|
|
227
|
+
let b: Polygon[] = [];
|
|
228
|
+
for (const p of polygons) split(this.plane, p, f, b, f, b);
|
|
229
|
+
if (this.front) f = this.front.clipPolygons(f);
|
|
230
|
+
b = this.back ? this.back.clipPolygons(b) : [];
|
|
231
|
+
return f.concat(b);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
interface Bounds {
|
|
236
|
+
min: [number, number, number];
|
|
237
|
+
max: [number, number, number];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function boundsOf(polygons: readonly Polygon[]): Bounds {
|
|
241
|
+
const min: [number, number, number] = [Infinity, Infinity, Infinity];
|
|
242
|
+
const max: [number, number, number] = [-Infinity, -Infinity, -Infinity];
|
|
243
|
+
for (const p of polygons) {
|
|
244
|
+
for (const v of p.vertices) {
|
|
245
|
+
if (v.x < min[0]) min[0] = v.x;
|
|
246
|
+
if (v.y < min[1]) min[1] = v.y;
|
|
247
|
+
if (v.z < min[2]) min[2] = v.z;
|
|
248
|
+
if (v.x > max[0]) max[0] = v.x;
|
|
249
|
+
if (v.y > max[1]) max[1] = v.y;
|
|
250
|
+
if (v.z > max[2]) max[2] = v.z;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return { min, max };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* `polygons` split into those that may meet `bounds` and those clear of it by more than the plane
|
|
258
|
+
* tolerance on some axis — which are outside the solid `bounds` encloses, with no tree to ask.
|
|
259
|
+
*/
|
|
260
|
+
function partition(polygons: readonly Polygon[], bounds: Bounds): [Polygon[], Polygon[]] {
|
|
261
|
+
const near: Polygon[] = [];
|
|
262
|
+
const clear: Polygon[] = [];
|
|
263
|
+
const { min, max } = bounds;
|
|
264
|
+
for (const p of polygons) {
|
|
265
|
+
let lx = Infinity;
|
|
266
|
+
let ly = Infinity;
|
|
267
|
+
let lz = Infinity;
|
|
268
|
+
let hx = -Infinity;
|
|
269
|
+
let hy = -Infinity;
|
|
270
|
+
let hz = -Infinity;
|
|
271
|
+
for (const v of p.vertices) {
|
|
272
|
+
lx = Math.min(lx, v.x);
|
|
273
|
+
ly = Math.min(ly, v.y);
|
|
274
|
+
lz = Math.min(lz, v.z);
|
|
275
|
+
hx = Math.max(hx, v.x);
|
|
276
|
+
hy = Math.max(hy, v.y);
|
|
277
|
+
hz = Math.max(hz, v.z);
|
|
278
|
+
}
|
|
279
|
+
const apart =
|
|
280
|
+
hx < min[0] - EPSILON ||
|
|
281
|
+
hy < min[1] - EPSILON ||
|
|
282
|
+
hz < min[2] - EPSILON ||
|
|
283
|
+
lx > max[0] + EPSILON ||
|
|
284
|
+
ly > max[1] + EPSILON ||
|
|
285
|
+
lz > max[2] + EPSILON;
|
|
286
|
+
(apart ? clear : near).push(p);
|
|
287
|
+
}
|
|
288
|
+
return [near, clear];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const flipAll = (polygons: readonly Polygon[]): Polygon[] => polygons.map(flipPolygon);
|
|
292
|
+
|
|
293
|
+
function toPolygons(solid: Solid): Polygon[] {
|
|
294
|
+
const out: Polygon[] = [];
|
|
295
|
+
const p = solid.positions;
|
|
296
|
+
const n = solid.normals;
|
|
297
|
+
const uv = solid.uvs;
|
|
298
|
+
const vertex = (i: number): Vertex => ({
|
|
299
|
+
x: p[i * 3] ?? 0,
|
|
300
|
+
y: p[i * 3 + 1] ?? 0,
|
|
301
|
+
z: p[i * 3 + 2] ?? 0,
|
|
302
|
+
nx: n[i * 3] ?? 0,
|
|
303
|
+
ny: n[i * 3 + 1] ?? 0,
|
|
304
|
+
nz: n[i * 3 + 2] ?? 0,
|
|
305
|
+
u: uv[i * 2] ?? 0,
|
|
306
|
+
v: uv[i * 2 + 1] ?? 0,
|
|
307
|
+
});
|
|
308
|
+
for (let t = 0; t < solid.indices.length; t += 3) {
|
|
309
|
+
const a = vertex(solid.indices[t] ?? 0);
|
|
310
|
+
const b = vertex(solid.indices[t + 1] ?? 0);
|
|
311
|
+
const c = vertex(solid.indices[t + 2] ?? 0);
|
|
312
|
+
const plane = planeFrom(a, b, c);
|
|
313
|
+
if (plane) out.push({ vertices: [a, b, c], plane });
|
|
314
|
+
}
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Polygons back to a solid, each fanned from its first corner — they are convex. */
|
|
319
|
+
function fromPolygons(polygons: Polygon[]): Solid {
|
|
320
|
+
const out = new SolidWriter();
|
|
321
|
+
for (const polygon of polygons) {
|
|
322
|
+
const base = out.vertexCount;
|
|
323
|
+
for (const v of polygon.vertices) out.vertex(v.x, v.y, v.z, v.nx, v.ny, v.nz, v.u, v.v);
|
|
324
|
+
for (let i = 1; i + 1 < polygon.vertices.length; i++)
|
|
325
|
+
out.triangle(base, base + i, base + i + 1);
|
|
326
|
+
}
|
|
327
|
+
return out.finish();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function tree(polygons: Polygon[]): Node {
|
|
331
|
+
const node = new Node();
|
|
332
|
+
node.build(polygons);
|
|
333
|
+
return node;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const isEmpty = (s: Solid): boolean => s.indices.length === 0;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Both operands' polygons, each split by the other's bounds. The three operations below are the
|
|
340
|
+
* published method's steps written over these lists, with the clear polygons settled by the rule
|
|
341
|
+
* each operation applies to everything outside the other operand.
|
|
342
|
+
*/
|
|
343
|
+
function operands(a: Solid, b: Solid) {
|
|
344
|
+
const pa = toPolygons(a);
|
|
345
|
+
const pb = toPolygons(b);
|
|
346
|
+
const [aNear, aClear] = partition(pa, boundsOf(pb));
|
|
347
|
+
const [bNear, bClear] = partition(pb, boundsOf(pa));
|
|
348
|
+
return { pa, pb, aNear, aClear, bNear, bClear };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Everything inside either solid. */
|
|
352
|
+
export function solidUnion(a: Solid, b: Solid): Solid {
|
|
353
|
+
if (isEmpty(b)) return a;
|
|
354
|
+
if (isEmpty(a)) return b;
|
|
355
|
+
const { pa, pb, aNear, aClear, bNear, bClear } = operands(a, b);
|
|
356
|
+
const ta = tree(pa);
|
|
357
|
+
const aKept = tree(pb).clipPolygons(aNear);
|
|
358
|
+
/* B outside A, then without the faces lying on A's facing the same way, which A already has. */
|
|
359
|
+
const bKept = flipAll(ta.clipPolygons(flipAll(ta.clipPolygons(bNear))));
|
|
360
|
+
return fromPolygons([...aClear, ...aKept, ...bClear, ...bKept]);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Everything inside `a` and outside `b`. */
|
|
364
|
+
export function solidSubtract(a: Solid, b: Solid): Solid {
|
|
365
|
+
if (isEmpty(a)) return emptySolid();
|
|
366
|
+
if (isEmpty(b)) return a;
|
|
367
|
+
const { pa, pb, aNear, aClear, bNear } = operands(a, b);
|
|
368
|
+
const outside = tree(pa).invert();
|
|
369
|
+
const aKept = flipAll(tree(pb).clipPolygons(flipAll(aNear)));
|
|
370
|
+
/* B inside A, turned to face into the cavity it leaves. */
|
|
371
|
+
const bKept = outside.clipPolygons(flipAll(outside.clipPolygons(bNear)));
|
|
372
|
+
return fromPolygons([...aClear, ...aKept, ...bKept]);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Everything inside both solids. */
|
|
376
|
+
export function solidIntersect(a: Solid, b: Solid): Solid {
|
|
377
|
+
if (isEmpty(a) || isEmpty(b)) return emptySolid();
|
|
378
|
+
const { pa, pb, aNear, bNear } = operands(a, b);
|
|
379
|
+
const outsideA = tree(pa).invert();
|
|
380
|
+
const outsideB = tree(pb).invert();
|
|
381
|
+
const aKept = flipAll(outsideB.clipPolygons(flipAll(aNear)));
|
|
382
|
+
const bKept = flipAll(outsideA.clipPolygons(flipAll(outsideA.clipPolygons(bNear))));
|
|
383
|
+
return fromPolygons([...aKept, ...bKept]);
|
|
384
|
+
}
|
|
@@ -128,6 +128,7 @@ export class MeshBuilder {
|
|
|
128
128
|
* inverse transpose, and nothing here has wanted that yet.
|
|
129
129
|
*/
|
|
130
130
|
addMesh(mesh: MeshData, x: number, y: number, z: number, scale = 1): this {
|
|
131
|
+
refuseLayers(mesh);
|
|
131
132
|
const base = this.vertexCount;
|
|
132
133
|
const count = mesh.positions.length / 3;
|
|
133
134
|
for (let i = 0; i < count; i++) {
|
|
@@ -199,6 +200,7 @@ export class MeshBuilder {
|
|
|
199
200
|
forward: Vec3,
|
|
200
201
|
scale = 1,
|
|
201
202
|
): this {
|
|
203
|
+
refuseLayers(mesh);
|
|
202
204
|
assertOrthonormal(right, up, forward);
|
|
203
205
|
const base = this.vertexCount;
|
|
204
206
|
const count = mesh.positions.length / 3;
|
|
@@ -1399,3 +1401,16 @@ const CORNERS = [
|
|
|
1399
1401
|
[1, 1],
|
|
1400
1402
|
[-1, 1],
|
|
1401
1403
|
] as const;
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* A builder has no layer channel, so merging a layered mesh would keep its texture coordinates and
|
|
1407
|
+
* silently put every face on layer 0 — the first image of the array, which reads as the wrong
|
|
1408
|
+
* facade everywhere rather than as an error. Refused instead; `concatMeshes` joins layered meshes.
|
|
1409
|
+
*/
|
|
1410
|
+
function refuseLayers(mesh: MeshData): void {
|
|
1411
|
+
if (mesh.layers === undefined) return;
|
|
1412
|
+
throw new Error(
|
|
1413
|
+
'MeshBuilder: this mesh names texture-array layers, which a builder does not carry — merging ' +
|
|
1414
|
+
'it here would put every face on layer 0. Join layered meshes with concatMeshes instead.',
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
@@ -67,6 +67,8 @@ const GLYPHS: Record<string, readonly number[]> = {
|
|
|
67
67
|
"'": [0b00100, 0b00100, 0, 0, 0, 0, 0],
|
|
68
68
|
'(': [0b00010, 0b00100, 0b01000, 0b01000, 0b01000, 0b00100, 0b00010],
|
|
69
69
|
')': [0b01000, 0b00100, 0b00010, 0b00010, 0b00010, 0b00100, 0b01000],
|
|
70
|
+
'[': [0b01110, 0b01000, 0b01000, 0b01000, 0b01000, 0b01000, 0b01110],
|
|
71
|
+
']': [0b01110, 0b00010, 0b00010, 0b00010, 0b00010, 0b00010, 0b01110],
|
|
70
72
|
'%': [0b11001, 0b11010, 0b00010, 0b00100, 0b01000, 0b01011, 0b10011],
|
|
71
73
|
'·': [0, 0, 0, 0b01100, 0b01100, 0, 0],
|
|
72
74
|
'×': [0, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001, 0],
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A solid: an indexed triangle surface with normals and texture coordinates, and the operations
|
|
3
|
+
* every procedural builder needs on one — place it, join several, measure it, and hand it to the
|
|
4
|
+
* renderer as a mesh.
|
|
5
|
+
*
|
|
6
|
+
* **Why a type of its own rather than `MeshData`.** A mesh carries a dozen per-vertex channels a
|
|
7
|
+
* renderer reads (colour, emissive, roughness, grain, joints…) and a solid is what exists before any
|
|
8
|
+
* of them are decided: shape alone. Booleans, smoothing and transforms are defined on shape, and
|
|
9
|
+
* dragging eleven channels through a boolean would mean deciding how each one interpolates across a
|
|
10
|
+
* cut. The caller paints a finished solid once, with `solidToMesh` or its own writer.
|
|
11
|
+
*
|
|
12
|
+
* **Named `Solid` because `*Shape` is taken**: the physics colliders the core barrel re-exports are
|
|
13
|
+
* `boxShape`, `cylinderShape` and the rest, and a second meaning for the word would read as the
|
|
14
|
+
* same thing. Every primitive here is closed except `solidQuad`, a single-sided plane.
|
|
15
|
+
*
|
|
16
|
+
* Built at load or bake time, never per frame, so these functions allocate their results.
|
|
17
|
+
*/
|
|
18
|
+
import type { MeshData } from '../render/mesh.ts';
|
|
19
|
+
import type { Vec3 } from '../math/color.ts';
|
|
20
|
+
|
|
21
|
+
export interface Solid {
|
|
22
|
+
/** Three floats a vertex. */
|
|
23
|
+
readonly positions: Float32Array;
|
|
24
|
+
/** Three floats a vertex, unit length, outward. */
|
|
25
|
+
readonly normals: Float32Array;
|
|
26
|
+
/**
|
|
27
|
+
* Two floats a vertex, parametric: 0..1 across each face of a box, around and along a round
|
|
28
|
+
* primitive. Tiling in metres is the caller's, who knows the sizes.
|
|
29
|
+
*/
|
|
30
|
+
readonly uvs: Float32Array;
|
|
31
|
+
/** Counter-clockwise triangles seen from outside. */
|
|
32
|
+
readonly indices: Uint32Array;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function emptySolid(): Solid {
|
|
36
|
+
return {
|
|
37
|
+
positions: new Float32Array(0),
|
|
38
|
+
normals: new Float32Array(0),
|
|
39
|
+
uvs: new Float32Array(0),
|
|
40
|
+
indices: new Uint32Array(0),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `solid` moved by a column-major 4×4 model matrix.
|
|
46
|
+
*
|
|
47
|
+
* **Normals go through the inverse transpose of the upper 3×3**, then are renormalised. Rotating
|
|
48
|
+
* them by the matrix itself is right only for rotations and uniform scales; under a non-uniform one
|
|
49
|
+
* — a unit cylinder scaled to a post, a sphere to an ellipsoid — it leaves every normal leaning
|
|
50
|
+
* toward the stretched axis, and the surface is lit as if it faced somewhere else, which reads as a
|
|
51
|
+
* shading bug rather than a maths one.
|
|
52
|
+
*
|
|
53
|
+
* **A mirror reverses the winding**, because a negative determinant turns every triangle inside
|
|
54
|
+
* out; without the reversal a mirrored solid is culled from outside and its volume goes negative.
|
|
55
|
+
*/
|
|
56
|
+
export function transformSolid(solid: Solid, model: ArrayLike<number>): Solid {
|
|
57
|
+
const m = (i: number): number => model[i] ?? 0;
|
|
58
|
+
const a = m(0);
|
|
59
|
+
const b = m(4);
|
|
60
|
+
const c = m(8);
|
|
61
|
+
const d = m(1);
|
|
62
|
+
const e = m(5);
|
|
63
|
+
const f = m(9);
|
|
64
|
+
const g = m(2);
|
|
65
|
+
const h = m(6);
|
|
66
|
+
const k = m(10);
|
|
67
|
+
/* Cofactors of the upper 3×3 in row-major (a b c / d e f / g h k): the inverse transpose up to
|
|
68
|
+
the determinant's scale, which renormalising removes — except its sign, kept below. */
|
|
69
|
+
const c00 = e * k - f * h;
|
|
70
|
+
const c01 = f * g - d * k;
|
|
71
|
+
const c02 = d * h - e * g;
|
|
72
|
+
const c10 = c * h - b * k;
|
|
73
|
+
const c11 = a * k - c * g;
|
|
74
|
+
const c12 = b * g - a * h;
|
|
75
|
+
const c20 = b * f - c * e;
|
|
76
|
+
const c21 = c * d - a * f;
|
|
77
|
+
const c22 = a * e - b * d;
|
|
78
|
+
const det = a * c00 + b * c01 + c * c02;
|
|
79
|
+
const sign = det < 0 ? -1 : 1;
|
|
80
|
+
|
|
81
|
+
const count = solid.positions.length / 3;
|
|
82
|
+
const positions = new Float32Array(count * 3);
|
|
83
|
+
const normals = new Float32Array(count * 3);
|
|
84
|
+
for (let v = 0; v < count; v++) {
|
|
85
|
+
const x = solid.positions[v * 3] ?? 0;
|
|
86
|
+
const y = solid.positions[v * 3 + 1] ?? 0;
|
|
87
|
+
const z = solid.positions[v * 3 + 2] ?? 0;
|
|
88
|
+
positions[v * 3] = a * x + b * y + c * z + m(12);
|
|
89
|
+
positions[v * 3 + 1] = d * x + e * y + f * z + m(13);
|
|
90
|
+
positions[v * 3 + 2] = g * x + h * y + k * z + m(14);
|
|
91
|
+
const nx = solid.normals[v * 3] ?? 0;
|
|
92
|
+
const ny = solid.normals[v * 3 + 1] ?? 0;
|
|
93
|
+
const nz = solid.normals[v * 3 + 2] ?? 0;
|
|
94
|
+
const tx = (c00 * nx + c01 * ny + c02 * nz) * sign;
|
|
95
|
+
const ty = (c10 * nx + c11 * ny + c12 * nz) * sign;
|
|
96
|
+
const tz = (c20 * nx + c21 * ny + c22 * nz) * sign;
|
|
97
|
+
const len = Math.hypot(tx, ty, tz) || 1;
|
|
98
|
+
normals[v * 3] = tx / len;
|
|
99
|
+
normals[v * 3 + 1] = ty / len;
|
|
100
|
+
normals[v * 3 + 2] = tz / len;
|
|
101
|
+
}
|
|
102
|
+
let indices = solid.indices;
|
|
103
|
+
if (det < 0) {
|
|
104
|
+
indices = new Uint32Array(solid.indices.length);
|
|
105
|
+
for (let t = 0; t < indices.length; t += 3) {
|
|
106
|
+
indices[t] = solid.indices[t] ?? 0;
|
|
107
|
+
indices[t + 1] = solid.indices[t + 2] ?? 0;
|
|
108
|
+
indices[t + 2] = solid.indices[t + 1] ?? 0;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return { positions, normals, uvs: solid.uvs, indices };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Several solids as one, each one's indices offset past the vertices before it. */
|
|
115
|
+
export function mergeSolids(solids: readonly Solid[]): Solid {
|
|
116
|
+
if (solids.length === 1 && solids[0] !== undefined) return solids[0];
|
|
117
|
+
let vertices = 0;
|
|
118
|
+
let triangles = 0;
|
|
119
|
+
for (const s of solids) {
|
|
120
|
+
vertices += s.positions.length / 3;
|
|
121
|
+
triangles += s.indices.length;
|
|
122
|
+
}
|
|
123
|
+
const positions = new Float32Array(vertices * 3);
|
|
124
|
+
const normals = new Float32Array(vertices * 3);
|
|
125
|
+
const uvs = new Float32Array(vertices * 2);
|
|
126
|
+
const indices = new Uint32Array(triangles);
|
|
127
|
+
let v = 0;
|
|
128
|
+
let t = 0;
|
|
129
|
+
for (const s of solids) {
|
|
130
|
+
positions.set(s.positions, v * 3);
|
|
131
|
+
normals.set(s.normals, v * 3);
|
|
132
|
+
uvs.set(s.uvs, v * 2);
|
|
133
|
+
for (let i = 0; i < s.indices.length; i++) indices[t + i] = (s.indices[i] ?? 0) + v;
|
|
134
|
+
v += s.positions.length / 3;
|
|
135
|
+
t += s.indices.length;
|
|
136
|
+
}
|
|
137
|
+
return { positions, normals, uvs, indices };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The enclosed volume by the divergence theorem: the sum of each triangle's signed tetrahedron
|
|
142
|
+
* with the origin. Positive for a closed, outward-wound solid; meaningless for an open one.
|
|
143
|
+
*
|
|
144
|
+
* Exact for any closed surface, T-junctions included, which is why the CSG tests can use it on a
|
|
145
|
+
* boolean's output where an edge-pairing check cannot.
|
|
146
|
+
*/
|
|
147
|
+
export function solidVolume(solid: Solid): number {
|
|
148
|
+
const p = solid.positions;
|
|
149
|
+
const idx = solid.indices;
|
|
150
|
+
let sum = 0;
|
|
151
|
+
for (let t = 0; t < idx.length; t += 3) {
|
|
152
|
+
const a = (idx[t] ?? 0) * 3;
|
|
153
|
+
const b = (idx[t + 1] ?? 0) * 3;
|
|
154
|
+
const c = (idx[t + 2] ?? 0) * 3;
|
|
155
|
+
const ax = p[a] ?? 0;
|
|
156
|
+
const ay = p[a + 1] ?? 0;
|
|
157
|
+
const az = p[a + 2] ?? 0;
|
|
158
|
+
const bx = p[b] ?? 0;
|
|
159
|
+
const by = p[b + 1] ?? 0;
|
|
160
|
+
const bz = p[b + 2] ?? 0;
|
|
161
|
+
const cx = p[c] ?? 0;
|
|
162
|
+
const cy = p[c + 1] ?? 0;
|
|
163
|
+
const cz = p[c + 2] ?? 0;
|
|
164
|
+
sum += ax * (by * cz - bz * cy) - ay * (bx * cz - bz * cx) + az * (bx * cy - by * cx);
|
|
165
|
+
}
|
|
166
|
+
return sum / 6;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* A solid painted one colour, as a mesh the renderer draws. `emissive` is the usual scalar: the
|
|
171
|
+
* surface's own colour scaled, gated by the environment's night factor like every emissive.
|
|
172
|
+
*/
|
|
173
|
+
export function solidToMesh(solid: Solid, color: Vec3, emissive = 0): MeshData {
|
|
174
|
+
const count = solid.positions.length / 3;
|
|
175
|
+
const colors = new Float32Array(count * 3);
|
|
176
|
+
for (let v = 0; v < count; v++) {
|
|
177
|
+
colors[v * 3] = color[0];
|
|
178
|
+
colors[v * 3 + 1] = color[1];
|
|
179
|
+
colors[v * 3 + 2] = color[2];
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
positions: solid.positions,
|
|
183
|
+
normals: solid.normals,
|
|
184
|
+
colors,
|
|
185
|
+
emissive: new Float32Array(count).fill(emissive),
|
|
186
|
+
uvs: solid.uvs,
|
|
187
|
+
indices: solid.indices,
|
|
188
|
+
};
|
|
189
|
+
}
|