@driftengine/core 4.9.0 → 4.10.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 (135) hide show
  1. package/CHANGELOG.json +62 -11
  2. package/README.md +43 -38
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +4 -3
  5. package/dist/render/ambientHarmonics.d.ts +1 -1
  6. package/dist/render/ambientHarmonics.js +1 -1
  7. package/dist/render/areaLights.d.ts +4 -4
  8. package/dist/render/areaLights.js +1 -1
  9. package/dist/render/backend/api.d.ts +10 -3
  10. package/dist/render/backend/select.js +11 -5
  11. package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
  12. package/dist/render/backend/webgl2/boneAnimations.js +80 -0
  13. package/dist/render/backend/webgl2/instanced.d.ts +5 -1
  14. package/dist/render/backend/webgl2/instanced.js +13 -1
  15. package/dist/render/backend/webgl2/renderer.d.ts +90 -2
  16. package/dist/render/backend/webgl2/renderer.js +253 -24
  17. package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
  18. package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
  19. package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
  20. package/dist/render/backend/webgpu/boneAnimations.js +76 -0
  21. package/dist/render/backend/webgpu/buffers.d.ts +19 -4
  22. package/dist/render/backend/webgpu/buffers.js +18 -11
  23. package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
  24. package/dist/render/backend/webgpu/compressedUpload.js +53 -8
  25. package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
  26. package/dist/render/backend/webgpu/depthPass.js +49 -14
  27. package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
  28. package/dist/render/backend/webgpu/drawCommand.js +10 -3
  29. package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
  30. package/dist/render/backend/webgpu/flatPass.js +136 -20
  31. package/dist/render/backend/webgpu/instanced.d.ts +14 -1
  32. package/dist/render/backend/webgpu/instanced.js +24 -1
  33. package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
  34. package/dist/render/backend/webgpu/materialStore.js +265 -0
  35. package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
  36. package/dist/render/backend/webgpu/pipelineCache.js +7 -0
  37. package/dist/render/backend/webgpu/renderer.d.ts +152 -35
  38. package/dist/render/backend/webgpu/renderer.js +787 -142
  39. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
  40. package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
  41. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
  42. package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
  43. package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
  44. package/dist/render/backend/webgpu/staticBundles.js +241 -0
  45. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
  46. package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
  47. package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
  48. package/dist/render/backend/webgpu/uniformRing.js +9 -1
  49. package/dist/render/bloomChain.d.ts +3 -3
  50. package/dist/render/boneAnimation.d.ts +92 -0
  51. package/dist/render/boneAnimation.js +133 -0
  52. package/dist/render/compressedSource.d.ts +33 -13
  53. package/dist/render/compressedSource.js +70 -25
  54. package/dist/render/glCompressed.js +83 -0
  55. package/dist/render/instances.d.ts +26 -0
  56. package/dist/render/lightBudget.d.ts +10 -0
  57. package/dist/render/lightBudget.js +10 -0
  58. package/dist/render/lightmap.d.ts +1 -1
  59. package/dist/render/mesh.d.ts +5 -0
  60. package/dist/render/mesh.js +6 -0
  61. package/dist/render/pointLightSelection.d.ts +4 -4
  62. package/dist/render/screenSpaceReflection.d.ts +2 -1
  63. package/dist/render/shaders/boneAnimation.d.ts +19 -0
  64. package/dist/render/shaders/boneAnimation.js +63 -0
  65. package/dist/render/shaders/depth.d.ts +7 -0
  66. package/dist/render/shaders/depth.js +35 -10
  67. package/dist/render/shaders/flat/index.d.ts +6 -0
  68. package/dist/render/shaders/flat/index.js +27 -5
  69. package/dist/render/shaders/flat/main.d.ts +1 -1
  70. package/dist/render/shaders/flat/main.js +15 -5
  71. package/dist/render/shaders/flat/models.js +1 -1
  72. package/dist/render/shaders/flat/overlay.js +1 -1
  73. package/dist/render/shaders/flat/pointShadow.js +2 -2
  74. package/dist/render/shaders/flat/preamble.js +38 -24
  75. package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
  76. package/dist/render/shaders/generated/depth.wgsl.js +93 -0
  77. package/dist/render/shaders/generated/flat.wgsl.d.ts +3077 -2903
  78. package/dist/render/shaders/generated/flat.wgsl.js +3100 -2925
  79. package/dist/render/shaders/outputTransform.d.ts +7 -0
  80. package/dist/render/shaders/outputTransform.js +28 -14
  81. package/dist/render/shaders/vertexChannel.d.ts +7 -0
  82. package/dist/render/shaders/vertexChannel.js +26 -0
  83. package/dist/render/staticDraws.d.ts +47 -0
  84. package/dist/render/staticDraws.js +43 -0
  85. package/dist/render/surfaceTexture.d.ts +38 -7
  86. package/dist/render/surfaceTexture.js +56 -17
  87. package/package.json +6 -6
  88. package/src/index.ts +8 -2
  89. package/src/render/ambientHarmonics.ts +1 -1
  90. package/src/render/areaLights.ts +4 -4
  91. package/src/render/backend/api.ts +13 -3
  92. package/src/render/backend/select.ts +10 -5
  93. package/src/render/backend/webgl2/boneAnimations.ts +105 -0
  94. package/src/render/backend/webgl2/instanced.ts +20 -1
  95. package/src/render/backend/webgl2/renderer.ts +315 -24
  96. package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
  97. package/src/render/backend/webgpu/boneAnimations.ts +97 -0
  98. package/src/render/backend/webgpu/buffers.ts +42 -16
  99. package/src/render/backend/webgpu/compressedUpload.ts +59 -9
  100. package/src/render/backend/webgpu/depthPass.ts +51 -10
  101. package/src/render/backend/webgpu/drawCommand.ts +22 -4
  102. package/src/render/backend/webgpu/flatPass.ts +187 -14
  103. package/src/render/backend/webgpu/instanced.ts +32 -1
  104. package/src/render/backend/webgpu/materialStore.ts +276 -0
  105. package/src/render/backend/webgpu/pipelineCache.ts +7 -0
  106. package/src/render/backend/webgpu/renderer.ts +928 -155
  107. package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
  108. package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
  109. package/src/render/backend/webgpu/staticBundles.ts +363 -0
  110. package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
  111. package/src/render/backend/webgpu/uniformRing.ts +10 -1
  112. package/src/render/bloomChain.ts +3 -3
  113. package/src/render/boneAnimation.ts +223 -0
  114. package/src/render/compressedSource.ts +121 -29
  115. package/src/render/glCompressed.ts +83 -0
  116. package/src/render/instances.ts +27 -0
  117. package/src/render/lightBudget.ts +11 -0
  118. package/src/render/lightmap.ts +1 -1
  119. package/src/render/mesh.ts +6 -0
  120. package/src/render/pointLightSelection.ts +4 -4
  121. package/src/render/screenSpaceReflection.ts +2 -1
  122. package/src/render/shaders/boneAnimation.ts +63 -0
  123. package/src/render/shaders/depth.ts +45 -10
  124. package/src/render/shaders/flat/index.ts +33 -5
  125. package/src/render/shaders/flat/main.ts +15 -5
  126. package/src/render/shaders/flat/models.ts +1 -1
  127. package/src/render/shaders/flat/overlay.ts +1 -1
  128. package/src/render/shaders/flat/pointShadow.ts +2 -2
  129. package/src/render/shaders/flat/preamble.ts +38 -24
  130. package/src/render/shaders/generated/depth.wgsl.ts +95 -0
  131. package/src/render/shaders/generated/flat.wgsl.ts +3100 -2925
  132. package/src/render/shaders/outputTransform.ts +30 -14
  133. package/src/render/shaders/vertexChannel.ts +27 -0
  134. package/src/render/staticDraws.ts +95 -0
  135. package/src/render/surfaceTexture.ts +94 -27
@@ -136,6 +136,11 @@ export class Mesh {
136
136
  isSkinnedEight;
137
137
  /** Whether it has texture coordinates, without which a material's maps read one texel. */
138
138
  hasUvs;
139
+ /**
140
+ * Whether it carries a grain lane, which is where second coordinates ride (`lightmap.ts`): what
141
+ * a bone animation reads each vertex's bone from.
142
+ */
143
+ hasGrain;
139
144
  /** How many vertices it has, which a cloth binding is checked against. */
140
145
  vertexCount;
141
146
  /**
@@ -304,6 +309,7 @@ export class Mesh {
304
309
  else {
305
310
  this.attachAttribute(gl, ATTR_TANGENT, data.tangents, 4);
306
311
  }
312
+ this.hasGrain = data.grain !== undefined;
307
313
  if (data.grain === undefined) {
308
314
  gl.disableVertexAttribArray(ATTR_GRAIN);
309
315
  this.constants.push({ location: ATTR_GRAIN, value: ABSENT_ATTRIBUTE['grain'] });
@@ -71,10 +71,10 @@ export interface PointLightSource {
71
71
  inLightField?: boolean;
72
72
  /**
73
73
  * How the light fades with distance when it names its own way: `(1 - (d/R)^2)^n` with no distance
74
- * term, `R` its radius and `n` this. Unreal's falloff for a light without inverse-square falloff,
75
- * so a rig authored there — a fill of brightness 160 across 164 m, a key at exponent 8 — lights
76
- * the space it was authored for rather than one that ends a few metres out. Absent or 0, the
77
- * frame's own falloff (`pointLightFalloff`), which is every light before 4.8.7.
74
+ * term, `R` its radius and `n` this. The usual falloff for a light without inverse-square
75
+ * falloff, so a rig authored that way — a fill of brightness 160 across 164 m, a key at exponent
76
+ * 8 — lights the space it was authored for rather than one that ends a few metres out. Absent or
77
+ * 0, the frame's own falloff (`pointLightFalloff`), which is every light before 4.8.7.
78
78
  */
79
79
  falloffExponent?: number;
80
80
  }
@@ -179,7 +179,8 @@ export interface ReflectiveSurfaceOptions extends DecalBox {
179
179
  * box. Each pixel then returns `F0 · A + B` of what its ray finds, the split-sum environment BRDF
180
180
  * at its own view and `roughness` — Karis's fit, the one the lit stage reflects its environment
181
181
  * by — so a floor returns more toward the horizon than underfoot, and the share follows the
182
- * camera as Unreal weighs its reflections. **False by default**, the one share as before.
182
+ * camera as a physically based renderer weighs its reflections. **False by default**, the one
183
+ * share as before.
183
184
  *
184
185
  * **What it gives up**: the pass has no material buffer, so the roughness and F0 are the box's,
185
186
  * not each pixel's, and nothing occludes the reflection where the lit stage's ambient occlusion
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The vertex-stage bone animation: a crowd instance's vertex turned and placed by its bone, at its
3
+ * instance's moment of the clip — the GLSL of `boneAnimation.ts`'s `animateBoneVertex`, line for
4
+ * line. Shared by the lit stage and the depth stage, so a crowd's shadow moves with it.
5
+ *
6
+ * **Three textures, read by index and never filtered**: the places and the turns, a texel a bone a
7
+ * frame (bones across, frames down, sizes read with `textureSize` so no uniform says them), and the
8
+ * batch's clocks, two floats an instance wrapped onto rows, read at `gl_InstanceID`. The clip's speed
9
+ * and its bone scale are the one uniform, `uBoneClip`; the scene's time is the pass's `uSceneTime`.
10
+ *
11
+ * **The bone is read from the grain lane**, which is where a mesh's second coordinates ride, stored
12
+ * as `-1 - u` (`lightmap.ts`). An instanced batch reads no grain of its own from that lane: a crowd's
13
+ * mesh carries second coordinates, which is what makes it one.
14
+ *
15
+ * Integer arithmetic for the lattice, by the 2026-09-20 rule, except where a float is clamped onto
16
+ * it: the first frame is `min(floor(wrapped), frames - 1)`, so the wrap that division-by-reciprocal
17
+ * lands on `frames` is the last frame fully blended into the first — the first, which is right.
18
+ */
19
+ export declare const BONE_ANIMATION_GLSL = "\nuniform highp sampler2D uBonePlaces;\nuniform highp sampler2D uBoneTurns;\nuniform highp sampler2D uInstanceClocks;\n/** x the clip's frames a second, y how many bones one unit of the second coordinates' u counts. */\nuniform vec2 uBoneClip;\n\n/** v turned by the unit quaternion q. */\nvec3 boneTurn(vec4 q, vec3 v) {\n return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);\n}\n\n/**\n * Where the clip puts this vertex, from its place at rest; its normal and tangent turned with it.\n */\nvec3 boneAnimate(vec3 position, inout vec3 normal, inout vec3 tangent) {\n ivec2 size = textureSize(uBonePlaces, 0);\n int clocksWidth = textureSize(uInstanceClocks, 0).x;\n vec2 clock = texelFetch(\n uInstanceClocks,\n ivec2(gl_InstanceID % clocksWidth, gl_InstanceID / clocksWidth),\n 0\n ).xy;\n float moment = (uSceneTime * clock.y + clock.x) * uBoneClip.x;\n float frames = float(size.y);\n float wrapped = moment - frames * floor(moment / frames);\n int first = min(int(floor(wrapped)), size.y - 1);\n int second = first + 1 == size.y ? 0 : first + 1;\n float blend = clamp(wrapped - float(first), 0.0, 1.0);\n int bone = clamp(int(floor((-1.0 - aGrain) * uBoneClip.y + 0.5)), 0, size.x - 1);\n vec4 q0 = texelFetch(uBoneTurns, ivec2(bone, first), 0);\n vec4 q1 = texelFetch(uBoneTurns, ivec2(bone, second), 0);\n /* The shorter arc: a quaternion and its negation are one turn. */\n if (dot(q0, q1) < 0.0) q1 = -q1;\n vec4 q = normalize(mix(q0, q1, blend));\n vec3 place = mix(\n texelFetch(uBonePlaces, ivec2(bone, first), 0).xyz,\n texelFetch(uBonePlaces, ivec2(bone, second), 0).xyz,\n blend\n );\n normal = boneTurn(q, normal);\n tangent = boneTurn(q, tangent);\n return boneTurn(q, position) + place;\n}\n";
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The vertex-stage bone animation: a crowd instance's vertex turned and placed by its bone, at its
3
+ * instance's moment of the clip — the GLSL of `boneAnimation.ts`'s `animateBoneVertex`, line for
4
+ * line. Shared by the lit stage and the depth stage, so a crowd's shadow moves with it.
5
+ *
6
+ * **Three textures, read by index and never filtered**: the places and the turns, a texel a bone a
7
+ * frame (bones across, frames down, sizes read with `textureSize` so no uniform says them), and the
8
+ * batch's clocks, two floats an instance wrapped onto rows, read at `gl_InstanceID`. The clip's speed
9
+ * and its bone scale are the one uniform, `uBoneClip`; the scene's time is the pass's `uSceneTime`.
10
+ *
11
+ * **The bone is read from the grain lane**, which is where a mesh's second coordinates ride, stored
12
+ * as `-1 - u` (`lightmap.ts`). An instanced batch reads no grain of its own from that lane: a crowd's
13
+ * mesh carries second coordinates, which is what makes it one.
14
+ *
15
+ * Integer arithmetic for the lattice, by the 2026-09-20 rule, except where a float is clamped onto
16
+ * it: the first frame is `min(floor(wrapped), frames - 1)`, so the wrap that division-by-reciprocal
17
+ * lands on `frames` is the last frame fully blended into the first — the first, which is right.
18
+ */
19
+ export const BONE_ANIMATION_GLSL = `
20
+ uniform highp sampler2D uBonePlaces;
21
+ uniform highp sampler2D uBoneTurns;
22
+ uniform highp sampler2D uInstanceClocks;
23
+ /** x the clip's frames a second, y how many bones one unit of the second coordinates' u counts. */
24
+ uniform vec2 uBoneClip;
25
+
26
+ /** v turned by the unit quaternion q. */
27
+ vec3 boneTurn(vec4 q, vec3 v) {
28
+ return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);
29
+ }
30
+
31
+ /**
32
+ * Where the clip puts this vertex, from its place at rest; its normal and tangent turned with it.
33
+ */
34
+ vec3 boneAnimate(vec3 position, inout vec3 normal, inout vec3 tangent) {
35
+ ivec2 size = textureSize(uBonePlaces, 0);
36
+ int clocksWidth = textureSize(uInstanceClocks, 0).x;
37
+ vec2 clock = texelFetch(
38
+ uInstanceClocks,
39
+ ivec2(gl_InstanceID % clocksWidth, gl_InstanceID / clocksWidth),
40
+ 0
41
+ ).xy;
42
+ float moment = (uSceneTime * clock.y + clock.x) * uBoneClip.x;
43
+ float frames = float(size.y);
44
+ float wrapped = moment - frames * floor(moment / frames);
45
+ int first = min(int(floor(wrapped)), size.y - 1);
46
+ int second = first + 1 == size.y ? 0 : first + 1;
47
+ float blend = clamp(wrapped - float(first), 0.0, 1.0);
48
+ int bone = clamp(int(floor((-1.0 - aGrain) * uBoneClip.y + 0.5)), 0, size.x - 1);
49
+ vec4 q0 = texelFetch(uBoneTurns, ivec2(bone, first), 0);
50
+ vec4 q1 = texelFetch(uBoneTurns, ivec2(bone, second), 0);
51
+ /* The shorter arc: a quaternion and its negation are one turn. */
52
+ if (dot(q0, q1) < 0.0) q1 = -q1;
53
+ vec4 q = normalize(mix(q0, q1, blend));
54
+ vec3 place = mix(
55
+ texelFetch(uBonePlaces, ivec2(bone, first), 0).xyz,
56
+ texelFetch(uBonePlaces, ivec2(bone, second), 0).xyz,
57
+ blend
58
+ );
59
+ normal = boneTurn(q, normal);
60
+ tangent = boneTurn(q, tangent);
61
+ return boneTurn(q, position) + place;
62
+ }
63
+ `;
@@ -23,6 +23,13 @@ export declare const DEPTH_INSTANCED_VERT: string;
23
23
  */
24
24
  export declare const DEPTH_CUTOUT_VERT: string;
25
25
  export declare const DEPTH_INSTANCED_CUTOUT_VERT: string;
26
+ /**
27
+ * The variants a crowd casts through: an instanced batch playing a bone animation, its vertices
28
+ * where the clip puts them at each instance's moment. See `shaders/boneAnimation.ts`. No glass
29
+ * variant: a crowd is not glass, and a glass batch that animates casts its tint from rest.
30
+ */
31
+ export declare const DEPTH_INSTANCED_ANIMATED_VERT: string;
32
+ export declare const DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT: string;
26
33
  export declare const DEPTH_FRAG = "#version 300 es\nprecision highp float;\nin vec4 vLightPosition;\n\nuniform highp sampler2DArray uPreviousShadowMap;\nuniform int uPeelShadowLayer;\n\nvoid main() {\n if (uPeelShadowLayer != 0) {\n vec3 p = vLightPosition.xyz / vLightPosition.w;\n vec2 uv = p.xy * 0.5 + 0.5;\n float previousDepth = texture(uPreviousShadowMap, vec3(uv, 0.0)).r;\n // Discard the already-recorded surface and everything in front of it. The\n // ordinary depth test then stores the next independently fading occluder.\n if (gl_FragCoord.z <= previousDepth + 0.00001) discard;\n }\n}\n";
27
34
  /**
28
35
  * The cutout caster's depth: the plain pass, and a texel of alpha under the cutoff writes nothing.
@@ -5,6 +5,7 @@ import { CUTOUT_DITHER_GLSL } from '../cutoutDither.js';
5
5
  import { SKINNING_GLSL } from './skinning.js';
6
6
  import { CLOTH_BINDING_GLSL } from './clothBinding.js';
7
7
  import { CHANNEL_ATTRIBUTE, CHANNEL_BEND } from './vertexChannel.js';
8
+ import { BONE_ANIMATION_GLSL } from './boneAnimation.js';
8
9
  /**
9
10
  * Depth-only shadow pass. Its optional peel mode rejects the first recorded
10
11
  * surface so the next independently fading occluder can be retained.
@@ -77,6 +78,17 @@ uniform vec2 uUvOffset;
77
78
  uniform vec2 uAlphaCutout;
78
79
  #endif
79
80
 
81
+ #if ANIMATED
82
+ /*
83
+ * **A bone animation's, after every other declaration** so no field of the variants before it
84
+ * moves: the grain lane the vertex's bone rides in, the scene's clock, and the chunk the colour
85
+ * pass runs — the same expression, or a crowd's shadow stands still while the crowd moves.
86
+ */
87
+ layout(location = 8) in float aGrain;
88
+ uniform float uSceneTime;
89
+ ${BONE_ANIMATION_GLSL}
90
+ #endif
91
+
80
92
  out vec4 vLightPosition;
81
93
  #if CUTOUT
82
94
  /* The texture coordinate and, in z, the texture-array layer the face wears. */
@@ -98,6 +110,12 @@ void main() {
98
110
  #else
99
111
  vec4 local = vec4(aPosition, 1.0);
100
112
  #endif
113
+ #if ANIMATED
114
+ /* Turned and placed as the colour pass turns and places it; its normal and tangent unused. */
115
+ vec3 animatedNormal = vec3(0.0, 1.0, 0.0);
116
+ vec3 animatedTangent = vec3(1.0, 0.0, 0.0);
117
+ local = vec4(boneAnimate(local.xyz, animatedNormal, animatedTangent), 1.0);
118
+ #endif
101
119
  #if INSTANCED
102
120
  mat4 model = mat4(aInstanceModel0, aInstanceModel1, aInstanceModel2, aInstanceModel3);
103
121
  /* The bottom row is the instance's texture cell, rebuilt as the row it stands for: see flat. */
@@ -144,9 +162,9 @@ void main() {
144
162
  }
145
163
  `;
146
164
  /** The rigid variant: the world, a prop, a mesh with no rig behind it. */
147
- export const DEPTH_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: false, CUTOUT: false, GLASS: false }, 'depth');
165
+ export const DEPTH_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: false, CUTOUT: false, GLASS: false, ANIMATED: false }, 'depth');
148
166
  /** The skinned variant, which reads a joint palette and moves the vertex by it. */
149
- export const DEPTH_SKINNED_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: true, INSTANCED: false, CUTOUT: false, GLASS: false }, 'depth-skinned');
167
+ export const DEPTH_SKINNED_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: true, INSTANCED: false, CUTOUT: false, GLASS: false, ANIMATED: false }, 'depth-skinned');
150
168
  /**
151
169
  * The variant an instanced draw casts its shadow through.
152
170
  *
@@ -155,7 +173,7 @@ export const DEPTH_SKINNED_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINN
155
173
  * right place and the rest stacked underneath it, which reads as a lighting fault rather than as
156
174
  * a missing variant, and is the shape the per-draw ring exists to prevent one level up.
157
175
  */
158
- export const DEPTH_INSTANCED_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: true, CUTOUT: false, GLASS: false }, 'depth-instanced');
176
+ export const DEPTH_INSTANCED_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: true, CUTOUT: false, GLASS: false, ANIMATED: false }, 'depth-instanced');
159
177
  /**
160
178
  * The variants a cutout caster casts through: a leaf card, a chain link, a fence, whose shape is in
161
179
  * its texture's alpha rather than in its geometry.
@@ -166,8 +184,15 @@ export const DEPTH_INSTANCED_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKI
166
184
  * leaves every opaque caster byte for byte as it was. What it costs is one more program each, and
167
185
  * a skinned cutout, which nothing has asked for, is not among them.
168
186
  */
169
- export const DEPTH_CUTOUT_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: false, CUTOUT: true, GLASS: false }, 'depth-cutout');
170
- export const DEPTH_INSTANCED_CUTOUT_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: true, CUTOUT: true, GLASS: false }, 'depth-instanced-cutout');
187
+ export const DEPTH_CUTOUT_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: false, CUTOUT: true, GLASS: false, ANIMATED: false }, 'depth-cutout');
188
+ export const DEPTH_INSTANCED_CUTOUT_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: true, CUTOUT: true, GLASS: false, ANIMATED: false }, 'depth-instanced-cutout');
189
+ /**
190
+ * The variants a crowd casts through: an instanced batch playing a bone animation, its vertices
191
+ * where the clip puts them at each instance's moment. See `shaders/boneAnimation.ts`. No glass
192
+ * variant: a crowd is not glass, and a glass batch that animates casts its tint from rest.
193
+ */
194
+ export const DEPTH_INSTANCED_ANIMATED_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: true, CUTOUT: false, GLASS: false, ANIMATED: true }, 'depth-instanced-animated');
195
+ export const DEPTH_INSTANCED_ANIMATED_CUTOUT_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: true, CUTOUT: true, GLASS: false, ANIMATED: true }, 'depth-instanced-animated-cutout');
171
196
  export const DEPTH_FRAG = `#version 300 es
172
197
  precision highp float;
173
198
  in vec4 vLightPosition;
@@ -238,11 +263,11 @@ void main() {
238
263
  * exactly where it casts its depth, deformed and placed the same way, or the coloured patch parts
239
264
  * from the pane's own shadow edge. The only addition is `vGlassWorld`.
240
265
  */
241
- export const GLASS_TINT_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: false, CUTOUT: false, GLASS: true }, 'glass-tint');
242
- export const GLASS_TINT_SKINNED_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: true, INSTANCED: false, CUTOUT: false, GLASS: true }, 'glass-tint-skinned');
243
- export const GLASS_TINT_INSTANCED_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: true, CUTOUT: false, GLASS: true }, 'glass-tint-instanced');
244
- export const GLASS_TINT_CUTOUT_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: false, CUTOUT: true, GLASS: true }, 'glass-tint-cutout');
245
- export const GLASS_TINT_INSTANCED_CUTOUT_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: true, CUTOUT: true, GLASS: true }, 'glass-tint-instanced-cutout');
266
+ export const GLASS_TINT_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: false, CUTOUT: false, GLASS: true, ANIMATED: false }, 'glass-tint');
267
+ export const GLASS_TINT_SKINNED_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: true, INSTANCED: false, CUTOUT: false, GLASS: true, ANIMATED: false }, 'glass-tint-skinned');
268
+ export const GLASS_TINT_INSTANCED_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: true, CUTOUT: false, GLASS: true, ANIMATED: false }, 'glass-tint-instanced');
269
+ export const GLASS_TINT_CUTOUT_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: false, CUTOUT: true, GLASS: true, ANIMATED: false }, 'glass-tint-cutout');
270
+ export const GLASS_TINT_INSTANCED_CUTOUT_VERT = resolveConditionals(DEPTH_VERT_SOURCE, { SKINNED: false, INSTANCED: true, CUTOUT: true, GLASS: true, ANIMATED: false }, 'glass-tint-instanced-cutout');
246
271
  /**
247
272
  * What a pane lets through: its transmission and colour, less what it reflects toward the light,
248
273
  * and how clear it is — `glassShadow.ts`'s `paneTexel`, stated for the device. Drawn with a
@@ -38,6 +38,12 @@ export interface FlatVertexOptions {
38
38
  * Excludes `skinned`: the joint attributes and the instance matrix want the same locations.
39
39
  */
40
40
  readonly instanced: boolean;
41
+ /**
42
+ * Whether an instanced variant follows a bone animation: \`shaders/boneAnimation.ts\`. A
43
+ * variant rather than a pipeline constant, because it declares three textures the other
44
+ * instanced pipelines would otherwise have to bind stand-ins for. Instanced only.
45
+ */
46
+ readonly animated?: boolean;
41
47
  }
42
48
  /**
43
49
  * The flat vertex shader, built for one variant.
@@ -21,7 +21,8 @@ import { DRIFT_LIGHT_GLSL } from './driftLight.js';
21
21
  import { SURFACE_EFFECTS_GLSL } from './surfaceEffects.js';
22
22
  import { overlayGlsl } from './overlay.js';
23
23
  import { reflectionSurfaceGlsl } from './reflectionSurface.js';
24
- import { CHANNEL_ATTRIBUTE, CHANNEL_BEND } from '../vertexChannel.js';
24
+ import { CHANNEL_BEND, LIT_CHANNEL_ATTRIBUTE } from '../vertexChannel.js';
25
+ import { BONE_ANIMATION_GLSL } from '../boneAnimation.js';
25
26
  import { FULL_LIGHT_BUDGET } from '../../uniformVectorBudget.js';
26
27
  /**
27
28
  * Flat-shaded vertex colours with one dominant directional source, ambient,
@@ -55,10 +56,16 @@ layout(location = 10) in vec4 aTangent;
55
56
  ${SKINNING_GLSL}
56
57
  #endif
57
58
 
58
- uniform mat4 uViewProj;
59
+ uniform mat4 uViewProj; // wgsl:view
59
60
  /** 1 when the mesh carries a tangent frame. See vHasTangents. */
60
61
  uniform int uHasTangents;
61
- uniform mat4 uLightViewProj;
62
+ uniform mat4 uLightViewProj; // wgsl:view
63
+ /**
64
+ * The scene's clock, what \`setAnimationTime\` set: what a bone animation's instances read
65
+ * their moment from. In the view block, before the wind, so every variant's view block is the
66
+ * plain one's or a prefix of it — the instanced variant declares no wind and keeps this.
67
+ */
68
+ uniform float uSceneTime; // wgsl:view
62
69
  /**
63
70
  * How often the surface texture repeats across this mesh's UV range, per axis.
64
71
  *
@@ -121,6 +128,9 @@ layout(location = 15) in vec4 aInstanceTint;
121
128
  * opacity of one. See lightmap.ts.
122
129
  */
123
130
  const bool LIGHTMAP_REGIONS = false; // wgsl:override
131
+ #if ANIMATED
132
+ ${BONE_ANIMATION_GLSL}
133
+ #endif
124
134
  #else
125
135
  uniform mat4 uModel;
126
136
  /**
@@ -146,7 +156,7 @@ uniform vec3 uTint;
146
156
  * agreeing about where the shared uniforms sit: the instanced variant drops these five along with
147
157
  * the two above it, and a dropped field moves nothing. flatPass.ts asserts exactly that.
148
158
  */
149
- ${CHANNEL_ATTRIBUTE}
159
+ ${LIT_CHANNEL_ATTRIBUTE}
150
160
  ${CHANNEL_BEND}
151
161
  #endif
152
162
 
@@ -244,6 +254,10 @@ void main() {
244
254
  vec3 localNormal = aNormal;
245
255
  vec3 localTangent = aTangent.xyz;
246
256
  #endif
257
+ #if ANIMATED
258
+ /* The clip's turn and place, in the mesh's own space, before the instance places it. */
259
+ local = vec4(boneAnimate(local.xyz, localNormal, localTangent), 1.0);
260
+ #endif
247
261
  #if INSTANCED
248
262
  mat4 model = mat4(aInstanceModel0, aInstanceModel1, aInstanceModel2, aInstanceModel3);
249
263
  /*
@@ -353,7 +367,15 @@ export function flatVert(options) {
353
367
  throw new Error('flatVert: a variant cannot be both morphed and instanced — a morph weight is per draw, ' +
354
368
  'so every instance of the batch would wear one expression between them.');
355
369
  }
356
- return resolveConditionals(FLAT_VERT_SOURCE, { SKINNED: options.skinned, MORPHED: options.morphed, INSTANCED: options.instanced }, 'flatVert');
370
+ if (options.animated === true && !options.instanced) {
371
+ throw new Error('flatVert: a bone animation is played by an instanced variant alone.');
372
+ }
373
+ return resolveConditionals(FLAT_VERT_SOURCE, {
374
+ SKINNED: options.skinned,
375
+ MORPHED: options.morphed,
376
+ INSTANCED: options.instanced,
377
+ ANIMATED: options.animated === true,
378
+ }, 'flatVert');
357
379
  }
358
380
  /**
359
381
  * The flat fragment shader, built for one quality profile.