@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.
- package/dist/anisotropyPbrGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/anisotropyPbrGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/anisotropyPbrGlMeshMaterialRenderer.js +45 -0
- package/dist/anisotropyPbrGlMeshMaterialRenderer.js.map +1 -0
- package/dist/blinnPhongGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/blinnPhongGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/blinnPhongGlMeshMaterialRenderer.js +102 -0
- package/dist/blinnPhongGlMeshMaterialRenderer.js.map +1 -0
- package/dist/clearcoatPbrGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/clearcoatPbrGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/clearcoatPbrGlMeshMaterialRenderer.js +45 -0
- package/dist/clearcoatPbrGlMeshMaterialRenderer.js.map +1 -0
- package/dist/customShaderGlMeshMaterialRenderer.d.ts +6 -0
- package/dist/customShaderGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/customShaderGlMeshMaterialRenderer.js +136 -0
- package/dist/customShaderGlMeshMaterialRenderer.js.map +1 -0
- package/dist/depthGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/depthGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/depthGlMeshMaterialRenderer.js +36 -0
- package/dist/depthGlMeshMaterialRenderer.js.map +1 -0
- package/dist/drawGlScene3D.d.ts +3 -0
- package/dist/drawGlScene3D.d.ts.map +1 -0
- package/dist/drawGlScene3D.js +251 -0
- package/dist/drawGlScene3D.js.map +1 -0
- package/dist/emissiveGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/emissiveGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/emissiveGlMeshMaterialRenderer.js +49 -0
- package/dist/emissiveGlMeshMaterialRenderer.js.map +1 -0
- package/dist/enableGlScene3DColorSpaceGuards.d.ts +4 -0
- package/dist/enableGlScene3DColorSpaceGuards.d.ts.map +1 -0
- package/dist/enableGlScene3DColorSpaceGuards.js +22 -0
- package/dist/enableGlScene3DColorSpaceGuards.js.map +1 -0
- package/dist/enableGlScene3DCustomShaderGuards.d.ts +4 -0
- package/dist/enableGlScene3DCustomShaderGuards.d.ts.map +1 -0
- package/dist/enableGlScene3DCustomShaderGuards.js +71 -0
- package/dist/enableGlScene3DCustomShaderGuards.js.map +1 -0
- package/dist/enableGlScene3DDeformGuards.d.ts +4 -0
- package/dist/enableGlScene3DDeformGuards.d.ts.map +1 -0
- package/dist/enableGlScene3DDeformGuards.js +42 -0
- package/dist/enableGlScene3DDeformGuards.js.map +1 -0
- package/dist/enableGlScene3DForwardLightSelectionGuards.d.ts +4 -0
- package/dist/enableGlScene3DForwardLightSelectionGuards.d.ts.map +1 -0
- package/dist/enableGlScene3DForwardLightSelectionGuards.js +21 -0
- package/dist/enableGlScene3DForwardLightSelectionGuards.js.map +1 -0
- package/dist/explainGlScene3DForwardLightSelection.d.ts +3 -0
- package/dist/explainGlScene3DForwardLightSelection.d.ts.map +1 -0
- package/dist/explainGlScene3DForwardLightSelection.js +19 -0
- package/dist/explainGlScene3DForwardLightSelection.js.map +1 -0
- package/dist/glClassicPrelude.d.ts +9 -0
- package/dist/glClassicPrelude.d.ts.map +1 -0
- package/dist/glClassicPrelude.js +288 -0
- package/dist/glClassicPrelude.js.map +1 -0
- package/dist/glDebugPrelude.d.ts +9 -0
- package/dist/glDebugPrelude.d.ts.map +1 -0
- package/dist/glDebugPrelude.js +143 -0
- package/dist/glDebugPrelude.js.map +1 -0
- package/dist/glEnvironmentCube.d.ts +5 -0
- package/dist/glEnvironmentCube.d.ts.map +1 -0
- package/dist/glEnvironmentCube.js +65 -0
- package/dist/glEnvironmentCube.js.map +1 -0
- package/dist/glEnvironmentIblBake.d.ts +4 -0
- package/dist/glEnvironmentIblBake.d.ts.map +1 -0
- package/dist/glEnvironmentIblBake.js +365 -0
- package/dist/glEnvironmentIblBake.js.map +1 -0
- package/dist/glEnvironmentSkybox.d.ts +3 -0
- package/dist/glEnvironmentSkybox.d.ts.map +1 -0
- package/dist/glEnvironmentSkybox.js +95 -0
- package/dist/glEnvironmentSkybox.js.map +1 -0
- package/dist/glLitProgram.d.ts +5 -0
- package/dist/glLitProgram.d.ts.map +1 -0
- package/dist/glLitProgram.js +211 -0
- package/dist/glLitProgram.js.map +1 -0
- package/dist/glMatcapPrelude.d.ts +8 -0
- package/dist/glMatcapPrelude.d.ts.map +1 -0
- package/dist/glMatcapPrelude.js +118 -0
- package/dist/glMatcapPrelude.js.map +1 -0
- package/dist/glMeshMaterialRegistry.d.ts +5 -0
- package/dist/glMeshMaterialRegistry.d.ts.map +1 -0
- package/dist/glMeshMaterialRegistry.js +29 -0
- package/dist/glMeshMaterialRegistry.js.map +1 -0
- package/dist/glMeshProgram.d.ts +14 -0
- package/dist/glMeshProgram.d.ts.map +1 -0
- package/dist/glMeshProgram.js +193 -0
- package/dist/glMeshProgram.js.map +1 -0
- package/dist/glMeshUpload.d.ts +4 -0
- package/dist/glMeshUpload.d.ts.map +1 -0
- package/dist/glMeshUpload.js +190 -0
- package/dist/glMeshUpload.js.map +1 -0
- package/dist/glParticleEmitter3D.d.ts +4 -0
- package/dist/glParticleEmitter3D.d.ts.map +1 -0
- package/dist/glParticleEmitter3D.js +342 -0
- package/dist/glParticleEmitter3D.js.map +1 -0
- package/dist/glPbrPrelude.d.ts +8 -0
- package/dist/glPbrPrelude.d.ts.map +1 -0
- package/dist/glPbrPrelude.js +569 -0
- package/dist/glPbrPrelude.js.map +1 -0
- package/dist/glPbrProgramCache.d.ts +4 -0
- package/dist/glPbrProgramCache.d.ts.map +1 -0
- package/dist/glPbrProgramCache.js +63 -0
- package/dist/glPbrProgramCache.js.map +1 -0
- package/dist/glPbrStandardBlock.d.ts +13 -0
- package/dist/glPbrStandardBlock.d.ts.map +1 -0
- package/dist/glPbrStandardBlock.js +107 -0
- package/dist/glPbrStandardBlock.js.map +1 -0
- package/dist/glScene3DRuntime.d.ts +5 -0
- package/dist/glScene3DRuntime.d.ts.map +1 -0
- package/dist/glScene3DRuntime.js +99 -0
- package/dist/glScene3DRuntime.js.map +1 -0
- package/dist/glScene3DTestHelper.d.ts +20 -0
- package/dist/glScene3DTestHelper.d.ts.map +1 -0
- package/dist/glScene3DTestHelper.js +178 -0
- package/dist/glScene3DTestHelper.js.map +1 -0
- package/dist/glScene3DTime.d.ts +4 -0
- package/dist/glScene3DTime.d.ts.map +1 -0
- package/dist/glScene3DTime.js +18 -0
- package/dist/glScene3DTime.js.map +1 -0
- package/dist/glShadedBuiltInModifiers.d.ts +11 -0
- package/dist/glShadedBuiltInModifiers.d.ts.map +1 -0
- package/dist/glShadedBuiltInModifiers.js +386 -0
- package/dist/glShadedBuiltInModifiers.js.map +1 -0
- package/dist/glShadedModifierSnippet.d.ts +4 -0
- package/dist/glShadedModifierSnippet.d.ts.map +1 -0
- package/dist/glShadedModifierSnippet.js +24 -0
- package/dist/glShadedModifierSnippet.js.map +1 -0
- package/dist/glShadedPrelude.d.ts +5 -0
- package/dist/glShadedPrelude.d.ts.map +1 -0
- package/dist/glShadedPrelude.js +393 -0
- package/dist/glShadedPrelude.js.map +1 -0
- package/dist/glShadowMap.d.ts +3 -0
- package/dist/glShadowMap.d.ts.map +1 -0
- package/dist/glShadowMap.js +150 -0
- package/dist/glShadowMap.js.map +1 -0
- package/dist/glToonPrelude.d.ts +7 -0
- package/dist/glToonPrelude.d.ts.map +1 -0
- package/dist/glToonPrelude.js +172 -0
- package/dist/glToonPrelude.js.map +1 -0
- package/dist/glUnlitPrelude.d.ts +9 -0
- package/dist/glUnlitPrelude.d.ts.map +1 -0
- package/dist/glUnlitPrelude.js +160 -0
- package/dist/glUnlitPrelude.js.map +1 -0
- package/dist/glWireframePrelude.d.ts +6 -0
- package/dist/glWireframePrelude.d.ts.map +1 -0
- package/dist/glWireframePrelude.js +56 -0
- package/dist/glWireframePrelude.js.map +1 -0
- package/dist/glWireframeUpload.d.ts +4 -0
- package/dist/glWireframeUpload.d.ts.map +1 -0
- package/dist/glWireframeUpload.js +85 -0
- package/dist/glWireframeUpload.js.map +1 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/iridescencePbrGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/iridescencePbrGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/iridescencePbrGlMeshMaterialRenderer.js +54 -0
- package/dist/iridescencePbrGlMeshMaterialRenderer.js.map +1 -0
- package/dist/lambertGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/lambertGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/lambertGlMeshMaterialRenderer.js +70 -0
- package/dist/lambertGlMeshMaterialRenderer.js.map +1 -0
- package/dist/matcapGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/matcapGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/matcapGlMeshMaterialRenderer.js +49 -0
- package/dist/matcapGlMeshMaterialRenderer.js.map +1 -0
- package/dist/normalGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/normalGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/normalGlMeshMaterialRenderer.js +39 -0
- package/dist/normalGlMeshMaterialRenderer.js.map +1 -0
- package/dist/phongGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/phongGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/phongGlMeshMaterialRenderer.js +90 -0
- package/dist/phongGlMeshMaterialRenderer.js.map +1 -0
- package/dist/prepareGlScene3DForwardLights.d.ts +3 -0
- package/dist/prepareGlScene3DForwardLights.d.ts.map +1 -0
- package/dist/prepareGlScene3DForwardLights.js +117 -0
- package/dist/prepareGlScene3DForwardLights.js.map +1 -0
- package/dist/presentGlScene3D.d.ts +3 -0
- package/dist/presentGlScene3D.d.ts.map +1 -0
- package/dist/presentGlScene3D.js +21 -0
- package/dist/presentGlScene3D.js.map +1 -0
- package/dist/registerStandardPbrGlMaterial.d.ts +3 -0
- package/dist/registerStandardPbrGlMaterial.d.ts.map +1 -0
- package/dist/registerStandardPbrGlMaterial.js +11 -0
- package/dist/registerStandardPbrGlMaterial.js.map +1 -0
- package/dist/shadedGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/shadedGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/shadedGlMeshMaterialRenderer.js +135 -0
- package/dist/shadedGlMeshMaterialRenderer.js.map +1 -0
- package/dist/sheenPbrGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/sheenPbrGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/sheenPbrGlMeshMaterialRenderer.js +54 -0
- package/dist/sheenPbrGlMeshMaterialRenderer.js.map +1 -0
- package/dist/specularGlossinessPbrGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/specularGlossinessPbrGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/specularGlossinessPbrGlMeshMaterialRenderer.js +121 -0
- package/dist/specularGlossinessPbrGlMeshMaterialRenderer.js.map +1 -0
- package/dist/specularPbrGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/specularPbrGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/specularPbrGlMeshMaterialRenderer.js +55 -0
- package/dist/specularPbrGlMeshMaterialRenderer.js.map +1 -0
- package/dist/standardPbrGlMeshMaterialRenderer.d.ts +3 -0
- package/dist/standardPbrGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/standardPbrGlMeshMaterialRenderer.js +35 -0
- package/dist/standardPbrGlMeshMaterialRenderer.js.map +1 -0
- package/dist/subsurfacePbrGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/subsurfacePbrGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/subsurfacePbrGlMeshMaterialRenderer.js +57 -0
- package/dist/subsurfacePbrGlMeshMaterialRenderer.js.map +1 -0
- package/dist/toonGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/toonGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/toonGlMeshMaterialRenderer.js +81 -0
- package/dist/toonGlMeshMaterialRenderer.js.map +1 -0
- package/dist/transmissionVolumePbrGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/transmissionVolumePbrGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/transmissionVolumePbrGlMeshMaterialRenderer.js +62 -0
- package/dist/transmissionVolumePbrGlMeshMaterialRenderer.js.map +1 -0
- package/dist/unlitGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/unlitGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/unlitGlMeshMaterialRenderer.js +61 -0
- package/dist/unlitGlMeshMaterialRenderer.js.map +1 -0
- package/dist/vertexColorGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/vertexColorGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/vertexColorGlMeshMaterialRenderer.js +54 -0
- package/dist/vertexColorGlMeshMaterialRenderer.js.map +1 -0
- package/dist/wireframeGlMeshMaterialRenderer.d.ts +4 -0
- package/dist/wireframeGlMeshMaterialRenderer.d.ts.map +1 -0
- package/dist/wireframeGlMeshMaterialRenderer.js +50 -0
- package/dist/wireframeGlMeshMaterialRenderer.js.map +1 -0
- package/package.json +58 -0
- package/src/anisotropyPbrGlMeshMaterialRenderer.test.ts +86 -0
- package/src/blinnPhongGlMeshMaterialRenderer.test.ts +94 -0
- package/src/clearcoatPbrGlMeshMaterialRenderer.test.ts +81 -0
- package/src/customShaderGlMeshMaterialRenderer.test.ts +169 -0
- package/src/depthGlMeshMaterialRenderer.test.ts +69 -0
- package/src/drawGlScene3D.test.ts +304 -0
- package/src/emissiveGlMeshMaterialRenderer.test.ts +60 -0
- package/src/enableGlScene3DColorSpaceGuards.test.ts +63 -0
- package/src/enableGlScene3DCustomShaderGuards.test.ts +85 -0
- package/src/enableGlScene3DDeformGuards.test.ts +98 -0
- package/src/enableGlScene3DForwardLightSelectionGuards.test.ts +84 -0
- package/src/explainGlScene3DForwardLightSelection.test.ts +32 -0
- package/src/glClassicPrelude.test.ts +214 -0
- package/src/glDebugPrelude.test.ts +94 -0
- package/src/glEnvironmentCube.test.ts +71 -0
- package/src/glEnvironmentIblBake.test.ts +31 -0
- package/src/glEnvironmentSkybox.test.ts +24 -0
- package/src/glLitProgram.test.ts +181 -0
- package/src/glMatcapPrelude.test.ts +74 -0
- package/src/glMeshMaterialRegistry.test.ts +52 -0
- package/src/glMeshProgram.test.ts +406 -0
- package/src/glMeshUpload.test.ts +311 -0
- package/src/glParticleEmitter3D.test.ts +280 -0
- package/src/glPbrPrelude.test.ts +178 -0
- package/src/glPbrProgramCache.test.ts +116 -0
- package/src/glPbrStandardBlock.test.ts +85 -0
- package/src/glScene3DRuntime.test.ts +108 -0
- package/src/glScene3DTime.test.ts +31 -0
- package/src/glShadedBuiltInModifiers.test.ts +377 -0
- package/src/glShadedModifierSnippet.test.ts +46 -0
- package/src/glShadedPrelude.test.ts +177 -0
- package/src/glShadowMap.test.ts +219 -0
- package/src/glToonPrelude.test.ts +118 -0
- package/src/glUnlitPrelude.test.ts +156 -0
- package/src/glWireframePrelude.test.ts +46 -0
- package/src/glWireframeUpload.test.ts +50 -0
- package/src/iridescencePbrGlMeshMaterialRenderer.test.ts +86 -0
- package/src/lambertGlMeshMaterialRenderer.test.ts +92 -0
- package/src/matcapGlMeshMaterialRenderer.test.ts +70 -0
- package/src/normalGlMeshMaterialRenderer.test.ts +69 -0
- package/src/phongGlMeshMaterialRenderer.test.ts +94 -0
- package/src/prepareGlScene3DForwardLights.test.ts +106 -0
- package/src/presentGlScene3D.test.ts +98 -0
- package/src/registerStandardPbrGlMaterial.test.ts +14 -0
- package/src/shadedGlMeshMaterialRenderer.test.ts +106 -0
- package/src/sheenPbrGlMeshMaterialRenderer.test.ts +83 -0
- package/src/specularGlossinessPbrGlMeshMaterialRenderer.test.ts +96 -0
- package/src/specularPbrGlMeshMaterialRenderer.test.ts +83 -0
- package/src/standardPbrGlMeshMaterialRenderer.test.ts +88 -0
- package/src/subsurfacePbrGlMeshMaterialRenderer.test.ts +86 -0
- package/src/toonGlMeshMaterialRenderer.test.ts +92 -0
- package/src/transmissionVolumePbrGlMeshMaterialRenderer.test.ts +93 -0
- package/src/unlitGlMeshMaterialRenderer.test.ts +88 -0
- package/src/vertexColorGlMeshMaterialRenderer.test.ts +60 -0
- package/src/wireframeGlMeshMaterialRenderer.test.ts +69 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAnimatedNormalModifier,
|
|
3
|
+
createDissolveModifier,
|
|
4
|
+
createEmissiveModifier,
|
|
5
|
+
createEnvReflectModifier,
|
|
6
|
+
createFogModifier,
|
|
7
|
+
createRimModifier,
|
|
8
|
+
createToonModifier,
|
|
9
|
+
createVertexDisplaceModifier,
|
|
10
|
+
} from '@flighthq/shading';
|
|
11
|
+
import type { Texture, GlModifierBindContext } from '@flighthq/types';
|
|
12
|
+
import {
|
|
13
|
+
AnimatedNormalModifierKind,
|
|
14
|
+
DissolveModifierKind,
|
|
15
|
+
EmissiveModifierFacing,
|
|
16
|
+
EmissiveModifierKind,
|
|
17
|
+
EnvReflectModifierKind,
|
|
18
|
+
FogModifierKind,
|
|
19
|
+
FogModifierMode,
|
|
20
|
+
RimModifierKind,
|
|
21
|
+
ToonModifierKind,
|
|
22
|
+
VertexDisplaceModifierKind,
|
|
23
|
+
VertexDisplaceModifierSource,
|
|
24
|
+
} from '@flighthq/types';
|
|
25
|
+
|
|
26
|
+
import type { FakeGl2 } from './glScene3DTestHelper';
|
|
27
|
+
import { makeGlScene3DState } from './glScene3DTestHelper';
|
|
28
|
+
import {
|
|
29
|
+
animatedNormalGlModifierSnippet,
|
|
30
|
+
dissolveGlModifierSnippet,
|
|
31
|
+
emissiveGlModifierSnippet,
|
|
32
|
+
envReflectGlModifierSnippet,
|
|
33
|
+
fogGlModifierSnippet,
|
|
34
|
+
registerBuiltInGlModifierSnippets,
|
|
35
|
+
rimGlModifierSnippet,
|
|
36
|
+
toonGlModifierSnippet,
|
|
37
|
+
vertexDisplaceGlModifierSnippet,
|
|
38
|
+
} from './glShadedBuiltInModifiers';
|
|
39
|
+
import { resolveGlModifierSnippet } from './glShadedModifierSnippet';
|
|
40
|
+
|
|
41
|
+
// A Texture whose image is unloaded — the snippet binds the sampler unit without an upload.
|
|
42
|
+
const UNLOADED_TEXTURE = { image: null } as unknown as Texture;
|
|
43
|
+
|
|
44
|
+
function makeContext(state: ReturnType<typeof makeGlScene3DState>['state'], index = 0): GlModifierBindContext {
|
|
45
|
+
let unit = 3;
|
|
46
|
+
return {
|
|
47
|
+
acquireModifierTextureUnit: () => unit++,
|
|
48
|
+
index,
|
|
49
|
+
program: {} as WebGLProgram,
|
|
50
|
+
state,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function countCalls(gl: FakeGl2, name: string): number {
|
|
55
|
+
return gl.calls.filter((c) => c.name === name).length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe('animatedNormalGlModifierSnippet', () => {
|
|
59
|
+
it('reports the disabled/single/dual define signature from map structure', () => {
|
|
60
|
+
const disabled = createAnimatedNormalModifier({ map: null, scroll: { x: 0, y: 0 } });
|
|
61
|
+
const single = createAnimatedNormalModifier({ map: UNLOADED_TEXTURE, scroll: { x: 0.1, y: 0 } });
|
|
62
|
+
const dual = createAnimatedNormalModifier({
|
|
63
|
+
map: UNLOADED_TEXTURE,
|
|
64
|
+
scroll: { x: 0.1, y: 0 },
|
|
65
|
+
secondaryMap: UNLOADED_TEXTURE,
|
|
66
|
+
secondaryScroll: { x: -0.05, y: 0 },
|
|
67
|
+
});
|
|
68
|
+
expect(animatedNormalGlModifierSnippet.getDefineSignature?.(disabled)).toBe('0');
|
|
69
|
+
expect(animatedNormalGlModifierSnippet.getDefineSignature?.(single)).toBe('1');
|
|
70
|
+
expect(animatedNormalGlModifierSnippet.getDefineSignature?.(dual)).toBe('2');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('emits u_time-scrolled sampling into the normal for a mapped modifier', () => {
|
|
74
|
+
const modifier = createAnimatedNormalModifier({ map: UNLOADED_TEXTURE, scroll: { x: 0.1, y: 0 } });
|
|
75
|
+
const contribution = animatedNormalGlModifierSnippet.contribution(modifier, 0);
|
|
76
|
+
expect(contribution).toContain('u_animNormalScroll_0 * u_time');
|
|
77
|
+
expect(contribution).toContain('normal = normalize(tbn * animNormal);');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('emits nothing for a disabled (null-map) modifier', () => {
|
|
81
|
+
const modifier = createAnimatedNormalModifier({ map: null, scroll: { x: 0, y: 0 } });
|
|
82
|
+
expect(animatedNormalGlModifierSnippet.declarations?.(modifier, 0)).toBe('');
|
|
83
|
+
expect(animatedNormalGlModifierSnippet.contribution(modifier, 0)).toBe('');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('bind uploads scroll + strength and binds the sampler unit', () => {
|
|
87
|
+
const { state, gl } = makeGlScene3DState();
|
|
88
|
+
const modifier = createAnimatedNormalModifier({ map: UNLOADED_TEXTURE, scroll: { x: 0.2, y: 0.3 }, strength: 2 });
|
|
89
|
+
animatedNormalGlModifierSnippet.bind?.(modifier, makeContext(state));
|
|
90
|
+
expect(countCalls(gl, 'uniform2f')).toBe(1);
|
|
91
|
+
expect(countCalls(gl, 'uniform1f')).toBe(1);
|
|
92
|
+
expect(countCalls(gl, 'uniform1i')).toBe(1);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('bind is a no-op for a disabled modifier', () => {
|
|
96
|
+
const { state, gl } = makeGlScene3DState();
|
|
97
|
+
const modifier = createAnimatedNormalModifier({ map: null, scroll: { x: 0, y: 0 } });
|
|
98
|
+
animatedNormalGlModifierSnippet.bind?.(modifier, makeContext(state));
|
|
99
|
+
expect(countCalls(gl, 'uniform2f')).toBe(0);
|
|
100
|
+
expect(countCalls(gl, 'uniform1i')).toBe(0);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('drops the sampler (no bind) when the texture-unit allocator is exhausted', () => {
|
|
104
|
+
const { state, gl } = makeGlScene3DState();
|
|
105
|
+
const modifier = createAnimatedNormalModifier({ map: UNLOADED_TEXTURE, scroll: { x: 0.2, y: 0.3 } });
|
|
106
|
+
const exhausted: GlModifierBindContext = {
|
|
107
|
+
acquireModifierTextureUnit: () => -1,
|
|
108
|
+
index: 0,
|
|
109
|
+
program: {} as WebGLProgram,
|
|
110
|
+
state,
|
|
111
|
+
};
|
|
112
|
+
animatedNormalGlModifierSnippet.bind?.(modifier, exhausted);
|
|
113
|
+
expect(countCalls(gl, 'uniform1i')).toBe(0);
|
|
114
|
+
expect(countCalls(gl, 'activeTexture')).toBe(0);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('dissolveGlModifierSnippet', () => {
|
|
119
|
+
it('reports the procedural/mapped define signature structurally', () => {
|
|
120
|
+
expect(dissolveGlModifierSnippet.getDefineSignature?.(createDissolveModifier({ threshold: 0.5 }))).toBe('');
|
|
121
|
+
expect(
|
|
122
|
+
dissolveGlModifierSnippet.getDefineSignature?.(createDissolveModifier({ threshold: 0.5, map: UNLOADED_TEXTURE })),
|
|
123
|
+
).toBe('m');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('discards below threshold and tints the burn edge into the radiance', () => {
|
|
127
|
+
const contribution = dissolveGlModifierSnippet.contribution(createDissolveModifier({ threshold: 0.4 }), 0);
|
|
128
|
+
expect(contribution).toContain('discard;');
|
|
129
|
+
expect(contribution).toContain('radiance = mix(radiance, u_dissolveEdgeColor_0, dissolveEdge);');
|
|
130
|
+
expect(contribution).toContain('shadedValueNoise');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('samples the noise map instead of procedural noise when a map is set', () => {
|
|
134
|
+
const contribution = dissolveGlModifierSnippet.contribution(
|
|
135
|
+
createDissolveModifier({ threshold: 0.4, map: UNLOADED_TEXTURE }),
|
|
136
|
+
1,
|
|
137
|
+
);
|
|
138
|
+
expect(contribution).toContain('texture(u_dissolveMap_1, v_uv0).r');
|
|
139
|
+
expect(contribution).not.toContain('shadedValueNoise');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('bind uploads threshold/edge and the noise scale for the procedural variant', () => {
|
|
143
|
+
const { state, gl } = makeGlScene3DState();
|
|
144
|
+
dissolveGlModifierSnippet.bind?.(createDissolveModifier({ threshold: 0.5 }), makeContext(state));
|
|
145
|
+
// threshold + edgeWidth + scale.
|
|
146
|
+
expect(countCalls(gl, 'uniform1f')).toBe(3);
|
|
147
|
+
expect(countCalls(gl, 'uniform3f')).toBe(1);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('emissiveGlModifierSnippet', () => {
|
|
152
|
+
it('reports the mask/gate define signature structurally', () => {
|
|
153
|
+
expect(emissiveGlModifierSnippet.getDefineSignature?.(createEmissiveModifier({ color: 0xffffffff }))).toBe('');
|
|
154
|
+
expect(
|
|
155
|
+
emissiveGlModifierSnippet.getDefineSignature?.(
|
|
156
|
+
createEmissiveModifier({ color: 0xffffffff, facing: EmissiveModifierFacing.AwayFromLight }),
|
|
157
|
+
),
|
|
158
|
+
).toBe('g');
|
|
159
|
+
expect(
|
|
160
|
+
emissiveGlModifierSnippet.getDefineSignature?.(
|
|
161
|
+
createEmissiveModifier({
|
|
162
|
+
color: 0xffffffff,
|
|
163
|
+
facing: EmissiveModifierFacing.AwayFromLight,
|
|
164
|
+
mask: UNLOADED_TEXTURE,
|
|
165
|
+
}),
|
|
166
|
+
),
|
|
167
|
+
).toBe('mg');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('gates the contribution by facing only when gated', () => {
|
|
171
|
+
const plain = emissiveGlModifierSnippet.contribution(createEmissiveModifier({ color: 0xffffffff }), 0);
|
|
172
|
+
expect(plain).not.toContain('u_emissiveFacingSign_0');
|
|
173
|
+
const gated = emissiveGlModifierSnippet.contribution(
|
|
174
|
+
createEmissiveModifier({ color: 0xffffffff, facing: EmissiveModifierFacing.AwayFromLight }),
|
|
175
|
+
0,
|
|
176
|
+
);
|
|
177
|
+
expect(gated).toContain('u_emissiveFacingSign_0');
|
|
178
|
+
expect(gated).toContain('emissive += emissiveTerm;');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('bind uploads color + strength, plus the facing sign when gated', () => {
|
|
182
|
+
const { state, gl } = makeGlScene3DState();
|
|
183
|
+
emissiveGlModifierSnippet.bind?.(createEmissiveModifier({ color: 0xffcc88ff }), makeContext(state));
|
|
184
|
+
expect(countCalls(gl, 'uniform3f')).toBe(1);
|
|
185
|
+
const plainFloats = countCalls(gl, 'uniform1f');
|
|
186
|
+
expect(plainFloats).toBe(1);
|
|
187
|
+
|
|
188
|
+
const gated = makeGlScene3DState();
|
|
189
|
+
emissiveGlModifierSnippet.bind?.(
|
|
190
|
+
createEmissiveModifier({ color: 0xffcc88ff, facing: EmissiveModifierFacing.AwayFromLight, facingSoftness: 0.1 }),
|
|
191
|
+
makeContext(gated.state),
|
|
192
|
+
);
|
|
193
|
+
// strength + facing sign + facing softness.
|
|
194
|
+
expect(countCalls(gated.gl, 'uniform1f')).toBe(3);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('envReflectGlModifierSnippet', () => {
|
|
199
|
+
it('has no define signature (single program shape)', () => {
|
|
200
|
+
expect(envReflectGlModifierSnippet.getDefineSignature).toBeUndefined();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('reflects the view vector and samples the shared prefiltered environment cube', () => {
|
|
204
|
+
const contribution = envReflectGlModifierSnippet.contribution(createEnvReflectModifier(), 0);
|
|
205
|
+
expect(contribution).toContain('reflect(-viewDir, normal)');
|
|
206
|
+
expect(contribution).toContain('textureLod(u_iblPrefiltered, envReflectDir');
|
|
207
|
+
expect(contribution).toContain('radiance +=');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('declares the shared IBL environment samplers so two instances can be deduped', () => {
|
|
211
|
+
const declarations = envReflectGlModifierSnippet.declarations?.(createEnvReflectModifier(), 0) ?? '';
|
|
212
|
+
expect(declarations).toContain('uniform samplerCube u_iblPrefiltered;');
|
|
213
|
+
expect(declarations).toContain('uniform float u_iblEnabled;');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('bind uploads the tint and the three reflection scalars', () => {
|
|
217
|
+
const { state, gl } = makeGlScene3DState();
|
|
218
|
+
envReflectGlModifierSnippet.bind?.(createEnvReflectModifier(), makeContext(state));
|
|
219
|
+
expect(countCalls(gl, 'uniform3f')).toBe(1);
|
|
220
|
+
// intensity + fresnel + roughness.
|
|
221
|
+
expect(countCalls(gl, 'uniform1f')).toBe(3);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('fogGlModifierSnippet', () => {
|
|
226
|
+
it('reports the mode define signature structurally', () => {
|
|
227
|
+
expect(fogGlModifierSnippet.getDefineSignature?.(createFogModifier({ color: 0xffffffff }))).toBe('l');
|
|
228
|
+
expect(
|
|
229
|
+
fogGlModifierSnippet.getDefineSignature?.(
|
|
230
|
+
createFogModifier({ color: 0xffffffff, mode: FogModifierMode.Exponential }),
|
|
231
|
+
),
|
|
232
|
+
).toBe('e');
|
|
233
|
+
expect(
|
|
234
|
+
fogGlModifierSnippet.getDefineSignature?.(
|
|
235
|
+
createFogModifier({ color: 0xffffffff, mode: FogModifierMode.Exponential2 }),
|
|
236
|
+
),
|
|
237
|
+
).toBe('x');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('emits a near/far ramp for linear and an exp falloff for exponential', () => {
|
|
241
|
+
const linear = fogGlModifierSnippet.contribution(createFogModifier({ color: 0xffffffff }), 0);
|
|
242
|
+
expect(linear).toContain('u_fogNear_0');
|
|
243
|
+
expect(linear).toContain('length(u_cameraPosition - v_worldPosition)');
|
|
244
|
+
const exp = fogGlModifierSnippet.contribution(
|
|
245
|
+
createFogModifier({ color: 0xffffffff, mode: FogModifierMode.Exponential }),
|
|
246
|
+
0,
|
|
247
|
+
);
|
|
248
|
+
expect(exp).toContain('exp(-u_fogDensity_0 * fogDist)');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('bind uploads the color and near/far for linear, density for exponential', () => {
|
|
252
|
+
const linear = makeGlScene3DState();
|
|
253
|
+
fogGlModifierSnippet.bind?.(createFogModifier({ color: 0xaabbccff }), makeContext(linear.state));
|
|
254
|
+
expect(countCalls(linear.gl, 'uniform3f')).toBe(1);
|
|
255
|
+
expect(countCalls(linear.gl, 'uniform1f')).toBe(2);
|
|
256
|
+
|
|
257
|
+
const exp = makeGlScene3DState();
|
|
258
|
+
fogGlModifierSnippet.bind?.(
|
|
259
|
+
createFogModifier({ color: 0xaabbccff, mode: FogModifierMode.Exponential2, density: 0.02 }),
|
|
260
|
+
makeContext(exp.state),
|
|
261
|
+
);
|
|
262
|
+
expect(countCalls(exp.gl, 'uniform1f')).toBe(1);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('registerBuiltInGlModifierSnippets', () => {
|
|
267
|
+
it('registers all eight built-in snippets on the state', () => {
|
|
268
|
+
const { state } = makeGlScene3DState();
|
|
269
|
+
registerBuiltInGlModifierSnippets(state);
|
|
270
|
+
expect(resolveGlModifierSnippet(state, AnimatedNormalModifierKind)).toBe(animatedNormalGlModifierSnippet);
|
|
271
|
+
expect(resolveGlModifierSnippet(state, EmissiveModifierKind)).toBe(emissiveGlModifierSnippet);
|
|
272
|
+
expect(resolveGlModifierSnippet(state, RimModifierKind)).toBe(rimGlModifierSnippet);
|
|
273
|
+
expect(resolveGlModifierSnippet(state, EnvReflectModifierKind)).toBe(envReflectGlModifierSnippet);
|
|
274
|
+
expect(resolveGlModifierSnippet(state, FogModifierKind)).toBe(fogGlModifierSnippet);
|
|
275
|
+
expect(resolveGlModifierSnippet(state, DissolveModifierKind)).toBe(dissolveGlModifierSnippet);
|
|
276
|
+
expect(resolveGlModifierSnippet(state, ToonModifierKind)).toBe(toonGlModifierSnippet);
|
|
277
|
+
expect(resolveGlModifierSnippet(state, VertexDisplaceModifierKind)).toBe(vertexDisplaceGlModifierSnippet);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe('rimGlModifierSnippet', () => {
|
|
282
|
+
it('has no define signature (single program shape)', () => {
|
|
283
|
+
expect(rimGlModifierSnippet.getDefineSignature).toBeUndefined();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('adds a view-dependent Fresnel term to the radiance', () => {
|
|
287
|
+
const contribution = rimGlModifierSnippet.contribution(createRimModifier({ color: 0x88ccffff }), 0);
|
|
288
|
+
expect(contribution).toContain('dot(normal, viewDir)');
|
|
289
|
+
expect(contribution).toContain('radiance += u_rimColor_0 * rim;');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('bind uploads the rim color and power/intensity/bias uniforms', () => {
|
|
293
|
+
const { state, gl } = makeGlScene3DState();
|
|
294
|
+
rimGlModifierSnippet.bind?.(createRimModifier({ color: 0x88ccffff }), makeContext(state));
|
|
295
|
+
expect(countCalls(gl, 'uniform3f')).toBe(1);
|
|
296
|
+
expect(countCalls(gl, 'uniform1f')).toBe(3);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe('toonGlModifierSnippet', () => {
|
|
301
|
+
it('has no define signature (single program shape)', () => {
|
|
302
|
+
expect(toonGlModifierSnippet.getDefineSignature).toBeUndefined();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('quantizes the radiance luminance into cel bands', () => {
|
|
306
|
+
const contribution = toonGlModifierSnippet.contribution(createToonModifier({ steps: 3 }), 0);
|
|
307
|
+
expect(contribution).toContain('u_toonSteps_0');
|
|
308
|
+
expect(contribution).toContain('radiance *=');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('bind uploads steps (floored at 2) and smoothness', () => {
|
|
312
|
+
const { state, gl } = makeGlScene3DState();
|
|
313
|
+
toonGlModifierSnippet.bind?.(createToonModifier({ steps: 1 }), makeContext(state));
|
|
314
|
+
expect(countCalls(gl, 'uniform1f')).toBe(2);
|
|
315
|
+
const stepsCall = gl.calls.find((c) => c.name === 'uniform1f');
|
|
316
|
+
expect(stepsCall?.args[1]).toBe(2);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('vertexDisplaceGlModifierSnippet', () => {
|
|
321
|
+
it('reports the source and fixed-axis define signature structurally', () => {
|
|
322
|
+
expect(
|
|
323
|
+
vertexDisplaceGlModifierSnippet.getDefineSignature?.(
|
|
324
|
+
createVertexDisplaceModifier({ source: VertexDisplaceModifierSource.Sine, amplitude: 1 }),
|
|
325
|
+
),
|
|
326
|
+
).toBe('s');
|
|
327
|
+
expect(
|
|
328
|
+
vertexDisplaceGlModifierSnippet.getDefineSignature?.(
|
|
329
|
+
createVertexDisplaceModifier({ source: VertexDisplaceModifierSource.HeightMap, amplitude: 1 }),
|
|
330
|
+
),
|
|
331
|
+
).toBe('h');
|
|
332
|
+
expect(
|
|
333
|
+
vertexDisplaceGlModifierSnippet.getDefineSignature?.(
|
|
334
|
+
createVertexDisplaceModifier({
|
|
335
|
+
source: VertexDisplaceModifierSource.Sine,
|
|
336
|
+
amplitude: 1,
|
|
337
|
+
axis: { x: 0, y: 1, z: 0 },
|
|
338
|
+
}),
|
|
339
|
+
),
|
|
340
|
+
).toBe('sa');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('displaces localPosition along the normal with a u_time-scrolled sine wave', () => {
|
|
344
|
+
const contribution = vertexDisplaceGlModifierSnippet.contribution(
|
|
345
|
+
createVertexDisplaceModifier({ source: VertexDisplaceModifierSource.Sine, amplitude: 0.2 }),
|
|
346
|
+
0,
|
|
347
|
+
);
|
|
348
|
+
expect(contribution).toContain('normalize(localNormal)');
|
|
349
|
+
expect(contribution).toContain('u_time * u_vDisplaceSpeed_0');
|
|
350
|
+
expect(contribution).toContain('localPosition.xyz += vDisplaceAxis * vDisplaceAmount;');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('reads the height map and a fixed axis when configured', () => {
|
|
354
|
+
const contribution = vertexDisplaceGlModifierSnippet.contribution(
|
|
355
|
+
createVertexDisplaceModifier({
|
|
356
|
+
source: VertexDisplaceModifierSource.HeightMap,
|
|
357
|
+
amplitude: 1,
|
|
358
|
+
axis: { x: 0, y: 1, z: 0 },
|
|
359
|
+
}),
|
|
360
|
+
2,
|
|
361
|
+
);
|
|
362
|
+
expect(contribution).toContain('texture(u_vDisplaceMap_2, vertexUv).r');
|
|
363
|
+
expect(contribution).toContain('normalize(u_vDisplaceAxis_2)');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('bind uploads amplitude plus the sine wave params for the procedural source', () => {
|
|
367
|
+
const { state, gl } = makeGlScene3DState();
|
|
368
|
+
vertexDisplaceGlModifierSnippet.bind?.(
|
|
369
|
+
createVertexDisplaceModifier({ source: VertexDisplaceModifierSource.Sine, amplitude: 0.3 }),
|
|
370
|
+
makeContext(state),
|
|
371
|
+
);
|
|
372
|
+
// amplitude + frequency + speed.
|
|
373
|
+
expect(countCalls(gl, 'uniform1f')).toBe(3);
|
|
374
|
+
// direction.
|
|
375
|
+
expect(countCalls(gl, 'uniform3f')).toBe(1);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ModifierSlot } from '@flighthq/types';
|
|
2
|
+
import type { GlModifierSnippet } from '@flighthq/types';
|
|
3
|
+
|
|
4
|
+
import { getGlScene3DRuntime } from './glScene3DRuntime';
|
|
5
|
+
import { makeGlScene3DState } from './glScene3DTestHelper';
|
|
6
|
+
import { registerGlModifierSnippet, resolveGlModifierSnippet } from './glShadedModifierSnippet';
|
|
7
|
+
|
|
8
|
+
function makeSnippet(overrides?: Partial<GlModifierSnippet>): GlModifierSnippet {
|
|
9
|
+
return {
|
|
10
|
+
kind: 'acme.Test',
|
|
11
|
+
slot: ModifierSlot.Effect,
|
|
12
|
+
contribution: () => '// noop',
|
|
13
|
+
...overrides,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('registerGlModifierSnippet', () => {
|
|
18
|
+
it('leaves the modifier registry unallocated until the first registration', () => {
|
|
19
|
+
const { state } = makeGlScene3DState();
|
|
20
|
+
expect(getGlScene3DRuntime(state).modifierSnippetRegistry).toBeNull();
|
|
21
|
+
registerGlModifierSnippet(state, makeSnippet());
|
|
22
|
+
expect(getGlScene3DRuntime(state).modifierSnippetRegistry).not.toBeNull();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('stores a snippet resolvable by its kind', () => {
|
|
26
|
+
const { state } = makeGlScene3DState();
|
|
27
|
+
const snippet = makeSnippet();
|
|
28
|
+
registerGlModifierSnippet(state, snippet);
|
|
29
|
+
expect(resolveGlModifierSnippet(state, 'acme.Test')).toBe(snippet);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('is last-write-wins for the same kind', () => {
|
|
33
|
+
const { state } = makeGlScene3DState();
|
|
34
|
+
registerGlModifierSnippet(state, makeSnippet());
|
|
35
|
+
const override = makeSnippet({ contribution: () => '// override' });
|
|
36
|
+
registerGlModifierSnippet(state, override);
|
|
37
|
+
expect(resolveGlModifierSnippet(state, 'acme.Test')).toBe(override);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('resolveGlModifierSnippet', () => {
|
|
42
|
+
it('returns null for an unregistered kind', () => {
|
|
43
|
+
const { state } = makeGlScene3DState();
|
|
44
|
+
expect(resolveGlModifierSnippet(state, 'acme.Missing')).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEmissiveModifier,
|
|
3
|
+
createEnvReflectModifier,
|
|
4
|
+
createModifierRegistry,
|
|
5
|
+
createVertexDisplaceModifier,
|
|
6
|
+
registerModifier,
|
|
7
|
+
} from '@flighthq/shading';
|
|
8
|
+
import type { Modifier, GlShadedDefineKey } from '@flighthq/types';
|
|
9
|
+
import { VertexDisplaceModifierSource } from '@flighthq/types';
|
|
10
|
+
|
|
11
|
+
import { getGlScene3DRuntime } from './glScene3DRuntime';
|
|
12
|
+
import { makeFakeGl2, makeGlScene3DState } from './glScene3DTestHelper';
|
|
13
|
+
import { emissiveGlModifierSnippet, envReflectGlModifierSnippet } from './glShadedBuiltInModifiers';
|
|
14
|
+
import { registerBuiltInGlModifierSnippets, vertexDisplaceGlModifierSnippet } from './glShadedBuiltInModifiers';
|
|
15
|
+
import { buildGlShadedCacheKey, compileGlShadedProgram, ensureGlShadedProgram } from './glShadedPrelude';
|
|
16
|
+
|
|
17
|
+
const BASE_KEY: GlShadedDefineKey = {
|
|
18
|
+
alphaMaskEnabled: false,
|
|
19
|
+
hasDiffuseMap: false,
|
|
20
|
+
hasNormalMap: false,
|
|
21
|
+
hasSpecularMap: false,
|
|
22
|
+
hasUvTransform: false,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function fragmentSourceFrom(calls: { name: string; args: unknown[] }[]): string {
|
|
26
|
+
const source = calls.find((c) => c.name === 'shaderSource' && String(c.args[1]).includes('fragColor'));
|
|
27
|
+
return String(source?.args[1] ?? '');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function vertexSourceFrom(calls: { name: string; args: unknown[] }[]): string {
|
|
31
|
+
const source = calls.find(
|
|
32
|
+
(c) =>
|
|
33
|
+
c.name === 'shaderSource' && String(c.args[1]).includes('a_position') && !String(c.args[1]).includes('fragColor'),
|
|
34
|
+
);
|
|
35
|
+
return String(source?.args[1] ?? '');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('buildGlShadedCacheKey', () => {
|
|
39
|
+
it('namespaces under shaded: and joins base flags with the modifier define-key', () => {
|
|
40
|
+
expect(buildGlShadedCacheKey(BASE_KEY, '')).toBe('shaded:------|');
|
|
41
|
+
expect(buildGlShadedCacheKey({ ...BASE_KEY, hasDiffuseMap: true }, 'EmissiveModifier:m')).toBe(
|
|
42
|
+
'shaded:-d----|EmissiveModifier:m',
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('encodes a non-identity uv transform in the u slot ahead of skin', () => {
|
|
47
|
+
expect(buildGlShadedCacheKey({ ...BASE_KEY, hasUvTransform: true }, '')).toBe('shaded:----u-|');
|
|
48
|
+
expect(buildGlShadedCacheKey({ ...BASE_KEY, hasUvTransform: true }, '')).not.toBe(
|
|
49
|
+
buildGlShadedCacheKey(BASE_KEY, ''),
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('sets the trailing skin flag so a skinned variant keys distinctly from the rigid one', () => {
|
|
54
|
+
expect(buildGlShadedCacheKey({ ...BASE_KEY, hasSkin: true }, '')).toBe('shaded:-----k|');
|
|
55
|
+
expect(buildGlShadedCacheKey({ ...BASE_KEY, hasSkin: true }, '')).not.toBe(buildGlShadedCacheKey(BASE_KEY, ''));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('compileGlShadedProgram', () => {
|
|
60
|
+
it('assembles the lean base shader with no modifier GLSL for an empty stack', () => {
|
|
61
|
+
const gl = makeFakeGl2();
|
|
62
|
+
compileGlShadedProgram(gl, BASE_KEY, [], createModifierRegistry());
|
|
63
|
+
const fragment = fragmentSourceFrom(gl.calls);
|
|
64
|
+
expect(fragment).toContain('void main()');
|
|
65
|
+
expect(fragment).toContain('fragColor');
|
|
66
|
+
expect(fragment).not.toContain('u_emissiveColor');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('injects a registered modifier snippet at its slot hook', () => {
|
|
70
|
+
const gl = makeFakeGl2();
|
|
71
|
+
const registry = createModifierRegistry();
|
|
72
|
+
registerModifier(registry, emissiveGlModifierSnippet);
|
|
73
|
+
const modifiers: readonly Modifier[] = [createEmissiveModifier({ color: 0xffcc88ff })];
|
|
74
|
+
compileGlShadedProgram(gl, BASE_KEY, modifiers, registry);
|
|
75
|
+
const fragment = fragmentSourceFrom(gl.calls);
|
|
76
|
+
expect(fragment).toContain('uniform vec3 u_emissiveColor_0;');
|
|
77
|
+
expect(fragment).toContain('emissive += emissiveTerm;');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('Gram-Schmidt-reorthogonalizes the tangent frame before sampling the normal map', () => {
|
|
81
|
+
const gl = makeFakeGl2();
|
|
82
|
+
compileGlShadedProgram(gl, { ...BASE_KEY, hasNormalMap: true }, [], createModifierRegistry());
|
|
83
|
+
const fragment = fragmentSourceFrom(gl.calls);
|
|
84
|
+
// The interpolated tangent is projected off the normal (the fix for the skewed-TBN normal-map bug).
|
|
85
|
+
expect(fragment).toContain('v_tangent.xyz - geometricNormal * dot(v_tangent.xyz, geometricNormal)');
|
|
86
|
+
expect(fragment).toContain('normal = normalize(tbn * baseTangentNormal);');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('reuses GL_MESH_LIGHT_BLOCK_GLSL rather than declaring a second light block', () => {
|
|
90
|
+
const gl = makeFakeGl2();
|
|
91
|
+
compileGlShadedProgram(gl, BASE_KEY, [], createModifierRegistry());
|
|
92
|
+
const fragment = fragmentSourceFrom(gl.calls);
|
|
93
|
+
// The shared block declares u_directional exactly once (no forked light loop).
|
|
94
|
+
expect(fragment.split('uniform vec4 u_directional;').length - 1).toBe(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('injects the skin define + vertex declarations only into the skinned vertex source', () => {
|
|
98
|
+
const gl = makeFakeGl2();
|
|
99
|
+
compileGlShadedProgram(gl, { ...BASE_KEY, hasSkin: true }, [], createModifierRegistry());
|
|
100
|
+
const vertex = vertexSourceFrom(gl.calls);
|
|
101
|
+
expect(vertex).toContain('#define HAS_SKIN');
|
|
102
|
+
expect(vertex).not.toContain('#define MAX_JOINTS');
|
|
103
|
+
expect(vertex).toContain('sampler2D u_jointTexture');
|
|
104
|
+
expect(vertex).toContain('texelFetch');
|
|
105
|
+
expect(vertex).toContain('mat4 skinMatrix()');
|
|
106
|
+
expect(vertex).toContain('a_joints0');
|
|
107
|
+
// Skinning is vertex-only — the fragment scene2d never sees the skin attributes.
|
|
108
|
+
expect(fragmentSourceFrom(gl.calls)).not.toContain('a_joints0');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('leaves the rigid vertex source free of the skin define and declarations', () => {
|
|
112
|
+
const gl = makeFakeGl2();
|
|
113
|
+
compileGlShadedProgram(gl, BASE_KEY, [], createModifierRegistry());
|
|
114
|
+
const vertex = vertexSourceFrom(gl.calls);
|
|
115
|
+
expect(vertex).not.toContain('#define HAS_SKIN');
|
|
116
|
+
expect(vertex).not.toContain('a_joints0');
|
|
117
|
+
expect(fragmentSourceFrom(gl.calls)).not.toContain('a_joints0');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('injects a Vertex-slot modifier into the vertex source, never the fragment', () => {
|
|
121
|
+
const gl = makeFakeGl2();
|
|
122
|
+
const registry = createModifierRegistry();
|
|
123
|
+
registerModifier(registry, vertexDisplaceGlModifierSnippet);
|
|
124
|
+
const modifiers: readonly Modifier[] = [
|
|
125
|
+
createVertexDisplaceModifier({ source: VertexDisplaceModifierSource.Sine, amplitude: 0.2 }),
|
|
126
|
+
];
|
|
127
|
+
compileGlShadedProgram(gl, BASE_KEY, modifiers, registry);
|
|
128
|
+
const vertex = vertexSourceFrom(gl.calls);
|
|
129
|
+
expect(vertex).toContain('localPosition.xyz += vDisplaceAxis * vDisplaceAmount;');
|
|
130
|
+
expect(vertex).toContain('uniform float u_vDisplaceAmplitude_0;');
|
|
131
|
+
expect(fragmentSourceFrom(gl.calls)).not.toContain('u_vDisplaceAmplitude_0');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('dedupes the shared IBL environment declaration across two env-reflect modifiers', () => {
|
|
135
|
+
const gl = makeFakeGl2();
|
|
136
|
+
const registry = createModifierRegistry();
|
|
137
|
+
registerModifier(registry, envReflectGlModifierSnippet);
|
|
138
|
+
const modifiers: readonly Modifier[] = [createEnvReflectModifier(), createEnvReflectModifier()];
|
|
139
|
+
compileGlShadedProgram(gl, BASE_KEY, modifiers, registry);
|
|
140
|
+
const fragment = fragmentSourceFrom(gl.calls);
|
|
141
|
+
// The shared samplerCube declares once (deduped), while the per-instance tints stay distinct.
|
|
142
|
+
expect(fragment.split('uniform samplerCube u_iblPrefiltered;').length - 1).toBe(1);
|
|
143
|
+
expect(fragment).toContain('u_envReflectTint_0');
|
|
144
|
+
expect(fragment).toContain('u_envReflectTint_1');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('ensureGlShadedProgram', () => {
|
|
149
|
+
it('caches the compiled program under a shaded: key and reuses it', () => {
|
|
150
|
+
const { state } = makeGlScene3DState();
|
|
151
|
+
const first = ensureGlShadedProgram(state, BASE_KEY, []);
|
|
152
|
+
const second = ensureGlShadedProgram(state, BASE_KEY, []);
|
|
153
|
+
expect(second).toBe(first);
|
|
154
|
+
const keys = [...getGlScene3DRuntime(state).programCache.keys()];
|
|
155
|
+
expect(keys.some((k) => k.startsWith('shaded:'))).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('folds the render-state skinned-run flag into a distinct HAS_SKIN variant', () => {
|
|
159
|
+
const { state } = makeGlScene3DState();
|
|
160
|
+
const rigid = ensureGlShadedProgram(state, BASE_KEY, []);
|
|
161
|
+
getGlScene3DRuntime(state).activeSkinnedRun = true;
|
|
162
|
+
const skinned = ensureGlShadedProgram(state, BASE_KEY, []);
|
|
163
|
+
|
|
164
|
+
expect(skinned).not.toBe(rigid);
|
|
165
|
+
expect([...getGlScene3DRuntime(state).programCache.keys()]).toContain('shaded:-----k|');
|
|
166
|
+
expect(skinned.locJointTexture).not.toBeNull();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('compiles distinct variants for distinct modifier feature-sets', () => {
|
|
170
|
+
const { state } = makeGlScene3DState();
|
|
171
|
+
registerBuiltInGlModifierSnippets(state);
|
|
172
|
+
ensureGlShadedProgram(state, BASE_KEY, []);
|
|
173
|
+
ensureGlShadedProgram(state, BASE_KEY, [createEmissiveModifier({ color: 0xffffffff })]);
|
|
174
|
+
const keys = [...getGlScene3DRuntime(state).programCache.keys()].filter((k) => k.startsWith('shaded:'));
|
|
175
|
+
expect(new Set(keys).size).toBe(2);
|
|
176
|
+
});
|
|
177
|
+
});
|