@driftengine/core 4.10.0 → 4.11.1

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 (165) hide show
  1. package/CHANGELOG.json +107 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +6 -5
  4. package/dist/index.js +2 -2
  5. package/dist/render/ambientOcclusionPass.d.ts +3 -2
  6. package/dist/render/ambientOcclusionPass.js +4 -2
  7. package/dist/render/backend/api.d.ts +21 -1
  8. package/dist/render/backend/webgl2/renderer.d.ts +141 -3
  9. package/dist/render/backend/webgl2/renderer.js +454 -42
  10. package/dist/render/backend/webgpu/buffers.d.ts +5 -0
  11. package/dist/render/backend/webgpu/buffers.js +98 -48
  12. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  13. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  14. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  15. package/dist/render/backend/webgpu/clothOps.js +78 -0
  16. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  17. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  18. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  19. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  20. package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
  21. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  22. package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
  23. package/dist/render/backend/webgpu/flatPass.js +2 -1
  24. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  25. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  26. package/dist/render/backend/webgpu/latePass.js +1 -0
  27. package/dist/render/backend/webgpu/motionPass.js +15 -6
  28. package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
  29. package/dist/render/backend/webgpu/pipelineCache.js +39 -1
  30. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  31. package/dist/render/backend/webgpu/renderer.d.ts +100 -1
  32. package/dist/render/backend/webgpu/renderer.js +423 -95
  33. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  34. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  35. package/dist/render/backend/webgpu/staticBundles.js +2 -1
  36. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  37. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  38. package/dist/render/globalMediumPass.d.ts +2 -0
  39. package/dist/render/globalMediumPass.js +1 -0
  40. package/dist/render/indexWidth.d.ts +17 -0
  41. package/dist/render/indexWidth.js +23 -0
  42. package/dist/render/instances.d.ts +13 -1
  43. package/dist/render/instances.js +10 -0
  44. package/dist/render/lightMatrix.d.ts +11 -0
  45. package/dist/render/lightMatrix.js +14 -0
  46. package/dist/render/mesh.d.ts +9 -0
  47. package/dist/render/mesh.js +38 -14
  48. package/dist/render/occlusionFade.d.ts +21 -0
  49. package/dist/render/occlusionFade.js +27 -0
  50. package/dist/render/renderQuality.d.ts +30 -0
  51. package/dist/render/renderQuality.js +5 -0
  52. package/dist/render/rendererHarness.d.ts +7 -0
  53. package/dist/render/rendererHarness.js +12 -0
  54. package/dist/render/sceneTarget.d.ts +2 -0
  55. package/dist/render/sceneTarget.js +1 -1
  56. package/dist/render/shader.d.ts +15 -0
  57. package/dist/render/shader.js +41 -0
  58. package/dist/render/shaders/ambientOcclusion.js +12 -1
  59. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  60. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  61. package/dist/render/shaders/flat/glassTint.js +3 -0
  62. package/dist/render/shaders/flat/index.d.ts +15 -0
  63. package/dist/render/shaders/flat/index.js +24 -4
  64. package/dist/render/shaders/flat/layered.d.ts +27 -0
  65. package/dist/render/shaders/flat/layered.js +198 -0
  66. package/dist/render/shaders/flat/main.d.ts +1 -1
  67. package/dist/render/shaders/flat/main.js +50 -16
  68. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  69. package/dist/render/shaders/flat/mapReads.js +38 -0
  70. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  71. package/dist/render/shaders/flat/movingSun.js +46 -0
  72. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  73. package/dist/render/shaders/flat/normalMap.js +23 -3
  74. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  75. package/dist/render/shaders/flat/overlay.js +41 -19
  76. package/dist/render/shaders/flat/preamble.js +23 -5
  77. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  78. package/dist/render/shaders/flat/worldUv.js +113 -0
  79. package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +6 -1
  80. package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
  81. package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
  82. package/dist/render/shaders/generated/flat.wgsl.js +791 -391
  83. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  84. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  85. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  86. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  87. package/dist/render/shaders/globalMedium.js +9 -1
  88. package/dist/render/shaders/lightVolume.js +9 -1
  89. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  90. package/dist/render/shaders/tangentFrame.js +16 -3
  91. package/dist/render/skinnedClothRun.d.ts +28 -0
  92. package/dist/render/skinnedClothRun.js +103 -35
  93. package/dist/render/surfaceLayers.d.ts +107 -0
  94. package/dist/render/surfaceLayers.js +82 -0
  95. package/dist/render/surfaceOverlay.d.ts +16 -3
  96. package/dist/render/surfaceOverlay.js +11 -6
  97. package/dist/render/surfaceProjection.d.ts +34 -0
  98. package/dist/render/surfaceProjection.js +30 -0
  99. package/dist/render/surfaceTexture.d.ts +39 -0
  100. package/dist/render/transmission.d.ts +21 -0
  101. package/dist/render/transmission.js +21 -0
  102. package/dist/render/vertexPacking.d.ts +48 -0
  103. package/dist/render/vertexPacking.js +68 -0
  104. package/dist/ui/splash.d.ts +42 -2
  105. package/dist/ui/splash.js +78 -13
  106. package/package.json +6 -6
  107. package/src/index.ts +6 -2
  108. package/src/render/ambientOcclusionPass.ts +4 -1
  109. package/src/render/backend/api.ts +31 -0
  110. package/src/render/backend/webgl2/renderer.ts +519 -56
  111. package/src/render/backend/webgpu/buffers.ts +153 -54
  112. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  113. package/src/render/backend/webgpu/clothOps.ts +86 -0
  114. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  115. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  116. package/src/render/backend/webgpu/drawCommand.ts +3 -0
  117. package/src/render/backend/webgpu/flatPass.ts +26 -28
  118. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  119. package/src/render/backend/webgpu/latePass.ts +1 -0
  120. package/src/render/backend/webgpu/motionPass.ts +18 -7
  121. package/src/render/backend/webgpu/pipelineCache.ts +46 -7
  122. package/src/render/backend/webgpu/renderer.ts +622 -164
  123. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  124. package/src/render/backend/webgpu/staticBundles.ts +2 -1
  125. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  126. package/src/render/globalMediumPass.ts +7 -0
  127. package/src/render/indexWidth.ts +27 -0
  128. package/src/render/instances.ts +18 -1
  129. package/src/render/lightMatrix.ts +15 -0
  130. package/src/render/mesh.ts +66 -14
  131. package/src/render/occlusionFade.ts +29 -0
  132. package/src/render/renderQuality.ts +38 -0
  133. package/src/render/rendererHarness.ts +15 -0
  134. package/src/render/sceneTarget.ts +3 -0
  135. package/src/render/shader.ts +59 -0
  136. package/src/render/shaders/ambientOcclusion.ts +12 -1
  137. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  138. package/src/render/shaders/flat/glassTint.ts +7 -1
  139. package/src/render/shaders/flat/index.ts +39 -4
  140. package/src/render/shaders/flat/layered.ts +198 -0
  141. package/src/render/shaders/flat/main.ts +50 -16
  142. package/src/render/shaders/flat/mapReads.ts +38 -0
  143. package/src/render/shaders/flat/movingSun.ts +46 -0
  144. package/src/render/shaders/flat/normalMap.ts +23 -3
  145. package/src/render/shaders/flat/overlay.ts +41 -19
  146. package/src/render/shaders/flat/preamble.ts +23 -5
  147. package/src/render/shaders/flat/worldUv.ts +113 -0
  148. package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
  149. package/src/render/shaders/generated/flat.wgsl.ts +791 -391
  150. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  151. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  152. package/src/render/shaders/globalMedium.ts +9 -1
  153. package/src/render/shaders/lightVolume.ts +9 -1
  154. package/src/render/shaders/tangentFrame.ts +16 -3
  155. package/src/render/skinnedClothRun.ts +138 -38
  156. package/src/render/surfaceLayers.ts +176 -0
  157. package/src/render/surfaceOverlay.ts +24 -7
  158. package/src/render/surfaceProjection.ts +57 -0
  159. package/src/render/surfaceTexture.ts +39 -0
  160. package/src/render/transmission.ts +27 -0
  161. package/src/render/vertexPacking.ts +109 -0
  162. package/src/ui/splash.ts +83 -14
  163. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  164. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  165. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
@@ -11,74 +11,142 @@
11
11
  * millisecond a frame on the main thread, and 4,500 are twenty — a garment that size wants the
12
12
  * device. The device solver ran 4,500 particles in 0.65 ms a frame, most of it the dispatches
13
13
  * rather than the particles (`demo/dev/skinnedCloth.html?bench=1`).
14
+ *
15
+ * **Several garments step as one with `createSkinnedClothSet`**: one pass, one submit and one
16
+ * garment's dispatches a step for all of them, where a garment each was a pass, a submit and its
17
+ * own dispatches each. They must share a step, substeps, iterations and most steps a frame.
14
18
  */
15
19
  import { SkinnedCloth } from '@driftengine/physics';
16
20
  import { computeHostOf } from './backend/webgpu/computeHost.js';
17
- import { GpuSkinnedCloth } from './backend/webgpu/gpuSkinnedCloth.js';
21
+ import { GpuClothSet } from './backend/webgpu/gpuClothSet.js';
18
22
  /** A garment for `renderer`, from a set-up checked by name. */
19
23
  export function createSkinnedCloth(renderer, setup) {
20
24
  const host = computeHostOf(renderer);
21
- return host === null ? new CpuCloth(renderer, setup) : new DeviceCloth(host, setup);
25
+ const set = host === null ? new CpuClothSet(renderer, [setup]) : new DeviceClothSet(host, [setup]);
26
+ return new OneGarment(set);
27
+ }
28
+ /** A set of one, as a solver: what `createSkinnedCloth` hands back. */
29
+ class OneGarment {
30
+ set;
31
+ particles;
32
+ runsOn;
33
+ constructor(set) {
34
+ this.set = set;
35
+ this.particles = set.particles[0];
36
+ this.runsOn = set.runsOn;
37
+ }
38
+ step(globals, model, dt) {
39
+ this.set.setPose(0, globals, model);
40
+ this.set.step(dt);
41
+ }
42
+ setWind(x, y, z) {
43
+ this.set.setWind(0, x, y, z);
44
+ }
45
+ reset() {
46
+ this.set.reset(0);
47
+ }
48
+ dispose() {
49
+ this.set.dispose();
50
+ }
51
+ /** The particles where the last step left them: the parity check's door. */
52
+ read() {
53
+ return this.set.read(0);
54
+ }
22
55
  }
23
- class DeviceCloth {
56
+ /**
57
+ * Garments for `renderer` stepped together, from set-ups checked by name: one pass, one submit and
58
+ * one garment's dispatches a step on the device, whatever the count. They must agree on `step`,
59
+ * `substeps`, `iterations` and `maxSteps`, and are refused by the first they disagree on.
60
+ */
61
+ export function createSkinnedClothSet(renderer, setups) {
62
+ const host = computeHostOf(renderer);
63
+ return host === null ? new CpuClothSet(renderer, setups) : new DeviceClothSet(host, setups);
64
+ }
65
+ class DeviceClothSet {
24
66
  host;
25
67
  runsOn = 'device';
26
68
  particles;
27
- cloth;
28
- constructor(host, setup) {
69
+ set;
70
+ constructor(host, setups) {
29
71
  this.host = host;
30
- this.cloth = new GpuSkinnedCloth(host.device(), setup);
31
- this.particles = this.cloth.particles;
72
+ this.set = new GpuClothSet(host.device(), setups);
73
+ const particles = [];
74
+ for (let g = 0; g < this.set.size; g++)
75
+ particles.push(this.set.particles(g));
76
+ this.particles = particles;
32
77
  }
33
- step(globals, model, dt) {
78
+ setPose(g, globals, model) {
34
79
  if (this.host.lost())
35
80
  return;
36
- this.cloth.advance(globals, model, dt, this.host.changeFrame());
81
+ this.set.setPose(g, globals, model);
37
82
  }
38
- setWind(x, y, z) {
39
- this.cloth.setWind(x, y, z);
83
+ setWind(g, x, y, z) {
84
+ this.set.setWind(g, x, y, z);
40
85
  }
41
- reset() {
86
+ reset(g) {
87
+ if (this.host.lost())
88
+ return;
89
+ this.set.reset(g);
90
+ }
91
+ step(dt) {
42
92
  if (this.host.lost())
43
93
  return;
44
- this.cloth.reset();
94
+ this.set.step(dt, this.host.changeFrame());
45
95
  }
46
96
  /** Its buffers at once — its work is submitted — and its textures with the frame's. */
47
97
  dispose() {
48
- this.cloth.dispose();
49
- this.host.retire(this.cloth.particles.textures);
98
+ this.set.dispose();
99
+ for (let g = 0; g < this.set.size; g++)
100
+ this.host.retire(this.set.particles(g).textures);
50
101
  }
51
- /** The particles where the last step left them: the parity check's door, waits on the device. */
52
- read() {
53
- return this.cloth.read();
102
+ /** Garment `g`'s particles where its last step left them: the parity check's, waits on the device. */
103
+ read(g) {
104
+ return this.set.read(g);
54
105
  }
55
106
  }
56
- class CpuCloth {
107
+ class CpuClothSet {
57
108
  renderer;
58
109
  runsOn = 'cpu';
59
110
  particles;
60
- cloth;
61
- /** The frame's particles at `alpha`, before they go up. */
111
+ cloths;
112
+ /** Each garment's particles at `alpha`, before they go up. */
62
113
  staging;
63
- constructor(renderer, setup) {
114
+ constructor(renderer, setups) {
64
115
  this.renderer = renderer;
65
- this.cloth = new SkinnedCloth(setup);
66
- this.particles = renderer.createClothParticles(this.cloth.count);
67
- this.staging = new Float32Array(this.cloth.count * 3);
116
+ this.cloths = setups.map((setup) => new SkinnedCloth(setup));
117
+ this.particles = this.cloths.map((cloth) => renderer.createClothParticles(cloth.count));
118
+ this.staging = this.cloths.map((cloth) => new Float32Array(cloth.count * 3));
68
119
  }
69
- step(globals, model, dt) {
70
- this.cloth.setPose(globals, model);
71
- this.cloth.advance(dt);
72
- this.cloth.interpolate(this.cloth.alpha, this.staging);
73
- this.renderer.updateClothParticles(this.particles, this.staging);
120
+ setPose(g, globals, model) {
121
+ this.at(g).setPose(globals, model);
74
122
  }
75
- setWind(x, y, z) {
76
- this.cloth.setWind(x, y, z);
123
+ setWind(g, x, y, z) {
124
+ this.at(g).setWind(x, y, z);
77
125
  }
78
- reset() {
79
- this.cloth.reset();
126
+ reset(g) {
127
+ this.at(g).reset();
128
+ }
129
+ step(dt) {
130
+ for (let g = 0; g < this.cloths.length; g++) {
131
+ const cloth = this.cloths[g];
132
+ const staging = this.staging[g];
133
+ cloth.advance(dt);
134
+ cloth.interpolate(cloth.alpha, staging);
135
+ this.renderer.updateClothParticles(this.particles[g], staging);
136
+ }
80
137
  }
81
138
  dispose() {
82
- this.renderer.disposeClothParticles(this.particles);
139
+ for (const particles of this.particles)
140
+ this.renderer.disposeClothParticles(particles);
141
+ }
142
+ /** Garment `g`'s particles where its last step left them, as the device set reads its own. */
143
+ read(g) {
144
+ return Promise.resolve(new Float32Array(this.at(g).positions));
145
+ }
146
+ at(g) {
147
+ const cloth = this.cloths[g];
148
+ if (cloth === undefined)
149
+ throw new Error(`skinned cloth set: no garment ${g}`);
150
+ return cloth;
83
151
  }
84
152
  }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * A material blended from layers by a mask: `SurfaceMaterial.layers`, the four vectors
3
+ * `shaders/flat/layered.ts` reads it from, and the decisions both backends take about it.
4
+ */
5
+ import type { SurfaceProjection } from './surfaceProjection.ts';
6
+ /** The most layers a material blends: a base and one for each of the mask's four channels. */
7
+ export declare const MAX_SURFACE_LAYERS = 5;
8
+ /**
9
+ * Up to five layers, each its own colour, normal and roughness at its own repeat, blended by a mask.
10
+ *
11
+ * **Layer `i` is layer `i` of the material's arrays**: `albedo`, `normal` and `orm` each an array
12
+ * whose layers are the material's layers in order, read at the mesh's coordinates times
13
+ * `repeats[i]`, so rock can repeat forty times across a cliff while the moss on it repeats three.
14
+ * Where the material has a `projection`, the layers are read at the world's horizontal position
15
+ * instead, so a repeat is so many a metre (with the projection's own scale at 1). **The mask** is
16
+ * read at the mesh's own coordinates and lays each layer over those before it: red lays layer 1
17
+ * over the base, green layer 2 over that, blue layer 3, alpha layer 4. `emissiveLayer` gives the
18
+ * material's emissive map to one layer, glowing where that layer shows and nowhere it is covered;
19
+ * absent, the map glows as on any material.
20
+ *
21
+ * **Maps beyond the layers ride the material's own arrays, after its layers**, so they take no
22
+ * texture binding of their own — the lit stage has none to spare: a mask the ORM array carries
23
+ * (`mask: 'orm'`) is the ORM array's layer just past the layers, an `addMask` the one after that,
24
+ * and `meshNormal` the normal array's layer just past the layers. Each shares its array's size and
25
+ * format, so a four-channel mask wants an array that stores four.
26
+ *
27
+ * **What it gives up**: every layer's maps are read wherever any shows, three reads a layer, so five
28
+ * layers are fifteen reads where one material is three; layers share an array's size and format; a
29
+ * cutout reads the base layer's alpha; a mask read where a model's map goes leaves no room for one,
30
+ * so a lightmapped surface carries its mask in its ORM array or its vertices; a triplanar projection
31
+ * lays layers on the horizontal plane alone; and a mask is sampled linear, so it is uploaded as data
32
+ * (`colorSpace: 'linear'`).
33
+ */
34
+ export interface SurfaceLayers<Texture> {
35
+ /**
36
+ * Where the weights come from, red to alpha laying layers 1 to 4: a map read where a shading
37
+ * model's map goes; `'orm'`, the ORM array's layer just past the layers; or `'vertex'`, the
38
+ * vertex colour's red, green and blue, which then weights the layers instead of tinting them.
39
+ */
40
+ readonly mask: Texture | 'orm' | 'vertex';
41
+ /** Each layer's repeats across the mesh's coordinates, base first: one to five of them. */
42
+ readonly repeats: readonly number[];
43
+ /** The layer the emissive map glows on, 0 to 4. Absent: everywhere, as on any material. */
44
+ readonly emissiveLayer?: number;
45
+ /**
46
+ * How the layers combine. `'over'`, the default, lays each over the ones before it by its
47
+ * channel. `'sum'` mixes the base toward the sum of each layer times its channel, by the channels'
48
+ * sum held to 1: two layers at half each are both half there, where laying over would let the
49
+ * second cover half of the first.
50
+ */
51
+ readonly blend?: 'over' | 'sum';
52
+ /**
53
+ * A second mask added to one layer's weight, read where the layers are (the world, under a
54
+ * `projection`) times `repeat`, its red times `intensity`: sand drifting into the gravel's layer at
55
+ * a scale of its own. The ORM array's layer after the layers and any mask it carries.
56
+ */
57
+ readonly addMask?: {
58
+ readonly layer: number;
59
+ readonly repeat: number;
60
+ readonly intensity: number;
61
+ };
62
+ /**
63
+ * One layer weighted by how much the surface faces up rather than by its mask channel:
64
+ * `saturate(bias + sharpness × (up / 2 + 1/2))`, `up` the world normal's upward share before any
65
+ * normal map. Snow on the tops of rocks, moss on a ledge.
66
+ */
67
+ readonly facing?: {
68
+ readonly layer: number;
69
+ readonly bias: number;
70
+ readonly sharpness: number;
71
+ };
72
+ /**
73
+ * A normal map read at the mesh's own coordinates and tangents, the normal array's layer just past
74
+ * the layers, under the layers' blended normal: the large shapes of a rock or a cliff, which a
75
+ * layer repeating forty times across it cannot carry.
76
+ */
77
+ readonly meshNormal?: boolean;
78
+ }
79
+ /** Floats `packSurfaceLayers` writes: four vectors. */
80
+ export declare const LAYER_FLOATS = 16;
81
+ /**
82
+ * `layers` into `out`, four vectors. The first five floats are the repeats (1 for a layer not
83
+ * given), then how many layers there are, which one glows (−1 for none) and where the mask comes
84
+ * from (0 a map, 1 the ORM array, 2 the vertex colour). Then the blend (0 over, 1 sum), whether a
85
+ * mesh normal is laid under the layers, the added mask's layer (−1 for none) and repeat; its
86
+ * intensity; and the facing layer (−1 for none), its bias and its sharpness. Zeros, no layers, where
87
+ * there are none.
88
+ *
89
+ * A repeat that is not a positive number is 1, so a layer is never laid at a scale that collapses it
90
+ * to a point; a glow, an added mask or a facing layer naming a layer there is not is none, and the
91
+ * base, which no channel lays, is not one an added mask or the facing can name.
92
+ */
93
+ export declare function packSurfaceLayers<Texture>(layers: SurfaceLayers<Texture> | null | undefined, out: Float32Array): void;
94
+ /** The texture a layered material's mask is, where it is one rather than another array or the vertices. */
95
+ export declare function layerMaskMap<Texture>(layers: SurfaceLayers<Texture> | null | undefined): Texture | null;
96
+ /**
97
+ * Whether a material's layers can be drawn beside its `modelMap`: always, unless the mask is a map,
98
+ * which is read where a model's map goes and so cannot share the slot with one — a lightmap's page
99
+ * among them. The backends draw such a material as one layer and say so once.
100
+ */
101
+ export declare function layersFitBeside<Texture>(layers: SurfaceLayers<Texture>, modelMap: Texture | null | undefined): boolean;
102
+ /**
103
+ * The projection a layered material is drawn with: a triplanar one is laid on the horizontal plane,
104
+ * at its own scale, since every layer's maps read three times over would be forty-five reads a pixel.
105
+ * Anything else as it was. Whether to say so is the caller's.
106
+ */
107
+ export declare function layeredProjection<Texture>(projection: SurfaceProjection | null | undefined, layers: SurfaceLayers<Texture> | null | undefined): SurfaceProjection | null;
@@ -0,0 +1,82 @@
1
+ /** The most layers a material blends: a base and one for each of the mask's four channels. */
2
+ export const MAX_SURFACE_LAYERS = 5;
3
+ /** Floats `packSurfaceLayers` writes: four vectors. */
4
+ export const LAYER_FLOATS = 16;
5
+ /** Where the mask is read, as the fourth float of the second vector carries it. */
6
+ const MASK_SOURCE = { map: 0, orm: 1, vertex: 2 };
7
+ /**
8
+ * `layers` into `out`, four vectors. The first five floats are the repeats (1 for a layer not
9
+ * given), then how many layers there are, which one glows (−1 for none) and where the mask comes
10
+ * from (0 a map, 1 the ORM array, 2 the vertex colour). Then the blend (0 over, 1 sum), whether a
11
+ * mesh normal is laid under the layers, the added mask's layer (−1 for none) and repeat; its
12
+ * intensity; and the facing layer (−1 for none), its bias and its sharpness. Zeros, no layers, where
13
+ * there are none.
14
+ *
15
+ * A repeat that is not a positive number is 1, so a layer is never laid at a scale that collapses it
16
+ * to a point; a glow, an added mask or a facing layer naming a layer there is not is none, and the
17
+ * base, which no channel lays, is not one an added mask or the facing can name.
18
+ */
19
+ export function packSurfaceLayers(layers, out) {
20
+ out.fill(0);
21
+ if (layers === null || layers === undefined || layers.repeats.length === 0)
22
+ return;
23
+ const count = Math.min(layers.repeats.length, MAX_SURFACE_LAYERS);
24
+ for (let i = 0; i < MAX_SURFACE_LAYERS; i++)
25
+ out[i] = positive(i < count ? layers.repeats[i] : 1);
26
+ out[5] = count;
27
+ const glow = layers.emissiveLayer;
28
+ out[6] = glow !== undefined && Number.isInteger(glow) && glow >= 0 && glow < count ? glow : -1;
29
+ out[7] =
30
+ layers.mask === 'orm'
31
+ ? MASK_SOURCE.orm
32
+ : layers.mask === 'vertex'
33
+ ? MASK_SOURCE.vertex
34
+ : MASK_SOURCE.map;
35
+ out[8] = layers.blend === 'sum' ? 1 : 0;
36
+ out[9] = layers.meshNormal === true ? 1 : 0;
37
+ const added = layers.addMask;
38
+ const addLayer = laidLayer(added?.layer, count);
39
+ out[10] = addLayer;
40
+ out[11] = addLayer < 0 ? 0 : positive(added?.repeat);
41
+ out[12] = addLayer < 0 ? 0 : finite(added?.intensity);
42
+ const facing = layers.facing;
43
+ const faceLayer = laidLayer(facing?.layer, count);
44
+ out[13] = faceLayer;
45
+ out[14] = faceLayer < 0 ? 0 : finite(facing?.bias);
46
+ out[15] = faceLayer < 0 ? 0 : finite(facing?.sharpness);
47
+ }
48
+ /** The texture a layered material's mask is, where it is one rather than another array or the vertices. */
49
+ export function layerMaskMap(layers) {
50
+ const mask = layers?.mask;
51
+ return mask === undefined || mask === 'orm' || mask === 'vertex' ? null : mask;
52
+ }
53
+ /**
54
+ * Whether a material's layers can be drawn beside its `modelMap`: always, unless the mask is a map,
55
+ * which is read where a model's map goes and so cannot share the slot with one — a lightmap's page
56
+ * among them. The backends draw such a material as one layer and say so once.
57
+ */
58
+ export function layersFitBeside(layers, modelMap) {
59
+ return modelMap === null || modelMap === undefined || layerMaskMap(layers) === null;
60
+ }
61
+ /**
62
+ * The projection a layered material is drawn with: a triplanar one is laid on the horizontal plane,
63
+ * at its own scale, since every layer's maps read three times over would be forty-five reads a pixel.
64
+ * Anything else as it was. Whether to say so is the caller's.
65
+ */
66
+ export function layeredProjection(projection, layers) {
67
+ if (projection === null || projection === undefined)
68
+ return null;
69
+ if (layers === null || layers === undefined || projection.kind !== 'triplanar')
70
+ return projection;
71
+ return { kind: 'planar', scale: projection.scale };
72
+ }
73
+ /** A layer an added mask or the facing may name: one a channel lays, 1 to `count − 1`, or −1. */
74
+ function laidLayer(layer, count) {
75
+ return layer !== undefined && Number.isInteger(layer) && layer >= 1 && layer < count ? layer : -1;
76
+ }
77
+ function positive(value) {
78
+ return value !== undefined && Number.isFinite(value) && value > 0 ? value : 1;
79
+ }
80
+ function finite(value) {
81
+ return value !== undefined && Number.isFinite(value) ? value : 0;
82
+ }
@@ -27,12 +27,24 @@ export interface OverlayRegion {
27
27
  * raised to a contrast, times a noise laid in screen space, a slow pulse and a mask, in a colour.
28
28
  * Added to what the surface emits, and not gated on the night as emission is: it is a glow the
29
29
  * surface wears, not light it gives off.
30
+ *
31
+ * **Or, with `mode: 'blend'`, a colour the surface is pulled toward**, lit like the rest of it: the
32
+ * base colour mixed toward the rim's by the edge times `alpha`, before any light reads it, so the
33
+ * rim is bright where light falls and dark where none does. The colour is `colour` times
34
+ * `intensity`, each channel held at 1 as a base colour is, so a colour of 8 red is a red of 1 and
35
+ * not a glow; `alpha` is the weight, which may pass 1 to saturate the edge sooner.
30
36
  */
31
37
  export interface SurfaceRim {
32
38
  readonly colour: Vec3;
33
- /** How bright, 0 for none. Multiplied with `alpha`. */
39
+ /**
40
+ * How bright, 0 for none, multiplied with `alpha`. Where the rim blends it scales `colour`
41
+ * instead, so 0 there is a rim of black, and `alpha` alone says how much: 0 for none.
42
+ */
34
43
  readonly intensity: number;
44
+ /** The rim's weight, 1 unless given. */
35
45
  readonly alpha?: number;
46
+ /** `'add'`, the default, lays the rim on as glow; `'blend'` pulls the base colour toward it, lit. */
47
+ readonly mode?: 'add' | 'blend';
36
48
  /** The edge's exponent: larger keeps the rim nearer the silhouette. 1.5 unless given. */
37
49
  readonly falloff?: number;
38
50
  /**
@@ -118,10 +130,11 @@ export declare const OVERLAY_FLOATS: number;
118
130
  * no overlay. A region with a zero scale is an image not given, and its term reads as none — a
119
131
  * noise of 1, a mask of 0, no wrinkle — and every region is one without `maps`. Fifteen vectors:
120
132
  *
121
- * - 0: rim colour, rim strength (intensity times alpha; 0 is no rim)
133
+ * - 0: rim colour, rim strength (intensity times alpha; 0 is no rim). A blending rim: the colour
134
+ * times intensity, each channel held at 1, and the alpha alone
122
135
  * - 1: falloff, upward lean, contrast, mask weight
123
136
  * - 2: the rim's noise region; 3: its scroll along x and y, its tiling, the pulse's rate
124
- * - 4: the pulse's low and high (1 and 1 where none)
137
+ * - 4: the pulse's low and high (1 and 1 where none), and the mode: 0 adds, 1 blends
125
138
  * - 5: the rim's mask region
126
139
  * - 6: the dissolve's noise region (a zero scale is no dissolve); 7: its tiling, threshold, edge
127
140
  * - 8: the edge's light and its strength; 9: the laid-over colour and its amount
@@ -14,10 +14,11 @@ function region(out, at, r) {
14
14
  * no overlay. A region with a zero scale is an image not given, and its term reads as none — a
15
15
  * noise of 1, a mask of 0, no wrinkle — and every region is one without `maps`. Fifteen vectors:
16
16
  *
17
- * - 0: rim colour, rim strength (intensity times alpha; 0 is no rim)
17
+ * - 0: rim colour, rim strength (intensity times alpha; 0 is no rim). A blending rim: the colour
18
+ * times intensity, each channel held at 1, and the alpha alone
18
19
  * - 1: falloff, upward lean, contrast, mask weight
19
20
  * - 2: the rim's noise region; 3: its scroll along x and y, its tiling, the pulse's rate
20
- * - 4: the pulse's low and high (1 and 1 where none)
21
+ * - 4: the pulse's low and high (1 and 1 where none), and the mode: 0 adds, 1 blends
21
22
  * - 5: the rim's mask region
22
23
  * - 6: the dissolve's noise region (a zero scale is no dissolve); 7: its tiling, threshold, edge
23
24
  * - 8: the edge's light and its strength; 9: the laid-over colour and its amount
@@ -31,10 +32,14 @@ export function packSurfaceOverlay(overlay, out) {
31
32
  const images = overlay.maps !== undefined;
32
33
  const rim = overlay.rim;
33
34
  if (rim !== undefined) {
34
- out[0] = finite(rim.colour[0], 0);
35
- out[1] = finite(rim.colour[1], 0);
36
- out[2] = finite(rim.colour[2], 0);
37
- out[3] = Math.max(0, finite(rim.intensity, 0) * finite(rim.alpha, 1));
35
+ const blend = rim.mode === 'blend';
36
+ const scale = blend ? finite(rim.intensity, 0) : 1;
37
+ for (let c = 0; c < 3; c++) {
38
+ const channel = finite(rim.colour[c], 0) * scale;
39
+ out[c] = blend ? Math.min(Math.max(channel, 0), 1) : channel;
40
+ }
41
+ out[3] = Math.max(0, (blend ? 1 : finite(rim.intensity, 0)) * finite(rim.alpha, 1));
42
+ out[18] = blend ? 1 : 0;
38
43
  out[4] = Math.max(0, finite(rim.falloff, 1.5));
39
44
  out[5] = Math.max(0, finite(rim.upward, 0.4));
40
45
  out[6] = Math.max(0, finite(rim.contrast, 1));
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A material's maps placed by where a point is in the world: `SurfaceMaterial.projection`, and the
3
+ * one vector `shaders/flat/worldUv.ts` reads it from.
4
+ */
5
+ /**
6
+ * Texture coordinates from the world rather than from the mesh.
7
+ *
8
+ * **`'planar'`** lays the maps across the two horizontal axes, `x` and `z`: ground whose mesh
9
+ * carries flat coordinates, or none worth reading, shows its texture at `scale` repeats a metre.
10
+ * **`'triplanar'`** reads each map on the three planes across `x`, `y` and `z` and blends them by
11
+ * how squarely the surface faces each, the normal's components raised to `sharpness`, so walls and
12
+ * rock wear a texture without stretching. See `worldUv.ts` for the normal map's blend.
13
+ *
14
+ * **What it gives up**: the world's coordinates do not move with a mesh, so a moving mesh slides
15
+ * under its texture; triplanar reads every map three times; and the material's `uScale`, `vScale`
16
+ * and offsets, which are the mesh's coordinates', do not apply.
17
+ */
18
+ export interface SurfaceProjection {
19
+ readonly kind: 'planar' | 'triplanar';
20
+ /** Repeats a metre: 2 lays a texture every half metre. */
21
+ readonly scale: number;
22
+ /** How sharply triplanar's planes meet: the exponent on the normal. 4 unless given. */
23
+ readonly sharpness?: number;
24
+ }
25
+ /** Floats `packSurfaceProjection` writes: one vector. */
26
+ export declare const PROJECTION_FLOATS = 4;
27
+ /**
28
+ * `projection` into `out`: its kind (0 none, 1 planar, 2 triplanar), its repeats a metre and its
29
+ * sharpness; zeros for none. A scale that is not finite and above zero projects nothing, so a
30
+ * material is never drawn at a scale that collapses its texture to a point.
31
+ */
32
+ export declare function packSurfaceProjection(projection: SurfaceProjection | null | undefined, out: Float32Array): void;
33
+ /** Whether a material projects its maps at all. */
34
+ export declare function projects(projection: SurfaceProjection | null | undefined): boolean;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * A material's maps placed by where a point is in the world: `SurfaceMaterial.projection`, and the
3
+ * one vector `shaders/flat/worldUv.ts` reads it from.
4
+ */
5
+ /** Floats `packSurfaceProjection` writes: one vector. */
6
+ export const PROJECTION_FLOATS = 4;
7
+ /**
8
+ * `projection` into `out`: its kind (0 none, 1 planar, 2 triplanar), its repeats a metre and its
9
+ * sharpness; zeros for none. A scale that is not finite and above zero projects nothing, so a
10
+ * material is never drawn at a scale that collapses its texture to a point.
11
+ */
12
+ export function packSurfaceProjection(projection, out) {
13
+ out.fill(0);
14
+ if (projection === null || projection === undefined)
15
+ return;
16
+ const scale = projection.scale;
17
+ if (!Number.isFinite(scale) || scale <= 0)
18
+ return;
19
+ out[0] = projection.kind === 'triplanar' ? 2 : 1;
20
+ out[1] = scale;
21
+ const sharpness = projection.sharpness ?? 4;
22
+ out[2] = Number.isFinite(sharpness) ? Math.min(Math.max(sharpness, 1), 64) : 4;
23
+ }
24
+ /** Whether a material projects its maps at all. */
25
+ export function projects(projection) {
26
+ return (projection !== null &&
27
+ projection !== undefined &&
28
+ Number.isFinite(projection.scale) &&
29
+ projection.scale > 0);
30
+ }
@@ -8,6 +8,8 @@
8
8
  * array samples exactly as a 2D texture does. A mesh names its layer per vertex (`MeshData.layers`),
9
9
  * so a merged block wearing forty facades is one draw.
10
10
  */
11
+ import type { SurfaceLayers } from './surfaceLayers.ts';
12
+ import type { SurfaceProjection } from './surfaceProjection.ts';
11
13
  import type { SurfaceModel } from './surfaceModel.ts';
12
14
  import type { CompressedTextureFormat, CompressedTextureSource, SurfaceSource } from './compressedSource.ts';
13
15
  import type { SurfaceLayerEffect } from './surfaceEffects.ts';
@@ -196,6 +198,43 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
196
198
  * and an 8-bit frame clips the brighter peak without `hdrScene`.
197
199
  */
198
200
  physicalSpecular?: boolean;
201
+ /**
202
+ * Texture coordinates from where a point is in the world rather than from the mesh: `'planar'`
203
+ * across the horizontal axes for ground, `'triplanar'` on three planes for walls and rock, at so
204
+ * many repeats a metre. Absent, the mesh's, as every material's were. **A lit switch**, compiled in
205
+ * the first time a material asks, so a scene that never asks pays nothing. See
206
+ * `surfaceProjection.ts` for what it gives up.
207
+ */
208
+ projection?: SurfaceProjection | null;
209
+ /**
210
+ * How much light from behind a thin surface lets through, 0 to 1: a banner lit from behind, a
211
+ * leaf against the sun, a lampshade. The light falling on the far side — the sun, lamps, area
212
+ * lights, DriftLight — reaches the eye through the surface, coloured by its own colour; a metal
213
+ * lets none through. 0, the default, is every material as it was. Two-sided or not: a one-sided
214
+ * surface seen from its front shows the light behind it as well.
215
+ *
216
+ * **What it gives up**: a thin surface, not a volume, so a thick one lets through as much as a
217
+ * sheet; the light behind is not blurred by the surface; and none of it on glass, which lets
218
+ * light through by its own rule (`TranslucentMeshOptions.glass`).
219
+ */
220
+ diffuseTransmission?: number;
221
+ /**
222
+ * The colour the light from behind takes through the surface, linear, in place of its own colour:
223
+ * a banner whose cloth glows a flat red behind a printed face, a leaf whose veins are not what the
224
+ * light through it shows. Absent, the surface's colour, as `diffuseTransmission` has always used.
225
+ * Each component is held at zero or above. Read only where `diffuseTransmission` lets light through.
226
+ */
227
+ transmissionColor?: readonly [number, number, number] | null;
228
+ /**
229
+ * Up to five layers blended by a mask, each at its own repeat: `albedo`, `normal` and `orm` arrays
230
+ * whose layers are the material's, and a mask laying each over the ones before it or summing them,
231
+ * from a map, the ORM array or the vertex colour; under a `projection` the layers are placed by the
232
+ * world. Absent, the material is one layer, as every material was. **A lit switch**, compiled in the
233
+ * first time a material asks. A mask that is a map is read where `modelMap` goes, so such a
234
+ * material carries one or the other; a lightmapped one keeps its mask in its ORM array or its
235
+ * vertices. See `surfaceLayers.ts` for the rest and what it gives up.
236
+ */
237
+ layers?: SurfaceLayers<Texture> | null;
199
238
  }
200
239
  export interface SurfaceTextureOptions {
201
240
  /**
@@ -0,0 +1,21 @@
1
+ /**
2
+ * A thin surface's light from behind as the lit stage reads it: `SurfaceMaterial.diffuseTransmission`
3
+ * and `transmissionColor` into the four lanes of the glass tint an opaque material leaves unused.
4
+ *
5
+ * **The colour rides lanes glass owns**, because the lit stage's uniform rows are spent
6
+ * (`flat/preamble.ts`): a pane writes its own tint there for its draw, so whatever binds a pane
7
+ * puts this back after it, or the next draw of the material lets light through in the pane's colour.
8
+ * **−1 in the colour lanes means "the albedo"**, which is what a material naming no colour has
9
+ * always let through, so a scene that names none draws to the bit as it did.
10
+ */
11
+ /** What a material lets through from behind and in which colour, as the glass tint carries it. */
12
+ export interface Transmission {
13
+ readonly diffuseTransmission?: number;
14
+ readonly transmissionColor?: readonly [number, number, number] | null;
15
+ }
16
+ /**
17
+ * `material` into `out`: the colour, or −1 three times where it names none, then the amount held
18
+ * to 0..1. A colour component below zero is held at zero, so a mistyped colour lets no light
19
+ * through rather than reading as "the albedo".
20
+ */
21
+ export declare function resolveTransmission(material: Transmission | null, out: Float32Array): void;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * A thin surface's light from behind as the lit stage reads it: `SurfaceMaterial.diffuseTransmission`
3
+ * and `transmissionColor` into the four lanes of the glass tint an opaque material leaves unused.
4
+ *
5
+ * **The colour rides lanes glass owns**, because the lit stage's uniform rows are spent
6
+ * (`flat/preamble.ts`): a pane writes its own tint there for its draw, so whatever binds a pane
7
+ * puts this back after it, or the next draw of the material lets light through in the pane's colour.
8
+ * **−1 in the colour lanes means "the albedo"**, which is what a material naming no colour has
9
+ * always let through, so a scene that names none draws to the bit as it did.
10
+ */
11
+ /**
12
+ * `material` into `out`: the colour, or −1 three times where it names none, then the amount held
13
+ * to 0..1. A colour component below zero is held at zero, so a mistyped colour lets no light
14
+ * through rather than reading as "the albedo".
15
+ */
16
+ export function resolveTransmission(material, out) {
17
+ const colour = material?.transmissionColor ?? null;
18
+ for (let i = 0; i < 3; i++)
19
+ out[i] = colour === null ? -1 : Math.max(colour[i] ?? 0, 0) || 0;
20
+ out[3] = Math.min(Math.max(material?.diffuseTransmission ?? 0, 0), 1) || 0;
21
+ }