@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.
Files changed (67) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/README.md +9 -0
  4. package/dist/animationData.d.ts +53 -0
  5. package/dist/animationData.js +13 -0
  6. package/dist/coarseFirst.d.ts +27 -0
  7. package/dist/coarseFirst.js +118 -0
  8. package/dist/drftColliders.d.ts +53 -0
  9. package/dist/drftColliders.js +137 -0
  10. package/dist/drftFormat.d.ts +454 -0
  11. package/dist/drftFormat.js +350 -0
  12. package/dist/drftRead.d.ts +121 -0
  13. package/dist/drftRead.js +487 -0
  14. package/dist/drftSkin.d.ts +40 -0
  15. package/dist/drftSkin.js +270 -0
  16. package/dist/drftStream.d.ts +170 -0
  17. package/dist/drftStream.js +315 -0
  18. package/dist/drftSubs.d.ts +18 -0
  19. package/dist/drftSubs.js +70 -0
  20. package/dist/drftWrite.d.ts +103 -0
  21. package/dist/drftWrite.js +478 -0
  22. package/dist/fixtures/v1-0.d.ts +9 -0
  23. package/dist/fixtures/v1-0.js +9 -0
  24. package/dist/fixtures/v1-1.d.ts +16 -0
  25. package/dist/fixtures/v1-1.js +16 -0
  26. package/dist/fixtures/v1-11.d.ts +14 -0
  27. package/dist/fixtures/v1-11.js +14 -0
  28. package/dist/fixtures/v1-2.d.ts +19 -0
  29. package/dist/fixtures/v1-2.js +19 -0
  30. package/dist/fixtures/v1-3.d.ts +18 -0
  31. package/dist/fixtures/v1-3.js +18 -0
  32. package/dist/fixtures/v1-4.d.ts +13 -0
  33. package/dist/fixtures/v1-4.js +13 -0
  34. package/dist/fixtures/v1-5.d.ts +14 -0
  35. package/dist/fixtures/v1-5.js +14 -0
  36. package/dist/fixtures/v1-6.d.ts +9 -0
  37. package/dist/fixtures/v1-6.js +9 -0
  38. package/dist/fixtures/v1-7.d.ts +9 -0
  39. package/dist/fixtures/v1-7.js +9 -0
  40. package/dist/fixtures/v1-9.d.ts +9 -0
  41. package/dist/fixtures/v1-9.js +9 -0
  42. package/dist/index.d.ts +26 -0
  43. package/dist/index.js +29 -0
  44. package/dist/meshData.d.ts +210 -0
  45. package/dist/meshData.js +105 -0
  46. package/package.json +57 -0
  47. package/src/animationData.ts +58 -0
  48. package/src/coarseFirst.ts +113 -0
  49. package/src/drftColliders.ts +153 -0
  50. package/src/drftFormat.ts +537 -0
  51. package/src/drftRead.ts +652 -0
  52. package/src/drftSkin.ts +326 -0
  53. package/src/drftStream.ts +436 -0
  54. package/src/drftSubs.ts +86 -0
  55. package/src/drftWrite.ts +613 -0
  56. package/src/fixtures/v1-0.ts +10 -0
  57. package/src/fixtures/v1-1.ts +17 -0
  58. package/src/fixtures/v1-11.ts +15 -0
  59. package/src/fixtures/v1-2.ts +20 -0
  60. package/src/fixtures/v1-3.ts +19 -0
  61. package/src/fixtures/v1-4.ts +14 -0
  62. package/src/fixtures/v1-5.ts +15 -0
  63. package/src/fixtures/v1-6.ts +10 -0
  64. package/src/fixtures/v1-7.ts +10 -0
  65. package/src/fixtures/v1-9.ts +10 -0
  66. package/src/index.ts +56 -0
  67. package/src/meshData.ts +301 -0
@@ -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
+ }