@driftengine/drft 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/README.md +1 -1
- package/dist/assemble.d.ts +7 -0
- package/dist/assemble.js +242 -0
- package/dist/drftAssembly.d.ts +62 -0
- package/dist/drftAssembly.js +267 -0
- package/dist/drftFormat.d.ts +43 -1
- package/dist/drftFormat.js +52 -2
- package/dist/drftLightVolume.d.ts +12 -0
- package/dist/drftLightVolume.js +172 -0
- package/dist/drftQuantised.js +4 -1
- package/dist/drftRead.d.ts +21 -1
- package/dist/drftRead.js +71 -4
- package/dist/drftRegions.d.ts +58 -0
- package/dist/drftRegions.js +313 -0
- package/dist/drftStream.d.ts +49 -1
- package/dist/drftStream.js +74 -5
- package/dist/drftWrite.d.ts +35 -1
- package/dist/drftWrite.js +97 -6
- package/dist/index.d.ts +9 -0
- package/dist/index.js +7 -0
- package/dist/meshData.d.ts +19 -0
- package/dist/meshData.js +15 -0
- package/package.json +4 -4
- package/src/assemble.ts +268 -0
- package/src/drftAssembly.ts +322 -0
- package/src/drftFormat.ts +59 -2
- package/src/drftLightVolume.ts +179 -0
- package/src/drftQuantised.ts +8 -1
- package/src/drftRead.ts +97 -4
- package/src/drftRegions.ts +350 -0
- package/src/drftStream.ts +112 -7
- package/src/drftWrite.ts +141 -5
- package/src/index.ts +40 -0
- package/src/meshData.ts +38 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The `.drft` container: read and write a baked scene. Declares no runtime dependency.
|
|
4
4
|
|
|
5
|
-
**Cost:
|
|
5
|
+
**Cost: 14.0 KB gzipped.** 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,7 @@
|
|
|
1
|
+
import type { DrftAssembly } from './drftAssembly.ts';
|
|
2
|
+
import type { MeshData } from './meshData.ts';
|
|
3
|
+
/** Finds a kit piece by mesh ordinal. */
|
|
4
|
+
export type PieceLookup = (ordinal: number) => MeshData;
|
|
5
|
+
export declare function expandAssembly(assembly: DrftAssembly, piece: PieceLookup): MeshData;
|
|
6
|
+
/** Min xyz then max xyz of every copy: its piece's box through its matrix, corner by corner. */
|
|
7
|
+
export declare function assemblyBounds(assembly: DrftAssembly, piece: PieceLookup, out?: Float32Array): Float32Array;
|
package/dist/assemble.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An assembly expanded into the mesh it describes: every copy's piece through its matrix, painted
|
|
3
|
+
* by its surface, its texture coordinates stretched as the copy says.
|
|
4
|
+
*
|
|
5
|
+
* **Positions by the matrix, normals by its inverse transpose**, so a slope stretched along one
|
|
6
|
+
* axis stays perpendicular to itself rather than leaning with the stretch. **A copy that mirrors
|
|
7
|
+
* — a negative determinant — winds its triangles back** and flips its tangent's handedness, so its
|
|
8
|
+
* faces still wind counter-clockwise seen from outside and a mirrored texture still lights the
|
|
9
|
+
* right way up. Tangents follow the matrix, since a tangent is a direction along the surface.
|
|
10
|
+
*
|
|
11
|
+
* **A coordinate is stretched along the piece's own tangent**: u by `|uStretch ⊙ t|` for the
|
|
12
|
+
* piece's unit tangent t, v the same along its bitangent, then offset. That is what lets one unit
|
|
13
|
+
* box serve every box in a city: its face coordinates run 0..1, and a copy 13 m by 4 m carrying
|
|
14
|
+
* stretches of size over tile repeats its facade in metres on every face. What it gives up is a
|
|
15
|
+
* stretch that varies across one face — a cone's side, where the circumference shrinks up the
|
|
16
|
+
* slope — which comes out as the stretch at each vertex's own tangent, and exact only where the
|
|
17
|
+
* piece's coordinates are linear in its surface.
|
|
18
|
+
*
|
|
19
|
+
* One allocation per output array, sized by a first pass over the copies.
|
|
20
|
+
*/
|
|
21
|
+
import { COPY_MATRIX_FLOATS, COPY_UV_FLOATS, SURFACE, SURFACE_FLOATS, determinant, } from './drftAssembly.js';
|
|
22
|
+
import { ATTR_EMISSIVE_COLOR, ATTR_GRAIN, ATTR_LAYERS, ATTR_RELIEF, ATTR_ROUGHNESS, ATTR_SPECULAR, ATTR_TANGENT, ATTR_UVS, ATTR_CHANNEL, DrftError, } from './drftFormat.js';
|
|
23
|
+
function pieceOf(piece, assembly, c) {
|
|
24
|
+
const mesh = piece(assembly.pieces[c]);
|
|
25
|
+
const textured = (assembly.attributes & ATTR_UVS) !== 0;
|
|
26
|
+
if (textured && (mesh.uvs === undefined || mesh.tangents === undefined)) {
|
|
27
|
+
throw new DrftError(`piece ${assembly.pieces[c]} has no ${mesh.uvs === undefined ? 'texture coordinates' : 'tangents'}, and a textured assembly copies it`);
|
|
28
|
+
}
|
|
29
|
+
if ((assembly.attributes & ATTR_TANGENT) !== 0 && mesh.tangents === undefined) {
|
|
30
|
+
throw new DrftError(`piece ${assembly.pieces[c]} has no tangents, and the assembly carries them`);
|
|
31
|
+
}
|
|
32
|
+
if ((assembly.attributes & ATTR_CHANNEL) !== 0 && mesh.channel === undefined) {
|
|
33
|
+
throw new DrftError(`piece ${assembly.pieces[c]} has no channel, and the assembly sways`);
|
|
34
|
+
}
|
|
35
|
+
return mesh;
|
|
36
|
+
}
|
|
37
|
+
export function expandAssembly(assembly, piece) {
|
|
38
|
+
const { attributes, surfaces, surfaceOf, transforms, uv } = assembly;
|
|
39
|
+
const copies = assembly.pieces.length;
|
|
40
|
+
let vertices = 0;
|
|
41
|
+
let indexCount = 0;
|
|
42
|
+
for (let c = 0; c < copies; c++) {
|
|
43
|
+
const mesh = pieceOf(piece, assembly, c);
|
|
44
|
+
vertices += mesh.positions.length / 3;
|
|
45
|
+
indexCount += mesh.indices.length;
|
|
46
|
+
}
|
|
47
|
+
const has = (bit) => (attributes & bit) !== 0;
|
|
48
|
+
const out = {
|
|
49
|
+
positions: new Float32Array(vertices * 3),
|
|
50
|
+
normals: new Float32Array(vertices * 3),
|
|
51
|
+
colors: new Float32Array(vertices * 3),
|
|
52
|
+
emissive: new Float32Array(vertices),
|
|
53
|
+
indices: new Uint32Array(indexCount),
|
|
54
|
+
};
|
|
55
|
+
if (has(ATTR_SPECULAR))
|
|
56
|
+
out.specular = new Float32Array(vertices);
|
|
57
|
+
if (has(ATTR_UVS))
|
|
58
|
+
out.uvs = new Float32Array(vertices * 2);
|
|
59
|
+
if (has(ATTR_EMISSIVE_COLOR))
|
|
60
|
+
out.emissiveColor = new Float32Array(vertices * 3);
|
|
61
|
+
if (has(ATTR_ROUGHNESS))
|
|
62
|
+
out.roughness = new Float32Array(vertices);
|
|
63
|
+
if (has(ATTR_GRAIN))
|
|
64
|
+
out.grain = new Float32Array(vertices);
|
|
65
|
+
if (has(ATTR_RELIEF))
|
|
66
|
+
out.relief = new Float32Array(vertices);
|
|
67
|
+
if (has(ATTR_TANGENT))
|
|
68
|
+
out.tangents = new Float32Array(vertices * 4);
|
|
69
|
+
if (has(ATTR_LAYERS))
|
|
70
|
+
out.layers = new Float32Array(vertices);
|
|
71
|
+
if (has(ATTR_CHANNEL))
|
|
72
|
+
out.channel = new Float32Array(vertices * 4);
|
|
73
|
+
let v0 = 0;
|
|
74
|
+
let i0 = 0;
|
|
75
|
+
for (let c = 0; c < copies; c++) {
|
|
76
|
+
const mesh = pieceOf(piece, assembly, c);
|
|
77
|
+
const m = c * COPY_MATRIX_FLOATS;
|
|
78
|
+
const t = transforms;
|
|
79
|
+
const [a, b, cc, d, e, f, g, h, k] = [
|
|
80
|
+
t[m],
|
|
81
|
+
t[m + 1],
|
|
82
|
+
t[m + 2],
|
|
83
|
+
t[m + 3],
|
|
84
|
+
t[m + 4],
|
|
85
|
+
t[m + 5],
|
|
86
|
+
t[m + 6],
|
|
87
|
+
t[m + 7],
|
|
88
|
+
t[m + 8],
|
|
89
|
+
];
|
|
90
|
+
const [tx, ty, tz] = [t[m + 9], t[m + 10], t[m + 11]];
|
|
91
|
+
const det = determinant(t, m);
|
|
92
|
+
/* The inverse transpose is the cofactor matrix over the determinant. With columns (a b cc),
|
|
93
|
+
(d e f), (g h k) the matrix's rows are (a d g), (b e h), (cc f k), and nRC is the cofactor of
|
|
94
|
+
row R, column C — not of C, R, which would be the plain inverse. */
|
|
95
|
+
const n00 = (e * k - f * h) / det;
|
|
96
|
+
const n01 = (cc * h - b * k) / det;
|
|
97
|
+
const n02 = (b * f - cc * e) / det;
|
|
98
|
+
const n10 = (f * g - d * k) / det;
|
|
99
|
+
const n11 = (a * k - cc * g) / det;
|
|
100
|
+
const n12 = (cc * d - a * f) / det;
|
|
101
|
+
const n20 = (d * h - e * g) / det;
|
|
102
|
+
const n21 = (b * g - a * h) / det;
|
|
103
|
+
const n22 = (a * e - b * d) / det;
|
|
104
|
+
const hand = det < 0 ? -1 : 1;
|
|
105
|
+
const s = surfaceOf[c] * SURFACE_FLOATS;
|
|
106
|
+
const u = c * COPY_UV_FLOATS;
|
|
107
|
+
const count = mesh.positions.length / 3;
|
|
108
|
+
const p = mesh.positions;
|
|
109
|
+
const n = mesh.normals;
|
|
110
|
+
const tan = mesh.tangents;
|
|
111
|
+
const tex = mesh.uvs;
|
|
112
|
+
for (let i = 0; i < count; i++) {
|
|
113
|
+
const o = v0 + i;
|
|
114
|
+
const px = p[i * 3];
|
|
115
|
+
const py = p[i * 3 + 1];
|
|
116
|
+
const pz = p[i * 3 + 2];
|
|
117
|
+
out.positions[o * 3] = a * px + d * py + g * pz + tx;
|
|
118
|
+
out.positions[o * 3 + 1] = b * px + e * py + h * pz + ty;
|
|
119
|
+
out.positions[o * 3 + 2] = cc * px + f * py + k * pz + tz;
|
|
120
|
+
const nx = n[i * 3];
|
|
121
|
+
const ny = n[i * 3 + 1];
|
|
122
|
+
const nz = n[i * 3 + 2];
|
|
123
|
+
let wx = n00 * nx + n01 * ny + n02 * nz;
|
|
124
|
+
let wy = n10 * nx + n11 * ny + n12 * nz;
|
|
125
|
+
let wz = n20 * nx + n21 * ny + n22 * nz;
|
|
126
|
+
const nl = Math.hypot(wx, wy, wz) || 1;
|
|
127
|
+
wx /= nl;
|
|
128
|
+
wy /= nl;
|
|
129
|
+
wz /= nl;
|
|
130
|
+
out.normals[o * 3] = wx;
|
|
131
|
+
out.normals[o * 3 + 1] = wy;
|
|
132
|
+
out.normals[o * 3 + 2] = wz;
|
|
133
|
+
out.colors[o * 3] = surfaces[s + SURFACE.color];
|
|
134
|
+
out.colors[o * 3 + 1] = surfaces[s + SURFACE.color + 1];
|
|
135
|
+
out.colors[o * 3 + 2] = surfaces[s + SURFACE.color + 2];
|
|
136
|
+
out.emissive[o] = surfaces[s + SURFACE.emissive];
|
|
137
|
+
if (out.specular)
|
|
138
|
+
out.specular[o] = surfaces[s + SURFACE.specular];
|
|
139
|
+
if (out.emissiveColor) {
|
|
140
|
+
out.emissiveColor[o * 3] = surfaces[s + SURFACE.emissiveColor];
|
|
141
|
+
out.emissiveColor[o * 3 + 1] = surfaces[s + SURFACE.emissiveColor + 1];
|
|
142
|
+
out.emissiveColor[o * 3 + 2] = surfaces[s + SURFACE.emissiveColor + 2];
|
|
143
|
+
}
|
|
144
|
+
if (out.roughness)
|
|
145
|
+
out.roughness[o] = surfaces[s + SURFACE.roughness];
|
|
146
|
+
if (out.grain)
|
|
147
|
+
out.grain[o] = surfaces[s + SURFACE.grain];
|
|
148
|
+
if (out.relief)
|
|
149
|
+
out.relief[o] = surfaces[s + SURFACE.relief];
|
|
150
|
+
if (out.layers)
|
|
151
|
+
out.layers[o] = surfaces[s + SURFACE.layer];
|
|
152
|
+
/* Sway, sky and opacity are the piece's own: shape, not paint. */
|
|
153
|
+
if (out.channel && mesh.channel !== undefined) {
|
|
154
|
+
for (let lane = 0; lane < 4; lane++) {
|
|
155
|
+
out.channel[o * 4 + lane] = mesh.channel[i * 4 + lane];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (tan !== undefined && (out.tangents !== undefined || out.uvs !== undefined)) {
|
|
159
|
+
const qx = tan[i * 4];
|
|
160
|
+
const qy = tan[i * 4 + 1];
|
|
161
|
+
const qz = tan[i * 4 + 2];
|
|
162
|
+
const qw = tan[i * 4 + 3];
|
|
163
|
+
if (out.tangents) {
|
|
164
|
+
let rx = a * qx + d * qy + g * qz;
|
|
165
|
+
let ry = b * qx + e * qy + h * qz;
|
|
166
|
+
let rz = cc * qx + f * qy + k * qz;
|
|
167
|
+
const rl = Math.hypot(rx, ry, rz) || 1;
|
|
168
|
+
rx /= rl;
|
|
169
|
+
ry /= rl;
|
|
170
|
+
rz /= rl;
|
|
171
|
+
out.tangents[o * 4] = rx;
|
|
172
|
+
out.tangents[o * 4 + 1] = ry;
|
|
173
|
+
out.tangents[o * 4 + 2] = rz;
|
|
174
|
+
out.tangents[o * 4 + 3] = qw * hand;
|
|
175
|
+
}
|
|
176
|
+
if (out.uvs && tex !== undefined) {
|
|
177
|
+
/* The piece's own directions for u and v: its tangent, and the bitangent n × t · w. */
|
|
178
|
+
const ql = Math.hypot(qx, qy, qz) || 1;
|
|
179
|
+
const bx = (ny * qz - nz * qy) * qw;
|
|
180
|
+
const by = (nz * qx - nx * qz) * qw;
|
|
181
|
+
const bz = (nx * qy - ny * qx) * qw;
|
|
182
|
+
const bl = Math.hypot(bx, by, bz) || 1;
|
|
183
|
+
const su = Math.hypot((uv[u] * qx) / ql, (uv[u + 1] * qy) / ql, (uv[u + 2] * qz) / ql);
|
|
184
|
+
const sv = Math.hypot((uv[u + 3] * bx) / bl, (uv[u + 4] * by) / bl, (uv[u + 5] * bz) / bl);
|
|
185
|
+
out.uvs[o * 2] = tex[i * 2] * su + uv[u + 6];
|
|
186
|
+
out.uvs[o * 2 + 1] = tex[i * 2 + 1] * sv + uv[u + 7];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const idx = mesh.indices;
|
|
191
|
+
for (let j = 0; j + 2 < idx.length; j += 3) {
|
|
192
|
+
out.indices[i0 + j] = v0 + idx[j];
|
|
193
|
+
/* A mirrored copy winds back, or its faces turn inside out. */
|
|
194
|
+
out.indices[i0 + j + 1] = v0 + idx[hand < 0 ? j + 2 : j + 1];
|
|
195
|
+
out.indices[i0 + j + 2] = v0 + idx[hand < 0 ? j + 1 : j + 2];
|
|
196
|
+
}
|
|
197
|
+
v0 += count;
|
|
198
|
+
i0 += idx.length;
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
/** Min xyz then max xyz of every copy: its piece's box through its matrix, corner by corner. */
|
|
203
|
+
export function assemblyBounds(assembly, piece, out = new Float32Array(6)) {
|
|
204
|
+
out.set([Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity]);
|
|
205
|
+
for (let c = 0; c < assembly.pieces.length; c++) {
|
|
206
|
+
const p = piece(assembly.pieces[c]).positions;
|
|
207
|
+
let lx = Infinity;
|
|
208
|
+
let ly = Infinity;
|
|
209
|
+
let lz = Infinity;
|
|
210
|
+
let hx = -Infinity;
|
|
211
|
+
let hy = -Infinity;
|
|
212
|
+
let hz = -Infinity;
|
|
213
|
+
for (let i = 0; i + 2 < p.length; i += 3) {
|
|
214
|
+
lx = Math.min(lx, p[i]);
|
|
215
|
+
ly = Math.min(ly, p[i + 1]);
|
|
216
|
+
lz = Math.min(lz, p[i + 2]);
|
|
217
|
+
hx = Math.max(hx, p[i]);
|
|
218
|
+
hy = Math.max(hy, p[i + 1]);
|
|
219
|
+
hz = Math.max(hz, p[i + 2]);
|
|
220
|
+
}
|
|
221
|
+
if (lx > hx)
|
|
222
|
+
continue;
|
|
223
|
+
const m = c * COPY_MATRIX_FLOATS;
|
|
224
|
+
const t = assembly.transforms;
|
|
225
|
+
for (let corner = 0; corner < 8; corner++) {
|
|
226
|
+
const x = corner & 1 ? hx : lx;
|
|
227
|
+
const y = corner & 2 ? hy : ly;
|
|
228
|
+
const z = corner & 4 ? hz : lz;
|
|
229
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
230
|
+
const w = t[m + axis] * x +
|
|
231
|
+
t[m + 3 + axis] * y +
|
|
232
|
+
t[m + 6 + axis] * z +
|
|
233
|
+
t[m + 9 + axis];
|
|
234
|
+
if (w < out[axis])
|
|
235
|
+
out[axis] = w;
|
|
236
|
+
if (w > out[axis + 3])
|
|
237
|
+
out[axis + 3] = w;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { MeshData } from './meshData.ts';
|
|
2
|
+
/** Where each value sits in a surface, whichever of them the mesh carries. */
|
|
3
|
+
export declare const SURFACE: {
|
|
4
|
+
readonly color: 0;
|
|
5
|
+
readonly emissive: 3;
|
|
6
|
+
readonly specular: 4;
|
|
7
|
+
readonly emissiveColor: 5;
|
|
8
|
+
readonly roughness: 8;
|
|
9
|
+
readonly grain: 9;
|
|
10
|
+
readonly relief: 10;
|
|
11
|
+
readonly layer: 11;
|
|
12
|
+
};
|
|
13
|
+
export declare const SURFACE_FLOATS = 12;
|
|
14
|
+
/** A copy's matrix: three columns of the linear part, then the translation. */
|
|
15
|
+
export declare const COPY_MATRIX_FLOATS = 12;
|
|
16
|
+
/** A copy's texture stretch: u along each piece axis, v along each, then the offset. */
|
|
17
|
+
export declare const COPY_UV_FLOATS = 8;
|
|
18
|
+
/** The optional arrays an assembled mesh may carry. Skinning is a piece of a character, not a kit. */
|
|
19
|
+
export declare const ASSEMBLY_ATTRIBUTES: number;
|
|
20
|
+
export interface DrftAssembly {
|
|
21
|
+
/** Which optional arrays the expanded mesh carries, as a `MESH`'s attribute bits. */
|
|
22
|
+
readonly attributes: number;
|
|
23
|
+
/** `SURFACE_FLOATS` a surface, laid out as `SURFACE` says. */
|
|
24
|
+
readonly surfaces: Float32Array;
|
|
25
|
+
/** One kit piece a copy, by mesh ordinal. */
|
|
26
|
+
readonly pieces: Uint32Array;
|
|
27
|
+
/** One surface a copy. */
|
|
28
|
+
readonly surfaceOf: Uint32Array;
|
|
29
|
+
/** `COPY_MATRIX_FLOATS` a copy, taking the piece to where the copy stands. */
|
|
30
|
+
readonly transforms: Float32Array;
|
|
31
|
+
/**
|
|
32
|
+
* `COPY_UV_FLOATS` a copy. A coordinate is multiplied by how far the copy stretches the piece's
|
|
33
|
+
* own direction for it — `|stretch ⊙ t|` for the unit tangent `t`, the bitangent for v — so a
|
|
34
|
+
* unit box copied at 13 × 4 m with stretches of size over tile repeats its texture in metres,
|
|
35
|
+
* face by face. `(1, 1, 1)` twice and no offset leave a piece's coordinates as they are.
|
|
36
|
+
*/
|
|
37
|
+
readonly uv: Float32Array;
|
|
38
|
+
}
|
|
39
|
+
/** Whether a mesh slot holds an assembly rather than vertices. */
|
|
40
|
+
export declare function isAssembly(mesh: object): mesh is DrftAssembly;
|
|
41
|
+
export declare function buildKit(ordinals: readonly number[]): Uint8Array;
|
|
42
|
+
/** The kit's pieces, ascending and each a mesh the file carries. */
|
|
43
|
+
export declare function readKit(buffer: ArrayBufferLike, offset: number, byteLength: number, meshCount: number): number[];
|
|
44
|
+
export declare function checkKit(ordinals: readonly number[], meshCount: number): void;
|
|
45
|
+
export declare function buildAssembly(assembly: DrftAssembly): Uint8Array;
|
|
46
|
+
/** One `MSHC` payload, checked on its own; its pieces are the caller's to check. */
|
|
47
|
+
export declare function readAssembly(buffer: ArrayBufferLike, offset: number, byteLength: number): DrftAssembly;
|
|
48
|
+
/**
|
|
49
|
+
* What an assembly must be on its own: known bits, whole records, surfaces it names, finite
|
|
50
|
+
* numbers, whole layers, and no copy flattened to nothing — whose normals would be a division by
|
|
51
|
+
* zero rather than a direction.
|
|
52
|
+
*/
|
|
53
|
+
export declare function checkAssembly(assembly: DrftAssembly, name: string): void;
|
|
54
|
+
/** The determinant of the linear part of the copy matrix at `m`. */
|
|
55
|
+
export declare function determinant(t: ArrayLike<number>, m: number): number;
|
|
56
|
+
/**
|
|
57
|
+
* What an assembly must be to the kit around it: every copy a declared piece that comes before it
|
|
58
|
+
* in the file — so a streaming reader holds the piece when the copy lands — and every piece
|
|
59
|
+
* carrying what the assembly asks of it. Shared by the writer and both readers, so a file one
|
|
60
|
+
* refuses the others refuse too.
|
|
61
|
+
*/
|
|
62
|
+
export declare function checkCopies(assembly: DrftAssembly, ordinal: number, kit: ReadonlySet<number>, piece: (ordinal: number) => MeshData | undefined): void;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `KITS` and `MSHC`: a mesh carried as painted copies of a few pieces, rather than as its vertices.
|
|
3
|
+
*
|
|
4
|
+
* **What it is for is a world built from a kit.** A city of boxes, columns and cornices is a few
|
|
5
|
+
* thousand distinct shapes placed half a million times, each at its own size and in its own paint.
|
|
6
|
+
* Merged, its vertices are most of a gigabyte even quantised; as placements of the kit they are a
|
|
7
|
+
* few tens of megabytes. So a mesh may arrive as `MSHC` — a table of surfaces and, per copy, which
|
|
8
|
+
* piece, which surface, a 3×4 matrix and how its texture stretches — and is expanded by arithmetic
|
|
9
|
+
* into exactly the `MeshData` a `MESH` would have carried (`assemble.ts`). It stands in place of a
|
|
10
|
+
* `MESH` and counts as one in every ordinal, as `MSHQ` does, so materials and regions name it
|
|
11
|
+
* like any other.
|
|
12
|
+
*
|
|
13
|
+
* **`KITS` names the pieces, and is written ahead of the geometry.** A piece is an ordinary mesh
|
|
14
|
+
* that is drawn only inside the meshes assembled from it; without the declaration a streaming
|
|
15
|
+
* reader would upload every piece as a part and draw the whole kit at the origin. Both chunks are
|
|
16
|
+
* **required**, for the `INST` reason: a reader that skipped `KITS` would draw that kit, and one
|
|
17
|
+
* that skipped `MSHC` would draw nothing where a district stands. A refusal naming them is the
|
|
18
|
+
* honest answer, and what it gives up is opening such a file in a reader before 1.23.
|
|
19
|
+
*
|
|
20
|
+
* **A piece is shape; a copy is shape placed and painted.** A piece's colours, emissive and every
|
|
21
|
+
* other per-vertex constant are ignored — the copy's surface supplies them. A piece carries
|
|
22
|
+
* positions, normals, and texture coordinates with tangents where the mesh is textured, since the
|
|
23
|
+
* tangent is what says which way a coordinate runs, and so how a copy's stretch applies to it.
|
|
24
|
+
* **Its sway is shape too**: where the assembly carries `channel`, each copy takes its piece's, as
|
|
25
|
+
* a tree's rises from its root to its leaves however it is placed.
|
|
26
|
+
*
|
|
27
|
+
* Layout, little-endian:
|
|
28
|
+
* - `KITS`: u32 count, then that many u32 mesh ordinals, ascending;
|
|
29
|
+
* - `MSHC`: u32 attribute bits (a `MESH`'s, less skinning), u32 surface count, then
|
|
30
|
+
* `SURFACE_FLOATS` f32 a surface; u32 copy count, then per copy u32 piece ordinal, u32 surface,
|
|
31
|
+
* 12 f32 of matrix (three columns, then the translation), 3 f32 of u stretch, 3 f32 of v stretch
|
|
32
|
+
* and 2 f32 of offset: 88 bytes a copy.
|
|
33
|
+
*/
|
|
34
|
+
import { ATTR_EMISSIVE_COLOR, ATTR_GRAIN, ATTR_LAYERS, ATTR_RELIEF, ATTR_ROUGHNESS, ATTR_SPECULAR, ATTR_TANGENT, ATTR_UVS, ATTR_CHANNEL, DrftError, align, } from './drftFormat.js';
|
|
35
|
+
/** Where each value sits in a surface, whichever of them the mesh carries. */
|
|
36
|
+
export const SURFACE = {
|
|
37
|
+
color: 0,
|
|
38
|
+
emissive: 3,
|
|
39
|
+
specular: 4,
|
|
40
|
+
emissiveColor: 5,
|
|
41
|
+
roughness: 8,
|
|
42
|
+
grain: 9,
|
|
43
|
+
relief: 10,
|
|
44
|
+
layer: 11,
|
|
45
|
+
};
|
|
46
|
+
export const SURFACE_FLOATS = 12;
|
|
47
|
+
/** A copy's matrix: three columns of the linear part, then the translation. */
|
|
48
|
+
export const COPY_MATRIX_FLOATS = 12;
|
|
49
|
+
/** A copy's texture stretch: u along each piece axis, v along each, then the offset. */
|
|
50
|
+
export const COPY_UV_FLOATS = 8;
|
|
51
|
+
const COPY_BYTES = 8 + (COPY_MATRIX_FLOATS + COPY_UV_FLOATS) * 4;
|
|
52
|
+
/** The optional arrays an assembled mesh may carry. Skinning is a piece of a character, not a kit. */
|
|
53
|
+
export const ASSEMBLY_ATTRIBUTES = ATTR_SPECULAR |
|
|
54
|
+
ATTR_UVS |
|
|
55
|
+
ATTR_EMISSIVE_COLOR |
|
|
56
|
+
ATTR_ROUGHNESS |
|
|
57
|
+
ATTR_GRAIN |
|
|
58
|
+
ATTR_RELIEF |
|
|
59
|
+
ATTR_TANGENT |
|
|
60
|
+
ATTR_LAYERS |
|
|
61
|
+
ATTR_CHANNEL;
|
|
62
|
+
/** Whether a mesh slot holds an assembly rather than vertices. */
|
|
63
|
+
export function isAssembly(mesh) {
|
|
64
|
+
return 'pieces' in mesh && 'surfaceOf' in mesh;
|
|
65
|
+
}
|
|
66
|
+
export function buildKit(ordinals) {
|
|
67
|
+
const out = new Uint8Array(align(4 + ordinals.length * 4));
|
|
68
|
+
const view = new DataView(out.buffer);
|
|
69
|
+
view.setUint32(0, ordinals.length, true);
|
|
70
|
+
ordinals.forEach((o, i) => view.setUint32(4 + i * 4, o, true));
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
/** The kit's pieces, ascending and each a mesh the file carries. */
|
|
74
|
+
export function readKit(buffer, offset, byteLength, meshCount) {
|
|
75
|
+
const view = new DataView(buffer, offset, byteLength);
|
|
76
|
+
if (byteLength < 4)
|
|
77
|
+
throw new DrftError('KITS ends inside its count');
|
|
78
|
+
const count = view.getUint32(0, true);
|
|
79
|
+
if (4 + count * 4 > byteLength)
|
|
80
|
+
throw new DrftError('KITS ends inside its pieces');
|
|
81
|
+
const out = [];
|
|
82
|
+
for (let i = 0; i < count; i++)
|
|
83
|
+
out.push(view.getUint32(4 + i * 4, true));
|
|
84
|
+
checkKit(out, meshCount);
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
export function checkKit(ordinals, meshCount) {
|
|
88
|
+
let previous = -1;
|
|
89
|
+
for (const o of ordinals) {
|
|
90
|
+
if (o >= meshCount)
|
|
91
|
+
throw new DrftError(`KITS names mesh ${o}, and the file carries ${meshCount}`);
|
|
92
|
+
if (o <= previous)
|
|
93
|
+
throw new DrftError('KITS names its pieces out of order or twice');
|
|
94
|
+
previous = o;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function buildAssembly(assembly) {
|
|
98
|
+
const copies = assembly.pieces.length;
|
|
99
|
+
const bytes = 12 + assembly.surfaces.length * 4 + copies * COPY_BYTES;
|
|
100
|
+
const out = new Uint8Array(align(bytes));
|
|
101
|
+
const view = new DataView(out.buffer);
|
|
102
|
+
let at = 0;
|
|
103
|
+
const u32 = (v) => {
|
|
104
|
+
view.setUint32(at, v, true);
|
|
105
|
+
at += 4;
|
|
106
|
+
};
|
|
107
|
+
const f32 = (v) => {
|
|
108
|
+
view.setFloat32(at, v, true);
|
|
109
|
+
at += 4;
|
|
110
|
+
};
|
|
111
|
+
u32(assembly.attributes);
|
|
112
|
+
u32(assembly.surfaces.length / SURFACE_FLOATS);
|
|
113
|
+
for (let i = 0; i < assembly.surfaces.length; i++)
|
|
114
|
+
f32(assembly.surfaces[i]);
|
|
115
|
+
u32(copies);
|
|
116
|
+
for (let c = 0; c < copies; c++) {
|
|
117
|
+
u32(assembly.pieces[c]);
|
|
118
|
+
u32(assembly.surfaceOf[c]);
|
|
119
|
+
for (let i = 0; i < COPY_MATRIX_FLOATS; i++)
|
|
120
|
+
f32(assembly.transforms[c * COPY_MATRIX_FLOATS + i]);
|
|
121
|
+
for (let i = 0; i < COPY_UV_FLOATS; i++)
|
|
122
|
+
f32(assembly.uv[c * COPY_UV_FLOATS + i]);
|
|
123
|
+
}
|
|
124
|
+
if (at !== bytes)
|
|
125
|
+
throw new DrftError(`internal: MSHC wrote ${at} of ${bytes} bytes`);
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
/** One `MSHC` payload, checked on its own; its pieces are the caller's to check. */
|
|
129
|
+
export function readAssembly(buffer, offset, byteLength) {
|
|
130
|
+
const view = new DataView(buffer, offset, byteLength);
|
|
131
|
+
let at = 0;
|
|
132
|
+
const need = (bytes, what) => {
|
|
133
|
+
if (at + bytes > byteLength)
|
|
134
|
+
throw new DrftError(`MSHC ends inside ${what}`);
|
|
135
|
+
};
|
|
136
|
+
need(8, 'its header');
|
|
137
|
+
const attributes = view.getUint32(0, true);
|
|
138
|
+
const surfaceCount = view.getUint32(4, true);
|
|
139
|
+
at = 8;
|
|
140
|
+
need(surfaceCount * SURFACE_FLOATS * 4 + 4, 'its surfaces');
|
|
141
|
+
const surfaces = new Float32Array(surfaceCount * SURFACE_FLOATS);
|
|
142
|
+
for (let i = 0; i < surfaces.length; i++)
|
|
143
|
+
surfaces[i] = view.getFloat32(at + i * 4, true);
|
|
144
|
+
at += surfaces.length * 4;
|
|
145
|
+
const copies = view.getUint32(at, true);
|
|
146
|
+
at += 4;
|
|
147
|
+
need(copies * COPY_BYTES, 'its copies');
|
|
148
|
+
const pieces = new Uint32Array(copies);
|
|
149
|
+
const surfaceOf = new Uint32Array(copies);
|
|
150
|
+
const transforms = new Float32Array(copies * COPY_MATRIX_FLOATS);
|
|
151
|
+
const uv = new Float32Array(copies * COPY_UV_FLOATS);
|
|
152
|
+
for (let c = 0; c < copies; c++) {
|
|
153
|
+
pieces[c] = view.getUint32(at, true);
|
|
154
|
+
surfaceOf[c] = view.getUint32(at + 4, true);
|
|
155
|
+
at += 8;
|
|
156
|
+
for (let i = 0; i < COPY_MATRIX_FLOATS; i++, at += 4)
|
|
157
|
+
transforms[c * COPY_MATRIX_FLOATS + i] = view.getFloat32(at, true);
|
|
158
|
+
for (let i = 0; i < COPY_UV_FLOATS; i++, at += 4)
|
|
159
|
+
uv[c * COPY_UV_FLOATS + i] = view.getFloat32(at, true);
|
|
160
|
+
}
|
|
161
|
+
const assembly = { attributes, surfaces, pieces, surfaceOf, transforms, uv };
|
|
162
|
+
checkAssembly(assembly, 'MSHC');
|
|
163
|
+
return assembly;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* What an assembly must be on its own: known bits, whole records, surfaces it names, finite
|
|
167
|
+
* numbers, whole layers, and no copy flattened to nothing — whose normals would be a division by
|
|
168
|
+
* zero rather than a direction.
|
|
169
|
+
*/
|
|
170
|
+
export function checkAssembly(assembly, name) {
|
|
171
|
+
const { attributes, surfaces, pieces, surfaceOf, transforms, uv } = assembly;
|
|
172
|
+
if ((attributes & ~ASSEMBLY_ATTRIBUTES) !== 0) {
|
|
173
|
+
throw new DrftError(`${name} carries attribute bits ${attributes} an assembly cannot`);
|
|
174
|
+
}
|
|
175
|
+
if ((attributes & ATTR_LAYERS) !== 0 && (attributes & ATTR_UVS) === 0) {
|
|
176
|
+
throw new DrftError(`${name} names layers without texture coordinates for them to ride with`);
|
|
177
|
+
}
|
|
178
|
+
if ((attributes & ATTR_UVS) !== 0 && (attributes & ATTR_TANGENT) === 0) {
|
|
179
|
+
throw new DrftError(`${name} is textured without tangents; a copy's stretch follows the piece's tangent`);
|
|
180
|
+
}
|
|
181
|
+
const copies = pieces.length;
|
|
182
|
+
if (surfaces.length % SURFACE_FLOATS !== 0 ||
|
|
183
|
+
surfaceOf.length !== copies ||
|
|
184
|
+
transforms.length !== copies * COPY_MATRIX_FLOATS ||
|
|
185
|
+
uv.length !== copies * COPY_UV_FLOATS) {
|
|
186
|
+
throw new DrftError(`${name}'s surfaces and copies are not whole records`);
|
|
187
|
+
}
|
|
188
|
+
const surfaceCount = surfaces.length / SURFACE_FLOATS;
|
|
189
|
+
for (let s = 0; s < surfaceCount; s++) {
|
|
190
|
+
for (let i = 0; i < SURFACE_FLOATS; i++) {
|
|
191
|
+
if (!Number.isFinite(surfaces[s * SURFACE_FLOATS + i])) {
|
|
192
|
+
throw new DrftError(`${name}'s surface ${s} is not finite`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const layer = surfaces[s * SURFACE_FLOATS + SURFACE.layer];
|
|
196
|
+
if (!(layer >= 0) || Math.floor(layer) !== layer) {
|
|
197
|
+
throw new DrftError(`${name}'s surface ${s} names layer ${layer}, not a whole number`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
for (let c = 0; c < copies; c++) {
|
|
201
|
+
if (surfaceOf[c] >= surfaceCount) {
|
|
202
|
+
throw new DrftError(`${name}'s copy ${c} wears surface ${surfaceOf[c]} of ${surfaceCount}`);
|
|
203
|
+
}
|
|
204
|
+
const m = c * COPY_MATRIX_FLOATS;
|
|
205
|
+
for (let i = 0; i < COPY_MATRIX_FLOATS; i++) {
|
|
206
|
+
if (!Number.isFinite(transforms[m + i])) {
|
|
207
|
+
throw new DrftError(`${name}'s copy ${c} has a matrix that is not finite`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (let i = 0; i < COPY_UV_FLOATS; i++) {
|
|
211
|
+
if (!Number.isFinite(uv[c * COPY_UV_FLOATS + i])) {
|
|
212
|
+
throw new DrftError(`${name}'s copy ${c} has a texture stretch that is not finite`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (Math.abs(determinant(transforms, m)) < 1e-12) {
|
|
216
|
+
throw new DrftError(`${name}'s copy ${c} is flattened to nothing by its matrix`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** The determinant of the linear part of the copy matrix at `m`. */
|
|
221
|
+
export function determinant(t, m) {
|
|
222
|
+
const a = t[m];
|
|
223
|
+
const b = t[m + 1];
|
|
224
|
+
const c = t[m + 2];
|
|
225
|
+
const d = t[m + 3];
|
|
226
|
+
const e = t[m + 4];
|
|
227
|
+
const f = t[m + 5];
|
|
228
|
+
const g = t[m + 6];
|
|
229
|
+
const h = t[m + 7];
|
|
230
|
+
const i = t[m + 8];
|
|
231
|
+
return a * (e * i - f * h) - d * (b * i - c * h) + g * (b * f - c * e);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* What an assembly must be to the kit around it: every copy a declared piece that comes before it
|
|
235
|
+
* in the file — so a streaming reader holds the piece when the copy lands — and every piece
|
|
236
|
+
* carrying what the assembly asks of it. Shared by the writer and both readers, so a file one
|
|
237
|
+
* refuses the others refuse too.
|
|
238
|
+
*/
|
|
239
|
+
export function checkCopies(assembly, ordinal, kit, piece) {
|
|
240
|
+
const textured = (assembly.attributes & ATTR_UVS) !== 0;
|
|
241
|
+
const tangents = textured || (assembly.attributes & ATTR_TANGENT) !== 0;
|
|
242
|
+
const seen = new Set();
|
|
243
|
+
for (let c = 0; c < assembly.pieces.length; c++) {
|
|
244
|
+
const o = assembly.pieces[c];
|
|
245
|
+
if (seen.has(o))
|
|
246
|
+
continue;
|
|
247
|
+
seen.add(o);
|
|
248
|
+
if (!kit.has(o)) {
|
|
249
|
+
throw new DrftError(`mesh ${ordinal} copies mesh ${o}, which KITS does not name a piece`);
|
|
250
|
+
}
|
|
251
|
+
if (o >= ordinal) {
|
|
252
|
+
throw new DrftError(`mesh ${ordinal} copies piece ${o}, which comes after it; a piece is written before the meshes assembled from it`);
|
|
253
|
+
}
|
|
254
|
+
const mesh = piece(o);
|
|
255
|
+
if (mesh === undefined)
|
|
256
|
+
throw new DrftError(`mesh ${ordinal} copies piece ${o}, which has not arrived`);
|
|
257
|
+
if (textured && mesh.uvs === undefined) {
|
|
258
|
+
throw new DrftError(`mesh ${ordinal} is textured and piece ${o} has no texture coordinates`);
|
|
259
|
+
}
|
|
260
|
+
if (tangents && mesh.tangents === undefined) {
|
|
261
|
+
throw new DrftError(`mesh ${ordinal} needs tangents and piece ${o} has none`);
|
|
262
|
+
}
|
|
263
|
+
if ((assembly.attributes & ATTR_CHANNEL) !== 0 && mesh.channel === undefined) {
|
|
264
|
+
throw new DrftError(`mesh ${ordinal} sways and piece ${o} carries no channel to sway by`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
package/dist/drftFormat.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare const DRFT_VERSION_MAJOR = 1;
|
|
|
20
20
|
* Additive revisions within a generation: new optional chunks, new attribute bits, new
|
|
21
21
|
* enum values with a defined fallback. Never a changed meaning.
|
|
22
22
|
*/
|
|
23
|
-
export declare const DRFT_VERSION_MINOR =
|
|
23
|
+
export declare const DRFT_VERSION_MINOR = 23;
|
|
24
24
|
/** Bytes before the chunk table. */
|
|
25
25
|
export declare const HEADER_BYTES = 32;
|
|
26
26
|
/** Bytes per chunk table entry. */
|
|
@@ -231,6 +231,30 @@ export declare const CHUNK_INST: number;
|
|
|
231
231
|
* See `drftQuantised.ts`. Added in 1.18.
|
|
232
232
|
*/
|
|
233
233
|
export declare const CHUNK_MSHQ: number;
|
|
234
|
+
/**
|
|
235
|
+
* One region of a streamed world: its levels of detail as mesh ordinals, its prop groups, its
|
|
236
|
+
* occluders and its collision. **Required**, one chunk per region, written just ahead of the meshes
|
|
237
|
+
* it introduces; see `drftRegions.ts`. Added in 1.21.
|
|
238
|
+
*/
|
|
239
|
+
export declare const CHUNK_REGN: number;
|
|
240
|
+
/**
|
|
241
|
+
* A world's fixed lights summed offline into one dense volume. Optional: a reader that skips it
|
|
242
|
+
* loses the glow of every light past the ones it shades exactly. See `drftLightVolume.ts`. Added in
|
|
243
|
+
* 1.22.
|
|
244
|
+
*/
|
|
245
|
+
export declare const CHUNK_LVOL: number;
|
|
246
|
+
/**
|
|
247
|
+
* The meshes that are pieces of a kit, drawn only as copies inside assembled meshes. **Required**,
|
|
248
|
+
* and written ahead of the geometry; see `drftAssembly.ts`. Added in 1.23.
|
|
249
|
+
*/
|
|
250
|
+
export declare const CHUNK_KITS: number;
|
|
251
|
+
/**
|
|
252
|
+
* A mesh carried as painted copies of kit pieces, expanded on read into the `MeshData` a `MESH`
|
|
253
|
+
* gives. **Required**, and counted as a mesh in every ordinal. See `drftAssembly.ts`. Added in 1.23.
|
|
254
|
+
*/
|
|
255
|
+
export declare const CHUNK_MSHC: number;
|
|
256
|
+
/** Whether a chunk is a mesh in the ordinal sequence: plain, quantised or assembled. */
|
|
257
|
+
export declare function isMeshChunk(code: number): boolean;
|
|
234
258
|
/** Bytes before the records in a `SPLT` payload. See `DrftSplatBlock` for the fields. */
|
|
235
259
|
export declare const SPLAT_BLOCK_PREFIX = 40;
|
|
236
260
|
/**
|
|
@@ -323,6 +347,24 @@ export declare const ATTR_TANGENT: number;
|
|
|
323
347
|
export declare const ATTR_JOINTS: number;
|
|
324
348
|
/** How much each of `ATTR_JOINTS`' four influences moves the vertex. See it for the whole story. */
|
|
325
349
|
export declare const ATTR_WEIGHTS: number;
|
|
350
|
+
/**
|
|
351
|
+
* Added in 1.20: which image of a texture array each vertex's face wears. See `MeshData.layers`.
|
|
352
|
+
*
|
|
353
|
+
* Next free bit, appended last, for the reason `grain` gives at length: a 1.19 reader meets the
|
|
354
|
+
* arrays it knows where it expects them, stops before this one, and draws every face at layer 0 —
|
|
355
|
+
* the first image of the array, which is what a reader that has never heard of layers should
|
|
356
|
+
* produce.
|
|
357
|
+
*/
|
|
358
|
+
export declare const ATTR_LAYERS: number;
|
|
359
|
+
/**
|
|
360
|
+
* Added in 1.23: `MeshData.channel`, four floats a vertex — sway, sky, opacity, and a spare lane.
|
|
361
|
+
*
|
|
362
|
+
* **The writer dropped it without a word until then**, which is the silent kind of missing: a tree
|
|
363
|
+
* baked with its sway came back still, and nothing said the lane had not been written. Next free
|
|
364
|
+
* bit, appended last, so a 1.22 reader meets every array it knows where it expects them, stops
|
|
365
|
+
* before this one, and draws the mesh planted, sunlit and opaque — the absent lane's own meaning.
|
|
366
|
+
*/
|
|
367
|
+
export declare const ATTR_CHANNEL: number;
|
|
326
368
|
/** Everything this version defines, so a reader can spot bits from a later writer. */
|
|
327
369
|
export declare const ATTR_KNOWN: number;
|
|
328
370
|
/**
|