@driftengine/core 4.8.5 → 4.8.7

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 (214) hide show
  1. package/CHANGELOG.json +4392 -4250
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +12 -4
  4. package/dist/index.js +12 -2
  5. package/dist/render/ambientHarmonics.d.ts +46 -0
  6. package/dist/render/ambientHarmonics.js +80 -0
  7. package/dist/render/anisotropicLobe.d.ts +10 -0
  8. package/dist/render/anisotropicLobe.js +19 -0
  9. package/dist/render/areaLights.d.ts +54 -0
  10. package/dist/render/areaLights.js +55 -0
  11. package/dist/render/backend/api.d.ts +43 -2
  12. package/dist/render/backend/webgl2/instanced.d.ts +2 -0
  13. package/dist/render/backend/webgl2/instanced.js +11 -2
  14. package/dist/render/backend/webgl2/renderer.d.ts +120 -11
  15. package/dist/render/backend/webgl2/renderer.js +356 -53
  16. package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
  17. package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
  18. package/dist/render/backend/webgl2/skinScatter.d.ts +14 -0
  19. package/dist/render/backend/webgl2/skinScatter.js +88 -26
  20. package/dist/render/backend/webgpu/buffers.js +2 -1
  21. package/dist/render/backend/webgpu/clusterBinner.js +3 -2
  22. package/dist/render/backend/webgpu/flatPass.d.ts +7 -5
  23. package/dist/render/backend/webgpu/flatPass.js +15 -13
  24. package/dist/render/backend/webgpu/particlePass.d.ts +37 -4
  25. package/dist/render/backend/webgpu/particlePass.js +81 -12
  26. package/dist/render/backend/webgpu/postPass.d.ts +6 -1
  27. package/dist/render/backend/webgpu/postPass.js +7 -2
  28. package/dist/render/backend/webgpu/renderer.d.ts +93 -8
  29. package/dist/render/backend/webgpu/renderer.js +483 -93
  30. package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
  31. package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
  32. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
  33. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.js +41 -1
  34. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +40 -28
  35. package/dist/render/backend/webgpu/skinPaletteRing.js +69 -32
  36. package/dist/render/backend/webgpu/skinScatterPass.d.ts +10 -2
  37. package/dist/render/backend/webgpu/skinScatterPass.js +59 -9
  38. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -1
  39. package/dist/render/backend/webgpu/surfaceTexturePass.js +45 -5
  40. package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
  41. package/dist/render/bloomChain.d.ts +44 -0
  42. package/dist/render/bloomChain.js +28 -0
  43. package/dist/render/bloomPass.d.ts +3 -1
  44. package/dist/render/bloomPass.js +16 -2
  45. package/dist/render/clusteredLights.d.ts +78 -6
  46. package/dist/render/clusteredLights.js +214 -13
  47. package/dist/render/driftLight/bake.d.ts +2 -0
  48. package/dist/render/driftLight/bake.js +1 -1
  49. package/dist/render/driftLight/falloff.d.ts +4 -2
  50. package/dist/render/driftLight/falloff.js +8 -2
  51. package/dist/render/fixtureAtlas.js +1 -1
  52. package/dist/render/gi/bounceLights.d.ts +3 -2
  53. package/dist/render/gi/bounceLights.js +8 -3
  54. package/dist/render/instanceCull.js +12 -0
  55. package/dist/render/instances.d.ts +47 -11
  56. package/dist/render/instances.js +24 -9
  57. package/dist/render/lightBudget.d.ts +38 -2
  58. package/dist/render/lightBudget.js +48 -5
  59. package/dist/render/lightmap.d.ts +116 -0
  60. package/dist/render/lightmap.js +140 -0
  61. package/dist/render/mesh.js +2 -1
  62. package/dist/render/particleBatch.d.ts +59 -3
  63. package/dist/render/particleBatch.js +53 -2
  64. package/dist/render/particleMaterial.d.ts +6 -1
  65. package/dist/render/particleMaterial.js +10 -2
  66. package/dist/render/particlePool.d.ts +32 -0
  67. package/dist/render/particlePool.js +9 -0
  68. package/dist/render/particleSort.d.ts +34 -0
  69. package/dist/render/particleSort.js +96 -0
  70. package/dist/render/pointLightSelection.d.ts +13 -0
  71. package/dist/render/pointLightSelection.js +3 -1
  72. package/dist/render/renderQuality.d.ts +8 -7
  73. package/dist/render/rendererHarness.js +3 -0
  74. package/dist/render/rgb9e5.d.ts +22 -0
  75. package/dist/render/rgb9e5.js +47 -0
  76. package/dist/render/sceneCapture.d.ts +29 -0
  77. package/dist/render/sceneCapture.js +36 -0
  78. package/dist/render/sceneTarget.d.ts +5 -0
  79. package/dist/render/sceneTarget.js +8 -1
  80. package/dist/render/screenSpaceReflection.d.ts +27 -1
  81. package/dist/render/screenSpaceReflection.js +9 -0
  82. package/dist/render/shaders/bloom.d.ts +8 -4
  83. package/dist/render/shaders/bloom.js +20 -3
  84. package/dist/render/shaders/bolt.d.ts +1 -1
  85. package/dist/render/shaders/bolt.js +7 -1
  86. package/dist/render/shaders/depth.js +11 -0
  87. package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
  88. package/dist/render/shaders/flat/anisotropic.js +37 -3
  89. package/dist/render/shaders/flat/areaLight.d.ts +10 -0
  90. package/dist/render/shaders/flat/areaLight.js +142 -0
  91. package/dist/render/shaders/flat/glassTint.d.ts +3 -2
  92. package/dist/render/shaders/flat/glassTint.js +3 -1
  93. package/dist/render/shaders/flat/index.d.ts +5 -0
  94. package/dist/render/shaders/flat/index.js +46 -6
  95. package/dist/render/shaders/flat/lobes.d.ts +6 -0
  96. package/dist/render/shaders/flat/lobes.js +49 -43
  97. package/dist/render/shaders/flat/main.d.ts +1 -1
  98. package/dist/render/shaders/flat/main.js +158 -121
  99. package/dist/render/shaders/flat/models.d.ts +21 -6
  100. package/dist/render/shaders/flat/models.js +161 -17
  101. package/dist/render/shaders/flat/normalMap.d.ts +10 -0
  102. package/dist/render/shaders/flat/normalMap.js +25 -0
  103. package/dist/render/shaders/flat/preamble.js +84 -40
  104. package/dist/render/shaders/flat/skin.js +6 -5
  105. package/dist/render/shaders/generated/bloom.wgsl.d.ts +13 -3
  106. package/dist/render/shaders/generated/bloom.wgsl.js +13 -3
  107. package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
  108. package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
  109. package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
  110. package/dist/render/shaders/generated/depth.wgsl.js +4 -4
  111. package/dist/render/shaders/generated/flat.wgsl.d.ts +1647 -2044
  112. package/dist/render/shaders/generated/flat.wgsl.js +1685 -2082
  113. package/dist/render/shaders/generated/particle.wgsl.d.ts +115 -1
  114. package/dist/render/shaders/generated/particle.wgsl.js +115 -1
  115. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +12 -2
  116. package/dist/render/shaders/generated/skinBlur.wgsl.js +12 -2
  117. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +11 -6
  118. package/dist/render/shaders/generated/ssrTrace.wgsl.js +11 -6
  119. package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
  120. package/dist/render/shaders/generated/water.wgsl.js +23 -16
  121. package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
  122. package/dist/render/shaders/particle.d.ts +17 -1
  123. package/dist/render/shaders/particle.js +133 -1
  124. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  125. package/dist/render/shaders/recon/motion.wgsl.js +13 -2
  126. package/dist/render/shaders/skinBlur.js +25 -3
  127. package/dist/render/shaders/ssrTrace.js +20 -1
  128. package/dist/render/shaders/water.d.ts +1 -1
  129. package/dist/render/shaders/water.js +12 -3
  130. package/dist/render/skinBlur.d.ts +8 -5
  131. package/dist/render/skinBlur.js +8 -5
  132. package/dist/render/skinScatter.d.ts +3 -1
  133. package/dist/render/skinScatter.js +3 -1
  134. package/dist/render/ssrPass.js +1 -0
  135. package/dist/render/surfaceModel.d.ts +49 -8
  136. package/dist/render/surfaceModel.js +54 -8
  137. package/dist/render/surfaceTexture.d.ts +38 -1
  138. package/dist/render/surfaceTexture.js +37 -2
  139. package/package.json +6 -6
  140. package/src/index.ts +26 -3
  141. package/src/render/ambientHarmonics.ts +92 -0
  142. package/src/render/anisotropicLobe.ts +29 -0
  143. package/src/render/areaLights.ts +97 -0
  144. package/src/render/backend/api.ts +61 -1
  145. package/src/render/backend/webgl2/instanced.ts +12 -2
  146. package/src/render/backend/webgl2/renderer.ts +470 -59
  147. package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
  148. package/src/render/backend/webgl2/skinScatter.ts +120 -43
  149. package/src/render/backend/webgpu/buffers.ts +2 -1
  150. package/src/render/backend/webgpu/clusterBinner.ts +3 -1
  151. package/src/render/backend/webgpu/flatPass.ts +18 -13
  152. package/src/render/backend/webgpu/particlePass.ts +119 -12
  153. package/src/render/backend/webgpu/postPass.ts +7 -2
  154. package/src/render/backend/webgpu/renderer.ts +589 -113
  155. package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
  156. package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +41 -1
  157. package/src/render/backend/webgpu/skinPaletteRing.ts +65 -33
  158. package/src/render/backend/webgpu/skinScatterPass.ts +59 -9
  159. package/src/render/backend/webgpu/surfaceTexturePass.ts +57 -6
  160. package/src/render/bloomChain.ts +69 -0
  161. package/src/render/bloomPass.ts +26 -2
  162. package/src/render/clusteredLights.ts +259 -12
  163. package/src/render/driftLight/bake.ts +3 -1
  164. package/src/render/driftLight/falloff.ts +13 -2
  165. package/src/render/fixtureAtlas.ts +1 -1
  166. package/src/render/gi/bounceLights.ts +12 -3
  167. package/src/render/instanceCull.ts +9 -0
  168. package/src/render/instances.ts +61 -12
  169. package/src/render/lightBudget.ts +88 -4
  170. package/src/render/lightmap.ts +243 -0
  171. package/src/render/mesh.ts +2 -1
  172. package/src/render/particleBatch.ts +95 -4
  173. package/src/render/particleMaterial.ts +11 -1
  174. package/src/render/particlePool.ts +36 -0
  175. package/src/render/particleSort.ts +133 -0
  176. package/src/render/pointLightSelection.ts +20 -1
  177. package/src/render/renderQuality.ts +8 -7
  178. package/src/render/rendererHarness.ts +3 -0
  179. package/src/render/rgb9e5.ts +52 -0
  180. package/src/render/sceneCapture.ts +56 -0
  181. package/src/render/sceneTarget.ts +18 -1
  182. package/src/render/screenSpaceReflection.ts +33 -1
  183. package/src/render/shaders/bloom.ts +20 -3
  184. package/src/render/shaders/bolt.ts +7 -1
  185. package/src/render/shaders/depth.ts +11 -0
  186. package/src/render/shaders/flat/anisotropic.ts +37 -3
  187. package/src/render/shaders/flat/areaLight.ts +142 -0
  188. package/src/render/shaders/flat/glassTint.ts +9 -2
  189. package/src/render/shaders/flat/index.ts +51 -6
  190. package/src/render/shaders/flat/lobes.ts +50 -43
  191. package/src/render/shaders/flat/main.ts +158 -121
  192. package/src/render/shaders/flat/models.ts +170 -21
  193. package/src/render/shaders/flat/normalMap.ts +25 -0
  194. package/src/render/shaders/flat/preamble.ts +84 -40
  195. package/src/render/shaders/flat/skin.ts +6 -5
  196. package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
  197. package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
  198. package/src/render/shaders/generated/depth.wgsl.ts +4 -4
  199. package/src/render/shaders/generated/flat.wgsl.ts +1685 -2082
  200. package/src/render/shaders/generated/particle.wgsl.ts +116 -1
  201. package/src/render/shaders/generated/skinBlur.wgsl.ts +12 -2
  202. package/src/render/shaders/generated/ssrTrace.wgsl.ts +11 -6
  203. package/src/render/shaders/generated/water.wgsl.ts +23 -16
  204. package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
  205. package/src/render/shaders/particle.ts +134 -1
  206. package/src/render/shaders/recon/motion.wgsl.ts +13 -2
  207. package/src/render/shaders/skinBlur.ts +25 -3
  208. package/src/render/shaders/ssrTrace.ts +20 -1
  209. package/src/render/shaders/water.ts +12 -3
  210. package/src/render/skinBlur.ts +8 -5
  211. package/src/render/skinScatter.ts +3 -1
  212. package/src/render/ssrPass.ts +1 -0
  213. package/src/render/surfaceModel.ts +82 -10
  214. package/src/render/surfaceTexture.ts +101 -3
@@ -684,15 +684,19 @@ fn probeLampsAt(p: vec3<f32>, n: vec3<f32>) -> vec3<f32> {
684
684
  var sum = vec3<f32>(0.0);
685
685
  let count = u32(bake.lightCount);
686
686
  for (var i = 0u; i < count; i = i + 1u) {
687
- let at = bounceLights[i * 3u];
688
- let colour = bounceLights[i * 3u + 1u];
689
- let aim = bounceLights[i * 3u + 2u];
687
+ let at = bounceLights[i * 4u];
688
+ let colour = bounceLights[i * 4u + 1u];
689
+ let aim = bounceLights[i * 4u + 2u];
690
+ let lightExponent = bounceLights[i * 4u + 3u].x;
690
691
  let lightRadius = at.w;
691
692
  let toLight = at.xyz - p;
692
693
  let dist = length(toLight);
693
694
  let coneFalloff = smoothstep(aim.w, colour.w, dot(-toLight / max(dist, 1e-4), aim.xyz));
694
695
  var falloff: f32;
695
- if (bake.falloff == 1.0) {
696
+ if (lightExponent > 0.0) {
697
+ let reach = dist / max(lightRadius, 1e-4);
698
+ falloff = pow(clamp(1.0 - reach * reach, 0.0, 1.0), lightExponent);
699
+ } else if (bake.falloff == 1.0) {
696
700
  let window = clamp(1.0 - pow(dist / max(lightRadius, 1e-4), 4.0), 0.0, 1.0);
697
701
  falloff = window * window / max(dist * dist, 0.01);
698
702
  } else {
@@ -701,7 +705,7 @@ fn probeLampsAt(p: vec3<f32>, n: vec3<f32>) -> vec3<f32> {
701
705
  if (falloff <= 0.0) { continue; }
702
706
  let ndl = max(dot(n, toLight / max(dist, 1e-4)), 0.0);
703
707
  if (ndl <= 0.0) { continue; }
704
- let shape = select(falloff * falloff, falloff, bake.falloff == 1.0) * coneFalloff;
708
+ let shape = select(falloff * falloff, falloff, lightExponent > 0.0 || bake.falloff == 1.0) * coneFalloff;
705
709
  if (shape <= 0.0) { continue; }
706
710
  let fixture = max(0.3, 2.0 * march.finestStep);
707
711
  let seen = probeLampVisible(p, n, toLight / max(dist, 1e-4), dist - fixture);
@@ -1,5 +1,6 @@
1
1
  import { FOG_GLSL } from './fog.ts';
2
2
  import { OUTPUT_TRANSFORM_GLSL } from './outputTransform.ts';
3
+ import { glslIsFarDepth, glslSceneDepthToNdc } from '../depthConvention.ts';
3
4
  import { MAX_POINT_LIGHTS } from '../lightBudget.ts';
4
5
 
5
6
  /**
@@ -87,6 +88,11 @@ layout(location = 6) in float aAlpha;
87
88
  layout(location = 7) in float aAge;
88
89
  layout(location = 8) in float aSeed;
89
90
  layout(location = 9) in vec3 aVelocity;
91
+ /**
92
+ * A sprite's flipbook frame and its half-height: \`ParticleInstances.frames\` and \`heights\`. The
93
+ * procedural materials read neither, and a height of 0 is a square, its height its width.
94
+ */
95
+ layout(location = 10) in vec2 aSprite;
90
96
 
91
97
  uniform mat4 uViewProj;
92
98
  uniform vec3 uCameraPos;
@@ -126,6 +132,8 @@ uniform float uStretchSec;
126
132
  * from everywhere.
127
133
  */
128
134
  uniform float uCameraFacing;
135
+ /** How far each particle is moved toward the eye, in metres: \`ParticleBatchOptions.cameraOffset\`. */
136
+ uniform float uCameraOffset;
129
137
 
130
138
  out vec2 vUv;
131
139
  out vec3 vColor;
@@ -134,6 +142,9 @@ out float vAge;
134
142
  out float vSeed;
135
143
  out vec3 vWorldPos;
136
144
  out vec3 vNormal;
145
+ /** The flipbook frame, for the sprite material; and the depth from the eye, for its fades. */
146
+ out float vFrame;
147
+ out float vViewDepth;
137
148
 
138
149
  void main() {
139
150
  float s = sin(aSpin);
@@ -150,7 +161,18 @@ void main() {
150
161
  vec3 across;
151
162
  vec3 up;
152
163
  float bladeGain;
153
- if (uCameraFacing > 0.5) {
164
+ /*
165
+ * **Along its travel**, \`facing: 'velocity'\` (2): the quad's up is the velocity seen across
166
+ * the view, so a streak of light or a stretched spark lies along its motion and still faces the
167
+ * eye. A particle at rest has no such direction and faces the camera as the default does.
168
+ */
169
+ vec3 viewer = normalize(uCameraPos - aPos);
170
+ vec3 alongView = aVelocity - viewer * dot(aVelocity, viewer);
171
+ if (uCameraFacing > 1.5 && dot(alongView, alongView) > 1e-10) {
172
+ up = normalize(alongView);
173
+ across = cross(up, viewer);
174
+ bladeGain = aBlade < 0.5 ? 1.0 : 0.0;
175
+ } else if (uCameraFacing > 0.5) {
154
176
  vec3 toCamera = normalize(uCameraPos - aPos);
155
177
  /* Any reference not parallel to the view. World up serves everywhere except looking
156
178
  straight up or down it, where the two are the same line and the cross product
@@ -173,6 +195,16 @@ void main() {
173
195
  bladeGain = 1.0;
174
196
  }
175
197
  vec3 offset = (across * rolled.x + up * rolled.y) * aSize;
198
+ /*
199
+ * **A sprite of two sizes** — a half-width and a half-height — rolls as a rectangle: the corner
200
+ * is scaled first and turned after. Only where a height was given, so a square particle keeps
201
+ * the arithmetic it always had to the bit.
202
+ */
203
+ if (aSprite.y > 0.0) {
204
+ vec2 extent = vec2(aCorner.x * aSize, aCorner.y * aSprite.y);
205
+ vec2 turned = vec2(extent.x * c - extent.y * s, extent.x * s + extent.y * c);
206
+ offset = across * turned.x + up * turned.y;
207
+ }
176
208
 
177
209
  /*
178
210
  * Stretched along travel. Applied as an extra displacement on the component of
@@ -188,7 +220,10 @@ void main() {
188
220
  /* bladeGain applied here, after the stretch rather than before it, so a dropped blade
189
221
  is degenerate whatever the stretch added: all four of its corners land on aPos. */
190
222
  vec3 world = aPos + offset * bladeGain;
223
+ /* Toward the eye, so a sprite set into a surface is drawn in front of it. 0 moves nothing. */
224
+ world += viewer * uCameraOffset * bladeGain;
191
225
  vUv = aCorner;
226
+ vFrame = aSprite.x;
192
227
  vColor = aColor;
193
228
  vAlpha = aAlpha;
194
229
  vAge = aAge;
@@ -207,6 +242,8 @@ void main() {
207
242
  vNormal = dot(face, toEye) < 0.0 ? -face : face;
208
243
 
209
244
  gl_Position = uViewProj * vec4(world, 1.0);
245
+ /* A perspective projection's w is the depth from the eye, which is what the fades measure. */
246
+ vViewDepth = gl_Position.w;
210
247
  }
211
248
  `;
212
249
 
@@ -436,3 +473,99 @@ void main() {
436
473
  outColor = vec4(applyOutputTransform(color), alpha);
437
474
  }
438
475
  `;
476
+
477
+ /**
478
+ * A textured sprite: an image a consumer supplies, a cell of it at a time, at the colour and opacity
479
+ * each particle carries — what another engine's sprite renderer draws, and the material the three
480
+ * procedural ones above cannot be. Unlit: the colour is the particle's times the image's.
481
+ *
482
+ * **A flipbook** of `uCells.x` by `uCells.y` cells, numbered across then down from the top-left,
483
+ * a particle's frame naming one; with `uCells.z` at 1 the frame's fraction blends toward the next,
484
+ * so a slow flipbook does not step.
485
+ *
486
+ * **Two fades, each 0 for off.** A soft edge where the sprite meets the scene, by how far in front
487
+ * of the opaque depth it is over `uFade.x` metres — the hard line a flat card draws where it cuts
488
+ * a floor — and a fade over `uFade.y` metres in front of the eye, so a card the camera passes
489
+ * through does not fill the screen and vanish. The first reads a copy of the frame's depth, which
490
+ * is `uFade.z` at 1; where no copy can be had it is 0 and the edge stays hard.
491
+ */
492
+ export const PARTICLE_SPRITE_FRAG = `#version 300 es
493
+ precision highp float;
494
+
495
+ /* Every output of the vertex stage, in its order, read or not: the generated stages number their
496
+ locations in declaration order, so a fragment declaring a subset reads the wrong ones. */
497
+ in vec2 vUv;
498
+ in vec3 vColor;
499
+ in float vAlpha;
500
+ in float vAge;
501
+ in float vSeed;
502
+ in vec3 vWorldPos;
503
+ in vec3 vNormal;
504
+ in float vFrame;
505
+ in float vViewDepth;
506
+
507
+ /** The image, a surface texture: its first layer. */
508
+ uniform mediump sampler2DArray uSprite;
509
+ /** The frame's opaque depth, copied, for the soft edge. highp for the reason depth always is. */
510
+ uniform highp sampler2D uDepth;
511
+ /** Columns, rows, and 1 to blend between cells. */
512
+ uniform vec4 uCells;
513
+ /** The soft edge's distance, the eye fade's, and whether the depth copy is there to read. */
514
+ uniform vec4 uFade;
515
+ /** A depth sample to view-space metres, as the skin blur takes it. */
516
+ uniform vec4 uDepthToViewZ;
517
+ /** The target's size in pixels, so a fragment finds its own texel of the depth copy. */
518
+ uniform vec2 uViewport;
519
+ uniform vec3 uCameraPos;
520
+ /** Whether the colour recedes into the medium: \`ParticleBatchOptions.fog\`. */
521
+ uniform int uFogEnabled;
522
+ ${FOG_GLSL}
523
+
524
+ out vec4 outColor;
525
+ ${OUTPUT_TRANSFORM_GLSL}
526
+
527
+ /** Cell \`cell\` of the flipbook, at \`uv\` inside it. Integer arithmetic: see AGENTS.md. */
528
+ vec2 cellUv(int cell, vec2 uv) {
529
+ int columns = max(int(uCells.x + 0.5), 1);
530
+ int rows = max(int(uCells.y + 0.5), 1);
531
+ int wrapped = cell % (columns * rows);
532
+ int column = wrapped % columns;
533
+ int row = wrapped / columns;
534
+ return (vec2(float(column), float(row)) + uv) / vec2(float(columns), float(rows));
535
+ }
536
+
537
+ float viewZ(float depth) {
538
+ float ndc = ${glslSceneDepthToNdc('depth')};
539
+ return (uDepthToViewZ.x * ndc + uDepthToViewZ.y) / (uDepthToViewZ.z * ndc + uDepthToViewZ.w);
540
+ }
541
+
542
+ void main() {
543
+ /* The corner's square to the image's: v runs down an image, and up a quad. */
544
+ vec2 uv = vec2(vUv.x * 0.5 + 0.5, 0.5 - vUv.y * 0.5);
545
+ float frame = max(vFrame, 0.0);
546
+ int first = int(floor(frame));
547
+ vec4 image = texture(uSprite, vec3(cellUv(first, uv), 0.0));
548
+ if (uCells.z > 0.5) {
549
+ vec4 next = texture(uSprite, vec3(cellUv(first + 1, uv), 0.0));
550
+ image = mix(image, next, fract(frame));
551
+ }
552
+
553
+ float fade = 1.0;
554
+ if (uFade.z > 0.5 && uFade.x > 0.0) {
555
+ float depth = textureLod(uDepth, gl_FragCoord.xy / uViewport, 0.0).r;
556
+ if (!(${glslIsFarDepth('depth')})) {
557
+ fade = clamp((abs(viewZ(depth)) - vViewDepth) / uFade.x, 0.0, 1.0);
558
+ }
559
+ }
560
+ if (uFade.y > 0.0) fade *= clamp(vViewDepth / uFade.y, 0.0, 1.0);
561
+
562
+ float alpha = vAlpha * image.a * fade;
563
+ if (alpha <= 0.001) discard;
564
+ vec3 color = vColor * image.rgb;
565
+ if (uFogEnabled != 0) {
566
+ float fog = mediumFog(distance(vWorldPos, uCameraPos), vWorldPos.y);
567
+ color = mix(color, mediumColor(), fog);
568
+ }
569
+ outColor = vec4(applyOutputTransform(color), alpha);
570
+ }
571
+ `;
@@ -248,6 +248,17 @@ fn motionDynamicVert(
248
248
  return out;
249
249
  }
250
250
 
251
+ /* An instance's placement with its bottom row, which carries its texture cell, rebuilt as the
252
+ [0, 0, 0, 1] an affine placement has: \`MeshInstances.uvRegions\`. */
253
+ fn placementOf(c0: vec4<f32>, c1: vec4<f32>, c2: vec4<f32>, c3: vec4<f32>) -> mat4x4<f32> {
254
+ return mat4x4<f32>(
255
+ vec4<f32>(c0.xyz, 0.0),
256
+ vec4<f32>(c1.xyz, 0.0),
257
+ vec4<f32>(c2.xyz, 0.0),
258
+ vec4<f32>(c3.xyz, 1.0),
259
+ );
260
+ }
261
+
251
262
  /*
252
263
  * The instanced stage: each instance's matrix this frame and last frame, read from the batch's
253
264
  * placement and the copy of last frame's it kept. Slot for slot, so an instance is paired with
@@ -265,8 +276,8 @@ fn motionInstancedVert(
265
276
  @location(8) p2: vec4<f32>,
266
277
  @location(9) p3: vec4<f32>,
267
278
  ) -> Varyings {
268
- let world = mat4x4<f32>(m0, m1, m2, m3) * vec4<f32>(position, 1.0);
269
- let wasWorld = mat4x4<f32>(p0, p1, p2, p3) * vec4<f32>(position, 1.0);
279
+ let world = placementOf(m0, m1, m2, m3) * vec4<f32>(position, 1.0);
280
+ let wasWorld = placementOf(p0, p1, p2, p3) * vec4<f32>(position, 1.0);
270
281
  var out: Varyings;
271
282
  out.clip = frame.raster * world;
272
283
  out.now = frame.viewProj * world;
@@ -6,6 +6,11 @@
6
6
  * **One pass, run twice**: along x into a target of its own, keeping the profile in the alpha, then
7
7
  * along y blended additively into the frame — its colour added, the frame's alpha kept.
8
8
  *
9
+ * **What it spreads is light, and the colour is applied after**, from the skin's own colour half at
10
+ * this pixel. Light travels beneath skin; the pigment a brow, a lip line or a freckle is made of
11
+ * does not, so spreading the coloured diffuse — as this did until 4.8.6 — smeared every mark on a
12
+ * face into the skin around it, and the blur's taps laid faint copies of each mark beside it.
13
+ *
9
14
  * **Refuses what is not the same skin**: a neighbour that wrote no skin, or one further in depth
10
15
  * than four of the profile's widest distances, takes no weight, and what is kept is divided by
11
16
  * what was kept, so a cheek's light does not pull toward the black beside its silhouette. A tap
@@ -34,6 +39,12 @@ uniform vec4 uDepthToViewZ;
34
39
  uniform float uFocal;
35
40
  /** Each profile's scatter distance per channel, in metres. */
36
41
  uniform vec4 uProfiles[${SKIN_PROFILES}];
42
+ /**
43
+ * The skin's colour, and its coverage in the alpha: what the spread light is multiplied by on the
44
+ * second axis, where \`uApplyAlbedo\` is 1. Read at this pixel alone, because pigment does not move.
45
+ */
46
+ uniform sampler2D uAlbedo;
47
+ uniform float uApplyAlbedo;
37
48
 
38
49
  out vec4 fragColor;
39
50
 
@@ -50,6 +61,17 @@ vec3 burleyMass(float x, vec3 d) {
50
61
  return 0.5 - (exp(-x / d) + 3.0 * exp(-x / (3.0 * d))) * 0.125;
51
62
  }
52
63
 
64
+ /**
65
+ * The spread light times the skin's colour here, on the second axis; the light as it is on the
66
+ * first. The colour target holds colour times coverage over coverage, so dividing gives the skin's
67
+ * own colour at an edge, which the light — weighted for coverage already — is then multiplied by.
68
+ */
69
+ vec3 withAlbedo(vec3 light) {
70
+ if (uApplyAlbedo < 0.5) return light;
71
+ vec4 albedo = textureLod(uAlbedo, vUv, 0.0);
72
+ return light * albedo.rgb / max(albedo.a, 1e-4);
73
+ }
74
+
53
75
  void main() {
54
76
  vec4 own = textureLod(uSkin, vUv, 0.0);
55
77
  /*
@@ -63,7 +85,7 @@ void main() {
63
85
  }
64
86
  float depth = textureLod(uDepth, vUv, 0.0).r;
65
87
  if (${glslIsFarDepth('depth')}) {
66
- fragColor = own;
88
+ fragColor = vec4(withAlbedo(own.rgb), own.a);
67
89
  return;
68
90
  }
69
91
  /* A part-covered pixel's code is its share of the whole: read it as the nearest, which is the
@@ -75,7 +97,7 @@ void main() {
75
97
  /* The widest distance in pixels here: under half of one, there is nothing to spread across. */
76
98
  float pixels = widest * uFocal / max(z, 1e-4);
77
99
  if (pixels < 0.5) {
78
- fragColor = own;
100
+ fragColor = vec4(withAlbedo(own.rgb), own.a);
79
101
  return;
80
102
  }
81
103
  vec3 centre = 2.0 * burleyMass(EDGES[0] * widest, d);
@@ -100,6 +122,6 @@ void main() {
100
122
  weight += w * (cover * keep);
101
123
  }
102
124
  }
103
- fragColor = vec4(sum / weight, own.a);
125
+ fragColor = vec4(withAlbedo(sum / weight), own.a);
104
126
  }
105
127
  `;
@@ -21,6 +21,7 @@
21
21
  */
22
22
 
23
23
  import { glslIsFarDepth } from '../depthConvention.ts';
24
+ import { ENV_BRDF_GLSL } from './flat/lobes.ts';
24
25
 
25
26
  /**
26
27
  * The most samples the march may take. `uSsrSteps` breaks out early.
@@ -64,6 +65,11 @@ uniform vec3 uSsrEye;
64
65
  uniform vec3 uSsrAxis;
65
66
  uniform vec3 uSsrTint;
66
67
  uniform float uSsrStrength;
68
+ /**
69
+ * Whether \`uSsrStrength\` is the surface's reflectance head-on, carried per pixel through the
70
+ * split-sum BRDF (x, 1 or 0), and the roughness it is carried at (y). \`ReflectiveSurface.fresnel\`.
71
+ */
72
+ uniform vec2 uSsrFresnel;
67
73
  uniform float uSsrFacingCos;
68
74
  uniform float uSsrReach;
69
75
  uniform float uSsrThickness;
@@ -73,6 +79,8 @@ uniform float uSsrEdgeFade;
73
79
 
74
80
  out vec4 fragColor;
75
81
 
82
+ ${ENV_BRDF_GLSL}
83
+
76
84
  /** What the frame drew at a screen position, as a world point. */
77
85
  vec3 worldAt(vec2 uv, float stored) {
78
86
  vec4 world = uSsrDepthToWorld * vec4(uv * 2.0 - 1.0, stored, 1.0);
@@ -133,7 +141,18 @@ void main() {
133
141
  mask *= smoothstep(uSsrFacingCos, min(1.0, uSsrFacingCos + 0.25), facing);
134
142
  /* Nothing was drawn here, so there is no surface to reflect in. */
135
143
  mask *= ${glslIsFarDepth('stored')} ? 0.0 : 1.0;
136
- mask *= uSsrStrength;
144
+ /*
145
+ * **How much the surface returns**: one number over the box, or, where the caller asks, its
146
+ * reflectance head-on carried through the split-sum BRDF at this pixel's own view and the
147
+ * surface's roughness — the lit stage's fit, so the two agree on what a surface reflects. A floor
148
+ * then returns more toward the horizon than underfoot, and the share follows the camera.
149
+ */
150
+ float returned = uSsrStrength;
151
+ if (uSsrFresnel.x > 0.5) {
152
+ vec2 dfg = envBrdfApprox(clamp(dot(normal, normalize(toEye)), 0.0, 1.0), uSsrFresnel.y);
153
+ returned = clamp(uSsrStrength * dfg.x + dfg.y, 0.0, 1.0);
154
+ }
155
+ mask *= returned;
137
156
 
138
157
  vec3 found = vec3(0.0);
139
158
  float weight = 0.0;
@@ -177,8 +177,11 @@ uniform vec3 uAmbient;
177
177
  * is the single strongest cue that a surface *is* water switched off because no
178
178
  * directional source existed to make it. A lantern on a jetty threw nothing.
179
179
  *
180
- * Same slots, same weights and the same two falloff shapes as the surface shader, so a
181
- * lamp behaves the same way over water as it does over the ground beside it.
180
+ * Same slots, same weights and the surface shader's falloffs, a light's own exponent among
181
+ * them, so a lamp reaches as far over water as over the ground beside it. **What it gives up**:
182
+ * a spot's cone, which water does not read, so a spot lights the water around it as a lamp
183
+ * would; and the smooth falloff's square, which the surface applies and this has never, so under
184
+ * that falloff a lamp's pool on water is the wider of the two.
182
185
  */
183
186
  #define MAX_LIGHTS ${MAX_POINT_LIGHTS}
184
187
  uniform int uLightCount;
@@ -187,6 +190,8 @@ uniform vec3 uLightColor[MAX_LIGHTS];
187
190
  uniform float uLightRadius[MAX_LIGHTS];
188
191
  uniform float uLightWeight[MAX_LIGHTS];
189
192
  uniform int uLightFalloff;
193
+ /* The surface shader's cone record, of which water reads z alone: a light's own falloff exponent. */
194
+ uniform vec4 uLightCone[MAX_LIGHTS];
190
195
  ${FOG_GLSL}
191
196
  uniform vec3 uCameraPos;
192
197
  uniform sampler2D uReflectionMap;
@@ -243,7 +248,11 @@ void main() {
243
248
  vec3 toLight = uLightPos[i] - vWorldPos;
244
249
  float lightDist = length(toLight);
245
250
  float falloff;
246
- if (uLightFalloff == 1) {
251
+ float exponent = uLightCone[i].z;
252
+ if (exponent > 0.0) {
253
+ float reach = lightDist / max(uLightRadius[i], 1e-4);
254
+ falloff = pow(clamp(1.0 - reach * reach, 0.0, 1.0), exponent);
255
+ } else if (uLightFalloff == 1) {
247
256
  float window = clamp(1.0 - pow(lightDist / max(uLightRadius[i], 1e-4), 4.0), 0.0, 1.0);
248
257
  falloff = window * window / max(lightDist * lightDist, 0.01);
249
258
  } else {
@@ -3,11 +3,14 @@
3
3
  * the kernel to, and nothing at run time calls it.
4
4
  *
5
5
  * **What it spreads.** With `skinScattering: 'screen-space'`, a skin draw writes its specular to the
6
- * frame and its diffuse — the light that went beneath the surface — to a target of its own, with the
7
- * profile it scatters by in the alpha. Before anything blended is drawn, that target is blurred
8
- * along the screen's two axes by Burley's normalised diffusion, one width per channel, and added
9
- * back: light that entered a cheek leaves it a centimetre away, reddest furthest, across the
10
- * pixels that centimetre covers at the cheek's distance. The pre-integrated fit stays in the lit
6
+ * frame, the light its diffuse is made of — the light that went beneath the surface, shaded on a
7
+ * white surface — to a target of its own with the profile it scatters by in the alpha, and its
8
+ * colour to a third. Before anything blended is drawn, the light is blurred along the screen's two
9
+ * axes by Burley's normalised diffusion, one width per channel, multiplied by the colour at each
10
+ * pixel and added back: light that entered a cheek leaves it a centimetre away, reddest furthest,
11
+ * across the pixels that centimetre covers at the cheek's distance, and a brow stays where it is
12
+ * drawn. **Until 4.8.6 the coloured diffuse was spread**, which smeared every mark on a face into
13
+ * the skin around it and laid faint copies of each mark at the taps' offsets. The pre-integrated fit stays in the lit
11
14
  * stage for what this cannot see — a terminator's curvature — and this carries what that cannot:
12
15
  * light crossing a shadow's edge, or a nostril's rim, in the picture rather than in a fit.
13
16
  *
@@ -7,7 +7,9 @@
7
7
  * neighbours' `N·L` as well as its own: the clamped cosine averaged around a ring of the surface's
8
8
  * curvature, weighted by the diffusion profile at each neighbour's distance. The profile is Burley's
9
9
  * normalised diffusion, one per channel, at a scatter distance `d` — the model's `radius` times its
10
- * scatter colour — projected onto the ring's plane, as a cylinder's cross-section sees it. The
10
+ * scatter colour, over `SKIN_MEAN_EXIT`, since `radius` is how far light travels and the profile
11
+ * carries it two and a half `d` — projected onto the ring's plane, as a cylinder's cross-section
12
+ * sees it. The
11
13
  * result depends on `N·L` and on one number per channel, `k = d × curvature`, and at `k = 0` it is
12
14
  * Lambert's.
13
15
  *
@@ -173,6 +173,7 @@ export class SsrPass {
173
173
  gl.uniform3fv(u['uSsrAxis'] ?? null, surface.axis);
174
174
  gl.uniform3fv(u['uSsrTint'] ?? null, surface.tint);
175
175
  gl.uniform1f(u['uSsrStrength'] ?? null, surface.strength);
176
+ gl.uniform2f(u['uSsrFresnel'] ?? null, surface.fresnel ? 1 : 0, surface.roughness);
176
177
  gl.uniform1f(u['uSsrFacingCos'] ?? null, surface.facingCos);
177
178
  gl.uniform1f(u['uSsrReach'] ?? null, surface.reachM);
178
179
  gl.uniform1f(u['uSsrThickness'] ?? null, surface.thicknessM);
@@ -18,6 +18,8 @@
18
18
  * the draw's model matrix and the eye's joint each draw, with `eyeAxisInWorld`.
19
19
  */
20
20
  import type { Vec3 } from '../math/color.ts';
21
+ import { lightmapRegionOf } from './lightmap.ts';
22
+ import type { LightmapRegion } from './lightmap.ts';
21
23
 
22
24
  /** Strands of brushed metal, satin or hair-like fibre: a highlight stretched across them. */
23
25
  export interface AnisotropicModel {
@@ -52,7 +54,12 @@ export interface SkinModel {
52
54
  readonly kind: 'skin';
53
55
  /** The colour light keeps travelling beneath the surface — the mean free path, as a colour. */
54
56
  readonly scatterColor: Vec3;
55
- /** Metres: how far light travels beneath it before it leaves. */
57
+ /**
58
+ * Metres: how far light travels beneath it, on average, before it leaves — from where it entered
59
+ * to where it leaves, for a channel whose share of `scatterColor` is 1. Burley's profile, which
60
+ * the lit stage and the screen-space blur both scatter by, does that at a distance `d` two fifths
61
+ * of it, and carries a tail of light out to about three and a half times it.
62
+ */
56
63
  readonly radius: number;
57
64
  /** 0 to 1: how much a light behind a thin part shows through it. */
58
65
  readonly transmission: number;
@@ -77,8 +84,19 @@ export interface EyeModel {
77
84
  readonly joint: number;
78
85
  }
79
86
 
87
+ /**
88
+ * A surface whose static light was baked: the standard model, plus a lightmap page's light read at
89
+ * the mesh's second coordinates (`MeshData.lightmapUvs`). The material's `modelMap` is the page,
90
+ * from `createLightmap`; see `lightmap.ts` for what it adds and what it gives up.
91
+ */
92
+ export interface LightmapModel {
93
+ readonly kind: 'lightmap';
94
+ /** Where this material's surfaces are on the page: `uv2 * scale + bias`. See `LightmapRegion`. */
95
+ readonly region: LightmapRegion;
96
+ }
97
+
80
98
  /** Every model a material can name, by its `kind`: see `SurfaceMaterial.model`. */
81
- export type SurfaceModel = AnisotropicModel | HairModel | SkinModel | EyeModel;
99
+ export type SurfaceModel = AnisotropicModel | HairModel | SkinModel | EyeModel | LightmapModel;
82
100
  /** A model's name, which also picks its pipeline switch: `SURFACE_MODEL_SWITCH`. */
83
101
  export type SurfaceModelKind = SurfaceModel['kind'];
84
102
 
@@ -88,6 +106,7 @@ export const SURFACE_MODEL_KINDS: readonly SurfaceModelKind[] = [
88
106
  'hair',
89
107
  'skin',
90
108
  'eye',
109
+ 'lightmap',
91
110
  ];
92
111
 
93
112
  /** The lit stage's switch for each kind. */
@@ -96,6 +115,7 @@ export const SURFACE_MODEL_SWITCH: Readonly<Record<SurfaceModelKind, string>> =
96
115
  hair: 'MODEL_HAIR',
97
116
  skin: 'MODEL_SKIN',
98
117
  eye: 'MODEL_EYE',
118
+ lightmap: 'MODEL_LIGHTMAP',
99
119
  };
100
120
 
101
121
  /** Floats a material's model takes: `uModelParams[2]`. */
@@ -144,7 +164,25 @@ export function hairModel(
144
164
  });
145
165
  }
146
166
 
147
- /** Skin. The defaults are a fair skin's: a red mean free path a little over a centimetre long. */
167
+ /**
168
+ * How far Burley's normalised diffusion carries light on average, in units of its own distance
169
+ * `d`: its radial density `(e^(−r/d) + e^(−r/3d)) / 4d` has the mean `(d² + 9d²) / 4d = 2.5 d`. So a
170
+ * skin whose light travels `radius` scatters by a profile at `d = radius / 2.5`.
171
+ *
172
+ * **Until 4.8.7 `radius` was `d` itself**, so light travelled two and a half times as far as the
173
+ * model said, and the screen-space blur — whose taps reach nine `d` — spread a face at arm's length
174
+ * over sixty pixels: a nose, lips and brows washed into one blur where a centimetre covers a few.
175
+ * The lit stage's pre-integrated fit, transmission and penumbra take the same `d`, so the two paths
176
+ * still agree with each other.
177
+ */
178
+ export const SKIN_MEAN_EXIT = 2.5;
179
+
180
+ /** Burley's distance `d` for one channel of a skin, in metres: see `SKIN_MEAN_EXIT`. */
181
+ export function skinProfileDistance(model: SkinModel, channel: 0 | 1 | 2): number {
182
+ return ((model.scatterColor[channel] as number) * model.radius) / SKIN_MEAN_EXIT;
183
+ }
184
+
185
+ /** Skin. The defaults are a fair skin's: red light leaving a little over a centimetre away. */
148
186
  export function skinModel(
149
187
  options: { scatterColor?: Vec3; radius?: number; transmission?: number; profile?: number } = {},
150
188
  ): SkinModel {
@@ -188,25 +226,51 @@ export function eyeModel(
188
226
  });
189
227
  }
190
228
 
229
+ /**
230
+ * A baked surface, reading its region of a lightmap page: the whole page where none is named. An
231
+ * instanced batch's regions compose with this one, each instance's applied first: see
232
+ * `MeshInstances.lightmapRegions`.
233
+ */
234
+ export function lightmapModel(options: { region?: LightmapRegion } = {}): LightmapModel {
235
+ const region = lightmapRegionOf(options.region);
236
+ return Object.freeze({
237
+ kind: 'lightmap',
238
+ region: Object.freeze([region[0], region[1], region[2], region[3]]) as LightmapRegion,
239
+ });
240
+ }
241
+
191
242
  /**
192
243
  * A model's numbers into `out` — eight floats, `uModelParams[0]` then `[1]` — and whether a model
193
- * map is bound into the last. Zeros for the standard model. An eye's axis goes in with the
194
- * descriptor's own until the draw turns it: see `eyeAxisInWorld`.
244
+ * map is bound into the last. An eye's axis goes in with the descriptor's own until the draw turns
245
+ * it: see `eyeAxisInWorld`. `physical` is `SurfaceMaterial.physicalSpecular`, in the lane the
246
+ * models that read it leave free; skin, hair and the eye shade their own highlights and never do.
195
247
  *
196
- * - anisotropic: strength, cos and sin of the rotation · −, −, −, map
248
+ * - standard (null): − · −, −, physical, −
249
+ * - anisotropic: strength, cos and sin of the rotation · −, −, physical, map
197
250
  * - hair: shift, scatter, backlit, − · −, −, −, map
198
- * - skin: the scatter colour, radius · transmission, profile, −, map
251
+ * - skin: the scatter colour, radius / `SKIN_MEAN_EXIT` · transmission, profile, −, map
199
252
  * - eye: iris radius, iris depth, ior, cornea roughness · the axis, map
253
+ * - lightmap: the region's scale and bias · −, −, physical, map
200
254
  */
201
- export function packModel(model: SurfaceModel | null, mapped: boolean, out: Float32Array): void {
255
+ export function packModel(
256
+ model: SurfaceModel | null,
257
+ mapped: boolean,
258
+ out: Float32Array,
259
+ physical = false,
260
+ ): void {
202
261
  out.fill(0);
203
- if (model === null) return;
262
+ const highlight = physical ? 1 : 0;
263
+ if (model === null) {
264
+ out[6] = highlight;
265
+ return;
266
+ }
204
267
  out[7] = mapped ? 1 : 0;
205
268
  switch (model.kind) {
206
269
  case 'anisotropic':
207
270
  out[0] = model.strength;
208
271
  out[1] = Math.cos(model.rotation);
209
272
  out[2] = Math.sin(model.rotation);
273
+ out[6] = highlight;
210
274
  return;
211
275
  case 'hair':
212
276
  out[0] = model.shift;
@@ -217,7 +281,8 @@ export function packModel(model: SurfaceModel | null, mapped: boolean, out: Floa
217
281
  out[0] = model.scatterColor[0];
218
282
  out[1] = model.scatterColor[1];
219
283
  out[2] = model.scatterColor[2];
220
- out[3] = model.radius;
284
+ /* Burley's distance for a channel of share 1, which the shader scales by each share. */
285
+ out[3] = model.radius / SKIN_MEAN_EXIT;
221
286
  out[4] = model.transmission;
222
287
  out[5] = model.profile;
223
288
  return;
@@ -230,6 +295,13 @@ export function packModel(model: SurfaceModel | null, mapped: boolean, out: Floa
230
295
  out[5] = model.axis[1];
231
296
  out[6] = model.axis[2];
232
297
  return;
298
+ case 'lightmap':
299
+ out[0] = model.region[0];
300
+ out[1] = model.region[1];
301
+ out[2] = model.region[2];
302
+ out[3] = model.region[3];
303
+ out[6] = highlight;
304
+ return;
233
305
  }
234
306
  }
235
307