@driftengine/drft 3.61.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 +202 -0
- package/NOTICE +9 -0
- package/README.md +9 -0
- package/dist/animationData.d.ts +53 -0
- package/dist/animationData.js +13 -0
- package/dist/coarseFirst.d.ts +27 -0
- package/dist/coarseFirst.js +118 -0
- package/dist/drftColliders.d.ts +53 -0
- package/dist/drftColliders.js +137 -0
- package/dist/drftFormat.d.ts +454 -0
- package/dist/drftFormat.js +350 -0
- package/dist/drftRead.d.ts +121 -0
- package/dist/drftRead.js +487 -0
- package/dist/drftSkin.d.ts +40 -0
- package/dist/drftSkin.js +270 -0
- package/dist/drftStream.d.ts +170 -0
- package/dist/drftStream.js +315 -0
- package/dist/drftSubs.d.ts +18 -0
- package/dist/drftSubs.js +70 -0
- package/dist/drftWrite.d.ts +103 -0
- package/dist/drftWrite.js +478 -0
- package/dist/fixtures/v1-0.d.ts +9 -0
- package/dist/fixtures/v1-0.js +9 -0
- package/dist/fixtures/v1-1.d.ts +16 -0
- package/dist/fixtures/v1-1.js +16 -0
- package/dist/fixtures/v1-11.d.ts +14 -0
- package/dist/fixtures/v1-11.js +14 -0
- package/dist/fixtures/v1-2.d.ts +19 -0
- package/dist/fixtures/v1-2.js +19 -0
- package/dist/fixtures/v1-3.d.ts +18 -0
- package/dist/fixtures/v1-3.js +18 -0
- package/dist/fixtures/v1-4.d.ts +13 -0
- package/dist/fixtures/v1-4.js +13 -0
- package/dist/fixtures/v1-5.d.ts +14 -0
- package/dist/fixtures/v1-5.js +14 -0
- package/dist/fixtures/v1-6.d.ts +9 -0
- package/dist/fixtures/v1-6.js +9 -0
- package/dist/fixtures/v1-7.d.ts +9 -0
- package/dist/fixtures/v1-7.js +9 -0
- package/dist/fixtures/v1-9.d.ts +9 -0
- package/dist/fixtures/v1-9.js +9 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +29 -0
- package/dist/meshData.d.ts +210 -0
- package/dist/meshData.js +105 -0
- package/package.json +57 -0
- package/src/animationData.ts +58 -0
- package/src/coarseFirst.ts +113 -0
- package/src/drftColliders.ts +153 -0
- package/src/drftFormat.ts +537 -0
- package/src/drftRead.ts +652 -0
- package/src/drftSkin.ts +326 -0
- package/src/drftStream.ts +436 -0
- package/src/drftSubs.ts +86 -0
- package/src/drftWrite.ts +613 -0
- package/src/fixtures/v1-0.ts +10 -0
- package/src/fixtures/v1-1.ts +17 -0
- package/src/fixtures/v1-11.ts +15 -0
- package/src/fixtures/v1-2.ts +20 -0
- package/src/fixtures/v1-3.ts +19 -0
- package/src/fixtures/v1-4.ts +14 -0
- package/src/fixtures/v1-5.ts +15 -0
- package/src/fixtures/v1-6.ts +10 -0
- package/src/fixtures/v1-7.ts +10 -0
- package/src/fixtures/v1-9.ts +10 -0
- package/src/index.ts +56 -0
- package/src/meshData.ts +301 -0
package/src/meshData.ts
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A description of vertex data, and the check that it is coherent.
|
|
3
|
+
*
|
|
4
|
+
* It lives in the format package rather than the renderer because it is the boundary object
|
|
5
|
+
* between them: the container's whole job is to carry one of these, and every import the
|
|
6
|
+
* format code took from `render` was this type or its validator. Putting it here is what lets
|
|
7
|
+
* the container be read and written by something that never draws.
|
|
8
|
+
*
|
|
9
|
+
* The renderer re-exports both, so nothing that consumed them from there has to move.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface MeshData {
|
|
13
|
+
positions: Float32Array;
|
|
14
|
+
normals: Float32Array;
|
|
15
|
+
colors: Float32Array;
|
|
16
|
+
/** One float per vertex: self-illumination, revealed only at night. */
|
|
17
|
+
emissive: Float32Array;
|
|
18
|
+
/**
|
|
19
|
+
* One float per vertex, 0–1: how sharply this surface takes a sun highlight.
|
|
20
|
+
*
|
|
21
|
+
* Optional, and that is a cost decision rather than a convenience one. Seventeen
|
|
22
|
+
* places build a `MeshData`, and only a handful of props shine — so rather than make every
|
|
23
|
+
* producer fill an array of zeroes, an absent array leaves attribute 4 disabled and
|
|
24
|
+
* the shader reads the constant WebGL supplies for it. A world with no shiny
|
|
25
|
+
* geometry allocates nothing and uploads nothing.
|
|
26
|
+
*/
|
|
27
|
+
specular?: Float32Array;
|
|
28
|
+
/**
|
|
29
|
+
* Four floats per vertex: a tangent, and the handedness of the bitangent in `w`.
|
|
30
|
+
*
|
|
31
|
+
* Optional for the same cost reason as `specular` and `uvs`, and the reason bites harder here:
|
|
32
|
+
* four floats a vertex is the widest optional attribute in the format, and only geometry that
|
|
33
|
+
* carries a normal map has any use for it. A world with none allocates nothing, uploads
|
|
34
|
+
* nothing, and reads the constant its backend supplies.
|
|
35
|
+
*
|
|
36
|
+
* `w` is ±1. The bitangent is `cross(normal, tangent) * w`, and the sign is what keeps a
|
|
37
|
+
* mirrored UV layout from lighting one side of a model inside out. See `generateTangents`.
|
|
38
|
+
*/
|
|
39
|
+
tangents?: Float32Array;
|
|
40
|
+
/**
|
|
41
|
+
* Two floats per vertex: where this vertex sits in a surface texture.
|
|
42
|
+
*
|
|
43
|
+
* Optional for the same cost reason as `specular`, and the reason carries further
|
|
44
|
+
* here because most geometry in this engine is coloured rather than textured. An
|
|
45
|
+
* absent array leaves attribute 5 disabled and the shader reads the constant WebGL
|
|
46
|
+
* supplies, so a world with no textured surfaces allocates nothing and uploads
|
|
47
|
+
* nothing — and none of the existing `MeshData` producers had to change to gain a
|
|
48
|
+
* coordinate they will never use.
|
|
49
|
+
*
|
|
50
|
+
* Only meaningful alongside a `SurfaceTexture` at draw time. Geometry carrying UVs
|
|
51
|
+
* and drawn without a texture shades from its vertex colours exactly as before.
|
|
52
|
+
*/
|
|
53
|
+
uvs?: Float32Array;
|
|
54
|
+
/**
|
|
55
|
+
* Three floats per vertex: the colour this vertex emits, independent of its albedo.
|
|
56
|
+
*
|
|
57
|
+
* Optional, and absent means what this engine always did — the emissive term is the
|
|
58
|
+
* surface's own colour scaled by `emissive`, which is right for anything glowing because
|
|
59
|
+
* it is hot or lit from within. It is wrong whenever the glow is a different colour from
|
|
60
|
+
* the paint, and the approximation gets brightness right and hue wrong: a ceiling tile
|
|
61
|
+
* emitting a dull warm haze over a pale panel cannot be expressed by scaling the panel.
|
|
62
|
+
*
|
|
63
|
+
* A negative component means "inherit the albedo", which is what an absent array supplies
|
|
64
|
+
* for every vertex — so a mesh that names a colour for some of its geometry and not the
|
|
65
|
+
* rest is one buffer rather than two meshes.
|
|
66
|
+
*/
|
|
67
|
+
emissiveColor?: Float32Array;
|
|
68
|
+
/**
|
|
69
|
+
* One float per vertex, 0–1: how rough this surface is, which is the *shape* of its
|
|
70
|
+
* highlight rather than its strength.
|
|
71
|
+
*
|
|
72
|
+
* Absent means the old constant, so nothing existing changes. It matters because a
|
|
73
|
+
* highlight's width is not something intensity can express: a polished floor seen at a
|
|
74
|
+
* grazing angle smears a lamp into a long streak down the view direction, and a tight
|
|
75
|
+
* fixed lobe can only ever make a small round dot brighter. `specular` says how much
|
|
76
|
+
* light comes back; this says over how wide an angle.
|
|
77
|
+
*/
|
|
78
|
+
roughness?: Float32Array;
|
|
79
|
+
/**
|
|
80
|
+
* One float per vertex, 0–1: how much visible mineral structure this surface has.
|
|
81
|
+
*
|
|
82
|
+
* **Absent means none**, which is the one thing the previous two attempts could not say.
|
|
83
|
+
* Grain was first gated on `specular > 0`, then weighted by `roughness`, and both are
|
|
84
|
+
* proxies rather than statements: painted plaster is rough and has no grain, polished
|
|
85
|
+
* granite is smooth and has a great deal of it. Deriving either property from the other
|
|
86
|
+
* guesses, and the guess was wrong in both directions — a painted tower at roughness 0.55
|
|
87
|
+
* took 55% grain and read as marble.
|
|
88
|
+
*
|
|
89
|
+
* So it is its own attribute, exactly as `specular` and `roughness` are, and a surface
|
|
90
|
+
* states it while it is being built. This is the *material* half of the answer; how strong
|
|
91
|
+
* the pattern is at a given amount belongs to the shader's constants.
|
|
92
|
+
*/
|
|
93
|
+
grain?: Float32Array;
|
|
94
|
+
/**
|
|
95
|
+
* How much microscopic relief a surface has, 0 to 1. Absent means a perfectly smooth one.
|
|
96
|
+
*
|
|
97
|
+
* **The sibling of `grain`, and the difference between them is the whole point.** Grain says
|
|
98
|
+
* how much *light* a point takes, so it varies brightness across a face that stays flat. This
|
|
99
|
+
* says which way the surface is *facing*, so it varies the direction light leaves it. Only the
|
|
100
|
+
* second gives a surface texture that survives a shallow angle and moves as you walk past:
|
|
101
|
+
* asphalt aggregate, cast concrete, orange peel on paint, hammered metal, plaster stipple.
|
|
102
|
+
* Neither stands in for the other, which is why it is a second attribute rather than a
|
|
103
|
+
* weighting of the first.
|
|
104
|
+
*
|
|
105
|
+
* Absent means none, so every mesh built before this existed is unchanged. How coarse the
|
|
106
|
+
* relief is and how strong belongs to the material rather than the geometry, since that is what
|
|
107
|
+
* separates asphalt from plaster, and the pass states it: see `Renderer.setSurfaceRelief`.
|
|
108
|
+
*/
|
|
109
|
+
relief?: Float32Array;
|
|
110
|
+
/**
|
|
111
|
+
* Four floats per vertex, and the only attribute whose lanes mean four different things.
|
|
112
|
+
*
|
|
113
|
+
* `.x` **sway**: how far the shared wind moves this vertex, along the wind's own direction.
|
|
114
|
+
* `.y` **skyDirect**: how much of the directional term this vertex receives, 0 to 1.
|
|
115
|
+
* `.z` **alpha**: multiplies the draw's own opacity and the texture's cutout coverage.
|
|
116
|
+
* `.w` is reserved, declared and unread.
|
|
117
|
+
*
|
|
118
|
+
* **One attribute and not three, because locations are the scarce resource here.** WebGL2
|
|
119
|
+
* guarantees sixteen vertex attribute locations, eleven are already spent, and an instanced
|
|
120
|
+
* draw spends all sixteen. A `vec4` costs the same one location a `float` would, so the spare
|
|
121
|
+
* lane is free and the next per-vertex question does not have to re-argue the budget.
|
|
122
|
+
*
|
|
123
|
+
* **`skyDirect` is a separate lane rather than a factor in `colors`, and that is the defect it
|
|
124
|
+
* exists to fix.** The shader reads `albedo = vColor` and then `lit = albedo * (ambient + sun)`,
|
|
125
|
+
* so a sky factor carried in the vertex colour scales both terms and an enclosed face is
|
|
126
|
+
* darkened twice — once for having no sky, once for the ambient it should still have received.
|
|
127
|
+
* A consumer reporting this raised a floor constant to 0.45 to compensate and measured what it
|
|
128
|
+
* cost: 55% of a chunk's vertices sat between 0.10 and 0.20.
|
|
129
|
+
*
|
|
130
|
+
* Absent means `(0, 1, 1, 0)`: planted, fully sunlit, opaque. Every mesh built before this
|
|
131
|
+
* existed is unchanged to the bit, which is what the absent-attribute constant buys.
|
|
132
|
+
*
|
|
133
|
+
* **Sway is authored, not derived.** `scatter.ts` squares its own falloff because it computes
|
|
134
|
+
* one from height, where a linear response slides a whole plant sideways and reads as the
|
|
135
|
+
* ground moving. Here the author writes the curve — 0 on a trunk, 1 at a leaf tip — so squaring
|
|
136
|
+
* it would overrule a shape somebody had already chosen. What that costs is that a lane filled
|
|
137
|
+
* linearly up a trunk gives a tree that slides at its base, and the fix is the curve.
|
|
138
|
+
*/
|
|
139
|
+
channel?: Float32Array;
|
|
140
|
+
/**
|
|
141
|
+
* Four floats per vertex: which joints move this vertex, as indices into a skinning palette.
|
|
142
|
+
*
|
|
143
|
+
* Optional for the same cost reason as every attribute above it, and the reason is strongest
|
|
144
|
+
* here: only a skinned character has any use for one, and a world of walls and props allocates
|
|
145
|
+
* nothing and uploads nothing. Absent means unskinned, and an unskinned mesh takes exactly the
|
|
146
|
+
* draw path it took before skinning existed.
|
|
147
|
+
*
|
|
148
|
+
* **Four influences and not eight.** Four is what glTF's `JOINTS_0` carries, what every DCC tool
|
|
149
|
+
* exports without being asked, and what fits one attribute. Eight doubles the attribute cost of
|
|
150
|
+
* every skinned vertex to serve a case that rarely survives an export.
|
|
151
|
+
*
|
|
152
|
+
* **`Float32Array` and not `Uint8Array`, which costs twelve bytes a vertex.** Every attribute in
|
|
153
|
+
* this format is float32 and the absent-attribute mechanism depends on it: `mesh.ts` attaches
|
|
154
|
+
* with `gl.FLOAT`, `ABSENT_ATTRIBUTE` is a number array, and `buffers.ts` interleaves floats and
|
|
155
|
+
* writes float constants for what a mesh omits. An integer attribute needs `vertexAttribIPointer`
|
|
156
|
+
* on one backend, a `uint8x4` entry on the other, and an integer arm through the constants buffer
|
|
157
|
+
* — four places where one question starts being answered twice. Integers to 2^24 are exact in
|
|
158
|
+
* float32, so an index is never rounded. What would make it wrong is a consumer whose payload is
|
|
159
|
+
* dominated by skinned meshes; the fix is a `uint8x4` attribute and that fourth mechanism.
|
|
160
|
+
*/
|
|
161
|
+
joints?: Float32Array;
|
|
162
|
+
/**
|
|
163
|
+
* Four floats per vertex: how much each of `joints`' four influences moves this vertex.
|
|
164
|
+
*
|
|
165
|
+
* Normalised by the importer rather than by the shader, because normalising per vertex per frame
|
|
166
|
+
* costs a divide on every vertex to correct data that should have been fixed once. `gltfSkin.ts`
|
|
167
|
+
* normalises on the way in and warns when a set does not sum to one.
|
|
168
|
+
*
|
|
169
|
+
* Absent means unskinned, and it must be absent exactly when `joints` is — one without the other
|
|
170
|
+
* is refused below rather than drawn.
|
|
171
|
+
*/
|
|
172
|
+
weights?: Float32Array;
|
|
173
|
+
/**
|
|
174
|
+
* Morph target position deltas: `morphTargetCount` targets, three floats a vertex each.
|
|
175
|
+
*
|
|
176
|
+
* Laid out **interleaved by vertex** — every target of one vertex adjacent — because the shader
|
|
177
|
+
* reads all of a vertex's targets together and nothing reads one target across many vertices.
|
|
178
|
+
* So the index of target `t` for vertex `v` is `(v * count + t) * 3`.
|
|
179
|
+
*
|
|
180
|
+
* A delta rather than an absolute position, which is what makes them additive: several targets
|
|
181
|
+
* apply at once by weight, and a weight of zero contributes nothing rather than dragging the
|
|
182
|
+
* vertex toward some other shape.
|
|
183
|
+
*
|
|
184
|
+
* **Positions only.** A target that also moved normals would double this array and add a second
|
|
185
|
+
* fetch per target in the vertex stage; what it gives up is shading that lags a strongly morphed
|
|
186
|
+
* surface. What would make it wrong is a face rig, where the lighting is most of the effect.
|
|
187
|
+
*/
|
|
188
|
+
morphTargets?: Float32Array;
|
|
189
|
+
/** How many targets `morphTargets` holds. Absent means none, whatever the array says. */
|
|
190
|
+
morphTargetCount?: number;
|
|
191
|
+
indices: Uint32Array;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Every attribute covers every vertex, and every index names one that exists.
|
|
196
|
+
*
|
|
197
|
+
* **Why this is checked rather than assumed.** WebGL does not require a driver to survive
|
|
198
|
+
* an attribute buffer that is too small for the draw: the implementation may read zeroes,
|
|
199
|
+
* and it may equally **drop the draw entirely**. Desktop drivers clamp and carry on, which
|
|
200
|
+
* means a mesh with one short array renders perfectly for the author and vanishes without
|
|
201
|
+
* a word on hardware they do not own. There is no GL error either way, so nothing
|
|
202
|
+
* downstream can tell the difference between a mesh that was rejected and a mesh that was
|
|
203
|
+
* never submitted.
|
|
204
|
+
*
|
|
205
|
+
* Thrown rather than warned, because this is construction and not a frame: a mesh built
|
|
206
|
+
* from inconsistent arrays is a bug in the caller with exactly one correct outcome, and
|
|
207
|
+
* the engine's rule is that initialisation fails loudly so a running frame never has to.
|
|
208
|
+
*
|
|
209
|
+
* The message names the array, its length, and the length it should have had — everything
|
|
210
|
+
* needed to fix it without a debugger, since the machine where this fires may not have one.
|
|
211
|
+
*/
|
|
212
|
+
export function validateMeshData(data: MeshData): void {
|
|
213
|
+
if (data.positions.length % 3 !== 0) {
|
|
214
|
+
throw new Error(`Mesh: positions has ${data.positions.length} floats, not a multiple of 3`);
|
|
215
|
+
}
|
|
216
|
+
const vertices = data.positions.length / 3;
|
|
217
|
+
|
|
218
|
+
const check = (name: string, array: Float32Array | undefined, perVertex: number): void => {
|
|
219
|
+
if (array === undefined) return;
|
|
220
|
+
if (array.length !== vertices * perVertex) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`Mesh: ${name} has ${array.length} floats for ${vertices} vertices; ` +
|
|
223
|
+
`expected ${vertices * perVertex} (${perVertex} per vertex). A short attribute ` +
|
|
224
|
+
`buffer is not a harmless one — a driver may draw zeroes or drop the draw entirely.`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
check('normals', data.normals, 3);
|
|
229
|
+
check('colors', data.colors, 3);
|
|
230
|
+
check('emissive', data.emissive, 1);
|
|
231
|
+
check('specular', data.specular, 1);
|
|
232
|
+
check('uvs', data.uvs, 2);
|
|
233
|
+
check('emissiveColor', data.emissiveColor, 3);
|
|
234
|
+
check('roughness', data.roughness, 1);
|
|
235
|
+
check('grain', data.grain, 1);
|
|
236
|
+
check('relief', data.relief, 1);
|
|
237
|
+
check('channel', data.channel, 4);
|
|
238
|
+
/*
|
|
239
|
+
* `tangents` was declared optional on 2026-08-22 and was not checked here until 2026-08-25, which
|
|
240
|
+
* made the widest optional attribute in the format the one a short buffer could reach a driver
|
|
241
|
+
* through — the exact hazard this function's header describes, unguarded, for three releases.
|
|
242
|
+
*/
|
|
243
|
+
check('tangents', data.tangents, 4);
|
|
244
|
+
check('joints', data.joints, 4);
|
|
245
|
+
check('weights', data.weights, 4);
|
|
246
|
+
|
|
247
|
+
/*
|
|
248
|
+
* Both or neither. The shader multiplies four matrices by four weights, so a missing half means
|
|
249
|
+
* either every weight or every index is the absent-attribute constant — and both collapse the
|
|
250
|
+
* mesh onto joint zero at the origin, which draws a picture rather than raising anything.
|
|
251
|
+
*
|
|
252
|
+
* What it costs is that a caller cannot supply indices alone. What would make it wrong is a path
|
|
253
|
+
* that skins from indices with implicit unit weights; nothing proposes one, and it would be a
|
|
254
|
+
* different attribute rather than a relaxation of this rule.
|
|
255
|
+
*/
|
|
256
|
+
/*
|
|
257
|
+
* Morph deltas are checked against their own declared count rather than through `check`, because
|
|
258
|
+
* they are the one attribute whose length is not a fixed multiple of the vertex count — it scales
|
|
259
|
+
* with the targets too. A short array is the same hazard as any other: the tail reads as zero, so
|
|
260
|
+
* the last targets silently never move anything.
|
|
261
|
+
*/
|
|
262
|
+
if (data.morphTargets !== undefined || data.morphTargetCount !== undefined) {
|
|
263
|
+
const count = data.morphTargetCount ?? 0;
|
|
264
|
+
if (data.morphTargets === undefined || count < 1) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
'Mesh: morphTargets and morphTargetCount go together — one without the other is a mesh ' +
|
|
267
|
+
'that declares a deformation it cannot perform',
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
const expected = vertices * count * 3;
|
|
271
|
+
if (data.morphTargets.length !== expected) {
|
|
272
|
+
throw new Error(
|
|
273
|
+
`Mesh: morphTargets has ${data.morphTargets.length} floats for ${vertices} vertices and ` +
|
|
274
|
+
`${count} targets; expected ${expected} (3 per vertex per target).`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if ((data.joints === undefined) !== (data.weights === undefined)) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
data.joints === undefined
|
|
282
|
+
? 'Mesh: weights without joints — a skinned mesh needs both, or every vertex collapses onto joint 0'
|
|
283
|
+
: 'Mesh: joints without weights — a skinned mesh needs both, or every vertex collapses onto joint 0',
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/*
|
|
288
|
+
* An index past the end is the same hazard from the other side, and the same latitude
|
|
289
|
+
* applies: a driver may clamp it or refuse the draw. Scanned once at construction, which
|
|
290
|
+
* is a cost paid when a mesh is built rather than when it is drawn.
|
|
291
|
+
*/
|
|
292
|
+
for (let at = 0; at < data.indices.length; at++) {
|
|
293
|
+
const index = data.indices[at] as number;
|
|
294
|
+
if (index >= vertices) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`Mesh: index ${at} refers to vertex ${index}, but the mesh has ${vertices}. ` +
|
|
297
|
+
`Out-of-range indices may be clamped on one driver and reject the draw on another.`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|