@aethervtt/shard-structure 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/bench.d.ts +15 -0
- package/dist/bench.js +112 -0
- package/dist/bricks.d.ts +27 -0
- package/dist/bricks.js +98 -0
- package/dist/compile.d.ts +292 -0
- package/dist/compile.js +1602 -0
- package/dist/components.d.ts +156 -0
- package/dist/components.js +294 -0
- package/dist/contact-shade.d.ts +4 -0
- package/dist/contact-shade.js +76 -0
- package/dist/contact.d.ts +65 -0
- package/dist/contact.js +343 -0
- package/dist/curve.d.ts +50 -0
- package/dist/curve.js +224 -0
- package/dist/fixtures.d.ts +77 -0
- package/dist/fixtures.js +327 -0
- package/dist/geometry.d.ts +178 -0
- package/dist/geometry.js +926 -0
- package/dist/harness.d.ts +27 -0
- package/dist/harness.js +74 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +189 -0
- package/dist/interior-plugin.d.ts +206 -0
- package/dist/interior-plugin.js +988 -0
- package/dist/interior.d.ts +159 -0
- package/dist/interior.js +645 -0
- package/dist/planar.d.ts +61 -0
- package/dist/planar.js +105 -0
- package/dist/plugin.d.ts +10 -0
- package/dist/plugin.js +119 -0
- package/package.json +49 -4
package/dist/fixtures.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { ChildOf } from "@aethervtt/shard-core";
|
|
2
|
+
import { cylinder, sphere } from "@aethervtt/shard-mesh";
|
|
3
|
+
import { createMirror } from "@aethervtt/shard-mirror";
|
|
4
|
+
import {
|
|
5
|
+
GpuAssetsResource,
|
|
6
|
+
MaterialAsset,
|
|
7
|
+
Materials,
|
|
8
|
+
Mesh3d,
|
|
9
|
+
Meshes,
|
|
10
|
+
MeshMaterial
|
|
11
|
+
} from "@aethervtt/shard-render";
|
|
12
|
+
import { packMetallicRoughness, Texture, Textures } from "@aethervtt/shard-texture";
|
|
13
|
+
import { Transform } from "@aethervtt/shard-transform";
|
|
14
|
+
import {
|
|
15
|
+
LIMITS,
|
|
16
|
+
maxScene,
|
|
17
|
+
shadowStress
|
|
18
|
+
} from "../fixtures/structure/index.mjs";
|
|
19
|
+
import { Floor, Opening, Wall } from "./components.js";
|
|
20
|
+
const PX_TO_WORLD = 1.5 / 70;
|
|
21
|
+
function linear(hex) {
|
|
22
|
+
const c = (i) => {
|
|
23
|
+
const v = Number.parseInt(hex.slice(1 + i * 2, 3 + i * 2), 16) / 255;
|
|
24
|
+
return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
25
|
+
};
|
|
26
|
+
return [c(0), c(1), c(2), 1];
|
|
27
|
+
}
|
|
28
|
+
function sceneMaterialValue(doc, pxToWorld, texture, packed = null) {
|
|
29
|
+
const repeat = doc.repeat ?? { x: 1 / pxToWorld, y: 1 / pxToWorld };
|
|
30
|
+
const slot = (ref) => ref && {
|
|
31
|
+
texture: ref,
|
|
32
|
+
scale: [1 / (repeat.x * pxToWorld), 1 / (repeat.y * pxToWorld)],
|
|
33
|
+
rotation: (doc.rotation ?? 0) * Math.PI / 180,
|
|
34
|
+
wrap: doc.wrap === "mirrored-repeat" ? "mirror" : "repeat"
|
|
35
|
+
};
|
|
36
|
+
const value = {
|
|
37
|
+
baseColor: linear(doc.tint),
|
|
38
|
+
roughness: doc.roughness,
|
|
39
|
+
metallic: doc.metalness ?? 0
|
|
40
|
+
};
|
|
41
|
+
const base = doc.baseColorTexture ? texture(doc.baseColorTexture, "color") : null;
|
|
42
|
+
if (base) value.baseColorTexture = slot(base);
|
|
43
|
+
const normal = doc.normal ? texture(doc.normal.texture, "normal") : null;
|
|
44
|
+
if (normal) {
|
|
45
|
+
value.normalTexture = slot(normal);
|
|
46
|
+
value.normalScale = doc.normal.strength;
|
|
47
|
+
}
|
|
48
|
+
const ao = doc.ambientOcclusion ? texture(doc.ambientOcclusion.texture, "data") : null;
|
|
49
|
+
if (ao) {
|
|
50
|
+
value.occlusionTexture = slot(ao);
|
|
51
|
+
value.occlusionStrength = doc.ambientOcclusion.strength;
|
|
52
|
+
}
|
|
53
|
+
if (packed) value.metallicRoughnessTexture = slot(packed);
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
class HostScene {
|
|
57
|
+
world;
|
|
58
|
+
k;
|
|
59
|
+
materials;
|
|
60
|
+
walls;
|
|
61
|
+
openings;
|
|
62
|
+
floors;
|
|
63
|
+
props;
|
|
64
|
+
tokens;
|
|
65
|
+
/** Token root → its disc. */
|
|
66
|
+
discs = /* @__PURE__ */ new Map();
|
|
67
|
+
grid;
|
|
68
|
+
materialRefs = /* @__PURE__ */ new Map();
|
|
69
|
+
propMeshes = /* @__PURE__ */ new Map();
|
|
70
|
+
tokenDocs = /* @__PURE__ */ new Map();
|
|
71
|
+
propDocs = /* @__PURE__ */ new Map();
|
|
72
|
+
discMesh;
|
|
73
|
+
tokenMaterial;
|
|
74
|
+
/** Textures made from the host's images, by ref and use. */
|
|
75
|
+
textureRefs = /* @__PURE__ */ new Map();
|
|
76
|
+
images;
|
|
77
|
+
constructor(world, pxToWorld = PX_TO_WORLD, images) {
|
|
78
|
+
this.world = world;
|
|
79
|
+
this.k = pxToWorld;
|
|
80
|
+
this.images = images;
|
|
81
|
+
const k = pxToWorld;
|
|
82
|
+
const rev = (d) => d.rev;
|
|
83
|
+
const id = (d) => d.id;
|
|
84
|
+
this.materials = createMirror(world, {
|
|
85
|
+
key: id,
|
|
86
|
+
rev,
|
|
87
|
+
spawn: (doc) => {
|
|
88
|
+
const ref = world.resource(Materials).add(new MaterialAsset({}), `host:material/${doc.id}`);
|
|
89
|
+
this.materialRefs.set(doc.id, ref);
|
|
90
|
+
return world.spawn();
|
|
91
|
+
},
|
|
92
|
+
apply: (_e, doc) => {
|
|
93
|
+
const ref = this.materialRefs.get(doc.id);
|
|
94
|
+
world.resource(Materials).get(ref)?.set(this.materialValue(doc));
|
|
95
|
+
},
|
|
96
|
+
despawn: (e, w, key) => {
|
|
97
|
+
const ref = this.materialRefs.get(key);
|
|
98
|
+
if (ref) {
|
|
99
|
+
const material = w.resource(Materials).get(ref);
|
|
100
|
+
if (material) w.tryResource(GpuAssetsResource)?.releaseMaterial(material);
|
|
101
|
+
w.resource(Materials).delete(ref.guid);
|
|
102
|
+
this.materialRefs.delete(key);
|
|
103
|
+
}
|
|
104
|
+
w.despawn(e);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
this.walls = createMirror(world, {
|
|
108
|
+
key: id,
|
|
109
|
+
rev,
|
|
110
|
+
spawn: () => world.spawn(Wall),
|
|
111
|
+
apply: (e, d) => world.set(e, Wall, {
|
|
112
|
+
a: [d.a.x * k, d.a.y * k],
|
|
113
|
+
b: [d.b.x * k, d.b.y * k],
|
|
114
|
+
shape: d.curve?.kind ?? "straight",
|
|
115
|
+
bow: d.curve?.kind === "arc" ? d.curve.bow * k : 0,
|
|
116
|
+
c0: d.curve?.kind === "bezier" ? [d.curve.c0.x * k, d.curve.c0.y * k] : [0, 0],
|
|
117
|
+
c1: d.curve?.kind === "bezier" ? [d.curve.c1.x * k, d.curve.c1.y * k] : [0, 0],
|
|
118
|
+
height: d.height * k,
|
|
119
|
+
thickness: d.thickness * k,
|
|
120
|
+
elevation: d.elevation * k,
|
|
121
|
+
material: this.materialRefs.get(d.materialId) ?? null
|
|
122
|
+
})
|
|
123
|
+
});
|
|
124
|
+
this.openings = createMirror(world, {
|
|
125
|
+
key: id,
|
|
126
|
+
rev,
|
|
127
|
+
spawn: () => world.spawn(Opening),
|
|
128
|
+
apply: (e, d) => world.set(e, Opening, {
|
|
129
|
+
wall: this.walls.entity(d.hostWallId) ?? null,
|
|
130
|
+
kind: d.kind,
|
|
131
|
+
offset: d.offset * k,
|
|
132
|
+
width: d.width * k,
|
|
133
|
+
height: d.height * k,
|
|
134
|
+
sill: d.sill * k,
|
|
135
|
+
frameWidth: d.frameWidth * k,
|
|
136
|
+
frameDepth: d.frameDepth * k,
|
|
137
|
+
frameMaterial: d.frameMaterialId ? this.materialRefs.get(d.frameMaterialId) ?? null : null,
|
|
138
|
+
hinge: d.hinge ?? "start",
|
|
139
|
+
swing: d.swing ?? "left",
|
|
140
|
+
state: d.state ?? "closed",
|
|
141
|
+
sight: d.sight,
|
|
142
|
+
movement: d.movement
|
|
143
|
+
})
|
|
144
|
+
});
|
|
145
|
+
this.floors = createMirror(world, {
|
|
146
|
+
key: id,
|
|
147
|
+
rev,
|
|
148
|
+
spawn: () => world.spawn(Floor),
|
|
149
|
+
apply: (e, d) => world.set(e, Floor, {
|
|
150
|
+
points: d.points.map((p) => [p.x * k, p.y * k]),
|
|
151
|
+
elevation: d.elevation * k,
|
|
152
|
+
material: d.surface.kind === "material" ? this.materialRefs.get(d.surface.materialId) ?? null : null
|
|
153
|
+
})
|
|
154
|
+
});
|
|
155
|
+
this.props = createMirror(world, {
|
|
156
|
+
key: id,
|
|
157
|
+
rev,
|
|
158
|
+
spawn: (d) => world.spawn(
|
|
159
|
+
[Mesh3d, { mesh: this.propMesh(d.asset) }],
|
|
160
|
+
[MeshMaterial, { material: this.materialRefs.values().next().value ?? null }],
|
|
161
|
+
Transform
|
|
162
|
+
),
|
|
163
|
+
apply: (e, d) => {
|
|
164
|
+
this.propDocs.set(e, d);
|
|
165
|
+
this.placeProp(e, d);
|
|
166
|
+
},
|
|
167
|
+
despawn: (e) => {
|
|
168
|
+
this.propDocs.delete(e);
|
|
169
|
+
world.despawn(e);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
this.tokens = createMirror(world, {
|
|
173
|
+
key: id,
|
|
174
|
+
rev,
|
|
175
|
+
spawn: () => {
|
|
176
|
+
const root = world.spawn(Transform);
|
|
177
|
+
const disc = world.spawn(
|
|
178
|
+
[Mesh3d, { mesh: this.disc() }],
|
|
179
|
+
[MeshMaterial, { material: this.tokenMat() }],
|
|
180
|
+
Transform,
|
|
181
|
+
[ChildOf, { parent: root }]
|
|
182
|
+
);
|
|
183
|
+
this.discs.set(root, disc);
|
|
184
|
+
return root;
|
|
185
|
+
},
|
|
186
|
+
apply: (e, d) => {
|
|
187
|
+
this.tokenDocs.set(e, d);
|
|
188
|
+
world.set(e, Transform, { translation: [d.x * k, 0, d.y * k] });
|
|
189
|
+
this.sizeToken(e, d);
|
|
190
|
+
},
|
|
191
|
+
despawn: (e) => {
|
|
192
|
+
this.discs.delete(e);
|
|
193
|
+
this.tokenDocs.delete(e);
|
|
194
|
+
world.despawn(e);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/** A scene material's value: its textures made from the host's images on first use. */
|
|
199
|
+
materialValue(doc) {
|
|
200
|
+
const texture = (ref, usage) => {
|
|
201
|
+
const key = `${usage}:${ref}`;
|
|
202
|
+
let t = this.textureRefs.get(key);
|
|
203
|
+
if (!t) {
|
|
204
|
+
const image = this.images?.(ref);
|
|
205
|
+
if (!image) return null;
|
|
206
|
+
t = this.world.resource(Textures).add(
|
|
207
|
+
Texture.create({
|
|
208
|
+
width: image.width,
|
|
209
|
+
height: image.height,
|
|
210
|
+
usage,
|
|
211
|
+
mips: [image.data],
|
|
212
|
+
mipmaps: true
|
|
213
|
+
}),
|
|
214
|
+
`host:texture/${key}`
|
|
215
|
+
);
|
|
216
|
+
this.textureRefs.set(key, t);
|
|
217
|
+
}
|
|
218
|
+
return t;
|
|
219
|
+
};
|
|
220
|
+
let packed = null;
|
|
221
|
+
if (doc.roughnessTexture || doc.metalnessTexture) {
|
|
222
|
+
const key = `mr:${doc.roughnessTexture ?? ""}:${doc.metalnessTexture ?? ""}`;
|
|
223
|
+
packed = this.textureRefs.get(key) ?? null;
|
|
224
|
+
if (!packed) {
|
|
225
|
+
const r = doc.roughnessTexture ? this.images?.(doc.roughnessTexture) : void 0;
|
|
226
|
+
const m = doc.metalnessTexture ? this.images?.(doc.metalnessTexture) : void 0;
|
|
227
|
+
if (r || m) {
|
|
228
|
+
packed = this.world.resource(Textures).add(packMetallicRoughness(r, m), `host:texture/${key}`);
|
|
229
|
+
this.textureRefs.set(key, packed);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return sceneMaterialValue(doc, this.k, texture, packed);
|
|
234
|
+
}
|
|
235
|
+
/** World size of one grid cell: the grid's pixel size through the visual scale. */
|
|
236
|
+
get cell() {
|
|
237
|
+
return (this.grid?.size ?? 70) * this.k;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Applies a full host state. Only documents whose revision changed are applied. A grid whose
|
|
241
|
+
* pixel size changed resizes cell-sized things (token footprints, props); its game distance,
|
|
242
|
+
* unit and diagonal rule place nothing.
|
|
243
|
+
*/
|
|
244
|
+
sync(docs) {
|
|
245
|
+
const counts = { spawned: 0, applied: 0, removed: 0, resized: 0 };
|
|
246
|
+
const add = (c) => {
|
|
247
|
+
counts.spawned += c.spawned;
|
|
248
|
+
counts.applied += c.applied;
|
|
249
|
+
counts.removed += c.removed;
|
|
250
|
+
};
|
|
251
|
+
const resize = this.grid !== void 0 && this.grid.size !== docs.grid.size;
|
|
252
|
+
this.grid = docs.grid;
|
|
253
|
+
add(this.materials.sync(docs.materials));
|
|
254
|
+
add(this.walls.sync(docs.walls));
|
|
255
|
+
add(this.openings.sync(docs.openings));
|
|
256
|
+
add(this.floors.sync(docs.floors));
|
|
257
|
+
add(this.props.sync(docs.props));
|
|
258
|
+
add(this.tokens.sync(docs.tokens));
|
|
259
|
+
if (resize) {
|
|
260
|
+
for (const [e, d] of this.tokenDocs) this.sizeToken(e, d);
|
|
261
|
+
for (const [e, d] of this.propDocs) this.placeProp(e, d);
|
|
262
|
+
counts.resized = this.tokenDocs.size + this.propDocs.size;
|
|
263
|
+
}
|
|
264
|
+
return counts;
|
|
265
|
+
}
|
|
266
|
+
sizeToken(root, d) {
|
|
267
|
+
const disc = this.discs.get(root);
|
|
268
|
+
const footprint = d.size * this.cell * 0.9;
|
|
269
|
+
this.world.set(disc, Transform, {
|
|
270
|
+
translation: [0, 0.03, 0],
|
|
271
|
+
scale: [footprint, 0.06, footprint]
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
placeProp(e, d) {
|
|
275
|
+
const size = d.cells * this.cell;
|
|
276
|
+
const a = d.rotation * Math.PI / 360;
|
|
277
|
+
this.world.set(e, Transform, {
|
|
278
|
+
translation: [d.x * this.k, size / 2, d.y * this.k],
|
|
279
|
+
rotation: [0, Math.sin(a), 0, Math.cos(a)],
|
|
280
|
+
scale: [size, size, size]
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
disc() {
|
|
284
|
+
this.discMesh ??= this.world.resource(Meshes).add(
|
|
285
|
+
cylinder({ radius: 0.5, height: 1, segments: 32 }),
|
|
286
|
+
"host:token-disc"
|
|
287
|
+
);
|
|
288
|
+
return this.discMesh;
|
|
289
|
+
}
|
|
290
|
+
tokenMat() {
|
|
291
|
+
this.tokenMaterial ??= this.world.resource(Materials).add(
|
|
292
|
+
new MaterialAsset({ baseColor: [0.75, 0.2, 0.15, 1], roughness: 0.5 }),
|
|
293
|
+
"host:token"
|
|
294
|
+
);
|
|
295
|
+
return this.tokenMaterial;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* One mesh per prop asset: spheres of about 1,950 triangles, so 256 props come to Aether's
|
|
299
|
+
* 500k prop triangles.
|
|
300
|
+
*/
|
|
301
|
+
propMesh(asset) {
|
|
302
|
+
let ref = this.propMeshes.get(asset);
|
|
303
|
+
if (!ref) {
|
|
304
|
+
const n = Number.parseInt(asset.slice(asset.lastIndexOf("-") + 1), 10) || 0;
|
|
305
|
+
const segments = 31 + n % 3;
|
|
306
|
+
const mesh = sphere({ radius: 0.5, segments, rings: segments });
|
|
307
|
+
ref = this.world.resource(Meshes).add(mesh, `host:prop/${asset}`);
|
|
308
|
+
this.propMeshes.set(asset, ref);
|
|
309
|
+
}
|
|
310
|
+
return ref;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function hostScene(world, pxToWorld = PX_TO_WORLD, images) {
|
|
314
|
+
return new HostScene(world, pxToWorld, images);
|
|
315
|
+
}
|
|
316
|
+
import { brickMaterial, brickTextures } from "./bricks.js";
|
|
317
|
+
export {
|
|
318
|
+
HostScene,
|
|
319
|
+
LIMITS,
|
|
320
|
+
PX_TO_WORLD,
|
|
321
|
+
brickMaterial,
|
|
322
|
+
brickTextures,
|
|
323
|
+
hostScene,
|
|
324
|
+
maxScene,
|
|
325
|
+
sceneMaterialValue,
|
|
326
|
+
shadowStress
|
|
327
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { type Centerline } from './curve.js';
|
|
2
|
+
/** A wall in world units: a centreline in the XZ plane, extruded up from `elevation`. */
|
|
3
|
+
export interface WallShape {
|
|
4
|
+
ax: number;
|
|
5
|
+
az: number;
|
|
6
|
+
bx: number;
|
|
7
|
+
bz: number;
|
|
8
|
+
height: number;
|
|
9
|
+
thickness: number;
|
|
10
|
+
elevation: number;
|
|
11
|
+
/** The sampled centreline (two samples for a straight wall). */
|
|
12
|
+
line: Centerline;
|
|
13
|
+
}
|
|
14
|
+
/** An opening along its host wall, in world units of arc length from the wall's start. */
|
|
15
|
+
export interface OpeningShape {
|
|
16
|
+
kind: 'door' | 'window';
|
|
17
|
+
offset: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
sill: number;
|
|
21
|
+
frameWidth: number;
|
|
22
|
+
frameDepth: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A box along a wall: `[s0, s1]` of arc length along it, `[y0, y1]` up, `half` either side of the
|
|
26
|
+
* centreline. Caps are the faces at s0 and s1; a piece that continues into another has none there.
|
|
27
|
+
*/
|
|
28
|
+
export interface Piece {
|
|
29
|
+
s0: number;
|
|
30
|
+
s1: number;
|
|
31
|
+
y0: number;
|
|
32
|
+
y1: number;
|
|
33
|
+
half: number;
|
|
34
|
+
capStart: boolean;
|
|
35
|
+
capEnd: boolean;
|
|
36
|
+
/** Whether the underside shows (a lintel over a door; not a wall standing on the floor). */
|
|
37
|
+
bottom: boolean;
|
|
38
|
+
/** A door or window frame, drawn with the opening's frame material. */
|
|
39
|
+
frame: boolean;
|
|
40
|
+
/** Index of the opening (in the list passed to `wallPieces`) this piece frames; -1 for wall. */
|
|
41
|
+
source: number;
|
|
42
|
+
}
|
|
43
|
+
/** Length of a wall's centreline (its arc length when curved). */
|
|
44
|
+
export declare function wallLength(w: WallShape): number;
|
|
45
|
+
/**
|
|
46
|
+
* Splits a wall around its openings (sorted by offset) into pieces: solid spans at full height,
|
|
47
|
+
* the wall below a window's sill and above an opening's head, and frame boxes. Returns the count
|
|
48
|
+
* written to `out` (entries are reused).
|
|
49
|
+
*/
|
|
50
|
+
export declare function wallPieces(w: WallShape, openings: readonly OpeningShape[], out: Piece[]): number;
|
|
51
|
+
/** A chunk's key from its integer coordinates (each in ±32,767). */
|
|
52
|
+
export declare function chunkKey(cx: number, cz: number): number;
|
|
53
|
+
export declare function chunkX(key: number): number;
|
|
54
|
+
export declare function chunkZ(key: number): number;
|
|
55
|
+
/** Scratch for clipping: footprint quads, clipped to at most 8 points, with arc lengths. */
|
|
56
|
+
export declare class ClipScratch {
|
|
57
|
+
readonly a: Float64Array<ArrayBuffer>;
|
|
58
|
+
readonly b: Float64Array<ArrayBuffer>;
|
|
59
|
+
readonly la: Int8Array<ArrayBuffer>;
|
|
60
|
+
readonly lb: Int8Array<ArrayBuffer>;
|
|
61
|
+
/** Arc length at each point. */
|
|
62
|
+
readonly sa: Float64Array<ArrayBuffer>;
|
|
63
|
+
readonly sb: Float64Array<ArrayBuffer>;
|
|
64
|
+
readonly tri: Float64Array<ArrayBuffer>;
|
|
65
|
+
/** A centreline point and normal (pointAt). */
|
|
66
|
+
readonly at: Float64Array<ArrayBuffer>;
|
|
67
|
+
/** The quad boundaries of the piece being walked: arc lengths. */
|
|
68
|
+
bounds: Float64Array<ArrayBuffer>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Adds the keys of every chunk a wall's pieces overlap to `out` (a piece touching a chunk's edge
|
|
72
|
+
* without area in it doesn't count). The same quads and test decide what each chunk draws.
|
|
73
|
+
*/
|
|
74
|
+
export declare function wallChunks(w: WallShape, pieces: readonly Piece[], count: number, size: number, out: Set<number>, s: ClipScratch): void;
|
|
75
|
+
/** Grows as vertices are added; `reset` keeps its memory. */
|
|
76
|
+
export declare class MeshBuilder {
|
|
77
|
+
positions: Float32Array<ArrayBuffer>;
|
|
78
|
+
normals: Float32Array<ArrayBuffer>;
|
|
79
|
+
uvs: Float32Array<ArrayBuffer>;
|
|
80
|
+
tangents: Float32Array<ArrayBuffer>;
|
|
81
|
+
indices: Uint32Array<ArrayBuffer>;
|
|
82
|
+
vertexCount: number;
|
|
83
|
+
indexCount: number;
|
|
84
|
+
/** Local-space bounds of what was added. */
|
|
85
|
+
minX: number;
|
|
86
|
+
minY: number;
|
|
87
|
+
minZ: number;
|
|
88
|
+
maxX: number;
|
|
89
|
+
maxY: number;
|
|
90
|
+
maxZ: number;
|
|
91
|
+
reset(): void;
|
|
92
|
+
/** A vertex: position, normal, uv, and the tangent along u with its bitangent sign. */
|
|
93
|
+
vertex(x: number, y: number, z: number, nx: number, ny: number, nz: number, u: number, v: number, tx: number, ty: number, tz: number, tw: number): number;
|
|
94
|
+
triangle(a: number, b: number, c: number): void;
|
|
95
|
+
private growVertices;
|
|
96
|
+
/**
|
|
97
|
+
* A convex planar polygon as a fan, wound so its front faces along (nx, ny, nz). `first` is the
|
|
98
|
+
* index of its first vertex; the rest follow.
|
|
99
|
+
*/
|
|
100
|
+
fan(first: number, count: number, nx: number, ny: number, nz: number): void;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Emits the parts of a wall's pieces inside one chunk square: tops, undersides where they show,
|
|
104
|
+
* the long sides, and caps at the wall's ends and around openings. Faces along the chunk's
|
|
105
|
+
* edges, and between the quads of a curve, are left open. `builder(i)` is where piece i goes (by
|
|
106
|
+
* its material). Returns whether anything was emitted.
|
|
107
|
+
*/
|
|
108
|
+
export declare function emitWall(w: WallShape, pieces: readonly Piece[], count: number, minX: number, minZ: number, maxX: number, maxZ: number, builder: (piece: number) => MeshBuilder, s: ClipScratch): boolean;
|
|
109
|
+
/** A hole's frame (0067): a band `width` wide around ring `ring`, standing `depth` above the surface. */
|
|
110
|
+
export interface SlabFrame {
|
|
111
|
+
ring: number;
|
|
112
|
+
width: number;
|
|
113
|
+
depth: number;
|
|
114
|
+
}
|
|
115
|
+
export interface SlabOptions {
|
|
116
|
+
/** Holes, each a simple polygon inside the outline, either winding. */
|
|
117
|
+
holes?: readonly ArrayLike<ArrayLike<number>>[];
|
|
118
|
+
/** Frames around holes (`ring` 1 is the first hole). */
|
|
119
|
+
frames?: readonly SlabFrame[];
|
|
120
|
+
thickness?: number;
|
|
121
|
+
/** Degrees: 0 is flat; more is one slope rising along `ridge` from the outline's lowest point. */
|
|
122
|
+
pitch?: number;
|
|
123
|
+
/** The direction the slope rises, (x, z). */
|
|
124
|
+
ridge?: ArrayLike<number>;
|
|
125
|
+
}
|
|
126
|
+
/** A floor or roof ready to clip: its rings, triangles and frames, computed once per edit. */
|
|
127
|
+
export interface FloorShape {
|
|
128
|
+
/** Flat (x, z) pairs: the outline, then each hole. */
|
|
129
|
+
points: Float64Array;
|
|
130
|
+
/** Where each ring starts, in points, then the total: ring r is `rings[r]` to `rings[r + 1]`. */
|
|
131
|
+
rings: Uint32Array;
|
|
132
|
+
/** Per ring, the sign that turns an edge's left normal into the one pointing out of the slab. */
|
|
133
|
+
sides: Int8Array;
|
|
134
|
+
/** Three vertex indices per triangle. */
|
|
135
|
+
triangles: Uint32Array;
|
|
136
|
+
/** The surface's height at its lowest point: y = elevation + slope × (x·rx + z·rz − d0). */
|
|
137
|
+
elevation: number;
|
|
138
|
+
slope: number;
|
|
139
|
+
rx: number;
|
|
140
|
+
rz: number;
|
|
141
|
+
d0: number;
|
|
142
|
+
thickness: number;
|
|
143
|
+
frames: readonly SlabFrame[];
|
|
144
|
+
/** Each framed ring's points offset onto the slab by its frame's width (indexed as points). */
|
|
145
|
+
frameOuter: Float64Array;
|
|
146
|
+
/** Bounds of everything it draws (the outline and frames). */
|
|
147
|
+
minX: number;
|
|
148
|
+
minZ: number;
|
|
149
|
+
maxX: number;
|
|
150
|
+
maxZ: number;
|
|
151
|
+
}
|
|
152
|
+
/** The surface height of a slab at (x, z). */
|
|
153
|
+
export declare function slabY(f: FloorShape, x: number, z: number): number;
|
|
154
|
+
/**
|
|
155
|
+
* Triangulates a slab: an outline (a simple polygon, either winding) with holes, and the frames
|
|
156
|
+
* around them.
|
|
157
|
+
*/
|
|
158
|
+
export declare function floorShape(points: ArrayLike<ArrayLike<number>>, elevation: number, options?: SlabOptions): FloorShape;
|
|
159
|
+
/** One ring of a slab as a slab of its own on the same surface, `drop` below it (a hatch, a pane). */
|
|
160
|
+
export declare function ringShape(f: FloorShape, ring: number, thickness: number, drop?: number): FloorShape;
|
|
161
|
+
/** Adds the keys of every chunk a slab's surface or frames overlap to `out`. */
|
|
162
|
+
export declare function floorChunks(f: FloorShape, size: number, out: Set<number>, s: ClipScratch): void;
|
|
163
|
+
/**
|
|
164
|
+
* Adds the chunks a slab's ring covers to `out`: the hole's area, its rim and its frame. Moving a
|
|
165
|
+
* cutout changes nothing drawn outside these, whatever the new triangulation (0067).
|
|
166
|
+
*/
|
|
167
|
+
export declare function ringChunks(f: FloorShape, ring: number, size: number, out: Set<number>, s: ClipScratch): void;
|
|
168
|
+
/**
|
|
169
|
+
* Emits a slab's part inside one chunk square: its surface (u, v in metres along it) and, when
|
|
170
|
+
* thick, its underside, edges and hole rims. Frames go to `frame(k)`, for frame k of `f.frames`.
|
|
171
|
+
*/
|
|
172
|
+
export declare function emitFloor(f: FloorShape, minX: number, minZ: number, maxX: number, maxZ: number, m: MeshBuilder, s: ClipScratch, frame?: (k: number) => MeshBuilder): boolean;
|
|
173
|
+
/** Whether (x, z) is inside points `start` to `end` of a flat ring; on an edge counts as `edge`. */
|
|
174
|
+
export declare function insideRing(points: ArrayLike<number>, start: number, end: number, x: number, z: number, edge: boolean): boolean;
|
|
175
|
+
/** Whether ring `inner` (flat points) lies strictly inside ring `outer`, touching nowhere. */
|
|
176
|
+
export declare function ringInside(inner: Float64Array, outer: Float64Array): boolean;
|
|
177
|
+
/** Whether two rings (flat points) overlap or touch. */
|
|
178
|
+
export declare function ringsOverlap(a: Float64Array, b: Float64Array): boolean;
|