@flighthq/scene3d-gl 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 (284) hide show
  1. package/dist/anisotropyPbrGlMeshMaterialRenderer.d.ts +4 -0
  2. package/dist/anisotropyPbrGlMeshMaterialRenderer.d.ts.map +1 -0
  3. package/dist/anisotropyPbrGlMeshMaterialRenderer.js +45 -0
  4. package/dist/anisotropyPbrGlMeshMaterialRenderer.js.map +1 -0
  5. package/dist/blinnPhongGlMeshMaterialRenderer.d.ts +4 -0
  6. package/dist/blinnPhongGlMeshMaterialRenderer.d.ts.map +1 -0
  7. package/dist/blinnPhongGlMeshMaterialRenderer.js +102 -0
  8. package/dist/blinnPhongGlMeshMaterialRenderer.js.map +1 -0
  9. package/dist/clearcoatPbrGlMeshMaterialRenderer.d.ts +4 -0
  10. package/dist/clearcoatPbrGlMeshMaterialRenderer.d.ts.map +1 -0
  11. package/dist/clearcoatPbrGlMeshMaterialRenderer.js +45 -0
  12. package/dist/clearcoatPbrGlMeshMaterialRenderer.js.map +1 -0
  13. package/dist/customShaderGlMeshMaterialRenderer.d.ts +6 -0
  14. package/dist/customShaderGlMeshMaterialRenderer.d.ts.map +1 -0
  15. package/dist/customShaderGlMeshMaterialRenderer.js +136 -0
  16. package/dist/customShaderGlMeshMaterialRenderer.js.map +1 -0
  17. package/dist/depthGlMeshMaterialRenderer.d.ts +4 -0
  18. package/dist/depthGlMeshMaterialRenderer.d.ts.map +1 -0
  19. package/dist/depthGlMeshMaterialRenderer.js +36 -0
  20. package/dist/depthGlMeshMaterialRenderer.js.map +1 -0
  21. package/dist/drawGlScene3D.d.ts +3 -0
  22. package/dist/drawGlScene3D.d.ts.map +1 -0
  23. package/dist/drawGlScene3D.js +251 -0
  24. package/dist/drawGlScene3D.js.map +1 -0
  25. package/dist/emissiveGlMeshMaterialRenderer.d.ts +4 -0
  26. package/dist/emissiveGlMeshMaterialRenderer.d.ts.map +1 -0
  27. package/dist/emissiveGlMeshMaterialRenderer.js +49 -0
  28. package/dist/emissiveGlMeshMaterialRenderer.js.map +1 -0
  29. package/dist/enableGlScene3DColorSpaceGuards.d.ts +4 -0
  30. package/dist/enableGlScene3DColorSpaceGuards.d.ts.map +1 -0
  31. package/dist/enableGlScene3DColorSpaceGuards.js +22 -0
  32. package/dist/enableGlScene3DColorSpaceGuards.js.map +1 -0
  33. package/dist/enableGlScene3DCustomShaderGuards.d.ts +4 -0
  34. package/dist/enableGlScene3DCustomShaderGuards.d.ts.map +1 -0
  35. package/dist/enableGlScene3DCustomShaderGuards.js +71 -0
  36. package/dist/enableGlScene3DCustomShaderGuards.js.map +1 -0
  37. package/dist/enableGlScene3DDeformGuards.d.ts +4 -0
  38. package/dist/enableGlScene3DDeformGuards.d.ts.map +1 -0
  39. package/dist/enableGlScene3DDeformGuards.js +42 -0
  40. package/dist/enableGlScene3DDeformGuards.js.map +1 -0
  41. package/dist/enableGlScene3DForwardLightSelectionGuards.d.ts +4 -0
  42. package/dist/enableGlScene3DForwardLightSelectionGuards.d.ts.map +1 -0
  43. package/dist/enableGlScene3DForwardLightSelectionGuards.js +21 -0
  44. package/dist/enableGlScene3DForwardLightSelectionGuards.js.map +1 -0
  45. package/dist/explainGlScene3DForwardLightSelection.d.ts +3 -0
  46. package/dist/explainGlScene3DForwardLightSelection.d.ts.map +1 -0
  47. package/dist/explainGlScene3DForwardLightSelection.js +19 -0
  48. package/dist/explainGlScene3DForwardLightSelection.js.map +1 -0
  49. package/dist/glClassicPrelude.d.ts +9 -0
  50. package/dist/glClassicPrelude.d.ts.map +1 -0
  51. package/dist/glClassicPrelude.js +288 -0
  52. package/dist/glClassicPrelude.js.map +1 -0
  53. package/dist/glDebugPrelude.d.ts +9 -0
  54. package/dist/glDebugPrelude.d.ts.map +1 -0
  55. package/dist/glDebugPrelude.js +143 -0
  56. package/dist/glDebugPrelude.js.map +1 -0
  57. package/dist/glEnvironmentCube.d.ts +5 -0
  58. package/dist/glEnvironmentCube.d.ts.map +1 -0
  59. package/dist/glEnvironmentCube.js +65 -0
  60. package/dist/glEnvironmentCube.js.map +1 -0
  61. package/dist/glEnvironmentIblBake.d.ts +4 -0
  62. package/dist/glEnvironmentIblBake.d.ts.map +1 -0
  63. package/dist/glEnvironmentIblBake.js +365 -0
  64. package/dist/glEnvironmentIblBake.js.map +1 -0
  65. package/dist/glEnvironmentSkybox.d.ts +3 -0
  66. package/dist/glEnvironmentSkybox.d.ts.map +1 -0
  67. package/dist/glEnvironmentSkybox.js +95 -0
  68. package/dist/glEnvironmentSkybox.js.map +1 -0
  69. package/dist/glLitProgram.d.ts +5 -0
  70. package/dist/glLitProgram.d.ts.map +1 -0
  71. package/dist/glLitProgram.js +211 -0
  72. package/dist/glLitProgram.js.map +1 -0
  73. package/dist/glMatcapPrelude.d.ts +8 -0
  74. package/dist/glMatcapPrelude.d.ts.map +1 -0
  75. package/dist/glMatcapPrelude.js +118 -0
  76. package/dist/glMatcapPrelude.js.map +1 -0
  77. package/dist/glMeshMaterialRegistry.d.ts +5 -0
  78. package/dist/glMeshMaterialRegistry.d.ts.map +1 -0
  79. package/dist/glMeshMaterialRegistry.js +29 -0
  80. package/dist/glMeshMaterialRegistry.js.map +1 -0
  81. package/dist/glMeshProgram.d.ts +14 -0
  82. package/dist/glMeshProgram.d.ts.map +1 -0
  83. package/dist/glMeshProgram.js +193 -0
  84. package/dist/glMeshProgram.js.map +1 -0
  85. package/dist/glMeshUpload.d.ts +4 -0
  86. package/dist/glMeshUpload.d.ts.map +1 -0
  87. package/dist/glMeshUpload.js +190 -0
  88. package/dist/glMeshUpload.js.map +1 -0
  89. package/dist/glParticleEmitter3D.d.ts +4 -0
  90. package/dist/glParticleEmitter3D.d.ts.map +1 -0
  91. package/dist/glParticleEmitter3D.js +342 -0
  92. package/dist/glParticleEmitter3D.js.map +1 -0
  93. package/dist/glPbrPrelude.d.ts +8 -0
  94. package/dist/glPbrPrelude.d.ts.map +1 -0
  95. package/dist/glPbrPrelude.js +569 -0
  96. package/dist/glPbrPrelude.js.map +1 -0
  97. package/dist/glPbrProgramCache.d.ts +4 -0
  98. package/dist/glPbrProgramCache.d.ts.map +1 -0
  99. package/dist/glPbrProgramCache.js +63 -0
  100. package/dist/glPbrProgramCache.js.map +1 -0
  101. package/dist/glPbrStandardBlock.d.ts +13 -0
  102. package/dist/glPbrStandardBlock.d.ts.map +1 -0
  103. package/dist/glPbrStandardBlock.js +107 -0
  104. package/dist/glPbrStandardBlock.js.map +1 -0
  105. package/dist/glScene3DRuntime.d.ts +5 -0
  106. package/dist/glScene3DRuntime.d.ts.map +1 -0
  107. package/dist/glScene3DRuntime.js +99 -0
  108. package/dist/glScene3DRuntime.js.map +1 -0
  109. package/dist/glScene3DTestHelper.d.ts +20 -0
  110. package/dist/glScene3DTestHelper.d.ts.map +1 -0
  111. package/dist/glScene3DTestHelper.js +178 -0
  112. package/dist/glScene3DTestHelper.js.map +1 -0
  113. package/dist/glScene3DTime.d.ts +4 -0
  114. package/dist/glScene3DTime.d.ts.map +1 -0
  115. package/dist/glScene3DTime.js +18 -0
  116. package/dist/glScene3DTime.js.map +1 -0
  117. package/dist/glShadedBuiltInModifiers.d.ts +11 -0
  118. package/dist/glShadedBuiltInModifiers.d.ts.map +1 -0
  119. package/dist/glShadedBuiltInModifiers.js +386 -0
  120. package/dist/glShadedBuiltInModifiers.js.map +1 -0
  121. package/dist/glShadedModifierSnippet.d.ts +4 -0
  122. package/dist/glShadedModifierSnippet.d.ts.map +1 -0
  123. package/dist/glShadedModifierSnippet.js +24 -0
  124. package/dist/glShadedModifierSnippet.js.map +1 -0
  125. package/dist/glShadedPrelude.d.ts +5 -0
  126. package/dist/glShadedPrelude.d.ts.map +1 -0
  127. package/dist/glShadedPrelude.js +393 -0
  128. package/dist/glShadedPrelude.js.map +1 -0
  129. package/dist/glShadowMap.d.ts +3 -0
  130. package/dist/glShadowMap.d.ts.map +1 -0
  131. package/dist/glShadowMap.js +150 -0
  132. package/dist/glShadowMap.js.map +1 -0
  133. package/dist/glToonPrelude.d.ts +7 -0
  134. package/dist/glToonPrelude.d.ts.map +1 -0
  135. package/dist/glToonPrelude.js +172 -0
  136. package/dist/glToonPrelude.js.map +1 -0
  137. package/dist/glUnlitPrelude.d.ts +9 -0
  138. package/dist/glUnlitPrelude.d.ts.map +1 -0
  139. package/dist/glUnlitPrelude.js +160 -0
  140. package/dist/glUnlitPrelude.js.map +1 -0
  141. package/dist/glWireframePrelude.d.ts +6 -0
  142. package/dist/glWireframePrelude.d.ts.map +1 -0
  143. package/dist/glWireframePrelude.js +56 -0
  144. package/dist/glWireframePrelude.js.map +1 -0
  145. package/dist/glWireframeUpload.d.ts +4 -0
  146. package/dist/glWireframeUpload.d.ts.map +1 -0
  147. package/dist/glWireframeUpload.js +85 -0
  148. package/dist/glWireframeUpload.js.map +1 -0
  149. package/dist/index.d.ts +56 -0
  150. package/dist/index.d.ts.map +1 -0
  151. package/dist/index.js +56 -0
  152. package/dist/index.js.map +1 -0
  153. package/dist/iridescencePbrGlMeshMaterialRenderer.d.ts +4 -0
  154. package/dist/iridescencePbrGlMeshMaterialRenderer.d.ts.map +1 -0
  155. package/dist/iridescencePbrGlMeshMaterialRenderer.js +54 -0
  156. package/dist/iridescencePbrGlMeshMaterialRenderer.js.map +1 -0
  157. package/dist/lambertGlMeshMaterialRenderer.d.ts +4 -0
  158. package/dist/lambertGlMeshMaterialRenderer.d.ts.map +1 -0
  159. package/dist/lambertGlMeshMaterialRenderer.js +70 -0
  160. package/dist/lambertGlMeshMaterialRenderer.js.map +1 -0
  161. package/dist/matcapGlMeshMaterialRenderer.d.ts +4 -0
  162. package/dist/matcapGlMeshMaterialRenderer.d.ts.map +1 -0
  163. package/dist/matcapGlMeshMaterialRenderer.js +49 -0
  164. package/dist/matcapGlMeshMaterialRenderer.js.map +1 -0
  165. package/dist/normalGlMeshMaterialRenderer.d.ts +4 -0
  166. package/dist/normalGlMeshMaterialRenderer.d.ts.map +1 -0
  167. package/dist/normalGlMeshMaterialRenderer.js +39 -0
  168. package/dist/normalGlMeshMaterialRenderer.js.map +1 -0
  169. package/dist/phongGlMeshMaterialRenderer.d.ts +4 -0
  170. package/dist/phongGlMeshMaterialRenderer.d.ts.map +1 -0
  171. package/dist/phongGlMeshMaterialRenderer.js +90 -0
  172. package/dist/phongGlMeshMaterialRenderer.js.map +1 -0
  173. package/dist/prepareGlScene3DForwardLights.d.ts +3 -0
  174. package/dist/prepareGlScene3DForwardLights.d.ts.map +1 -0
  175. package/dist/prepareGlScene3DForwardLights.js +117 -0
  176. package/dist/prepareGlScene3DForwardLights.js.map +1 -0
  177. package/dist/presentGlScene3D.d.ts +3 -0
  178. package/dist/presentGlScene3D.d.ts.map +1 -0
  179. package/dist/presentGlScene3D.js +21 -0
  180. package/dist/presentGlScene3D.js.map +1 -0
  181. package/dist/registerStandardPbrGlMaterial.d.ts +3 -0
  182. package/dist/registerStandardPbrGlMaterial.d.ts.map +1 -0
  183. package/dist/registerStandardPbrGlMaterial.js +11 -0
  184. package/dist/registerStandardPbrGlMaterial.js.map +1 -0
  185. package/dist/shadedGlMeshMaterialRenderer.d.ts +4 -0
  186. package/dist/shadedGlMeshMaterialRenderer.d.ts.map +1 -0
  187. package/dist/shadedGlMeshMaterialRenderer.js +135 -0
  188. package/dist/shadedGlMeshMaterialRenderer.js.map +1 -0
  189. package/dist/sheenPbrGlMeshMaterialRenderer.d.ts +4 -0
  190. package/dist/sheenPbrGlMeshMaterialRenderer.d.ts.map +1 -0
  191. package/dist/sheenPbrGlMeshMaterialRenderer.js +54 -0
  192. package/dist/sheenPbrGlMeshMaterialRenderer.js.map +1 -0
  193. package/dist/specularGlossinessPbrGlMeshMaterialRenderer.d.ts +4 -0
  194. package/dist/specularGlossinessPbrGlMeshMaterialRenderer.d.ts.map +1 -0
  195. package/dist/specularGlossinessPbrGlMeshMaterialRenderer.js +121 -0
  196. package/dist/specularGlossinessPbrGlMeshMaterialRenderer.js.map +1 -0
  197. package/dist/specularPbrGlMeshMaterialRenderer.d.ts +4 -0
  198. package/dist/specularPbrGlMeshMaterialRenderer.d.ts.map +1 -0
  199. package/dist/specularPbrGlMeshMaterialRenderer.js +55 -0
  200. package/dist/specularPbrGlMeshMaterialRenderer.js.map +1 -0
  201. package/dist/standardPbrGlMeshMaterialRenderer.d.ts +3 -0
  202. package/dist/standardPbrGlMeshMaterialRenderer.d.ts.map +1 -0
  203. package/dist/standardPbrGlMeshMaterialRenderer.js +35 -0
  204. package/dist/standardPbrGlMeshMaterialRenderer.js.map +1 -0
  205. package/dist/subsurfacePbrGlMeshMaterialRenderer.d.ts +4 -0
  206. package/dist/subsurfacePbrGlMeshMaterialRenderer.d.ts.map +1 -0
  207. package/dist/subsurfacePbrGlMeshMaterialRenderer.js +57 -0
  208. package/dist/subsurfacePbrGlMeshMaterialRenderer.js.map +1 -0
  209. package/dist/toonGlMeshMaterialRenderer.d.ts +4 -0
  210. package/dist/toonGlMeshMaterialRenderer.d.ts.map +1 -0
  211. package/dist/toonGlMeshMaterialRenderer.js +81 -0
  212. package/dist/toonGlMeshMaterialRenderer.js.map +1 -0
  213. package/dist/transmissionVolumePbrGlMeshMaterialRenderer.d.ts +4 -0
  214. package/dist/transmissionVolumePbrGlMeshMaterialRenderer.d.ts.map +1 -0
  215. package/dist/transmissionVolumePbrGlMeshMaterialRenderer.js +62 -0
  216. package/dist/transmissionVolumePbrGlMeshMaterialRenderer.js.map +1 -0
  217. package/dist/unlitGlMeshMaterialRenderer.d.ts +4 -0
  218. package/dist/unlitGlMeshMaterialRenderer.d.ts.map +1 -0
  219. package/dist/unlitGlMeshMaterialRenderer.js +61 -0
  220. package/dist/unlitGlMeshMaterialRenderer.js.map +1 -0
  221. package/dist/vertexColorGlMeshMaterialRenderer.d.ts +4 -0
  222. package/dist/vertexColorGlMeshMaterialRenderer.d.ts.map +1 -0
  223. package/dist/vertexColorGlMeshMaterialRenderer.js +54 -0
  224. package/dist/vertexColorGlMeshMaterialRenderer.js.map +1 -0
  225. package/dist/wireframeGlMeshMaterialRenderer.d.ts +4 -0
  226. package/dist/wireframeGlMeshMaterialRenderer.d.ts.map +1 -0
  227. package/dist/wireframeGlMeshMaterialRenderer.js +50 -0
  228. package/dist/wireframeGlMeshMaterialRenderer.js.map +1 -0
  229. package/package.json +58 -0
  230. package/src/anisotropyPbrGlMeshMaterialRenderer.test.ts +86 -0
  231. package/src/blinnPhongGlMeshMaterialRenderer.test.ts +94 -0
  232. package/src/clearcoatPbrGlMeshMaterialRenderer.test.ts +81 -0
  233. package/src/customShaderGlMeshMaterialRenderer.test.ts +169 -0
  234. package/src/depthGlMeshMaterialRenderer.test.ts +69 -0
  235. package/src/drawGlScene3D.test.ts +304 -0
  236. package/src/emissiveGlMeshMaterialRenderer.test.ts +60 -0
  237. package/src/enableGlScene3DColorSpaceGuards.test.ts +63 -0
  238. package/src/enableGlScene3DCustomShaderGuards.test.ts +85 -0
  239. package/src/enableGlScene3DDeformGuards.test.ts +98 -0
  240. package/src/enableGlScene3DForwardLightSelectionGuards.test.ts +84 -0
  241. package/src/explainGlScene3DForwardLightSelection.test.ts +32 -0
  242. package/src/glClassicPrelude.test.ts +214 -0
  243. package/src/glDebugPrelude.test.ts +94 -0
  244. package/src/glEnvironmentCube.test.ts +71 -0
  245. package/src/glEnvironmentIblBake.test.ts +31 -0
  246. package/src/glEnvironmentSkybox.test.ts +24 -0
  247. package/src/glLitProgram.test.ts +181 -0
  248. package/src/glMatcapPrelude.test.ts +74 -0
  249. package/src/glMeshMaterialRegistry.test.ts +52 -0
  250. package/src/glMeshProgram.test.ts +406 -0
  251. package/src/glMeshUpload.test.ts +311 -0
  252. package/src/glParticleEmitter3D.test.ts +280 -0
  253. package/src/glPbrPrelude.test.ts +178 -0
  254. package/src/glPbrProgramCache.test.ts +116 -0
  255. package/src/glPbrStandardBlock.test.ts +85 -0
  256. package/src/glScene3DRuntime.test.ts +108 -0
  257. package/src/glScene3DTime.test.ts +31 -0
  258. package/src/glShadedBuiltInModifiers.test.ts +377 -0
  259. package/src/glShadedModifierSnippet.test.ts +46 -0
  260. package/src/glShadedPrelude.test.ts +177 -0
  261. package/src/glShadowMap.test.ts +219 -0
  262. package/src/glToonPrelude.test.ts +118 -0
  263. package/src/glUnlitPrelude.test.ts +156 -0
  264. package/src/glWireframePrelude.test.ts +46 -0
  265. package/src/glWireframeUpload.test.ts +50 -0
  266. package/src/iridescencePbrGlMeshMaterialRenderer.test.ts +86 -0
  267. package/src/lambertGlMeshMaterialRenderer.test.ts +92 -0
  268. package/src/matcapGlMeshMaterialRenderer.test.ts +70 -0
  269. package/src/normalGlMeshMaterialRenderer.test.ts +69 -0
  270. package/src/phongGlMeshMaterialRenderer.test.ts +94 -0
  271. package/src/prepareGlScene3DForwardLights.test.ts +106 -0
  272. package/src/presentGlScene3D.test.ts +98 -0
  273. package/src/registerStandardPbrGlMaterial.test.ts +14 -0
  274. package/src/shadedGlMeshMaterialRenderer.test.ts +106 -0
  275. package/src/sheenPbrGlMeshMaterialRenderer.test.ts +83 -0
  276. package/src/specularGlossinessPbrGlMeshMaterialRenderer.test.ts +96 -0
  277. package/src/specularPbrGlMeshMaterialRenderer.test.ts +83 -0
  278. package/src/standardPbrGlMeshMaterialRenderer.test.ts +88 -0
  279. package/src/subsurfacePbrGlMeshMaterialRenderer.test.ts +86 -0
  280. package/src/toonGlMeshMaterialRenderer.test.ts +92 -0
  281. package/src/transmissionVolumePbrGlMeshMaterialRenderer.test.ts +93 -0
  282. package/src/unlitGlMeshMaterialRenderer.test.ts +88 -0
  283. package/src/vertexColorGlMeshMaterialRenderer.test.ts +60 -0
  284. package/src/wireframeGlMeshMaterialRenderer.test.ts +69 -0
@@ -0,0 +1,304 @@
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 { createBoxMeshGeometry } from '@flighthq/mesh';
6
+ import { addNodeChild, invalidateNodeLocalTransform } from '@flighthq/node';
7
+ import { createParticleEmitter3D, reserveParticleEmitter3D } from '@flighthq/particleemitter';
8
+ import { getGlRenderStateRuntime } from '@flighthq/render-gl';
9
+ import { createMesh, createNode3D, Node3DKind } from '@flighthq/scene3d';
10
+ import type { Camera3D, GlRenderTarget, Scene3DLightsLike } from '@flighthq/types';
11
+
12
+ import { drawGlScene3D } from './drawGlScene3D';
13
+ import { getGlScene3DRuntime } from './glScene3DRuntime';
14
+ import { makeGlScene3DState } from './glScene3DTestHelper';
15
+ import { registerStandardPbrGlMaterial } from './registerStandardPbrGlMaterial';
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('drawGlScene3D', () => {
33
+ it('binds once for a run of subsets sharing a material', () => {
34
+ const { state, gl } = makeGlScene3DState();
35
+ registerStandardPbrGlMaterial(state);
36
+
37
+ const scene = createNode3D(Node3DKind);
38
+ const material = createStandardPbrMaterial();
39
+ const meshA = createMesh(createBoxMeshGeometry(), [material]);
40
+ const meshB = createMesh(createBoxMeshGeometry(), [material]);
41
+ addNodeChild(scene, meshA);
42
+ addNodeChild(scene, meshB);
43
+
44
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
45
+
46
+ // Same material across both meshes: bound once, drawn twice.
47
+ expect(gl.calls.filter((c) => c.name === 'useProgram').length).toBe(1);
48
+ expect(gl.calls.filter((c) => c.name === 'drawElements').length).toBe(2);
49
+ });
50
+
51
+ it('invalidates render-gl binding cache after drawing so the present pass re-binds', () => {
52
+ const { state } = makeGlScene3DState();
53
+ registerStandardPbrGlMaterial(state);
54
+
55
+ const scene = createNode3D(Node3DKind);
56
+ addNodeChild(scene, createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]));
57
+
58
+ // Stand in for render-gl having a program bound and cached before the scene draws. The mesh
59
+ // renderers bind their own programs with raw gl.useProgram, which render-gl's cache never sees;
60
+ // without the post-draw invalidation the stale program would survive and the effect-pipeline
61
+ // present pass would set uniforms against a program that is no longer bound (INVALID_OPERATION).
62
+ const runtime = getGlRenderStateRuntime(state);
63
+ runtime.currentProgram = {} as WebGLProgram;
64
+
65
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
66
+
67
+ expect(runtime.currentProgram).toBeNull();
68
+ });
69
+
70
+ it("declares the bound render target 'linear' (scene materials output linear HDR)", () => {
71
+ const { state } = makeGlScene3DState();
72
+ registerStandardPbrGlMaterial(state);
73
+ const scene = createNode3D(Node3DKind);
74
+ addNodeChild(scene, createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]));
75
+
76
+ const runtime = getGlRenderStateRuntime(state);
77
+ const target = { colorSpace: 'srgb' } as GlRenderTarget;
78
+ runtime.currentRenderTarget = target;
79
+
80
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
81
+
82
+ expect(target.colorSpace).toBe('linear');
83
+ });
84
+
85
+ it('does not draw a disabled mesh', () => {
86
+ const { state, gl } = makeGlScene3DState();
87
+ registerStandardPbrGlMaterial(state);
88
+
89
+ const scene = createNode3D(Node3DKind);
90
+ const mesh = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
91
+ mesh.enabled = false;
92
+ addNodeChild(scene, mesh);
93
+
94
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
95
+ expect(gl.calls.some((c) => c.name === 'drawElements')).toBe(false);
96
+ });
97
+
98
+ it('draws each visible mesh subset with its registered material renderer', () => {
99
+ const { state, gl } = makeGlScene3DState();
100
+ registerStandardPbrGlMaterial(state);
101
+
102
+ const scene = createNode3D(Node3DKind);
103
+ const mesh = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
104
+ addNodeChild(scene, mesh);
105
+
106
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
107
+
108
+ expect(gl.calls.some((c) => c.name === 'useProgram')).toBe(true);
109
+ expect(gl.calls.some((c) => c.name === 'drawElements')).toBe(true);
110
+ });
111
+
112
+ it('enables GL blend for subsets with alphaMode blend and disables it after', () => {
113
+ const { state, gl } = makeGlScene3DState();
114
+ registerStandardPbrGlMaterial(state);
115
+
116
+ const scene = createNode3D(Node3DKind);
117
+ const blendedMaterial = createStandardPbrMaterial();
118
+ blendedMaterial.alphaMode = 'blend';
119
+ const mesh = createMesh(createBoxMeshGeometry(), [blendedMaterial]);
120
+ addNodeChild(scene, mesh);
121
+
122
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
123
+
124
+ const enableCalls = gl.calls.filter((c) => c.name === 'enable');
125
+ const disableCalls = gl.calls.filter((c) => c.name === 'disable');
126
+ // BLEND = 0x0be2
127
+ expect(enableCalls.some((c) => c.args[0] === 0x0be2)).toBe(true);
128
+ expect(disableCalls.some((c) => c.args[0] === 0x0be2)).toBe(true);
129
+ expect(gl.calls.some((c) => c.name === 'blendFunc')).toBe(true);
130
+ expect(gl.calls.some((c) => c.name === 'drawElements')).toBe(true);
131
+ });
132
+
133
+ it('draws opaque subsets before blended subsets regardless of scene order', () => {
134
+ const { state, gl } = makeGlScene3DState();
135
+ registerStandardPbrGlMaterial(state);
136
+
137
+ const scene = createNode3D(Node3DKind);
138
+ // Add blended first, then opaque — opaque should still draw before blended.
139
+ const blendedMaterial = createStandardPbrMaterial();
140
+ blendedMaterial.alphaMode = 'blend';
141
+ const opaqueMaterial = createStandardPbrMaterial();
142
+
143
+ const blendedMesh = createMesh(createBoxMeshGeometry(), [blendedMaterial]);
144
+ const opaqueMesh = createMesh(createBoxMeshGeometry(), [opaqueMaterial]);
145
+ addNodeChild(scene, blendedMesh);
146
+ addNodeChild(scene, opaqueMesh);
147
+
148
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
149
+
150
+ // Both meshes drawn.
151
+ expect(gl.calls.filter((c) => c.name === 'drawElements').length).toBe(2);
152
+ // GL blending was enabled and then disabled (blended pass ran).
153
+ expect(gl.calls.some((c) => c.name === 'enable' && c.args[0] === 0x0be2)).toBe(true);
154
+ });
155
+
156
+ it('does not enable GL blend when all subsets are opaque', () => {
157
+ const { state, gl } = makeGlScene3DState();
158
+ registerStandardPbrGlMaterial(state);
159
+
160
+ const scene = createNode3D(Node3DKind);
161
+ const mesh = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
162
+ addNodeChild(scene, mesh);
163
+
164
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
165
+
166
+ // No blended subsets: GL_BLEND should not be enabled.
167
+ expect(gl.calls.some((c) => c.name === 'enable' && c.args[0] === 0x0be2)).toBe(false);
168
+ });
169
+
170
+ it('routes a mesh with node alpha below 1 through the blended pass even with an opaque material', () => {
171
+ const { state, gl } = makeGlScene3DState();
172
+ registerStandardPbrGlMaterial(state);
173
+
174
+ const scene = createNode3D(Node3DKind);
175
+ // Opaque material, but the node is faded (alpha < 1). prepareScene3DRender (run inside drawGlScene3D)
176
+ // folds the authored alpha into worldAlpha, and drawGlScene3D must route the fading object through
177
+ // the blended pass so it composites correctly.
178
+ const mesh = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
179
+ mesh.alpha = 0.5;
180
+ addNodeChild(scene, mesh);
181
+
182
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
183
+
184
+ expect(gl.calls.some((c) => c.name === 'enable' && c.args[0] === 0x0be2)).toBe(true);
185
+ expect(gl.calls.some((c) => c.name === 'drawElements')).toBe(true);
186
+ // The resolved node opacity was uploaded to u_objectAlpha (a uniform1f of 0.5).
187
+ expect(gl.calls.some((c) => c.name === 'uniform1f' && c.args[1] === 0.5)).toBe(true);
188
+ });
189
+
190
+ it('reuses opaque and blended draw records across frames', () => {
191
+ const { state } = makeGlScene3DState();
192
+ registerStandardPbrGlMaterial(state);
193
+
194
+ const scene = createNode3D(Node3DKind);
195
+ const opaque = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
196
+ const blendedMaterial = createStandardPbrMaterial();
197
+ blendedMaterial.alphaMode = 'blend';
198
+ const blended = createMesh(createBoxMeshGeometry(), [blendedMaterial]);
199
+ addNodeChild(scene, opaque);
200
+ addNodeChild(scene, blended);
201
+
202
+ const camera = makeCamera();
203
+ drawGlScene3D(state, scene, camera, LIGHTS);
204
+ const runtime = getGlScene3DRuntime(state);
205
+ const opaqueEntry = runtime.opaqueDrawList[0];
206
+ const blendedEntry = runtime.blendedDrawList[0];
207
+
208
+ drawGlScene3D(state, scene, camera, LIGHTS);
209
+
210
+ expect(runtime.opaqueDrawList[0]).toBe(opaqueEntry);
211
+ expect(runtime.blendedDrawList[0]).toBe(blendedEntry);
212
+ expect(runtime.opaquePool).toHaveLength(0);
213
+ expect(runtime.blendedPool).toHaveLength(0);
214
+ });
215
+
216
+ it('keeps a fully-opaque mesh (node alpha 1) in the opaque pass', () => {
217
+ const { state, gl } = makeGlScene3DState();
218
+ registerStandardPbrGlMaterial(state);
219
+
220
+ const scene = createNode3D(Node3DKind);
221
+ const mesh = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
222
+ mesh.alpha = 1;
223
+ addNodeChild(scene, mesh);
224
+
225
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
226
+
227
+ expect(gl.calls.some((c) => c.name === 'enable' && c.args[0] === 0x0be2)).toBe(false);
228
+ expect(gl.calls.some((c) => c.name === 'drawElements')).toBe(true);
229
+ });
230
+
231
+ it('skips a subset whose material has no registered renderer (no fallback)', () => {
232
+ const { state, gl } = makeGlScene3DState();
233
+ // No registerStandardPbrGlMaterial: nothing resolves.
234
+ const scene = createNode3D(Node3DKind);
235
+ const mesh = createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]);
236
+ addNodeChild(scene, mesh);
237
+
238
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
239
+ expect(gl.calls.some((c) => c.name === 'drawElements')).toBe(false);
240
+ });
241
+
242
+ it('sorts blended subsets back-to-front by camera depth', () => {
243
+ const { state, gl } = makeGlScene3DState();
244
+ registerStandardPbrGlMaterial(state);
245
+
246
+ const scene = createNode3D(Node3DKind);
247
+ const blendedMaterial = createStandardPbrMaterial();
248
+ blendedMaterial.alphaMode = 'blend';
249
+
250
+ // Place two meshes at different Z depths: far (z=-3) and near (z=-1). The far mesh should be
251
+ // drawn first (larger clip-W, farthest from camera) in the blended pass. Author the depth via the
252
+ // node's translation z — the translation column of the composed local matrix.
253
+ const farMesh = createMesh(createBoxMeshGeometry(), [blendedMaterial]);
254
+ const nearMesh = createMesh(createBoxMeshGeometry(), [blendedMaterial]);
255
+ farMesh.position.z = -3;
256
+ invalidateNodeLocalTransform(farMesh);
257
+ nearMesh.position.z = -1;
258
+ invalidateNodeLocalTransform(nearMesh);
259
+ addNodeChild(scene, nearMesh);
260
+ addNodeChild(scene, farMesh);
261
+
262
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
263
+
264
+ // Both blended meshes are drawn.
265
+ expect(gl.calls.filter((c) => c.name === 'drawElements').length).toBe(2);
266
+ });
267
+
268
+ it('draws a ParticleEmitter3D node in the scene via the single drawGlScene3D call', () => {
269
+ const { state, gl } = makeGlScene3DState();
270
+ registerStandardPbrGlMaterial(state);
271
+
272
+ const scene = createNode3D(Node3DKind);
273
+ // An emitter carries no geometry, so it never appears in the visible-mesh list; drawGlScene3D must
274
+ // run the emitter pass internally. The emitter's instanced pass is the only drawElementsInstanced
275
+ // caller, so its presence proves the emitter was drawn without a separate manual pass.
276
+ const emitter = createParticleEmitter3D();
277
+ reserveParticleEmitter3D(emitter, 2);
278
+ emitter.data.particleCount = 2;
279
+ for (let i = 0; i < 2; i++) {
280
+ emitter.data.transforms[i * 4 + 3] = 1;
281
+ emitter.data.alphas[i] = 1;
282
+ emitter.data.colors[i * 3] = 1;
283
+ emitter.data.colors[i * 3 + 1] = 1;
284
+ emitter.data.colors[i * 3 + 2] = 1;
285
+ }
286
+ addNodeChild(scene, emitter);
287
+
288
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
289
+
290
+ expect(gl.calls.some((c) => c.name === 'drawElementsInstanced')).toBe(true);
291
+ });
292
+
293
+ it('does not draw an instanced particle pass when the scene has no emitters', () => {
294
+ const { state, gl } = makeGlScene3DState();
295
+ registerStandardPbrGlMaterial(state);
296
+
297
+ const scene = createNode3D(Node3DKind);
298
+ addNodeChild(scene, createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]));
299
+
300
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
301
+
302
+ expect(gl.calls.some((c) => c.name === 'drawElementsInstanced')).toBe(false);
303
+ });
304
+ });
@@ -0,0 +1,60 @@
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 { emissiveGlMeshMaterialRenderer, registerEmissiveGlMaterial } from './emissiveGlMeshMaterialRenderer';
9
+ import { getGlMeshMaterialRenderer } from './glMeshMaterialRegistry';
10
+ import { makeGlScene3DState } from './glScene3DTestHelper';
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('emissiveGlMeshMaterialRenderer', () => {
37
+ it('bind uploads the emissive color scaled by emissiveStrength via the intensity uniform', () => {
38
+ const { state, gl } = makeGlScene3DState();
39
+ const material = createEmissiveMaterial({ emissiveStrength: 4 });
40
+ emissiveGlMeshMaterialRenderer.bind(state, material, NO_LIGHTS, makeCamera());
41
+ expect(gl.calls.some((c) => c.name === 'uniform4f')).toBe(true);
42
+ expect(gl.calls.some((c) => c.name === 'uniform1f')).toBe(true);
43
+ });
44
+
45
+ it('draw issues an indexed draw over the subset after bind', () => {
46
+ const { state, gl } = makeGlScene3DState();
47
+ const proxy = makeProxy();
48
+ emissiveGlMeshMaterialRenderer.bind(state, proxy.material, NO_LIGHTS, makeCamera());
49
+ emissiveGlMeshMaterialRenderer.draw(state, proxy, createBoxMeshGeometry());
50
+ expect(gl.calls.some((c) => c.name === 'drawElements')).toBe(true);
51
+ });
52
+ });
53
+
54
+ describe('registerEmissiveGlMaterial', () => {
55
+ it('installs the renderer for EmissiveMaterialKind', () => {
56
+ const { state } = makeGlScene3DState();
57
+ registerEmissiveGlMaterial(state);
58
+ expect(getGlMeshMaterialRenderer(state, EmissiveMaterialKind)).toBe(emissiveGlMeshMaterialRenderer);
59
+ });
60
+ });
@@ -0,0 +1,63 @@
1
+ import { createCamera3D, setCamera3DViewMatrix4FromLookAt } from '@flighthq/camera';
2
+ import { createVector3 } from '@flighthq/geometry';
3
+ import { createAmbientLight, createDirectionalLight } from '@flighthq/lighting';
4
+ import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log';
5
+ import { createStandardPbrMaterial } from '@flighthq/materials';
6
+ import { createBoxMeshGeometry } from '@flighthq/mesh';
7
+ import { addNodeChild } from '@flighthq/node';
8
+ import { createMesh, createNode3D, Node3DKind } from '@flighthq/scene3d';
9
+ import type { Camera3D, Scene3DLightsLike } from '@flighthq/types';
10
+
11
+ import { drawGlScene3D } from './drawGlScene3D';
12
+ import {
13
+ areGlScene3DColorSpaceGuardsEnabled,
14
+ enableGlScene3DColorSpaceGuards,
15
+ } from './enableGlScene3DColorSpaceGuards';
16
+ import { makeGlScene3DState } from './glScene3DTestHelper';
17
+ import { registerStandardPbrGlMaterial } from './registerStandardPbrGlMaterial';
18
+
19
+ function makeCamera(): Camera3D {
20
+ const camera = createCamera3D({
21
+ far: 100,
22
+ near: 0.1,
23
+ projection: { aspect: 1, fovY: Math.PI / 3, kind: 'perspective' },
24
+ });
25
+ setCamera3DViewMatrix4FromLookAt(camera, { x: 0, y: 0, z: 5 }, { x: 0, y: 0, z: 0 }, { x: 0, y: 1, z: 0 });
26
+ return camera;
27
+ }
28
+
29
+ const LIGHTS: Scene3DLightsLike = {
30
+ ambient: createAmbientLight({ color: 0xffffffff, intensity: 0.2 }),
31
+ directional: createDirectionalLight({ color: 0xffffffff, direction: createVector3(0, -1, -1), intensity: 1 }),
32
+ };
33
+
34
+ describe('areGlScene3DColorSpaceGuardsEnabled', () => {
35
+ it('reports false until guards are installed, then true', () => {
36
+ const { state } = makeGlScene3DState();
37
+ expect(areGlScene3DColorSpaceGuardsEnabled(state)).toBe(false);
38
+ enableGlScene3DColorSpaceGuards(state);
39
+ expect(areGlScene3DColorSpaceGuardsEnabled(state)).toBe(true);
40
+ });
41
+ });
42
+
43
+ describe('enableGlScene3DColorSpaceGuards', () => {
44
+ it('warns once when a scene is drawn directly to the canvas (no target to encode)', () => {
45
+ const { state } = makeGlScene3DState();
46
+ registerStandardPbrGlMaterial(state);
47
+ const scene = createNode3D(Node3DKind);
48
+ addNodeChild(scene, createMesh(createBoxMeshGeometry(), [createStandardPbrMaterial()]));
49
+
50
+ const sink = createMemoryLogSink(8);
51
+ addLogSink(sink.sink);
52
+ try {
53
+ enableGlScene3DColorSpaceGuards(state);
54
+ // No beginGlRenderPass: currentRenderTarget is null, so the scene draws straight to the canvas.
55
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
56
+ const entries = getMemoryLogSinkEntries(sink);
57
+ expect(entries.length).toBe(1);
58
+ expect(String((entries[0].data as Record<string, unknown>).message)).toContain('drawGlScene3D');
59
+ } finally {
60
+ removeLogSink(sink.sink);
61
+ }
62
+ });
63
+ });
@@ -0,0 +1,85 @@
1
+ import { createCamera3D } from '@flighthq/camera';
2
+ import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log';
3
+ import { createCustomShaderMaterial } from '@flighthq/materials';
4
+ import type { Camera3D, Scene3DLightBlock } from '@flighthq/types';
5
+
6
+ import {
7
+ customShaderGlMeshMaterialRenderer,
8
+ registerGlCustomMaterialShader,
9
+ } from './customShaderGlMeshMaterialRenderer';
10
+ import {
11
+ areGlScene3DCustomShaderGuardsEnabled,
12
+ enableGlScene3DCustomShaderGuards,
13
+ } from './enableGlScene3DCustomShaderGuards';
14
+ import { makeFakeGl2, makeGlScene3DState } from './glScene3DTestHelper';
15
+
16
+ // GL uniform-type enums the mock and the guard compare against (WebGL2 spec values).
17
+ const GL_FLOAT_MAT3 = 0x8b5b;
18
+ const GL_FLOAT_MAT4 = 0x8b5c;
19
+
20
+ function makeCamera(): Camera3D {
21
+ return createCamera3D({ far: 100, near: 0.1, projection: { aspect: 1, fovY: Math.PI / 3, kind: 'perspective' } });
22
+ }
23
+
24
+ const NO_LIGHTS: Scene3DLightBlock = {
25
+ ambientCount: 0,
26
+ data: new Float32Array(12),
27
+ directionalCount: 0,
28
+ hemisphereCount: 0,
29
+ pointCount: 0,
30
+ spotCount: 0,
31
+ version: 1,
32
+ };
33
+
34
+ const SOURCE = { fragment: 'frag', vertex: 'vert' };
35
+
36
+ // Binds a custom-shader material under `shaderKey` with the guard installed, against a fake context whose
37
+ // active uniforms are `activeUniforms`, and returns the messages the guard emitted.
38
+ function bindWithGuard(shaderKey: string, activeUniforms: readonly { name: string; type: number }[]): string[] {
39
+ const { state } = makeGlScene3DState(makeFakeGl2({ activeUniforms }));
40
+ registerGlCustomMaterialShader(state, shaderKey, SOURCE);
41
+ enableGlScene3DCustomShaderGuards(state);
42
+
43
+ const sink = createMemoryLogSink(8);
44
+ addLogSink(sink.sink);
45
+ try {
46
+ customShaderGlMeshMaterialRenderer.bind(state, createCustomShaderMaterial({ shaderKey }), NO_LIGHTS, makeCamera());
47
+ customShaderGlMeshMaterialRenderer.bind(state, createCustomShaderMaterial({ shaderKey }), NO_LIGHTS, makeCamera());
48
+ return getMemoryLogSinkEntries(sink).map((e) => String((e.data as Record<string, unknown>).message));
49
+ } finally {
50
+ removeLogSink(sink.sink);
51
+ }
52
+ }
53
+
54
+ describe('areGlScene3DCustomShaderGuardsEnabled', () => {
55
+ it('reports false until guards are installed, then true', () => {
56
+ const { state } = makeGlScene3DState();
57
+ expect(areGlScene3DCustomShaderGuardsEnabled(state)).toBe(false);
58
+ enableGlScene3DCustomShaderGuards(state);
59
+ expect(areGlScene3DCustomShaderGuardsEnabled(state)).toBe(true);
60
+ });
61
+ });
62
+
63
+ describe('enableGlScene3DCustomShaderGuards', () => {
64
+ it('warns once, naming the fix, when u_normalMatrix is declared mat4 instead of mat3', () => {
65
+ const messages = bindWithGuard('badNormalMatrix', [{ name: 'u_normalMatrix', type: GL_FLOAT_MAT4 }]);
66
+ expect(messages).toHaveLength(1);
67
+ expect(messages[0]).toContain('u_normalMatrix');
68
+ expect(messages[0]).toContain('declares u_normalMatrix as mat4');
69
+ expect(messages[0]).toContain("Declare 'mat3 u_normalMatrix'");
70
+ });
71
+
72
+ it('stays silent when the built-in uniforms are declared with the expected types', () => {
73
+ const messages = bindWithGuard('okNormalMatrix', [
74
+ { name: 'u_normalMatrix', type: GL_FLOAT_MAT3 },
75
+ { name: 'u_model', type: GL_FLOAT_MAT4 },
76
+ { name: 'u_viewProjection', type: GL_FLOAT_MAT4 },
77
+ ]);
78
+ expect(messages).toHaveLength(0);
79
+ });
80
+
81
+ it('ignores non-built-in uniforms so a custom mat4 uniform is not flagged', () => {
82
+ const messages = bindWithGuard('customUniform', [{ name: 'u_userTransform', type: GL_FLOAT_MAT4 }]);
83
+ expect(messages).toHaveLength(0);
84
+ });
85
+ });
@@ -0,0 +1,98 @@
1
+ import { createCamera3D, setCamera3DViewMatrix4FromLookAt } from '@flighthq/camera';
2
+ import { createVector3 } from '@flighthq/geometry';
3
+ import { createAmbientLight, createDirectionalLight } from '@flighthq/lighting';
4
+ import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log';
5
+ import { CANONICAL_SKINNED_MESH_GEOMETRY_LAYOUT, createMeshGeometry, createBoxMeshGeometry } from '@flighthq/mesh';
6
+ import { addNodeChild } from '@flighthq/node';
7
+ import { createMesh, createNode3D, prepareScene3DMorph, Node3DKind } from '@flighthq/scene3d';
8
+ import { createSkeleton3D, prepareScene3DSkinning } from '@flighthq/skeleton3d';
9
+ import type { Camera3D, MeshMorph, Scene3DLightsLike } from '@flighthq/types';
10
+
11
+ import { drawGlScene3D } from './drawGlScene3D';
12
+ import { areGlScene3DDeformGuardsEnabled, enableGlScene3DDeformGuards } from './enableGlScene3DDeformGuards';
13
+ import { makeGlScene3DState } from './glScene3DTestHelper';
14
+
15
+ function makeCamera(): Camera3D {
16
+ const camera = createCamera3D({
17
+ far: 100,
18
+ near: 0.1,
19
+ projection: { aspect: 1, fovY: Math.PI / 3, kind: 'perspective' },
20
+ });
21
+ setCamera3DViewMatrix4FromLookAt(camera, { x: 0, y: 0, z: 5 }, { x: 0, y: 0, z: 0 }, { x: 0, y: 1, z: 0 });
22
+ return camera;
23
+ }
24
+
25
+ const LIGHTS: Scene3DLightsLike = {
26
+ ambient: createAmbientLight({ color: 0xffffffff, intensity: 0.2 }),
27
+ directional: createDirectionalLight({ color: 0xffffffff, direction: createVector3(0, -1, -1), intensity: 1 }),
28
+ };
29
+
30
+ function morphMesh() {
31
+ // A box geometry carrying a morph; drawn with no material so the guard (top of the mesh loop) runs
32
+ // without exercising the material draw path.
33
+ const mesh = createMesh(createBoxMeshGeometry(), []);
34
+ const morph: MeshMorph = {
35
+ targets: [{ normalDeltas: null, positionDeltas: new Float32Array(72), tangentDeltas: null }],
36
+ weights: new Float32Array([1]),
37
+ };
38
+ mesh.morph = morph;
39
+ return mesh;
40
+ }
41
+
42
+ function skinnedMesh() {
43
+ const geometry = createMeshGeometry({
44
+ layout: CANONICAL_SKINNED_MESH_GEOMETRY_LAYOUT,
45
+ vertices: new Float32Array(20),
46
+ });
47
+ const mesh = createMesh(geometry, []);
48
+ mesh.skin = { skeleton: createSkeleton3D([createNode3D()]) };
49
+ return mesh;
50
+ }
51
+
52
+ function drawWithGuard(scene: ReturnType<typeof createNode3D>): number {
53
+ const { state } = makeGlScene3DState();
54
+ enableGlScene3DDeformGuards(state);
55
+ const sink = createMemoryLogSink(8);
56
+ addLogSink(sink.sink);
57
+ try {
58
+ drawGlScene3D(state, scene, makeCamera(), LIGHTS);
59
+ return getMemoryLogSinkEntries(sink).length;
60
+ } finally {
61
+ removeLogSink(sink.sink);
62
+ }
63
+ }
64
+
65
+ describe('areGlScene3DDeformGuardsEnabled', () => {
66
+ it('reports false until guards are installed, then true', () => {
67
+ const { state } = makeGlScene3DState();
68
+ expect(areGlScene3DDeformGuardsEnabled(state)).toBe(false);
69
+ enableGlScene3DDeformGuards(state);
70
+ expect(areGlScene3DDeformGuardsEnabled(state)).toBe(true);
71
+ });
72
+ });
73
+
74
+ describe('enableGlScene3DDeformGuards', () => {
75
+ // Runs FIRST, before the fire cases below consume the logOnce keys: a prepared mesh must not reach the
76
+ // warn branch at all, so this proves silence with fresh keys rather than relying on dedup to hide a bug.
77
+ it('stays silent once the deform passes have run', () => {
78
+ const scene = createNode3D(Node3DKind);
79
+ addNodeChild(scene, morphMesh());
80
+ addNodeChild(scene, skinnedMesh());
81
+ // Ready both deformers, exactly as an app would before prepareScene3DRender.
82
+ prepareScene3DMorph(scene);
83
+ prepareScene3DSkinning(scene);
84
+ expect(drawWithGuard(scene)).toBe(0);
85
+ });
86
+
87
+ it('warns when a morphed mesh is drawn without prepareScene3DMorph', () => {
88
+ const scene = createNode3D(Node3DKind);
89
+ addNodeChild(scene, morphMesh());
90
+ expect(drawWithGuard(scene)).toBe(1);
91
+ });
92
+
93
+ it('warns when a GPU-skinned mesh is drawn without prepareScene3DSkinning', () => {
94
+ const scene = createNode3D(Node3DKind);
95
+ addNodeChild(scene, skinnedMesh());
96
+ expect(drawWithGuard(scene)).toBe(1);
97
+ });
98
+ });