@flighthq/scene3d-wgpu 0.2.1-next.840.857425c

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 (263) hide show
  1. package/dist/anisotropyPbrWgpuMeshMaterialRenderer.d.ts +4 -0
  2. package/dist/anisotropyPbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
  3. package/dist/anisotropyPbrWgpuMeshMaterialRenderer.js +52 -0
  4. package/dist/anisotropyPbrWgpuMeshMaterialRenderer.js.map +1 -0
  5. package/dist/blinnPhongWgpuMeshMaterialRenderer.d.ts +4 -0
  6. package/dist/blinnPhongWgpuMeshMaterialRenderer.d.ts.map +1 -0
  7. package/dist/blinnPhongWgpuMeshMaterialRenderer.js +66 -0
  8. package/dist/blinnPhongWgpuMeshMaterialRenderer.js.map +1 -0
  9. package/dist/clearcoatPbrWgpuMeshMaterialRenderer.d.ts +4 -0
  10. package/dist/clearcoatPbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
  11. package/dist/clearcoatPbrWgpuMeshMaterialRenderer.js +52 -0
  12. package/dist/clearcoatPbrWgpuMeshMaterialRenderer.js.map +1 -0
  13. package/dist/customShaderWgpuMeshMaterialRenderer.d.ts +7 -0
  14. package/dist/customShaderWgpuMeshMaterialRenderer.d.ts.map +1 -0
  15. package/dist/customShaderWgpuMeshMaterialRenderer.js +225 -0
  16. package/dist/customShaderWgpuMeshMaterialRenderer.js.map +1 -0
  17. package/dist/depthWgpuMeshMaterialRenderer.d.ts +4 -0
  18. package/dist/depthWgpuMeshMaterialRenderer.d.ts.map +1 -0
  19. package/dist/depthWgpuMeshMaterialRenderer.js +42 -0
  20. package/dist/depthWgpuMeshMaterialRenderer.js.map +1 -0
  21. package/dist/drawWgpuScene3D.d.ts +4 -0
  22. package/dist/drawWgpuScene3D.d.ts.map +1 -0
  23. package/dist/drawWgpuScene3D.js +171 -0
  24. package/dist/drawWgpuScene3D.js.map +1 -0
  25. package/dist/emissiveWgpuMeshMaterialRenderer.d.ts +4 -0
  26. package/dist/emissiveWgpuMeshMaterialRenderer.d.ts.map +1 -0
  27. package/dist/emissiveWgpuMeshMaterialRenderer.js +51 -0
  28. package/dist/emissiveWgpuMeshMaterialRenderer.js.map +1 -0
  29. package/dist/enableWgpuScene3DCustomShaderGuards.d.ts +5 -0
  30. package/dist/enableWgpuScene3DCustomShaderGuards.d.ts.map +1 -0
  31. package/dist/enableWgpuScene3DCustomShaderGuards.js +56 -0
  32. package/dist/enableWgpuScene3DCustomShaderGuards.js.map +1 -0
  33. package/dist/enableWgpuScene3DForwardLightSelectionGuards.d.ts +4 -0
  34. package/dist/enableWgpuScene3DForwardLightSelectionGuards.d.ts.map +1 -0
  35. package/dist/enableWgpuScene3DForwardLightSelectionGuards.js +17 -0
  36. package/dist/enableWgpuScene3DForwardLightSelectionGuards.js.map +1 -0
  37. package/dist/explainWgpuScene3DForwardLightSelection.d.ts +3 -0
  38. package/dist/explainWgpuScene3DForwardLightSelection.d.ts.map +1 -0
  39. package/dist/explainWgpuScene3DForwardLightSelection.js +18 -0
  40. package/dist/explainWgpuScene3DForwardLightSelection.js.map +1 -0
  41. package/dist/index.d.ts +51 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +51 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/iridescencePbrWgpuMeshMaterialRenderer.d.ts +4 -0
  46. package/dist/iridescencePbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
  47. package/dist/iridescencePbrWgpuMeshMaterialRenderer.js +60 -0
  48. package/dist/iridescencePbrWgpuMeshMaterialRenderer.js.map +1 -0
  49. package/dist/lambertWgpuMeshMaterialRenderer.d.ts +4 -0
  50. package/dist/lambertWgpuMeshMaterialRenderer.d.ts.map +1 -0
  51. package/dist/lambertWgpuMeshMaterialRenderer.js +61 -0
  52. package/dist/lambertWgpuMeshMaterialRenderer.js.map +1 -0
  53. package/dist/matcapWgpuMeshMaterialRenderer.d.ts +4 -0
  54. package/dist/matcapWgpuMeshMaterialRenderer.d.ts.map +1 -0
  55. package/dist/matcapWgpuMeshMaterialRenderer.js +54 -0
  56. package/dist/matcapWgpuMeshMaterialRenderer.js.map +1 -0
  57. package/dist/normalWgpuMeshMaterialRenderer.d.ts +4 -0
  58. package/dist/normalWgpuMeshMaterialRenderer.d.ts.map +1 -0
  59. package/dist/normalWgpuMeshMaterialRenderer.js +47 -0
  60. package/dist/normalWgpuMeshMaterialRenderer.js.map +1 -0
  61. package/dist/phongWgpuMeshMaterialRenderer.d.ts +4 -0
  62. package/dist/phongWgpuMeshMaterialRenderer.d.ts.map +1 -0
  63. package/dist/phongWgpuMeshMaterialRenderer.js +63 -0
  64. package/dist/phongWgpuMeshMaterialRenderer.js.map +1 -0
  65. package/dist/prepareWgpuScene3DForwardLights.d.ts +3 -0
  66. package/dist/prepareWgpuScene3DForwardLights.d.ts.map +1 -0
  67. package/dist/prepareWgpuScene3DForwardLights.js +110 -0
  68. package/dist/prepareWgpuScene3DForwardLights.js.map +1 -0
  69. package/dist/registerStandardPbrWgpuMaterial.d.ts +3 -0
  70. package/dist/registerStandardPbrWgpuMaterial.d.ts.map +1 -0
  71. package/dist/registerStandardPbrWgpuMaterial.js +11 -0
  72. package/dist/registerStandardPbrWgpuMaterial.js.map +1 -0
  73. package/dist/shadedWgpuMeshMaterialRenderer.d.ts +11 -0
  74. package/dist/shadedWgpuMeshMaterialRenderer.d.ts.map +1 -0
  75. package/dist/shadedWgpuMeshMaterialRenderer.js +47 -0
  76. package/dist/shadedWgpuMeshMaterialRenderer.js.map +1 -0
  77. package/dist/sheenPbrWgpuMeshMaterialRenderer.d.ts +4 -0
  78. package/dist/sheenPbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
  79. package/dist/sheenPbrWgpuMeshMaterialRenderer.js +58 -0
  80. package/dist/sheenPbrWgpuMeshMaterialRenderer.js.map +1 -0
  81. package/dist/specularGlossinessPbrWgpuMeshMaterialRenderer.d.ts +4 -0
  82. package/dist/specularGlossinessPbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
  83. package/dist/specularGlossinessPbrWgpuMeshMaterialRenderer.js +126 -0
  84. package/dist/specularGlossinessPbrWgpuMeshMaterialRenderer.js.map +1 -0
  85. package/dist/specularPbrWgpuMeshMaterialRenderer.d.ts +4 -0
  86. package/dist/specularPbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
  87. package/dist/specularPbrWgpuMeshMaterialRenderer.js +65 -0
  88. package/dist/specularPbrWgpuMeshMaterialRenderer.js.map +1 -0
  89. package/dist/standardPbrWgpuMeshMaterialRenderer.d.ts +9 -0
  90. package/dist/standardPbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
  91. package/dist/standardPbrWgpuMeshMaterialRenderer.js +174 -0
  92. package/dist/standardPbrWgpuMeshMaterialRenderer.js.map +1 -0
  93. package/dist/subsurfacePbrWgpuMeshMaterialRenderer.d.ts +4 -0
  94. package/dist/subsurfacePbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
  95. package/dist/subsurfacePbrWgpuMeshMaterialRenderer.js +68 -0
  96. package/dist/subsurfacePbrWgpuMeshMaterialRenderer.js.map +1 -0
  97. package/dist/toonWgpuMeshMaterialRenderer.d.ts +4 -0
  98. package/dist/toonWgpuMeshMaterialRenderer.d.ts.map +1 -0
  99. package/dist/toonWgpuMeshMaterialRenderer.js +71 -0
  100. package/dist/toonWgpuMeshMaterialRenderer.js.map +1 -0
  101. package/dist/transmissionVolumePbrWgpuMeshMaterialRenderer.d.ts +4 -0
  102. package/dist/transmissionVolumePbrWgpuMeshMaterialRenderer.d.ts.map +1 -0
  103. package/dist/transmissionVolumePbrWgpuMeshMaterialRenderer.js +74 -0
  104. package/dist/transmissionVolumePbrWgpuMeshMaterialRenderer.js.map +1 -0
  105. package/dist/unlitWgpuMeshMaterialRenderer.d.ts +4 -0
  106. package/dist/unlitWgpuMeshMaterialRenderer.d.ts.map +1 -0
  107. package/dist/unlitWgpuMeshMaterialRenderer.js +57 -0
  108. package/dist/unlitWgpuMeshMaterialRenderer.js.map +1 -0
  109. package/dist/vertexColorWgpuMeshMaterialRenderer.d.ts +4 -0
  110. package/dist/vertexColorWgpuMeshMaterialRenderer.d.ts.map +1 -0
  111. package/dist/vertexColorWgpuMeshMaterialRenderer.js +55 -0
  112. package/dist/vertexColorWgpuMeshMaterialRenderer.js.map +1 -0
  113. package/dist/wgpuClassicPrelude.d.ts +8 -0
  114. package/dist/wgpuClassicPrelude.d.ts.map +1 -0
  115. package/dist/wgpuClassicPrelude.js +319 -0
  116. package/dist/wgpuClassicPrelude.js.map +1 -0
  117. package/dist/wgpuCustomMaterialAbi.d.ts +3 -0
  118. package/dist/wgpuCustomMaterialAbi.d.ts.map +1 -0
  119. package/dist/wgpuCustomMaterialAbi.js +5 -0
  120. package/dist/wgpuCustomMaterialAbi.js.map +1 -0
  121. package/dist/wgpuDebugPrelude.d.ts +7 -0
  122. package/dist/wgpuDebugPrelude.d.ts.map +1 -0
  123. package/dist/wgpuDebugPrelude.js +133 -0
  124. package/dist/wgpuDebugPrelude.js.map +1 -0
  125. package/dist/wgpuEnvironmentCube.d.ts +4 -0
  126. package/dist/wgpuEnvironmentCube.d.ts.map +1 -0
  127. package/dist/wgpuEnvironmentCube.js +66 -0
  128. package/dist/wgpuEnvironmentCube.js.map +1 -0
  129. package/dist/wgpuEnvironmentIblBake.d.ts +4 -0
  130. package/dist/wgpuEnvironmentIblBake.d.ts.map +1 -0
  131. package/dist/wgpuEnvironmentIblBake.js +458 -0
  132. package/dist/wgpuEnvironmentIblBake.js.map +1 -0
  133. package/dist/wgpuEnvironmentSkybox.d.ts +3 -0
  134. package/dist/wgpuEnvironmentSkybox.d.ts.map +1 -0
  135. package/dist/wgpuEnvironmentSkybox.js +159 -0
  136. package/dist/wgpuEnvironmentSkybox.js.map +1 -0
  137. package/dist/wgpuMatcapPrelude.d.ts +7 -0
  138. package/dist/wgpuMatcapPrelude.d.ts.map +1 -0
  139. package/dist/wgpuMatcapPrelude.js +134 -0
  140. package/dist/wgpuMatcapPrelude.js.map +1 -0
  141. package/dist/wgpuMeshMaterialRegistry.d.ts +5 -0
  142. package/dist/wgpuMeshMaterialRegistry.d.ts.map +1 -0
  143. package/dist/wgpuMeshMaterialRegistry.js +29 -0
  144. package/dist/wgpuMeshMaterialRegistry.js.map +1 -0
  145. package/dist/wgpuMeshPipeline.d.ts +43 -0
  146. package/dist/wgpuMeshPipeline.d.ts.map +1 -0
  147. package/dist/wgpuMeshPipeline.js +1007 -0
  148. package/dist/wgpuMeshPipeline.js.map +1 -0
  149. package/dist/wgpuMeshUpload.d.ts +3 -0
  150. package/dist/wgpuMeshUpload.d.ts.map +1 -0
  151. package/dist/wgpuMeshUpload.js +58 -0
  152. package/dist/wgpuMeshUpload.js.map +1 -0
  153. package/dist/wgpuParticleEmitter3D.d.ts +4 -0
  154. package/dist/wgpuParticleEmitter3D.d.ts.map +1 -0
  155. package/dist/wgpuParticleEmitter3D.js +403 -0
  156. package/dist/wgpuParticleEmitter3D.js.map +1 -0
  157. package/dist/wgpuPbrPipelineCache.d.ts +4 -0
  158. package/dist/wgpuPbrPipelineCache.d.ts.map +1 -0
  159. package/dist/wgpuPbrPipelineCache.js +52 -0
  160. package/dist/wgpuPbrPipelineCache.js.map +1 -0
  161. package/dist/wgpuPbrPrelude.d.ts +7 -0
  162. package/dist/wgpuPbrPrelude.d.ts.map +1 -0
  163. package/dist/wgpuPbrPrelude.js +551 -0
  164. package/dist/wgpuPbrPrelude.js.map +1 -0
  165. package/dist/wgpuScene3DRuntime.d.ts +5 -0
  166. package/dist/wgpuScene3DRuntime.d.ts.map +1 -0
  167. package/dist/wgpuScene3DRuntime.js +81 -0
  168. package/dist/wgpuScene3DRuntime.js.map +1 -0
  169. package/dist/wgpuScene3DTestHelper.d.ts +14 -0
  170. package/dist/wgpuScene3DTestHelper.d.ts.map +1 -0
  171. package/dist/wgpuScene3DTestHelper.js +121 -0
  172. package/dist/wgpuScene3DTestHelper.js.map +1 -0
  173. package/dist/wgpuScene3DTime.d.ts +4 -0
  174. package/dist/wgpuScene3DTime.d.ts.map +1 -0
  175. package/dist/wgpuScene3DTime.js +11 -0
  176. package/dist/wgpuScene3DTime.js.map +1 -0
  177. package/dist/wgpuShadedModifierSnippet.d.ts +4 -0
  178. package/dist/wgpuShadedModifierSnippet.d.ts.map +1 -0
  179. package/dist/wgpuShadedModifierSnippet.js +18 -0
  180. package/dist/wgpuShadedModifierSnippet.js.map +1 -0
  181. package/dist/wgpuShadedPrelude.d.ts +16 -0
  182. package/dist/wgpuShadedPrelude.d.ts.map +1 -0
  183. package/dist/wgpuShadedPrelude.js +625 -0
  184. package/dist/wgpuShadedPrelude.js.map +1 -0
  185. package/dist/wgpuShadowMap.d.ts +4 -0
  186. package/dist/wgpuShadowMap.d.ts.map +1 -0
  187. package/dist/wgpuShadowMap.js +155 -0
  188. package/dist/wgpuShadowMap.js.map +1 -0
  189. package/dist/wgpuSkinPalette.d.ts +7 -0
  190. package/dist/wgpuSkinPalette.d.ts.map +1 -0
  191. package/dist/wgpuSkinPalette.js +173 -0
  192. package/dist/wgpuSkinPalette.js.map +1 -0
  193. package/dist/wgpuToonPrelude.d.ts +8 -0
  194. package/dist/wgpuToonPrelude.d.ts.map +1 -0
  195. package/dist/wgpuToonPrelude.js +192 -0
  196. package/dist/wgpuToonPrelude.js.map +1 -0
  197. package/dist/wgpuUnlitPrelude.d.ts +9 -0
  198. package/dist/wgpuUnlitPrelude.d.ts.map +1 -0
  199. package/dist/wgpuUnlitPrelude.js +142 -0
  200. package/dist/wgpuUnlitPrelude.js.map +1 -0
  201. package/dist/wgpuWireframePrelude.d.ts +6 -0
  202. package/dist/wgpuWireframePrelude.d.ts.map +1 -0
  203. package/dist/wgpuWireframePrelude.js +77 -0
  204. package/dist/wgpuWireframePrelude.js.map +1 -0
  205. package/dist/wgpuWireframeUpload.d.ts +3 -0
  206. package/dist/wgpuWireframeUpload.d.ts.map +1 -0
  207. package/dist/wgpuWireframeUpload.js +68 -0
  208. package/dist/wgpuWireframeUpload.js.map +1 -0
  209. package/dist/wireframeWgpuMeshMaterialRenderer.d.ts +4 -0
  210. package/dist/wireframeWgpuMeshMaterialRenderer.d.ts.map +1 -0
  211. package/dist/wireframeWgpuMeshMaterialRenderer.js +69 -0
  212. package/dist/wireframeWgpuMeshMaterialRenderer.js.map +1 -0
  213. package/package.json +57 -0
  214. package/src/anisotropyPbrWgpuMeshMaterialRenderer.test.ts +90 -0
  215. package/src/blinnPhongWgpuMeshMaterialRenderer.test.ts +82 -0
  216. package/src/clearcoatPbrWgpuMeshMaterialRenderer.test.ts +90 -0
  217. package/src/customShaderWgpuMeshMaterialRenderer.test.ts +207 -0
  218. package/src/depthWgpuMeshMaterialRenderer.test.ts +69 -0
  219. package/src/drawWgpuScene3D.test.ts +247 -0
  220. package/src/emissiveWgpuMeshMaterialRenderer.test.ts +64 -0
  221. package/src/enableWgpuScene3DCustomShaderGuards.test.ts +73 -0
  222. package/src/enableWgpuScene3DForwardLightSelectionGuards.test.ts +39 -0
  223. package/src/explainWgpuScene3DForwardLightSelection.test.ts +15 -0
  224. package/src/iridescencePbrWgpuMeshMaterialRenderer.test.ts +90 -0
  225. package/src/lambertWgpuMeshMaterialRenderer.test.ts +77 -0
  226. package/src/matcapWgpuMeshMaterialRenderer.test.ts +69 -0
  227. package/src/normalWgpuMeshMaterialRenderer.test.ts +69 -0
  228. package/src/phongWgpuMeshMaterialRenderer.test.ts +79 -0
  229. package/src/prepareWgpuScene3DForwardLights.test.ts +84 -0
  230. package/src/registerStandardPbrWgpuMaterial.test.ts +15 -0
  231. package/src/shadedWgpuMeshMaterialRenderer.test.ts +63 -0
  232. package/src/sheenPbrWgpuMeshMaterialRenderer.test.ts +87 -0
  233. package/src/specularGlossinessPbrWgpuMeshMaterialRenderer.test.ts +100 -0
  234. package/src/specularPbrWgpuMeshMaterialRenderer.test.ts +90 -0
  235. package/src/standardPbrWgpuMeshMaterialRenderer.test.ts +172 -0
  236. package/src/subsurfacePbrWgpuMeshMaterialRenderer.test.ts +90 -0
  237. package/src/toonWgpuMeshMaterialRenderer.test.ts +89 -0
  238. package/src/transmissionVolumePbrWgpuMeshMaterialRenderer.test.ts +97 -0
  239. package/src/unlitWgpuMeshMaterialRenderer.test.ts +94 -0
  240. package/src/vertexColorWgpuMeshMaterialRenderer.test.ts +62 -0
  241. package/src/wgpuClassicPrelude.test.ts +176 -0
  242. package/src/wgpuDebugPrelude.test.ts +79 -0
  243. package/src/wgpuEnvironmentCube.test.ts +86 -0
  244. package/src/wgpuEnvironmentIblBake.test.ts +79 -0
  245. package/src/wgpuEnvironmentSkybox.test.ts +56 -0
  246. package/src/wgpuMatcapPrelude.test.ts +70 -0
  247. package/src/wgpuMeshMaterialRegistry.test.ts +55 -0
  248. package/src/wgpuMeshPipeline.test.ts +844 -0
  249. package/src/wgpuMeshUpload.test.ts +177 -0
  250. package/src/wgpuParticleEmitter3D.test.ts +200 -0
  251. package/src/wgpuPbrPipelineCache.test.ts +67 -0
  252. package/src/wgpuPbrPrelude.test.ts +162 -0
  253. package/src/wgpuScene3DRuntime.test.ts +35 -0
  254. package/src/wgpuScene3DTime.test.ts +20 -0
  255. package/src/wgpuShadedModifierSnippet.test.ts +33 -0
  256. package/src/wgpuShadedPrelude.test.ts +394 -0
  257. package/src/wgpuShadowMap.test.ts +169 -0
  258. package/src/wgpuSkinPalette.test.ts +88 -0
  259. package/src/wgpuToonPrelude.test.ts +93 -0
  260. package/src/wgpuUnlitPrelude.test.ts +102 -0
  261. package/src/wgpuWireframePrelude.test.ts +54 -0
  262. package/src/wgpuWireframeUpload.test.ts +36 -0
  263. package/src/wireframeWgpuMeshMaterialRenderer.test.ts +69 -0
@@ -0,0 +1,247 @@
1
+ import { createCamera3D, setCamera3DViewMatrix4FromLookAt } from '@flighthq/camera';
2
+ import { createVector3 } from '@flighthq/geometry';
3
+ import { createAmbientLight, createDirectionalLight } from '@flighthq/lighting';
4
+ import { createStandardPbrMaterial } from '@flighthq/materials';
5
+ import { CANONICAL_SKINNED_MESH_GEOMETRY_LAYOUT, createBoxMeshGeometry, createMeshGeometry } from '@flighthq/mesh';
6
+ import { addNodeChild, invalidateNodeLocalTransform } from '@flighthq/node';
7
+ import { createParticleEmitter3D, reserveParticleEmitter3D } from '@flighthq/particleemitter';
8
+ import { createMesh, createNode3D, Node3DKind } from '@flighthq/scene3d';
9
+ import type { Camera3D, ParticleEmitter3D, Scene3DLightsLike, Skeleton3D } from '@flighthq/types';
10
+
11
+ import { drawWgpuScene3D, isWgpuMeshGpuSkinned } from './drawWgpuScene3D';
12
+ import { registerStandardPbrWgpuMaterial } from './registerStandardPbrWgpuMaterial';
13
+ import { getWgpuScene3DRuntime } from './wgpuScene3DRuntime';
14
+ import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
15
+ import { registerWgpuGpuSkinning } from './wgpuSkinPalette';
16
+
17
+ function makeCamera(): Camera3D {
18
+ const camera = createCamera3D({
19
+ far: 100,
20
+ near: 0.1,
21
+ projection: { aspect: 1, fovY: Math.PI / 3, kind: 'perspective' },
22
+ });
23
+ setCamera3DViewMatrix4FromLookAt(camera, { x: 0, y: 0, z: 5 }, { x: 0, y: 0, z: 0 }, { x: 0, y: 1, z: 0 });
24
+ return camera;
25
+ }
26
+
27
+ const LIGHTS: Scene3DLightsLike = {
28
+ ambient: createAmbientLight({ color: 0xffffffff, intensity: 0.2 }),
29
+ directional: createDirectionalLight({ color: 0xffffffff, direction: createVector3(0, -1, -1), intensity: 1 }),
30
+ };
31
+
32
+ describe('drawWgpuScene3D', () => {
33
+ it('draws each visible mesh subset with its registered material renderer', () => {
34
+ const { fake, state } = makeWgpuScene3DState();
35
+ registerStandardPbrWgpuMaterial(state);
36
+
37
+ const scene = createNode3D(Node3DKind);
38
+ const mesh = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
39
+ addNodeChild(scene, mesh);
40
+
41
+ drawWgpuScene3D(state, scene, makeCamera(), LIGHTS);
42
+
43
+ expect(fake.calls.some((c) => c.name === 'setPipeline')).toBe(true);
44
+ expect(fake.calls.some((c) => c.name === 'drawIndexed')).toBe(true);
45
+ });
46
+
47
+ it('skips a subset whose material has no registered renderer (no fallback)', () => {
48
+ const { fake, state } = makeWgpuScene3DState();
49
+ // No registerStandardPbrWgpuMaterial: nothing resolves.
50
+ const scene = createNode3D(Node3DKind);
51
+ const mesh = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
52
+ addNodeChild(scene, mesh);
53
+
54
+ drawWgpuScene3D(state, scene, makeCamera(), LIGHTS);
55
+ expect(fake.calls.some((c) => c.name === 'drawIndexed')).toBe(false);
56
+ });
57
+
58
+ it('binds once for a run of subsets sharing a material', () => {
59
+ const { fake, state } = makeWgpuScene3DState();
60
+ registerStandardPbrWgpuMaterial(state);
61
+
62
+ const scene = createNode3D(Node3DKind);
63
+ const material = createStandardPbrMaterial();
64
+ const meshA = createMesh(createBoxMeshGeometry(), [material]);
65
+ const meshB = createMesh(createBoxMeshGeometry(), [material]);
66
+ addNodeChild(scene, meshA);
67
+ addNodeChild(scene, meshB);
68
+
69
+ drawWgpuScene3D(state, scene, makeCamera(), LIGHTS);
70
+
71
+ // Same material across both meshes: pipeline bound once, drawn twice.
72
+ expect(fake.calls.filter((c) => c.name === 'setPipeline').length).toBe(1);
73
+ expect(fake.calls.filter((c) => c.name === 'drawIndexed').length).toBe(2);
74
+ });
75
+
76
+ it('does not draw a disabled mesh', () => {
77
+ const { fake, state } = makeWgpuScene3DState();
78
+ registerStandardPbrWgpuMaterial(state);
79
+
80
+ const scene = createNode3D(Node3DKind);
81
+ const mesh = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
82
+ mesh.enabled = false;
83
+ addNodeChild(scene, mesh);
84
+
85
+ drawWgpuScene3D(state, scene, makeCamera(), LIGHTS);
86
+ expect(fake.calls.some((c) => c.name === 'drawIndexed')).toBe(false);
87
+ });
88
+
89
+ it('partitions opaque and blended subsets into distinct pipeline runs', () => {
90
+ const { state } = makeWgpuScene3DState();
91
+ registerStandardPbrWgpuMaterial(state);
92
+ const scene = createNode3D(Node3DKind);
93
+ const blendedMaterial = createStandardPbrMaterial();
94
+ blendedMaterial.alphaMode = 'blend';
95
+ const blended = createMesh(createBoxMeshGeometry(), [blendedMaterial]);
96
+ const opaque = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
97
+ addNodeChild(scene, blended);
98
+ addNodeChild(scene, opaque);
99
+
100
+ drawWgpuScene3D(state, scene, makeCamera(), LIGHTS);
101
+
102
+ const runtime = getWgpuScene3DRuntime(state);
103
+ expect(runtime.opaqueDrawList.map((entry) => entry.mesh)).toEqual([opaque]);
104
+ expect(runtime.blendedDrawList.map((entry) => entry.mesh)).toEqual([blended]);
105
+ expect(Array.from(runtime.pipelineCache.keys()).some((key) => key.endsWith('|opaque|rigid'))).toBe(true);
106
+ expect(Array.from(runtime.pipelineCache.keys()).some((key) => key.endsWith('|blend|rigid'))).toBe(true);
107
+ });
108
+
109
+ it('routes resolved node alpha through the blended pass and draw proxy', () => {
110
+ const { state } = makeWgpuScene3DState();
111
+ registerStandardPbrWgpuMaterial(state);
112
+ const scene = createNode3D(Node3DKind);
113
+ const mesh = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
114
+ mesh.alpha = 0.5;
115
+ addNodeChild(scene, mesh);
116
+
117
+ drawWgpuScene3D(state, scene, makeCamera(), LIGHTS);
118
+
119
+ const runtime = getWgpuScene3DRuntime(state);
120
+ expect(runtime.opaqueDrawList).toHaveLength(0);
121
+ expect(runtime.blendedDrawList[0]!.alpha).toBeCloseTo(0.5);
122
+ expect(Array.from(runtime.pipelineCache.keys()).some((key) => key.endsWith('|blend|rigid'))).toBe(true);
123
+ });
124
+
125
+ it('sorts blended subsets back-to-front by projected depth', () => {
126
+ const { state } = makeWgpuScene3DState();
127
+ registerStandardPbrWgpuMaterial(state);
128
+ const scene = createNode3D(Node3DKind);
129
+ const material = createStandardPbrMaterial();
130
+ material.alphaMode = 'blend';
131
+ const far = createMesh(createBoxMeshGeometry(), [material]);
132
+ const near = createMesh(createBoxMeshGeometry(), [material]);
133
+ far.position.z = -3;
134
+ near.position.z = -1;
135
+ invalidateNodeLocalTransform(far);
136
+ invalidateNodeLocalTransform(near);
137
+ addNodeChild(scene, near);
138
+ addNodeChild(scene, far);
139
+
140
+ drawWgpuScene3D(state, scene, makeCamera(), LIGHTS);
141
+
142
+ expect(getWgpuScene3DRuntime(state).blendedDrawList.map((entry) => entry.mesh)).toEqual([far, near]);
143
+ });
144
+
145
+ it('sorts blended subsets back-to-front with an orthographic camera', () => {
146
+ const { state } = makeWgpuScene3DState();
147
+ registerStandardPbrWgpuMaterial(state);
148
+ const scene = createNode3D(Node3DKind);
149
+ const material = createStandardPbrMaterial();
150
+ material.alphaMode = 'blend';
151
+ const far = createMesh(createBoxMeshGeometry(), [material]);
152
+ const near = createMesh(createBoxMeshGeometry(), [material]);
153
+ far.position.z = -3;
154
+ near.position.z = -1;
155
+ invalidateNodeLocalTransform(far);
156
+ invalidateNodeLocalTransform(near);
157
+ addNodeChild(scene, near);
158
+ addNodeChild(scene, far);
159
+ const camera = createCamera3D({
160
+ far: 100,
161
+ near: 0.1,
162
+ projection: { halfHeight: 1, halfWidth: 1, kind: 'orthographic' },
163
+ });
164
+ setCamera3DViewMatrix4FromLookAt(camera, { x: 0, y: 0, z: 5 }, { x: 0, y: 0, z: 0 }, { x: 0, y: 1, z: 0 });
165
+ drawWgpuScene3D(state, scene, camera, LIGHTS);
166
+ expect(getWgpuScene3DRuntime(state).blendedDrawList.map((entry) => entry.mesh)).toEqual([far, near]);
167
+ });
168
+
169
+ it('reuses opaque and blended draw records across frames', () => {
170
+ const { state } = makeWgpuScene3DState();
171
+ registerStandardPbrWgpuMaterial(state);
172
+ const scene = createNode3D(Node3DKind);
173
+ const opaque = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
174
+ const material = createStandardPbrMaterial();
175
+ material.alphaMode = 'blend';
176
+ const blended = createMesh(createBoxMeshGeometry(), [material]);
177
+ addNodeChild(scene, opaque);
178
+ addNodeChild(scene, blended);
179
+ const camera = makeCamera();
180
+
181
+ drawWgpuScene3D(state, scene, camera, LIGHTS);
182
+ const runtime = getWgpuScene3DRuntime(state);
183
+ const opaqueEntry = runtime.opaqueDrawList[0];
184
+ const blendedEntry = runtime.blendedDrawList[0];
185
+ drawWgpuScene3D(state, scene, camera, LIGHTS);
186
+
187
+ expect(runtime.opaqueDrawList[0]).toBe(opaqueEntry);
188
+ expect(runtime.blendedDrawList[0]).toBe(blendedEntry);
189
+ expect(runtime.opaquePool).toHaveLength(0);
190
+ expect(runtime.blendedPool).toHaveLength(0);
191
+ });
192
+
193
+ it('draws a scene ParticleEmitter3D as a final pass without a manual emitter call', () => {
194
+ const { fake, state } = makeWgpuScene3DState();
195
+ const scene = createNode3D(Node3DKind);
196
+ addNodeChild(scene, makeParticleEmitter2D(3));
197
+ // No mesh and no manual drawWgpuScene3DParticleEmitter3Ds — drawWgpuScene3D must render the emitter itself
198
+ // (a 6-index instanced quad draw), mirroring drawGlScene3D's automatic emitter pass.
199
+ drawWgpuScene3D(state, scene, makeCamera(), LIGHTS);
200
+ const draw = fake.calls.find((c) => c.name === 'drawIndexed');
201
+ expect(draw).toBeDefined();
202
+ expect(draw!.args[0]).toBe(6);
203
+ expect(draw!.args[1]).toBe(3);
204
+ });
205
+ });
206
+
207
+ describe('isWgpuMeshGpuSkinned', () => {
208
+ it('selects GPU skinning only for a skin plus joints0 and weights0 geometry', () => {
209
+ const { state } = makeWgpuScene3DState();
210
+ registerWgpuGpuSkinning(state);
211
+ const rigid = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
212
+ expect(isWgpuMeshGpuSkinned(state, rigid)).toBe(false);
213
+
214
+ const skinned = createMesh(
215
+ createMeshGeometry({
216
+ indices: new Uint16Array([0, 0, 0]),
217
+ layout: CANONICAL_SKINNED_MESH_GEOMETRY_LAYOUT,
218
+ vertices: new Float32Array(20),
219
+ }),
220
+ [createStandardPbrMaterial()],
221
+ );
222
+ skinned.skin = { skeleton: { jointMatrices: new Float32Array(16) } as Skeleton3D };
223
+ expect(isWgpuMeshGpuSkinned(state, skinned)).toBe(true);
224
+ });
225
+ });
226
+
227
+ function makeParticleEmitter2D(count: number): ParticleEmitter3D {
228
+ const emitter = createParticleEmitter3D();
229
+ reserveParticleEmitter3D(emitter, count);
230
+ const data = emitter.data;
231
+ data.particleCount = count;
232
+ for (let i = 0; i < count; i++) {
233
+ const tt = i * 4;
234
+ data.transforms[tt] = i;
235
+ data.transforms[tt + 1] = i;
236
+ data.transforms[tt + 2] = 0;
237
+ data.transforms[tt + 3] = 1;
238
+ data.positionsZ[i] = 0;
239
+ data.alphas[i] = 1;
240
+ const ct = i * 3;
241
+ data.colors[ct] = 1;
242
+ data.colors[ct + 1] = 1;
243
+ data.colors[ct + 2] = 1;
244
+ data.ids[i] = 0;
245
+ }
246
+ return emitter;
247
+ }
@@ -0,0 +1,64 @@
1
+ import { createCamera3D } from '@flighthq/camera';
2
+ import { createMatrix3, createMatrix4 } from '@flighthq/geometry';
3
+ import { createEmissiveMaterial } from '@flighthq/materials';
4
+ import { createBoxMeshGeometry } from '@flighthq/mesh';
5
+ import type { Camera3D, Scene3DLightBlock, Scene3DRenderProxy } from '@flighthq/types';
6
+ import { EmissiveMaterialKind } from '@flighthq/types';
7
+
8
+ import { emissiveWgpuMeshMaterialRenderer, registerEmissiveWgpuMaterial } from './emissiveWgpuMeshMaterialRenderer';
9
+ import { getWgpuMeshMaterialRenderer } from './wgpuMeshMaterialRegistry';
10
+ import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
11
+
12
+ function makeCamera(): Camera3D {
13
+ return createCamera3D({ far: 100, near: 0.1, projection: { aspect: 1, fovY: Math.PI / 3, kind: 'perspective' } });
14
+ }
15
+
16
+ const NO_LIGHTS: Scene3DLightBlock = {
17
+ ambientCount: 0,
18
+ data: new Float32Array(12),
19
+ directionalCount: 0,
20
+ hemisphereCount: 0,
21
+ pointCount: 0,
22
+ spotCount: 0,
23
+ version: 1,
24
+ };
25
+
26
+ function makeProxy(): Scene3DRenderProxy {
27
+ const geometry = createBoxMeshGeometry();
28
+ return {
29
+ material: createEmissiveMaterial(),
30
+ normalMatrix: createMatrix3(),
31
+ subset: geometry.subsets[0],
32
+ worldMatrix: createMatrix4(),
33
+ };
34
+ }
35
+
36
+ describe('emissiveWgpuMeshMaterialRenderer', () => {
37
+ it('bind uploads the emissive color and issues frame + material binds', () => {
38
+ const { fake, state } = makeWgpuScene3DState();
39
+ emissiveWgpuMeshMaterialRenderer.bind(
40
+ state,
41
+ createEmissiveMaterial({ emissiveStrength: 3 }),
42
+ NO_LIGHTS,
43
+ makeCamera(),
44
+ );
45
+ expect(fake.calls.some((c) => c.name === 'setPipeline')).toBe(true);
46
+ expect(fake.calls.some((c) => c.name === 'writeBuffer')).toBe(true);
47
+ });
48
+
49
+ it('draw issues an indexed draw over the subset after bind', () => {
50
+ const { fake, state } = makeWgpuScene3DState();
51
+ const proxy = makeProxy();
52
+ emissiveWgpuMeshMaterialRenderer.bind(state, proxy.material, NO_LIGHTS, makeCamera());
53
+ emissiveWgpuMeshMaterialRenderer.draw(state, proxy, createBoxMeshGeometry());
54
+ expect(fake.calls.some((c) => c.name === 'drawIndexed')).toBe(true);
55
+ });
56
+ });
57
+
58
+ describe('registerEmissiveWgpuMaterial', () => {
59
+ it('installs the renderer for EmissiveMaterialKind', () => {
60
+ const { state } = makeWgpuScene3DState();
61
+ registerEmissiveWgpuMaterial(state);
62
+ expect(getWgpuMeshMaterialRenderer(state, EmissiveMaterialKind)).toBe(emissiveWgpuMeshMaterialRenderer);
63
+ });
64
+ });
@@ -0,0 +1,73 @@
1
+ import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log';
2
+ import { createCustomShaderMaterial } from '@flighthq/materials';
3
+
4
+ import {
5
+ areWgpuScene3DCustomShaderGuardsEnabled,
6
+ enableWgpuScene3DCustomShaderGuards,
7
+ runWgpuCustomShaderGuards,
8
+ } from './enableWgpuScene3DCustomShaderGuards';
9
+ import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
10
+
11
+ describe('areWgpuScene3DCustomShaderGuardsEnabled', () => {
12
+ it('reports installation state', () => {
13
+ const { state } = makeWgpuScene3DState();
14
+ expect(areWgpuScene3DCustomShaderGuardsEnabled(state)).toBe(false);
15
+ enableWgpuScene3DCustomShaderGuards(state);
16
+ expect(areWgpuScene3DCustomShaderGuardsEnabled(state)).toBe(true);
17
+ });
18
+ });
19
+
20
+ describe('enableWgpuScene3DCustomShaderGuards', () => {
21
+ it('is idempotent', () => {
22
+ const { state } = makeWgpuScene3DState();
23
+ enableWgpuScene3DCustomShaderGuards(state);
24
+ enableWgpuScene3DCustomShaderGuards(state);
25
+ expect(areWgpuScene3DCustomShaderGuardsEnabled(state)).toBe(true);
26
+ });
27
+ });
28
+
29
+ describe('runWgpuCustomShaderGuards', () => {
30
+ it('warns on bad vector lengths, capacity overflow, and missing reserved bindings', () => {
31
+ const { state } = makeWgpuScene3DState();
32
+ enableWgpuScene3DCustomShaderGuards(state);
33
+ const uniforms: Record<string, number[]> = {};
34
+ for (let i = 0; i < 33; i++) uniforms[`value${i}`] = [i];
35
+ uniforms['invalid'] = [1, 2, 3, 4, 5];
36
+ const material = createCustomShaderMaterial({
37
+ shaderKey: 'guard-contract-test',
38
+ textures: { a: {} as never },
39
+ uniforms,
40
+ });
41
+ const sink = createMemoryLogSink(16);
42
+ addLogSink(sink.sink);
43
+ try {
44
+ runWgpuCustomShaderGuards(
45
+ state,
46
+ material.shaderKey,
47
+ '@group(0) @binding(0) var<uniform> frame: vec4f;',
48
+ material,
49
+ );
50
+ const messages = getMemoryLogSinkEntries(sink).map((entry) =>
51
+ String((entry.data as Record<string, unknown>).message),
52
+ );
53
+ expect(messages.some((message) => message.includes('1–4 components'))).toBe(true);
54
+ expect(messages.some((message) => message.includes('32-vec4'))).toBe(true);
55
+ expect(messages.some((message) => message.includes('@group(1) @binding(0)'))).toBe(true);
56
+ expect(messages.some((message) => message.includes('@group(3) @binding(1)'))).toBe(true);
57
+ } finally {
58
+ removeLogSink(sink.sink);
59
+ }
60
+ });
61
+
62
+ it('does nothing until enabled', () => {
63
+ const { state } = makeWgpuScene3DState();
64
+ const sink = createMemoryLogSink(2);
65
+ addLogSink(sink.sink);
66
+ try {
67
+ runWgpuCustomShaderGuards(state, 'disabled-guard-test', '', createCustomShaderMaterial());
68
+ expect(getMemoryLogSinkEntries(sink)).toHaveLength(0);
69
+ } finally {
70
+ removeLogSink(sink.sink);
71
+ }
72
+ });
73
+ });
@@ -0,0 +1,39 @@
1
+ import { createPointLight } from '@flighthq/lighting';
2
+ import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log';
3
+
4
+ import {
5
+ areWgpuScene3DForwardLightSelectionGuardsEnabled,
6
+ enableWgpuScene3DForwardLightSelectionGuards,
7
+ } from './enableWgpuScene3DForwardLightSelectionGuards';
8
+ import { getWgpuScene3DRuntime } from './wgpuScene3DRuntime';
9
+ import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
10
+
11
+ describe('areWgpuScene3DForwardLightSelectionGuardsEnabled', () => {
12
+ it('reports false before installation and true afterward', () => {
13
+ const { state } = makeWgpuScene3DState();
14
+ expect(areWgpuScene3DForwardLightSelectionGuardsEnabled(state)).toBe(false);
15
+ enableWgpuScene3DForwardLightSelectionGuards(state);
16
+ expect(areWgpuScene3DForwardLightSelectionGuardsEnabled(state)).toBe(true);
17
+ });
18
+ });
19
+
20
+ describe('enableWgpuScene3DForwardLightSelectionGuards', () => {
21
+ it('installs the opt-in warning with actionable selection guidance', () => {
22
+ const { state } = makeWgpuScene3DState();
23
+ enableWgpuScene3DForwardLightSelectionGuards(state);
24
+ const sink = createMemoryLogSink(4);
25
+ addLogSink(sink.sink);
26
+ try {
27
+ getWgpuScene3DRuntime(state).forwardLightSelectionGuard!({
28
+ ambient: null,
29
+ directional: null,
30
+ point: Array.from({ length: 5 }, () => createPointLight()),
31
+ });
32
+ const entries = getMemoryLogSinkEntries(sink);
33
+ expect(entries).toHaveLength(1);
34
+ expect(String((entries[0].data as Record<string, unknown>).message)).toContain('prepareWgpuScene3DForwardLights');
35
+ } finally {
36
+ removeLogSink(sink.sink);
37
+ }
38
+ });
39
+ });
@@ -0,0 +1,15 @@
1
+ import { createPointLight } from '@flighthq/lighting';
2
+
3
+ import { explainWgpuScene3DForwardLightSelection } from './explainWgpuScene3DForwardLightSelection';
4
+
5
+ describe('explainWgpuScene3DForwardLightSelection', () => {
6
+ it('distinguishes within-budget, required, and prepared selection', () => {
7
+ const within = { ambient: null, directional: null, point: [createPointLight()] };
8
+ expect(explainWgpuScene3DForwardLightSelection(within).reason).toBe('within-budget');
9
+ const excess = { ambient: null, directional: null, point: Array.from({ length: 5 }, () => createPointLight()) };
10
+ expect(explainWgpuScene3DForwardLightSelection(excess).reason).toBe('selection-required');
11
+ expect(explainWgpuScene3DForwardLightSelection(excess, { meshCount: 0, meshLightBlocks: [] }).reason).toBe(
12
+ 'selection-prepared',
13
+ );
14
+ });
15
+ });
@@ -0,0 +1,90 @@
1
+ import { createCamera3D } from '@flighthq/camera';
2
+ import { createMatrix3, createMatrix4 } from '@flighthq/geometry';
3
+ import { createIridescencePbrMaterial } from '@flighthq/materials';
4
+ import { createBoxMeshGeometry } from '@flighthq/mesh';
5
+ import type { Camera3D, Matrix3, Matrix4, Scene3DLightBlock, Scene3DRenderProxy } from '@flighthq/types';
6
+ import { IridescencePbrMaterialKind } from '@flighthq/types';
7
+
8
+ import {
9
+ iridescencePbrWgpuMeshMaterialRenderer,
10
+ registerIridescencePbrWgpuMaterial,
11
+ } from './iridescencePbrWgpuMeshMaterialRenderer';
12
+ import { getWgpuMeshMaterialRenderer } from './wgpuMeshMaterialRegistry';
13
+ import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
14
+
15
+ function makeCamera(): Camera3D {
16
+ return createCamera3D({ far: 100, near: 0.1, projection: { aspect: 1, fovY: Math.PI / 3, kind: 'perspective' } });
17
+ }
18
+
19
+ function makeLights(): Scene3DLightBlock {
20
+ const data = new Float32Array(12);
21
+ data[1] = -1;
22
+ data[4] = 1;
23
+ data[5] = 1;
24
+ data[6] = 1;
25
+ data[8] = 0.1;
26
+ data[9] = 0.1;
27
+ data[10] = 0.1;
28
+ return { ambientCount: 1, data, directionalCount: 1, hemisphereCount: 0, pointCount: 0, spotCount: 0, version: 1 };
29
+ }
30
+
31
+ function makeProxy(): Scene3DRenderProxy {
32
+ const geometry = createBoxMeshGeometry();
33
+ return {
34
+ material: createIridescencePbrMaterial({ iridescence: 1 }),
35
+ normalMatrix: createMatrix3() as Matrix3,
36
+ subset: geometry.subsets[0],
37
+ worldMatrix: createMatrix4() as Matrix4,
38
+ };
39
+ }
40
+
41
+ describe('iridescencePbrWgpuMeshMaterialRenderer', () => {
42
+ it('bind selects a pipeline and binds frame + material groups + uploads uniforms', () => {
43
+ const { fake, state } = makeWgpuScene3DState();
44
+ iridescencePbrWgpuMeshMaterialRenderer.bind(
45
+ state,
46
+ createIridescencePbrMaterial({ iridescence: 1 }),
47
+ makeLights(),
48
+ makeCamera(),
49
+ );
50
+
51
+ expect(fake.calls.some((c) => c.name === 'createRenderPipeline')).toBe(true);
52
+ expect(fake.calls.some((c) => c.name === 'setPipeline')).toBe(true);
53
+ expect(fake.calls.filter((c) => c.name === 'setBindGroup').length).toBeGreaterThanOrEqual(2);
54
+ expect(fake.calls.some((c) => c.name === 'writeBuffer')).toBe(true);
55
+ });
56
+
57
+ it('bind compiles the IRIDESCENCE extension variant', () => {
58
+ const { fake, state } = makeWgpuScene3DState();
59
+ iridescencePbrWgpuMeshMaterialRenderer.bind(state, createIridescencePbrMaterial(), makeLights(), makeCamera());
60
+ const moduleCall = fake.calls.find((c) => c.name === 'createShaderModule');
61
+ const code = (moduleCall!.args[0] as { code: string }).code;
62
+ expect(code).toContain('const IRIDESCENCE : bool = true;');
63
+ });
64
+
65
+ it('draw uploads geometry and issues an indexed draw over the subset range', () => {
66
+ const { fake, state } = makeWgpuScene3DState();
67
+ const proxy = makeProxy();
68
+ iridescencePbrWgpuMeshMaterialRenderer.bind(state, proxy.material, makeLights(), makeCamera());
69
+ iridescencePbrWgpuMeshMaterialRenderer.draw(state, proxy, createBoxMeshGeometry());
70
+
71
+ const drawCall = fake.calls.find((c) => c.name === 'drawIndexed');
72
+ expect(drawCall).toBeDefined();
73
+ expect(drawCall!.args[0]).toBe(proxy.subset.indexCount);
74
+ expect(drawCall!.args[2]).toBe(proxy.subset.indexOffset);
75
+ });
76
+
77
+ it('draw is a no-op when bind has not selected a pipeline', () => {
78
+ const { fake, state } = makeWgpuScene3DState();
79
+ iridescencePbrWgpuMeshMaterialRenderer.draw(state, makeProxy(), createBoxMeshGeometry());
80
+ expect(fake.calls.some((c) => c.name === 'drawIndexed')).toBe(false);
81
+ });
82
+ });
83
+
84
+ describe('registerIridescencePbrWgpuMaterial', () => {
85
+ it('installs the renderer for IridescencePbrMaterialKind', () => {
86
+ const { state } = makeWgpuScene3DState();
87
+ registerIridescencePbrWgpuMaterial(state);
88
+ expect(getWgpuMeshMaterialRenderer(state, IridescencePbrMaterialKind)).toBe(iridescencePbrWgpuMeshMaterialRenderer);
89
+ });
90
+ });
@@ -0,0 +1,77 @@
1
+ import { createCamera3D } from '@flighthq/camera';
2
+ import { createMatrix3, createMatrix4 } from '@flighthq/geometry';
3
+ import { createLambertMaterial } from '@flighthq/materials';
4
+ import { createBoxMeshGeometry } from '@flighthq/mesh';
5
+ import type { Camera3D, Matrix3, Matrix4, Scene3DLightBlock, Scene3DRenderProxy } from '@flighthq/types';
6
+ import { LambertMaterialKind } from '@flighthq/types';
7
+
8
+ import { lambertWgpuMeshMaterialRenderer, registerLambertWgpuMaterial } from './lambertWgpuMeshMaterialRenderer';
9
+ import { getWgpuMeshMaterialRenderer } from './wgpuMeshMaterialRegistry';
10
+ import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
11
+
12
+ function makeCamera(): Camera3D {
13
+ return createCamera3D({ far: 100, near: 0.1, projection: { aspect: 1, fovY: Math.PI / 3, kind: 'perspective' } });
14
+ }
15
+
16
+ function makeLights(): Scene3DLightBlock {
17
+ const data = new Float32Array(12);
18
+ data[0] = 0;
19
+ data[1] = -1;
20
+ data[2] = 0;
21
+ data[4] = 1;
22
+ data[5] = 1;
23
+ data[6] = 1;
24
+ data[8] = 0.1;
25
+ data[9] = 0.1;
26
+ data[10] = 0.1;
27
+ return { ambientCount: 1, data, directionalCount: 1, hemisphereCount: 0, pointCount: 0, spotCount: 0, version: 1 };
28
+ }
29
+
30
+ function makeProxy(): Scene3DRenderProxy {
31
+ const geometry = createBoxMeshGeometry();
32
+ return {
33
+ material: createLambertMaterial(),
34
+ normalMatrix: createMatrix3() as Matrix3,
35
+ subset: geometry.subsets[0],
36
+ worldMatrix: createMatrix4() as Matrix4,
37
+ };
38
+ }
39
+
40
+ describe('lambertWgpuMeshMaterialRenderer', () => {
41
+ it('bind selects a pipeline and binds frame + material groups + uploads uniforms', () => {
42
+ const { fake, state } = makeWgpuScene3DState();
43
+ lambertWgpuMeshMaterialRenderer.bind(state, createLambertMaterial(), makeLights(), makeCamera());
44
+
45
+ expect(fake.calls.some((c) => c.name === 'createRenderPipeline')).toBe(true);
46
+ expect(fake.calls.some((c) => c.name === 'setPipeline')).toBe(true);
47
+ expect(fake.calls.filter((c) => c.name === 'setBindGroup').length).toBeGreaterThanOrEqual(2);
48
+ expect(fake.calls.some((c) => c.name === 'writeBuffer')).toBe(true);
49
+ });
50
+
51
+ it('draw uploads geometry and issues an indexed draw over the subset range', () => {
52
+ const { fake, state } = makeWgpuScene3DState();
53
+ const proxy = makeProxy();
54
+ const geometry = createBoxMeshGeometry();
55
+ lambertWgpuMeshMaterialRenderer.bind(state, proxy.material, makeLights(), makeCamera());
56
+ lambertWgpuMeshMaterialRenderer.draw(state, proxy, geometry);
57
+
58
+ const drawCall = fake.calls.find((c) => c.name === 'drawIndexed');
59
+ expect(drawCall).toBeDefined();
60
+ expect(drawCall!.args[0]).toBe(proxy.subset.indexCount);
61
+ expect(drawCall!.args[2]).toBe(proxy.subset.indexOffset);
62
+ });
63
+
64
+ it('draw is a no-op when bind has not selected a pipeline', () => {
65
+ const { fake, state } = makeWgpuScene3DState();
66
+ lambertWgpuMeshMaterialRenderer.draw(state, makeProxy(), createBoxMeshGeometry());
67
+ expect(fake.calls.some((c) => c.name === 'drawIndexed')).toBe(false);
68
+ });
69
+ });
70
+
71
+ describe('registerLambertWgpuMaterial', () => {
72
+ it('installs the renderer for LambertMaterialKind', () => {
73
+ const { state } = makeWgpuScene3DState();
74
+ registerLambertWgpuMaterial(state);
75
+ expect(getWgpuMeshMaterialRenderer(state, LambertMaterialKind)).toBe(lambertWgpuMeshMaterialRenderer);
76
+ });
77
+ });
@@ -0,0 +1,69 @@
1
+ import { createCamera3D } from '@flighthq/camera';
2
+ import { createMatrix3, createMatrix4 } from '@flighthq/geometry';
3
+ import { createMatcapMaterial } from '@flighthq/materials';
4
+ import { createBoxMeshGeometry } from '@flighthq/mesh';
5
+ import type { Camera3D, Scene3DLightBlock, Scene3DRenderProxy } from '@flighthq/types';
6
+ import { MatcapMaterialKind } from '@flighthq/types';
7
+
8
+ import { matcapWgpuMeshMaterialRenderer, registerMatcapWgpuMaterial } from './matcapWgpuMeshMaterialRenderer';
9
+ import { getWgpuMeshMaterialRenderer } from './wgpuMeshMaterialRegistry';
10
+ import { makeWgpuScene3DState } from './wgpuScene3DTestHelper';
11
+
12
+ function makeCamera(): Camera3D {
13
+ return createCamera3D({ far: 100, near: 0.1, projection: { aspect: 1, fovY: Math.PI / 3, kind: 'perspective' } });
14
+ }
15
+
16
+ const NO_LIGHTS: Scene3DLightBlock = {
17
+ ambientCount: 0,
18
+ data: new Float32Array(12),
19
+ directionalCount: 0,
20
+ hemisphereCount: 0,
21
+ pointCount: 0,
22
+ spotCount: 0,
23
+ version: 1,
24
+ };
25
+
26
+ function makeProxy(): Scene3DRenderProxy {
27
+ const geometry = createBoxMeshGeometry();
28
+ return {
29
+ material: createMatcapMaterial(),
30
+ normalMatrix: createMatrix3(),
31
+ subset: geometry.subsets[0],
32
+ worldMatrix: createMatrix4(),
33
+ };
34
+ }
35
+
36
+ describe('matcapWgpuMeshMaterialRenderer', () => {
37
+ it('bind selects a pipeline and binds the frame + material groups', () => {
38
+ const { fake, state } = makeWgpuScene3DState();
39
+ matcapWgpuMeshMaterialRenderer.bind(state, createMatcapMaterial(), NO_LIGHTS, makeCamera());
40
+ expect(fake.calls.some((c) => c.name === 'createRenderPipeline')).toBe(true);
41
+ expect(fake.calls.some((c) => c.name === 'setPipeline')).toBe(true);
42
+ expect(fake.calls.filter((c) => c.name === 'setBindGroup').length).toBeGreaterThanOrEqual(2);
43
+ expect(fake.calls.some((c) => c.name === 'writeBuffer')).toBe(true);
44
+ });
45
+
46
+ it('draw issues an indexed draw over the subset after bind', () => {
47
+ const { fake, state } = makeWgpuScene3DState();
48
+ const proxy = makeProxy();
49
+ matcapWgpuMeshMaterialRenderer.bind(state, proxy.material, NO_LIGHTS, makeCamera());
50
+ matcapWgpuMeshMaterialRenderer.draw(state, proxy, createBoxMeshGeometry());
51
+ const drawCall = fake.calls.find((c) => c.name === 'drawIndexed');
52
+ expect(drawCall).toBeDefined();
53
+ expect(drawCall!.args[0]).toBe(proxy.subset.indexCount);
54
+ });
55
+
56
+ it('draw is a no-op when bind has not selected a pipeline', () => {
57
+ const { fake, state } = makeWgpuScene3DState();
58
+ matcapWgpuMeshMaterialRenderer.draw(state, makeProxy(), createBoxMeshGeometry());
59
+ expect(fake.calls.some((c) => c.name === 'drawIndexed')).toBe(false);
60
+ });
61
+ });
62
+
63
+ describe('registerMatcapWgpuMaterial', () => {
64
+ it('installs the renderer for MatcapMaterialKind', () => {
65
+ const { state } = makeWgpuScene3DState();
66
+ registerMatcapWgpuMaterial(state);
67
+ expect(getWgpuMeshMaterialRenderer(state, MatcapMaterialKind)).toBe(matcapWgpuMeshMaterialRenderer);
68
+ });
69
+ });