From 5d37ccc60ba3e8be864b91b1dfaa775280336194 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 26 Mar 2026 15:36:37 +0800 Subject: [PATCH 001/130] fix(shader-lab): resolve GVec4 generic return type for texture() builtin functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit texture(sampler2D, vec2) returns GVec4 which was incorrectly resolved to the sampler type instead of vec4, causing "No overload function type found" when passing the result to user-defined functions like decode32(vec4). Add resolveGenericReturnType() to correctly map GSampler* → GVec4: sampler2D/sampler3D/samplerCube → vec4 isampler2D/isampler3D/... → ivec4 usampler2D/usampler3D/... → uvec4 --- .../src/parser/builtin/functions.ts | 61 ++++++++++++-- tests/src/shader-lab/ShaderLab.test.ts | 5 ++ .../shader-lab/shaders/texture-generic.shader | 84 +++++++++++++++++++ 3 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 tests/src/shader-lab/shaders/texture-generic.shader diff --git a/packages/shader-lab/src/parser/builtin/functions.ts b/packages/shader-lab/src/parser/builtin/functions.ts index 46c0d49d29..33d55ebefb 100644 --- a/packages/shader-lab/src/parser/builtin/functions.ts +++ b/packages/shader-lab/src/parser/builtin/functions.ts @@ -26,6 +26,46 @@ function isGenericType(t: BuiltinType) { return t >= EGenType.GenType && t <= EGenType.GSampler2DArray; } +/** + * Resolve a generic return type from the actual type of a generic parameter. + * + * Same-family generics (GenType→GenType, GenIntType→GenIntType) pass through directly. + * Cross-family generics (GSampler→GVec4) require a mapping: + * sampler2D/sampler3D/samplerCube → vec4 + * isampler2D/isampler3D/... → ivec4 + * usampler2D/usampler3D/... → uvec4 + */ +function resolveGenericReturnType( + genericReturnType: EGenType, + genericParamType: EGenType, + actualParamType: NonGenericGalaceanType +): NonGenericGalaceanType { + // Cross-family: GSampler* → GVec4 + if ( + genericParamType >= EGenType.GSampler2D && + genericParamType <= EGenType.GSampler2DArray && + genericReturnType === EGenType.GVec4 + ) { + switch (actualParamType) { + case Keyword.I_SAMPLER2D: + case Keyword.I_SAMPLER3D: + case Keyword.I_SAMPLER_CUBE: + case Keyword.I_SAMPLER2D_ARRAY: + return Keyword.IVEC4; + case Keyword.U_SAMPLER2D: + case Keyword.U_SAMPLER3D: + case Keyword.U_SAMPLER_CUBE: + case Keyword.U_SAMPLER2D_ARRAY: + return Keyword.UVEC4; + default: + return Keyword.VEC4; + } + } + + // Same-family: GenType→GenType etc. — pass through directly + return actualParamType; +} + const BuiltinFunctionTable: Map = new Map(); export class BuiltinFunction { @@ -78,12 +118,18 @@ export class BuiltinFunction { const argLength = fnArgs.length; if (argLength !== parameterTypes.length) continue; // Try to match generic parameter type. - let returnType = TypeAny; + let resolvedReturnType: NonGenericGalaceanType = TypeAny; let found = true; for (let i = 0; i < argLength; i++) { const curFnArg = fnArgs[i]; if (isGenericType(curFnArg)) { - if (returnType === TypeAny) returnType = parameterTypes[i]; + if (resolvedReturnType === TypeAny) { + resolvedReturnType = resolveGenericReturnType( + fn._returnType as EGenType, + curFnArg as EGenType, + parameterTypes[i] + ); + } } else { if (curFnArg !== parameterTypes[i] && parameterTypes[i] !== TypeAny) { found = false; @@ -92,7 +138,10 @@ export class BuiltinFunction { } } if (found) { - fn._realReturnType = returnType; + fn._realReturnType = + isGenericType(fn._returnType) && resolvedReturnType !== TypeAny + ? resolvedReturnType + : (fn._returnType as NonGenericGalaceanType); return fn; } } @@ -303,10 +352,10 @@ BuiltinFunction._create("textureLod", EGenType.GVec4, EGenType.GSampler2DArray, BuiltinFunction._create("texture2DLodEXT", EGenType.GVec4, EGenType.GSampler2D, Keyword.VEC2, Keyword.FLOAT); BuiltinFunction._create("texture2DLodEXT", EGenType.GVec4, EGenType.GSampler3D, Keyword.VEC3, Keyword.FLOAT); -BuiltinFunction._create("textureCube", Keyword.SAMPLER_CUBE, Keyword.VEC3); -BuiltinFunction._create("textureCube", Keyword.SAMPLER_CUBE, Keyword.VEC3, Keyword.FLOAT); +BuiltinFunction._create("textureCube", Keyword.VEC4, Keyword.SAMPLER_CUBE, Keyword.VEC3); +BuiltinFunction._create("textureCube", Keyword.VEC4, Keyword.SAMPLER_CUBE, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create("textureCube", EGenType.GVec4, EGenType.GSamplerCube, Keyword.VEC3, Keyword.FLOAT); -BuiltinFunction._create("textureCubeLod", Keyword.SAMPLER_CUBE, Keyword.VEC3, Keyword.FLOAT); +BuiltinFunction._create("textureCubeLod", Keyword.VEC4, Keyword.SAMPLER_CUBE, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create("textureCubeLodEXT", EGenType.GVec4, EGenType.GSamplerCube, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create( diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index e998533e5e..b2a3485436 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -251,4 +251,9 @@ describe("ShaderLab", async () => { const shaderSource = await readFile("./shaders/mrt-struct.shader"); glslValidate(engine, shaderSource, shaderLabRelease); }); + + it("texture-generic (GVec4 → vec4 resolve)", async () => { + const shaderSource = await readFile("./shaders/texture-generic.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + }); }); diff --git a/tests/src/shader-lab/shaders/texture-generic.shader b/tests/src/shader-lab/shaders/texture-generic.shader new file mode 100644 index 0000000000..9f023023c9 --- /dev/null +++ b/tests/src/shader-lab/shaders/texture-generic.shader @@ -0,0 +1,84 @@ +Shader "texture-generic-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec4 POSITION; }; + struct Varyings { vec2 uv; }; + + VertexShader = vert; + FragmentShader = frag; + + // Test: user-defined function taking vec4, called with texture() return value. + // texture(sampler2D, vec2) returns GVec4 which must resolve to vec4. + float decode32(vec4 rgba) { + rgba = rgba * 255.0; + float Sign = 1.0 - step(128.0, rgba[3] + 0.5) * 2.0; + float Exponent = 2.0 * mod(float(int(rgba[3] + 0.5)), 128.0) + step(128.0, rgba[2] + 0.5) - 127.0; + float Mantissa = mod(float(int(rgba[2] + 0.5)), 128.0) * 65536.0 + rgba[1] * 256.0 + rgba[0] + 8388608.0; + return Sign * exp2(Exponent - 23.0) * Mantissa; + } + + sampler2D u_texture; + + // Test: texture() result passed to user function (GVec4 → vec4 resolve) + mat4 getJointMatrix(float i) { + float x = 4.0 * i; + vec4 v1 = vec4( + decode32(texture(u_texture, vec2((x + 0.5) / 1024.0, 0.5))), + decode32(texture(u_texture, vec2((x + 1.5) / 1024.0, 0.5))), + decode32(texture(u_texture, vec2((x + 2.5) / 1024.0, 0.5))), + decode32(texture(u_texture, vec2((x + 3.5) / 1024.0, 0.5))) + ); + return mat4(v1, v1, v1, vec4(0.0, 0.0, 0.0, 1.0)); + } + + // Test: texture() result used directly in arithmetic (GVec4 → vec4) + vec4 sampleAndScale(sampler2D tex, vec2 coord, float scale) { + vec4 color = texture(tex, coord); + return color * scale; + } + + // Test: textureLod also returns GVec4 + vec4 sampleLod(sampler2D tex, vec2 coord, float lod) { + vec4 color = textureLod(tex, coord, lod); + return color; + } + + // Test: texture() with samplerCube (GVec4 → vec4 resolve via GSamplerCube) + samplerCube u_cubeMap; + vec4 sampleCube(samplerCube tex, vec3 dir) { + return texture(tex, dir); + } + + // Test: textureLod with samplerCube (GVec4 → vec4 resolve via GSamplerCube) + vec4 sampleCubeLod(samplerCube tex, vec3 dir, float lod) { + return textureLod(tex, dir, lod); + } + + // Test: texture(samplerCube) result passed to user function (vec4 param matching) + float decodeCube(vec4 rgba) { + return rgba.r + rgba.g; + } + float sampleAndDecode(samplerCube tex, vec3 dir) { + return decodeCube(texture(tex, dir)); + } + + Varyings vert(Attributes attr) { + Varyings o; + gl_Position = renderer_MVPMat * attr.POSITION; + o.uv = attr.POSITION.xy; + return o; + } + + void frag(Varyings v) { + vec4 sampled = sampleAndScale(u_texture, v.uv, 1.0); + vec4 lodSampled = sampleLod(u_texture, v.uv, 0.0); + vec4 cubeSampled = sampleCube(u_cubeMap, vec3(v.uv, 1.0)); + vec4 cubeLodSampled = sampleCubeLod(u_cubeMap, vec3(v.uv, 1.0), 0.0); + float decoded = sampleAndDecode(u_cubeMap, vec3(v.uv, 1.0)); + gl_FragColor = sampled + lodSampled + cubeSampled + cubeLodSampled + vec4(decoded); + } + } + } +} From 727bcec6e88849370439f4e110ef748407240c35 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 26 Mar 2026 16:10:06 +0800 Subject: [PATCH 002/130] fix(shader-lab): simplify resolveGenericReturnType, fix textureCube/texture2DLod signatures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Simplify resolveGenericReturnType: remove genericParamType param, only check if return type is GVec4 - Fix textureCube/textureCubeLod return type: SAMPLER_CUBE → VEC4 - Add missing texture2DLod builtin function registration - Add texture2DLod test cases to texture-generic.shader --- .../src/parser/builtin/functions.ts | 22 +++++-------------- .../shader-lab/shaders/texture-generic.shader | 10 +++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/shader-lab/src/parser/builtin/functions.ts b/packages/shader-lab/src/parser/builtin/functions.ts index 33d55ebefb..660b617659 100644 --- a/packages/shader-lab/src/parser/builtin/functions.ts +++ b/packages/shader-lab/src/parser/builtin/functions.ts @@ -29,23 +29,18 @@ function isGenericType(t: BuiltinType) { /** * Resolve a generic return type from the actual type of a generic parameter. * - * Same-family generics (GenType→GenType, GenIntType→GenIntType) pass through directly. - * Cross-family generics (GSampler→GVec4) require a mapping: + * For GVec4 return type, maps sampler variants to the correct vec4 type: * sampler2D/sampler3D/samplerCube → vec4 * isampler2D/isampler3D/... → ivec4 * usampler2D/usampler3D/... → uvec4 + * + * For all other generic return types (GenType etc.), passes through the actual param type directly. */ function resolveGenericReturnType( genericReturnType: EGenType, - genericParamType: EGenType, actualParamType: NonGenericGalaceanType ): NonGenericGalaceanType { - // Cross-family: GSampler* → GVec4 - if ( - genericParamType >= EGenType.GSampler2D && - genericParamType <= EGenType.GSampler2DArray && - genericReturnType === EGenType.GVec4 - ) { + if (genericReturnType === EGenType.GVec4) { switch (actualParamType) { case Keyword.I_SAMPLER2D: case Keyword.I_SAMPLER3D: @@ -61,8 +56,6 @@ function resolveGenericReturnType( return Keyword.VEC4; } } - - // Same-family: GenType→GenType etc. — pass through directly return actualParamType; } @@ -124,11 +117,7 @@ export class BuiltinFunction { const curFnArg = fnArgs[i]; if (isGenericType(curFnArg)) { if (resolvedReturnType === TypeAny) { - resolvedReturnType = resolveGenericReturnType( - fn._returnType as EGenType, - curFnArg as EGenType, - parameterTypes[i] - ); + resolvedReturnType = resolveGenericReturnType(fn._returnType as EGenType, parameterTypes[i]); } } else { if (curFnArg !== parameterTypes[i] && parameterTypes[i] !== TypeAny) { @@ -349,6 +338,7 @@ BuiltinFunction._create("textureLod", EGenType.GVec4, EGenType.GSampler3D, Keywo BuiltinFunction._create("textureLod", EGenType.GVec4, EGenType.GSamplerCube, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create("textureLod", Keyword.FLOAT, Keyword.SAMPLER2D_SHADOW, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create("textureLod", EGenType.GVec4, EGenType.GSampler2DArray, Keyword.VEC3, Keyword.FLOAT); +BuiltinFunction._create("texture2DLod", Keyword.VEC4, Keyword.SAMPLER2D, Keyword.VEC2, Keyword.FLOAT); BuiltinFunction._create("texture2DLodEXT", EGenType.GVec4, EGenType.GSampler2D, Keyword.VEC2, Keyword.FLOAT); BuiltinFunction._create("texture2DLodEXT", EGenType.GVec4, EGenType.GSampler3D, Keyword.VEC3, Keyword.FLOAT); diff --git a/tests/src/shader-lab/shaders/texture-generic.shader b/tests/src/shader-lab/shaders/texture-generic.shader index 9f023023c9..a27789175c 100644 --- a/tests/src/shader-lab/shaders/texture-generic.shader +++ b/tests/src/shader-lab/shaders/texture-generic.shader @@ -45,6 +45,16 @@ Shader "texture-generic-test" { return color; } + // Test: texture2DLod (ES 1.0 function, concrete types) + vec4 sampleLod2D(sampler2D tex, vec2 coord, float lod) { + return texture2DLod(tex, coord, lod); + } + + // Test: texture2DLod result passed to user function + float decodeLod2D(sampler2D tex, vec2 coord) { + return decode32(texture2DLod(tex, coord, 0.0)); + } + // Test: texture() with samplerCube (GVec4 → vec4 resolve via GSamplerCube) samplerCube u_cubeMap; vec4 sampleCube(samplerCube tex, vec3 dir) { From b69233bfd01f3b3adf5a2f879b957a39a888ba91 Mon Sep 17 00:00:00 2001 From: hhhhkrx <155431265+hhhhkrx@users.noreply.github.com> Date: Mon, 23 Mar 2026 17:29:12 +0800 Subject: [PATCH 003/130] implement HorizontalBillboard render mode (#2938) * feat: implement HorizontalBillboard render mode --- .../particleRenderer-horizontal-billboard.ts | 82 +++++++++++++++++++ e2e/config.ts | 6 ++ ..._particleRenderer-horizontal-billboard.jpg | 3 + .../core/src/particle/ParticleRenderer.ts | 1 - .../particle/horizontal_billboard.glsl | 22 +++-- .../core/particle/ParticleRenderer.test.ts | 5 +- 6 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 e2e/case/particleRenderer-horizontal-billboard.ts create mode 100644 e2e/fixtures/originImage/Particle_particleRenderer-horizontal-billboard.jpg diff --git a/e2e/case/particleRenderer-horizontal-billboard.ts b/e2e/case/particleRenderer-horizontal-billboard.ts new file mode 100644 index 0000000000..3f373f6845 --- /dev/null +++ b/e2e/case/particleRenderer-horizontal-billboard.ts @@ -0,0 +1,82 @@ +/** + * @title Particle Horizontal Billboard + * @category Particle + */ +import { + AssetType, + Camera, + Color, + Logger, + ParticleMaterial, + ParticleRenderer, + ParticleRenderMode, + Texture2D, + Vector3, + WebGLEngine +} from "@galacean/engine"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +WebGLEngine.create({ + canvas: "canvas" +}).then((engine) => { + Logger.enable(); + engine.canvas.resizeByClientSize(); + + const rootEntity = engine.sceneManager.activeScene.createRootEntity("Root"); + + // Create camera + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.transform.position = new Vector3(0, 0.9, 1.5); + cameraEntity.transform.rotation = new Vector3(-15, 0, 0); + const camera = cameraEntity.addComponent(Camera); + camera.fieldOfView = 60; + camera.nearClipPlane = 0.3; + camera.farClipPlane = 1000; + + engine.resourceManager + .load([ + { + url: "https://mdn.alipayobjects.com/huamei_9ahbho/afts/img/A*QJvmQ6g4ujYAAAAAgCAAAAgAegDwAQ/original", + type: AssetType.Texture2D + } + ]) + .then((resources) => { + const particleEntity = rootEntity.createChild("Particle"); + const particleRenderer = particleEntity.addComponent(ParticleRenderer); + + // Material + const material = new ParticleMaterial(engine); + material.baseColor = new Color(1.0, 1.0, 1.0, 1.0); + material.baseTexture = resources[0]; + particleRenderer.setMaterial(material); + + // Render mode + particleRenderer.renderMode = ParticleRenderMode.HorizontalBillboard; + + const generator = particleRenderer.generator; + generator.useAutoRandomSeed = false; + const { main, emission, rotationOverLifetime } = generator; + + // Main module + main.duration = 5; + main.isLoop = true; + main.startDelay.constant = 0; + main.startLifetime.constant = 5; + main.startSpeed.constant = 1; + main.startSize.constant = 1; + main.startRotationZ.constant = -90; + main.startColor.constant = new Color(1.0, 1.0, 1.0, 1.0); + main.gravityModifier.constant = 0; + main.maxParticles = 1000; + + // Emission module + emission.rateOverTime.constant = 10; + + // Rotation over lifetime module + rotationOverLifetime.enabled = true; + rotationOverLifetime.rotationZ.constant = 45; + + updateForE2E(engine, 300); + initScreenshot(engine, camera); + }); +}); diff --git a/e2e/config.ts b/e2e/config.ts index 0ceaae0533..e5a9b621cf 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -424,6 +424,12 @@ export const E2E_CONFIG = { caseFileName: "particleRenderer-emit-billboard-stretched", threshold: 0, diffPercentage: 0.0 + }, + particleHorizontalBillboard: { + category: "Particle", + caseFileName: "particleRenderer-horizontal-billboard", + threshold: 0, + diffPercentage: 0.2162 } }, PostProcess: { diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-horizontal-billboard.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-horizontal-billboard.jpg new file mode 100644 index 0000000000..01a3398378 --- /dev/null +++ b/e2e/fixtures/originImage/Particle_particleRenderer-horizontal-billboard.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6d349ac4271c7127504e4b087c6ebbf08b53823c4e24c4253942d45e6512cbb +size 141096 diff --git a/packages/core/src/particle/ParticleRenderer.ts b/packages/core/src/particle/ParticleRenderer.ts index 4db5bb7dab..633e166989 100644 --- a/packages/core/src/particle/ParticleRenderer.ts +++ b/packages/core/src/particle/ParticleRenderer.ts @@ -73,7 +73,6 @@ export class ParticleRenderer extends Renderer { renderModeMacro = ParticleRenderer._stretchedBillboardModeMacro; break; case ParticleRenderMode.HorizontalBillboard: - throw "Not implemented"; renderModeMacro = ParticleRenderer._horizontalBillboardModeMacro; break; case ParticleRenderMode.VerticalBillboard: diff --git a/packages/core/src/shaderlib/particle/horizontal_billboard.glsl b/packages/core/src/shaderlib/particle/horizontal_billboard.glsl index ebcd6d4696..b9649f94e7 100644 --- a/packages/core/src/shaderlib/particle/horizontal_billboard.glsl +++ b/packages/core/src/shaderlib/particle/horizontal_billboard.glsl @@ -1,13 +1,21 @@ #ifdef RENDERER_MODE_HORIZONTAL_BILLBOARD - vec2 corner = a_CornerTextureCoordinate.xy + renderer_PivotOffset.xy; // Billboard模式z轴无效 - const vec3 cameraUpVector = vec3(0.0, 0.0, 1.0); - const vec3 sideVector = vec3(-1.0, 0.0, 0.0); + vec2 corner = a_CornerTextureCoordinate.xy + renderer_PivotOffset.xy; + const vec3 sideVector = vec3(1.0, 0.0, 0.0); + const vec3 upVector = vec3(0.0, 0.0, -1.0); + corner *= computeParticleSizeBillboard(a_StartSize.xy, normalizedAge); + + // HorizontalBillboard rotates in XZ plane (around Y-axis normal). + // Uses Z-axis rotation data to match Unity behavior. + float rot; + if (renderer_ThreeDStartRotation) { + rot = radians(computeParticleRotationFloat(a_StartRotation0.z, age, normalizedAge)); + } else { + rot = radians(computeParticleRotationFloat(a_StartRotation0.x, age, normalizedAge)); + } - float rot = radians(computeParticleRotationFloat(a_StartRotation0.x, age, normalizedAge)); float c = cos(rot); float s = sin(rot); mat2 rotation = mat2(c, -s, s, c); - corner = rotation * corner * cos(0.78539816339744830961566084581988); // TODO:临时缩小cos45,不确定U3D原因 - corner *= computeParticleSizeBillboard(a_StartSize.xy, normalizedAge); - center += renderer_SizeScale.xzy * (corner.x * sideVector + corner.y * cameraUpVector); + corner = rotation * corner; + center += renderer_SizeScale.xzy * (corner.x * sideVector + corner.y * upVector); #endif \ No newline at end of file diff --git a/tests/src/core/particle/ParticleRenderer.test.ts b/tests/src/core/particle/ParticleRenderer.test.ts index 065eaeab68..dada7b9be0 100644 --- a/tests/src/core/particle/ParticleRenderer.test.ts +++ b/tests/src/core/particle/ParticleRenderer.test.ts @@ -49,9 +49,8 @@ describe("ParticleRenderer", () => { expect(renderer.renderMode).to.eq(ParticleRenderMode.Billboard); renderer.renderMode = ParticleRenderMode.StretchBillboard; expect(renderer.renderMode).to.eq(ParticleRenderMode.StretchBillboard); - expect(() => { - renderer.renderMode = ParticleRenderMode.HorizontalBillboard; - }).to.throw("Not implemented"); + renderer.renderMode = ParticleRenderMode.HorizontalBillboard; + expect(renderer.renderMode).to.eq(ParticleRenderMode.HorizontalBillboard); renderer.renderMode = ParticleRenderMode.Mesh; expect(renderer.renderMode).to.eq(ParticleRenderMode.Mesh); expect(() => { From 8a0a605b98c7e66fbf52dee361a933d458f7b91b Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 26 Mar 2026 17:20:00 +0800 Subject: [PATCH 004/130] fix(shader-lab): return TypeAny for unresolved generic builtin return type When a builtin generic function (e.g. normalize) receives TypeAny args, resolvedReturnType stays TypeAny. Previously the else branch returned the raw EGenType enum value (200), which is neither a concrete type nor a wildcard, causing downstream user-function overload matching to fail. --- .../src/parser/builtin/functions.ts | 7 ++- tests/src/shader-lab/ShaderLab.test.ts | 5 ++ .../shaders/generic-return-type.shader | 52 +++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 tests/src/shader-lab/shaders/generic-return-type.shader diff --git a/packages/shader-lab/src/parser/builtin/functions.ts b/packages/shader-lab/src/parser/builtin/functions.ts index 660b617659..859951641c 100644 --- a/packages/shader-lab/src/parser/builtin/functions.ts +++ b/packages/shader-lab/src/parser/builtin/functions.ts @@ -127,10 +127,9 @@ export class BuiltinFunction { } } if (found) { - fn._realReturnType = - isGenericType(fn._returnType) && resolvedReturnType !== TypeAny - ? resolvedReturnType - : (fn._returnType as NonGenericGalaceanType); + fn._realReturnType = isGenericType(fn._returnType) + ? resolvedReturnType + : (fn._returnType as NonGenericGalaceanType); return fn; } } diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index b2a3485436..fde4f37670 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -256,4 +256,9 @@ describe("ShaderLab", async () => { const shaderSource = await readFile("./shaders/texture-generic.shader"); glslValidate(engine, shaderSource, shaderLabRelease); }); + + it("generic-return-type (builtin generic return as arg to user function)", async () => { + const shaderSource = await readFile("./shaders/generic-return-type.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + }); }); diff --git a/tests/src/shader-lab/shaders/generic-return-type.shader b/tests/src/shader-lab/shaders/generic-return-type.shader new file mode 100644 index 0000000000..72e96b7f3d --- /dev/null +++ b/tests/src/shader-lab/shaders/generic-return-type.shader @@ -0,0 +1,52 @@ +Shader "generic-return-type-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec4 POSITION; }; + struct Varyings { vec2 uv; vec3 worldNormal; vec3 worldTangent; }; + + VertexShader = vert; + FragmentShader = frag; + + // Test: user-defined function with concrete parameter types + vec3 CalculateNormalFromTangentSpace(vec3 normalFromTangentSpace, float normalStrength, vec3 normal, vec3 tangent, float mirrorNormal) { + vec3 binormal = cross(normal, tangent) * mirrorNormal; + return (normalFromTangentSpace.x * normalStrength) * normalize(tangent) + + (normalFromTangentSpace.y * normalStrength) * normalize(binormal) + + normalFromTangentSpace.z * normalize(normal); + } + + sampler2D u_normalMap; + vec4 u_scaleAndStrength; + + Varyings vert(Attributes attr) { + Varyings o; + gl_Position = renderer_MVPMat * attr.POSITION; + o.uv = attr.POSITION.xy; + o.worldNormal = attr.POSITION.xyz; + o.worldTangent = attr.POSITION.xyz; + return o; + } + + void frag(Varyings v) { + vec3 normal = v.worldNormal; + + // Test: builtin generic functions (normalize) on swizzled values, + // passed as arguments to a user-defined function. + // normalize(vec3) should not produce EGenType.GenType (200) as return type, + // it should produce TypeAny so overload matching succeeds. + vec3 nmmp = texture(u_normalMap, v.uv).xyz - vec3(0.5); + normal = CalculateNormalFromTangentSpace( + nmmp, + u_scaleAndStrength.w, + normalize(normal.xyz), + normalize(v.worldTangent), + 1.0 + ); + + gl_FragColor = vec4(normalize(normal), 1.0); + } + } + } +} From 889b0961219406eaeb3c4c90f96f8be266b8f3db Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 26 Mar 2026 15:36:37 +0800 Subject: [PATCH 005/130] fix(shader-lab): resolve GVec4 generic return type for texture() builtin functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit texture(sampler2D, vec2) returns GVec4 which was incorrectly resolved to the sampler type instead of vec4, causing "No overload function type found" when passing the result to user-defined functions like decode32(vec4). Add resolveGenericReturnType() to correctly map GSampler* → GVec4: sampler2D/sampler3D/samplerCube → vec4 isampler2D/isampler3D/... → ivec4 usampler2D/usampler3D/... → uvec4 --- .../src/parser/builtin/functions.ts | 61 ++++++++++++-- tests/src/shader-lab/ShaderLab.test.ts | 5 ++ .../shader-lab/shaders/texture-generic.shader | 84 +++++++++++++++++++ 3 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 tests/src/shader-lab/shaders/texture-generic.shader diff --git a/packages/shader-lab/src/parser/builtin/functions.ts b/packages/shader-lab/src/parser/builtin/functions.ts index 46c0d49d29..33d55ebefb 100644 --- a/packages/shader-lab/src/parser/builtin/functions.ts +++ b/packages/shader-lab/src/parser/builtin/functions.ts @@ -26,6 +26,46 @@ function isGenericType(t: BuiltinType) { return t >= EGenType.GenType && t <= EGenType.GSampler2DArray; } +/** + * Resolve a generic return type from the actual type of a generic parameter. + * + * Same-family generics (GenType→GenType, GenIntType→GenIntType) pass through directly. + * Cross-family generics (GSampler→GVec4) require a mapping: + * sampler2D/sampler3D/samplerCube → vec4 + * isampler2D/isampler3D/... → ivec4 + * usampler2D/usampler3D/... → uvec4 + */ +function resolveGenericReturnType( + genericReturnType: EGenType, + genericParamType: EGenType, + actualParamType: NonGenericGalaceanType +): NonGenericGalaceanType { + // Cross-family: GSampler* → GVec4 + if ( + genericParamType >= EGenType.GSampler2D && + genericParamType <= EGenType.GSampler2DArray && + genericReturnType === EGenType.GVec4 + ) { + switch (actualParamType) { + case Keyword.I_SAMPLER2D: + case Keyword.I_SAMPLER3D: + case Keyword.I_SAMPLER_CUBE: + case Keyword.I_SAMPLER2D_ARRAY: + return Keyword.IVEC4; + case Keyword.U_SAMPLER2D: + case Keyword.U_SAMPLER3D: + case Keyword.U_SAMPLER_CUBE: + case Keyword.U_SAMPLER2D_ARRAY: + return Keyword.UVEC4; + default: + return Keyword.VEC4; + } + } + + // Same-family: GenType→GenType etc. — pass through directly + return actualParamType; +} + const BuiltinFunctionTable: Map = new Map(); export class BuiltinFunction { @@ -78,12 +118,18 @@ export class BuiltinFunction { const argLength = fnArgs.length; if (argLength !== parameterTypes.length) continue; // Try to match generic parameter type. - let returnType = TypeAny; + let resolvedReturnType: NonGenericGalaceanType = TypeAny; let found = true; for (let i = 0; i < argLength; i++) { const curFnArg = fnArgs[i]; if (isGenericType(curFnArg)) { - if (returnType === TypeAny) returnType = parameterTypes[i]; + if (resolvedReturnType === TypeAny) { + resolvedReturnType = resolveGenericReturnType( + fn._returnType as EGenType, + curFnArg as EGenType, + parameterTypes[i] + ); + } } else { if (curFnArg !== parameterTypes[i] && parameterTypes[i] !== TypeAny) { found = false; @@ -92,7 +138,10 @@ export class BuiltinFunction { } } if (found) { - fn._realReturnType = returnType; + fn._realReturnType = + isGenericType(fn._returnType) && resolvedReturnType !== TypeAny + ? resolvedReturnType + : (fn._returnType as NonGenericGalaceanType); return fn; } } @@ -303,10 +352,10 @@ BuiltinFunction._create("textureLod", EGenType.GVec4, EGenType.GSampler2DArray, BuiltinFunction._create("texture2DLodEXT", EGenType.GVec4, EGenType.GSampler2D, Keyword.VEC2, Keyword.FLOAT); BuiltinFunction._create("texture2DLodEXT", EGenType.GVec4, EGenType.GSampler3D, Keyword.VEC3, Keyword.FLOAT); -BuiltinFunction._create("textureCube", Keyword.SAMPLER_CUBE, Keyword.VEC3); -BuiltinFunction._create("textureCube", Keyword.SAMPLER_CUBE, Keyword.VEC3, Keyword.FLOAT); +BuiltinFunction._create("textureCube", Keyword.VEC4, Keyword.SAMPLER_CUBE, Keyword.VEC3); +BuiltinFunction._create("textureCube", Keyword.VEC4, Keyword.SAMPLER_CUBE, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create("textureCube", EGenType.GVec4, EGenType.GSamplerCube, Keyword.VEC3, Keyword.FLOAT); -BuiltinFunction._create("textureCubeLod", Keyword.SAMPLER_CUBE, Keyword.VEC3, Keyword.FLOAT); +BuiltinFunction._create("textureCubeLod", Keyword.VEC4, Keyword.SAMPLER_CUBE, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create("textureCubeLodEXT", EGenType.GVec4, EGenType.GSamplerCube, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create( diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index e998533e5e..b2a3485436 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -251,4 +251,9 @@ describe("ShaderLab", async () => { const shaderSource = await readFile("./shaders/mrt-struct.shader"); glslValidate(engine, shaderSource, shaderLabRelease); }); + + it("texture-generic (GVec4 → vec4 resolve)", async () => { + const shaderSource = await readFile("./shaders/texture-generic.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + }); }); diff --git a/tests/src/shader-lab/shaders/texture-generic.shader b/tests/src/shader-lab/shaders/texture-generic.shader new file mode 100644 index 0000000000..9f023023c9 --- /dev/null +++ b/tests/src/shader-lab/shaders/texture-generic.shader @@ -0,0 +1,84 @@ +Shader "texture-generic-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec4 POSITION; }; + struct Varyings { vec2 uv; }; + + VertexShader = vert; + FragmentShader = frag; + + // Test: user-defined function taking vec4, called with texture() return value. + // texture(sampler2D, vec2) returns GVec4 which must resolve to vec4. + float decode32(vec4 rgba) { + rgba = rgba * 255.0; + float Sign = 1.0 - step(128.0, rgba[3] + 0.5) * 2.0; + float Exponent = 2.0 * mod(float(int(rgba[3] + 0.5)), 128.0) + step(128.0, rgba[2] + 0.5) - 127.0; + float Mantissa = mod(float(int(rgba[2] + 0.5)), 128.0) * 65536.0 + rgba[1] * 256.0 + rgba[0] + 8388608.0; + return Sign * exp2(Exponent - 23.0) * Mantissa; + } + + sampler2D u_texture; + + // Test: texture() result passed to user function (GVec4 → vec4 resolve) + mat4 getJointMatrix(float i) { + float x = 4.0 * i; + vec4 v1 = vec4( + decode32(texture(u_texture, vec2((x + 0.5) / 1024.0, 0.5))), + decode32(texture(u_texture, vec2((x + 1.5) / 1024.0, 0.5))), + decode32(texture(u_texture, vec2((x + 2.5) / 1024.0, 0.5))), + decode32(texture(u_texture, vec2((x + 3.5) / 1024.0, 0.5))) + ); + return mat4(v1, v1, v1, vec4(0.0, 0.0, 0.0, 1.0)); + } + + // Test: texture() result used directly in arithmetic (GVec4 → vec4) + vec4 sampleAndScale(sampler2D tex, vec2 coord, float scale) { + vec4 color = texture(tex, coord); + return color * scale; + } + + // Test: textureLod also returns GVec4 + vec4 sampleLod(sampler2D tex, vec2 coord, float lod) { + vec4 color = textureLod(tex, coord, lod); + return color; + } + + // Test: texture() with samplerCube (GVec4 → vec4 resolve via GSamplerCube) + samplerCube u_cubeMap; + vec4 sampleCube(samplerCube tex, vec3 dir) { + return texture(tex, dir); + } + + // Test: textureLod with samplerCube (GVec4 → vec4 resolve via GSamplerCube) + vec4 sampleCubeLod(samplerCube tex, vec3 dir, float lod) { + return textureLod(tex, dir, lod); + } + + // Test: texture(samplerCube) result passed to user function (vec4 param matching) + float decodeCube(vec4 rgba) { + return rgba.r + rgba.g; + } + float sampleAndDecode(samplerCube tex, vec3 dir) { + return decodeCube(texture(tex, dir)); + } + + Varyings vert(Attributes attr) { + Varyings o; + gl_Position = renderer_MVPMat * attr.POSITION; + o.uv = attr.POSITION.xy; + return o; + } + + void frag(Varyings v) { + vec4 sampled = sampleAndScale(u_texture, v.uv, 1.0); + vec4 lodSampled = sampleLod(u_texture, v.uv, 0.0); + vec4 cubeSampled = sampleCube(u_cubeMap, vec3(v.uv, 1.0)); + vec4 cubeLodSampled = sampleCubeLod(u_cubeMap, vec3(v.uv, 1.0), 0.0); + float decoded = sampleAndDecode(u_cubeMap, vec3(v.uv, 1.0)); + gl_FragColor = sampled + lodSampled + cubeSampled + cubeLodSampled + vec4(decoded); + } + } + } +} From 2d2512940cca3bea20744aaf8da107a9d4a9a0c9 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 26 Mar 2026 16:10:06 +0800 Subject: [PATCH 006/130] fix(shader-lab): simplify resolveGenericReturnType, fix textureCube/texture2DLod signatures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Simplify resolveGenericReturnType: remove genericParamType param, only check if return type is GVec4 - Fix textureCube/textureCubeLod return type: SAMPLER_CUBE → VEC4 - Add missing texture2DLod builtin function registration - Add texture2DLod test cases to texture-generic.shader --- .../src/parser/builtin/functions.ts | 22 +++++-------------- .../shader-lab/shaders/texture-generic.shader | 10 +++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/shader-lab/src/parser/builtin/functions.ts b/packages/shader-lab/src/parser/builtin/functions.ts index 33d55ebefb..660b617659 100644 --- a/packages/shader-lab/src/parser/builtin/functions.ts +++ b/packages/shader-lab/src/parser/builtin/functions.ts @@ -29,23 +29,18 @@ function isGenericType(t: BuiltinType) { /** * Resolve a generic return type from the actual type of a generic parameter. * - * Same-family generics (GenType→GenType, GenIntType→GenIntType) pass through directly. - * Cross-family generics (GSampler→GVec4) require a mapping: + * For GVec4 return type, maps sampler variants to the correct vec4 type: * sampler2D/sampler3D/samplerCube → vec4 * isampler2D/isampler3D/... → ivec4 * usampler2D/usampler3D/... → uvec4 + * + * For all other generic return types (GenType etc.), passes through the actual param type directly. */ function resolveGenericReturnType( genericReturnType: EGenType, - genericParamType: EGenType, actualParamType: NonGenericGalaceanType ): NonGenericGalaceanType { - // Cross-family: GSampler* → GVec4 - if ( - genericParamType >= EGenType.GSampler2D && - genericParamType <= EGenType.GSampler2DArray && - genericReturnType === EGenType.GVec4 - ) { + if (genericReturnType === EGenType.GVec4) { switch (actualParamType) { case Keyword.I_SAMPLER2D: case Keyword.I_SAMPLER3D: @@ -61,8 +56,6 @@ function resolveGenericReturnType( return Keyword.VEC4; } } - - // Same-family: GenType→GenType etc. — pass through directly return actualParamType; } @@ -124,11 +117,7 @@ export class BuiltinFunction { const curFnArg = fnArgs[i]; if (isGenericType(curFnArg)) { if (resolvedReturnType === TypeAny) { - resolvedReturnType = resolveGenericReturnType( - fn._returnType as EGenType, - curFnArg as EGenType, - parameterTypes[i] - ); + resolvedReturnType = resolveGenericReturnType(fn._returnType as EGenType, parameterTypes[i]); } } else { if (curFnArg !== parameterTypes[i] && parameterTypes[i] !== TypeAny) { @@ -349,6 +338,7 @@ BuiltinFunction._create("textureLod", EGenType.GVec4, EGenType.GSampler3D, Keywo BuiltinFunction._create("textureLod", EGenType.GVec4, EGenType.GSamplerCube, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create("textureLod", Keyword.FLOAT, Keyword.SAMPLER2D_SHADOW, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create("textureLod", EGenType.GVec4, EGenType.GSampler2DArray, Keyword.VEC3, Keyword.FLOAT); +BuiltinFunction._create("texture2DLod", Keyword.VEC4, Keyword.SAMPLER2D, Keyword.VEC2, Keyword.FLOAT); BuiltinFunction._create("texture2DLodEXT", EGenType.GVec4, EGenType.GSampler2D, Keyword.VEC2, Keyword.FLOAT); BuiltinFunction._create("texture2DLodEXT", EGenType.GVec4, EGenType.GSampler3D, Keyword.VEC3, Keyword.FLOAT); diff --git a/tests/src/shader-lab/shaders/texture-generic.shader b/tests/src/shader-lab/shaders/texture-generic.shader index 9f023023c9..a27789175c 100644 --- a/tests/src/shader-lab/shaders/texture-generic.shader +++ b/tests/src/shader-lab/shaders/texture-generic.shader @@ -45,6 +45,16 @@ Shader "texture-generic-test" { return color; } + // Test: texture2DLod (ES 1.0 function, concrete types) + vec4 sampleLod2D(sampler2D tex, vec2 coord, float lod) { + return texture2DLod(tex, coord, lod); + } + + // Test: texture2DLod result passed to user function + float decodeLod2D(sampler2D tex, vec2 coord) { + return decode32(texture2DLod(tex, coord, 0.0)); + } + // Test: texture() with samplerCube (GVec4 → vec4 resolve via GSamplerCube) samplerCube u_cubeMap; vec4 sampleCube(samplerCube tex, vec3 dir) { From 4b3d6321598cc73f85a2a443f4f49bf8ce0ed714 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 26 Mar 2026 17:20:00 +0800 Subject: [PATCH 007/130] fix(shader-lab): return TypeAny for unresolved generic builtin return type When a builtin generic function (e.g. normalize) receives TypeAny args, resolvedReturnType stays TypeAny. Previously the else branch returned the raw EGenType enum value (200), which is neither a concrete type nor a wildcard, causing downstream user-function overload matching to fail. --- .../src/parser/builtin/functions.ts | 7 ++- tests/src/shader-lab/ShaderLab.test.ts | 5 ++ .../shaders/generic-return-type.shader | 52 +++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 tests/src/shader-lab/shaders/generic-return-type.shader diff --git a/packages/shader-lab/src/parser/builtin/functions.ts b/packages/shader-lab/src/parser/builtin/functions.ts index 660b617659..859951641c 100644 --- a/packages/shader-lab/src/parser/builtin/functions.ts +++ b/packages/shader-lab/src/parser/builtin/functions.ts @@ -127,10 +127,9 @@ export class BuiltinFunction { } } if (found) { - fn._realReturnType = - isGenericType(fn._returnType) && resolvedReturnType !== TypeAny - ? resolvedReturnType - : (fn._returnType as NonGenericGalaceanType); + fn._realReturnType = isGenericType(fn._returnType) + ? resolvedReturnType + : (fn._returnType as NonGenericGalaceanType); return fn; } } diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index b2a3485436..fde4f37670 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -256,4 +256,9 @@ describe("ShaderLab", async () => { const shaderSource = await readFile("./shaders/texture-generic.shader"); glslValidate(engine, shaderSource, shaderLabRelease); }); + + it("generic-return-type (builtin generic return as arg to user function)", async () => { + const shaderSource = await readFile("./shaders/generic-return-type.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + }); }); diff --git a/tests/src/shader-lab/shaders/generic-return-type.shader b/tests/src/shader-lab/shaders/generic-return-type.shader new file mode 100644 index 0000000000..72e96b7f3d --- /dev/null +++ b/tests/src/shader-lab/shaders/generic-return-type.shader @@ -0,0 +1,52 @@ +Shader "generic-return-type-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec4 POSITION; }; + struct Varyings { vec2 uv; vec3 worldNormal; vec3 worldTangent; }; + + VertexShader = vert; + FragmentShader = frag; + + // Test: user-defined function with concrete parameter types + vec3 CalculateNormalFromTangentSpace(vec3 normalFromTangentSpace, float normalStrength, vec3 normal, vec3 tangent, float mirrorNormal) { + vec3 binormal = cross(normal, tangent) * mirrorNormal; + return (normalFromTangentSpace.x * normalStrength) * normalize(tangent) + + (normalFromTangentSpace.y * normalStrength) * normalize(binormal) + + normalFromTangentSpace.z * normalize(normal); + } + + sampler2D u_normalMap; + vec4 u_scaleAndStrength; + + Varyings vert(Attributes attr) { + Varyings o; + gl_Position = renderer_MVPMat * attr.POSITION; + o.uv = attr.POSITION.xy; + o.worldNormal = attr.POSITION.xyz; + o.worldTangent = attr.POSITION.xyz; + return o; + } + + void frag(Varyings v) { + vec3 normal = v.worldNormal; + + // Test: builtin generic functions (normalize) on swizzled values, + // passed as arguments to a user-defined function. + // normalize(vec3) should not produce EGenType.GenType (200) as return type, + // it should produce TypeAny so overload matching succeeds. + vec3 nmmp = texture(u_normalMap, v.uv).xyz - vec3(0.5); + normal = CalculateNormalFromTangentSpace( + nmmp, + u_scaleAndStrength.w, + normalize(normal.xyz), + normalize(v.worldTangent), + 1.0 + ); + + gl_FragColor = vec4(normalize(normal), 1.0); + } + } + } +} From 2f6712a1d93fb7947adc2cba11adfce355c57088 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 26 Mar 2026 20:22:20 +0800 Subject: [PATCH 008/130] fix(shader-lab): transform struct member access in #define values during CodeGen When #define values contain struct member access like `o.v_uv` (where `o` is a Varyings/Attributes/MRT struct variable), the CodeGen now correctly transforms them to just the property name (e.g. `v_uv`), matching the behavior of direct struct member access in regular code. Closes #2944. --- packages/shader-lab/src/Preprocessor.ts | 8 ++ .../shader-lab/src/codeGen/CodeGenVisitor.ts | 101 +++++++++++++++++- .../shader-lab/src/codeGen/GLESVisitor.ts | 7 +- .../shader-lab/src/codeGen/VisitorContext.ts | 24 +++++ tests/src/shader-lab/ShaderLab.test.ts | 5 + .../shaders/define-struct-access.shader | 29 +++++ 6 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 tests/src/shader-lab/shaders/define-struct-access.shader diff --git a/packages/shader-lab/src/Preprocessor.ts b/packages/shader-lab/src/Preprocessor.ts index 17e907da73..8f3e1abb27 100644 --- a/packages/shader-lab/src/Preprocessor.ts +++ b/packages/shader-lab/src/Preprocessor.ts @@ -5,6 +5,7 @@ import { ShaderLib } from "@galacean/engine"; export enum MacroValueType { Number, // 1, 1.1 Symbol, // variable name + MemberAccess, // member access, e.g. input.v_uv, v.rgb FunctionCall, // function call, e.g. clamp(a, 0.0, 1.0) Other // shaderLab does not check this } @@ -27,6 +28,7 @@ export class Preprocessor { private static readonly _macroRegex = /^\s*#define\s+(\w+)[ ]*(\(([^)]*)\))?[ ]+(\(?\w+\)?.*?)(?:\/\/.*|\/\*.*?\*\/)?\s*$/gm; private static readonly _symbolReg = /^[a-zA-Z_][a-zA-Z0-9_]*$/; + private static readonly _memberAccessReg = /^([a-zA-Z_][a-zA-Z0-9_]*)(\.[a-zA-Z_][a-zA-Z0-9_]*)+$/; private static readonly _funcCallReg = /^([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$/; private static readonly _macroDefineIncludeMap = new Map(); @@ -61,6 +63,10 @@ export class Preprocessor { const referencedName = valueType === MacroValueType.FunctionCall ? info.functionCallName : info.value; if (info.params.indexOf(referencedName) !== -1) continue; if (out.indexOf(referencedName) === -1) out.push(referencedName); + } else if (valueType === MacroValueType.MemberAccess) { + // Extract root symbol: "input.v_uv" → "input" + const rootName = info.value.substring(0, info.value.indexOf(".")); + if (out.indexOf(rootName) === -1) out.push(rootName); } else if (valueType === MacroValueType.Other) { // #if _VERBOSE Logger.warn( @@ -110,6 +116,8 @@ export class Preprocessor { valueType = MacroValueType.Number; } else if (this._symbolReg.test(value)) { valueType = MacroValueType.Symbol; + } else if (this._memberAccessReg.test(value)) { + valueType = MacroValueType.MemberAccess; } else { const callMatch = this._funcCallReg.exec(value); if (callMatch) { diff --git a/packages/shader-lab/src/codeGen/CodeGenVisitor.ts b/packages/shader-lab/src/codeGen/CodeGenVisitor.ts index 35beaa1d98..6ea4bdbadc 100644 --- a/packages/shader-lab/src/codeGen/CodeGenVisitor.ts +++ b/packages/shader-lab/src/codeGen/CodeGenVisitor.ts @@ -31,13 +31,19 @@ export abstract class CodeGenVisitor { protected static _tmpArrayPool = new ReturnableObjectPool(TempArray, 10); + private static readonly _memberAccessReg = /\b(\w+)\.(\w+)\b/g; + defaultCodeGen(children: NodeChild[]) { const pool = CodeGenVisitor._tmpArrayPool; let ret = pool.get(); ret.dispose(); for (const child of children) { if (child instanceof BaseToken) { - ret.array.push(child.lexeme); + if (child.type === Keyword.MACRO_DEFINE_EXPRESSION) { + ret.array.push(this._transformMacroDefineValue(child.lexeme)); + } else { + ret.array.push(child.lexeme); + } } else { ret.array.push(child.codeGen(this)); } @@ -46,6 +52,29 @@ export abstract class CodeGenVisitor { return ret.array.join(" "); } + protected _transformMacroDefineValue(lexeme: string): string { + const context = VisitorContext.context; + const structVarMap = context._structVarMap; + if (!structVarMap) return lexeme; + + const spaceIdx = lexeme.indexOf(" "); + if (spaceIdx === -1) return lexeme; + + const macroName = lexeme.substring(0, spaceIdx); + let value = lexeme.substring(spaceIdx); + + const reg = CodeGenVisitor._memberAccessReg; + reg.lastIndex = 0; + value = value.replace(reg, (match, varName, propName) => { + const role = structVarMap[varName]; + if (!role) return match; + context.referenceStructPropByName(role, propName); + return propName; + }); + + return macroName + value; + } + visitPostfixExpression(node: ASTNode.PostfixExpression): string { const children = node.children; const derivationLength = children.length; @@ -351,6 +380,8 @@ export abstract class CodeGenVisitor { const fnName = fnNode.protoType.ident.lexeme; const context = VisitorContext.context; + this._collectStructVars(fnNode, context); + if (fnName == context.stageEntry) { const statements = fnNode.statements.codeGen(this); return `void main() ${statements}`; @@ -359,6 +390,74 @@ export abstract class CodeGenVisitor { } } + private _collectStructVars(fnNode: ASTNode.FunctionDefinition, context: VisitorContext): void { + const map = context._structVarMap; + // Clear previous function's mappings + for (const key in map) delete map[key]; + + // Collect from function parameters + const paramList = fnNode.protoType.parameterList; + if (paramList) { + for (const param of paramList) { + if (param.ident && param.typeInfo && typeof param.typeInfo.type === "string") { + const role = context.getStructRole(param.typeInfo.typeLexeme); + if (role) map[param.ident.lexeme] = role; + } + } + } + + // Collect from local variable declarations in function body + this._collectStructVarsFromNode(fnNode.statements, context, map); + } + + private _collectStructVarsFromNode( + node: TreeNode, + context: VisitorContext, + map: Record + ): void { + const children = node.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child instanceof ASTNode.InitDeclaratorList) { + const typeLexeme = child.typeInfo?.typeLexeme; + if (typeLexeme) { + const role = context.getStructRole(typeLexeme); + if (role) { + // Extract variable name from SingleDeclaration or comma-separated identifiers + this._extractVarNamesFromInitDeclaratorList(child, map, role); + } + } + } else if (child instanceof TreeNode) { + this._collectStructVarsFromNode(child, context, map); + } + } + } + + private _extractVarNamesFromInitDeclaratorList( + node: ASTNode.InitDeclaratorList, + map: Record, + role: "varying" | "attribute" | "mrt" + ): void { + const children = node.children; + if (children.length === 1) { + // SingleDeclaration: type ident + const singleDecl = children[0] as ASTNode.SingleDeclaration; + const identChildren = singleDecl.children; + if (identChildren.length >= 2 && identChildren[1] instanceof BaseToken) { + map[identChildren[1].lexeme] = role; + } + } else if (children.length >= 3) { + // InitDeclaratorList , ident ... + const initDeclList = children[0]; + if (initDeclList instanceof ASTNode.InitDeclaratorList) { + this._extractVarNamesFromInitDeclaratorList(initDeclList, map, role); + } + if (children[2] instanceof BaseToken) { + map[children[2].lexeme] = role; + } + } + } + protected _reportError(loc: ShaderRange | ShaderPosition, message: string): void { // #if _VERBOSE this.errors.push(new GSError(GSErrorName.CompilationError, message, loc, ShaderLab._processingPassText)); diff --git a/packages/shader-lab/src/codeGen/GLESVisitor.ts b/packages/shader-lab/src/codeGen/GLESVisitor.ts index fbf611729f..227a3ed0dd 100644 --- a/packages/shader-lab/src/codeGen/GLESVisitor.ts +++ b/packages/shader-lab/src/codeGen/GLESVisitor.ts @@ -253,7 +253,12 @@ export abstract class GLESVisitor extends CodeGenVisitor { let result: ICodeSegment[] = []; result.push( ...macro.macroExpressions.map((item) => ({ - text: item instanceof BaseToken ? item.lexeme : item.codeGen(this), + text: + item instanceof BaseToken + ? item.type === Keyword.MACRO_DEFINE_EXPRESSION + ? this._transformMacroDefineValue(item.lexeme) + : item.lexeme + : item.codeGen(this), index: item.location.start.index })) ); diff --git a/packages/shader-lab/src/codeGen/VisitorContext.ts b/packages/shader-lab/src/codeGen/VisitorContext.ts index 25211f008a..92fbf7c576 100644 --- a/packages/shader-lab/src/codeGen/VisitorContext.ts +++ b/packages/shader-lab/src/codeGen/VisitorContext.ts @@ -38,6 +38,8 @@ export class VisitorContext { _referencedMRTList: Record; _referencedGlobals: Record; _referencedGlobalMacroASTs: TreeNode[] = []; + /** Maps variable names to their struct role for #define value transformation. */ + _structVarMap: Record; _passSymbolTable: SymbolTable; @@ -56,6 +58,28 @@ export class VisitorContext { this._referencedMRTList = Object.create(null); this._referencedGlobals = Object.create(null); this._referencedGlobalMacroASTs.length = 0; + this._structVarMap = Object.create(null); + } + + getStructRole(typeLexeme: string): "varying" | "attribute" | "mrt" | undefined { + if (this.isAttributeStruct(typeLexeme)) return "attribute"; + if (this.isVaryingStruct(typeLexeme)) return "varying"; + if (this.isMRTStruct(typeLexeme)) return "mrt"; + } + + referenceStructPropByName(role: "varying" | "attribute" | "mrt", propName: string): void { + const list = role === "varying" ? this.varyingList : role === "attribute" ? this.attributeList : this.mrtList; + const refList = + role === "varying" + ? this._referencedVaryingList + : role === "attribute" + ? this._referencedAttributeList + : this._referencedMRTList; + if (refList[propName]) return; + const props = list.filter((item) => item.ident.lexeme === propName); + if (props.length) { + refList[propName] = props; + } } isAttributeStruct(type: string) { diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index fde4f37670..b714040559 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -261,4 +261,9 @@ describe("ShaderLab", async () => { const shaderSource = await readFile("./shaders/generic-return-type.shader"); glslValidate(engine, shaderSource, shaderLabRelease); }); + + it("define-struct-access (#define value with struct member access)", async () => { + const shaderSource = await readFile("./shaders/define-struct-access.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + }); }); diff --git a/tests/src/shader-lab/shaders/define-struct-access.shader b/tests/src/shader-lab/shaders/define-struct-access.shader new file mode 100644 index 0000000000..28fd75fdc6 --- /dev/null +++ b/tests/src/shader-lab/shaders/define-struct-access.shader @@ -0,0 +1,29 @@ +Shader "define-struct-access-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec4 POSITION; vec2 TEXCOORD_0; }; + struct Varyings { vec2 v_uv; }; + + VertexShader = vert; + FragmentShader = frag; + + sampler2D u_texture; + + Varyings vert(Attributes attr) { + Varyings o; + #define ATTR_POS attr.POSITION + #define VARYING_UV o.v_uv + gl_Position = renderer_MVPMat * ATTR_POS; + VARYING_UV = attr.TEXCOORD_0; + return o; + } + + void frag(Varyings v) { + #define FRAG_UV v.v_uv + gl_FragColor = texture(u_texture, FRAG_UV); + } + } + } +} From a0e6fc8f4e1ad245988ce5ffaae1f80f9e49d8f9 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 26 Mar 2026 20:40:18 +0800 Subject: [PATCH 009/130] test(shader-lab): add transformation result assertions for define-struct-access Verify the actual CodeGen output instead of just checking GLSL compilation: - #define values with struct member access are correctly transformed - varying/attribute declarations are emitted for referenced properties --- tests/src/shader-lab/ShaderLab.test.ts | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index b714040559..df602bc2bf 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -264,6 +264,41 @@ describe("ShaderLab", async () => { it("define-struct-access (#define value with struct member access)", async () => { const shaderSource = await readFile("./shaders/define-struct-access.shader"); + + // Validate GLSL compilation glslValidate(engine, shaderSource, shaderLabRelease); + + // Verify CodeGen transformation results + const shader = shaderLabVerbose._parseShaderSource(shaderSource); + const passSource = shader.subShaders[0].passes[0]; + const shaderProgram = shaderLabVerbose._parseShaderPass( + passSource.contents, + passSource.vertexEntry, + passSource.fragmentEntry, + 0, // GLSLES100 + "" + ); + + const { vertex, fragment } = shaderProgram!; + + // Vertex: `#define ATTR_POS attr.POSITION` → `#define ATTR_POS POSITION` + expect(vertex).to.include("#define ATTR_POS POSITION"); + expect(vertex).not.to.include("#define ATTR_POS attr.POSITION"); + + // Vertex: `#define VARYING_UV o.v_uv` → `#define VARYING_UV v_uv` + expect(vertex).to.include("#define VARYING_UV v_uv"); + expect(vertex).not.to.include("#define VARYING_UV o.v_uv"); + + // Vertex: varying declaration should be emitted for referenced v_uv + expect(vertex).to.match(/varying\s+vec2\s+v_uv/); + // Vertex: attribute declaration should be emitted for referenced POSITION + expect(vertex).to.match(/attribute\s+vec4\s+POSITION/); + + // Fragment: `#define FRAG_UV v.v_uv` → `#define FRAG_UV v_uv` + expect(fragment).to.include("#define FRAG_UV v_uv"); + expect(fragment).not.to.include("#define FRAG_UV v.v_uv"); + + // Fragment: varying declaration should be emitted for referenced v_uv + expect(fragment).to.match(/varying\s+vec2\s+v_uv/); }); }); From 60480bbfb464da53cf5dc6e0000f7054c693b483 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 26 Mar 2026 21:06:49 +0800 Subject: [PATCH 010/130] test(shader-lab): enrich define-struct-access tests with usage assertions Add assertions for macro usage in expressions (not just #define transformation): - Macro as RHS in multiplication, as LHS in assignment - Macro as function argument in dot(), texture2D() - Multiple varying properties (v_uv, v_normal) referenced via #define --- tests/src/shader-lab/ShaderLab.test.ts | 38 ++++++++++++++----- .../shaders/define-struct-access.shader | 9 ++++- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index df602bc2bf..7b526ad95f 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -281,24 +281,42 @@ describe("ShaderLab", async () => { const { vertex, fragment } = shaderProgram!; - // Vertex: `#define ATTR_POS attr.POSITION` → `#define ATTR_POS POSITION` + // ---- #define transformation ---- + // Vertex: struct member access should be stripped to property name expect(vertex).to.include("#define ATTR_POS POSITION"); expect(vertex).not.to.include("#define ATTR_POS attr.POSITION"); - - // Vertex: `#define VARYING_UV o.v_uv` → `#define VARYING_UV v_uv` expect(vertex).to.include("#define VARYING_UV v_uv"); expect(vertex).not.to.include("#define VARYING_UV o.v_uv"); + expect(vertex).to.include("#define VARYING_NORMAL v_normal"); + expect(vertex).not.to.include("#define VARYING_NORMAL o.v_normal"); - // Vertex: varying declaration should be emitted for referenced v_uv - expect(vertex).to.match(/varying\s+vec2\s+v_uv/); - // Vertex: attribute declaration should be emitted for referenced POSITION - expect(vertex).to.match(/attribute\s+vec4\s+POSITION/); - - // Fragment: `#define FRAG_UV v.v_uv` → `#define FRAG_UV v_uv` + // Fragment: same transformation expect(fragment).to.include("#define FRAG_UV v_uv"); expect(fragment).not.to.include("#define FRAG_UV v.v_uv"); + expect(fragment).to.include("#define FRAG_NORMAL v_normal"); + expect(fragment).not.to.include("#define FRAG_NORMAL v.v_normal"); - // Fragment: varying declaration should be emitted for referenced v_uv + // ---- Declarations emitted from #define references ---- + expect(vertex).to.match(/attribute\s+vec4\s+POSITION/); + expect(vertex).to.match(/attribute\s+vec2\s+TEXCOORD_0/); + expect(vertex).to.match(/varying\s+vec2\s+v_uv/); + expect(vertex).to.match(/varying\s+vec3\s+v_normal/); expect(fragment).to.match(/varying\s+vec2\s+v_uv/); + expect(fragment).to.match(/varying\s+vec3\s+v_normal/); + + // ---- Macro usage in expressions ---- + // CodeGen outputs spaces between tokens, so use regex for flexible matching. + + // Vertex: ATTR_POS used in multiplication expression + expect(vertex).to.match(/renderer_MVPMat\s*\*\s*ATTR_POS/); + // Vertex: VARYING_UV used as assignment target (LHS) + expect(vertex).to.match(/VARYING_UV\s*=\s*TEXCOORD_0/); + // Vertex: VARYING_NORMAL used as assignment target + expect(vertex).to.match(/VARYING_NORMAL\s*=\s*vec3/); + + // Fragment: FRAG_NORMAL used as argument in dot() call + expect(fragment).to.match(/dot\s*\(\s*FRAG_NORMAL/); + // Fragment: FRAG_UV used as argument in texture2D() call + expect(fragment).to.match(/texture2D\s*\(\s*u_texture\s*,\s*FRAG_UV\s*\)/); }); }); diff --git a/tests/src/shader-lab/shaders/define-struct-access.shader b/tests/src/shader-lab/shaders/define-struct-access.shader index 28fd75fdc6..a28171dc40 100644 --- a/tests/src/shader-lab/shaders/define-struct-access.shader +++ b/tests/src/shader-lab/shaders/define-struct-access.shader @@ -4,25 +4,30 @@ Shader "define-struct-access-test" { mat4 renderer_MVPMat; struct Attributes { vec4 POSITION; vec2 TEXCOORD_0; }; - struct Varyings { vec2 v_uv; }; + struct Varyings { vec2 v_uv; vec3 v_normal; }; VertexShader = vert; FragmentShader = frag; sampler2D u_texture; + vec3 u_lightDir; Varyings vert(Attributes attr) { Varyings o; #define ATTR_POS attr.POSITION #define VARYING_UV o.v_uv + #define VARYING_NORMAL o.v_normal gl_Position = renderer_MVPMat * ATTR_POS; VARYING_UV = attr.TEXCOORD_0; + VARYING_NORMAL = vec3(0.0, 1.0, 0.0); return o; } void frag(Varyings v) { #define FRAG_UV v.v_uv - gl_FragColor = texture(u_texture, FRAG_UV); + #define FRAG_NORMAL v.v_normal + float NdotL = dot(FRAG_NORMAL, u_lightDir); + gl_FragColor = texture2D(u_texture, FRAG_UV) * NdotL; } } } From d05187b7fcc5449f94a50fded21c4a11d40a222e Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 26 Mar 2026 22:32:08 +0800 Subject: [PATCH 011/130] fix(shader-lab): support global #define with cross-stage struct var transform Build a combined _globalStructVarMap in visitShaderProgram by scanning both vertex and fragment entry functions, so global #define values like `attr.POSITION` or `o.v_uv` are correctly transformed in all stages. Rewrite define-struct-access tests to use snapshot file comparison against expected/ GLSL outputs for clearer verification. --- .../shader-lab/src/codeGen/CodeGenVisitor.ts | 11 +- .../shader-lab/src/codeGen/GLESVisitor.ts | 140 +++++++++++++++++- .../shader-lab/src/codeGen/VisitorContext.ts | 7 +- tests/src/shader-lab/ShaderLab.test.ts | 76 ++++------ .../define-struct-access-global.frag.glsl | 10 ++ .../define-struct-access-global.vert.glsl | 16 ++ .../expected/define-struct-access.frag.glsl | 11 ++ .../expected/define-struct-access.vert.glsl | 18 +++ .../define-struct-access-global.shader | 31 ++++ 9 files changed, 266 insertions(+), 54 deletions(-) create mode 100644 tests/src/shader-lab/expected/define-struct-access-global.frag.glsl create mode 100644 tests/src/shader-lab/expected/define-struct-access-global.vert.glsl create mode 100644 tests/src/shader-lab/expected/define-struct-access.frag.glsl create mode 100644 tests/src/shader-lab/expected/define-struct-access.vert.glsl create mode 100644 tests/src/shader-lab/shaders/define-struct-access-global.shader diff --git a/packages/shader-lab/src/codeGen/CodeGenVisitor.ts b/packages/shader-lab/src/codeGen/CodeGenVisitor.ts index 6ea4bdbadc..1870a0e88b 100644 --- a/packages/shader-lab/src/codeGen/CodeGenVisitor.ts +++ b/packages/shader-lab/src/codeGen/CodeGenVisitor.ts @@ -31,7 +31,7 @@ export abstract class CodeGenVisitor { protected static _tmpArrayPool = new ReturnableObjectPool(TempArray, 10); - private static readonly _memberAccessReg = /\b(\w+)\.(\w+)\b/g; + protected static readonly _memberAccessReg = /\b(\w+)\.(\w+)\b/g; defaultCodeGen(children: NodeChild[]) { const pool = CodeGenVisitor._tmpArrayPool; @@ -52,9 +52,12 @@ export abstract class CodeGenVisitor { return ret.array.join(" "); } - protected _transformMacroDefineValue(lexeme: string): string { + protected _transformMacroDefineValue( + lexeme: string, + overrideMap?: Record + ): string { const context = VisitorContext.context; - const structVarMap = context._structVarMap; + const structVarMap = overrideMap ?? context._structVarMap; if (!structVarMap) return lexeme; const spaceIdx = lexeme.indexOf(" "); @@ -433,7 +436,7 @@ export abstract class CodeGenVisitor { } } - private _extractVarNamesFromInitDeclaratorList( + protected _extractVarNamesFromInitDeclaratorList( node: ASTNode.InitDeclaratorList, map: Record, role: "varying" | "attribute" | "mrt" diff --git a/packages/shader-lab/src/codeGen/GLESVisitor.ts b/packages/shader-lab/src/codeGen/GLESVisitor.ts index 227a3ed0dd..a530a06f89 100644 --- a/packages/shader-lab/src/codeGen/GLESVisitor.ts +++ b/packages/shader-lab/src/codeGen/GLESVisitor.ts @@ -5,6 +5,7 @@ import { Keyword } from "../common/enums/Keyword"; import { ASTNode, TreeNode } from "../parser/AST"; import { ShaderData } from "../parser/ShaderInfo"; import { ESymbolType, FnSymbol, StructSymbol, SymbolInfo } from "../parser/symbolTable"; +import { NodeChild } from "../parser/types"; import { CodeGenVisitor } from "./CodeGenVisitor"; import { ICodeSegment } from "./types"; import { VisitorContext } from "./VisitorContext"; @@ -37,10 +38,15 @@ export abstract class GLESVisitor extends CodeGenVisitor { this.reset(); const shaderData = node.shaderData; - VisitorContext.context._passSymbolTable = shaderData.symbolTable; + const context = VisitorContext.context; + context._passSymbolTable = shaderData.symbolTable; const outerGlobalMacroDeclarations = shaderData.getOuterGlobalMacroDeclarations(); + // Build combined _globalStructVarMap from both entry functions before per-stage processing. + // This must happen here because vertex runs first and doesn't yet know fragment's variables. + this._buildGlobalStructVarMap(vertexEntry, fragmentEntry, shaderData, context); + return { vertex: this._vertexMain(vertexEntry, shaderData, outerGlobalMacroDeclarations), fragment: this._fragmentMain(fragmentEntry, shaderData, outerGlobalMacroDeclarations) @@ -109,6 +115,9 @@ export abstract class GLESVisitor extends CodeGenVisitor { } }); + // Pre-register global #define member access references for this stage + this._registerGlobalMacroReferences(outerGlobalMacroDeclarations, context); + const globalCodeArray = this._globalCodeArray; VisitorContext.context.referenceGlobal(entry, ESymbolType.FN); @@ -173,6 +182,9 @@ export abstract class GLESVisitor extends CodeGenVisitor { } }); + // Pre-register global #define member access references for this stage + this._registerGlobalMacroReferences(outerGlobalMacroStatements, context); + const globalCodeArray = this._globalCodeArray; VisitorContext.context.referenceGlobal(entry, ESymbolType.FN); @@ -193,6 +205,127 @@ export abstract class GLESVisitor extends CodeGenVisitor { return globalCode; } + /** + * Build _globalStructVarMap from both entry functions before per-stage processing. + * Classifies struct types by their position in function signatures: + * - vertex param[0] → attribute, vertex return type → varying + * - fragment param[0] → varying, fragment return type → mrt + */ + private _buildGlobalStructVarMap( + vertexEntry: string, + fragmentEntry: string, + data: ShaderData, + context: VisitorContext + ): void { + const map = context._globalStructVarMap; + const lookupSymbol = GLESVisitor._lookupSymbol; + const { symbolTable } = data; + + // Map struct type names to roles based on function signature positions + const structTypeRoles: Record = Object.create(null); + + // Vertex entry: param[0] type → attribute, return type → varying + lookupSymbol.set(vertexEntry, ESymbolType.FN); + const vertexFns = symbolTable.getSymbols(lookupSymbol, true, []); + for (const fn of vertexFns) { + const proto = fn.astNode.protoType; + const param0 = proto.parameterList?.[0]; + if (param0 && typeof param0.typeInfo.type === "string") { + structTypeRoles[param0.typeInfo.typeLexeme] = "attribute"; + } + if (typeof proto.returnType.type === "string") { + structTypeRoles[proto.returnType.type] = "varying"; + } + } + + // Fragment entry: param[0] type → varying, return type → mrt + lookupSymbol.set(fragmentEntry, ESymbolType.FN); + const fragmentFns = symbolTable.getSymbols(lookupSymbol, true, []); + for (const fn of fragmentFns) { + const proto = fn.astNode.protoType; + const param0 = proto.parameterList?.[0]; + if (param0 && typeof param0.typeInfo.type === "string") { + structTypeRoles[param0.typeInfo.typeLexeme] = "varying"; + } + if (typeof proto.returnType.type === "string") { + structTypeRoles[proto.returnType.type] = "mrt"; + } + } + + // Scan all entry functions' params and local vars, classify by structTypeRoles + for (const fn of [...vertexFns, ...fragmentFns]) { + const fnNode = fn.astNode; + const paramList = fnNode.protoType.parameterList; + if (paramList) { + for (const param of paramList) { + if (param.ident && param.typeInfo && typeof param.typeInfo.type === "string") { + const role = structTypeRoles[param.typeInfo.typeLexeme]; + if (role) map[param.ident.lexeme] = role; + } + } + } + this._collectStructVarsFromBody(fnNode.statements, structTypeRoles, map); + } + } + + private _collectStructVarsFromBody( + node: TreeNode, + structTypeRoles: Record, + map: Record + ): void { + const children = node.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child instanceof ASTNode.InitDeclaratorList) { + const typeLexeme = child.typeInfo?.typeLexeme; + if (typeLexeme) { + const role = structTypeRoles[typeLexeme]; + if (role) { + this._extractVarNamesFromInitDeclaratorList(child, map, role); + } + } + } else if (child instanceof TreeNode) { + this._collectStructVarsFromBody(child, structTypeRoles, map); + } + } + } + + /** + * Pre-register attribute/varying/mrt references from global #define member access patterns, + * so that declarations are emitted by _getCustomStruct for the current stage. + */ + private _registerGlobalMacroReferences(globalMacros: ASTNode.GlobalDeclaration[], context: VisitorContext): void { + const map = context._globalStructVarMap; + if (!Object.keys(map).length) return; + const reg = CodeGenVisitor._memberAccessReg; + for (const macro of globalMacros) { + this._scanMacroMemberAccess(macro.children, reg, map, context); + } + } + + private _scanMacroMemberAccess( + children: NodeChild[], + reg: RegExp, + map: Record, + context: VisitorContext + ): void { + for (const child of children) { + if (child instanceof BaseToken && child.type === Keyword.MACRO_DEFINE_EXPRESSION) { + const spaceIdx = child.lexeme.indexOf(" "); + if (spaceIdx === -1) continue; + const value = child.lexeme.substring(spaceIdx); + reg.lastIndex = 0; + let match: RegExpExecArray | null; + while ((match = reg.exec(value)) !== null) { + const role = map[match[1]]; + if (role) context.referenceStructPropByName(role, match[2]); + } + } else if (child instanceof TreeNode) { + this._scanMacroMemberAccess(child.children, reg, map, context); + } + } + } + private _getGlobalSymbol(out: ICodeSegment[]): void { const { _referencedGlobals } = VisitorContext.context; @@ -233,6 +366,7 @@ export abstract class GLESVisitor extends CodeGenVisitor { private _getGlobalMacroDeclarations(macros: ASTNode.GlobalDeclaration[], out: ICodeSegment[]): void { const context = VisitorContext.context; + const globalMap = context._globalStructVarMap; const referencedGlobals = context._referencedGlobals; const referencedGlobalMacroASTs = context._referencedGlobalMacroASTs; referencedGlobalMacroASTs.length = 0; @@ -256,7 +390,7 @@ export abstract class GLESVisitor extends CodeGenVisitor { text: item instanceof BaseToken ? item.type === Keyword.MACRO_DEFINE_EXPRESSION - ? this._transformMacroDefineValue(item.lexeme) + ? this._transformMacroDefineValue(item.lexeme, globalMap) : item.lexeme : item.codeGen(this), index: item.location.start.index @@ -269,6 +403,8 @@ export abstract class GLESVisitor extends CodeGenVisitor { .sort((a, b) => a.index - b.index) .map((item) => item.text) .join("\n"); + } else if (child instanceof BaseToken && child.type === Keyword.MACRO_DEFINE_EXPRESSION) { + text = this._transformMacroDefineValue(child.lexeme, globalMap); } else { text = macro.codeGen(this); } diff --git a/packages/shader-lab/src/codeGen/VisitorContext.ts b/packages/shader-lab/src/codeGen/VisitorContext.ts index 92fbf7c576..3ac2a3eb38 100644 --- a/packages/shader-lab/src/codeGen/VisitorContext.ts +++ b/packages/shader-lab/src/codeGen/VisitorContext.ts @@ -38,8 +38,10 @@ export class VisitorContext { _referencedMRTList: Record; _referencedGlobals: Record; _referencedGlobalMacroASTs: TreeNode[] = []; - /** Maps variable names to their struct role for #define value transformation. */ + /** Maps variable names to their struct role for function-body #define value transformation. */ _structVarMap: Record; + /** Combined mapping from all entry functions for global #define transformation. */ + _globalStructVarMap: Record; _passSymbolTable: SymbolTable; @@ -59,6 +61,9 @@ export class VisitorContext { this._referencedGlobals = Object.create(null); this._referencedGlobalMacroASTs.length = 0; this._structVarMap = Object.create(null); + if (resetAll) { + this._globalStructVarMap = Object.create(null); + } } getStructRole(typeLexeme: string): "varying" | "attribute" | "mrt" | undefined { diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index 7b526ad95f..d424508682 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -262,61 +262,43 @@ describe("ShaderLab", async () => { glslValidate(engine, shaderSource, shaderLabRelease); }); - it("define-struct-access (#define value with struct member access)", async () => { - const shaderSource = await readFile("./shaders/define-struct-access.shader"); + it("define-struct-access-global (global #define with struct member access)", async () => { + const shaderSource = await readFile("./shaders/define-struct-access-global.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); - // Validate GLSL compilation + const shader = shaderLabVerbose._parseShaderSource(shaderSource); + const passSource = shader.subShaders[0].passes[0]; + const { vertex, fragment } = shaderLabVerbose._parseShaderPass( + passSource.contents, + passSource.vertexEntry, + passSource.fragmentEntry, + 0, + "" + )!; + + const expectedVert = await readFile("./expected/define-struct-access-global.vert.glsl"); + const expectedFrag = await readFile("./expected/define-struct-access-global.frag.glsl"); + expect(vertex).to.equal(expectedVert); + expect(fragment).to.equal(expectedFrag); + }); + + it("define-struct-access (function-body #define with struct member access)", async () => { + const shaderSource = await readFile("./shaders/define-struct-access.shader"); glslValidate(engine, shaderSource, shaderLabRelease); - // Verify CodeGen transformation results const shader = shaderLabVerbose._parseShaderSource(shaderSource); const passSource = shader.subShaders[0].passes[0]; - const shaderProgram = shaderLabVerbose._parseShaderPass( + const { vertex, fragment } = shaderLabVerbose._parseShaderPass( passSource.contents, passSource.vertexEntry, passSource.fragmentEntry, - 0, // GLSLES100 + 0, "" - ); - - const { vertex, fragment } = shaderProgram!; - - // ---- #define transformation ---- - // Vertex: struct member access should be stripped to property name - expect(vertex).to.include("#define ATTR_POS POSITION"); - expect(vertex).not.to.include("#define ATTR_POS attr.POSITION"); - expect(vertex).to.include("#define VARYING_UV v_uv"); - expect(vertex).not.to.include("#define VARYING_UV o.v_uv"); - expect(vertex).to.include("#define VARYING_NORMAL v_normal"); - expect(vertex).not.to.include("#define VARYING_NORMAL o.v_normal"); - - // Fragment: same transformation - expect(fragment).to.include("#define FRAG_UV v_uv"); - expect(fragment).not.to.include("#define FRAG_UV v.v_uv"); - expect(fragment).to.include("#define FRAG_NORMAL v_normal"); - expect(fragment).not.to.include("#define FRAG_NORMAL v.v_normal"); - - // ---- Declarations emitted from #define references ---- - expect(vertex).to.match(/attribute\s+vec4\s+POSITION/); - expect(vertex).to.match(/attribute\s+vec2\s+TEXCOORD_0/); - expect(vertex).to.match(/varying\s+vec2\s+v_uv/); - expect(vertex).to.match(/varying\s+vec3\s+v_normal/); - expect(fragment).to.match(/varying\s+vec2\s+v_uv/); - expect(fragment).to.match(/varying\s+vec3\s+v_normal/); - - // ---- Macro usage in expressions ---- - // CodeGen outputs spaces between tokens, so use regex for flexible matching. - - // Vertex: ATTR_POS used in multiplication expression - expect(vertex).to.match(/renderer_MVPMat\s*\*\s*ATTR_POS/); - // Vertex: VARYING_UV used as assignment target (LHS) - expect(vertex).to.match(/VARYING_UV\s*=\s*TEXCOORD_0/); - // Vertex: VARYING_NORMAL used as assignment target - expect(vertex).to.match(/VARYING_NORMAL\s*=\s*vec3/); - - // Fragment: FRAG_NORMAL used as argument in dot() call - expect(fragment).to.match(/dot\s*\(\s*FRAG_NORMAL/); - // Fragment: FRAG_UV used as argument in texture2D() call - expect(fragment).to.match(/texture2D\s*\(\s*u_texture\s*,\s*FRAG_UV\s*\)/); + )!; + + const expectedVert = await readFile("./expected/define-struct-access.vert.glsl"); + const expectedFrag = await readFile("./expected/define-struct-access.frag.glsl"); + expect(vertex).to.equal(expectedVert); + expect(fragment).to.equal(expectedFrag); }); }); diff --git a/tests/src/shader-lab/expected/define-struct-access-global.frag.glsl b/tests/src/shader-lab/expected/define-struct-access-global.frag.glsl new file mode 100644 index 0000000000..e766b6e6a1 --- /dev/null +++ b/tests/src/shader-lab/expected/define-struct-access-global.frag.glsl @@ -0,0 +1,10 @@ +varying vec2 v_uv; + +uniform sampler2D u_texture; +#define ATTR_POS POSITION + +#define VARYING_UV v_uv + +#define FRAG_UV v_uv + +void main() { gl_FragColor = texture2D ( u_texture , FRAG_UV ) ; } \ No newline at end of file diff --git a/tests/src/shader-lab/expected/define-struct-access-global.vert.glsl b/tests/src/shader-lab/expected/define-struct-access-global.vert.glsl new file mode 100644 index 0000000000..782ad3ee52 --- /dev/null +++ b/tests/src/shader-lab/expected/define-struct-access-global.vert.glsl @@ -0,0 +1,16 @@ +uniform mat4 renderer_MVPMat; +attribute vec4 POSITION; +attribute vec2 TEXCOORD_0; + +varying vec2 v_uv; + +#define ATTR_POS POSITION + +#define VARYING_UV v_uv + +#define FRAG_UV v_uv + +void main() { +gl_Position = renderer_MVPMat * ATTR_POS ; +VARYING_UV = TEXCOORD_0 ; + } \ No newline at end of file diff --git a/tests/src/shader-lab/expected/define-struct-access.frag.glsl b/tests/src/shader-lab/expected/define-struct-access.frag.glsl new file mode 100644 index 0000000000..2c8ba502ac --- /dev/null +++ b/tests/src/shader-lab/expected/define-struct-access.frag.glsl @@ -0,0 +1,11 @@ +varying vec2 v_uv; +varying vec3 v_normal; + +uniform sampler2D u_texture; +uniform vec3 u_lightDir; +void main() { #define FRAG_UV v_uv + +#define FRAG_NORMAL v_normal + +float NdotL = dot ( FRAG_NORMAL , u_lightDir ) ; +gl_FragColor = texture2D ( u_texture , FRAG_UV ) * NdotL ; } \ No newline at end of file diff --git a/tests/src/shader-lab/expected/define-struct-access.vert.glsl b/tests/src/shader-lab/expected/define-struct-access.vert.glsl new file mode 100644 index 0000000000..1ee6d28522 --- /dev/null +++ b/tests/src/shader-lab/expected/define-struct-access.vert.glsl @@ -0,0 +1,18 @@ +uniform mat4 renderer_MVPMat; +attribute vec4 POSITION; +attribute vec2 TEXCOORD_0; + +varying vec2 v_uv; +varying vec3 v_normal; + +void main() { +#define ATTR_POS POSITION + +#define VARYING_UV v_uv + +#define VARYING_NORMAL v_normal + +gl_Position = renderer_MVPMat * ATTR_POS ; +VARYING_UV = TEXCOORD_0 ; +VARYING_NORMAL = vec3 ( 0.0 , 1.0 , 0.0 ) ; + } \ No newline at end of file diff --git a/tests/src/shader-lab/shaders/define-struct-access-global.shader b/tests/src/shader-lab/shaders/define-struct-access-global.shader new file mode 100644 index 0000000000..a8abffe570 --- /dev/null +++ b/tests/src/shader-lab/shaders/define-struct-access-global.shader @@ -0,0 +1,31 @@ +Shader "define-struct-access-global-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec4 POSITION; vec2 TEXCOORD_0; }; + struct Varyings { vec2 v_uv; }; + + VertexShader = vert; + FragmentShader = frag; + + sampler2D u_texture; + + // Global #define referencing entry function parameter names + #define ATTR_POS attr.POSITION + #define VARYING_UV o.v_uv + #define FRAG_UV v.v_uv + + Varyings vert(Attributes attr) { + Varyings o; + gl_Position = renderer_MVPMat * ATTR_POS; + VARYING_UV = attr.TEXCOORD_0; + return o; + } + + void frag(Varyings v) { + gl_FragColor = texture2D(u_texture, FRAG_UV); + } + } + } +} From 828752de065890c4f759337a4f0c3d6f37e7f906 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 26 Mar 2026 23:15:39 +0800 Subject: [PATCH 012/130] fix(shader-lab): skip type inference for member access macros in semantic analysis --- packages/shader-lab/src/parser/AST.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/shader-lab/src/parser/AST.ts b/packages/shader-lab/src/parser/AST.ts index 4c90c01444..243944df8f 100644 --- a/packages/shader-lab/src/parser/AST.ts +++ b/packages/shader-lab/src/parser/AST.ts @@ -4,7 +4,7 @@ import { ETokenType, GalaceanDataType, ShaderRange, TokenType, TypeAny } from ". import { BaseToken } from "../common/BaseToken"; import { Keyword } from "../common/enums/Keyword"; import { ParserUtils } from "../ParserUtils"; -import { Preprocessor } from "../Preprocessor"; +import { MacroValueType, Preprocessor } from "../Preprocessor"; import { ShaderLabUtils } from "../ShaderLabUtils"; import { BuiltinFunction, BuiltinVariable, NonGenericGalaceanType } from "./builtin"; import { NoneTerminal } from "./GrammarSymbol"; @@ -1424,7 +1424,12 @@ export namespace ASTNode { sa.reportWarning(this.location, `Please sure the identifier "${name}" will be declared before used.`); // #endif } else { - this.typeInfo = symbols[0].dataType?.type; + // For member access macros (e.g. #define FRAG_UV v.v_uv), the referenceSymbolNames + // contains the root variable ("v") whose type is the struct ("Varyings"), not the + // member type ("vec2"). Skip type inference in this case — keep TypeAny. + if (child instanceof BaseToken || !this._isMemberAccessMacro(sa, child)) { + this.typeInfo = symbols[0].dataType?.type; + } const currentScopeSymbol = sa.symbolTableStack.scope.getSymbol(lookupSymbol, true); if (currentScopeSymbol) { if ( @@ -1443,6 +1448,12 @@ export namespace ASTNode { } } + private _isMemberAccessMacro(sa: SemanticAnalyzer, child: MacroCallSymbol | MacroCallFunction): boolean { + const macroName = child.macroName; + const infos = sa.macroDefineList[macroName]; + return infos?.some((info) => info.valueType === MacroValueType.MemberAccess) ?? false; + } + override codeGen(visitor: CodeGenVisitor): string { return this.setCache(visitor.visitVariableIdentifier(this)); } From 7b18aef47ceede4998ac65c52823664304eba257 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 26 Mar 2026 23:23:00 +0800 Subject: [PATCH 013/130] test(shader-lab): add Cocos FSInput pattern test for member access macro as builtin arg --- tests/src/shader-lab/ShaderLab.test.ts | 26 ++++++++++ .../macro-member-access-builtin-arg.shader | 49 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 tests/src/shader-lab/shaders/macro-member-access-builtin-arg.shader diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index d424508682..d3d431a4d7 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -301,4 +301,30 @@ describe("ShaderLab", async () => { expect(vertex).to.equal(expectedVert); expect(fragment).to.equal(expectedFrag); }); + + it("macro-member-access-builtin-arg (Cocos FSInput pattern: member access macro as builtin fn arg)", async () => { + const shaderSource = await readFile("./shaders/macro-member-access-builtin-arg.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + + // Also verify verbose mode (semantic analysis) succeeds — this was the original bug: + // member access macros like #define FSInput_worldNormal v.v_normal.xyz resolved to + // struct type "Varyings" instead of TypeAny, causing builtin overload matching to fail. + const shader = shaderLabVerbose._parseShaderSource(shaderSource); + const passSource = shader.subShaders[0].passes[0]; + const { vertex, fragment } = shaderLabVerbose._parseShaderPass( + passSource.contents, + passSource.vertexEntry, + passSource.fragmentEntry, + 0, + "" + )!; + + expect(vertex).to.be.a("string").and.not.empty; + expect(fragment).to.be.a("string").and.not.empty; + + // Verify key builtins are present in output (macros expanded correctly) + expect(fragment).to.contain("normalize"); + expect(fragment).to.contain("dot"); + expect(fragment).to.contain("texture2D"); + }); }); diff --git a/tests/src/shader-lab/shaders/macro-member-access-builtin-arg.shader b/tests/src/shader-lab/shaders/macro-member-access-builtin-arg.shader new file mode 100644 index 0000000000..70ae36f3bc --- /dev/null +++ b/tests/src/shader-lab/shaders/macro-member-access-builtin-arg.shader @@ -0,0 +1,49 @@ +Shader "macro-member-access-builtin-arg-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec4 POSITION; vec3 NORMAL; vec2 TEXCOORD_0; }; + struct Varyings { vec2 v_uv; vec4 v_normal; vec3 v_worldPos; }; + + VertexShader = vert; + FragmentShader = frag; + + sampler2D u_texture; + vec3 u_lightDir; + vec3 u_cameraPos; + + // Cocos-style FSInput macros: member access used as builtin function args + #define FSInput_worldNormal v.v_normal.xyz + #define FSInput_faceSideSign v.v_normal.w + #define FSInput_worldPos v.v_worldPos + #define FSInput_texcoord v.v_uv + + Varyings vert(Attributes attr) { + Varyings o; + gl_Position = renderer_MVPMat * attr.POSITION; + o.v_uv = attr.TEXCOORD_0; + o.v_normal = vec4(attr.NORMAL, 1.0); + o.v_worldPos = attr.POSITION.xyz; + return o; + } + + void frag(Varyings v) { + // normalize() with member access macro as arg + vec3 N = normalize(FSInput_worldNormal); + + // dot() with member access macro as arg + float NdotL = dot(N, u_lightDir); + + // texture2D() with member access macro as arg + vec4 albedo = texture2D(u_texture, FSInput_texcoord); + + // mix() with member access macro and scalar macro + vec3 viewDir = normalize(u_cameraPos - FSInput_worldPos); + float rim = 1.0 - dot(N, viewDir); + + gl_FragColor = albedo * NdotL + vec4(vec3(rim), 0.0); + } + } + } +} From 80e6e3ebb37511350b61e004ce3a3be69db4e180 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Fri, 27 Mar 2026 00:33:21 +0800 Subject: [PATCH 014/130] fix(shader-lab): handle global struct-typed variables and simplify macro scanning - Suppress `uniform` output for global struct-typed variables (e.g. `Varyings o;`) - Register global struct vars in both per-function and cross-stage maps - Unify macro member access scanning into callback-based _forEachMacroMemberAccess - Add registerStructVar() encapsulation in VisitorContext - Add Cocos VSOutput pattern test (global-varying-var) --- .../shader-lab/src/codeGen/CodeGenVisitor.ts | 14 +++- .../shader-lab/src/codeGen/GLESVisitor.ts | 79 +++++++++++++++---- .../shader-lab/src/codeGen/VisitorContext.ts | 5 ++ tests/src/shader-lab/ShaderLab.test.ts | 32 ++++++++ .../shaders/global-varying-var.shader | 42 ++++++++++ 5 files changed, 157 insertions(+), 15 deletions(-) create mode 100644 tests/src/shader-lab/shaders/global-varying-var.shader diff --git a/packages/shader-lab/src/codeGen/CodeGenVisitor.ts b/packages/shader-lab/src/codeGen/CodeGenVisitor.ts index 1870a0e88b..f4d560c987 100644 --- a/packages/shader-lab/src/codeGen/CodeGenVisitor.ts +++ b/packages/shader-lab/src/codeGen/CodeGenVisitor.ts @@ -244,7 +244,19 @@ export abstract class CodeGenVisitor { const children = node.children; const fullType = children[0]; if (fullType instanceof ASTNode.FullySpecifiedType && fullType.typeSpecifier.isCustom) { - VisitorContext.context.referenceGlobal(fullType.type, ESymbolType.STRUCT); + const context = VisitorContext.context; + const typeLexeme = fullType.typeSpecifier.lexeme; + const role = context.getStructRole(typeLexeme); + if (role) { + // Global variable of a varying/attribute/mrt struct type (e.g. "Varyings o;"). + // Don't output as uniform; register the variable in struct var maps instead. + const ident = children[1]; + if (ident instanceof BaseToken) { + context.registerStructVar(ident.lexeme, role); + } + return ""; + } + context.referenceGlobal(fullType.type, ESymbolType.STRUCT); } return `uniform ${this.defaultCodeGen(children)}`; } diff --git a/packages/shader-lab/src/codeGen/GLESVisitor.ts b/packages/shader-lab/src/codeGen/GLESVisitor.ts index a530a06f89..d7596ae9ab 100644 --- a/packages/shader-lab/src/codeGen/GLESVisitor.ts +++ b/packages/shader-lab/src/codeGen/GLESVisitor.ts @@ -45,7 +45,7 @@ export abstract class GLESVisitor extends CodeGenVisitor { // Build combined _globalStructVarMap from both entry functions before per-stage processing. // This must happen here because vertex runs first and doesn't yet know fragment's variables. - this._buildGlobalStructVarMap(vertexEntry, fragmentEntry, shaderData, context); + this._buildGlobalStructVarMap(vertexEntry, fragmentEntry, shaderData, outerGlobalMacroDeclarations, context); return { vertex: this._vertexMain(vertexEntry, shaderData, outerGlobalMacroDeclarations), @@ -215,6 +215,7 @@ export abstract class GLESVisitor extends CodeGenVisitor { vertexEntry: string, fragmentEntry: string, data: ShaderData, + globalMacros: ASTNode.GlobalDeclaration[], context: VisitorContext ): void { const map = context._globalStructVarMap; @@ -266,6 +267,33 @@ export abstract class GLESVisitor extends CodeGenVisitor { } this._collectStructVarsFromBody(fnNode.statements, structTypeRoles, map); } + + // Also scan global macros for root variable names that might be global struct variables. + // e.g. #define VSOutput_worldPos o.v_worldPos → root "o" → look up in symbol table + let hasRoles = false; + for (const _ in structTypeRoles) { + hasRoles = true; + break; + } + if (hasRoles) { + const checked = new Set(); + const symOut: SymbolInfo[] = []; + this._forEachMacroMemberAccess(globalMacros, (rootName) => { + if (map[rootName] || checked.has(rootName)) return; + checked.add(rootName); + lookupSymbol.set(rootName, ESymbolType.VAR); + symbolTable.getSymbols(lookupSymbol, true, symOut); + for (const sym of symOut) { + if (sym.dataType) { + const role = structTypeRoles[sym.dataType.typeLexeme]; + if (role) { + map[rootName] = role; + break; + } + } + } + }); + } } private _collectStructVarsFromBody( @@ -296,18 +324,36 @@ export abstract class GLESVisitor extends CodeGenVisitor { */ private _registerGlobalMacroReferences(globalMacros: ASTNode.GlobalDeclaration[], context: VisitorContext): void { const map = context._globalStructVarMap; - if (!Object.keys(map).length) return; + let hasEntries = false; + for (const _ in map) { + hasEntries = true; + break; + } + if (!hasEntries) return; + this._forEachMacroMemberAccess(globalMacros, (rootName, propName) => { + const role = map[rootName]; + if (role) context.referenceStructPropByName(role, propName); + }); + } + + /** + * Traverse global macro declarations, extracting member access patterns (e.g. "o.v_uv") + * and invoking the callback with (rootName, propName) for each match. + */ + private _forEachMacroMemberAccess( + macros: ASTNode.GlobalDeclaration[], + callback: (rootName: string, propName: string) => void + ): void { const reg = CodeGenVisitor._memberAccessReg; - for (const macro of globalMacros) { - this._scanMacroMemberAccess(macro.children, reg, map, context); + for (const macro of macros) { + this._walkMacroChildren(macro.children, reg, callback); } } - private _scanMacroMemberAccess( + private _walkMacroChildren( children: NodeChild[], reg: RegExp, - map: Record, - context: VisitorContext + callback: (rootName: string, propName: string) => void ): void { for (const child of children) { if (child instanceof BaseToken && child.type === Keyword.MACRO_DEFINE_EXPRESSION) { @@ -317,19 +363,20 @@ export abstract class GLESVisitor extends CodeGenVisitor { reg.lastIndex = 0; let match: RegExpExecArray | null; while ((match = reg.exec(value)) !== null) { - const role = map[match[1]]; - if (role) context.referenceStructPropByName(role, match[2]); + callback(match[1], match[2]); } } else if (child instanceof TreeNode) { - this._scanMacroMemberAccess(child.children, reg, map, context); + this._walkMacroChildren(child.children, reg, callback); } } } private _getGlobalSymbol(out: ICodeSegment[]): void { - const { _referencedGlobals } = VisitorContext.context; + const context = VisitorContext.context; + const { _referencedGlobals } = context; - const lastLength = Object.keys(_referencedGlobals).length; + let lastLength = 0; + for (const _ in _referencedGlobals) lastLength++; if (lastLength === 0) return; for (const ident in _referencedGlobals) { @@ -339,7 +386,9 @@ export abstract class GLESVisitor extends CodeGenVisitor { const symbols = _referencedGlobals[ident]; for (let i = 0; i < symbols.length; i++) { const sm = symbols[i]; - const text = sm.astNode.codeGen(this) + (sm.type === ESymbolType.VAR ? ";" : ""); + const codeGenResult = sm.astNode.codeGen(this); + if (!codeGenResult) continue; + const text = codeGenResult + (sm.type === ESymbolType.VAR ? ";" : ""); if (!sm.isInMacroBranch) { out.push({ text, @@ -349,7 +398,9 @@ export abstract class GLESVisitor extends CodeGenVisitor { } } - if (Object.keys(_referencedGlobals).length !== lastLength) { + let newLength = 0; + for (const _ in _referencedGlobals) newLength++; + if (newLength !== lastLength) { this._getGlobalSymbol(out); } } diff --git a/packages/shader-lab/src/codeGen/VisitorContext.ts b/packages/shader-lab/src/codeGen/VisitorContext.ts index 3ac2a3eb38..1c1972c0db 100644 --- a/packages/shader-lab/src/codeGen/VisitorContext.ts +++ b/packages/shader-lab/src/codeGen/VisitorContext.ts @@ -72,6 +72,11 @@ export class VisitorContext { if (this.isMRTStruct(typeLexeme)) return "mrt"; } + registerStructVar(varName: string, role: "varying" | "attribute" | "mrt"): void { + this._structVarMap[varName] = role; + this._globalStructVarMap[varName] = role; + } + referenceStructPropByName(role: "varying" | "attribute" | "mrt", propName: string): void { const list = role === "varying" ? this.varyingList : role === "attribute" ? this.attributeList : this.mrtList; const refList = diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index d3d431a4d7..e68a9c3c09 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -327,4 +327,36 @@ describe("ShaderLab", async () => { expect(fragment).to.contain("dot"); expect(fragment).to.contain("texture2D"); }); + + it("global-varying-var (Cocos VSOutput pattern: global Varyings var with #define macros)", async () => { + const shaderSource = await readFile("./shaders/global-varying-var.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + + // Verify verbose mode: global "Varyings o;" should not produce "uniform Varyings o;" + // and should not duplicate varying declarations. + const shader = shaderLabVerbose._parseShaderSource(shaderSource); + const passSource = shader.subShaders[0].passes[0]; + const { vertex, fragment } = shaderLabVerbose._parseShaderPass( + passSource.contents, + passSource.vertexEntry, + passSource.fragmentEntry, + 0, + "" + )!; + + expect(vertex).to.be.a("string").and.not.empty; + expect(fragment).to.be.a("string").and.not.empty; + + // No "uniform Varyings o;" in output + expect(vertex).to.not.contain("uniform Varyings"); + expect(fragment).to.not.contain("uniform Varyings"); + + // Macros should be transformed: "o.v_worldPos" → "v_worldPos" + expect(vertex).to.contain("#define VSOutput_worldPos v_worldPos"); + expect(vertex).to.contain("#define VSOutput_worldNormal v_normal.xyz"); + + // No duplicate varying declarations + const varyingMatches = vertex.match(/varying vec3 v_worldPos/g); + expect(varyingMatches).to.have.lengthOf(1); + }); }); diff --git a/tests/src/shader-lab/shaders/global-varying-var.shader b/tests/src/shader-lab/shaders/global-varying-var.shader new file mode 100644 index 0000000000..bd6a5cf477 --- /dev/null +++ b/tests/src/shader-lab/shaders/global-varying-var.shader @@ -0,0 +1,42 @@ +Shader "global-varying-var-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec3 POSITION; vec3 NORMAL; vec2 TEXCOORD_0; }; + struct Varyings { vec3 v_worldPos; vec4 v_normal; vec2 v_uv; vec4 v_shadowBiasAndProbeId; }; + + VertexShader = vert; + FragmentShader = frag; + + sampler2D u_texture; + vec3 u_lightDir; + + #define VSOutput_worldPos o.v_worldPos + #define VSOutput_worldNormal o.v_normal.xyz + #define VSOutput_faceSideSign o.v_normal.w + #define VSOutput_texcoord o.v_uv + #define VSOutput_shadowBias o.v_shadowBiasAndProbeId.xy + + Varyings o; + + Varyings vert(Attributes input) { + mat4 matWorld = renderer_MVPMat; + vec4 pos = matWorld * vec4(input.POSITION, 1.0); + VSOutput_worldPos = pos.xyz; + VSOutput_worldNormal = input.NORMAL; + VSOutput_faceSideSign = 1.0; + VSOutput_texcoord = input.TEXCOORD_0; + VSOutput_shadowBias = vec2(0.0, 0.0); + gl_Position = pos; + return o; + } + + void frag(Varyings v) { + vec3 N = normalize(v.v_normal.xyz); + float NdotL = dot(N, u_lightDir); + gl_FragColor = texture2D(u_texture, v.v_uv) * NdotL; + } + } + } +} From bfe4e9ea2393c4fd7f61771e8b37399428d96715 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Fri, 27 Mar 2026 01:06:15 +0800 Subject: [PATCH 015/130] fix(shader-lab): add missing semicolon in GLES100 fragment return conversion GLES100 visitJumpStatement converted `return expr;` to `gl_FragColor = expr` without a trailing semicolon, causing WebGL compilation errors. Only triggered when fragment entry returns vec4 (Cocos pattern), not void (standard Galacean). --- packages/shader-lab/src/codeGen/GLES100.ts | 2 +- tests/src/shader-lab/ShaderLab.test.ts | 6 +++ .../shaders/frag-return-vec4.shader | 41 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/src/shader-lab/shaders/frag-return-vec4.shader diff --git a/packages/shader-lab/src/codeGen/GLES100.ts b/packages/shader-lab/src/codeGen/GLES100.ts index 995dccdeec..2c2e0faa93 100644 --- a/packages/shader-lab/src/codeGen/GLES100.ts +++ b/packages/shader-lab/src/codeGen/GLES100.ts @@ -47,7 +47,7 @@ export class GLES100Visitor extends GLESVisitor { return ""; } const expression = node.children[1] as ASTNode.Expression; - return `gl_FragColor = ${expression.codeGen(this)}`; + return `gl_FragColor = ${expression.codeGen(this)};`; } return super.visitJumpStatement(node); } diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index e68a9c3c09..4494ecb6e3 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -359,4 +359,10 @@ describe("ShaderLab", async () => { const varyingMatches = vertex.match(/varying vec3 v_worldPos/g); expect(varyingMatches).to.have.lengthOf(1); }); + + it("frag-return-vec4 (Cocos pattern: fragment entry returns vec4 instead of void)", async () => { + const shaderSource = await readFile("./shaders/frag-return-vec4.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + glslValidate(engine, shaderSource, shaderLabVerbose); + }); }); diff --git a/tests/src/shader-lab/shaders/frag-return-vec4.shader b/tests/src/shader-lab/shaders/frag-return-vec4.shader new file mode 100644 index 0000000000..9b3ea3d4ca --- /dev/null +++ b/tests/src/shader-lab/shaders/frag-return-vec4.shader @@ -0,0 +1,41 @@ +Shader "frag-return-vec4-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec3 POSITION; vec3 NORMAL; vec2 TEXCOORD_0; }; + struct Varyings { vec3 v_worldPos; vec4 v_normal; vec2 v_uv; }; + + VertexShader = vert; + FragmentShader = frag; + + sampler2D u_texture; + vec3 u_lightDir; + + vec3 SRGBToLinear(vec3 gamma) { return gamma * gamma; } + vec3 LinearToSRGB(vec3 linear) { return sqrt(linear); } + + vec4 SurfacesFragmentModifyBaseColorAndTransparency(Varyings input) { + vec4 color = texture2D(u_texture, input.v_uv); + color.rgb = SRGBToLinear(color.rgb); + return color; + } + + Varyings vert(Attributes attr) { + Varyings o; + vec4 pos = renderer_MVPMat * vec4(attr.POSITION, 1.0); + o.v_worldPos = pos.xyz; + o.v_normal = vec4(attr.NORMAL, 1.0); + o.v_uv = attr.TEXCOORD_0; + gl_Position = pos; + return o; + } + + vec4 frag(Varyings input) { + vec4 color = SurfacesFragmentModifyBaseColorAndTransparency(input); + color.rgb = LinearToSRGB(color.rgb); + return color; + } + } + } +} From b7146090d3ebd5116e8f782507016d5f3253e62d Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Fri, 27 Mar 2026 10:59:04 +0800 Subject: [PATCH 016/130] fix: temp --- packages/core/src/mesh/Skin.ts | 2 +- packages/loader/src/gltf/parser/GLTFAnimationParser.ts | 8 ++++++++ packages/rhi-webgl/src/GLPrimitive.ts | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/core/src/mesh/Skin.ts b/packages/core/src/mesh/Skin.ts index 7a9b3444b0..18489805c5 100644 --- a/packages/core/src/mesh/Skin.ts +++ b/packages/core/src/mesh/Skin.ts @@ -15,7 +15,7 @@ export class Skin extends EngineObject { inverseBindMatrices = new Array(); /** @internal */ - @ignoreClone + @deepClone _skinMatrices: Float32Array; /** @internal */ @ignoreClone diff --git a/packages/loader/src/gltf/parser/GLTFAnimationParser.ts b/packages/loader/src/gltf/parser/GLTFAnimationParser.ts index d1d7400c2f..dbb5c65d42 100644 --- a/packages/loader/src/gltf/parser/GLTFAnimationParser.ts +++ b/packages/loader/src/gltf/parser/GLTFAnimationParser.ts @@ -118,6 +118,14 @@ export class GLTFAnimationParser extends GLTFParser { continue; } + // For single-root scenes, the scene root IS the top-level node (e.g. mixamorig:Hips). + // When this clip is used on a multi-root model (which wraps nodes in a GLTF_ROOT container), + // the Animator sits on GLTF_ROOT and needs the root node name in the path. + // Include the scene root name for single-root scenes to ensure consistent bone paths. + const sceneNodes = context.glTF.scenes[context.glTF.scene ?? 0]?.nodes; + if (sceneNodes?.length === 1 && relativePath !== "") { + relativePath = `${entity.name}/${relativePath}`; + } let ComponentType: ComponentConstructor; let propertyName: string; switch (target.path) { diff --git a/packages/rhi-webgl/src/GLPrimitive.ts b/packages/rhi-webgl/src/GLPrimitive.ts index fa7d89f1e8..b679bc4699 100644 --- a/packages/rhi-webgl/src/GLPrimitive.ts +++ b/packages/rhi-webgl/src/GLPrimitive.ts @@ -118,6 +118,7 @@ export class GLPrimitive implements IPlatformPrimitive { const element = attributes[name]; if (element) { + if(!vertexBufferBindings[element.bindingIndex]) continue; const { buffer, stride } = vertexBufferBindings[element.bindingIndex]; vbo = buffer._platformBuffer._glBuffer; // prevent binding the vbo which already bound at the last loop, e.g. a buffer with multiple attributes. From 1749077779554d8732a1385d4bb9767dd127b045 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Fri, 27 Mar 2026 12:14:57 +0800 Subject: [PATCH 017/130] fix: temp --- packages/loader/src/GLTFLoader.ts | 2 +- packages/ui/src/component/UIRenderer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/loader/src/GLTFLoader.ts b/packages/loader/src/GLTFLoader.ts index c43a62d3e6..feadc93f90 100644 --- a/packages/loader/src/GLTFLoader.ts +++ b/packages/loader/src/GLTFLoader.ts @@ -40,7 +40,7 @@ export class GLTFLoader extends Loader { const params = item.params; const glTFResource = new GLTFResource(resourceManager.engine, item.url); const context = new GLTFParserContext(glTFResource, resourceManager, { - keepMeshData: false, + keepMeshData: true, ...params }); diff --git a/packages/ui/src/component/UIRenderer.ts b/packages/ui/src/component/UIRenderer.ts index 59a2fc434c..71420f43bb 100644 --- a/packages/ui/src/component/UIRenderer.ts +++ b/packages/ui/src/component/UIRenderer.ts @@ -71,7 +71,7 @@ export class UIRenderer extends Renderer implements IGraphics { _subChunk; @assignmentClone - private _raycastEnabled: boolean = true; + private _raycastEnabled: boolean = false; @deepClone protected _color: Color = new Color(1, 1, 1, 1); From c662731f2fa47ccabbfc2d0b2672fb0b3aa65fd2 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Fri, 27 Mar 2026 15:12:36 +0800 Subject: [PATCH 018/130] fix: entity disable --- packages/core/src/ComponentsManager.ts | 10 ----- packages/core/src/Entity.ts | 43 +++++++++------------- packages/core/src/asset/ResourceManager.ts | 35 ++++++++++-------- 3 files changed, 37 insertions(+), 51 deletions(-) diff --git a/packages/core/src/ComponentsManager.ts b/packages/core/src/ComponentsManager.ts index e9b9a1cc0d..96924c1a60 100644 --- a/packages/core/src/ComponentsManager.ts +++ b/packages/core/src/ComponentsManager.ts @@ -36,8 +36,6 @@ export class ComponentsManager { // Render private _onUpdateRenderers = new DisorderedArray(); - // Delay dispose active/inActive Pool - private _componentsContainerPool: Component[][] = []; addCamera(camera: Camera) { camera._cameraIndex = this._activeCameras.length; @@ -270,14 +268,6 @@ export class ComponentsManager { ); } - getActiveChangedTempList(): Component[] { - return this._componentsContainerPool.length ? this._componentsContainerPool.pop() : []; - } - - putActiveChangedTempList(componentContainer: Component[]): void { - componentContainer.length = 0; - this._componentsContainerPool.push(componentContainer); - } /** * @internal diff --git a/packages/core/src/Entity.ts b/packages/core/src/Entity.ts index 08503f5498..4540cebf1f 100644 --- a/packages/core/src/Entity.ts +++ b/packages/core/src/Entity.ts @@ -122,7 +122,7 @@ export class Entity extends EngineObject { private _transform: Transform; private _templateResource: ReferResource; private _parent: Entity = null; - private _activeChangedComponents: Component[]; + private _isActiveChanging: boolean = false; private _modifyFlagManager: UpdateFlagManager; /** @@ -565,24 +565,24 @@ export class Entity extends EngineObject { * @internal */ _processActive(activeChangeFlag: ActiveChangeFlag): void { - if (this._activeChangedComponents) { + if (this._isActiveChanging) { throw "Note: can't set the 'main inActive entity' active in hierarchy, if the operation is in main inActive entity or it's children script's onDisable Event."; } - this._activeChangedComponents = this._scene._componentsManager.getActiveChangedTempList(); - this._setActiveInHierarchy(this._activeChangedComponents, activeChangeFlag); - this._setActiveComponents(true, activeChangeFlag); + this._isActiveChanging = true; + this._setActiveInHierarchy(activeChangeFlag); + this._isActiveChanging = false; } /** * @internal */ _processInActive(activeChangeFlag: ActiveChangeFlag): void { - if (this._activeChangedComponents) { + if (this._isActiveChanging) { throw "Note: can't set the 'main active entity' inActive in hierarchy, if the operation is in main active entity or it's children script's onEnable Event."; } - this._activeChangedComponents = this._scene._componentsManager.getActiveChangedTempList(); - this._setInActiveInHierarchy(this._activeChangedComponents, activeChangeFlag); - this._setActiveComponents(false, activeChangeFlag); + this._isActiveChanging = true; + this._setInActiveInHierarchy(activeChangeFlag); + this._isActiveChanging = false; } /** @@ -690,42 +690,33 @@ export class Entity extends EngineObject { } } - private _setActiveComponents(isActive: boolean, activeChangeFlag: ActiveChangeFlag): void { - const activeChangedComponents = this._activeChangedComponents; - for (let i = 0, length = activeChangedComponents.length; i < length; ++i) { - activeChangedComponents[i]._setActive(isActive, activeChangeFlag); - } - this._scene._componentsManager.putActiveChangedTempList(activeChangedComponents); - this._activeChangedComponents = null; - } - - private _setActiveInHierarchy(activeChangedComponents: Component[], activeChangeFlag: ActiveChangeFlag): void { + private _setActiveInHierarchy(activeChangeFlag: ActiveChangeFlag): void { activeChangeFlag & ActiveChangeFlag.Hierarchy && (this._isActiveInHierarchy = true); activeChangeFlag & ActiveChangeFlag.Scene && (this._isActiveInScene = true); const components = this._components; for (let i = 0, n = components.length; i < n; i++) { const component = components[i]; - (component.enabled || !component._awoken) && activeChangedComponents.push(component); + (component.enabled || !component._awoken) && component._setActive(true, activeChangeFlag); } const children = this._children; - for (let i = 0, n = children.length; i < n; i++) { + for (let i = children.length - 1; i >= 0; i--) { const child = children[i]; - child.isActive && child._setActiveInHierarchy(activeChangedComponents, activeChangeFlag); + child.isActive && child._setActiveInHierarchy(activeChangeFlag); } } - private _setInActiveInHierarchy(activeChangedComponents: Component[], activeChangeFlag: ActiveChangeFlag): void { + private _setInActiveInHierarchy(activeChangeFlag: ActiveChangeFlag): void { activeChangeFlag & ActiveChangeFlag.Hierarchy && (this._isActiveInHierarchy = false); activeChangeFlag & ActiveChangeFlag.Scene && (this._isActiveInScene = false); const components = this._components; for (let i = 0, n = components.length; i < n; i++) { const component = components[i]; - component.enabled && activeChangedComponents.push(component); + component.enabled && component._setActive(false, activeChangeFlag); } const children = this._children; - for (let i = 0, n = children.length; i < n; i++) { + for (let i = children.length - 1; i >= 0; i--) { const child = children[i]; - child.isActive && child._setInActiveInHierarchy(activeChangedComponents, activeChangeFlag); + child.isActive && child._setInActiveInHierarchy(activeChangeFlag); } } diff --git a/packages/core/src/asset/ResourceManager.ts b/packages/core/src/asset/ResourceManager.ts index 0395b2b29b..31d04f524d 100644 --- a/packages/core/src/asset/ResourceManager.ts +++ b/packages/core/src/asset/ResourceManager.ts @@ -58,7 +58,7 @@ export class ResourceManager { * Create a ResourceManager. * @param engine - Engine to which the current ResourceManager belongs */ - constructor(public readonly engine: Engine) {} + constructor(public readonly engine: Engine) { } /** * Load the asset asynchronously by asset item information. @@ -342,7 +342,12 @@ export class ResourceManager { private _assignDefaultOptions(assetInfo: LoadItem): LoadItem { assetInfo.type = assetInfo.type ?? ResourceManager._getTypeByUrl(assetInfo.url); if (assetInfo.type === undefined) { - throw `asset type should be specified: ${assetInfo.url}`; + const remoteConfig = this._virtualPathResourceMap[assetInfo.url]; + if (remoteConfig) { + assetInfo.type = remoteConfig.type; + } else { + throw `asset type should be specified: ${assetInfo.url}`; + } } assetInfo.retryCount = assetInfo.retryCount ?? this.retryCount; assetInfo.timeout = assetInfo.timeout ?? this.timeout; @@ -621,7 +626,7 @@ export class ResourceManager { * @param extNames - Name of file extension */ export function resourceLoader(assetType: string, extNames: string[], useCache: boolean = true) { - return >(Target: { new (useCache: boolean): T }) => { + return >(Target: { new(useCache: boolean): T }) => { const loader = new Target(useCache); ResourceManager._addLoader(assetType, loader, extNames); }; @@ -632,18 +637,18 @@ const reEscapeChar = /\\(\\)?/g; const rePropName = RegExp( // Match anything that isn't a dot or bracket. "[^.[\\]]+" + - "|" + - // Or match property names within brackets. - "\\[(?:" + - // Match a non-string expression. - "([^\"'][^[]*)" + - "|" + - // Or match strings (supports escaping characters). - "([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2" + - ")\\]" + - "|" + - // Or match "" as the space between consecutive dots or empty brackets. - "(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))", + "|" + + // Or match property names within brackets. + "\\[(?:" + + // Match a non-string expression. + "([^\"'][^[]*)" + + "|" + + // Or match strings (supports escaping characters). + "([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2" + + ")\\]" + + "|" + + // Or match "" as the space between consecutive dots or empty brackets. + "(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))", "g" ); From d0dada8e52d0c946d218d9b8218431ed3a7a9175 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Fri, 27 Mar 2026 15:17:06 +0800 Subject: [PATCH 019/130] fix: keep mesh data --- packages/loader/src/GLTFLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/loader/src/GLTFLoader.ts b/packages/loader/src/GLTFLoader.ts index feadc93f90..c43a62d3e6 100644 --- a/packages/loader/src/GLTFLoader.ts +++ b/packages/loader/src/GLTFLoader.ts @@ -40,7 +40,7 @@ export class GLTFLoader extends Loader { const params = item.params; const glTFResource = new GLTFResource(resourceManager.engine, item.url); const context = new GLTFParserContext(glTFResource, resourceManager, { - keepMeshData: true, + keepMeshData: false, ...params }); From 3cf18831d45f69e3fd5005420bbe9ee690f4596d Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Fri, 27 Mar 2026 17:55:51 +0800 Subject: [PATCH 020/130] fix(shader-lab): allow logical NOT operator on numeric operands in preprocessor #if !0 and similar expressions now work correctly, matching C/GLSL preprocessor behavior. --- .../src/macroProcessor/MacroParser.ts | 2 +- tests/src/shader-lab/ShaderLab.test.ts | 6 +++ .../shaders/macro-negate-number.shader | 41 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/src/shader-lab/shaders/macro-negate-number.shader diff --git a/packages/shader-lab/src/macroProcessor/MacroParser.ts b/packages/shader-lab/src/macroProcessor/MacroParser.ts index d845102e1a..a2ed90c504 100644 --- a/packages/shader-lab/src/macroProcessor/MacroParser.ts +++ b/packages/shader-lab/src/macroProcessor/MacroParser.ts @@ -355,7 +355,7 @@ export class MacroParser { scanner.advance(1); scanner.skipSpace(false); const parenExpr = this._parseParenthesisExpression(scanner); - if ((operator === "!" && typeof parenExpr !== "boolean") || (operator !== "!" && typeof parenExpr !== "number")) { + if (operator !== "!" && typeof parenExpr !== "number") { this._reportError(opPos, "invalid operator.", scanner.source, scanner.file); } diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index 4494ecb6e3..b1d5085044 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -247,6 +247,12 @@ describe("ShaderLab", async () => { glslValidate(engine, shaderSource, shaderLabRelease); }); + it("macro-negate-number (!0, !1 in #if expressions)", async () => { + const shaderSource = await readFile("./shaders/macro-negate-number.shader"); + glslValidate(engine, shaderSource, shaderLabVerbose); + glslValidate(engine, shaderSource, shaderLabRelease); + }); + it("mrt-struct", async () => { const shaderSource = await readFile("./shaders/mrt-struct.shader"); glslValidate(engine, shaderSource, shaderLabRelease); diff --git a/tests/src/shader-lab/shaders/macro-negate-number.shader b/tests/src/shader-lab/shaders/macro-negate-number.shader new file mode 100644 index 0000000000..a224605393 --- /dev/null +++ b/tests/src/shader-lab/shaders/macro-negate-number.shader @@ -0,0 +1,41 @@ +Shader "macro-negate-number-test" { + SubShader "default" { + Pass "default" { + struct a2v { + vec4 POSITION; + }; + + mat4 renderer_MVPMat; + + #define SCENE_FOG_MODE 1 + + VertexShader = vert; + FragmentShader = frag; + + void vert(a2v v) { + gl_Position = renderer_MVPMat * v.POSITION; + } + + void frag() { + vec4 color = vec4(1.0); + + // Test: !0 should evaluate to true (like C preprocessor) + #if SCENE_FOG_MODE != 4 && (!0 || SCENE_FOG_MODE) + color = vec4(0.0, 1.0, 0.0, 1.0); + #endif + + // Test: !1 should evaluate to false + #if !1 + color = vec4(1.0, 0.0, 0.0, 1.0); + #endif + + // Test: !0 alone + #if !0 + color = vec4(0.0, 0.0, 1.0, 1.0); + #endif + + gl_FragColor = color; + } + } + } +} From 41f024e1f8bea6189659e3b70e21d2eb566b2b88 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Fri, 27 Mar 2026 21:08:39 +0800 Subject: [PATCH 021/130] fix(camera): make invViewProjMat ignore scale consistently with viewMatrix The viewMatrix getter intentionally ignores the camera entity's world scale (using Matrix.rotationTranslation), but _getInvViewProjMat() was using the full entity.transform.worldMatrix which includes inherited scale. This inconsistency causes screenPointToRay to produce incorrect world-space rays when the camera inherits scale from a parent entity (e.g. UICanvas in ScreenSpaceCamera mode with camera as a child of canvas). Closes #2748 Co-Authored-By: Claude Opus 4.6 --- packages/core/src/Camera.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/core/src/Camera.ts b/packages/core/src/Camera.ts index 53b689dd84..55d904f6c9 100644 --- a/packages/core/src/Camera.ts +++ b/packages/core/src/Camera.ts @@ -934,7 +934,15 @@ export class Camera extends Component { private _getInvViewProjMat(): Matrix { if (this._isInvViewProjDirty.flag) { this._isInvViewProjDirty.flag = false; - Matrix.multiply(this._entity.transform.worldMatrix, this._getInverseProjectionMatrix(), this._invViewProjMat); + const matrix = this._invViewProjMat; + if (this._isCustomViewMatrix) { + Matrix.invert(this.viewMatrix, matrix); + } else { + // Ignore scale, consistent with viewMatrix getter + const transform = this._entity.transform; + Matrix.rotationTranslation(transform.worldRotationQuaternion, transform.worldPosition, matrix); + } + matrix.multiply(this._getInverseProjectionMatrix()); } return this._invViewProjMat; } From 63dff37450da383e626891a773a65b04cbc7dc0d Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Fri, 27 Mar 2026 21:17:31 +0800 Subject: [PATCH 022/130] test(camera): add unit test for invViewProjMat scale consistency Verify that screenPointToRay and viewport-world round-trip produce correct results when the camera inherits non-identity scale from a parent entity. Without the fix, the round-trip deviates by the inherited scale factor (e.g. 105 -> 107.5 at scale 1.5). Co-Authored-By: Claude Opus 4.6 --- tests/src/core/Camera.test.ts | 58 +++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/tests/src/core/Camera.test.ts b/tests/src/core/Camera.test.ts index 52ae7890d1..5f77077f7f 100644 --- a/tests/src/core/Camera.test.ts +++ b/tests/src/core/Camera.test.ts @@ -245,10 +245,64 @@ describe("camera test", function () { expect(Math.abs(ray.direction.z)).not.eq(Infinity); }); + it("screenPointToRay should ignore inherited scale from parent entity", () => { + // Simulate UICanvas scenario: camera is a child of a scaled parent entity + const scene = engine.sceneManager.scenes[0]; + const parentEntity = scene.createRootEntity("scaledParent"); + parentEntity.transform.setScale(1.5, 1.5, 1.5); + parentEntity.transform.setPosition(100, 200, 0); + + const childEntity = parentEntity.createChild("cameraChild"); + childEntity.transform.setPosition(0, 0, 500); + const scaledCamera = childEntity.addComponent(Camera); + scaledCamera.isOrthographic = true; + scaledCamera.orthographicSize = 5; + scaledCamera.nearClipPlane = 0.1; + scaledCamera.farClipPlane = 1000; + + // A camera without inherited scale at the same world position/rotation for comparison + const refEntity = scene.createRootEntity("refCamera"); + refEntity.transform.setWorldPosition( + childEntity.transform.worldPosition.x, + childEntity.transform.worldPosition.y, + childEntity.transform.worldPosition.z + ); + const refCamera = refEntity.addComponent(Camera); + refCamera.isOrthographic = true; + refCamera.orthographicSize = 5; + refCamera.nearClipPlane = 0.1; + refCamera.farClipPlane = 1000; + + // Both cameras should produce the same ray for the same screen point + const screenPoint = new Vector2(128, 128); + const rayScaled = scaledCamera.screenPointToRay(screenPoint, new Ray()); + const rayRef = refCamera.screenPointToRay(screenPoint, new Ray()); + + expect(rayScaled.origin.x).to.be.closeTo(rayRef.origin.x, 0.001); + expect(rayScaled.origin.y).to.be.closeTo(rayRef.origin.y, 0.001); + expect(rayScaled.direction.x).to.be.closeTo(rayRef.direction.x, 0.001); + expect(rayScaled.direction.y).to.be.closeTo(rayRef.direction.y, 0.001); + expect(rayScaled.direction.z).to.be.closeTo(rayRef.direction.z, 0.001); + + // Round-trip: worldToViewportPoint -> viewportToWorldPoint should be accurate + const worldPoint = new Vector3(105, 210, 0); + const viewportPoint = scaledCamera.worldToViewportPoint(worldPoint, new Vector3()); + const recoveredPoint = scaledCamera.viewportToWorldPoint(viewportPoint, new Vector3()); + expect(recoveredPoint.x).to.be.closeTo(worldPoint.x, 0.01); + expect(recoveredPoint.y).to.be.closeTo(worldPoint.y, 0.01); + expect(recoveredPoint.z).to.be.closeTo(worldPoint.z, 0.01); + + // Clean up + scaledCamera.destroy(); + refCamera.destroy(); + parentEntity.destroy(); + refEntity.destroy(); + }); + /* Attention: - Below methods will change the default view of current Camera. - If executed in advance, it will affect the expected results of other test cases, + Below methods will change the default view of current Camera. + If executed in advance, it will affect the expected results of other test cases, so it should be placed at the end of the test case execution. */ it("projection matrix", () => { From ca0d518383f40a3b6bf5eee6cdcdf69cbb632fc3 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Sat, 28 Mar 2026 18:45:43 +0800 Subject: [PATCH 023/130] fix: use project type --- packages/core/src/asset/ResourceManager.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/src/asset/ResourceManager.ts b/packages/core/src/asset/ResourceManager.ts index 31d04f524d..b83a9360c4 100644 --- a/packages/core/src/asset/ResourceManager.ts +++ b/packages/core/src/asset/ResourceManager.ts @@ -340,14 +340,14 @@ export class ResourceManager { } private _assignDefaultOptions(assetInfo: LoadItem): LoadItem { - assetInfo.type = assetInfo.type ?? ResourceManager._getTypeByUrl(assetInfo.url); + const remoteConfig = this._virtualPathResourceMap[assetInfo.url]; + if (remoteConfig) { + assetInfo.type = remoteConfig.type; + } else { + assetInfo.type = assetInfo.type ?? ResourceManager._getTypeByUrl(assetInfo.url); + } if (assetInfo.type === undefined) { - const remoteConfig = this._virtualPathResourceMap[assetInfo.url]; - if (remoteConfig) { - assetInfo.type = remoteConfig.type; - } else { - throw `asset type should be specified: ${assetInfo.url}`; - } + throw `asset type should be specified: ${assetInfo.url}`; } assetInfo.retryCount = assetInfo.retryCount ?? this.retryCount; assetInfo.timeout = assetInfo.timeout ?? this.timeout; From 13521cc420d009351a30bc1ff99643b294aaf99b Mon Sep 17 00:00:00 2001 From: luzhuang Date: Sun, 29 Mar 2026 18:53:54 +0800 Subject: [PATCH 024/130] =?UTF-8?q?fix(loader):=20componentRef=20=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E6=94=AF=E6=8C=81=20clone=20entity=20=E5=AD=90?= =?UTF-8?q?=E6=A0=91=E6=9F=A5=E6=89=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ReflectionParser 解析 IComponentRef(entityPath + componentType)时, 目标组件可能在 GLB clone 的子 entity 上而非 clone 根 entity 自身。 getComponents 找不到时 fallback 到 getComponentsIncludeChildren。 --- .../resources/parser/ReflectionParser.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/loader/src/resource-deserialize/resources/parser/ReflectionParser.ts b/packages/loader/src/resource-deserialize/resources/parser/ReflectionParser.ts index e79ea06864..fe92bbcf6a 100644 --- a/packages/loader/src/resource-deserialize/resources/parser/ReflectionParser.ts +++ b/packages/loader/src/resource-deserialize/resources/parser/ReflectionParser.ts @@ -125,7 +125,14 @@ export class ReflectionParser { if (!entity) return Promise.resolve(null); const type = Loader.getClass(value.componentType); if (!type) return Promise.resolve(null); - return Promise.resolve(entity.getComponents(type, [])[value.componentIndex] ?? null); + // Try direct components first, fallback to children search (for GLB clone entities + // where the component lives on a child entity inside the clone) + const direct = entity.getComponents(type, []); + const result = direct[value.componentIndex]; + if (result) return Promise.resolve(result); + const includeChildren: any[] = []; + entity.getComponentsIncludeChildren(type, includeChildren); + return Promise.resolve(includeChildren[value.componentIndex] ?? null); } else if (ReflectionParser._isEntityRef(value)) { return Promise.resolve(this._resolveEntityByPath(value.entityPath)); } else if (ReflectionParser._isSignalRef(value)) { From a6156ba9fbd785681a9d2c16678481d82f6774b9 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Sun, 29 Mar 2026 19:02:38 +0800 Subject: [PATCH 025/130] =?UTF-8?q?fix(clone):=20prefab=20=E5=85=8B?= =?UTF-8?q?=E9=9A=86=E6=97=B6=E8=87=AA=E5=8A=A8=20deep=20clone=20=E5=90=8C?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=20Object=20=E5=B1=9E=E6=80=A7=EF=BC=8C?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E5=85=B1=E4=BA=AB=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CloneManager: 当 source 和 target 属性是同类型 Object 实例(如 Vector4)时, 自动升级为 DeepClone,避免 prefab 模板的引用覆盖克隆体的独立实例。 新增 Map/Set 类型的 deep clone 支持。 ModelMesh: throw string 改为 throw Error 以获得正确堆栈。 --- packages/core/src/clone/CloneManager.ts | 43 +++++++++++++++++++++++-- packages/core/src/mesh/ModelMesh.ts | 2 +- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/packages/core/src/clone/CloneManager.ts b/packages/core/src/clone/CloneManager.ts index be46462982..04c7c8d2c0 100644 --- a/packages/core/src/clone/CloneManager.ts +++ b/packages/core/src/clone/CloneManager.ts @@ -104,6 +104,7 @@ export class CloneManager { deepInstanceMap: Map ): void { const sourceProperty = source[k]; + let effectiveCloneMode = cloneMode; // Remappable references (Entity/Component) are always remapped, regardless of clone decorator if (sourceProperty instanceof Object && (sourceProperty)._remap) { @@ -111,10 +112,28 @@ export class CloneManager { return; } - if (cloneMode === CloneMode.Ignore) return; + if (effectiveCloneMode === CloneMode.Ignore) return; + + const targetProperty = target[k]; + if ( + effectiveCloneMode === undefined && + sourceProperty instanceof Object && + targetProperty && + targetProperty !== sourceProperty && + targetProperty.constructor === sourceProperty.constructor + ) { + // Component constructors already create instance-local mutable objects. + // Preserve that isolation when cloning prefab templates instead of + // overwriting the clone with the template's shared reference. + effectiveCloneMode = CloneMode.Deep; + } // Primitives, undecorated, or @assignmentClone: direct assign - if (!(sourceProperty instanceof Object) || cloneMode === undefined || cloneMode === CloneMode.Assignment) { + if ( + !(sourceProperty instanceof Object) || + effectiveCloneMode === undefined || + effectiveCloneMode === CloneMode.Assignment + ) { target[k] = sourceProperty; return; } @@ -137,6 +156,24 @@ export class CloneManager { targetPropertyT.set(sourceProperty); } break; + case Map: + let targetPropertyM = >target[k]; + if (targetPropertyM == null) { + target[k] = targetPropertyM = new Map(); + } else { + targetPropertyM.clear(); + } + (>sourceProperty).forEach((value, key) => targetPropertyM.set(key, value)); + break; + case Set: + let targetPropertyS = >target[k]; + if (targetPropertyS == null) { + target[k] = targetPropertyS = new Set(); + } else { + targetPropertyS.clear(); + } + (>sourceProperty).forEach((value) => targetPropertyS.add(value)); + break; case Array: let targetPropertyA = >target[k]; const length = (>sourceProperty).length; @@ -150,7 +187,7 @@ export class CloneManager { >sourceProperty, targetPropertyA, i, - cloneMode, + effectiveCloneMode, srcRoot, targetRoot, deepInstanceMap diff --git a/packages/core/src/mesh/ModelMesh.ts b/packages/core/src/mesh/ModelMesh.ts index 27b92ced49..8ee92c4825 100644 --- a/packages/core/src/mesh/ModelMesh.ts +++ b/packages/core/src/mesh/ModelMesh.ts @@ -925,7 +925,7 @@ export class ModelMesh extends Mesh { return null; } if (!buffer.readable) { - throw "Not allowed to access data while vertex buffer readable is false."; + throw new Error("Not allowed to access data while vertex buffer readable is false."); } const vertexCount = this.vertexCount; From d9a267477dd94d718f5a7dff2398941b163c5117 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Sun, 29 Mar 2026 20:56:45 +0800 Subject: [PATCH 026/130] fix(animation): normalize single-root clip binding paths --- packages/core/src/Entity.ts | 8 ++++ .../src/gltf/parser/GLTFAnimationParser.ts | 4 +- tests/src/core/Animator.test.ts | 42 +++++++++++++++++++ tests/src/core/Entity.test.ts | 15 ++++++- tests/src/loader/GLTFLoader.test.ts | 11 +++++ 5 files changed, 77 insertions(+), 3 deletions(-) diff --git a/packages/core/src/Entity.ts b/packages/core/src/Entity.ts index 4540cebf1f..43a32f7514 100644 --- a/packages/core/src/Entity.ts +++ b/packages/core/src/Entity.ts @@ -377,6 +377,14 @@ export class Entity extends EngineObject { return this; } + // Some imported animation clips are normalized to include the single scene root + // name (for example "mixamorig:Hips/..."), while the Animator may already sit on + // that root entity. Accept a self-name prefix so wrapped model roots and + // standalone single-root clips resolve through the same path convention. + if (splits[0] === this.name) { + return splits.length === 1 ? this : Entity._findChildByName(this, 0, splits, 1); + } + return Entity._findChildByName(this, 0, splits, 0); } diff --git a/packages/loader/src/gltf/parser/GLTFAnimationParser.ts b/packages/loader/src/gltf/parser/GLTFAnimationParser.ts index dbb5c65d42..10714899e7 100644 --- a/packages/loader/src/gltf/parser/GLTFAnimationParser.ts +++ b/packages/loader/src/gltf/parser/GLTFAnimationParser.ts @@ -123,8 +123,8 @@ export class GLTFAnimationParser extends GLTFParser { // the Animator sits on GLTF_ROOT and needs the root node name in the path. // Include the scene root name for single-root scenes to ensure consistent bone paths. const sceneNodes = context.glTF.scenes[context.glTF.scene ?? 0]?.nodes; - if (sceneNodes?.length === 1 && relativePath !== "") { - relativePath = `${entity.name}/${relativePath}`; + if (sceneNodes?.length === 1) { + relativePath = relativePath === "" ? entity.name : `${entity.name}/${relativePath}`; } let ComponentType: ComponentConstructor; let propertyName: string; diff --git a/tests/src/core/Animator.test.ts b/tests/src/core/Animator.test.ts index c37d476a92..9a255a5184 100644 --- a/tests/src/core/Animator.test.ts +++ b/tests/src/core/Animator.test.ts @@ -1046,6 +1046,48 @@ describe("Animator test", function () { expect(animator.entity.clone().getComponent(Animator).animatorController).to.eq(animator.animatorController); }); + it("samples self-name-prefixed curve paths on wrapped roots", () => { + const wrappedRoot = new Entity(engine, "GLTF_ROOT"); + const hips = new Entity(engine, "mixamorig:Hips"); + const spine = new Entity(engine, "mixamorig:Spine"); + hips.parent = wrappedRoot; + spine.parent = hips; + + const clip = new AnimationClip("idle"); + const hipsCurve = new AnimationFloatCurve(); + const spineCurve = new AnimationFloatCurve(); + const hipsStart = new Keyframe(); + const hipsEnd = new Keyframe(); + hipsStart.time = 0; + hipsStart.value = 0; + hipsEnd.time = 0.1; + hipsEnd.value = 1; + hipsCurve.addKey(hipsStart); + hipsCurve.addKey(hipsEnd); + + const spineStart = new Keyframe(); + const spineEnd = new Keyframe(); + spineStart.time = 0; + spineStart.value = 0; + spineEnd.time = 0.1; + spineEnd.value = 1; + spineCurve.addKey(spineStart); + spineCurve.addKey(spineEnd); + + clip.addCurveBinding("mixamorig:Hips", Transform, "position.x", hipsCurve); + clip.addCurveBinding("mixamorig:Hips/mixamorig:Spine", Transform, "position.y", spineCurve); + + expect(wrappedRoot.findByPath("mixamorig:Hips")).to.eq(hips); + expect(wrappedRoot.findByPath("mixamorig:Hips/mixamorig:Spine")).to.eq(spine); + + // @ts-ignore + clip._sampleAnimation(wrappedRoot, 0.1); + + expect(wrappedRoot.transform.position.x).to.eq(0); + expect(hips.transform.position.x).to.eq(1); + expect(spine.transform.position.y).to.eq(1); + }); + it("anyState transition interrupts crossFade", () => { const { animatorController } = animator; animatorController.addParameter("interrupt", false); diff --git a/tests/src/core/Entity.test.ts b/tests/src/core/Entity.test.ts index 2fe8f908ad..eba73370c0 100644 --- a/tests/src/core/Entity.test.ts +++ b/tests/src/core/Entity.test.ts @@ -320,6 +320,19 @@ describe("Entity", async () => { expect(parent.findByPath("child/grandson")).eq(grandson2); }); + it("findByPath accepts self-name prefix", () => { + const parent = new Entity(engine, "parent"); + parent.parent = scene.getRootEntity(); + const child = new Entity(engine, "child"); + child.parent = parent; + const grandson = new Entity(engine, "grandson"); + grandson.parent = child; + + expect(parent.findByPath("parent")).eq(parent); + expect(parent.findByPath("parent/child")).eq(child); + expect(parent.findByPath("parent/child/grandson")).eq(grandson); + }); + it("clearChildren", () => { const parent = new Entity(engine, "parent"); @@ -681,4 +694,4 @@ describe("Entity", async () => { expect(script.onDestroy).toHaveBeenCalledTimes(1); }); }); -}); \ No newline at end of file +}); diff --git a/tests/src/loader/GLTFLoader.test.ts b/tests/src/loader/GLTFLoader.test.ts index 7b35b7b330..402f2150c5 100644 --- a/tests/src/loader/GLTFLoader.test.ts +++ b/tests/src/loader/GLTFLoader.test.ts @@ -481,6 +481,17 @@ describe("glTF Loader test", function () { expect(renderer).to.exist; expect(renderer.blendShapeWeights).to.deep.include([1, 1]); }); + + it("single-root animation root channel should bind to the root node path", async () => { + const glTFResource: GLTFResource = await engine.resourceManager.load({ + type: AssetType.GLTF, + url: "mock/path/testA.gltf" + }); + + const clip = glTFResource.animations?.[0]; + expect(clip).to.exist; + expect(clip.curveBindings[0].relativePath).to.equal("entity1"); + }); }); describe("glTF instance test", function () { From 63c715b6358e5c6d372de0f31535db17a10f3ed7 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Mon, 30 Mar 2026 14:50:22 +0800 Subject: [PATCH 027/130] fix(animation): add per-instance speed to AnimatorStatePlayData AnimatorState.speed is part of the shared AnimatorController asset. Modifying it at runtime pollutes all Animator instances sharing the same controller, causing animation speed corruption after cloning. - Add speed field to AnimatorStatePlayData, initialized from AnimatorState.speed on reset - Add proxy properties (name/clip/wrapMode/transitions/addStateMachineScript) - Change speed calculation to playData.speed * animator.speed - findAnimatorState now returns per-instance AnimatorStatePlayData - Export AnimatorStatePlayData for consumer code --- packages/core/src/animation/Animator.ts | 28 +++++++++-- packages/core/src/animation/index.ts | 1 + .../internal/AnimatorStatePlayData.ts | 46 ++++++++++++++++++- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/packages/core/src/animation/Animator.ts b/packages/core/src/animation/Animator.ts index 180c2d1356..c25c30e3c8 100644 --- a/packages/core/src/animation/Animator.ts +++ b/packages/core/src/animation/Animator.ts @@ -218,13 +218,31 @@ export class Animator extends Component { * @param stateName - The state name * @param layerIndex - The layer index(default -1). If layer is -1, find the first state with the given state name */ - findAnimatorState(stateName: string, layerIndex: number = -1): AnimatorState { - return this._getAnimatorStateInfo(stateName, layerIndex).state; + /** + * Find the per-instance play data for a state by name. + * The returned object's `speed` is per-instance and safe to modify without affecting other Animator instances. + * @param stateName - The state name + * @param layerIndex - The layer index (default -1, searches all layers) + * @returns Per-instance AnimatorStatePlayData, or null if not found + */ + findAnimatorState(stateName: string, layerIndex: number = -1): AnimatorStatePlayData { + const { state, layerIndex: foundLayer } = this._getAnimatorStateInfo(stateName, layerIndex); + if (!state || foundLayer < 0) return null; + const layerData = this._animatorLayersData[foundLayer]; + if (!layerData) return null; + // Check srcPlayData and destPlayData for the matching state + if (layerData.srcPlayData.state === state) return layerData.srcPlayData; + if (layerData.destPlayData.state === state) return layerData.destPlayData; + // State exists in controller but not currently playing — return srcPlayData initialized with the state + return layerData.srcPlayData; } /** * Get the layer by name. * @param name - The layer's name. + * @todo Return per-instance layer data (like AnimatorStatePlayData for states) instead of shared asset. + * Currently returns the shared AnimatorControllerLayer — modifying `weight` affects all instances. + * Should follow Unity's pattern: Animator.SetLayerWeight/GetLayerWeight (per-instance). */ findLayerByName(name: string): AnimatorControllerLayer { return this._animatorController?._layersMap[name]; @@ -616,7 +634,7 @@ export class Animator extends Component { const { srcPlayData } = layerData; const { state } = srcPlayData; - const playSpeed = state.speed * this.speed; + const playSpeed = srcPlayData.speed * this.speed; const playDeltaTime = playSpeed * deltaTime; srcPlayData.updateOrientation(playDeltaTime); @@ -883,7 +901,7 @@ export class Animator extends Component { return; } - const playSpeed = state.speed * this.speed; + const playSpeed = destPlayData.speed * this.speed; const playDeltaTime = playSpeed * deltaTime; destPlayData.updateOrientation(playDeltaTime); @@ -989,7 +1007,7 @@ export class Animator extends Component { ): void { const playData = layerData.srcPlayData; const { state } = playData; - const actualSpeed = state.speed * this.speed; + const actualSpeed = playData.speed * this.speed; const actualDeltaTime = actualSpeed * deltaTime; playData.updateOrientation(actualDeltaTime); diff --git a/packages/core/src/animation/index.ts b/packages/core/src/animation/index.ts index b829ffde54..bd201a871f 100644 --- a/packages/core/src/animation/index.ts +++ b/packages/core/src/animation/index.ts @@ -11,6 +11,7 @@ export { Animator } from "./Animator"; export { AnimatorController } from "./AnimatorController"; export { AnimatorControllerLayer } from "./AnimatorControllerLayer"; export { AnimatorState } from "./AnimatorState"; +export { AnimatorStatePlayData } from "./internal/AnimatorStatePlayData"; export { AnimatorStateMachine } from "./AnimatorStateMachine"; export { AnimatorStateTransition } from "./AnimatorStateTransition"; export { AnimatorConditionMode } from "./enums/AnimatorConditionMode"; diff --git a/packages/core/src/animation/internal/AnimatorStatePlayData.ts b/packages/core/src/animation/internal/AnimatorStatePlayData.ts index 7d10fc2324..5cfa2fd3eb 100644 --- a/packages/core/src/animation/internal/AnimatorStatePlayData.ts +++ b/packages/core/src/animation/internal/AnimatorStatePlayData.ts @@ -1,20 +1,63 @@ +import { AnimationClip } from "../AnimationClip"; import { AnimatorState } from "../AnimatorState"; +import { AnimatorStateTransition } from "../AnimatorStateTransition"; import { AnimatorStatePlayState } from "../enums/AnimatorStatePlayState"; import { WrapMode } from "../enums/WrapMode"; +import { StateMachineScript } from "../StateMachineScript"; import { AnimatorStateData } from "./AnimatorStateData"; /** - * @internal + * Per-instance runtime data for an AnimatorState. + * Proxies read-only properties from the shared AnimatorState asset, + * while providing per-instance mutable properties (e.g. speed). */ export class AnimatorStatePlayData { + /** @internal */ state: AnimatorState; + /** @internal */ stateData: AnimatorStateData; + /** @internal */ playedTime: number; playState: AnimatorStatePlayState; + /** @internal */ clipTime: number; + /** @internal */ currentEventIndex: number; + /** @internal */ isForward = true; + /** @internal */ offsetFrameTime: number; + /** Per-instance speed. Initialized from AnimatorState.speed, safe to modify without affecting other instances. */ + speed: number = 1.0; + + // ── Proxy properties from AnimatorState (read-only) ── + + /** The name of the state. */ + get name(): string { + return this.state.name; + } + + /** The clip played by this state. */ + get clip(): AnimationClip { + return this.state.clip; + } + + /** The wrap mode. */ + get wrapMode(): WrapMode { + return this.state.wrapMode; + } + + /** The transitions going out of this state. */ + get transitions(): Readonly { + return this.state.transitions; + } + + /** + * Add a state machine script to the underlying AnimatorState. + */ + addStateMachineScript(scriptType: new () => T): T { + return this.state.addStateMachineScript(scriptType); + } private _changedOrientation = false; @@ -27,6 +70,7 @@ export class AnimatorStatePlayData { this.clipTime = state.clipStartTime * state.clip.length; this.currentEventIndex = 0; this.isForward = true; + this.speed = state.speed; this.state._transitionCollection.needResetCurrentCheckIndex = true; } From 13052d2f98a01eadcc1efee015f4a0a922e74427 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Mon, 30 Mar 2026 14:59:53 +0800 Subject: [PATCH 028/130] feat(ui): add SpriteSizeMode to Image component When sizeMode is set to Automatic, the UITransform size is automatically synchronized to the sprite's natural dimensions when the sprite changes. This matches Cocos Creator's Sprite.SizeMode.TRIMMED behavior. - Add SpriteSizeMode enum (Custom / Automatic) - Add sizeMode property to Image with getter/setter - Sync UITransform.size in set sprite and _onSpriteChange - Export SpriteSizeMode from component index --- packages/ui/src/component/advanced/Image.ts | 42 +++++++++++++++++++++ packages/ui/src/component/index.ts | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/component/advanced/Image.ts b/packages/ui/src/component/advanced/Image.ts index b8ca6ffe55..ba01a9cc02 100644 --- a/packages/ui/src/component/advanced/Image.ts +++ b/packages/ui/src/component/advanced/Image.ts @@ -21,6 +21,16 @@ import { RootCanvasModifyFlags } from "../UICanvas"; import { UIRenderer, UIRendererUpdateFlags } from "../UIRenderer"; import { UITransform, UITransformModifyFlags } from "../UITransform"; +/** + * Determines how the Image element's size is controlled relative to the sprite. + */ +export enum SpriteSizeMode { + /** The image size is controlled manually via UITransform (default, existing behavior). */ + Custom = 0, + /** The image size is automatically set to the sprite's natural dimensions when the sprite changes. */ + Automatic = 1 +} + /** * UI element that renders an image. */ @@ -35,6 +45,8 @@ export class Image extends UIRenderer implements ISpriteRenderer { private _tileMode: SpriteTileMode = SpriteTileMode.Continuous; @assignmentClone private _tiledAdaptiveThreshold: number = 0.5; + @assignmentClone + private _sizeMode: SpriteSizeMode = SpriteSizeMode.Custom; /** * The draw mode of the image. @@ -64,6 +76,23 @@ export class Image extends UIRenderer implements ISpriteRenderer { } } + /** + * The size mode of the image. When set to `Automatic`, the UITransform size + * is automatically synchronized to the sprite's natural dimensions. + */ + get sizeMode(): SpriteSizeMode { + return this._sizeMode; + } + + set sizeMode(value: SpriteSizeMode) { + if (this._sizeMode !== value) { + this._sizeMode = value; + if (value === SpriteSizeMode.Automatic && this._sprite) { + this._applySpriteSize(); + } + } + } + /** * The tiling mode of the image. (Only works in tiled mode.) */ @@ -122,6 +151,9 @@ export class Image extends UIRenderer implements ISpriteRenderer { this.shaderData.setTexture(UIRenderer._textureProperty, null); } this._sprite = value; + if (this._sizeMode === SpriteSizeMode.Automatic && value) { + this._applySpriteSize(); + } } } @@ -274,6 +306,9 @@ export class Image extends UIRenderer implements ISpriteRenderer { this.shaderData.setTexture(UIRenderer._textureProperty, this.sprite.texture); break; case SpriteModifyFlags.size: + if (this._sizeMode === SpriteSizeMode.Automatic) { + this._applySpriteSize(); + } switch (this._drawMode) { case SpriteDrawMode.Sliced: this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; @@ -309,6 +344,13 @@ export class Image extends UIRenderer implements ISpriteRenderer { break; } } + + private _applySpriteSize(): void { + const sprite = this._sprite; + if (sprite) { + (this._transformEntity.transform).size.set(sprite.width, sprite.height); + } + } } /** diff --git a/packages/ui/src/component/index.ts b/packages/ui/src/component/index.ts index 1f89431265..fc93806fb6 100644 --- a/packages/ui/src/component/index.ts +++ b/packages/ui/src/component/index.ts @@ -3,7 +3,7 @@ export { UIGroup } from "./UIGroup"; export { UIRenderer } from "./UIRenderer"; export { UITransform } from "./UITransform"; export { Button } from "./advanced/Button"; -export { Image } from "./advanced/Image"; +export { Image, SpriteSizeMode } from "./advanced/Image"; export { Text } from "./advanced/Text"; export { ColorTransition } from "./interactive/transition/ColorTransition"; export { ScaleTransition } from "./interactive/transition/ScaleTransition"; From 15b19affcd0e51a6c64db49c09092dc855a16648 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Wed, 1 Apr 2026 12:09:30 +0800 Subject: [PATCH 029/130] feat: particle bug not fix --- e2e/.dev/vite.config.js | 1 + e2e/case/particleRenderer-dream.ts | 4 ++-- packages/core/src/particle/ParticleGenerator.ts | 1 + packages/core/src/particle/ParticleRenderer.ts | 17 +++++++++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/e2e/.dev/vite.config.js b/e2e/.dev/vite.config.js index 2cfc11d824..498a9a0244 100644 --- a/e2e/.dev/vite.config.js +++ b/e2e/.dev/vite.config.js @@ -42,6 +42,7 @@ demoList.forEach(({ file }) => { fs.outputJSONSync(path.join(__dirname, OUT_PATH, ".demoList.json"), demoSorted); module.exports = { + publicDir: path.resolve(__dirname, "public"), server: { open: true, host: "0.0.0.0", diff --git a/e2e/case/particleRenderer-dream.ts b/e2e/case/particleRenderer-dream.ts index e515bd4fbf..6eb51637ed 100644 --- a/e2e/case/particleRenderer-dream.ts +++ b/e2e/case/particleRenderer-dream.ts @@ -68,8 +68,8 @@ WebGLEngine.create({ cameraEntity.addChild(fireEntity); - updateForE2E(engine, 500); - initScreenshot(engine, camera); + // updateForE2E(engine, 500); + // initScreenshot(engine, camera); }); }); diff --git a/packages/core/src/particle/ParticleGenerator.ts b/packages/core/src/particle/ParticleGenerator.ts index d180c7f929..75c50b5cf6 100644 --- a/packages/core/src/particle/ParticleGenerator.ts +++ b/packages/core/src/particle/ParticleGenerator.ts @@ -112,6 +112,7 @@ export class ParticleGenerator { @ignoreClone _subPrimitive = new SubMesh(0, 0, MeshTopology.Triangles); /** @internal */ + @ignoreClone readonly _renderer: ParticleRenderer; /** @internal */ diff --git a/packages/core/src/particle/ParticleRenderer.ts b/packages/core/src/particle/ParticleRenderer.ts index 633e166989..c7a8e27b10 100644 --- a/packages/core/src/particle/ParticleRenderer.ts +++ b/packages/core/src/particle/ParticleRenderer.ts @@ -258,6 +258,23 @@ export class ParticleRenderer extends Renderer { context.camera._renderPipeline.pushRenderElement(context, renderElement); } + /** + * @internal + */ + override _cloneTo(target: ParticleRenderer): void { + super._cloneTo(target); + + // ShaderData internals (_macroCollection, _propertyValueMap) are @ignoreClone, + // so the cloned shaderData only has the constructor-set Billboard macro. + // Must re-apply the source's shaderData state. + this.shaderData.cloneTo(target.shaderData); + + // Rebuild GPU resources to match cloned renderMode/mesh/maxParticles. + const gen = target.generator; + gen._reorganizeGeometryBuffers(); + gen._resizeInstanceBuffer(true, gen.main.maxParticles); + } + protected override _onDestroy(): void { const mesh = this._mesh; if (mesh) { From 207a3811b14d45a442cf4622311f849081e0d8e6 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 2 Apr 2026 16:37:45 +0800 Subject: [PATCH 030/130] feat(2d): add FilledSpriteAssembler and sprite filled mode support Co-Authored-By: Claude Opus 4.6 --- .../src/2d/assembler/FilledSpriteAssembler.ts | 595 ++++++++++++++++++ .../core/src/2d/assembler/ISpriteRenderer.ts | 6 + packages/core/src/2d/enums/SpriteDrawMode.ts | 4 +- .../core/src/2d/enums/SpriteFilledMode.ts | 15 + .../core/src/2d/enums/SpriteFilledOrigin.ts | 21 + packages/core/src/2d/index.ts | 3 + packages/core/src/2d/sprite/SpriteRenderer.ts | 85 +++ 7 files changed, 728 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/2d/assembler/FilledSpriteAssembler.ts create mode 100644 packages/core/src/2d/enums/SpriteFilledMode.ts create mode 100644 packages/core/src/2d/enums/SpriteFilledOrigin.ts diff --git a/packages/core/src/2d/assembler/FilledSpriteAssembler.ts b/packages/core/src/2d/assembler/FilledSpriteAssembler.ts new file mode 100644 index 0000000000..894f1b07c6 --- /dev/null +++ b/packages/core/src/2d/assembler/FilledSpriteAssembler.ts @@ -0,0 +1,595 @@ +import { BoundingBox, Matrix, Vector2, Vector3 } from "@galacean/engine-math"; +import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; +import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { SpriteFilledMode } from "../enums/SpriteFilledMode"; +import { SpriteFilledOrigin } from "../enums/SpriteFilledOrigin"; +import { ISpriteAssembler } from "./ISpriteAssembler"; +import { ISpriteRenderer } from "./ISpriteRenderer"; + +/** + * Assemble vertex data for the sprite renderer in filled mode. + */ +@StaticInterfaceImplement() +export class FilledSpriteAssembler { + private static _matrix = new Matrix(); + private static _worldPositions = [ + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3() + ]; + private static _uvs = [ + new Vector2(), + new Vector2(), + new Vector2(), + new Vector2(), + new Vector2(), + new Vector2(), + new Vector2(), + new Vector2(), + new Vector2() + ]; + private static _inPositions: Vector3[] = []; + private static _inUVs: Vector2[] = []; + private static _outPositions: Vector3[] = [new Vector3(), new Vector3(), new Vector3(), new Vector3()]; + private static _outUVs: Vector2[] = [new Vector2(), new Vector2(), new Vector2(), new Vector2()]; + private static _vertexOffset = 0; + private static _indicesOffset = 0; + + static resetData(renderer: ISpriteRenderer): void { + const manager = renderer._getChunkManager(); + const lastSubChunk = renderer._subChunk; + lastSubChunk && manager.freeSubChunk(lastSubChunk); + const subChunk = manager.allocateSubChunk(16); + subChunk.indices = []; + renderer._subChunk = subChunk; + } + + static updatePositions( + renderer: ISpriteRenderer, + worldMatrix: Matrix, + width: number, + height: number, + pivot: Vector2, + flipX: boolean, + flipY: boolean + ): void { + const { x: pivotX, y: pivotY } = pivot; + const modelMatrix = FilledSpriteAssembler._matrix; + const { elements: wE } = modelMatrix; + const { elements: pWE } = worldMatrix; + const sx = flipX ? -width : width; + const sy = flipY ? -height : height; + (wE[0] = pWE[0] * sx), (wE[1] = pWE[1] * sx), (wE[2] = pWE[2] * sx); + (wE[4] = pWE[4] * sy), (wE[5] = pWE[5] * sy), (wE[6] = pWE[6] * sy); + (wE[8] = pWE[8]), (wE[9] = pWE[9]), (wE[10] = pWE[10]); + wE[12] = pWE[12] - pivotX * wE[0] - pivotY * wE[4]; + wE[13] = pWE[13] - pivotX * wE[1] - pivotY * wE[5]; + wE[14] = pWE[14] - pivotX * wE[2] - pivotY * wE[6]; + + switch (renderer.filledMode) { + case SpriteFilledMode.Horizontal: + this._filledLinear(renderer, modelMatrix, true); + break; + case SpriteFilledMode.Vertical: + this._filledLinear(renderer, modelMatrix, false); + break; + case SpriteFilledMode.Radial90: + this._filledRadial90(renderer, modelMatrix, renderer.filledOrigin, renderer.filledAmount, renderer.filledClockWise); + break; + case SpriteFilledMode.Radial180: + this._filledRadial180(renderer, modelMatrix, renderer.filledOrigin, renderer.filledAmount, renderer.filledClockWise); + break; + case SpriteFilledMode.Radial360: + this._filledRadial360(renderer, modelMatrix, renderer.filledOrigin, renderer.filledAmount, renderer.filledClockWise); + break; + default: + break; + } + + // @ts-ignore + BoundingBox.transform(renderer.sprite._getBounds(), modelMatrix, renderer._bounds); + } + + static updateUVs(renderer: ISpriteRenderer): void { + // UVs are computed in updatePositions. + } + + static updateColor(renderer: ISpriteRenderer, alpha: number): void { + const subChunk = renderer._subChunk; + const { r, g, b, a } = renderer.color; + const finalAlpha = a * alpha; + const vertices = subChunk.chunk.vertices; + const vertexArea = subChunk.vertexArea; + for (let i = 0, o = vertexArea.start + 5, n = vertexArea.size / 9; i < n; ++i, o += 9) { + vertices[o] = r; + vertices[o + 1] = g; + vertices[o + 2] = b; + vertices[o + 3] = finalAlpha; + } + } + + private static _filledLinear(renderer: ISpriteRenderer, matrix: Matrix, isHorizontal: boolean): void { + const amount = renderer.filledAmount; + if (amount <= 0.001) { + renderer._subChunk.indices.length = 0; + return; + } + + const sprite = renderer.sprite; + const [lPosLB, lPosRB, lPosLT, lPosRT] = sprite._getPositions(); + const spriteUVs = sprite._getUVs(); + const { x: left, y: bottom } = spriteUVs[0]; + const { x: right, y: top } = spriteUVs[3]; + + const subChunk = renderer._subChunk; + const vertices = subChunk.chunk.vertices; + + let x0: number, y0: number, u0: number, v0: number; + let x1: number, y1: number, u1: number, v1: number; + let x2: number, y2: number, u2: number, v2: number; + let x3: number, y3: number, u3: number, v3: number; + + if (isHorizontal) { + const originIsStart = renderer.filledOrigin === SpriteFilledOrigin.Left; + const startX = originIsStart ? lPosLB.x : lPosRB.x - (lPosRB.x - lPosLB.x) * amount; + const endX = originIsStart ? lPosLB.x + (lPosRB.x - lPosLB.x) * amount : lPosRB.x; + const startU = originIsStart ? left : right - (right - left) * amount; + const endU = originIsStart ? left + (right - left) * amount : right; + (x0 = startX), (y0 = lPosLB.y), (u0 = startU), (v0 = bottom); + (x1 = endX), (y1 = lPosRB.y), (u1 = endU), (v1 = bottom); + (x2 = startX), (y2 = lPosLT.y), (u2 = startU), (v2 = top); + (x3 = endX), (y3 = lPosRT.y), (u3 = endU), (v3 = top); + } else { + const originIsStart = renderer.filledOrigin === SpriteFilledOrigin.Bottom; + const startY = originIsStart ? lPosLB.y : lPosLT.y - (lPosLT.y - lPosLB.y) * amount; + const endY = originIsStart ? lPosLB.y + (lPosLT.y - lPosLB.y) * amount : lPosLT.y; + const startV = originIsStart ? bottom : top - (top - bottom) * amount; + const endV = originIsStart ? bottom + (top - bottom) * amount : top; + (x0 = lPosLB.x), (y0 = startY), (u0 = left), (v0 = startV); + (x1 = lPosRB.x), (y1 = startY), (u1 = right), (v1 = startV); + (x2 = lPosLT.x), (y2 = endY), (u2 = left), (v2 = endV); + (x3 = lPosRT.x), (y3 = endY), (u3 = right), (v3 = endV); + } + + const { elements: wE } = matrix; + const start = subChunk.vertexArea.start; + // LB + vertices[start] = wE[0] * x0 + wE[4] * y0 + wE[12]; + vertices[start + 1] = wE[1] * x0 + wE[5] * y0 + wE[13]; + vertices[start + 2] = wE[2] * x0 + wE[6] * y0 + wE[14]; + vertices[start + 3] = u0; + vertices[start + 4] = v0; + // RB + vertices[start + 9] = wE[0] * x1 + wE[4] * y1 + wE[12]; + vertices[start + 10] = wE[1] * x1 + wE[5] * y1 + wE[13]; + vertices[start + 11] = wE[2] * x1 + wE[6] * y1 + wE[14]; + vertices[start + 12] = u1; + vertices[start + 13] = v1; + // LT + vertices[start + 18] = wE[0] * x2 + wE[4] * y2 + wE[12]; + vertices[start + 19] = wE[1] * x2 + wE[5] * y2 + wE[13]; + vertices[start + 20] = wE[2] * x2 + wE[6] * y2 + wE[14]; + vertices[start + 21] = u2; + vertices[start + 22] = v2; + // RT + vertices[start + 27] = wE[0] * x3 + wE[4] * y3 + wE[12]; + vertices[start + 28] = wE[1] * x3 + wE[5] * y3 + wE[13]; + vertices[start + 29] = wE[2] * x3 + wE[6] * y3 + wE[14]; + vertices[start + 30] = u3; + vertices[start + 31] = v3; + + const indices = subChunk.indices; + indices[0] = 0; + indices[1] = 1; + indices[2] = 2; + indices[3] = 2; + indices[4] = 1; + indices[5] = 3; + indices.length = 6; + } + + private static _filledRadial90( + renderer: ISpriteRenderer, + matrix: Matrix, + origin: SpriteFilledOrigin, + amount: number, + cw: boolean + ): void { + if (amount <= 0.001) { + renderer._subChunk.indices.length = 0; + return; + } + + const sprite = renderer.sprite; + const [lPosLB, lPosRB, lPosLT, lPosRT] = sprite._getPositions(); + const spriteUVs = sprite._getUVs(); + const { x: left, y: bottom } = spriteUVs[0]; + const { x: right, y: top } = spriteUVs[3]; + + // Transform 4 corners to world space + const [wLB, wRB, wLT, wRT] = this._worldPositions; + const [uvLB, uvRB, uvLT, uvRT] = this._uvs; + wLB.set(lPosLB.x, lPosLB.y, 0).transformToVec3(matrix), uvLB.set(left, bottom); + wRB.set(lPosRB.x, lPosRB.y, 0).transformToVec3(matrix), uvRB.set(right, bottom); + wLT.set(lPosLT.x, lPosLT.y, 0).transformToVec3(matrix), uvLT.set(left, top); + wRT.set(lPosRT.x, lPosRT.y, 0).transformToVec3(matrix), uvRT.set(right, top); + + // Map vertices based on origin corner: + // [center, CW-adjacent, CCW-adjacent, opposite] + const { _inPositions: inPositions, _inUVs: inUVs, _outPositions: outPositions, _outUVs: outUVs } = this; + switch (origin) { + case SpriteFilledOrigin.BottomLeft: + (inPositions[0] = wLB), (inUVs[0] = uvLB); + (inPositions[1] = wRB), (inUVs[1] = uvRB); + (inPositions[2] = wLT), (inUVs[2] = uvLT); + (inPositions[3] = wRT), (inUVs[3] = uvRT); + break; + case SpriteFilledOrigin.BottomRight: + (inPositions[0] = wRB), (inUVs[0] = uvRB); + (inPositions[1] = wRT), (inUVs[1] = uvRT); + (inPositions[2] = wLB), (inUVs[2] = uvLB); + (inPositions[3] = wLT), (inUVs[3] = uvLT); + break; + case SpriteFilledOrigin.TopRight: + (inPositions[0] = wRT), (inUVs[0] = uvRT); + (inPositions[1] = wLT), (inUVs[1] = uvLT); + (inPositions[2] = wRB), (inUVs[2] = uvRB); + (inPositions[3] = wLB), (inUVs[3] = uvLB); + break; + case SpriteFilledOrigin.TopLeft: + (inPositions[0] = wLT), (inUVs[0] = uvLT); + (inPositions[1] = wLB), (inUVs[1] = uvLB); + (inPositions[2] = wRT), (inUVs[2] = uvRT); + (inPositions[3] = wRB), (inUVs[3] = uvRB); + break; + default: + break; + } + + const startAngle = cw ? 90 - amount * 90 : 0; + const endAngle = cw ? 90 : amount * 90; + + this._vertexOffset = this._indicesOffset = 0; + this._radialCut(renderer._subChunk, inPositions, inUVs, startAngle, endAngle, outPositions, outUVs); + renderer._subChunk.indices.length = this._indicesOffset; + } + + private static _filledRadial180( + renderer: ISpriteRenderer, + matrix: Matrix, + origin: SpriteFilledOrigin, + amount: number, + cw: boolean + ): void { + if (amount <= 0.001) { + renderer._subChunk.indices.length = 0; + return; + } + + const sprite = renderer.sprite; + const [lPosLB, lPosRB, lPosLT, lPosRT] = sprite._getPositions(); + const spriteUVs = sprite._getUVs(); + const { x: left, y: bottom } = spriteUVs[0]; + const { x: right, y: top } = spriteUVs[3]; + + // Transform corners and compute edge midpoints + const [wLB, wMB, wRB, wLM, , wRM, wLT, wMT, wRT] = this._worldPositions; + const [uvLB, uvMB, uvRB, uvLM, , uvRM, uvLT, uvMT, uvRT] = this._uvs; + wLB.set(lPosLB.x, lPosLB.y, 0).transformToVec3(matrix), uvLB.set(left, bottom); + wRB.set(lPosRB.x, lPosRB.y, 0).transformToVec3(matrix), uvRB.set(right, bottom); + wLT.set(lPosLT.x, lPosLT.y, 0).transformToVec3(matrix), uvLT.set(left, top); + wRT.set(lPosRT.x, lPosRT.y, 0).transformToVec3(matrix), uvRT.set(right, top); + Vector3.lerp(wLB, wRB, 0.5, wMB), Vector2.lerp(uvLB, uvRB, 0.5, uvMB); + Vector3.lerp(wLB, wLT, 0.5, wLM), Vector2.lerp(uvLB, uvLT, 0.5, uvLM); + Vector3.lerp(wLT, wRT, 0.5, wMT), Vector2.lerp(uvLT, uvRT, 0.5, uvMT); + Vector3.lerp(wRB, wRT, 0.5, wRM), Vector2.lerp(uvRB, uvRT, 0.5, uvRM); + + const startAngle = cw ? 180 - amount * 180 : 0; + const endAngle = cw ? 180 : amount * 180; + + this._vertexOffset = this._indicesOffset = 0; + const { _inPositions: inPositions, _inUVs: inUVs, _outPositions: outPositions, _outUVs: outUVs } = this; + const { _subChunk: subChunk } = renderer; + + // Center is at the origin edge midpoint; two quadrants cover the full sprite. + // Quadrant A (0°-90°), Quadrant B (90°-180°) + switch (origin) { + case SpriteFilledOrigin.Bottom: + // Center=MB, A: [MB,RB,MT,RT], B: [MB,MT,LB,LT] + (inPositions[0] = wMB), (inUVs[0] = uvMB); + (inPositions[1] = wRB), (inUVs[1] = uvRB); + (inPositions[2] = wMT), (inUVs[2] = uvMT); + (inPositions[3] = wRT), (inUVs[3] = uvRT); + this._radialCut(subChunk, inPositions, inUVs, startAngle, endAngle, outPositions, outUVs); + (inPositions[1] = wMT), (inUVs[1] = uvMT); + (inPositions[2] = wLB), (inUVs[2] = uvLB); + (inPositions[3] = wLT), (inUVs[3] = uvLT); + this._radialCut(subChunk, inPositions, inUVs, startAngle - 90, endAngle - 90, outPositions, outUVs); + break; + case SpriteFilledOrigin.Top: + // Center=MT, A: [MT,LT,MB,LB], B: [MT,MB,RT,RB] + (inPositions[0] = wMT), (inUVs[0] = uvMT); + (inPositions[1] = wLT), (inUVs[1] = uvLT); + (inPositions[2] = wMB), (inUVs[2] = uvMB); + (inPositions[3] = wLB), (inUVs[3] = uvLB); + this._radialCut(subChunk, inPositions, inUVs, startAngle, endAngle, outPositions, outUVs); + (inPositions[1] = wMB), (inUVs[1] = uvMB); + (inPositions[2] = wRT), (inUVs[2] = uvRT); + (inPositions[3] = wRB), (inUVs[3] = uvRB); + this._radialCut(subChunk, inPositions, inUVs, startAngle - 90, endAngle - 90, outPositions, outUVs); + break; + case SpriteFilledOrigin.Left: + // Center=LM, A: [LM,LB,RM,RB], B: [LM,RM,LT,RT] + (inPositions[0] = wLM), (inUVs[0] = uvLM); + (inPositions[1] = wLB), (inUVs[1] = uvLB); + (inPositions[2] = wRM), (inUVs[2] = uvRM); + (inPositions[3] = wRB), (inUVs[3] = uvRB); + this._radialCut(subChunk, inPositions, inUVs, startAngle, endAngle, outPositions, outUVs); + (inPositions[1] = wRM), (inUVs[1] = uvRM); + (inPositions[2] = wLT), (inUVs[2] = uvLT); + (inPositions[3] = wRT), (inUVs[3] = uvRT); + this._radialCut(subChunk, inPositions, inUVs, startAngle - 90, endAngle - 90, outPositions, outUVs); + break; + case SpriteFilledOrigin.Right: + // Center=RM, A: [RM,RT,LM,LT], B: [RM,LM,RB,LB] + (inPositions[0] = wRM), (inUVs[0] = uvRM); + (inPositions[1] = wRT), (inUVs[1] = uvRT); + (inPositions[2] = wLM), (inUVs[2] = uvLM); + (inPositions[3] = wLT), (inUVs[3] = uvLT); + this._radialCut(subChunk, inPositions, inUVs, startAngle, endAngle, outPositions, outUVs); + (inPositions[1] = wLM), (inUVs[1] = uvLM); + (inPositions[2] = wRB), (inUVs[2] = uvRB); + (inPositions[3] = wLB), (inUVs[3] = uvLB); + this._radialCut(subChunk, inPositions, inUVs, startAngle - 90, endAngle - 90, outPositions, outUVs); + break; + default: + break; + } + + subChunk.indices.length = this._indicesOffset; + } + + private static _filledRadial360( + renderer: ISpriteRenderer, + matrix: Matrix, + origin: SpriteFilledOrigin, + amount: number, + cw: boolean + ): void { + if (amount <= 0.001) { + renderer._subChunk.indices.length = 0; + return; + } + + let startAngle = 0; + switch (origin) { + case SpriteFilledOrigin.Right: + startAngle = cw ? 360 - amount * 360 : 0; + break; + case SpriteFilledOrigin.Top: + startAngle = cw ? 450 - amount * 360 : 90; + break; + case SpriteFilledOrigin.Left: + startAngle = cw ? 540 - amount * 360 : 180; + break; + case SpriteFilledOrigin.Bottom: + startAngle = cw ? 630 - amount * 360 : 270; + break; + default: + break; + } + const endAngle = startAngle + amount * 360; + + this._processRadialGrid(renderer, matrix, startAngle, endAngle); + } + + /** + * Prepare the 3x3 grid and process 4 quadrants for radial fill. + */ + private static _processRadialGrid( + renderer: ISpriteRenderer, + matrix: Matrix, + startAngle: number, + endAngle: number + ): void { + const sprite = renderer.sprite; + const [lPosLB, lPosRB, lPosLT, lPosRT] = sprite._getPositions(); + const spriteUVs = sprite._getUVs(); + const { x: left, y: bottom } = spriteUVs[0]; + const { x: right, y: top } = spriteUVs[3]; + + // --------------- + // LT - MT - RT + // | | | + // LM - C - RM + // | | | + // LB - MB - RB + // --------------- + const [wPosLB, wPosMB, wPosRB, wPosLM, wPosC, wPosRM, wPosLT, wPosMT, wPosRT] = this._worldPositions; + const [uvLB, uvMB, uvRB, uvLM, uvC, uvRM, uvLT, uvMT, uvRT] = this._uvs; + + wPosLB.set(lPosLB.x, lPosLB.y, 0).transformToVec3(matrix), uvLB.set(left, bottom); + wPosRB.set(lPosRB.x, lPosRB.y, 0).transformToVec3(matrix), uvRB.set(right, bottom); + wPosLT.set(lPosLT.x, lPosLT.y, 0).transformToVec3(matrix), uvLT.set(left, top); + wPosRT.set(lPosRT.x, lPosRT.y, 0).transformToVec3(matrix), uvRT.set(right, top); + Vector3.lerp(wPosLB, wPosRB, 0.5, wPosMB), Vector2.lerp(uvLB, uvRB, 0.5, uvMB); + Vector3.lerp(wPosLB, wPosLT, 0.5, wPosLM), Vector2.lerp(uvLB, uvLT, 0.5, uvLM); + Vector3.lerp(wPosLT, wPosRT, 0.5, wPosMT), Vector2.lerp(uvLT, uvRT, 0.5, uvMT); + Vector3.lerp(wPosRB, wPosRT, 0.5, wPosRM), Vector2.lerp(uvRB, uvRT, 0.5, uvRM); + Vector3.lerp(wPosLB, wPosRT, 0.5, wPosC), Vector2.lerp(uvLB, uvRT, 0.5, uvC); + + this._vertexOffset = this._indicesOffset = 0; + const { _inPositions: inPositions, _inUVs: inUVs, _outPositions: outPositions, _outUVs: outUVs } = this; + const { _subChunk: subChunk } = renderer; + let quadrantStart = 0; + let quadrantEnd = 0; + (inPositions[0] = wPosC), (inUVs[0] = uvC); + + { + // First quadrant (0°-90°) + if (startAngle >= 90) { + quadrantStart = startAngle - 360; + quadrantEnd = endAngle - 360; + } else { + quadrantStart = startAngle; + quadrantEnd = endAngle; + } + (inPositions[1] = wPosRM), (inUVs[1] = uvRM); + (inPositions[2] = wPosMT), (inUVs[2] = uvMT); + (inPositions[3] = wPosRT), (inUVs[3] = uvRT); + this._radialCut(subChunk, inPositions, inUVs, quadrantStart, quadrantEnd, outPositions, outUVs); + } + + { + // Second quadrant (90°-180°) + if (startAngle >= 180) { + quadrantStart = startAngle - 360 - 90; + quadrantEnd = endAngle - 360 - 90; + } else { + quadrantStart = startAngle - 90; + quadrantEnd = endAngle - 90; + } + (inPositions[1] = wPosMT), (inUVs[1] = uvMT); + (inPositions[2] = wPosLM), (inUVs[2] = uvLM); + (inPositions[3] = wPosLT), (inUVs[3] = uvLT); + this._radialCut(subChunk, inPositions, inUVs, quadrantStart, quadrantEnd, outPositions, outUVs); + } + + { + // Third quadrant (180°-270°) + if (startAngle >= 270) { + quadrantStart = startAngle - 360 - 180; + quadrantEnd = endAngle - 360 - 180; + } else { + quadrantStart = startAngle - 180; + quadrantEnd = endAngle - 180; + } + (inPositions[1] = wPosLM), (inUVs[1] = uvLM); + (inPositions[2] = wPosMB), (inUVs[2] = uvMB); + (inPositions[3] = wPosLB), (inUVs[3] = uvLB); + this._radialCut(subChunk, inPositions, inUVs, quadrantStart, quadrantEnd, outPositions, outUVs); + } + + { + // Fourth quadrant (270°-360°) + if (startAngle >= 360) { + quadrantStart = startAngle - 360 - 270; + quadrantEnd = endAngle - 360 - 270; + } else { + quadrantStart = startAngle - 270; + quadrantEnd = endAngle - 270; + } + (inPositions[1] = wPosMB), (inUVs[1] = uvMB); + (inPositions[2] = wPosRM), (inUVs[2] = uvRM); + (inPositions[3] = wPosRB), (inUVs[3] = uvRB); + this._radialCut(subChunk, inPositions, inUVs, quadrantStart, quadrantEnd, outPositions, outUVs); + } + + subChunk.indices.length = this._indicesOffset; + } + + private static _radialCut( + subChunk: SubPrimitiveChunk, + positions: Vector3[], + uvs: Vector2[], + start: number, + end: number, + outPositions: Vector3[], + outUVs: Vector2[] + ): void { + if (start >= 90 || end <= 0) return; + outPositions[0].copyFrom(positions[0]); + outUVs[0].copyFrom(uvs[0]); + + if (start <= 0) { + outPositions[1].copyFrom(positions[1]); + outUVs[1].copyFrom(uvs[1]); + } else { + const startTan = Math.tan((start * Math.PI) / 180); + if (startTan < 1) { + Vector3.lerp(positions[1], positions[3], startTan, outPositions[1]); + Vector2.lerp(uvs[1], uvs[3], startTan, outUVs[1]); + } else { + Vector3.lerp(positions[2], positions[3], 1 / startTan, outPositions[1]); + Vector2.lerp(uvs[2], uvs[3], 1 / startTan, outUVs[1]); + } + } + + if (end >= 90) { + outPositions[2].copyFrom(positions[2]); + outUVs[2].copyFrom(uvs[2]); + } else { + const endTan = Math.tan((end * Math.PI) / 180); + if (endTan < 1) { + Vector3.lerp(positions[1], positions[3], endTan, outPositions[2]); + Vector2.lerp(uvs[1], uvs[3], endTan, outUVs[2]); + } else { + Vector3.lerp(positions[2], positions[3], 1 / endTan, outPositions[2]); + Vector2.lerp(uvs[2], uvs[3], 1 / endTan, outUVs[2]); + } + } + + if (start < 45 && end > 45) { + outPositions[3].copyFrom(positions[3]); + outUVs[3].copyFrom(uvs[3]); + this._addQuad(subChunk, outPositions, outUVs); + } else { + this._addTriangle(subChunk, outPositions, outUVs); + } + } + + private static _addTriangle(subChunk: SubPrimitiveChunk, positions: Vector3[], uvs: Vector2[]): void { + const vertices = subChunk.chunk.vertices; + const indices = subChunk.indices; + const vertexOffset = this._vertexOffset; + const vertexCount = vertexOffset / 9; + const start = subChunk.vertexArea.start + vertexOffset; + for (let i = 0, o = start; i < 3; ++i, o += 9) { + const position = positions[i]; + const uv = uvs[i]; + vertices[o] = position.x; + vertices[o + 1] = position.y; + vertices[o + 2] = position.z; + vertices[o + 3] = uv.x; + vertices[o + 4] = uv.y; + } + const indicesOffset = this._indicesOffset; + indices[indicesOffset] = vertexCount; + indices[indicesOffset + 1] = vertexCount + 1; + indices[indicesOffset + 2] = vertexCount + 2; + this._vertexOffset += 3 * 9; + this._indicesOffset += 3; + } + + private static _addQuad(subChunk: SubPrimitiveChunk, positions: Vector3[], uvs: Vector2[]): void { + const vertices = subChunk.chunk.vertices; + const indices = subChunk.indices; + const vertexOffset = this._vertexOffset; + const vertexCount = vertexOffset / 9; + const start = subChunk.vertexArea.start + vertexOffset; + for (let i = 0, o = start; i < 4; ++i, o += 9) { + const position = positions[i]; + const uv = uvs[i]; + vertices[o] = position.x; + vertices[o + 1] = position.y; + vertices[o + 2] = position.z; + vertices[o + 3] = uv.x; + vertices[o + 4] = uv.y; + } + const indicesOffset = this._indicesOffset; + indices[indicesOffset] = vertexCount; + indices[indicesOffset + 1] = vertexCount + 1; + indices[indicesOffset + 2] = vertexCount + 2; + indices[indicesOffset + 3] = vertexCount + 2; + indices[indicesOffset + 4] = vertexCount + 1; + indices[indicesOffset + 5] = vertexCount + 3; + this._vertexOffset += 4 * 9; + this._indicesOffset += 6; + } +} diff --git a/packages/core/src/2d/assembler/ISpriteRenderer.ts b/packages/core/src/2d/assembler/ISpriteRenderer.ts index a72f4e9436..04b50b3fc9 100644 --- a/packages/core/src/2d/assembler/ISpriteRenderer.ts +++ b/packages/core/src/2d/assembler/ISpriteRenderer.ts @@ -1,6 +1,8 @@ import { Color } from "@galacean/engine-math"; import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; +import { SpriteFilledMode } from "../enums/SpriteFilledMode"; +import { SpriteFilledOrigin } from "../enums/SpriteFilledOrigin"; import { SpriteTileMode } from "../enums/SpriteTileMode"; import { Sprite } from "../sprite"; @@ -12,6 +14,10 @@ export interface ISpriteRenderer { color?: Color; tileMode?: SpriteTileMode; tiledAdaptiveThreshold?: number; + filledMode?: SpriteFilledMode; + filledAmount?: number; + filledOrigin?: SpriteFilledOrigin; + filledClockWise?: boolean; _subChunk: SubPrimitiveChunk; _getChunkManager(): PrimitiveChunkManager; } diff --git a/packages/core/src/2d/enums/SpriteDrawMode.ts b/packages/core/src/2d/enums/SpriteDrawMode.ts index 46bcfd3783..6f35d8c1ef 100644 --- a/packages/core/src/2d/enums/SpriteDrawMode.ts +++ b/packages/core/src/2d/enums/SpriteDrawMode.ts @@ -7,5 +7,7 @@ export enum SpriteDrawMode { /** When modifying the size of the renderer, it scales to fill the range according to the sprite border settings. */ Sliced, /** When modifying the size of the renderer, it will tile to fill the range according to the sprite border settings. */ - Tiled + Tiled, + /** Fill the sprite partially, controlled by fill amount, mode and origin. */ + Filled } diff --git a/packages/core/src/2d/enums/SpriteFilledMode.ts b/packages/core/src/2d/enums/SpriteFilledMode.ts new file mode 100644 index 0000000000..a01cda9e53 --- /dev/null +++ b/packages/core/src/2d/enums/SpriteFilledMode.ts @@ -0,0 +1,15 @@ +/** + * Sprite's filled mode enumeration. + */ +export enum SpriteFilledMode { + /** Fill horizontally. */ + Horizontal, + /** Fill vertically. */ + Vertical, + /** Fill radially over 90 degrees. */ + Radial90, + /** Fill radially over 180 degrees. */ + Radial180, + /** Fill radially over 360 degrees. */ + Radial360 +} diff --git a/packages/core/src/2d/enums/SpriteFilledOrigin.ts b/packages/core/src/2d/enums/SpriteFilledOrigin.ts new file mode 100644 index 0000000000..e9b3193970 --- /dev/null +++ b/packages/core/src/2d/enums/SpriteFilledOrigin.ts @@ -0,0 +1,21 @@ +/** + * Sprite's filled origin enumeration. + */ +export enum SpriteFilledOrigin { + /** Origin at the right. */ + Right, + /** Origin at the top-right. */ + TopRight, + /** Origin at the top. */ + Top, + /** Origin at the top-left. */ + TopLeft, + /** Origin at the left. */ + Left, + /** Origin at the bottom-left. */ + BottomLeft, + /** Origin at the bottom. */ + Bottom, + /** Origin at the bottom-right. */ + BottomRight +} diff --git a/packages/core/src/2d/index.ts b/packages/core/src/2d/index.ts index 47be64ccfc..5481f42b28 100644 --- a/packages/core/src/2d/index.ts +++ b/packages/core/src/2d/index.ts @@ -1,11 +1,14 @@ export type { ISpriteAssembler } from "./assembler/ISpriteAssembler"; export type { ISpriteRenderer } from "./assembler/ISpriteRenderer"; +export { FilledSpriteAssembler } from "./assembler/FilledSpriteAssembler"; export { SimpleSpriteAssembler } from "./assembler/SimpleSpriteAssembler"; export { SlicedSpriteAssembler } from "./assembler/SlicedSpriteAssembler"; export { TiledSpriteAssembler } from "./assembler/TiledSpriteAssembler"; export { SpriteAtlas } from "./atlas/SpriteAtlas"; export { FontStyle } from "./enums/FontStyle"; export { SpriteDrawMode } from "./enums/SpriteDrawMode"; +export { SpriteFilledMode } from "./enums/SpriteFilledMode"; +export { SpriteFilledOrigin } from "./enums/SpriteFilledOrigin"; export { SpriteMaskInteraction } from "./enums/SpriteMaskInteraction"; export { SpriteModifyFlags } from "./enums/SpriteModifyFlags"; export { SpriteTileMode } from "./enums/SpriteTileMode"; diff --git a/packages/core/src/2d/sprite/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts index c1b183ee7a..a75dac1510 100644 --- a/packages/core/src/2d/sprite/SpriteRenderer.ts +++ b/packages/core/src/2d/sprite/SpriteRenderer.ts @@ -10,10 +10,13 @@ import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManage import { ShaderProperty } from "../../shader/ShaderProperty"; import { ISpriteAssembler } from "../assembler/ISpriteAssembler"; import { ISpriteRenderer } from "../assembler/ISpriteRenderer"; +import { FilledSpriteAssembler } from "../assembler/FilledSpriteAssembler"; import { SimpleSpriteAssembler } from "../assembler/SimpleSpriteAssembler"; import { SlicedSpriteAssembler } from "../assembler/SlicedSpriteAssembler"; import { TiledSpriteAssembler } from "../assembler/TiledSpriteAssembler"; import { SpriteDrawMode } from "../enums/SpriteDrawMode"; +import { SpriteFilledMode } from "../enums/SpriteFilledMode"; +import { SpriteFilledOrigin } from "../enums/SpriteFilledOrigin"; import { SpriteMaskInteraction } from "../enums/SpriteMaskInteraction"; import { SpriteModifyFlags } from "../enums/SpriteModifyFlags"; import { SpriteTileMode } from "../enums/SpriteTileMode"; @@ -39,6 +42,14 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer { private _tileMode: SpriteTileMode = SpriteTileMode.Continuous; @assignmentClone private _tiledAdaptiveThreshold: number = 0.5; + @assignmentClone + private _filledMode: SpriteFilledMode = SpriteFilledMode.Radial360; + @assignmentClone + private _filledAmount: number = 1; + @assignmentClone + private _filledOrigin: SpriteFilledOrigin = SpriteFilledOrigin.Bottom; + @assignmentClone + private _filledClockWise: boolean = true; @deepClone private _color: Color = new Color(1, 1, 1, 1); @@ -78,6 +89,9 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer { case SpriteDrawMode.Tiled: this._assembler = TiledSpriteAssembler; break; + case SpriteDrawMode.Filled: + this._assembler = FilledSpriteAssembler; + break; default: break; } @@ -119,6 +133,74 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer { } } + /** + * The fill amount of the sprite renderer, range from 0 to 1. (Only works in filled mode.) + */ + get filledAmount(): number { + return this._filledAmount; + } + + set filledAmount(value: number) { + value = MathUtil.clamp(value, 0, 1); + if (this._filledAmount !== value) { + this._filledAmount = value; + if (this._drawMode === SpriteDrawMode.Filled) { + this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeAndUV; + } + } + } + + /** + * The fill mode of the sprite renderer. (Only works in filled mode.) + */ + get filledMode(): SpriteFilledMode { + return this._filledMode; + } + + set filledMode(value: SpriteFilledMode) { + if (this._filledMode !== value) { + this._filledMode = value; + // Reset origin to a valid default for the new mode + this._filledOrigin = + value === SpriteFilledMode.Radial90 ? SpriteFilledOrigin.BottomLeft : SpriteFilledOrigin.Bottom; + if (this._drawMode === SpriteDrawMode.Filled) { + this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeAndUV; + } + } + } + + /** + * The fill origin of the sprite renderer. (Only works in filled mode.) + */ + get filledOrigin(): SpriteFilledOrigin { + return this._filledOrigin; + } + + set filledOrigin(value: SpriteFilledOrigin) { + if (this._filledOrigin !== value) { + this._filledOrigin = value; + if (this._drawMode === SpriteDrawMode.Filled) { + this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeAndUV; + } + } + } + + /** + * Whether the fill is clockwise. (Only works in filled radial mode.) + */ + get filledClockWise(): boolean { + return this._filledClockWise; + } + + set filledClockWise(value: boolean) { + if (this._filledClockWise !== value) { + this._filledClockWise = value; + if (this._drawMode === SpriteDrawMode.Filled) { + this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeAndUV; + } + } + } + /** * The Sprite to render. */ @@ -438,6 +520,9 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer { case SpriteDrawMode.Tiled: this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeUVAndColor; break; + case SpriteDrawMode.Filled: + this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.WorldVolumeUVAndColor; + break; } break; case SpriteModifyFlags.border: From ca5252a9a83aef495c97593719940f1a927e96de Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 7 Apr 2026 16:21:59 +0800 Subject: [PATCH 031/130] feat: supported filled --- examples/src/sprite-renderer-filled.ts | 146 ++++++++++++++ packages/core/src/Camera.ts | 4 +- packages/core/src/clone/CloneManager.ts | 14 +- .../core/src/particle/ParticleRenderer.ts | 4 +- packages/core/src/shader/ShaderData.ts | 6 +- packages/ui/src/component/advanced/Image.ts | 84 ++++++++ tests/src/core/ShaderData.test.ts | 184 ++++++++++++++++++ 7 files changed, 433 insertions(+), 9 deletions(-) create mode 100644 examples/src/sprite-renderer-filled.ts create mode 100644 tests/src/core/ShaderData.test.ts diff --git a/examples/src/sprite-renderer-filled.ts b/examples/src/sprite-renderer-filled.ts new file mode 100644 index 0000000000..e31504875f --- /dev/null +++ b/examples/src/sprite-renderer-filled.ts @@ -0,0 +1,146 @@ +/** + * @title Sprite Filled + * @category 2D + */ + +import * as dat from "dat.gui"; +import { + AssetType, + Camera, + Sprite, + SpriteDrawMode, + SpriteFilledMode, + SpriteFilledOrigin, + SpriteRenderer, + Texture2D, + Vector3, + WebGLEngine +} from "@galacean/engine"; + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + scene.background.solidColor.set(0.15, 0.15, 0.18, 1); + const rootEntity = scene.createRootEntity(); + + // Create camera + const cameraEntity = rootEntity.createChild("camera"); + cameraEntity.transform.setPosition(0, 0, 50); + cameraEntity.addComponent(Camera); + + // Load texture and create sprite + engine.resourceManager + .load({ + url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*ApFPTZSqcMkAAAAAAAAAAAAAARQnAQ", + type: AssetType.Texture2D + }) + .then((texture) => { + const spriteEntity = rootEntity.createChild("sprite"); + spriteEntity.transform.position = new Vector3(0, 0, 0); + spriteEntity.transform.setScale(2, 2, 2); + const renderer = spriteEntity.addComponent(SpriteRenderer); + renderer.sprite = new Sprite(engine, texture); + + // Set filled mode + renderer.drawMode = SpriteDrawMode.Filled; + renderer.filledMode = SpriteFilledMode.Radial360; + renderer.filledOrigin = SpriteFilledOrigin.Bottom; + renderer.filledAmount = 0.75; + renderer.filledClockWise = true; + + addGUI(renderer); + }); + + engine.run(); + + function addGUI(renderer: SpriteRenderer) { + const gui = new dat.GUI(); + + const filledModeMap: Record = { + Horizontal: SpriteFilledMode.Horizontal, + Vertical: SpriteFilledMode.Vertical, + Radial90: SpriteFilledMode.Radial90, + Radial180: SpriteFilledMode.Radial180, + Radial360: SpriteFilledMode.Radial360 + }; + + const originForRadial360: string[] = ["Right", "Top", "Left", "Bottom"]; + const originForRadial180: string[] = ["Right", "Top", "Left", "Bottom"]; + const originForRadial90: string[] = ["BottomLeft", "BottomRight", "TopRight", "TopLeft"]; + const originForHorizontal: string[] = ["Left", "Right"]; + const originForVertical: string[] = ["Bottom", "Top"]; + + const originMap: Record = { + Right: SpriteFilledOrigin.Right, + TopRight: SpriteFilledOrigin.TopRight, + Top: SpriteFilledOrigin.Top, + TopLeft: SpriteFilledOrigin.TopLeft, + Left: SpriteFilledOrigin.Left, + BottomLeft: SpriteFilledOrigin.BottomLeft, + Bottom: SpriteFilledOrigin.Bottom, + BottomRight: SpriteFilledOrigin.BottomRight + }; + + const state = { + filledMode: "Radial360", + origin: "Bottom", + amount: 0.75, + clockWise: true + }; + + const folder = gui.addFolder("Filled Sprite"); + folder.open(); + + // Filled mode + folder.add(state, "filledMode", Object.keys(filledModeMap)).onChange((value: string) => { + renderer.filledMode = filledModeMap[value]; + updateOriginOptions(value); + }); + + // Origin + let originCtrl = folder.add(state, "origin", originForRadial360).onChange((value: string) => { + renderer.filledOrigin = originMap[value]; + }); + + // Amount + folder.add(state, "amount", 0.0, 1.0, 0.01).onChange((value: number) => { + renderer.filledAmount = value; + }); + + // ClockWise + folder.add(state, "clockWise").onChange((value: boolean) => { + renderer.filledClockWise = value; + }); + + function updateOriginOptions(mode: string) { + folder.remove(originCtrl); + + let options: string[]; + switch (mode) { + case "Horizontal": + options = originForHorizontal; + break; + case "Vertical": + options = originForVertical; + break; + case "Radial90": + options = originForRadial90; + break; + case "Radial180": + options = originForRadial180; + break; + default: + options = originForRadial360; + break; + } + + state.origin = options[0]; + renderer.filledOrigin = originMap[state.origin]; + + originCtrl = folder.add(state, "origin", options).onChange((value: string) => { + renderer.filledOrigin = originMap[value]; + }); + } + } +}); diff --git a/packages/core/src/Camera.ts b/packages/core/src/Camera.ts index 55d904f6c9..3faf19a2ef 100644 --- a/packages/core/src/Camera.ts +++ b/packages/core/src/Camera.ts @@ -9,7 +9,7 @@ import { Transform } from "./Transform"; import { UpdateFlagManager } from "./UpdateFlagManager"; import { VirtualCamera } from "./VirtualCamera"; import { GLCapabilityType, Logger } from "./base"; -import { deepClone, ignoreClone } from "./clone/CloneManager"; +import { assignmentClone, deepClone, ignoreClone } from "./clone/CloneManager"; import { AntiAliasing } from "./enums/AntiAliasing"; import { CameraClearFlags } from "./enums/CameraClearFlags"; import { CameraModifyFlags } from "./enums/CameraModifyFlags"; @@ -125,8 +125,10 @@ export class Camera extends Component { @deepClone _virtualCamera: VirtualCamera = new VirtualCamera(); /** @internal */ + @assignmentClone _replacementShader: Shader = null; /** @internal */ + @assignmentClone _replacementSubShaderTag: ShaderTagKey = null; /** @internal */ _replacementFailureStrategy: ReplacementFailureStrategy = null; diff --git a/packages/core/src/clone/CloneManager.ts b/packages/core/src/clone/CloneManager.ts index 04c7c8d2c0..cd4129776a 100644 --- a/packages/core/src/clone/CloneManager.ts +++ b/packages/core/src/clone/CloneManager.ts @@ -195,16 +195,18 @@ export class CloneManager { } break; default: + // Check if we've already visited this source object (cycle detection) + if (deepInstanceMap.has(sourceProperty)) { + target[k] = deepInstanceMap.get(sourceProperty); + return; + } + let targetProperty = target[k]; - // If the target property is undefined, create new instance and keep reference sharing like the source if (!targetProperty) { - targetProperty = deepInstanceMap.get(sourceProperty); - if (!targetProperty) { - targetProperty = new sourceProperty.constructor(); - deepInstanceMap.set(sourceProperty, targetProperty); - } + targetProperty = new sourceProperty.constructor(); target[k] = targetProperty; } + deepInstanceMap.set(sourceProperty, targetProperty); if ((sourceProperty).copyFrom) { (targetProperty).copyFrom(sourceProperty); diff --git a/packages/core/src/particle/ParticleRenderer.ts b/packages/core/src/particle/ParticleRenderer.ts index c7a8e27b10..563648f126 100644 --- a/packages/core/src/particle/ParticleRenderer.ts +++ b/packages/core/src/particle/ParticleRenderer.ts @@ -5,7 +5,7 @@ import { Renderer, RendererUpdateFlags } from "../Renderer"; import { TransformModifyFlags } from "../Transform"; import { GLCapabilityType } from "../base/Constant"; import { Logger } from "../base/Logger"; -import { deepClone, ignoreClone, shallowClone } from "../clone/CloneManager"; +import { assignmentClone, deepClone, ignoreClone, shallowClone } from "../clone/CloneManager"; import { ModelMesh } from "../mesh/ModelMesh"; import { ShaderMacro } from "../shader/ShaderMacro"; import { ShaderProperty } from "../shader/ShaderProperty"; @@ -47,7 +47,9 @@ export class ParticleRenderer extends Renderer { @ignoreClone _transformedBounds = new BoundingBox(); + @assignmentClone private _renderMode: ParticleRenderMode; + @assignmentClone private _currentRenderModeMacro: ShaderMacro; private _mesh: ModelMesh; private _supportInstancedArrays: boolean; diff --git a/packages/core/src/shader/ShaderData.ts b/packages/core/src/shader/ShaderData.ts index 72a2f096f0..f2a8a7ccd5 100644 --- a/packages/core/src/shader/ShaderData.ts +++ b/packages/core/src/shader/ShaderData.ts @@ -607,7 +607,11 @@ export class ShaderData implements IReferable, IClone { cloneTo(target: ShaderData): void { CloneManager.deepCloneObject(this._macroCollection, target._macroCollection, new Map()); - Object.assign(target._macroMap, this._macroMap); + const targetMacroMap = target._macroMap; + for (const key in targetMacroMap) { + delete targetMacroMap[key]; + } + Object.assign(targetMacroMap, this._macroMap); const referCount = target._getReferCount(); const propertyValueMap = this._propertyValueMap; const targetPropertyValueMap = target._propertyValueMap; diff --git a/packages/ui/src/component/advanced/Image.ts b/packages/ui/src/component/advanced/Image.ts index ba01a9cc02..7f074fed75 100644 --- a/packages/ui/src/component/advanced/Image.ts +++ b/packages/ui/src/component/advanced/Image.ts @@ -1,6 +1,7 @@ import { BoundingBox, Entity, + FilledSpriteAssembler, ISpriteAssembler, ISpriteRenderer, MathUtil, @@ -10,6 +11,8 @@ import { SlicedSpriteAssembler, Sprite, SpriteDrawMode, + SpriteFilledMode, + SpriteFilledOrigin, SpriteModifyFlags, SpriteTileMode, TiledSpriteAssembler, @@ -46,6 +49,14 @@ export class Image extends UIRenderer implements ISpriteRenderer { @assignmentClone private _tiledAdaptiveThreshold: number = 0.5; @assignmentClone + private _filledMode: SpriteFilledMode = SpriteFilledMode.Radial360; + @assignmentClone + private _filledAmount: number = 1; + @assignmentClone + private _filledOrigin: SpriteFilledOrigin = SpriteFilledOrigin.Bottom; + @assignmentClone + private _filledClockWise: boolean = true; + @assignmentClone private _sizeMode: SpriteSizeMode = SpriteSizeMode.Custom; /** @@ -68,6 +79,9 @@ export class Image extends UIRenderer implements ISpriteRenderer { case SpriteDrawMode.Tiled: this._assembler = TiledSpriteAssembler; break; + case SpriteDrawMode.Filled: + this._assembler = FilledSpriteAssembler; + break; default: break; } @@ -126,6 +140,73 @@ export class Image extends UIRenderer implements ISpriteRenderer { } } + /** + * The fill amount of the image, range from 0 to 1. (Only works in filled mode.) + */ + get filledAmount(): number { + return this._filledAmount; + } + + set filledAmount(value: number) { + value = MathUtil.clamp(value, 0, 1); + if (this._filledAmount !== value) { + this._filledAmount = value; + if (this._drawMode === SpriteDrawMode.Filled) { + this._dirtyUpdateFlag |= ImageUpdateFlags.WorldVolumeAndUV; + } + } + } + + /** + * The fill mode of the image. (Only works in filled mode.) + */ + get filledMode(): SpriteFilledMode { + return this._filledMode; + } + + set filledMode(value: SpriteFilledMode) { + if (this._filledMode !== value) { + this._filledMode = value; + this._filledOrigin = + value === SpriteFilledMode.Radial90 ? SpriteFilledOrigin.BottomLeft : SpriteFilledOrigin.Bottom; + if (this._drawMode === SpriteDrawMode.Filled) { + this._dirtyUpdateFlag |= ImageUpdateFlags.WorldVolumeAndUV; + } + } + } + + /** + * The fill origin of the image. (Only works in filled mode.) + */ + get filledOrigin(): SpriteFilledOrigin { + return this._filledOrigin; + } + + set filledOrigin(value: SpriteFilledOrigin) { + if (this._filledOrigin !== value) { + this._filledOrigin = value; + if (this._drawMode === SpriteDrawMode.Filled) { + this._dirtyUpdateFlag |= ImageUpdateFlags.WorldVolumeAndUV; + } + } + } + + /** + * Whether the fill is clockwise. (Only works in filled radial mode.) + */ + get filledClockWise(): boolean { + return this._filledClockWise; + } + + set filledClockWise(value: boolean) { + if (this._filledClockWise !== value) { + this._filledClockWise = value; + if (this._drawMode === SpriteDrawMode.Filled) { + this._dirtyUpdateFlag |= ImageUpdateFlags.WorldVolumeAndUV; + } + } + } + /** * The Sprite to render. */ @@ -316,6 +397,9 @@ export class Image extends UIRenderer implements ISpriteRenderer { case SpriteDrawMode.Tiled: this._dirtyUpdateFlag |= ImageUpdateFlags.WorldVolumeUVAndColor; break; + case SpriteDrawMode.Filled: + this._dirtyUpdateFlag |= ImageUpdateFlags.WorldVolumeUVAndColor; + break; default: break; } diff --git a/tests/src/core/ShaderData.test.ts b/tests/src/core/ShaderData.test.ts new file mode 100644 index 0000000000..6903c67150 --- /dev/null +++ b/tests/src/core/ShaderData.test.ts @@ -0,0 +1,184 @@ +import { CloneManager, ShaderData, ShaderDataGroup, ShaderMacro } from "@galacean/engine-core"; +import { CloneMode } from "@galacean/engine-core/src/clone/enums/CloneMode"; +import { describe, expect, it } from "vitest"; + +describe("ShaderData", () => { + describe("Macro operations", () => { + it("enableMacro and disableMacro", () => { + const shaderData = new ShaderData(ShaderDataGroup.Renderer); + const macro = ShaderMacro.getByName("TEST_ENABLE_DISABLE"); + + shaderData.enableMacro("TEST_ENABLE_DISABLE"); + expect(shaderData._macroCollection.isEnable(macro)).to.be.true; + + const macros = shaderData.getMacros() as ShaderMacro[]; + expect(macros).to.have.lengthOf(1); + expect(macros[0]).to.equal(macro); + + shaderData.disableMacro("TEST_ENABLE_DISABLE"); + expect(shaderData._macroCollection.isEnable(macro)).to.be.false; + expect(shaderData.getMacros() as ShaderMacro[]).to.have.lengthOf(0); + }); + + it("enableMacro with value replaces same-name macro", () => { + const shaderData = new ShaderData(ShaderDataGroup.Renderer); + + shaderData.enableMacro("TEST_VALUE_MACRO", "1"); + const macro1 = ShaderMacro.getByName("TEST_VALUE_MACRO", "1"); + expect(shaderData._macroCollection.isEnable(macro1)).to.be.true; + + shaderData.enableMacro("TEST_VALUE_MACRO", "2"); + const macro2 = ShaderMacro.getByName("TEST_VALUE_MACRO", "2"); + expect(shaderData._macroCollection.isEnable(macro1)).to.be.false; + expect(shaderData._macroCollection.isEnable(macro2)).to.be.true; + expect(shaderData.getMacros() as ShaderMacro[]).to.have.lengthOf(1); + }); + }); + + describe("cloneTo", () => { + it("should produce identical macros in target", () => { + const source = new ShaderData(ShaderDataGroup.Renderer); + source.enableMacro("CLONE_MACRO_A"); + source.enableMacro("CLONE_MACRO_B"); + + const target = new ShaderData(ShaderDataGroup.Renderer); + source.cloneTo(target); + + const macroA = ShaderMacro.getByName("CLONE_MACRO_A"); + const macroB = ShaderMacro.getByName("CLONE_MACRO_B"); + expect(target._macroCollection.isEnable(macroA)).to.be.true; + expect(target._macroCollection.isEnable(macroB)).to.be.true; + + const targetMacros = target.getMacros() as ShaderMacro[]; + expect(targetMacros).to.have.lengthOf(2); + }); + + it("should clear stale macros in target before cloning", () => { + const source = new ShaderData(ShaderDataGroup.Renderer); + source.enableMacro("SOURCE_ONLY_MACRO"); + + const target = new ShaderData(ShaderDataGroup.Renderer); + target.enableMacro("TARGET_STALE_MACRO"); + + source.cloneTo(target); + + const staleMacro = ShaderMacro.getByName("TARGET_STALE_MACRO"); + const sourceMacro = ShaderMacro.getByName("SOURCE_ONLY_MACRO"); + + expect(target._macroCollection.isEnable(staleMacro)).to.be.false; + const targetMacros = target.getMacros() as ShaderMacro[]; + expect(targetMacros).to.have.lengthOf(1); + expect(targetMacros[0]).to.equal(sourceMacro); + + const macroMap = (target as any)._macroMap; + for (const key in macroMap) { + expect(Number(key)).to.equal(macroMap[key]._nameId); + } + }); + + it("should not have duplicate macros with same name under different keys", () => { + const target = new ShaderData(ShaderDataGroup.Renderer); + target.enableMacro("MACRO_X"); + target.enableMacro("MACRO_Y"); + target.enableMacro("MACRO_Z"); + + const source = new ShaderData(ShaderDataGroup.Renderer); + source.enableMacro("MACRO_Y"); + + source.cloneTo(target); + + const targetMacros = target.getMacros() as ShaderMacro[]; + expect(targetMacros).to.have.lengthOf(1); + expect(targetMacros[0].name).to.equal("MACRO_Y"); + + const names = targetMacros.map((m) => m.name); + const uniqueNames = [...new Set(names)]; + expect(names.length).to.equal(uniqueNames.length); + }); + + it("clone() should produce a clean independent copy", () => { + const source = new ShaderData(ShaderDataGroup.Renderer); + source.enableMacro("CLONE_INDEPENDENT_A"); + source.enableMacro("CLONE_INDEPENDENT_B"); + + const cloned = source.clone(); + + const macroA = ShaderMacro.getByName("CLONE_INDEPENDENT_A"); + const macroB = ShaderMacro.getByName("CLONE_INDEPENDENT_B"); + expect(cloned._macroCollection.isEnable(macroA)).to.be.true; + expect(cloned._macroCollection.isEnable(macroB)).to.be.true; + + source.disableMacro("CLONE_INDEPENDENT_A"); + expect(cloned._macroCollection.isEnable(macroA)).to.be.true; + }); + }); + + describe("CloneManager", () => { + it("default cloneMode deep-clones same-constructor objects", () => { + const sourceObj = { name: "B", id: 2 }; + const targetObj = { name: "A", id: 1 }; + const source = { _field: sourceObj }; + const target = { _field: targetObj }; + + CloneManager.cloneProperty(source, target, "_field", undefined, null, null, new Map()); + + expect(target._field).to.equal(targetObj); + expect(targetObj.name).to.equal("B"); + expect(targetObj.id).to.equal(2); + }); + + it("CloneMode.Assignment prevents singleton corruption", () => { + const macroA = ShaderMacro.getByName("SINGLETON_FIX_A"); + const macroB = ShaderMacro.getByName("SINGLETON_FIX_B"); + + const source = { _macro: macroB }; + const target = { _macro: macroA }; + + CloneManager.cloneProperty(source, target, "_macro", CloneMode.Assignment, null, null, new Map()); + + expect(macroA.name).to.equal("SINGLETON_FIX_A"); + expect(macroA._nameId).to.not.equal(macroB._nameId); + expect(target._macro).to.equal(macroB); + }); + + it("should not infinite loop on circular references", () => { + // Simulate AnimatorState ↔ AnimatorStateTransition cycle: + // State has transitions array, each Transition has destinationState pointing back to a State + class FakeState { + transitions: FakeTransition[] = []; + } + class FakeTransition { + destinationState: FakeState = null; + } + + // Build circular graph: stateA → transitionAB → stateB → transitionBA → stateA + const srcStateA = new FakeState(); + const srcStateB = new FakeState(); + const srcTransAB = new FakeTransition(); + const srcTransBA = new FakeTransition(); + srcTransAB.destinationState = srcStateB; + srcTransBA.destinationState = srcStateA; + srcStateA.transitions = [srcTransAB]; + srcStateB.transitions = [srcTransBA]; + + // Target has its own independent state graph + const tgtStateA = new FakeState(); + const tgtStateB = new FakeState(); + const tgtTransAB = new FakeTransition(); + const tgtTransBA = new FakeTransition(); + tgtTransAB.destinationState = tgtStateB; + tgtTransBA.destinationState = tgtStateA; + tgtStateA.transitions = [tgtTransAB]; + tgtStateB.transitions = [tgtTransBA]; + + const source = { state: srcStateA }; + const target = { state: tgtStateA }; + + // This must not hang — should complete within milliseconds + CloneManager.cloneProperty(source, target, "state", CloneMode.Deep, null, null, new Map()); + + // After clone, target's state graph should reflect source's data + expect(target.state.transitions).to.have.lengthOf(1); + }); + }); +}); From 5c2edee1ec7fa2ccec0b3c60d3cdfee9c40ff5b1 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Wed, 15 Apr 2026 15:12:58 +0800 Subject: [PATCH 032/130] fix: clone and sibling index --- packages/core/src/Entity.ts | 2 +- packages/core/src/clone/CloneManager.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core/src/Entity.ts b/packages/core/src/Entity.ts index 43a32f7514..6cb9945d56 100644 --- a/packages/core/src/Entity.ts +++ b/packages/core/src/Entity.ts @@ -218,7 +218,7 @@ export class Entity extends EngineObject { if (this._isRoot) { this._setSiblingIndex(this._scene._rootEntities, value); - } else { + } else if (this._parent) { const parent = this._parent; this._setSiblingIndex(parent._children, value); parent._dispatchModify(EntityModifyFlags.Child, parent); diff --git a/packages/core/src/clone/CloneManager.ts b/packages/core/src/clone/CloneManager.ts index cd4129776a..135bdd610b 100644 --- a/packages/core/src/clone/CloneManager.ts +++ b/packages/core/src/clone/CloneManager.ts @@ -115,8 +115,9 @@ export class CloneManager { if (effectiveCloneMode === CloneMode.Ignore) return; const targetProperty = target[k]; - if ( - effectiveCloneMode === undefined && + if (effectiveCloneMode === undefined) { + effectiveCloneMode = CloneMode.Assignment; + } else if ( sourceProperty instanceof Object && targetProperty && targetProperty !== sourceProperty && From 40006d952f0ead20384bedbb02dced7ff2179ebf Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Wed, 15 Apr 2026 16:37:08 +0800 Subject: [PATCH 033/130] fix: raycast collider layer --- packages/core/src/clone/CloneManager.ts | 117 ++++++++++++++++------ packages/core/src/physics/PhysicsScene.ts | 2 +- tests/src/core/CloneUtils.test.ts | 4 +- 3 files changed, 88 insertions(+), 35 deletions(-) diff --git a/packages/core/src/clone/CloneManager.ts b/packages/core/src/clone/CloneManager.ts index 135bdd610b..4a8bf584b2 100644 --- a/packages/core/src/clone/CloneManager.ts +++ b/packages/core/src/clone/CloneManager.ts @@ -104,42 +104,46 @@ export class CloneManager { deepInstanceMap: Map ): void { const sourceProperty = source[k]; - let effectiveCloneMode = cloneMode; - // Remappable references (Entity/Component) are always remapped, regardless of clone decorator + // 1. Remappable references (Entity/Component) are always remapped, highest priority if (sourceProperty instanceof Object && (sourceProperty)._remap) { target[k] = (sourceProperty)._remap(srcRoot, targetRoot); return; } - if (effectiveCloneMode === CloneMode.Ignore) return; + // 2. Explicit ignore + if (cloneMode === CloneMode.Ignore) return; + + // 3. Primitives / null / undefined - direct assign + if (!(sourceProperty instanceof Object)) { + target[k] = sourceProperty; + return; + } - const targetProperty = target[k]; + // 4. Determine effective clone mode + let effectiveCloneMode = cloneMode; if (effectiveCloneMode === undefined) { - effectiveCloneMode = CloneMode.Assignment; - } else if ( - sourceProperty instanceof Object && - targetProperty && - targetProperty !== sourceProperty && - targetProperty.constructor === sourceProperty.constructor - ) { - // Component constructors already create instance-local mutable objects. - // Preserve that isolation when cloning prefab templates instead of - // overwriting the clone with the template's shared reference. - effectiveCloneMode = CloneMode.Deep; + // Undecorated: infer from runtime type + effectiveCloneMode = CloneManager._inferCloneMode(sourceProperty, target[k]); + } else { + // Decorated: upgrade to Deep if target already has independent same-type instance + const targetProperty = target[k]; + if ( + targetProperty && + targetProperty !== sourceProperty && + targetProperty.constructor === sourceProperty.constructor + ) { + effectiveCloneMode = CloneMode.Deep; + } } - // Primitives, undecorated, or @assignmentClone: direct assign - if ( - !(sourceProperty instanceof Object) || - effectiveCloneMode === undefined || - effectiveCloneMode === CloneMode.Assignment - ) { + // 5. Assignment - direct reference copy + if (effectiveCloneMode === CloneMode.Assignment) { target[k] = sourceProperty; return; } - // @shallowClone / @deepClone: deep copy complex objects + // 6. Shallow/Deep clone for complex types const type = sourceProperty.constructor; switch (type) { case Uint8Array: @@ -164,7 +168,15 @@ export class CloneManager { } else { targetPropertyM.clear(); } - (>sourceProperty).forEach((value, key) => targetPropertyM.set(key, value)); + (>sourceProperty).forEach((value, key) => { + if (key instanceof Object && (key)._remap) { + key = (key)._remap(srcRoot, targetRoot); + } + if (value instanceof Object && (value)._remap) { + value = (value)._remap(srcRoot, targetRoot); + } + targetPropertyM.set(key, value); + }); break; case Set: let targetPropertyS = >target[k]; @@ -173,7 +185,12 @@ export class CloneManager { } else { targetPropertyS.clear(); } - (>sourceProperty).forEach((value) => targetPropertyS.add(value)); + (>sourceProperty).forEach((value) => { + if (value instanceof Object && (value)._remap) { + value = (value)._remap(srcRoot, targetRoot); + } + targetPropertyS.add(value); + }); break; case Array: let targetPropertyA = >target[k]; @@ -188,7 +205,7 @@ export class CloneManager { >sourceProperty, targetPropertyA, i, - effectiveCloneMode, + cloneMode, // Pass original mode: decorated → children inherit, undecorated → children infer independently srcRoot, targetRoot, deepInstanceMap @@ -202,21 +219,21 @@ export class CloneManager { return; } - let targetProperty = target[k]; - if (!targetProperty) { - targetProperty = new sourceProperty.constructor(); - target[k] = targetProperty; + let targetPropertyD = target[k]; + if (!targetPropertyD) { + targetPropertyD = new sourceProperty.constructor(); + target[k] = targetPropertyD; } - deepInstanceMap.set(sourceProperty, targetProperty); + deepInstanceMap.set(sourceProperty, targetPropertyD); if ((sourceProperty).copyFrom) { - (targetProperty).copyFrom(sourceProperty); + (targetPropertyD).copyFrom(sourceProperty); } else { const cloneModes = CloneManager.getCloneMode(sourceProperty.constructor); for (let k in sourceProperty) { CloneManager.cloneProperty( sourceProperty, - targetProperty, + targetPropertyD, k, cloneModes[k], srcRoot, @@ -224,12 +241,46 @@ export class CloneManager { deepInstanceMap ); } - (sourceProperty)._cloneTo?.(targetProperty, srcRoot, targetRoot); + (sourceProperty)._cloneTo?.(targetPropertyD, srcRoot, targetRoot); } break; } } + /** + * Infer the appropriate clone mode for an undecorated property based on its runtime type. + * This enables user custom scripts to get correct clone behavior without decorators. + */ + private static _inferCloneMode(sourceProperty: Object, targetProperty: any): CloneMode { + // If target already has an independent instance of the same type, + // deep clone to preserve isolation (e.g., constructor-created objects) + if ( + targetProperty && + targetProperty !== sourceProperty && + targetProperty.constructor === sourceProperty.constructor + ) { + return CloneMode.Deep; + } + + // Arrays need recursive processing (may contain Entity/Component refs) + if (Array.isArray(sourceProperty)) return CloneMode.Deep; + + // TypedArrays - copy data + if (ArrayBuffer.isView(sourceProperty)) return CloneMode.Deep; + + // Maps and Sets - create independent copies + if (sourceProperty instanceof Map || sourceProperty instanceof Set) return CloneMode.Deep; + + // Value types with copyFrom (math types like Vector3, Color, etc.) + if ((sourceProperty).copyFrom) return CloneMode.Deep; + + // Plain objects - deep clone (may contain Entity/Component refs) + if (sourceProperty.constructor === Object) return CloneMode.Deep; + + // Other class instances (engine resources like Material, Texture) - shared reference + return CloneMode.Assignment; + } + static deepCloneObject(source: Object, target: Object, deepInstanceMap: Map): void { for (let k in source) { CloneManager.cloneProperty(source, target, k, CloneMode.Deep, null, null, deepInstanceMap); diff --git a/packages/core/src/physics/PhysicsScene.ts b/packages/core/src/physics/PhysicsScene.ts index b1f96d77dd..ce6bf2e4e8 100644 --- a/packages/core/src/physics/PhysicsScene.ts +++ b/packages/core/src/physics/PhysicsScene.ts @@ -835,7 +835,7 @@ export class PhysicsScene { if (!shape) { return false; } - return shape.collider.entity.layer & mask && shape.isSceneQuery; + return shape.collider.collisionLayer & mask && shape.isSceneQuery; }; } diff --git a/tests/src/core/CloneUtils.test.ts b/tests/src/core/CloneUtils.test.ts index 7d7195f323..2304138bad 100644 --- a/tests/src/core/CloneUtils.test.ts +++ b/tests/src/core/CloneUtils.test.ts @@ -244,7 +244,9 @@ describe("Clone remap", async () => { expect(clonedScript.speed).eq(42); expect(clonedScript.name2).eq("test"); expect(clonedScript.flag).eq(true); - expect(clonedScript.data).eq(obj); + // Plain objects are now deep cloned (independent copy) for undecorated properties + expect(clonedScript.data).not.eq(obj); + expect((clonedScript.data).x).eq(1); rootEntity.destroy(); }); From 4fe4fae272bd312bdb57008440f781a4dda4ca39 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Wed, 15 Apr 2026 16:48:27 +0800 Subject: [PATCH 034/130] fix(shader): add missing camera_VPMat declaration in Transform.glsl --- packages/shader/src/shaders/Transform.glsl | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/shader/src/shaders/Transform.glsl b/packages/shader/src/shaders/Transform.glsl index b5d1a52a68..7fccb94266 100644 --- a/packages/shader/src/shaders/Transform.glsl +++ b/packages/shader/src/shaders/Transform.glsl @@ -5,6 +5,7 @@ mat4 renderer_LocalMat; mat4 renderer_ModelMat; mat4 camera_ViewMat; mat4 camera_ProjMat; +mat4 camera_VPMat; mat4 renderer_MVMat; mat4 renderer_MVPMat; mat4 renderer_NormalMat; From 3071f908c83eb638c9397d198fe467d03cf11495 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Thu, 26 Mar 2026 11:53:57 +0800 Subject: [PATCH 035/130] fix(loader): always create GLTF_ROOT container for consistent animation paths (#2942) Remove single-root vs multi-root branching in GLTFSceneParser, always creating a GLTF_ROOT wrapper entity. This ensures animation bone paths are consistent across different glTF files, fixing cross-file animation clip retargeting. --- .../loader/src/gltf/parser/GLTFSceneParser.ts | 15 +++++---------- tests/src/core/Animator.test.ts | 10 +++++----- tests/src/loader/GLTFLoader.test.ts | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/loader/src/gltf/parser/GLTFSceneParser.ts b/packages/loader/src/gltf/parser/GLTFSceneParser.ts index 6517356c97..5ee981b1d3 100644 --- a/packages/loader/src/gltf/parser/GLTFSceneParser.ts +++ b/packages/loader/src/gltf/parser/GLTFSceneParser.ts @@ -31,16 +31,11 @@ export class GLTFSceneParser extends GLTFParser { const sceneNodes = sceneInfo.nodes || []; let sceneRoot: Entity; - if (sceneNodes.length === 1) { - sceneRoot = context.get(GLTFParserType.Entity, sceneNodes[0]); - } else { - sceneRoot = new Entity(engine, "GLTF_ROOT"); - // @ts-ignore - sceneRoot._markAsTemplate(glTFResource); - for (let i = 0; i < sceneNodes.length; i++) { - const childEntity = context.get(GLTFParserType.Entity, sceneNodes[i]); - sceneRoot.addChild(childEntity); - } + sceneRoot = new Entity(engine, "GLTF_ROOT"); + // @ts-ignore + sceneRoot._markAsTemplate(glTFResource); + for (let i = 0; i < sceneNodes.length; i++) { + sceneRoot.addChild(context.get(GLTFParserType.Entity, sceneNodes[i])); } if (isDefaultScene) { diff --git a/tests/src/core/Animator.test.ts b/tests/src/core/Animator.test.ts index 9a255a5184..6cb74ea8e2 100644 --- a/tests/src/core/Animator.test.ts +++ b/tests/src/core/Animator.test.ts @@ -307,7 +307,7 @@ describe("Animator test", function () { const additiveLayer = new AnimatorControllerLayer("additiveLayer"); additiveLayer.stateMachine = animatorStateMachine; const mask = AnimatorLayerMask.createByEntity(animator.entity); - mask.setPathMaskActive("_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04", false, true); + mask.setPathMaskActive("root/_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04", false, true); additiveLayer.mask = mask; additiveLayer.blendingMode = AnimatorLayerBlendingMode.Additive; animatorController.addLayer(additiveLayer); @@ -319,12 +319,12 @@ describe("Animator test", function () { animator.play("Walk", 0); animator.play("Run", 1); - const parentEntity = animator.entity.findByPath("_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/"); + const parentEntity = animator.entity.findByPath("root/_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/"); const targetEntity = animator.entity.findByPath( - "_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04" + "root/_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04" ); const childEntity = animator.entity.findByPath( - "_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04/b_Head_05" + "root/_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04/b_Head_05" ); let layerData = animator["_animatorLayersData"][1]; @@ -338,7 +338,7 @@ describe("Animator test", function () { expect(childLayerCurveOwner.isActive).to.eq(false); animator.animatorController.removeLayer(1); - mask.removePathMask("_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04/b_Head_05"); + mask.removePathMask("root/_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04/b_Head_05"); animator.animatorController.addLayer(additiveLayer); animator.play("Run", 1); layerData = animator["_animatorLayersData"][1]; diff --git a/tests/src/loader/GLTFLoader.test.ts b/tests/src/loader/GLTFLoader.test.ts index 402f2150c5..6bd96bf070 100644 --- a/tests/src/loader/GLTFLoader.test.ts +++ b/tests/src/loader/GLTFLoader.test.ts @@ -528,6 +528,21 @@ describe("glTF instance test", function () { }); }); +describe("glTF scene root structure", function () { + it("Single root scene should have GLTF_ROOT container", async () => { + const glTFResource: GLTFResource = await engine.resourceManager.load({ + type: AssetType.GLTF, + url: "mock/path/testRoot.gltf" + }); + const { defaultSceneRoot } = glTFResource; + + // Should always create GLTF_ROOT container, even for single-root scenes + expect(defaultSceneRoot.name).to.equal("GLTF_ROOT"); + expect(defaultSceneRoot.children.length).to.equal(1); + expect(defaultSceneRoot.children[0].name).to.equal("entity1"); + }); +}); + describe("glTF instance test", function () { it("GLTFResource destroy directly", async () => { const glTFResource: GLTFResource = await engine.resourceManager.load({ From 40734f839439ce6caa1b46f1133c325734951ed1 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Thu, 26 Mar 2026 13:41:56 +0800 Subject: [PATCH 036/130] fix(e2e): update blendShape e2e tests for GLTF_ROOT nesting - animator-blendShape: use getComponentsIncludeChildren to find SkinnedMeshRenderer on child entity - animator-multiSubMeshBlendShape: place Animator on actual model root (children[0]) so curve bindings with empty path resolve correctly --- e2e/case/animator-blendShape.ts | 2 +- e2e/case/animator-multiSubMeshBlendShape.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/case/animator-blendShape.ts b/e2e/case/animator-blendShape.ts index 28641ca91f..27307ec95d 100644 --- a/e2e/case/animator-blendShape.ts +++ b/e2e/case/animator-blendShape.ts @@ -38,7 +38,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { const { defaultSceneRoot } = asset; rootEntity.addChild(defaultSceneRoot); const animator = defaultSceneRoot.getComponent(Animator); - const skinMeshRenderer = defaultSceneRoot.getComponent(SkinnedMeshRenderer); + const skinMeshRenderer = defaultSceneRoot.getComponentsIncludeChildren(SkinnedMeshRenderer, [])[0]; skinMeshRenderer.blendShapeWeights[0] = 1.0; animator.play("TheWave"); diff --git a/e2e/case/animator-multiSubMeshBlendShape.ts b/e2e/case/animator-multiSubMeshBlendShape.ts index 546d9b8f3d..5c1365a758 100644 --- a/e2e/case/animator-multiSubMeshBlendShape.ts +++ b/e2e/case/animator-multiSubMeshBlendShape.ts @@ -45,8 +45,8 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { .then((asset) => { const { defaultSceneRoot } = asset; rootEntity.addChild(defaultSceneRoot); - const entity = defaultSceneRoot; - defaultSceneRoot.transform.rotation = new Vector3(-90, -0, 0); + const entity = defaultSceneRoot.children[0]; + entity.transform.rotation = new Vector3(-90, -0, 0); const animator = entity.addComponent(Animator); animator.animatorController = new AnimatorController(engine); From 5abb3f5f02860b6dc988c6bddd6b517b116ff3f6 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Wed, 15 Apr 2026 15:30:03 +0800 Subject: [PATCH 037/130] fix(loader): restore single-root GLTF scene without GLTF_ROOT wrapper Single-root GLTF scenes no longer wrap the root node in a GLTF_ROOT container, which avoids redundant nesting and fixes animation path resolution for models like Mixamo characters. - GLTFSceneParser: single-root scenes use the node directly as scene root - GLTFAnimationParser: remove single-root path prefixing (no longer needed) - Entity.findByPath: prefer real same-name child over legacy self-name prefix - Add AGENTS.md to .gitignore --- .gitignore | 1 + packages/core/src/Entity.ts | 9 ++++----- .../loader/src/gltf/parser/GLTFAnimationParser.ts | 8 -------- .../loader/src/gltf/parser/GLTFSceneParser.ts | 15 ++++++++++----- tests/src/core/Entity.test.ts | 11 +++++++++++ 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 249ab8c2d5..529f7b9da6 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ playwright-report/ # Claude Code files .claude/ CLAUDE.md +AGENTS.md .husky/post-checkout .husky/post-commit diff --git a/packages/core/src/Entity.ts b/packages/core/src/Entity.ts index 6cb9945d56..d61da36ec8 100644 --- a/packages/core/src/Entity.ts +++ b/packages/core/src/Entity.ts @@ -377,11 +377,10 @@ export class Entity extends EngineObject { return this; } - // Some imported animation clips are normalized to include the single scene root - // name (for example "mixamorig:Hips/..."), while the Animator may already sit on - // that root entity. Accept a self-name prefix so wrapped model roots and - // standalone single-root clips resolve through the same path convention. - if (splits[0] === this.name) { + // Some imported clips may redundantly prefix the current entity name + // (for example "GLTF_ROOT/hips"). Keep accepting that legacy shape, + // but only when it doesn't shadow a real same-name child. + if (splits[0] === this.name && !this._children.some((child) => child.name === this.name)) { return splits.length === 1 ? this : Entity._findChildByName(this, 0, splits, 1); } diff --git a/packages/loader/src/gltf/parser/GLTFAnimationParser.ts b/packages/loader/src/gltf/parser/GLTFAnimationParser.ts index 10714899e7..d1d7400c2f 100644 --- a/packages/loader/src/gltf/parser/GLTFAnimationParser.ts +++ b/packages/loader/src/gltf/parser/GLTFAnimationParser.ts @@ -118,14 +118,6 @@ export class GLTFAnimationParser extends GLTFParser { continue; } - // For single-root scenes, the scene root IS the top-level node (e.g. mixamorig:Hips). - // When this clip is used on a multi-root model (which wraps nodes in a GLTF_ROOT container), - // the Animator sits on GLTF_ROOT and needs the root node name in the path. - // Include the scene root name for single-root scenes to ensure consistent bone paths. - const sceneNodes = context.glTF.scenes[context.glTF.scene ?? 0]?.nodes; - if (sceneNodes?.length === 1) { - relativePath = relativePath === "" ? entity.name : `${entity.name}/${relativePath}`; - } let ComponentType: ComponentConstructor; let propertyName: string; switch (target.path) { diff --git a/packages/loader/src/gltf/parser/GLTFSceneParser.ts b/packages/loader/src/gltf/parser/GLTFSceneParser.ts index 5ee981b1d3..6517356c97 100644 --- a/packages/loader/src/gltf/parser/GLTFSceneParser.ts +++ b/packages/loader/src/gltf/parser/GLTFSceneParser.ts @@ -31,11 +31,16 @@ export class GLTFSceneParser extends GLTFParser { const sceneNodes = sceneInfo.nodes || []; let sceneRoot: Entity; - sceneRoot = new Entity(engine, "GLTF_ROOT"); - // @ts-ignore - sceneRoot._markAsTemplate(glTFResource); - for (let i = 0; i < sceneNodes.length; i++) { - sceneRoot.addChild(context.get(GLTFParserType.Entity, sceneNodes[i])); + if (sceneNodes.length === 1) { + sceneRoot = context.get(GLTFParserType.Entity, sceneNodes[0]); + } else { + sceneRoot = new Entity(engine, "GLTF_ROOT"); + // @ts-ignore + sceneRoot._markAsTemplate(glTFResource); + for (let i = 0; i < sceneNodes.length; i++) { + const childEntity = context.get(GLTFParserType.Entity, sceneNodes[i]); + sceneRoot.addChild(childEntity); + } } if (isDefaultScene) { diff --git a/tests/src/core/Entity.test.ts b/tests/src/core/Entity.test.ts index eba73370c0..0e192ef30b 100644 --- a/tests/src/core/Entity.test.ts +++ b/tests/src/core/Entity.test.ts @@ -333,6 +333,17 @@ describe("Entity", async () => { expect(parent.findByPath("parent/child/grandson")).eq(grandson); }); + it("findByPath prefers a real same-name child over legacy self-name prefix", () => { + const outerRoot = new Entity(engine, "GLTF_ROOT"); + outerRoot.parent = scene.getRootEntity(); + const innerRoot = new Entity(engine, "GLTF_ROOT"); + innerRoot.parent = outerRoot; + const hips = new Entity(engine, "mixamorig:Hips"); + hips.parent = innerRoot; + + expect(outerRoot.findByPath("GLTF_ROOT/mixamorig:Hips")).eq(hips); + }); + it("clearChildren", () => { const parent = new Entity(engine, "parent"); From b3bf58a816a903b6157079f0c3682bb309be8eeb Mon Sep 17 00:00:00 2001 From: luzhuang Date: Wed, 15 Apr 2026 17:05:38 +0800 Subject: [PATCH 038/130] Revert "fix(loader): restore single-root GLTF scene without GLTF_ROOT wrapper" This reverts commit 8a86f279eca9b2bc4a2fa56f94f174236f9a5674. --- .gitignore | 1 - packages/core/src/Entity.ts | 9 +++++---- .../loader/src/gltf/parser/GLTFAnimationParser.ts | 8 ++++++++ .../loader/src/gltf/parser/GLTFSceneParser.ts | 15 +++++---------- tests/src/core/Entity.test.ts | 11 ----------- 5 files changed, 18 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 529f7b9da6..249ab8c2d5 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,6 @@ playwright-report/ # Claude Code files .claude/ CLAUDE.md -AGENTS.md .husky/post-checkout .husky/post-commit diff --git a/packages/core/src/Entity.ts b/packages/core/src/Entity.ts index d61da36ec8..6cb9945d56 100644 --- a/packages/core/src/Entity.ts +++ b/packages/core/src/Entity.ts @@ -377,10 +377,11 @@ export class Entity extends EngineObject { return this; } - // Some imported clips may redundantly prefix the current entity name - // (for example "GLTF_ROOT/hips"). Keep accepting that legacy shape, - // but only when it doesn't shadow a real same-name child. - if (splits[0] === this.name && !this._children.some((child) => child.name === this.name)) { + // Some imported animation clips are normalized to include the single scene root + // name (for example "mixamorig:Hips/..."), while the Animator may already sit on + // that root entity. Accept a self-name prefix so wrapped model roots and + // standalone single-root clips resolve through the same path convention. + if (splits[0] === this.name) { return splits.length === 1 ? this : Entity._findChildByName(this, 0, splits, 1); } diff --git a/packages/loader/src/gltf/parser/GLTFAnimationParser.ts b/packages/loader/src/gltf/parser/GLTFAnimationParser.ts index d1d7400c2f..10714899e7 100644 --- a/packages/loader/src/gltf/parser/GLTFAnimationParser.ts +++ b/packages/loader/src/gltf/parser/GLTFAnimationParser.ts @@ -118,6 +118,14 @@ export class GLTFAnimationParser extends GLTFParser { continue; } + // For single-root scenes, the scene root IS the top-level node (e.g. mixamorig:Hips). + // When this clip is used on a multi-root model (which wraps nodes in a GLTF_ROOT container), + // the Animator sits on GLTF_ROOT and needs the root node name in the path. + // Include the scene root name for single-root scenes to ensure consistent bone paths. + const sceneNodes = context.glTF.scenes[context.glTF.scene ?? 0]?.nodes; + if (sceneNodes?.length === 1) { + relativePath = relativePath === "" ? entity.name : `${entity.name}/${relativePath}`; + } let ComponentType: ComponentConstructor; let propertyName: string; switch (target.path) { diff --git a/packages/loader/src/gltf/parser/GLTFSceneParser.ts b/packages/loader/src/gltf/parser/GLTFSceneParser.ts index 6517356c97..5ee981b1d3 100644 --- a/packages/loader/src/gltf/parser/GLTFSceneParser.ts +++ b/packages/loader/src/gltf/parser/GLTFSceneParser.ts @@ -31,16 +31,11 @@ export class GLTFSceneParser extends GLTFParser { const sceneNodes = sceneInfo.nodes || []; let sceneRoot: Entity; - if (sceneNodes.length === 1) { - sceneRoot = context.get(GLTFParserType.Entity, sceneNodes[0]); - } else { - sceneRoot = new Entity(engine, "GLTF_ROOT"); - // @ts-ignore - sceneRoot._markAsTemplate(glTFResource); - for (let i = 0; i < sceneNodes.length; i++) { - const childEntity = context.get(GLTFParserType.Entity, sceneNodes[i]); - sceneRoot.addChild(childEntity); - } + sceneRoot = new Entity(engine, "GLTF_ROOT"); + // @ts-ignore + sceneRoot._markAsTemplate(glTFResource); + for (let i = 0; i < sceneNodes.length; i++) { + sceneRoot.addChild(context.get(GLTFParserType.Entity, sceneNodes[i])); } if (isDefaultScene) { diff --git a/tests/src/core/Entity.test.ts b/tests/src/core/Entity.test.ts index 0e192ef30b..eba73370c0 100644 --- a/tests/src/core/Entity.test.ts +++ b/tests/src/core/Entity.test.ts @@ -333,17 +333,6 @@ describe("Entity", async () => { expect(parent.findByPath("parent/child/grandson")).eq(grandson); }); - it("findByPath prefers a real same-name child over legacy self-name prefix", () => { - const outerRoot = new Entity(engine, "GLTF_ROOT"); - outerRoot.parent = scene.getRootEntity(); - const innerRoot = new Entity(engine, "GLTF_ROOT"); - innerRoot.parent = outerRoot; - const hips = new Entity(engine, "mixamorig:Hips"); - hips.parent = innerRoot; - - expect(outerRoot.findByPath("GLTF_ROOT/mixamorig:Hips")).eq(hips); - }); - it("clearChildren", () => { const parent = new Entity(engine, "parent"); From 069f182a86a676e1c682ea0ec21e5dde838844a6 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Wed, 15 Apr 2026 17:38:32 +0800 Subject: [PATCH 039/130] fix(loader): normalize gltf wrapper and skin roots --- .../src/gltf/parser/GLTFAnimationParser.ts | 8 --- .../src/gltf/parser/GLTFParserContext.ts | 4 +- .../loader/src/gltf/parser/GLTFSceneParser.ts | 1 + .../loader/src/gltf/parser/GLTFSkinParser.ts | 45 ++++++++++++- tests/src/loader/GLTFLoader.test.ts | 66 +++++++++++++++++++ 5 files changed, 113 insertions(+), 11 deletions(-) diff --git a/packages/loader/src/gltf/parser/GLTFAnimationParser.ts b/packages/loader/src/gltf/parser/GLTFAnimationParser.ts index 10714899e7..d1d7400c2f 100644 --- a/packages/loader/src/gltf/parser/GLTFAnimationParser.ts +++ b/packages/loader/src/gltf/parser/GLTFAnimationParser.ts @@ -118,14 +118,6 @@ export class GLTFAnimationParser extends GLTFParser { continue; } - // For single-root scenes, the scene root IS the top-level node (e.g. mixamorig:Hips). - // When this clip is used on a multi-root model (which wraps nodes in a GLTF_ROOT container), - // the Animator sits on GLTF_ROOT and needs the root node name in the path. - // Include the scene root name for single-root scenes to ensure consistent bone paths. - const sceneNodes = context.glTF.scenes[context.glTF.scene ?? 0]?.nodes; - if (sceneNodes?.length === 1) { - relativePath = relativePath === "" ? entity.name : `${entity.name}/${relativePath}`; - } let ComponentType: ComponentConstructor; let propertyName: string; switch (target.path) { diff --git a/packages/loader/src/gltf/parser/GLTFParserContext.ts b/packages/loader/src/gltf/parser/GLTFParserContext.ts index 83f9f65124..b549da5ca2 100644 --- a/packages/loader/src/gltf/parser/GLTFParserContext.ts +++ b/packages/loader/src/gltf/parser/GLTFParserContext.ts @@ -116,13 +116,13 @@ export class GLTFParserContext { return AssetPromise.all([ this.get(GLTFParserType.Validator), + this.get(GLTFParserType.Scene), this.get(GLTFParserType.Texture), this.get(GLTFParserType.Material), this.get(GLTFParserType.Mesh), this.get(GLTFParserType.Skin), this.get(GLTFParserType.Animation), - this.get(GLTFParserType.AnimatorController), - this.get(GLTFParserType.Scene) + this.get(GLTFParserType.AnimatorController) ]).then(() => { const glTFResource = this.glTFResource; const animatorController = glTFResource.animatorController; diff --git a/packages/loader/src/gltf/parser/GLTFSceneParser.ts b/packages/loader/src/gltf/parser/GLTFSceneParser.ts index 5ee981b1d3..b74424d2e6 100644 --- a/packages/loader/src/gltf/parser/GLTFSceneParser.ts +++ b/packages/loader/src/gltf/parser/GLTFSceneParser.ts @@ -38,6 +38,7 @@ export class GLTFSceneParser extends GLTFParser { sceneRoot.addChild(context.get(GLTFParserType.Entity, sceneNodes[i])); } + (glTFResource._sceneRoots ||= [])[index] = sceneRoot; if (isDefaultScene) { glTFResource._defaultSceneRoot = sceneRoot; } diff --git a/packages/loader/src/gltf/parser/GLTFSkinParser.ts b/packages/loader/src/gltf/parser/GLTFSkinParser.ts index 7c1580e4ca..978dd4780e 100644 --- a/packages/loader/src/gltf/parser/GLTFSkinParser.ts +++ b/packages/loader/src/gltf/parser/GLTFSkinParser.ts @@ -39,7 +39,7 @@ export class GLTFSkinParser extends GLTFParser { const rootBone = entities[skeleton]; skin.rootBone = rootBone; } else { - const rootBone = this._findSkeletonRootBone(joints, entities); + const rootBone = this._findSceneRootBone(context, joints, entities) ?? this._findSkeletonRootBone(joints, entities); if (rootBone) { skin.rootBone = rootBone; } else { @@ -53,6 +53,49 @@ export class GLTFSkinParser extends GLTFParser { return AssetPromise.resolve(skinPromise); } + private _findSceneRootBone(context: GLTFParserContext, joints: number[], entities: Entity[]): Entity | null { + const { glTF, glTFResource } = context; + const scenes = glTF.scenes; + const sceneRoots = glTFResource._sceneRoots; + + if (!scenes?.length || !sceneRoots?.length) { + return null; + } + + for (let i = 0, n = scenes.length; i < n; i++) { + const sceneNodes = scenes[i].nodes ?? []; + if (sceneNodes.length <= 1) { + continue; + } + + const sceneRoot = sceneRoots[i]; + if (!sceneRoot) { + continue; + } + + const sceneRootChildren = new Set(sceneNodes.map((nodeIndex) => entities[nodeIndex])); + let allJointsUnderSceneRoot = true; + + for (let j = 0, m = joints.length; j < m; j++) { + let entity = entities[joints[j]]; + while (entity?.parent) { + entity = entity.parent; + } + + if (!sceneRootChildren.has(entity)) { + allJointsUnderSceneRoot = false; + break; + } + } + + if (allJointsUnderSceneRoot) { + return sceneRoot; + } + } + + return null; + } + private _findSkeletonRootBone(joints: number[], entities: Entity[]): Entity { const paths = >{}; for (const index of joints) { diff --git a/tests/src/loader/GLTFLoader.test.ts b/tests/src/loader/GLTFLoader.test.ts index 6bd96bf070..3c8aa822a7 100644 --- a/tests/src/loader/GLTFLoader.test.ts +++ b/tests/src/loader/GLTFLoader.test.ts @@ -39,6 +39,60 @@ beforeAll(async function () { @registerGLTFParser(GLTFParserType.Schema) class GLTFCustomJSONParser extends GLTFParser { parse(context: GLTFParserContext) { + if (context.glTFResource.url.endsWith("testSkinRoot.gltf")) { + context.buffers = [new ArrayBuffer(128)]; + return Promise.resolve({ + asset: { + version: "2.0" + }, + scene: 0, + scenes: [ + { + nodes: [0, 1] + } + ], + nodes: [ + { + name: "Character_Man" + }, + { + name: "mixamorig:Hips", + children: [2] + }, + { + name: "mixamorig:Spine" + } + ], + skins: [ + { + inverseBindMatrices: 0, + joints: [1, 2] + } + ], + accessors: [ + { + bufferView: 0, + byteOffset: 0, + componentType: 5126, + count: 2, + type: "MAT4" + } + ], + bufferViews: [ + { + buffer: 0, + byteOffset: 0, + byteLength: 128 + } + ], + buffers: [ + { + byteLength: 128 + } + ] + }); + } + const glTF = { buffers: [ { @@ -541,6 +595,18 @@ describe("glTF scene root structure", function () { expect(defaultSceneRoot.children.length).to.equal(1); expect(defaultSceneRoot.children[0].name).to.equal("entity1"); }); + + it("Multi-root skins without skeleton should use the scene wrapper as rootBone", async () => { + const glTFResource: GLTFResource = await engine.resourceManager.load({ + type: AssetType.GLTF, + url: "mock/path/testSkinRoot.gltf" + }); + const { defaultSceneRoot, skins } = glTFResource; + + expect(defaultSceneRoot.name).to.equal("GLTF_ROOT"); + expect(defaultSceneRoot.children.length).to.equal(2); + expect(skins[0].rootBone).to.equal(defaultSceneRoot); + }); }); describe("glTF instance test", function () { From f5be42400b9f2e6f90905ae3fb6feb2ad49203c3 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Wed, 15 Apr 2026 19:38:17 +0800 Subject: [PATCH 040/130] fix: raycast and clone --- packages/core/src/clone/CloneManager.ts | 7 +- .../physics-physx/src/PhysXPhysicsScene.ts | 22 ++- tests/src/core/AnimatorHang.test.ts | 20 +++ tests/src/core/CloneUtils.test.ts | 158 ++++++++++++++++++ tests/src/core/physics/PhysicsScene.test.ts | 1 + tests/src/ui/UIEvent.test.ts | 8 +- 6 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 tests/src/core/AnimatorHang.test.ts diff --git a/packages/core/src/clone/CloneManager.ts b/packages/core/src/clone/CloneManager.ts index 4a8bf584b2..b6f94ce248 100644 --- a/packages/core/src/clone/CloneManager.ts +++ b/packages/core/src/clone/CloneManager.ts @@ -121,12 +121,13 @@ export class CloneManager { } // 4. Determine effective clone mode - let effectiveCloneMode = cloneMode; + let effectiveCloneMode: CloneMode = cloneMode; if (effectiveCloneMode === undefined) { // Undecorated: infer from runtime type effectiveCloneMode = CloneManager._inferCloneMode(sourceProperty, target[k]); - } else { - // Decorated: upgrade to Deep if target already has independent same-type instance + } else if (effectiveCloneMode !== CloneMode.Assignment) { + // Decorated Shallow/Deep: upgrade to Deep if target already has independent same-type instance. + // Assignment is never upgraded — it means the user explicitly wants a reference copy. const targetProperty = target[k]; if ( targetProperty && diff --git a/packages/physics-physx/src/PhysXPhysicsScene.ts b/packages/physics-physx/src/PhysXPhysicsScene.ts index a56a1fabce..675d5fb604 100644 --- a/packages/physics-physx/src/PhysXPhysicsScene.ts +++ b/packages/physics-physx/src/PhysXPhysicsScene.ts @@ -214,19 +214,37 @@ export class PhysXPhysicsScene implements IPhysicsScene { } else { return 0; // eNONE } + }, + postFilter: (filterData, hit) => { + // Skip shapes that contain the ray origin (distance === 0). + // This matches Bullet physics behavior used by Cocos Creator, + // where raycasts do not report shapes enclosing the ray origin. + if (hit.distance === 0) { + return 0; // eNONE — skip this hit, PhysX will continue to the next candidate + } + return 2; // eBLOCK } }; - const pxRaycastCallback = this._physXPhysics._physX.PxQueryFilterCallback.implement(raycastCallback); + // Use POST_FILTER in addition to PRE_FILTER so postFilter callback is invoked + const physX = this._physXPhysics._physX; + const pxRaycastFilterData = new physX.PxQueryFilterData(); + pxRaycastFilterData.flags = new physX.PxQueryFlags( + QueryFlag.STATIC | QueryFlag.DYNAMIC | QueryFlag.PRE_FILTER | QueryFlag.POST_FILTER + ); + + const pxRaycastCallback = physX.PxQueryFilterCallback.implement(raycastCallback); const result = this._pxScene.raycastSingle( ray.origin, ray.direction, distance, pxHitResult, - this._pxFilterData, + pxRaycastFilterData, pxRaycastCallback ); + pxRaycastFilterData.flags.delete(); + pxRaycastFilterData.delete(); pxRaycastCallback.delete(); if (result && hit != undefined) { diff --git a/tests/src/core/AnimatorHang.test.ts b/tests/src/core/AnimatorHang.test.ts new file mode 100644 index 0000000000..99a8a5ba14 --- /dev/null +++ b/tests/src/core/AnimatorHang.test.ts @@ -0,0 +1,20 @@ +import { Animator, Camera } from "@galacean/engine-core"; +import "@galacean/engine-loader"; +import type { GLTFResource } from "@galacean/engine-loader"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { describe, expect, it } from "vitest"; +import { glbResource } from "./model/fox"; +const canvasDOM = document.createElement("canvas"); +canvasDOM.width = 1024; +canvasDOM.height = 1024; +describe("Canvas 1024 test", async function () { + const engine = await WebGLEngine.create({ canvas: canvasDOM }); + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity(); + rootEntity.addComponent(Camera); + const resource = await engine.resourceManager.load(glbResource); + const defaultSceneRoot = resource.defaultSceneRoot; + rootEntity.addChild(defaultSceneRoot); + const animator = defaultSceneRoot.getComponent(Animator); + it("loaded", () => { expect(animator).not.eq(null); }); +}); diff --git a/tests/src/core/CloneUtils.test.ts b/tests/src/core/CloneUtils.test.ts index 2304138bad..80ffbe27dd 100644 --- a/tests/src/core/CloneUtils.test.ts +++ b/tests/src/core/CloneUtils.test.ts @@ -87,6 +87,26 @@ class NestedObjectScript extends Script { config: { target: Entity; label: string } = { target: null, label: "" }; } +/** Script with UNDECORATED array of entities (no @deepClone) */ +class UndecoratedArrayScript extends Script { + entities: Entity[] = []; +} + +/** Script with UNDECORATED nested object containing entity refs */ +class UndecoratedObjectScript extends Script { + config: { target: Entity; label: string } = { target: null, label: "" }; +} + +/** Script with UNDECORATED nested array of arrays containing entities */ +class NestedArrayScript extends Script { + groups: Entity[][] = []; +} + +/** Script with UNDECORATED Map containing entity values */ +class MapRefScript extends Script { + entityMap: Map = new Map(); +} + /** Script for testing multiple same-type components on one entity */ class CounterScript extends Script { value: number = 0; @@ -875,4 +895,142 @@ describe("Clone remap", async () => { rootEntity.destroy(); }); }); + + describe("Undecorated array auto clone + remap (type inference)", () => { + it("undecorated entity array should create new array and remap elements", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const childA = parent.createChild("childA"); + const childB = parent.createChild("childB"); + const script = parent.addComponent(UndecoratedArrayScript); + script.entities = [childA, childB]; + + const cloned = parent.clone(); + const cs = cloned.getComponent(UndecoratedArrayScript); + + expect(cs.entities).not.eq(script.entities); + expect(cs.entities.length).eq(2); + expect(cs.entities[0]).not.eq(childA); + expect(cs.entities[1]).not.eq(childB); + expect(cs.entities[0]).eq(cloned.children[0]); + expect(cs.entities[1]).eq(cloned.children[1]); + + rootEntity.destroy(); + }); + + it("undecorated entity array with external ref keeps original", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const child = parent.createChild("child"); + const external = rootEntity.createChild("external"); + const script = parent.addComponent(UndecoratedArrayScript); + script.entities = [child, external]; + + const cloned = parent.clone(); + const cs = cloned.getComponent(UndecoratedArrayScript); + + expect(cs.entities[0]).eq(cloned.children[0]); + expect(cs.entities[1]).eq(external); + + rootEntity.destroy(); + }); + + it("undecorated empty array stays empty with independent reference", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const script = parent.addComponent(UndecoratedArrayScript); + script.entities = []; + + const cloned = parent.clone(); + const cs = cloned.getComponent(UndecoratedArrayScript); + + expect(cs.entities).not.eq(script.entities); + expect(cs.entities.length).eq(0); + + rootEntity.destroy(); + }); + }); + + describe("Undecorated nested object auto clone + remap (type inference)", () => { + it("undecorated object with entity ref should deep clone and remap", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const child = parent.createChild("child"); + const script = parent.addComponent(UndecoratedObjectScript); + script.config = { target: child, label: "hello" }; + + const cloned = parent.clone(); + const cs = cloned.getComponent(UndecoratedObjectScript); + + expect(cs.config).not.eq(script.config); + expect(cs.config.label).eq("hello"); + expect(cs.config.target).not.eq(child); + expect(cs.config.target).eq(cloned.children[0]); + + rootEntity.destroy(); + }); + + it("undecorated object with external entity ref keeps original", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const external = rootEntity.createChild("external"); + const script = parent.addComponent(UndecoratedObjectScript); + script.config = { target: external, label: "ext" }; + + const cloned = parent.clone(); + const cs = cloned.getComponent(UndecoratedObjectScript); + + expect(cs.config.target).eq(external); + expect(cs.config.label).eq("ext"); + + rootEntity.destroy(); + }); + }); + + describe("Nested array of arrays with entity refs (type inference)", () => { + it("undecorated nested entity arrays should recursively clone and remap", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const childA = parent.createChild("childA"); + const childB = parent.createChild("childB"); + const childC = parent.createChild("childC"); + const script = parent.addComponent(NestedArrayScript); + script.groups = [[childA, childB], [childC]]; + + const cloned = parent.clone(); + const cs = cloned.getComponent(NestedArrayScript); + + expect(cs.groups).not.eq(script.groups); + expect(cs.groups.length).eq(2); + expect(cs.groups[0]).not.eq(script.groups[0]); + expect(cs.groups[1]).not.eq(script.groups[1]); + expect(cs.groups[0][0]).eq(cloned.children[0]); + expect(cs.groups[0][1]).eq(cloned.children[1]); + expect(cs.groups[1][0]).eq(cloned.children[2]); + + rootEntity.destroy(); + }); + }); + + describe("Map with entity values (type inference)", () => { + it("undecorated Map should create new Map and remap entity values", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const child = parent.createChild("child"); + const external = rootEntity.createChild("external"); + const script = parent.addComponent(MapRefScript); + script.entityMap.set("internal", child); + script.entityMap.set("external", external); + + const cloned = parent.clone(); + const cs = cloned.getComponent(MapRefScript); + + expect(cs.entityMap).not.eq(script.entityMap); + expect(cs.entityMap.size).eq(2); + expect(cs.entityMap.get("internal")).eq(cloned.children[0]); + expect(cs.entityMap.get("external")).eq(external); + + rootEntity.destroy(); + }); + }); }); diff --git a/tests/src/core/physics/PhysicsScene.test.ts b/tests/src/core/physics/PhysicsScene.test.ts index 406306a4b6..fb28579400 100644 --- a/tests/src/core/physics/PhysicsScene.test.ts +++ b/tests/src/core/physics/PhysicsScene.test.ts @@ -508,6 +508,7 @@ describe("Physics Test", () => { rootEntityCharacter.transform.position = new Vector3(0, 0, 0); const characterController = rootEntityCharacter.addComponent(CharacterController); + characterController.collisionLayer = Layer.Layer3; const boxShape2 = new BoxColliderShape(); boxShape2.size.set(1, 1, 1); boxShape2.position = new Vector3(0, 0, 0); diff --git a/tests/src/ui/UIEvent.test.ts b/tests/src/ui/UIEvent.test.ts index d2057a26cb..dc49457ba1 100644 --- a/tests/src/ui/UIEvent.test.ts +++ b/tests/src/ui/UIEvent.test.ts @@ -74,17 +74,20 @@ describe("UIEvent", async () => { // Add Image const imageEntity1 = canvasEntity.createChild("Image1"); - imageEntity1.addComponent(Image); + const image1 = imageEntity1.addComponent(Image); + image1.raycastEnabled = true; (imageEntity1.transform).size.set(300, 300); const script1 = imageEntity1.addComponent(TestScript); const imageEntity2 = imageEntity1.createChild("Image2"); - imageEntity2.addComponent(Image); + const image2 = imageEntity2.addComponent(Image); + image2.raycastEnabled = true; (imageEntity2.transform).size.set(200, 200); const script2 = imageEntity2.addComponent(TestScript); const imageEntity3 = imageEntity2.createChild("Image3"); const image3 = imageEntity3.addComponent(Image); + image3.raycastEnabled = true; (imageEntity3.transform).size.set(100, 100); const script3 = imageEntity3.addComponent(TestScript); @@ -93,6 +96,7 @@ describe("UIEvent", async () => { const { _pointerManager: pointerManager } = inputManager; const { _target: target } = pointerManager; const { left, top } = target.getBoundingClientRect(); + target.dispatchEvent(generatePointerEvent("pointerdown", 2, left + 2, top + 2)); engine.update(); From bc35a612eb7747688020d5ac950fa6e26b998990 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Wed, 15 Apr 2026 19:43:52 +0800 Subject: [PATCH 041/130] fix: revert code --- .../physics-physx/src/PhysXPhysicsScene.ts | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/packages/physics-physx/src/PhysXPhysicsScene.ts b/packages/physics-physx/src/PhysXPhysicsScene.ts index 675d5fb604..a56a1fabce 100644 --- a/packages/physics-physx/src/PhysXPhysicsScene.ts +++ b/packages/physics-physx/src/PhysXPhysicsScene.ts @@ -214,37 +214,19 @@ export class PhysXPhysicsScene implements IPhysicsScene { } else { return 0; // eNONE } - }, - postFilter: (filterData, hit) => { - // Skip shapes that contain the ray origin (distance === 0). - // This matches Bullet physics behavior used by Cocos Creator, - // where raycasts do not report shapes enclosing the ray origin. - if (hit.distance === 0) { - return 0; // eNONE — skip this hit, PhysX will continue to the next candidate - } - return 2; // eBLOCK } }; - // Use POST_FILTER in addition to PRE_FILTER so postFilter callback is invoked - const physX = this._physXPhysics._physX; - const pxRaycastFilterData = new physX.PxQueryFilterData(); - pxRaycastFilterData.flags = new physX.PxQueryFlags( - QueryFlag.STATIC | QueryFlag.DYNAMIC | QueryFlag.PRE_FILTER | QueryFlag.POST_FILTER - ); - - const pxRaycastCallback = physX.PxQueryFilterCallback.implement(raycastCallback); + const pxRaycastCallback = this._physXPhysics._physX.PxQueryFilterCallback.implement(raycastCallback); const result = this._pxScene.raycastSingle( ray.origin, ray.direction, distance, pxHitResult, - pxRaycastFilterData, + this._pxFilterData, pxRaycastCallback ); - pxRaycastFilterData.flags.delete(); - pxRaycastFilterData.delete(); pxRaycastCallback.delete(); if (result && hit != undefined) { From d5b6dd901241abb0374e03b7498c6a187df9ef98 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Wed, 15 Apr 2026 19:34:12 +0800 Subject: [PATCH 042/130] fix(loader): add audio extension to AudioLoader supported types --- packages/loader/src/AudioLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/loader/src/AudioLoader.ts b/packages/loader/src/AudioLoader.ts index 24c2b788c9..7233fb8e0f 100644 --- a/packages/loader/src/AudioLoader.ts +++ b/packages/loader/src/AudioLoader.ts @@ -9,7 +9,7 @@ import { ResourceManager, resourceLoader } from "@galacean/engine-core"; -@resourceLoader(AssetType.Audio, ["mp3", "ogg", "wav"]) +@resourceLoader(AssetType.Audio, ["mp3", "ogg", "wav", "audio"]) class AudioLoader extends Loader { load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { return new AssetPromise((resolve, reject) => { From bdf54903bfe39cc30745aef01d2eec4d4ef547eb Mon Sep 17 00:00:00 2001 From: luzhuang Date: Wed, 15 Apr 2026 21:20:49 +0800 Subject: [PATCH 043/130] =?UTF-8?q?fix(physics-physx):=20raycast=20?= =?UTF-8?q?=E5=92=8C=20sweep=20=E8=B7=B3=E8=BF=87=20initial=20overlap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PhysX 射线/sweep 起点在碰撞体内部时会返回 distance=0 的命中, 与 Unity 行为不一致。通过启用 POST_FILTER 并在 postFilter 中 过滤 distance<=0 的结果,使 PhysX 跳过 initial overlap 并继续 查找下一个最近命中。 - 启用 POST_FILTER query flag - raycast 和 sweep callback 添加 postFilter 过滤 distance<=0 - 更新 PhysX wasm CDN URL(对应 QueryBinding.h postFilter 改动) - 更新本地 wasm 产物 --- e2e/.dev/physx.release.js | 2 +- e2e/.dev/physx.release.simd.js | 2 +- e2e/.dev/physx.release.simd.wasm | Bin 2950762 -> 2949603 bytes e2e/.dev/physx.release.wasm | Bin 2467051 -> 2465887 bytes packages/physics-physx/libs/physx.release.js | 2 +- .../physics-physx/libs/physx.release.simd.js | 2 +- .../libs/physx.release.simd.wasm | Bin 2950762 -> 2949603 bytes .../physics-physx/libs/physx.release.wasm | Bin 2467051 -> 2465887 bytes packages/physics-physx/src/PhysXPhysics.ts | 4 ++-- .../physics-physx/src/PhysXPhysicsScene.ts | 16 +++++++++++++++- 10 files changed, 21 insertions(+), 7 deletions(-) diff --git a/e2e/.dev/physx.release.js b/e2e/.dev/physx.release.js index 426e4ca185..f816e9dce3 100644 --- a/e2e/.dev/physx.release.js +++ b/e2e/.dev/physx.release.js @@ -1,2 +1,2 @@ -var PHYSX=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["P"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return "https://mdn.alipayobjects.com/rms/afts/file/A*RkDQSaUOhxsAAAAAgCAAAAgAehQnAQ/physx.release.wasm"}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var __abort_js=()=>abort("");var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var emval_freelist=[];var emval_handles=[0,1,,1,null,1,true,1,false,1];var BindingError=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};var throwBindingError=message=>{throw new BindingError(message)};var Emval={toValue:handle=>{if(!handle){throwBindingError(`Cannot use deleted val. handle = ${handle}`)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};class PureVirtualError extends Error{}var AsciiToString=ptr=>{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var registerInheritedInstance=(class_,ptr,instance)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){throwBindingError(`Tried to register registered instance: ${ptr}`)}else{registeredInstances[ptr]=instance}};var registeredTypes={};var getTypeName=type=>{var ptr=___getTypeName(type);var rv=AsciiToString(ptr);_free(ptr);return rv};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var unregisterInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){delete registeredInstances[ptr]}else{throwBindingError(`Tried to unregister unregistered instance: ${ptr}`)}};var detachFinalizer=handle=>{};var finalizationRegistry=false;var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var attachFinalizer=handle=>{if(!globalThis.FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var __embind_create_inheriting_constructor=(constructorName,wrapperType,properties)=>{constructorName=AsciiToString(constructorName);wrapperType=requireRegisteredType(wrapperType,"wrapper");properties=Emval.toValue(properties);var registeredClass=wrapperType.registeredClass;var wrapperPrototype=registeredClass.instancePrototype;var baseClass=registeredClass.baseClass;var baseClassPrototype=baseClass.instancePrototype;var baseConstructor=registeredClass.baseClass.constructor;var ctor=createNamedFunction(constructorName,function(...args){for(var name of registeredClass.baseClass.pureVirtualFunctions){if(this[name]===baseClassPrototype[name]){throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`)}}Object.defineProperty(this,"__parent",{value:wrapperPrototype});this["__construct"](...args)});wrapperPrototype["__construct"]=function __construct(...args){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __construct")}var inner=baseConstructor["implement"](this,...args);detachFinalizer(inner);var $$=inner.$$;inner["notifyOnDestruction"]();$$.preservePointerOnDelete=true;Object.defineProperties(this,{$$:{value:$$}});attachFinalizer(this);registerInheritedInstance(registeredClass,$$.ptr,this)};wrapperPrototype["__destruct"]=function __destruct(){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __destruct")}detachFinalizer(this);unregisterInheritedInstance(registeredClass,this.$$.ptr)};ctor.prototype=Object.create(wrapperPrototype);Object.assign(ctor.prototype,properties);return Emval.toHandle(ctor)};var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var awaitingDependencies={};var typeDependencies={};var InternalError=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}}if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};for(var[i,field]of fieldRecords.entries()){const getterReturnType=fieldTypes[i];const getter=field.getter;const getterContext=field.getterContext;const setterArgumentType=fieldTypes[i+fieldRecords.length];const setter=field.setter;const setterContext=field.setterContext;fields[field.fieldName]={read:ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o));runDestructors(destructors)},optional:getterReturnType.optional}}return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)&&!fields[fieldName].optional){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){return sharedRegisterType(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0n;let fromWireType=value=>value;if(isUnsignedType){const bitSize=size*8;fromWireType=value=>BigInt.asUintN(bitSize,value);maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>{if(typeof value=="number"){value=BigInt(value)}return value},readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})};var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var init_ClassHandle=()=>{let proto=ClassHandle.prototype;Object.assign(proto,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}});const symbolDispose=Symbol.dispose;if(symbolDispose){proto[symbolDispose]=proto["delete"]}};function ClassHandle(){}var registeredPointers={};var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupported sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this.toWireType=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this.toWireType=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this.toWireType=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var embind__requireFunction=(signature,rawFunction,isAsync=false)=>{signature=AsciiToString(signature);function makeDynCaller(){var rtn=getWasmTableEntry(rawFunction);return rtn}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};class UnboundTypeError extends Error{}var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError(`Use 'new' to construct ${name}`)}if(undefined===registeredClass.constructor_body){throw new BindingError(`${name} has no accessible constructor`)}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};function usesDestructorStack(argTypes){for(var i=1;i{var array=[];for(var i=0;i>2])}return array};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex===-1)return signature;return signature.slice(0,argsIndex)};var __embind_register_class_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn,isAsync);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}if(classType.registeredClass.__derivedClasses){for(const derivedClass of classType.registeredClass.__derivedClasses){if(!derivedClass.constructor.hasOwnProperty(methodName)){derivedClass.constructor[methodName]=func}}}return[]});return[]})};var __embind_register_class_constructor=(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var validateThis=(this_,classType,humanName)=>{if(!(this_ instanceof Object)){throwBindingError(`${humanName} with invalid "this": ${this_}`)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`)}if(!this_.$$.ptr){throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`)}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)};var __embind_register_class_property=(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{fieldName=AsciiToString(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],classType=>{classType=classType[0];var humanName=`${classType.name}.${fieldName}`;var desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])}else{desc.set=v=>throwBindingError(humanName+" is a read-only property")}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],types=>{var getterReturnType=types[0];var desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType.fromWireType(getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})};var __embind_register_constant=(name,type,value)=>{name=AsciiToString(name);whenDependentTypesAreResolved([],[type],type=>{type=type[0];Module[name]=type.fromWireType(value);return[]})};var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function getEnumValueType(rawValueType){return rawValueType===0?"object":rawValueType===1?"number":"string"}var __embind_register_enum=(rawType,name,size,isSigned,rawValueType)=>{name=AsciiToString(name);const valueType=getEnumValueType(rawValueType);switch(valueType){case"object":{function ctor(){}ctor.values={};registerType(rawType,{name,constructor:ctor,valueType,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor);break}case"number":{var keysMap={};registerType(rawType,{name,keysMap,valueType,fromWireType:c=>c,toWireType:(destructors,c)=>c,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}case"string":{var valuesMap={};var reverseMap={};var keysMap={};registerType(rawType,{name,valuesMap,reverseMap,keysMap,valueType,fromWireType:function(c){return this.reverseMap[c]},toWireType:function(destructors,c){return this.valuesMap[c]},readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}}};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=AsciiToString(name);switch(enumType.valueType){case"object":{var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value;break}case"number":{enumType.keysMap[name]=enumValue;break}case"string":{enumType.valuesMap[name]=enumValue;enumType.reverseMap[enumValue]=name;enumType.keysMap[name]=name;break}}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=AsciiToString(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker,isAsync);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0;let fromWireType=value=>value;if(isUnsignedType){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift;maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var installIndexedIterator=(proto,sizeMethodName,getMethodName)=>{const makeIterator=(size,getValue)=>{let index=0;return{next(){if(index>=size){return{done:true}}const current=index;index++;const value=getValue(current);return{value,done:false}},[Symbol.iterator](){return this}}};if(!proto[Symbol.iterator]){proto[Symbol.iterator]=function(){const size=this[sizeMethodName]();return makeIterator(size,i=>this[getMethodName](i))}}};var __embind_register_iterable=(rawClassType,rawElementType,sizeMethodName,getMethodName)=>{sizeMethodName=AsciiToString(sizeMethodName);getMethodName=AsciiToString(getMethodName);whenDependentTypesAreResolved([],[rawClassType,rawElementType],types=>{const classType=types[0];installIndexedIterator(classType.registeredClass.instancePrototype,sizeMethodName,getMethodName);return[]})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=AsciiToString(name);registerType(rawType,{name,fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})};var EmValOptionalType=Object.assign({optional:true},EmValType);var __embind_register_optional=(rawOptionalType,rawType)=>{registerType(rawOptionalType,EmValOptionalType)};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var __embind_register_std_string=(rawType,name)=>{name=AsciiToString(name);var stdStringIsUTF8=true;registerType(rawType,{name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){str=UTF8ToString(payload,length,true)}else{str="";for(var i=0;i>2]=length;if(valueIsOfTypeString){if(stdStringIsUTF8){stringToUTF8(value,ptr,length+1)}else{for(var i=0;i255){_free(base);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}}else{HEAPU8.set(value,ptr)}if(destructors!==null){destructors.push(_free,base)}return base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var UTF16Decoder=globalThis.TextDecoder?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{var idx=ptr>>1;var endIdx=findStringEnd(HEAPU16,idx,maxBytesToRead/2,ignoreNul);if(endIdx-idx>16&&UTF16Decoder)return UTF16Decoder.decode(HEAPU16.subarray(idx,endIdx));var str="";for(var i=idx;i{maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{var str="";var startIdx=ptr>>2;for(var i=0;!(i>=maxBytesToRead/4);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i65535){i++}HEAP32[outPtr>>2]=codePoint;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i65535){i++}len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=AsciiToString(name);var decodeString,encodeString,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16}else{decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32}registerType(rawType,{name,fromWireType:value=>{var length=HEAPU32[value>>2];var str=decodeString(value+4,length*charSize,true);_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:AsciiToString(fieldName),getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext,setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext})};var __embind_register_void=(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{isVoid:true,name,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var emval_methodCallers=[];var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],`parameter ${i}`)}return a};var emval_returnValue=(toReturnWire,destructorsRef,handle)=>{var destructors=[];var result=toReturnWire(destructors,handle);if(destructors.length){HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)}return result};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return AsciiToString(address)}return symbol};var __emval_create_invoker=(argCount,argTypesPtr,kind)=>{var GenericWireTypeSize=8;var[retType,...argTypes]=emval_lookupTypes(argCount,argTypesPtr);var toReturnWire=retType.toWireType.bind(retType);var argFromPtr=argTypes.map(type=>type.readValueFromPointer.bind(type));argCount--;var argN=new Array(argCount);var invokerFunction=(handle,methodName,destructorsRef,args)=>{var offset=0;for(var i=0;it.name)}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))};var __emval_invoke=(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args);var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_new_object=()=>Emval.toHandle({});var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var __emval_set_property=(handle,key,value)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var _emscripten_date_now=()=>Date.now();var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var printCharBuffers=[null,[],[]];var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};init_ClassHandle();init_RegisteredPointer();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var ___getTypeName,_free,_malloc,__emscripten_timeout,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){___getTypeName=wasmExports["Q"];_free=Module["_free"]=wasmExports["R"];_malloc=Module["_malloc"]=wasmExports["S"];__emscripten_timeout=wasmExports["U"];memory=wasmMemory=wasmExports["O"];__indirect_function_table=wasmTable=wasmExports["T"]}var wasmImports={J:__abort_js,x:__embind_create_inheriting_constructor,s:__embind_finalize_value_object,z:__embind_register_bigint,A:__embind_register_bool,b:__embind_register_class,p:__embind_register_class_class_function,e:__embind_register_class_constructor,a:__embind_register_class_function,d:__embind_register_class_property,K:__embind_register_constant,M:__embind_register_emval,l:__embind_register_enum,f:__embind_register_enum_value,y:__embind_register_float,g:__embind_register_function,o:__embind_register_integer,q:__embind_register_iterable,h:__embind_register_memory_view,r:__embind_register_optional,N:__embind_register_std_string,u:__embind_register_std_wstring,t:__embind_register_value_object,m:__embind_register_value_object_field,B:__embind_register_void,D:__emscripten_runtime_keepalive_clear,k:__emval_create_invoker,n:__emval_decref,j:__emval_invoke,v:__emval_new_cstring,L:__emval_new_object,i:__emval_run_destructors,w:__emval_set_property,E:__setitimer_js,I:_emscripten_date_now,c:_emscripten_get_now,F:_emscripten_resize_heap,G:_exit,H:_fd_write,C:_proc_exit};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} +var PHYSX=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["P"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("physx.release.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var __abort_js=()=>abort("");var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var emval_freelist=[];var emval_handles=[0,1,,1,null,1,true,1,false,1];var BindingError=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};var throwBindingError=message=>{throw new BindingError(message)};var Emval={toValue:handle=>{if(!handle){throwBindingError(`Cannot use deleted val. handle = ${handle}`)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};class PureVirtualError extends Error{}var AsciiToString=ptr=>{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var registerInheritedInstance=(class_,ptr,instance)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){throwBindingError(`Tried to register registered instance: ${ptr}`)}else{registeredInstances[ptr]=instance}};var registeredTypes={};var getTypeName=type=>{var ptr=___getTypeName(type);var rv=AsciiToString(ptr);_free(ptr);return rv};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var unregisterInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){delete registeredInstances[ptr]}else{throwBindingError(`Tried to unregister unregistered instance: ${ptr}`)}};var detachFinalizer=handle=>{};var finalizationRegistry=false;var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var attachFinalizer=handle=>{if(!globalThis.FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var __embind_create_inheriting_constructor=(constructorName,wrapperType,properties)=>{constructorName=AsciiToString(constructorName);wrapperType=requireRegisteredType(wrapperType,"wrapper");properties=Emval.toValue(properties);var registeredClass=wrapperType.registeredClass;var wrapperPrototype=registeredClass.instancePrototype;var baseClass=registeredClass.baseClass;var baseClassPrototype=baseClass.instancePrototype;var baseConstructor=registeredClass.baseClass.constructor;var ctor=createNamedFunction(constructorName,function(...args){for(var name of registeredClass.baseClass.pureVirtualFunctions){if(this[name]===baseClassPrototype[name]){throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`)}}Object.defineProperty(this,"__parent",{value:wrapperPrototype});this["__construct"](...args)});wrapperPrototype["__construct"]=function __construct(...args){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __construct")}var inner=baseConstructor["implement"](this,...args);detachFinalizer(inner);var $$=inner.$$;inner["notifyOnDestruction"]();$$.preservePointerOnDelete=true;Object.defineProperties(this,{$$:{value:$$}});attachFinalizer(this);registerInheritedInstance(registeredClass,$$.ptr,this)};wrapperPrototype["__destruct"]=function __destruct(){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __destruct")}detachFinalizer(this);unregisterInheritedInstance(registeredClass,this.$$.ptr)};ctor.prototype=Object.create(wrapperPrototype);Object.assign(ctor.prototype,properties);return Emval.toHandle(ctor)};var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var awaitingDependencies={};var typeDependencies={};var InternalError=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}}if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};for(var[i,field]of fieldRecords.entries()){const getterReturnType=fieldTypes[i];const getter=field.getter;const getterContext=field.getterContext;const setterArgumentType=fieldTypes[i+fieldRecords.length];const setter=field.setter;const setterContext=field.setterContext;fields[field.fieldName]={read:ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o));runDestructors(destructors)},optional:getterReturnType.optional}}return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)&&!fields[fieldName].optional){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){return sharedRegisterType(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0n;let fromWireType=value=>value;if(isUnsignedType){const bitSize=size*8;fromWireType=value=>BigInt.asUintN(bitSize,value);maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>{if(typeof value=="number"){value=BigInt(value)}return value},readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})};var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var init_ClassHandle=()=>{let proto=ClassHandle.prototype;Object.assign(proto,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}});const symbolDispose=Symbol.dispose;if(symbolDispose){proto[symbolDispose]=proto["delete"]}};function ClassHandle(){}var registeredPointers={};var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupported sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this.toWireType=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this.toWireType=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this.toWireType=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var embind__requireFunction=(signature,rawFunction,isAsync=false)=>{signature=AsciiToString(signature);function makeDynCaller(){var rtn=getWasmTableEntry(rawFunction);return rtn}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};class UnboundTypeError extends Error{}var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError(`Use 'new' to construct ${name}`)}if(undefined===registeredClass.constructor_body){throw new BindingError(`${name} has no accessible constructor`)}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};function usesDestructorStack(argTypes){for(var i=1;i{var array=[];for(var i=0;i>2])}return array};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex===-1)return signature;return signature.slice(0,argsIndex)};var __embind_register_class_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn,isAsync);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}if(classType.registeredClass.__derivedClasses){for(const derivedClass of classType.registeredClass.__derivedClasses){if(!derivedClass.constructor.hasOwnProperty(methodName)){derivedClass.constructor[methodName]=func}}}return[]});return[]})};var __embind_register_class_constructor=(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var validateThis=(this_,classType,humanName)=>{if(!(this_ instanceof Object)){throwBindingError(`${humanName} with invalid "this": ${this_}`)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`)}if(!this_.$$.ptr){throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`)}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)};var __embind_register_class_property=(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{fieldName=AsciiToString(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],classType=>{classType=classType[0];var humanName=`${classType.name}.${fieldName}`;var desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])}else{desc.set=v=>throwBindingError(humanName+" is a read-only property")}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],types=>{var getterReturnType=types[0];var desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType.fromWireType(getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})};var __embind_register_constant=(name,type,value)=>{name=AsciiToString(name);whenDependentTypesAreResolved([],[type],type=>{type=type[0];Module[name]=type.fromWireType(value);return[]})};var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function getEnumValueType(rawValueType){return rawValueType===0?"object":rawValueType===1?"number":"string"}var __embind_register_enum=(rawType,name,size,isSigned,rawValueType)=>{name=AsciiToString(name);const valueType=getEnumValueType(rawValueType);switch(valueType){case"object":{function ctor(){}ctor.values={};registerType(rawType,{name,constructor:ctor,valueType,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor);break}case"number":{var keysMap={};registerType(rawType,{name,keysMap,valueType,fromWireType:c=>c,toWireType:(destructors,c)=>c,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}case"string":{var valuesMap={};var reverseMap={};var keysMap={};registerType(rawType,{name,valuesMap,reverseMap,keysMap,valueType,fromWireType:function(c){return this.reverseMap[c]},toWireType:function(destructors,c){return this.valuesMap[c]},readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}}};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=AsciiToString(name);switch(enumType.valueType){case"object":{var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value;break}case"number":{enumType.keysMap[name]=enumValue;break}case"string":{enumType.valuesMap[name]=enumValue;enumType.reverseMap[enumValue]=name;enumType.keysMap[name]=name;break}}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=AsciiToString(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker,isAsync);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0;let fromWireType=value=>value;if(isUnsignedType){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift;maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var installIndexedIterator=(proto,sizeMethodName,getMethodName)=>{const makeIterator=(size,getValue)=>{let index=0;return{next(){if(index>=size){return{done:true}}const current=index;index++;const value=getValue(current);return{value,done:false}},[Symbol.iterator](){return this}}};if(!proto[Symbol.iterator]){proto[Symbol.iterator]=function(){const size=this[sizeMethodName]();return makeIterator(size,i=>this[getMethodName](i))}}};var __embind_register_iterable=(rawClassType,rawElementType,sizeMethodName,getMethodName)=>{sizeMethodName=AsciiToString(sizeMethodName);getMethodName=AsciiToString(getMethodName);whenDependentTypesAreResolved([],[rawClassType,rawElementType],types=>{const classType=types[0];installIndexedIterator(classType.registeredClass.instancePrototype,sizeMethodName,getMethodName);return[]})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=AsciiToString(name);registerType(rawType,{name,fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})};var EmValOptionalType=Object.assign({optional:true},EmValType);var __embind_register_optional=(rawOptionalType,rawType)=>{registerType(rawOptionalType,EmValOptionalType)};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var __embind_register_std_string=(rawType,name)=>{name=AsciiToString(name);var stdStringIsUTF8=true;registerType(rawType,{name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){str=UTF8ToString(payload,length,true)}else{str="";for(var i=0;i>2]=length;if(valueIsOfTypeString){if(stdStringIsUTF8){stringToUTF8(value,ptr,length+1)}else{for(var i=0;i255){_free(base);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}}else{HEAPU8.set(value,ptr)}if(destructors!==null){destructors.push(_free,base)}return base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var UTF16Decoder=globalThis.TextDecoder?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{var idx=ptr>>1;var endIdx=findStringEnd(HEAPU16,idx,maxBytesToRead/2,ignoreNul);if(endIdx-idx>16&&UTF16Decoder)return UTF16Decoder.decode(HEAPU16.subarray(idx,endIdx));var str="";for(var i=idx;i{maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{var str="";var startIdx=ptr>>2;for(var i=0;!(i>=maxBytesToRead/4);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i65535){i++}HEAP32[outPtr>>2]=codePoint;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i65535){i++}len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=AsciiToString(name);var decodeString,encodeString,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16}else{decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32}registerType(rawType,{name,fromWireType:value=>{var length=HEAPU32[value>>2];var str=decodeString(value+4,length*charSize,true);_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:AsciiToString(fieldName),getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext,setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext})};var __embind_register_void=(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{isVoid:true,name,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var emval_methodCallers=[];var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],`parameter ${i}`)}return a};var emval_returnValue=(toReturnWire,destructorsRef,handle)=>{var destructors=[];var result=toReturnWire(destructors,handle);if(destructors.length){HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)}return result};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return AsciiToString(address)}return symbol};var __emval_create_invoker=(argCount,argTypesPtr,kind)=>{var GenericWireTypeSize=8;var[retType,...argTypes]=emval_lookupTypes(argCount,argTypesPtr);var toReturnWire=retType.toWireType.bind(retType);var argFromPtr=argTypes.map(type=>type.readValueFromPointer.bind(type));argCount--;var argN=new Array(argCount);var invokerFunction=(handle,methodName,destructorsRef,args)=>{var offset=0;for(var i=0;it.name)}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))};var __emval_invoke=(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args);var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_new_object=()=>Emval.toHandle({});var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var __emval_set_property=(handle,key,value)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var _emscripten_date_now=()=>Date.now();var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var printCharBuffers=[null,[],[]];var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};init_ClassHandle();init_RegisteredPointer();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var ___getTypeName,_free,_malloc,__emscripten_timeout,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){___getTypeName=wasmExports["Q"];_free=Module["_free"]=wasmExports["R"];_malloc=Module["_malloc"]=wasmExports["S"];__emscripten_timeout=wasmExports["U"];memory=wasmMemory=wasmExports["O"];__indirect_function_table=wasmTable=wasmExports["T"]}var wasmImports={J:__abort_js,x:__embind_create_inheriting_constructor,s:__embind_finalize_value_object,z:__embind_register_bigint,A:__embind_register_bool,b:__embind_register_class,p:__embind_register_class_class_function,e:__embind_register_class_constructor,a:__embind_register_class_function,d:__embind_register_class_property,K:__embind_register_constant,M:__embind_register_emval,l:__embind_register_enum,f:__embind_register_enum_value,y:__embind_register_float,g:__embind_register_function,o:__embind_register_integer,q:__embind_register_iterable,h:__embind_register_memory_view,r:__embind_register_optional,N:__embind_register_std_string,u:__embind_register_std_wstring,t:__embind_register_value_object,m:__embind_register_value_object_field,B:__embind_register_void,D:__emscripten_runtime_keepalive_clear,k:__emval_create_invoker,n:__emval_decref,j:__emval_invoke,v:__emval_new_cstring,L:__emval_new_object,i:__emval_run_destructors,w:__emval_set_property,E:__setitimer_js,I:_emscripten_date_now,c:_emscripten_get_now,F:_emscripten_resize_heap,G:_exit,H:_fd_write,C:_proc_exit};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} ;return moduleRtn}})();if(typeof exports==="object"&&typeof module==="object"){module.exports=PHYSX;module.exports.default=PHYSX}else if(typeof define==="function"&&define["amd"])define([],()=>PHYSX); diff --git a/e2e/.dev/physx.release.simd.js b/e2e/.dev/physx.release.simd.js index bdb1d70990..45843d369b 100644 --- a/e2e/.dev/physx.release.simd.js +++ b/e2e/.dev/physx.release.simd.js @@ -1,2 +1,2 @@ -var PHYSX=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["P"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return "https://mdn.alipayobjects.com/rms/afts/file/A*6vi1SJaSJ6IAAAAAgDAAAAgAehQnAQ/physx.release.simd.wasm"}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var __abort_js=()=>abort("");var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var emval_freelist=[];var emval_handles=[0,1,,1,null,1,true,1,false,1];var BindingError=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};var throwBindingError=message=>{throw new BindingError(message)};var Emval={toValue:handle=>{if(!handle){throwBindingError(`Cannot use deleted val. handle = ${handle}`)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};class PureVirtualError extends Error{}var AsciiToString=ptr=>{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var registerInheritedInstance=(class_,ptr,instance)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){throwBindingError(`Tried to register registered instance: ${ptr}`)}else{registeredInstances[ptr]=instance}};var registeredTypes={};var getTypeName=type=>{var ptr=___getTypeName(type);var rv=AsciiToString(ptr);_free(ptr);return rv};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var unregisterInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){delete registeredInstances[ptr]}else{throwBindingError(`Tried to unregister unregistered instance: ${ptr}`)}};var detachFinalizer=handle=>{};var finalizationRegistry=false;var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var attachFinalizer=handle=>{if(!globalThis.FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var __embind_create_inheriting_constructor=(constructorName,wrapperType,properties)=>{constructorName=AsciiToString(constructorName);wrapperType=requireRegisteredType(wrapperType,"wrapper");properties=Emval.toValue(properties);var registeredClass=wrapperType.registeredClass;var wrapperPrototype=registeredClass.instancePrototype;var baseClass=registeredClass.baseClass;var baseClassPrototype=baseClass.instancePrototype;var baseConstructor=registeredClass.baseClass.constructor;var ctor=createNamedFunction(constructorName,function(...args){for(var name of registeredClass.baseClass.pureVirtualFunctions){if(this[name]===baseClassPrototype[name]){throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`)}}Object.defineProperty(this,"__parent",{value:wrapperPrototype});this["__construct"](...args)});wrapperPrototype["__construct"]=function __construct(...args){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __construct")}var inner=baseConstructor["implement"](this,...args);detachFinalizer(inner);var $$=inner.$$;inner["notifyOnDestruction"]();$$.preservePointerOnDelete=true;Object.defineProperties(this,{$$:{value:$$}});attachFinalizer(this);registerInheritedInstance(registeredClass,$$.ptr,this)};wrapperPrototype["__destruct"]=function __destruct(){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __destruct")}detachFinalizer(this);unregisterInheritedInstance(registeredClass,this.$$.ptr)};ctor.prototype=Object.create(wrapperPrototype);Object.assign(ctor.prototype,properties);return Emval.toHandle(ctor)};var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var awaitingDependencies={};var typeDependencies={};var InternalError=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}}if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};for(var[i,field]of fieldRecords.entries()){const getterReturnType=fieldTypes[i];const getter=field.getter;const getterContext=field.getterContext;const setterArgumentType=fieldTypes[i+fieldRecords.length];const setter=field.setter;const setterContext=field.setterContext;fields[field.fieldName]={read:ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o));runDestructors(destructors)},optional:getterReturnType.optional}}return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)&&!fields[fieldName].optional){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){return sharedRegisterType(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0n;let fromWireType=value=>value;if(isUnsignedType){const bitSize=size*8;fromWireType=value=>BigInt.asUintN(bitSize,value);maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>{if(typeof value=="number"){value=BigInt(value)}return value},readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})};var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var init_ClassHandle=()=>{let proto=ClassHandle.prototype;Object.assign(proto,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}});const symbolDispose=Symbol.dispose;if(symbolDispose){proto[symbolDispose]=proto["delete"]}};function ClassHandle(){}var registeredPointers={};var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupported sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this.toWireType=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this.toWireType=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this.toWireType=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var embind__requireFunction=(signature,rawFunction,isAsync=false)=>{signature=AsciiToString(signature);function makeDynCaller(){var rtn=getWasmTableEntry(rawFunction);return rtn}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};class UnboundTypeError extends Error{}var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError(`Use 'new' to construct ${name}`)}if(undefined===registeredClass.constructor_body){throw new BindingError(`${name} has no accessible constructor`)}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};function usesDestructorStack(argTypes){for(var i=1;i{var array=[];for(var i=0;i>2])}return array};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex===-1)return signature;return signature.slice(0,argsIndex)};var __embind_register_class_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn,isAsync);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}if(classType.registeredClass.__derivedClasses){for(const derivedClass of classType.registeredClass.__derivedClasses){if(!derivedClass.constructor.hasOwnProperty(methodName)){derivedClass.constructor[methodName]=func}}}return[]});return[]})};var __embind_register_class_constructor=(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var validateThis=(this_,classType,humanName)=>{if(!(this_ instanceof Object)){throwBindingError(`${humanName} with invalid "this": ${this_}`)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`)}if(!this_.$$.ptr){throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`)}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)};var __embind_register_class_property=(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{fieldName=AsciiToString(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],classType=>{classType=classType[0];var humanName=`${classType.name}.${fieldName}`;var desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])}else{desc.set=v=>throwBindingError(humanName+" is a read-only property")}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],types=>{var getterReturnType=types[0];var desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType.fromWireType(getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})};var __embind_register_constant=(name,type,value)=>{name=AsciiToString(name);whenDependentTypesAreResolved([],[type],type=>{type=type[0];Module[name]=type.fromWireType(value);return[]})};var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function getEnumValueType(rawValueType){return rawValueType===0?"object":rawValueType===1?"number":"string"}var __embind_register_enum=(rawType,name,size,isSigned,rawValueType)=>{name=AsciiToString(name);const valueType=getEnumValueType(rawValueType);switch(valueType){case"object":{function ctor(){}ctor.values={};registerType(rawType,{name,constructor:ctor,valueType,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor);break}case"number":{var keysMap={};registerType(rawType,{name,keysMap,valueType,fromWireType:c=>c,toWireType:(destructors,c)=>c,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}case"string":{var valuesMap={};var reverseMap={};var keysMap={};registerType(rawType,{name,valuesMap,reverseMap,keysMap,valueType,fromWireType:function(c){return this.reverseMap[c]},toWireType:function(destructors,c){return this.valuesMap[c]},readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}}};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=AsciiToString(name);switch(enumType.valueType){case"object":{var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value;break}case"number":{enumType.keysMap[name]=enumValue;break}case"string":{enumType.valuesMap[name]=enumValue;enumType.reverseMap[enumValue]=name;enumType.keysMap[name]=name;break}}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=AsciiToString(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker,isAsync);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0;let fromWireType=value=>value;if(isUnsignedType){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift;maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var installIndexedIterator=(proto,sizeMethodName,getMethodName)=>{const makeIterator=(size,getValue)=>{let index=0;return{next(){if(index>=size){return{done:true}}const current=index;index++;const value=getValue(current);return{value,done:false}},[Symbol.iterator](){return this}}};if(!proto[Symbol.iterator]){proto[Symbol.iterator]=function(){const size=this[sizeMethodName]();return makeIterator(size,i=>this[getMethodName](i))}}};var __embind_register_iterable=(rawClassType,rawElementType,sizeMethodName,getMethodName)=>{sizeMethodName=AsciiToString(sizeMethodName);getMethodName=AsciiToString(getMethodName);whenDependentTypesAreResolved([],[rawClassType,rawElementType],types=>{const classType=types[0];installIndexedIterator(classType.registeredClass.instancePrototype,sizeMethodName,getMethodName);return[]})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=AsciiToString(name);registerType(rawType,{name,fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})};var EmValOptionalType=Object.assign({optional:true},EmValType);var __embind_register_optional=(rawOptionalType,rawType)=>{registerType(rawOptionalType,EmValOptionalType)};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var __embind_register_std_string=(rawType,name)=>{name=AsciiToString(name);var stdStringIsUTF8=true;registerType(rawType,{name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){str=UTF8ToString(payload,length,true)}else{str="";for(var i=0;i>2]=length;if(valueIsOfTypeString){if(stdStringIsUTF8){stringToUTF8(value,ptr,length+1)}else{for(var i=0;i255){_free(base);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}}else{HEAPU8.set(value,ptr)}if(destructors!==null){destructors.push(_free,base)}return base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var UTF16Decoder=globalThis.TextDecoder?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{var idx=ptr>>1;var endIdx=findStringEnd(HEAPU16,idx,maxBytesToRead/2,ignoreNul);if(endIdx-idx>16&&UTF16Decoder)return UTF16Decoder.decode(HEAPU16.subarray(idx,endIdx));var str="";for(var i=idx;i{maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{var str="";var startIdx=ptr>>2;for(var i=0;!(i>=maxBytesToRead/4);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i65535){i++}HEAP32[outPtr>>2]=codePoint;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i65535){i++}len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=AsciiToString(name);var decodeString,encodeString,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16}else{decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32}registerType(rawType,{name,fromWireType:value=>{var length=HEAPU32[value>>2];var str=decodeString(value+4,length*charSize,true);_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:AsciiToString(fieldName),getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext,setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext})};var __embind_register_void=(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{isVoid:true,name,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var emval_methodCallers=[];var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],`parameter ${i}`)}return a};var emval_returnValue=(toReturnWire,destructorsRef,handle)=>{var destructors=[];var result=toReturnWire(destructors,handle);if(destructors.length){HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)}return result};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return AsciiToString(address)}return symbol};var __emval_create_invoker=(argCount,argTypesPtr,kind)=>{var GenericWireTypeSize=8;var[retType,...argTypes]=emval_lookupTypes(argCount,argTypesPtr);var toReturnWire=retType.toWireType.bind(retType);var argFromPtr=argTypes.map(type=>type.readValueFromPointer.bind(type));argCount--;var argN=new Array(argCount);var invokerFunction=(handle,methodName,destructorsRef,args)=>{var offset=0;for(var i=0;it.name)}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))};var __emval_invoke=(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args);var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_new_object=()=>Emval.toHandle({});var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var __emval_set_property=(handle,key,value)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var _emscripten_date_now=()=>Date.now();var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var printCharBuffers=[null,[],[]];var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};init_ClassHandle();init_RegisteredPointer();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var ___getTypeName,_free,_malloc,__emscripten_timeout,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){___getTypeName=wasmExports["Q"];_free=Module["_free"]=wasmExports["R"];_malloc=Module["_malloc"]=wasmExports["S"];__emscripten_timeout=wasmExports["U"];memory=wasmMemory=wasmExports["O"];__indirect_function_table=wasmTable=wasmExports["T"]}var wasmImports={J:__abort_js,x:__embind_create_inheriting_constructor,s:__embind_finalize_value_object,z:__embind_register_bigint,A:__embind_register_bool,b:__embind_register_class,p:__embind_register_class_class_function,e:__embind_register_class_constructor,a:__embind_register_class_function,d:__embind_register_class_property,K:__embind_register_constant,M:__embind_register_emval,l:__embind_register_enum,f:__embind_register_enum_value,y:__embind_register_float,g:__embind_register_function,o:__embind_register_integer,q:__embind_register_iterable,h:__embind_register_memory_view,r:__embind_register_optional,N:__embind_register_std_string,u:__embind_register_std_wstring,t:__embind_register_value_object,m:__embind_register_value_object_field,B:__embind_register_void,D:__emscripten_runtime_keepalive_clear,k:__emval_create_invoker,n:__emval_decref,j:__emval_invoke,v:__emval_new_cstring,L:__emval_new_object,i:__emval_run_destructors,w:__emval_set_property,E:__setitimer_js,I:_emscripten_date_now,c:_emscripten_get_now,F:_emscripten_resize_heap,G:_exit,H:_fd_write,C:_proc_exit};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} +var PHYSX=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["P"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("physx.release.simd.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var __abort_js=()=>abort("");var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var emval_freelist=[];var emval_handles=[0,1,,1,null,1,true,1,false,1];var BindingError=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};var throwBindingError=message=>{throw new BindingError(message)};var Emval={toValue:handle=>{if(!handle){throwBindingError(`Cannot use deleted val. handle = ${handle}`)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};class PureVirtualError extends Error{}var AsciiToString=ptr=>{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var registerInheritedInstance=(class_,ptr,instance)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){throwBindingError(`Tried to register registered instance: ${ptr}`)}else{registeredInstances[ptr]=instance}};var registeredTypes={};var getTypeName=type=>{var ptr=___getTypeName(type);var rv=AsciiToString(ptr);_free(ptr);return rv};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var unregisterInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){delete registeredInstances[ptr]}else{throwBindingError(`Tried to unregister unregistered instance: ${ptr}`)}};var detachFinalizer=handle=>{};var finalizationRegistry=false;var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var attachFinalizer=handle=>{if(!globalThis.FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var __embind_create_inheriting_constructor=(constructorName,wrapperType,properties)=>{constructorName=AsciiToString(constructorName);wrapperType=requireRegisteredType(wrapperType,"wrapper");properties=Emval.toValue(properties);var registeredClass=wrapperType.registeredClass;var wrapperPrototype=registeredClass.instancePrototype;var baseClass=registeredClass.baseClass;var baseClassPrototype=baseClass.instancePrototype;var baseConstructor=registeredClass.baseClass.constructor;var ctor=createNamedFunction(constructorName,function(...args){for(var name of registeredClass.baseClass.pureVirtualFunctions){if(this[name]===baseClassPrototype[name]){throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`)}}Object.defineProperty(this,"__parent",{value:wrapperPrototype});this["__construct"](...args)});wrapperPrototype["__construct"]=function __construct(...args){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __construct")}var inner=baseConstructor["implement"](this,...args);detachFinalizer(inner);var $$=inner.$$;inner["notifyOnDestruction"]();$$.preservePointerOnDelete=true;Object.defineProperties(this,{$$:{value:$$}});attachFinalizer(this);registerInheritedInstance(registeredClass,$$.ptr,this)};wrapperPrototype["__destruct"]=function __destruct(){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __destruct")}detachFinalizer(this);unregisterInheritedInstance(registeredClass,this.$$.ptr)};ctor.prototype=Object.create(wrapperPrototype);Object.assign(ctor.prototype,properties);return Emval.toHandle(ctor)};var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var awaitingDependencies={};var typeDependencies={};var InternalError=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}}if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};for(var[i,field]of fieldRecords.entries()){const getterReturnType=fieldTypes[i];const getter=field.getter;const getterContext=field.getterContext;const setterArgumentType=fieldTypes[i+fieldRecords.length];const setter=field.setter;const setterContext=field.setterContext;fields[field.fieldName]={read:ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o));runDestructors(destructors)},optional:getterReturnType.optional}}return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)&&!fields[fieldName].optional){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){return sharedRegisterType(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0n;let fromWireType=value=>value;if(isUnsignedType){const bitSize=size*8;fromWireType=value=>BigInt.asUintN(bitSize,value);maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>{if(typeof value=="number"){value=BigInt(value)}return value},readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})};var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var init_ClassHandle=()=>{let proto=ClassHandle.prototype;Object.assign(proto,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}});const symbolDispose=Symbol.dispose;if(symbolDispose){proto[symbolDispose]=proto["delete"]}};function ClassHandle(){}var registeredPointers={};var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupported sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this.toWireType=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this.toWireType=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this.toWireType=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var embind__requireFunction=(signature,rawFunction,isAsync=false)=>{signature=AsciiToString(signature);function makeDynCaller(){var rtn=getWasmTableEntry(rawFunction);return rtn}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};class UnboundTypeError extends Error{}var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError(`Use 'new' to construct ${name}`)}if(undefined===registeredClass.constructor_body){throw new BindingError(`${name} has no accessible constructor`)}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};function usesDestructorStack(argTypes){for(var i=1;i{var array=[];for(var i=0;i>2])}return array};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex===-1)return signature;return signature.slice(0,argsIndex)};var __embind_register_class_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn,isAsync);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}if(classType.registeredClass.__derivedClasses){for(const derivedClass of classType.registeredClass.__derivedClasses){if(!derivedClass.constructor.hasOwnProperty(methodName)){derivedClass.constructor[methodName]=func}}}return[]});return[]})};var __embind_register_class_constructor=(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var validateThis=(this_,classType,humanName)=>{if(!(this_ instanceof Object)){throwBindingError(`${humanName} with invalid "this": ${this_}`)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`)}if(!this_.$$.ptr){throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`)}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)};var __embind_register_class_property=(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{fieldName=AsciiToString(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],classType=>{classType=classType[0];var humanName=`${classType.name}.${fieldName}`;var desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])}else{desc.set=v=>throwBindingError(humanName+" is a read-only property")}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],types=>{var getterReturnType=types[0];var desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType.fromWireType(getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})};var __embind_register_constant=(name,type,value)=>{name=AsciiToString(name);whenDependentTypesAreResolved([],[type],type=>{type=type[0];Module[name]=type.fromWireType(value);return[]})};var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function getEnumValueType(rawValueType){return rawValueType===0?"object":rawValueType===1?"number":"string"}var __embind_register_enum=(rawType,name,size,isSigned,rawValueType)=>{name=AsciiToString(name);const valueType=getEnumValueType(rawValueType);switch(valueType){case"object":{function ctor(){}ctor.values={};registerType(rawType,{name,constructor:ctor,valueType,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor);break}case"number":{var keysMap={};registerType(rawType,{name,keysMap,valueType,fromWireType:c=>c,toWireType:(destructors,c)=>c,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}case"string":{var valuesMap={};var reverseMap={};var keysMap={};registerType(rawType,{name,valuesMap,reverseMap,keysMap,valueType,fromWireType:function(c){return this.reverseMap[c]},toWireType:function(destructors,c){return this.valuesMap[c]},readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}}};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=AsciiToString(name);switch(enumType.valueType){case"object":{var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value;break}case"number":{enumType.keysMap[name]=enumValue;break}case"string":{enumType.valuesMap[name]=enumValue;enumType.reverseMap[enumValue]=name;enumType.keysMap[name]=name;break}}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=AsciiToString(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker,isAsync);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0;let fromWireType=value=>value;if(isUnsignedType){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift;maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var installIndexedIterator=(proto,sizeMethodName,getMethodName)=>{const makeIterator=(size,getValue)=>{let index=0;return{next(){if(index>=size){return{done:true}}const current=index;index++;const value=getValue(current);return{value,done:false}},[Symbol.iterator](){return this}}};if(!proto[Symbol.iterator]){proto[Symbol.iterator]=function(){const size=this[sizeMethodName]();return makeIterator(size,i=>this[getMethodName](i))}}};var __embind_register_iterable=(rawClassType,rawElementType,sizeMethodName,getMethodName)=>{sizeMethodName=AsciiToString(sizeMethodName);getMethodName=AsciiToString(getMethodName);whenDependentTypesAreResolved([],[rawClassType,rawElementType],types=>{const classType=types[0];installIndexedIterator(classType.registeredClass.instancePrototype,sizeMethodName,getMethodName);return[]})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=AsciiToString(name);registerType(rawType,{name,fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})};var EmValOptionalType=Object.assign({optional:true},EmValType);var __embind_register_optional=(rawOptionalType,rawType)=>{registerType(rawOptionalType,EmValOptionalType)};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var __embind_register_std_string=(rawType,name)=>{name=AsciiToString(name);var stdStringIsUTF8=true;registerType(rawType,{name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){str=UTF8ToString(payload,length,true)}else{str="";for(var i=0;i>2]=length;if(valueIsOfTypeString){if(stdStringIsUTF8){stringToUTF8(value,ptr,length+1)}else{for(var i=0;i255){_free(base);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}}else{HEAPU8.set(value,ptr)}if(destructors!==null){destructors.push(_free,base)}return base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var UTF16Decoder=globalThis.TextDecoder?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{var idx=ptr>>1;var endIdx=findStringEnd(HEAPU16,idx,maxBytesToRead/2,ignoreNul);if(endIdx-idx>16&&UTF16Decoder)return UTF16Decoder.decode(HEAPU16.subarray(idx,endIdx));var str="";for(var i=idx;i{maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{var str="";var startIdx=ptr>>2;for(var i=0;!(i>=maxBytesToRead/4);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i65535){i++}HEAP32[outPtr>>2]=codePoint;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i65535){i++}len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=AsciiToString(name);var decodeString,encodeString,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16}else{decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32}registerType(rawType,{name,fromWireType:value=>{var length=HEAPU32[value>>2];var str=decodeString(value+4,length*charSize,true);_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:AsciiToString(fieldName),getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext,setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext})};var __embind_register_void=(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{isVoid:true,name,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var emval_methodCallers=[];var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],`parameter ${i}`)}return a};var emval_returnValue=(toReturnWire,destructorsRef,handle)=>{var destructors=[];var result=toReturnWire(destructors,handle);if(destructors.length){HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)}return result};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return AsciiToString(address)}return symbol};var __emval_create_invoker=(argCount,argTypesPtr,kind)=>{var GenericWireTypeSize=8;var[retType,...argTypes]=emval_lookupTypes(argCount,argTypesPtr);var toReturnWire=retType.toWireType.bind(retType);var argFromPtr=argTypes.map(type=>type.readValueFromPointer.bind(type));argCount--;var argN=new Array(argCount);var invokerFunction=(handle,methodName,destructorsRef,args)=>{var offset=0;for(var i=0;it.name)}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))};var __emval_invoke=(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args);var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_new_object=()=>Emval.toHandle({});var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var __emval_set_property=(handle,key,value)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var _emscripten_date_now=()=>Date.now();var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var printCharBuffers=[null,[],[]];var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};init_ClassHandle();init_RegisteredPointer();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var ___getTypeName,_free,_malloc,__emscripten_timeout,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){___getTypeName=wasmExports["Q"];_free=Module["_free"]=wasmExports["R"];_malloc=Module["_malloc"]=wasmExports["S"];__emscripten_timeout=wasmExports["U"];memory=wasmMemory=wasmExports["O"];__indirect_function_table=wasmTable=wasmExports["T"]}var wasmImports={J:__abort_js,x:__embind_create_inheriting_constructor,s:__embind_finalize_value_object,z:__embind_register_bigint,A:__embind_register_bool,b:__embind_register_class,p:__embind_register_class_class_function,e:__embind_register_class_constructor,a:__embind_register_class_function,d:__embind_register_class_property,K:__embind_register_constant,M:__embind_register_emval,l:__embind_register_enum,f:__embind_register_enum_value,y:__embind_register_float,g:__embind_register_function,o:__embind_register_integer,q:__embind_register_iterable,h:__embind_register_memory_view,r:__embind_register_optional,N:__embind_register_std_string,u:__embind_register_std_wstring,t:__embind_register_value_object,m:__embind_register_value_object_field,B:__embind_register_void,D:__emscripten_runtime_keepalive_clear,k:__emval_create_invoker,n:__emval_decref,j:__emval_invoke,v:__emval_new_cstring,L:__emval_new_object,i:__emval_run_destructors,w:__emval_set_property,E:__setitimer_js,I:_emscripten_date_now,c:_emscripten_get_now,F:_emscripten_resize_heap,G:_exit,H:_fd_write,C:_proc_exit};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} ;return moduleRtn}})();if(typeof exports==="object"&&typeof module==="object"){module.exports=PHYSX;module.exports.default=PHYSX}else if(typeof define==="function"&&define["amd"])define([],()=>PHYSX); diff --git a/e2e/.dev/physx.release.simd.wasm b/e2e/.dev/physx.release.simd.wasm index 91e5bab34b9c88b3a73e718d3e2e84c537c6cee5..f52b48110ab6122d7df5f75811d4bc80a946384b 100755 GIT binary patch delta 143753 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�)nlT+OC2HBy&8R{>rWk)ITaF%+ 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

GAKhF2uX>*6)zn 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 z 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

d$?kX%&qe3-HVx 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 delta 144148 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�y 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 _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["P"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return "https://mdn.alipayobjects.com/rms/afts/file/A*RkDQSaUOhxsAAAAAgCAAAAgAehQnAQ/physx.release.wasm"}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var __abort_js=()=>abort("");var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var emval_freelist=[];var emval_handles=[0,1,,1,null,1,true,1,false,1];var BindingError=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};var throwBindingError=message=>{throw new BindingError(message)};var Emval={toValue:handle=>{if(!handle){throwBindingError(`Cannot use deleted val. handle = ${handle}`)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};class PureVirtualError extends Error{}var AsciiToString=ptr=>{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var registerInheritedInstance=(class_,ptr,instance)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){throwBindingError(`Tried to register registered instance: ${ptr}`)}else{registeredInstances[ptr]=instance}};var registeredTypes={};var getTypeName=type=>{var ptr=___getTypeName(type);var rv=AsciiToString(ptr);_free(ptr);return rv};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var unregisterInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){delete registeredInstances[ptr]}else{throwBindingError(`Tried to unregister unregistered instance: ${ptr}`)}};var detachFinalizer=handle=>{};var finalizationRegistry=false;var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var attachFinalizer=handle=>{if(!globalThis.FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var __embind_create_inheriting_constructor=(constructorName,wrapperType,properties)=>{constructorName=AsciiToString(constructorName);wrapperType=requireRegisteredType(wrapperType,"wrapper");properties=Emval.toValue(properties);var registeredClass=wrapperType.registeredClass;var wrapperPrototype=registeredClass.instancePrototype;var baseClass=registeredClass.baseClass;var baseClassPrototype=baseClass.instancePrototype;var baseConstructor=registeredClass.baseClass.constructor;var ctor=createNamedFunction(constructorName,function(...args){for(var name of registeredClass.baseClass.pureVirtualFunctions){if(this[name]===baseClassPrototype[name]){throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`)}}Object.defineProperty(this,"__parent",{value:wrapperPrototype});this["__construct"](...args)});wrapperPrototype["__construct"]=function __construct(...args){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __construct")}var inner=baseConstructor["implement"](this,...args);detachFinalizer(inner);var $$=inner.$$;inner["notifyOnDestruction"]();$$.preservePointerOnDelete=true;Object.defineProperties(this,{$$:{value:$$}});attachFinalizer(this);registerInheritedInstance(registeredClass,$$.ptr,this)};wrapperPrototype["__destruct"]=function __destruct(){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __destruct")}detachFinalizer(this);unregisterInheritedInstance(registeredClass,this.$$.ptr)};ctor.prototype=Object.create(wrapperPrototype);Object.assign(ctor.prototype,properties);return Emval.toHandle(ctor)};var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var awaitingDependencies={};var typeDependencies={};var InternalError=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}}if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};for(var[i,field]of fieldRecords.entries()){const getterReturnType=fieldTypes[i];const getter=field.getter;const getterContext=field.getterContext;const setterArgumentType=fieldTypes[i+fieldRecords.length];const setter=field.setter;const setterContext=field.setterContext;fields[field.fieldName]={read:ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o));runDestructors(destructors)},optional:getterReturnType.optional}}return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)&&!fields[fieldName].optional){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){return sharedRegisterType(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0n;let fromWireType=value=>value;if(isUnsignedType){const bitSize=size*8;fromWireType=value=>BigInt.asUintN(bitSize,value);maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>{if(typeof value=="number"){value=BigInt(value)}return value},readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})};var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var init_ClassHandle=()=>{let proto=ClassHandle.prototype;Object.assign(proto,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}});const symbolDispose=Symbol.dispose;if(symbolDispose){proto[symbolDispose]=proto["delete"]}};function ClassHandle(){}var registeredPointers={};var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupported sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this.toWireType=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this.toWireType=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this.toWireType=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var embind__requireFunction=(signature,rawFunction,isAsync=false)=>{signature=AsciiToString(signature);function makeDynCaller(){var rtn=getWasmTableEntry(rawFunction);return rtn}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};class UnboundTypeError extends Error{}var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError(`Use 'new' to construct ${name}`)}if(undefined===registeredClass.constructor_body){throw new BindingError(`${name} has no accessible constructor`)}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};function usesDestructorStack(argTypes){for(var i=1;i{var array=[];for(var i=0;i>2])}return array};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex===-1)return signature;return signature.slice(0,argsIndex)};var __embind_register_class_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn,isAsync);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}if(classType.registeredClass.__derivedClasses){for(const derivedClass of classType.registeredClass.__derivedClasses){if(!derivedClass.constructor.hasOwnProperty(methodName)){derivedClass.constructor[methodName]=func}}}return[]});return[]})};var __embind_register_class_constructor=(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var validateThis=(this_,classType,humanName)=>{if(!(this_ instanceof Object)){throwBindingError(`${humanName} with invalid "this": ${this_}`)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`)}if(!this_.$$.ptr){throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`)}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)};var __embind_register_class_property=(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{fieldName=AsciiToString(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],classType=>{classType=classType[0];var humanName=`${classType.name}.${fieldName}`;var desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])}else{desc.set=v=>throwBindingError(humanName+" is a read-only property")}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],types=>{var getterReturnType=types[0];var desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType.fromWireType(getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})};var __embind_register_constant=(name,type,value)=>{name=AsciiToString(name);whenDependentTypesAreResolved([],[type],type=>{type=type[0];Module[name]=type.fromWireType(value);return[]})};var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function getEnumValueType(rawValueType){return rawValueType===0?"object":rawValueType===1?"number":"string"}var __embind_register_enum=(rawType,name,size,isSigned,rawValueType)=>{name=AsciiToString(name);const valueType=getEnumValueType(rawValueType);switch(valueType){case"object":{function ctor(){}ctor.values={};registerType(rawType,{name,constructor:ctor,valueType,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor);break}case"number":{var keysMap={};registerType(rawType,{name,keysMap,valueType,fromWireType:c=>c,toWireType:(destructors,c)=>c,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}case"string":{var valuesMap={};var reverseMap={};var keysMap={};registerType(rawType,{name,valuesMap,reverseMap,keysMap,valueType,fromWireType:function(c){return this.reverseMap[c]},toWireType:function(destructors,c){return this.valuesMap[c]},readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}}};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=AsciiToString(name);switch(enumType.valueType){case"object":{var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value;break}case"number":{enumType.keysMap[name]=enumValue;break}case"string":{enumType.valuesMap[name]=enumValue;enumType.reverseMap[enumValue]=name;enumType.keysMap[name]=name;break}}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=AsciiToString(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker,isAsync);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0;let fromWireType=value=>value;if(isUnsignedType){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift;maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var installIndexedIterator=(proto,sizeMethodName,getMethodName)=>{const makeIterator=(size,getValue)=>{let index=0;return{next(){if(index>=size){return{done:true}}const current=index;index++;const value=getValue(current);return{value,done:false}},[Symbol.iterator](){return this}}};if(!proto[Symbol.iterator]){proto[Symbol.iterator]=function(){const size=this[sizeMethodName]();return makeIterator(size,i=>this[getMethodName](i))}}};var __embind_register_iterable=(rawClassType,rawElementType,sizeMethodName,getMethodName)=>{sizeMethodName=AsciiToString(sizeMethodName);getMethodName=AsciiToString(getMethodName);whenDependentTypesAreResolved([],[rawClassType,rawElementType],types=>{const classType=types[0];installIndexedIterator(classType.registeredClass.instancePrototype,sizeMethodName,getMethodName);return[]})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=AsciiToString(name);registerType(rawType,{name,fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})};var EmValOptionalType=Object.assign({optional:true},EmValType);var __embind_register_optional=(rawOptionalType,rawType)=>{registerType(rawOptionalType,EmValOptionalType)};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var __embind_register_std_string=(rawType,name)=>{name=AsciiToString(name);var stdStringIsUTF8=true;registerType(rawType,{name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){str=UTF8ToString(payload,length,true)}else{str="";for(var i=0;i>2]=length;if(valueIsOfTypeString){if(stdStringIsUTF8){stringToUTF8(value,ptr,length+1)}else{for(var i=0;i255){_free(base);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}}else{HEAPU8.set(value,ptr)}if(destructors!==null){destructors.push(_free,base)}return base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var UTF16Decoder=globalThis.TextDecoder?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{var idx=ptr>>1;var endIdx=findStringEnd(HEAPU16,idx,maxBytesToRead/2,ignoreNul);if(endIdx-idx>16&&UTF16Decoder)return UTF16Decoder.decode(HEAPU16.subarray(idx,endIdx));var str="";for(var i=idx;i{maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{var str="";var startIdx=ptr>>2;for(var i=0;!(i>=maxBytesToRead/4);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i65535){i++}HEAP32[outPtr>>2]=codePoint;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i65535){i++}len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=AsciiToString(name);var decodeString,encodeString,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16}else{decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32}registerType(rawType,{name,fromWireType:value=>{var length=HEAPU32[value>>2];var str=decodeString(value+4,length*charSize,true);_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:AsciiToString(fieldName),getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext,setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext})};var __embind_register_void=(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{isVoid:true,name,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var emval_methodCallers=[];var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],`parameter ${i}`)}return a};var emval_returnValue=(toReturnWire,destructorsRef,handle)=>{var destructors=[];var result=toReturnWire(destructors,handle);if(destructors.length){HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)}return result};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return AsciiToString(address)}return symbol};var __emval_create_invoker=(argCount,argTypesPtr,kind)=>{var GenericWireTypeSize=8;var[retType,...argTypes]=emval_lookupTypes(argCount,argTypesPtr);var toReturnWire=retType.toWireType.bind(retType);var argFromPtr=argTypes.map(type=>type.readValueFromPointer.bind(type));argCount--;var argN=new Array(argCount);var invokerFunction=(handle,methodName,destructorsRef,args)=>{var offset=0;for(var i=0;it.name)}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))};var __emval_invoke=(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args);var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_new_object=()=>Emval.toHandle({});var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var __emval_set_property=(handle,key,value)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var _emscripten_date_now=()=>Date.now();var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var printCharBuffers=[null,[],[]];var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};init_ClassHandle();init_RegisteredPointer();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var ___getTypeName,_free,_malloc,__emscripten_timeout,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){___getTypeName=wasmExports["Q"];_free=Module["_free"]=wasmExports["R"];_malloc=Module["_malloc"]=wasmExports["S"];__emscripten_timeout=wasmExports["U"];memory=wasmMemory=wasmExports["O"];__indirect_function_table=wasmTable=wasmExports["T"]}var wasmImports={J:__abort_js,x:__embind_create_inheriting_constructor,s:__embind_finalize_value_object,z:__embind_register_bigint,A:__embind_register_bool,b:__embind_register_class,p:__embind_register_class_class_function,e:__embind_register_class_constructor,a:__embind_register_class_function,d:__embind_register_class_property,K:__embind_register_constant,M:__embind_register_emval,l:__embind_register_enum,f:__embind_register_enum_value,y:__embind_register_float,g:__embind_register_function,o:__embind_register_integer,q:__embind_register_iterable,h:__embind_register_memory_view,r:__embind_register_optional,N:__embind_register_std_string,u:__embind_register_std_wstring,t:__embind_register_value_object,m:__embind_register_value_object_field,B:__embind_register_void,D:__emscripten_runtime_keepalive_clear,k:__emval_create_invoker,n:__emval_decref,j:__emval_invoke,v:__emval_new_cstring,L:__emval_new_object,i:__emval_run_destructors,w:__emval_set_property,E:__setitimer_js,I:_emscripten_date_now,c:_emscripten_get_now,F:_emscripten_resize_heap,G:_exit,H:_fd_write,C:_proc_exit};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} +var PHYSX=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["P"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("physx.release.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var __abort_js=()=>abort("");var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var emval_freelist=[];var emval_handles=[0,1,,1,null,1,true,1,false,1];var BindingError=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};var throwBindingError=message=>{throw new BindingError(message)};var Emval={toValue:handle=>{if(!handle){throwBindingError(`Cannot use deleted val. handle = ${handle}`)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};class PureVirtualError extends Error{}var AsciiToString=ptr=>{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var registerInheritedInstance=(class_,ptr,instance)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){throwBindingError(`Tried to register registered instance: ${ptr}`)}else{registeredInstances[ptr]=instance}};var registeredTypes={};var getTypeName=type=>{var ptr=___getTypeName(type);var rv=AsciiToString(ptr);_free(ptr);return rv};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var unregisterInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){delete registeredInstances[ptr]}else{throwBindingError(`Tried to unregister unregistered instance: ${ptr}`)}};var detachFinalizer=handle=>{};var finalizationRegistry=false;var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var attachFinalizer=handle=>{if(!globalThis.FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var __embind_create_inheriting_constructor=(constructorName,wrapperType,properties)=>{constructorName=AsciiToString(constructorName);wrapperType=requireRegisteredType(wrapperType,"wrapper");properties=Emval.toValue(properties);var registeredClass=wrapperType.registeredClass;var wrapperPrototype=registeredClass.instancePrototype;var baseClass=registeredClass.baseClass;var baseClassPrototype=baseClass.instancePrototype;var baseConstructor=registeredClass.baseClass.constructor;var ctor=createNamedFunction(constructorName,function(...args){for(var name of registeredClass.baseClass.pureVirtualFunctions){if(this[name]===baseClassPrototype[name]){throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`)}}Object.defineProperty(this,"__parent",{value:wrapperPrototype});this["__construct"](...args)});wrapperPrototype["__construct"]=function __construct(...args){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __construct")}var inner=baseConstructor["implement"](this,...args);detachFinalizer(inner);var $$=inner.$$;inner["notifyOnDestruction"]();$$.preservePointerOnDelete=true;Object.defineProperties(this,{$$:{value:$$}});attachFinalizer(this);registerInheritedInstance(registeredClass,$$.ptr,this)};wrapperPrototype["__destruct"]=function __destruct(){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __destruct")}detachFinalizer(this);unregisterInheritedInstance(registeredClass,this.$$.ptr)};ctor.prototype=Object.create(wrapperPrototype);Object.assign(ctor.prototype,properties);return Emval.toHandle(ctor)};var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var awaitingDependencies={};var typeDependencies={};var InternalError=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}}if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};for(var[i,field]of fieldRecords.entries()){const getterReturnType=fieldTypes[i];const getter=field.getter;const getterContext=field.getterContext;const setterArgumentType=fieldTypes[i+fieldRecords.length];const setter=field.setter;const setterContext=field.setterContext;fields[field.fieldName]={read:ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o));runDestructors(destructors)},optional:getterReturnType.optional}}return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)&&!fields[fieldName].optional){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){return sharedRegisterType(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0n;let fromWireType=value=>value;if(isUnsignedType){const bitSize=size*8;fromWireType=value=>BigInt.asUintN(bitSize,value);maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>{if(typeof value=="number"){value=BigInt(value)}return value},readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})};var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var init_ClassHandle=()=>{let proto=ClassHandle.prototype;Object.assign(proto,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}});const symbolDispose=Symbol.dispose;if(symbolDispose){proto[symbolDispose]=proto["delete"]}};function ClassHandle(){}var registeredPointers={};var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupported sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this.toWireType=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this.toWireType=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this.toWireType=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var embind__requireFunction=(signature,rawFunction,isAsync=false)=>{signature=AsciiToString(signature);function makeDynCaller(){var rtn=getWasmTableEntry(rawFunction);return rtn}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};class UnboundTypeError extends Error{}var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError(`Use 'new' to construct ${name}`)}if(undefined===registeredClass.constructor_body){throw new BindingError(`${name} has no accessible constructor`)}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};function usesDestructorStack(argTypes){for(var i=1;i{var array=[];for(var i=0;i>2])}return array};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex===-1)return signature;return signature.slice(0,argsIndex)};var __embind_register_class_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn,isAsync);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}if(classType.registeredClass.__derivedClasses){for(const derivedClass of classType.registeredClass.__derivedClasses){if(!derivedClass.constructor.hasOwnProperty(methodName)){derivedClass.constructor[methodName]=func}}}return[]});return[]})};var __embind_register_class_constructor=(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var validateThis=(this_,classType,humanName)=>{if(!(this_ instanceof Object)){throwBindingError(`${humanName} with invalid "this": ${this_}`)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`)}if(!this_.$$.ptr){throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`)}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)};var __embind_register_class_property=(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{fieldName=AsciiToString(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],classType=>{classType=classType[0];var humanName=`${classType.name}.${fieldName}`;var desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])}else{desc.set=v=>throwBindingError(humanName+" is a read-only property")}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],types=>{var getterReturnType=types[0];var desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType.fromWireType(getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})};var __embind_register_constant=(name,type,value)=>{name=AsciiToString(name);whenDependentTypesAreResolved([],[type],type=>{type=type[0];Module[name]=type.fromWireType(value);return[]})};var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function getEnumValueType(rawValueType){return rawValueType===0?"object":rawValueType===1?"number":"string"}var __embind_register_enum=(rawType,name,size,isSigned,rawValueType)=>{name=AsciiToString(name);const valueType=getEnumValueType(rawValueType);switch(valueType){case"object":{function ctor(){}ctor.values={};registerType(rawType,{name,constructor:ctor,valueType,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor);break}case"number":{var keysMap={};registerType(rawType,{name,keysMap,valueType,fromWireType:c=>c,toWireType:(destructors,c)=>c,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}case"string":{var valuesMap={};var reverseMap={};var keysMap={};registerType(rawType,{name,valuesMap,reverseMap,keysMap,valueType,fromWireType:function(c){return this.reverseMap[c]},toWireType:function(destructors,c){return this.valuesMap[c]},readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}}};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=AsciiToString(name);switch(enumType.valueType){case"object":{var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value;break}case"number":{enumType.keysMap[name]=enumValue;break}case"string":{enumType.valuesMap[name]=enumValue;enumType.reverseMap[enumValue]=name;enumType.keysMap[name]=name;break}}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=AsciiToString(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker,isAsync);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0;let fromWireType=value=>value;if(isUnsignedType){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift;maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var installIndexedIterator=(proto,sizeMethodName,getMethodName)=>{const makeIterator=(size,getValue)=>{let index=0;return{next(){if(index>=size){return{done:true}}const current=index;index++;const value=getValue(current);return{value,done:false}},[Symbol.iterator](){return this}}};if(!proto[Symbol.iterator]){proto[Symbol.iterator]=function(){const size=this[sizeMethodName]();return makeIterator(size,i=>this[getMethodName](i))}}};var __embind_register_iterable=(rawClassType,rawElementType,sizeMethodName,getMethodName)=>{sizeMethodName=AsciiToString(sizeMethodName);getMethodName=AsciiToString(getMethodName);whenDependentTypesAreResolved([],[rawClassType,rawElementType],types=>{const classType=types[0];installIndexedIterator(classType.registeredClass.instancePrototype,sizeMethodName,getMethodName);return[]})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=AsciiToString(name);registerType(rawType,{name,fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})};var EmValOptionalType=Object.assign({optional:true},EmValType);var __embind_register_optional=(rawOptionalType,rawType)=>{registerType(rawOptionalType,EmValOptionalType)};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var __embind_register_std_string=(rawType,name)=>{name=AsciiToString(name);var stdStringIsUTF8=true;registerType(rawType,{name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){str=UTF8ToString(payload,length,true)}else{str="";for(var i=0;i>2]=length;if(valueIsOfTypeString){if(stdStringIsUTF8){stringToUTF8(value,ptr,length+1)}else{for(var i=0;i255){_free(base);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}}else{HEAPU8.set(value,ptr)}if(destructors!==null){destructors.push(_free,base)}return base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var UTF16Decoder=globalThis.TextDecoder?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{var idx=ptr>>1;var endIdx=findStringEnd(HEAPU16,idx,maxBytesToRead/2,ignoreNul);if(endIdx-idx>16&&UTF16Decoder)return UTF16Decoder.decode(HEAPU16.subarray(idx,endIdx));var str="";for(var i=idx;i{maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{var str="";var startIdx=ptr>>2;for(var i=0;!(i>=maxBytesToRead/4);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i65535){i++}HEAP32[outPtr>>2]=codePoint;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i65535){i++}len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=AsciiToString(name);var decodeString,encodeString,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16}else{decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32}registerType(rawType,{name,fromWireType:value=>{var length=HEAPU32[value>>2];var str=decodeString(value+4,length*charSize,true);_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:AsciiToString(fieldName),getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext,setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext})};var __embind_register_void=(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{isVoid:true,name,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var emval_methodCallers=[];var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],`parameter ${i}`)}return a};var emval_returnValue=(toReturnWire,destructorsRef,handle)=>{var destructors=[];var result=toReturnWire(destructors,handle);if(destructors.length){HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)}return result};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return AsciiToString(address)}return symbol};var __emval_create_invoker=(argCount,argTypesPtr,kind)=>{var GenericWireTypeSize=8;var[retType,...argTypes]=emval_lookupTypes(argCount,argTypesPtr);var toReturnWire=retType.toWireType.bind(retType);var argFromPtr=argTypes.map(type=>type.readValueFromPointer.bind(type));argCount--;var argN=new Array(argCount);var invokerFunction=(handle,methodName,destructorsRef,args)=>{var offset=0;for(var i=0;it.name)}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))};var __emval_invoke=(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args);var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_new_object=()=>Emval.toHandle({});var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var __emval_set_property=(handle,key,value)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var _emscripten_date_now=()=>Date.now();var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var printCharBuffers=[null,[],[]];var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};init_ClassHandle();init_RegisteredPointer();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var ___getTypeName,_free,_malloc,__emscripten_timeout,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){___getTypeName=wasmExports["Q"];_free=Module["_free"]=wasmExports["R"];_malloc=Module["_malloc"]=wasmExports["S"];__emscripten_timeout=wasmExports["U"];memory=wasmMemory=wasmExports["O"];__indirect_function_table=wasmTable=wasmExports["T"]}var wasmImports={J:__abort_js,x:__embind_create_inheriting_constructor,s:__embind_finalize_value_object,z:__embind_register_bigint,A:__embind_register_bool,b:__embind_register_class,p:__embind_register_class_class_function,e:__embind_register_class_constructor,a:__embind_register_class_function,d:__embind_register_class_property,K:__embind_register_constant,M:__embind_register_emval,l:__embind_register_enum,f:__embind_register_enum_value,y:__embind_register_float,g:__embind_register_function,o:__embind_register_integer,q:__embind_register_iterable,h:__embind_register_memory_view,r:__embind_register_optional,N:__embind_register_std_string,u:__embind_register_std_wstring,t:__embind_register_value_object,m:__embind_register_value_object_field,B:__embind_register_void,D:__emscripten_runtime_keepalive_clear,k:__emval_create_invoker,n:__emval_decref,j:__emval_invoke,v:__emval_new_cstring,L:__emval_new_object,i:__emval_run_destructors,w:__emval_set_property,E:__setitimer_js,I:_emscripten_date_now,c:_emscripten_get_now,F:_emscripten_resize_heap,G:_exit,H:_fd_write,C:_proc_exit};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} ;return moduleRtn}})();if(typeof exports==="object"&&typeof module==="object"){module.exports=PHYSX;module.exports.default=PHYSX}else if(typeof define==="function"&&define["amd"])define([],()=>PHYSX); diff --git a/packages/physics-physx/libs/physx.release.simd.js b/packages/physics-physx/libs/physx.release.simd.js index bdb1d70990..45843d369b 100644 --- a/packages/physics-physx/libs/physx.release.simd.js +++ b/packages/physics-physx/libs/physx.release.simd.js @@ -1,2 +1,2 @@ -var PHYSX=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["P"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return "https://mdn.alipayobjects.com/rms/afts/file/A*6vi1SJaSJ6IAAAAAgDAAAAgAehQnAQ/physx.release.simd.wasm"}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var __abort_js=()=>abort("");var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var emval_freelist=[];var emval_handles=[0,1,,1,null,1,true,1,false,1];var BindingError=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};var throwBindingError=message=>{throw new BindingError(message)};var Emval={toValue:handle=>{if(!handle){throwBindingError(`Cannot use deleted val. handle = ${handle}`)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};class PureVirtualError extends Error{}var AsciiToString=ptr=>{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var registerInheritedInstance=(class_,ptr,instance)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){throwBindingError(`Tried to register registered instance: ${ptr}`)}else{registeredInstances[ptr]=instance}};var registeredTypes={};var getTypeName=type=>{var ptr=___getTypeName(type);var rv=AsciiToString(ptr);_free(ptr);return rv};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var unregisterInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){delete registeredInstances[ptr]}else{throwBindingError(`Tried to unregister unregistered instance: ${ptr}`)}};var detachFinalizer=handle=>{};var finalizationRegistry=false;var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var attachFinalizer=handle=>{if(!globalThis.FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var __embind_create_inheriting_constructor=(constructorName,wrapperType,properties)=>{constructorName=AsciiToString(constructorName);wrapperType=requireRegisteredType(wrapperType,"wrapper");properties=Emval.toValue(properties);var registeredClass=wrapperType.registeredClass;var wrapperPrototype=registeredClass.instancePrototype;var baseClass=registeredClass.baseClass;var baseClassPrototype=baseClass.instancePrototype;var baseConstructor=registeredClass.baseClass.constructor;var ctor=createNamedFunction(constructorName,function(...args){for(var name of registeredClass.baseClass.pureVirtualFunctions){if(this[name]===baseClassPrototype[name]){throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`)}}Object.defineProperty(this,"__parent",{value:wrapperPrototype});this["__construct"](...args)});wrapperPrototype["__construct"]=function __construct(...args){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __construct")}var inner=baseConstructor["implement"](this,...args);detachFinalizer(inner);var $$=inner.$$;inner["notifyOnDestruction"]();$$.preservePointerOnDelete=true;Object.defineProperties(this,{$$:{value:$$}});attachFinalizer(this);registerInheritedInstance(registeredClass,$$.ptr,this)};wrapperPrototype["__destruct"]=function __destruct(){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __destruct")}detachFinalizer(this);unregisterInheritedInstance(registeredClass,this.$$.ptr)};ctor.prototype=Object.create(wrapperPrototype);Object.assign(ctor.prototype,properties);return Emval.toHandle(ctor)};var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var awaitingDependencies={};var typeDependencies={};var InternalError=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}}if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};for(var[i,field]of fieldRecords.entries()){const getterReturnType=fieldTypes[i];const getter=field.getter;const getterContext=field.getterContext;const setterArgumentType=fieldTypes[i+fieldRecords.length];const setter=field.setter;const setterContext=field.setterContext;fields[field.fieldName]={read:ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o));runDestructors(destructors)},optional:getterReturnType.optional}}return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)&&!fields[fieldName].optional){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){return sharedRegisterType(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0n;let fromWireType=value=>value;if(isUnsignedType){const bitSize=size*8;fromWireType=value=>BigInt.asUintN(bitSize,value);maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>{if(typeof value=="number"){value=BigInt(value)}return value},readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})};var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var init_ClassHandle=()=>{let proto=ClassHandle.prototype;Object.assign(proto,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}});const symbolDispose=Symbol.dispose;if(symbolDispose){proto[symbolDispose]=proto["delete"]}};function ClassHandle(){}var registeredPointers={};var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupported sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this.toWireType=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this.toWireType=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this.toWireType=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var embind__requireFunction=(signature,rawFunction,isAsync=false)=>{signature=AsciiToString(signature);function makeDynCaller(){var rtn=getWasmTableEntry(rawFunction);return rtn}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};class UnboundTypeError extends Error{}var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError(`Use 'new' to construct ${name}`)}if(undefined===registeredClass.constructor_body){throw new BindingError(`${name} has no accessible constructor`)}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};function usesDestructorStack(argTypes){for(var i=1;i{var array=[];for(var i=0;i>2])}return array};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex===-1)return signature;return signature.slice(0,argsIndex)};var __embind_register_class_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn,isAsync);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}if(classType.registeredClass.__derivedClasses){for(const derivedClass of classType.registeredClass.__derivedClasses){if(!derivedClass.constructor.hasOwnProperty(methodName)){derivedClass.constructor[methodName]=func}}}return[]});return[]})};var __embind_register_class_constructor=(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var validateThis=(this_,classType,humanName)=>{if(!(this_ instanceof Object)){throwBindingError(`${humanName} with invalid "this": ${this_}`)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`)}if(!this_.$$.ptr){throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`)}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)};var __embind_register_class_property=(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{fieldName=AsciiToString(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],classType=>{classType=classType[0];var humanName=`${classType.name}.${fieldName}`;var desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])}else{desc.set=v=>throwBindingError(humanName+" is a read-only property")}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],types=>{var getterReturnType=types[0];var desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType.fromWireType(getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})};var __embind_register_constant=(name,type,value)=>{name=AsciiToString(name);whenDependentTypesAreResolved([],[type],type=>{type=type[0];Module[name]=type.fromWireType(value);return[]})};var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function getEnumValueType(rawValueType){return rawValueType===0?"object":rawValueType===1?"number":"string"}var __embind_register_enum=(rawType,name,size,isSigned,rawValueType)=>{name=AsciiToString(name);const valueType=getEnumValueType(rawValueType);switch(valueType){case"object":{function ctor(){}ctor.values={};registerType(rawType,{name,constructor:ctor,valueType,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor);break}case"number":{var keysMap={};registerType(rawType,{name,keysMap,valueType,fromWireType:c=>c,toWireType:(destructors,c)=>c,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}case"string":{var valuesMap={};var reverseMap={};var keysMap={};registerType(rawType,{name,valuesMap,reverseMap,keysMap,valueType,fromWireType:function(c){return this.reverseMap[c]},toWireType:function(destructors,c){return this.valuesMap[c]},readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}}};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=AsciiToString(name);switch(enumType.valueType){case"object":{var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value;break}case"number":{enumType.keysMap[name]=enumValue;break}case"string":{enumType.valuesMap[name]=enumValue;enumType.reverseMap[enumValue]=name;enumType.keysMap[name]=name;break}}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=AsciiToString(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker,isAsync);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0;let fromWireType=value=>value;if(isUnsignedType){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift;maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var installIndexedIterator=(proto,sizeMethodName,getMethodName)=>{const makeIterator=(size,getValue)=>{let index=0;return{next(){if(index>=size){return{done:true}}const current=index;index++;const value=getValue(current);return{value,done:false}},[Symbol.iterator](){return this}}};if(!proto[Symbol.iterator]){proto[Symbol.iterator]=function(){const size=this[sizeMethodName]();return makeIterator(size,i=>this[getMethodName](i))}}};var __embind_register_iterable=(rawClassType,rawElementType,sizeMethodName,getMethodName)=>{sizeMethodName=AsciiToString(sizeMethodName);getMethodName=AsciiToString(getMethodName);whenDependentTypesAreResolved([],[rawClassType,rawElementType],types=>{const classType=types[0];installIndexedIterator(classType.registeredClass.instancePrototype,sizeMethodName,getMethodName);return[]})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=AsciiToString(name);registerType(rawType,{name,fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})};var EmValOptionalType=Object.assign({optional:true},EmValType);var __embind_register_optional=(rawOptionalType,rawType)=>{registerType(rawOptionalType,EmValOptionalType)};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var __embind_register_std_string=(rawType,name)=>{name=AsciiToString(name);var stdStringIsUTF8=true;registerType(rawType,{name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){str=UTF8ToString(payload,length,true)}else{str="";for(var i=0;i>2]=length;if(valueIsOfTypeString){if(stdStringIsUTF8){stringToUTF8(value,ptr,length+1)}else{for(var i=0;i255){_free(base);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}}else{HEAPU8.set(value,ptr)}if(destructors!==null){destructors.push(_free,base)}return base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var UTF16Decoder=globalThis.TextDecoder?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{var idx=ptr>>1;var endIdx=findStringEnd(HEAPU16,idx,maxBytesToRead/2,ignoreNul);if(endIdx-idx>16&&UTF16Decoder)return UTF16Decoder.decode(HEAPU16.subarray(idx,endIdx));var str="";for(var i=idx;i{maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{var str="";var startIdx=ptr>>2;for(var i=0;!(i>=maxBytesToRead/4);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i65535){i++}HEAP32[outPtr>>2]=codePoint;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i65535){i++}len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=AsciiToString(name);var decodeString,encodeString,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16}else{decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32}registerType(rawType,{name,fromWireType:value=>{var length=HEAPU32[value>>2];var str=decodeString(value+4,length*charSize,true);_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:AsciiToString(fieldName),getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext,setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext})};var __embind_register_void=(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{isVoid:true,name,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var emval_methodCallers=[];var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],`parameter ${i}`)}return a};var emval_returnValue=(toReturnWire,destructorsRef,handle)=>{var destructors=[];var result=toReturnWire(destructors,handle);if(destructors.length){HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)}return result};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return AsciiToString(address)}return symbol};var __emval_create_invoker=(argCount,argTypesPtr,kind)=>{var GenericWireTypeSize=8;var[retType,...argTypes]=emval_lookupTypes(argCount,argTypesPtr);var toReturnWire=retType.toWireType.bind(retType);var argFromPtr=argTypes.map(type=>type.readValueFromPointer.bind(type));argCount--;var argN=new Array(argCount);var invokerFunction=(handle,methodName,destructorsRef,args)=>{var offset=0;for(var i=0;it.name)}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))};var __emval_invoke=(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args);var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_new_object=()=>Emval.toHandle({});var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var __emval_set_property=(handle,key,value)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var _emscripten_date_now=()=>Date.now();var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var printCharBuffers=[null,[],[]];var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};init_ClassHandle();init_RegisteredPointer();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var ___getTypeName,_free,_malloc,__emscripten_timeout,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){___getTypeName=wasmExports["Q"];_free=Module["_free"]=wasmExports["R"];_malloc=Module["_malloc"]=wasmExports["S"];__emscripten_timeout=wasmExports["U"];memory=wasmMemory=wasmExports["O"];__indirect_function_table=wasmTable=wasmExports["T"]}var wasmImports={J:__abort_js,x:__embind_create_inheriting_constructor,s:__embind_finalize_value_object,z:__embind_register_bigint,A:__embind_register_bool,b:__embind_register_class,p:__embind_register_class_class_function,e:__embind_register_class_constructor,a:__embind_register_class_function,d:__embind_register_class_property,K:__embind_register_constant,M:__embind_register_emval,l:__embind_register_enum,f:__embind_register_enum_value,y:__embind_register_float,g:__embind_register_function,o:__embind_register_integer,q:__embind_register_iterable,h:__embind_register_memory_view,r:__embind_register_optional,N:__embind_register_std_string,u:__embind_register_std_wstring,t:__embind_register_value_object,m:__embind_register_value_object_field,B:__embind_register_void,D:__emscripten_runtime_keepalive_clear,k:__emval_create_invoker,n:__emval_decref,j:__emval_invoke,v:__emval_new_cstring,L:__emval_new_object,i:__emval_run_destructors,w:__emval_set_property,E:__setitimer_js,I:_emscripten_date_now,c:_emscripten_get_now,F:_emscripten_resize_heap,G:_exit,H:_fd_write,C:_proc_exit};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} +var PHYSX=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["P"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("physx.release.simd.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var __abort_js=()=>abort("");var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var emval_freelist=[];var emval_handles=[0,1,,1,null,1,true,1,false,1];var BindingError=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};var throwBindingError=message=>{throw new BindingError(message)};var Emval={toValue:handle=>{if(!handle){throwBindingError(`Cannot use deleted val. handle = ${handle}`)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};class PureVirtualError extends Error{}var AsciiToString=ptr=>{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var registerInheritedInstance=(class_,ptr,instance)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){throwBindingError(`Tried to register registered instance: ${ptr}`)}else{registeredInstances[ptr]=instance}};var registeredTypes={};var getTypeName=type=>{var ptr=___getTypeName(type);var rv=AsciiToString(ptr);_free(ptr);return rv};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var unregisterInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){delete registeredInstances[ptr]}else{throwBindingError(`Tried to unregister unregistered instance: ${ptr}`)}};var detachFinalizer=handle=>{};var finalizationRegistry=false;var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var attachFinalizer=handle=>{if(!globalThis.FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var __embind_create_inheriting_constructor=(constructorName,wrapperType,properties)=>{constructorName=AsciiToString(constructorName);wrapperType=requireRegisteredType(wrapperType,"wrapper");properties=Emval.toValue(properties);var registeredClass=wrapperType.registeredClass;var wrapperPrototype=registeredClass.instancePrototype;var baseClass=registeredClass.baseClass;var baseClassPrototype=baseClass.instancePrototype;var baseConstructor=registeredClass.baseClass.constructor;var ctor=createNamedFunction(constructorName,function(...args){for(var name of registeredClass.baseClass.pureVirtualFunctions){if(this[name]===baseClassPrototype[name]){throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`)}}Object.defineProperty(this,"__parent",{value:wrapperPrototype});this["__construct"](...args)});wrapperPrototype["__construct"]=function __construct(...args){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __construct")}var inner=baseConstructor["implement"](this,...args);detachFinalizer(inner);var $$=inner.$$;inner["notifyOnDestruction"]();$$.preservePointerOnDelete=true;Object.defineProperties(this,{$$:{value:$$}});attachFinalizer(this);registerInheritedInstance(registeredClass,$$.ptr,this)};wrapperPrototype["__destruct"]=function __destruct(){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __destruct")}detachFinalizer(this);unregisterInheritedInstance(registeredClass,this.$$.ptr)};ctor.prototype=Object.create(wrapperPrototype);Object.assign(ctor.prototype,properties);return Emval.toHandle(ctor)};var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var awaitingDependencies={};var typeDependencies={};var InternalError=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}}if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};for(var[i,field]of fieldRecords.entries()){const getterReturnType=fieldTypes[i];const getter=field.getter;const getterContext=field.getterContext;const setterArgumentType=fieldTypes[i+fieldRecords.length];const setter=field.setter;const setterContext=field.setterContext;fields[field.fieldName]={read:ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o));runDestructors(destructors)},optional:getterReturnType.optional}}return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)&&!fields[fieldName].optional){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){return sharedRegisterType(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0n;let fromWireType=value=>value;if(isUnsignedType){const bitSize=size*8;fromWireType=value=>BigInt.asUintN(bitSize,value);maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>{if(typeof value=="number"){value=BigInt(value)}return value},readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})};var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var init_ClassHandle=()=>{let proto=ClassHandle.prototype;Object.assign(proto,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}});const symbolDispose=Symbol.dispose;if(symbolDispose){proto[symbolDispose]=proto["delete"]}};function ClassHandle(){}var registeredPointers={};var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupported sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this.toWireType=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this.toWireType=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this.toWireType=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var embind__requireFunction=(signature,rawFunction,isAsync=false)=>{signature=AsciiToString(signature);function makeDynCaller(){var rtn=getWasmTableEntry(rawFunction);return rtn}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};class UnboundTypeError extends Error{}var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError(`Use 'new' to construct ${name}`)}if(undefined===registeredClass.constructor_body){throw new BindingError(`${name} has no accessible constructor`)}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};function usesDestructorStack(argTypes){for(var i=1;i{var array=[];for(var i=0;i>2])}return array};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex===-1)return signature;return signature.slice(0,argsIndex)};var __embind_register_class_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn,isAsync);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}if(classType.registeredClass.__derivedClasses){for(const derivedClass of classType.registeredClass.__derivedClasses){if(!derivedClass.constructor.hasOwnProperty(methodName)){derivedClass.constructor[methodName]=func}}}return[]});return[]})};var __embind_register_class_constructor=(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var validateThis=(this_,classType,humanName)=>{if(!(this_ instanceof Object)){throwBindingError(`${humanName} with invalid "this": ${this_}`)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`)}if(!this_.$$.ptr){throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`)}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)};var __embind_register_class_property=(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{fieldName=AsciiToString(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],classType=>{classType=classType[0];var humanName=`${classType.name}.${fieldName}`;var desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])}else{desc.set=v=>throwBindingError(humanName+" is a read-only property")}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],types=>{var getterReturnType=types[0];var desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType.fromWireType(getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})};var __embind_register_constant=(name,type,value)=>{name=AsciiToString(name);whenDependentTypesAreResolved([],[type],type=>{type=type[0];Module[name]=type.fromWireType(value);return[]})};var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function getEnumValueType(rawValueType){return rawValueType===0?"object":rawValueType===1?"number":"string"}var __embind_register_enum=(rawType,name,size,isSigned,rawValueType)=>{name=AsciiToString(name);const valueType=getEnumValueType(rawValueType);switch(valueType){case"object":{function ctor(){}ctor.values={};registerType(rawType,{name,constructor:ctor,valueType,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor);break}case"number":{var keysMap={};registerType(rawType,{name,keysMap,valueType,fromWireType:c=>c,toWireType:(destructors,c)=>c,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}case"string":{var valuesMap={};var reverseMap={};var keysMap={};registerType(rawType,{name,valuesMap,reverseMap,keysMap,valueType,fromWireType:function(c){return this.reverseMap[c]},toWireType:function(destructors,c){return this.valuesMap[c]},readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}}};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=AsciiToString(name);switch(enumType.valueType){case"object":{var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value;break}case"number":{enumType.keysMap[name]=enumValue;break}case"string":{enumType.valuesMap[name]=enumValue;enumType.reverseMap[enumValue]=name;enumType.keysMap[name]=name;break}}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=AsciiToString(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker,isAsync);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0;let fromWireType=value=>value;if(isUnsignedType){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift;maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var installIndexedIterator=(proto,sizeMethodName,getMethodName)=>{const makeIterator=(size,getValue)=>{let index=0;return{next(){if(index>=size){return{done:true}}const current=index;index++;const value=getValue(current);return{value,done:false}},[Symbol.iterator](){return this}}};if(!proto[Symbol.iterator]){proto[Symbol.iterator]=function(){const size=this[sizeMethodName]();return makeIterator(size,i=>this[getMethodName](i))}}};var __embind_register_iterable=(rawClassType,rawElementType,sizeMethodName,getMethodName)=>{sizeMethodName=AsciiToString(sizeMethodName);getMethodName=AsciiToString(getMethodName);whenDependentTypesAreResolved([],[rawClassType,rawElementType],types=>{const classType=types[0];installIndexedIterator(classType.registeredClass.instancePrototype,sizeMethodName,getMethodName);return[]})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=AsciiToString(name);registerType(rawType,{name,fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})};var EmValOptionalType=Object.assign({optional:true},EmValType);var __embind_register_optional=(rawOptionalType,rawType)=>{registerType(rawOptionalType,EmValOptionalType)};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var __embind_register_std_string=(rawType,name)=>{name=AsciiToString(name);var stdStringIsUTF8=true;registerType(rawType,{name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){str=UTF8ToString(payload,length,true)}else{str="";for(var i=0;i>2]=length;if(valueIsOfTypeString){if(stdStringIsUTF8){stringToUTF8(value,ptr,length+1)}else{for(var i=0;i255){_free(base);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}}else{HEAPU8.set(value,ptr)}if(destructors!==null){destructors.push(_free,base)}return base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var UTF16Decoder=globalThis.TextDecoder?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{var idx=ptr>>1;var endIdx=findStringEnd(HEAPU16,idx,maxBytesToRead/2,ignoreNul);if(endIdx-idx>16&&UTF16Decoder)return UTF16Decoder.decode(HEAPU16.subarray(idx,endIdx));var str="";for(var i=idx;i{maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{var str="";var startIdx=ptr>>2;for(var i=0;!(i>=maxBytesToRead/4);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i65535){i++}HEAP32[outPtr>>2]=codePoint;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i65535){i++}len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=AsciiToString(name);var decodeString,encodeString,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16}else{decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32}registerType(rawType,{name,fromWireType:value=>{var length=HEAPU32[value>>2];var str=decodeString(value+4,length*charSize,true);_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:AsciiToString(fieldName),getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext,setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext})};var __embind_register_void=(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{isVoid:true,name,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var emval_methodCallers=[];var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],`parameter ${i}`)}return a};var emval_returnValue=(toReturnWire,destructorsRef,handle)=>{var destructors=[];var result=toReturnWire(destructors,handle);if(destructors.length){HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)}return result};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return AsciiToString(address)}return symbol};var __emval_create_invoker=(argCount,argTypesPtr,kind)=>{var GenericWireTypeSize=8;var[retType,...argTypes]=emval_lookupTypes(argCount,argTypesPtr);var toReturnWire=retType.toWireType.bind(retType);var argFromPtr=argTypes.map(type=>type.readValueFromPointer.bind(type));argCount--;var argN=new Array(argCount);var invokerFunction=(handle,methodName,destructorsRef,args)=>{var offset=0;for(var i=0;it.name)}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))};var __emval_invoke=(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args);var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_new_object=()=>Emval.toHandle({});var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var __emval_set_property=(handle,key,value)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var _emscripten_date_now=()=>Date.now();var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var printCharBuffers=[null,[],[]];var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};init_ClassHandle();init_RegisteredPointer();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var ___getTypeName,_free,_malloc,__emscripten_timeout,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){___getTypeName=wasmExports["Q"];_free=Module["_free"]=wasmExports["R"];_malloc=Module["_malloc"]=wasmExports["S"];__emscripten_timeout=wasmExports["U"];memory=wasmMemory=wasmExports["O"];__indirect_function_table=wasmTable=wasmExports["T"]}var wasmImports={J:__abort_js,x:__embind_create_inheriting_constructor,s:__embind_finalize_value_object,z:__embind_register_bigint,A:__embind_register_bool,b:__embind_register_class,p:__embind_register_class_class_function,e:__embind_register_class_constructor,a:__embind_register_class_function,d:__embind_register_class_property,K:__embind_register_constant,M:__embind_register_emval,l:__embind_register_enum,f:__embind_register_enum_value,y:__embind_register_float,g:__embind_register_function,o:__embind_register_integer,q:__embind_register_iterable,h:__embind_register_memory_view,r:__embind_register_optional,N:__embind_register_std_string,u:__embind_register_std_wstring,t:__embind_register_value_object,m:__embind_register_value_object_field,B:__embind_register_void,D:__emscripten_runtime_keepalive_clear,k:__emval_create_invoker,n:__emval_decref,j:__emval_invoke,v:__emval_new_cstring,L:__emval_new_object,i:__emval_run_destructors,w:__emval_set_property,E:__setitimer_js,I:_emscripten_date_now,c:_emscripten_get_now,F:_emscripten_resize_heap,G:_exit,H:_fd_write,C:_proc_exit};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} ;return moduleRtn}})();if(typeof exports==="object"&&typeof module==="object"){module.exports=PHYSX;module.exports.default=PHYSX}else if(typeof define==="function"&&define["amd"])define([],()=>PHYSX); diff --git a/packages/physics-physx/libs/physx.release.simd.wasm b/packages/physics-physx/libs/physx.release.simd.wasm index 91e5bab34b9c88b3a73e718d3e2e84c537c6cee5..f52b48110ab6122d7df5f75811d4bc80a946384b 100755 GIT binary patch delta 143753 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�)nlT+OC2HBy&8R{>rWk)ITaF%+ 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

GAKhF2uX>*6)zn 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 z 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

d$?kX%&qe3-HVx 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 delta 144148 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�y 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 { @@ -214,6 +216,12 @@ export class PhysXPhysicsScene implements IPhysicsScene { } else { return 0; // eNONE } + }, + postFilter: (filterData, distance) => { + if (distance <= 0) { + return 0; // eNONE — skip initial overlap + } + return 2; // eBLOCK } }; @@ -452,6 +460,12 @@ export class PhysXPhysicsScene implements IPhysicsScene { } else { return 0; // eNONE } + }, + postFilter: (filterData, distance) => { + if (distance <= 0) { + return 0; // eNONE — skip initial overlap + } + return 2; // eBLOCK } }; From 3075f4f85b22810579d03750ad873578ec9f3422 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Mon, 20 Apr 2026 15:58:33 +0800 Subject: [PATCH 044/130] fix: entity set sibling error when without parent --- packages/core/src/Entity.ts | 8 +++----- packages/core/src/SceneManager.ts | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/core/src/Entity.ts b/packages/core/src/Entity.ts index 6cb9945d56..14ec84e8b7 100644 --- a/packages/core/src/Entity.ts +++ b/packages/core/src/Entity.ts @@ -9,7 +9,7 @@ import { Script } from "./Script"; import { Transform } from "./Transform"; import { UpdateFlagManager } from "./UpdateFlagManager"; import { ReferResource } from "./asset/ReferResource"; -import { EngineObject } from "./base"; +import { EngineObject, Logger } from "./base"; import { CloneUtils } from "./clone/CloneUtils"; import { ComponentCloner } from "./clone/ComponentCloner"; import { ActiveChangeFlag } from "./enums/ActiveChangeFlag"; @@ -212,16 +212,14 @@ export class Entity extends EngineObject { } set siblingIndex(value: number) { - if (this._siblingIndex === -1) { - throw `The entity ${this.name} is not in the hierarchy`; - } - if (this._isRoot) { this._setSiblingIndex(this._scene._rootEntities, value); } else if (this._parent) { const parent = this._parent; this._setSiblingIndex(parent._children, value); parent._dispatchModify(EntityModifyFlags.Child, parent); + } else { + Logger.warn(`The entity ${this.name} is not in the hierarchy`); } } diff --git a/packages/core/src/SceneManager.ts b/packages/core/src/SceneManager.ts index 92de60d66b..984850fed4 100644 --- a/packages/core/src/SceneManager.ts +++ b/packages/core/src/SceneManager.ts @@ -94,7 +94,7 @@ export class SceneManager { const scenePromise = this.engine.resourceManager.load({ url, type: AssetType.Scene }); scenePromise.then((scene: Scene) => { if (destroyOldScene) { - const scenes = this._scenes.getArray(); + const scenes = this._scenes.getLoopArray(); for (let i = 0, n = scenes.length; i < n; i++) { scenes[i].destroy(); } From acf9c2d70e1d9b0731d6692094e9bf76a344c66f Mon Sep 17 00:00:00 2001 From: luzhuang Date: Tue, 21 Apr 2026 14:54:24 +0800 Subject: [PATCH 045/130] fix(core/ui): propagate reparent dirty to descendants in Transform and UITransform (#2973) --- packages/core/src/Transform.ts | 21 ++++- packages/ui/src/component/UITransform.ts | 44 +++++++++- tests/src/core/Transform.test.ts | 104 ++++++++++++++++++++++- 3 files changed, 166 insertions(+), 3 deletions(-) diff --git a/packages/core/src/Transform.ts b/packages/core/src/Transform.ts index f822474dcc..fe9fc720b1 100644 --- a/packages/core/src/Transform.ts +++ b/packages/core/src/Transform.ts @@ -563,7 +563,26 @@ export class Transform extends Component { */ _parentChange(): void { this._isParentDirty = true; - this._updateAllWorldFlag(TransformModifyFlags.WmWpWeWqWsWus); + // Reparent invalidates the world state of the entire subtree: + // 1) `_updateAllWorldFlag` has an early-exit that skips propagation when + // self's world dirty flags are already set — invalid after reparent. + // 2) Descendants may have cached a stale `_parentTransformCache` (e.g. if + // `_getParentTransform` was ever called while their ancestor chain was + // partially constructed during clone/instantiate). Force them to + // re-resolve the parent transform on next access. + this._propagateReparentDirty(TransformModifyFlags.WmWpWeWqWsWus); + } + + private _propagateReparentDirty(flags: TransformModifyFlags): void { + this._worldAssociatedChange(flags); + const children = this._entity._children; + for (let i = 0, n = children.length; i < n; i++) { + const transform = children[i].transform; + if (transform) { + transform._isParentDirty = true; + transform._propagateReparentDirty(flags); + } + } } /** diff --git a/packages/ui/src/component/UITransform.ts b/packages/ui/src/component/UITransform.ts index 9144373472..28e0087575 100644 --- a/packages/ui/src/component/UITransform.ts +++ b/packages/ui/src/component/UITransform.ts @@ -251,7 +251,49 @@ export class UITransform extends Transform { */ _parentChange(): void { this._isParentDirty = true; - this._updateWorldFlagWithParentRectChange(TransformModifyFlags.WmWpWeWqWsWus); + // Reparent invalidates the world state of the entire subtree: + // 1) `_updateWorldFlagWithParentRectChange` has an early-exit that skips + // propagation when self's world dirty flags are already all set — + // invalid after reparent. + // 2) Descendants may have cached a stale `_parentTransformCache` + // (e.g. `_getParentTransform` called while ancestor chain was partially + // constructed during clone/instantiate). Force them to re-resolve on + // next access. + this._propagateReparentDirtyUI(TransformModifyFlags.WmWpWeWqWsWus); + } + + private _propagateReparentDirtyUI(flags: number): void { + let selfChange = false; + const { _horizontalAlignment: horizontalAlignment, _verticalAlignment: verticalAlignment } = this; + if (horizontalAlignment || verticalAlignment) { + if ( + horizontalAlignment === HorizontalAlignmentMode.LeftAndRight || + verticalAlignment === VerticalAlignmentMode.TopAndBottom + ) { + this._updateSizeByAlignment(); + this._updateRectBySizeAndPivot(); + selfChange = true; + } + this._updatePositionByAlignment(); + this._setDirtyFlagTrue(TransformModifyFlags.LocalMatrix); + flags |= TransformModifyFlags.WmWp; + } + this._worldAssociatedChange(flags); + const children = this.entity.children; + for (let i = 0, n = children.length; i < n; i++) { + const transform = children[i].transform as any; + if (!transform) continue; + transform._isParentDirty = true; + if (typeof transform._propagateReparentDirtyUI === "function") { + transform._propagateReparentDirtyUI(flags); + } else if (typeof transform._propagateReparentDirty === "function") { + transform._propagateReparentDirty(flags); + } + } + if (selfChange) { + // @ts-ignore + this._entity._updateFlagManager.dispatch(UITransformModifyFlags.Size); + } } // @ts-ignore diff --git a/tests/src/core/Transform.test.ts b/tests/src/core/Transform.test.ts index 58ffe3f4a8..dc14acbf42 100644 --- a/tests/src/core/Transform.test.ts +++ b/tests/src/core/Transform.test.ts @@ -1,4 +1,4 @@ -import { deepClone, Entity, Scene, Transform } from "@galacean/engine-core"; +import { deepClone, Entity, Scene, Transform, TransformModifyFlags } from "@galacean/engine-core"; import { Vector2, Vector3 } from "@galacean/engine-math"; import { WebGLEngine } from "@galacean/engine-rhi-webgl"; import { beforeAll, describe, expect, it } from "vitest"; @@ -74,6 +74,108 @@ describe("Transform test", function () { expect(parent.transform.instanceId).eq(child.transform._getParentTransform()?.instanceId); }); + it("Reparent propagates world matrix dirty to deep descendants after clone", () => { + // Build source hierarchy: source -> middle -> inner (with local offset) + const source = new Entity(engine, "source"); + const srcMiddle = source.createChild("middle"); + const srcInner = srcMiddle.createChild("inner"); + srcInner.transform.setPosition(10, 20, 30); + + // Clone (equivalent to PrefabResource.instantiate) + const clone = source.clone(); + const cloneMiddle = clone.findByName("middle")!; + const cloneInner = cloneMiddle.findByName("inner")!; + + // Access cloneInner.worldMatrix before adding clone to a positioned parent. + const worldBeforeReparent = cloneInner.transform.worldMatrix; + expect(worldBeforeReparent.elements[12]).to.equal(10); + expect(worldBeforeReparent.elements[13]).to.equal(20); + expect(worldBeforeReparent.elements[14]).to.equal(30); + + // Reparent the clone under a positioned root — same as `table.addChild(levelNode)`. + const root = scene.createRootEntity("reparent-root"); + root.transform.setPosition(1000, 2000, 3000); + root.addChild(clone); + + // cloneInner.worldMatrix must reflect root's offset (deep descendant of moved subtree). + const worldAfterReparent = cloneInner.transform.worldMatrix; + expect(worldAfterReparent.elements[12]).to.equal(1010); + expect(worldAfterReparent.elements[13]).to.equal(2020); + expect(worldAfterReparent.elements[14]).to.equal(3030); + }); + + it("Reparent invalidates descendant world caches even when parent has all world flags set (engine dirty-flag bug)", () => { + // Reproduces a Galacean 2.0-alpha.24 engine bug observed in Screw game: + // Transform._parentChange() calls _updateAllWorldFlag which early-exits + // if `this` already has all target world dirty flags set. This skips + // propagation to descendants. After reparent, a descendant whose + // WorldMatrix flag was previously cleared keeps returning stale cache. + const parent = new Entity(engine, "parent"); + const child = parent.createChild("child"); + child.transform.setPosition(10, 20, 30); + + // 1) Access child.worldMatrix to CLEAR child's WorldMatrix dirty flag. + // The access also clears parent's WorldMatrix flag (chain compute). + const cached = child.transform.worldMatrix; + expect(cached.elements[12]).to.equal(10); + + // 2) Force parent into the failure state: + // "all world dirty flags set" (as if never accessed) — simulates the + // post-clone / lifecycle state where PARENT's world hasn't been read + // but a DESCENDANT's world was. + // @ts-ignore - white-box access for precise engine bug reproduction + parent.transform._dirtyFlag |= TransformModifyFlags.WmWpWeWqWsWus; + + // Sanity: child's WorldMatrix is CLEAR, parent has ALL world flags SET. + // @ts-ignore + expect(child.transform._dirtyFlag & TransformModifyFlags.WorldMatrix).to.equal(0); + // @ts-ignore + expect(parent.transform._dirtyFlag & TransformModifyFlags.WmWpWeWqWsWus).to.equal(TransformModifyFlags.WmWpWeWqWsWus); + + // 3) Reparent `parent` under a positioned root (triggers _parentChange on parent). + const root = scene.createRootEntity("reparent-root"); + root.transform.setPosition(1000, 2000, 3000); + root.addChild(parent); + + // 4) Child's worldMatrix MUST now reflect root's offset. + // Under the bug: early-exit in _updateAllWorldFlag skips propagation → child's + // WorldMatrix flag stays CLEAR → getter returns stale cached (10, 20, 30). + const afterReparent = child.transform.worldMatrix; + expect(afterReparent.elements[12]).to.equal(1010); + expect(afterReparent.elements[13]).to.equal(2020); + expect(afterReparent.elements[14]).to.equal(3030); + }); + + it("Reparent re-resolves descendant parent cache even when cached as null", () => { + // Reproduces the second half of the Galacean 2.0-alpha.24 bug: a descendant + // whose `_parentTransformCache` was resolved to `null` (because + // `_getParentTransform` was called while its ancestor chain was partially + // constructed) keeps returning identity worldMatrix even after the + // ancestor chain is fully wired up. + const parent = new Entity(engine, "parent"); + const child = parent.createChild("child"); + child.transform.setPosition(10, 20, 30); + + // Force child's parent cache to null with `_isParentDirty = false` — + // simulates the state observed in Screw where layer-001 had + // `_parentTransformCache = null, _isParentDirty = false` after clone. + // @ts-ignore + child.transform._parentTransformCache = null; + // @ts-ignore + child.transform._isParentDirty = false; + + // Add parent under a positioned root. If _parentChange on parent fails to + // invalidate child's parent cache, child.worldMatrix returns identity. + const root = scene.createRootEntity("cache-null-root"); + root.transform.setPosition(500, 600, 700); + root.addChild(parent); + + const after = child.transform.worldMatrix; + expect(after.elements[12]).to.equal(510); // 500 + 10 + expect(after.elements[13]).to.equal(620); // 600 + 20 + expect(after.elements[14]).to.equal(730); // 700 + 30 + }); + it("Subclasses of Transform", () => { // Create by constructor const entity0 = new Entity(engine, "entity"); From e5228d76d6b7a2235162dbfbaf552b8e506d5b9b Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 21 Apr 2026 14:58:33 +0800 Subject: [PATCH 046/130] chore: release v0.0.0-experimental-2.0-game --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 34a2f56922..b55ada5329 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index a57e6c4187..e5b0f67eb4 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 6be276f782..970ae5dc9d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index be735da868..d4f7cb7115 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 86e73a7c33..b7ccc0e8bb 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index a2bf568984..163a8369c1 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index 61e5825838..2e4d761bcb 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 42a52983a1..bc4c7b5968 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 30c4065480..5ca38d10c9 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 3cdabdd9a9..e399dc9807 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 9d22c1e383..b8cc820057 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index c6220b9a53..8678942f0c 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index e7e7eff423..5b5049a192 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index 4b51ee5bc2..200e958c5f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 9663c2321f..d6092a5638 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index ec2e3cc79e..c26a00b957 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index bcf0debcb4..5f833d35b0 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From b16d8b5d75937fdb727600a110d89b9546f12ed0 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 21 Apr 2026 17:31:35 +0800 Subject: [PATCH 047/130] fix(core): invoke structured Signal listeners with runtime args before bound args Previously `Signal._addListener` applied bound arguments first and runtime signal args last, producing `method(...boundArgs, ...signalArgs)`. This made the event object's position shift with the number of bound arguments and diverged from the DOM / Cocos `(event, customEventData)` convention. Flip the order so the listener method receives runtime args first and bound args last: `method(...signalArgs, ...boundArgs)`. The event object now always sits at index 0, making migrated Cocos scripts with `(event, customData)` signatures work without rewrites. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/core/src/Signal.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core/src/Signal.ts b/packages/core/src/Signal.ts index a18b86cf5a..e337e03d9d 100644 --- a/packages/core/src/Signal.ts +++ b/packages/core/src/Signal.ts @@ -20,9 +20,11 @@ export class Signal { on(fn: (...args: T) => void, target?: any): void; /** * Add a structured binding listener. Structured bindings support clone remapping. + * The target method will be invoked as `method(...signalArgs, ...args)` — + * runtime signal arguments come first, bound arguments are appended. * @param target - The target component * @param methodName - The method name to invoke on the target - * @param args - Pre-resolved arguments + * @param args - Pre-resolved arguments appended after the runtime signal arguments */ on(target: Component, methodName: string, ...args: any[]): void; on(fnOrTarget: ((...args: T) => void) | Component, targetOrMethodName?: any, ...args: any[]): void { @@ -37,9 +39,11 @@ export class Signal { once(fn: (...args: T) => void, target?: any): void; /** * Add a one-time structured binding listener. + * The target method will be invoked as `method(...signalArgs, ...args)` — + * runtime signal arguments come first, bound arguments are appended. * @param target - The target component * @param methodName - The method name to invoke on the target - * @param args - Pre-resolved arguments + * @param args - Pre-resolved arguments appended after the runtime signal arguments */ once(target: Component, methodName: string, ...args: any[]): void; once(fnOrTarget: ((...args: T) => void) | Component, targetOrMethodName?: any, ...args: any[]): void { @@ -171,7 +175,7 @@ export class Signal { const methodName = targetOrMethodName as string; const fn = args.length > 0 - ? (...signalArgs: any[]) => (target as any)[methodName](...args, ...signalArgs) + ? (...signalArgs: any[]) => (target as any)[methodName](...signalArgs, ...args) : (...signalArgs: any[]) => (target as any)[methodName](...signalArgs); this._listeners.push({ fn: fn as (...args: T) => void, From ae8c7db08db506f3cb1f3daa84ae8450a19d0be8 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 21 Apr 2026 17:40:01 +0800 Subject: [PATCH 048/130] chore: release v0.0.0-experimental-2.0-game.1 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index b55ada5329..b361eb29d9 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index e5b0f67eb4..d347072917 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 970ae5dc9d..d9b8179e4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index d4f7cb7115..cc09093273 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index b7ccc0e8bb..b0d8d42a37 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index 163a8369c1..9ec0252bc6 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index 2e4d761bcb..c3755a9b32 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index bc4c7b5968..7cc62567d8 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 5ca38d10c9..33b9348145 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index e399dc9807..8c7d1d9666 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index b8cc820057..7623f5fe55 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 8678942f0c..5b66d09c4f 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index 5b5049a192..c11971cb88 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index 200e958c5f..d0ee79e211 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index d6092a5638..498cd4fbce 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index c26a00b957..888db89501 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index 5f833d35b0..c43be60498 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game", + "version": "0.0.0-experimental-2.0-game.1", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From 60e963728a635b6a1a8e38fd2db8b70509573a56 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Wed, 22 Apr 2026 19:24:47 +0800 Subject: [PATCH 049/130] fix: clear children bug --- packages/core/src/Entity.ts | 7 +++++++ tests/src/core/Entity.test.ts | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/packages/core/src/Entity.ts b/packages/core/src/Entity.ts index 14ec84e8b7..ce2a10b665 100644 --- a/packages/core/src/Entity.ts +++ b/packages/core/src/Entity.ts @@ -409,6 +409,11 @@ export class Entity extends EngineObject { for (let i = children.length - 1; i >= 0; i--) { const child = children[i]; child._parent = null; + child._siblingIndex = -1; + // Dispatch `Child` to the old parent before `_processInActive` (which unregisters + // UI listeners via `cleanRootCanvas`), so subscribers such as UICanvas can react + // to the hierarchy change while still attached. + this._dispatchModify(EntityModifyFlags.Child, this); let activeChangeFlag = ActiveChangeFlag.None; child._isActiveInHierarchy && (activeChangeFlag |= ActiveChangeFlag.Hierarchy); @@ -416,6 +421,8 @@ export class Entity extends EngineObject { activeChangeFlag && child._processInActive(activeChangeFlag); Entity._traverseSetOwnerScene(child, null); // Must after child._processInActive(). + + child._setParentChange(); } children.length = 0; } diff --git a/tests/src/core/Entity.test.ts b/tests/src/core/Entity.test.ts index eba73370c0..212ee84286 100644 --- a/tests/src/core/Entity.test.ts +++ b/tests/src/core/Entity.test.ts @@ -341,8 +341,29 @@ describe("Entity", async () => { child.parent = parent; const child2 = new Entity(engine, "child2"); child2.parent = parent; + + const parentModifyCount = [0, 0, 0]; + const childModifyCount = [0, 0, 0]; + const child2ModifyCount = [0, 0, 0]; + // @ts-ignore + parent._registerModifyListener((flag: EntityModifyFlags) => ++parentModifyCount[flag]); + // @ts-ignore + child._registerModifyListener((flag: EntityModifyFlags) => ++childModifyCount[flag]); + // @ts-ignore + child2._registerModifyListener((flag: EntityModifyFlags) => ++child2ModifyCount[flag]); + parent.clearChildren(); expect(parent.children.length).eq(0); + + // Parent should receive a single `Child` modify event for the whole clear so + // listeners (e.g. UICanvas) can invalidate their cached state. + expect(parentModifyCount[EntityModifyFlags.Child]).eq(1); + // Each detached child should receive a `Parent` modify event. + expect(childModifyCount[EntityModifyFlags.Parent]).eq(1); + expect(child2ModifyCount[EntityModifyFlags.Parent]).eq(1); + // Sibling index must be reset so the entity is treated as lonely afterwards. + expect(child.siblingIndex).eq(-1); + expect(child2.siblingIndex).eq(-1); }); it("sibling index", () => { const root = scene.createRootEntity(); From 569c062a24681585af03f094cb0096f1648876c2 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 22 Apr 2026 19:45:27 +0800 Subject: [PATCH 050/130] fix(entity): remove redundant self-name prefix check in _findChildByName method --- packages/core/src/Entity.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/core/src/Entity.ts b/packages/core/src/Entity.ts index 14ec84e8b7..eb21caadb9 100644 --- a/packages/core/src/Entity.ts +++ b/packages/core/src/Entity.ts @@ -375,14 +375,6 @@ export class Entity extends EngineObject { return this; } - // Some imported animation clips are normalized to include the single scene root - // name (for example "mixamorig:Hips/..."), while the Animator may already sit on - // that root entity. Accept a self-name prefix so wrapped model roots and - // standalone single-root clips resolve through the same path convention. - if (splits[0] === this.name) { - return splits.length === 1 ? this : Entity._findChildByName(this, 0, splits, 1); - } - return Entity._findChildByName(this, 0, splits, 0); } From 599a7e61193ec1ae6e42d6cc99668fecc5f9a14d Mon Sep 17 00:00:00 2001 From: luzhuang Date: Wed, 22 Apr 2026 19:31:53 +0800 Subject: [PATCH 051/130] fix(core): evict active scene asset cache on loadScene to avoid self-destroy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When loadScene is called with a URL whose cached Scene is the current active scene, resourceManager.load returned the same instance, then destroyOldScene branch destroyed it and re-added it—leaving rootEntities empty and the native PhysicsScene released. Now evict the cached Scene asset before load so a fresh Scene instance is created, matching director.loadScene semantics in Cocos. --- packages/core/src/SceneManager.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/core/src/SceneManager.ts b/packages/core/src/SceneManager.ts index 984850fed4..1fa51ba265 100644 --- a/packages/core/src/SceneManager.ts +++ b/packages/core/src/SceneManager.ts @@ -91,7 +91,18 @@ export class SceneManager { * @returns scene promise */ loadScene(url: string, destroyOldScene: boolean = true): AssetPromise { - const scenePromise = this.engine.resourceManager.load({ url, type: AssetType.Scene }); + const resourceManager = this.engine.resourceManager; + // Evict the Scene asset cache for managed scenes about to be destroyed, so a fresh Scene + // instance is created by the loader instead of returning the same instance we're about to + // destroy (self-destroy would leave the active scene in a zombie state). + if (destroyOldScene) { + const realPath = resourceManager._virtualPathResourceMap[url]?.path ?? url; + const cached = resourceManager.getFromCache(realPath); + if (cached && this._scenes.indexOf(cached) !== -1) { + resourceManager._deleteAsset(cached); + } + } + const scenePromise = resourceManager.load({ url, type: AssetType.Scene }); scenePromise.then((scene: Scene) => { if (destroyOldScene) { const scenes = this._scenes.getLoopArray(); From dcae1d78472ffb8e1ef25e1ecebca43f6e7da3dc Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Wed, 22 Apr 2026 21:28:56 +0800 Subject: [PATCH 052/130] fix: opt get sprites error --- packages/core/src/2d/atlas/SpriteAtlas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/2d/atlas/SpriteAtlas.ts b/packages/core/src/2d/atlas/SpriteAtlas.ts index 44adadbfdd..93828efe2e 100644 --- a/packages/core/src/2d/atlas/SpriteAtlas.ts +++ b/packages/core/src/2d/atlas/SpriteAtlas.ts @@ -45,7 +45,7 @@ export class SpriteAtlas extends ReferResource { sprite.name === name && outSprites.push(sprite); } } else { - console.warn("The name of the sprite you want to find is not exit in SpriteAtlas."); + console.warn("There is no sprite named " + name + " in the atlas."); } return outSprites; } From 14407d9dd2366d34b6e0eb35a109e9b5ecba8a81 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Wed, 22 Apr 2026 21:52:03 +0800 Subject: [PATCH 053/130] fix(audio): restore pending playback correctly --- packages/core/src/audio/AudioManager.ts | 86 +++++-- packages/core/src/audio/AudioSource.ts | 52 ++-- .../audio/AudioSourcePendingPlayback.test.ts | 232 ++++++++++++++++++ tests/vitest.config.ts | 9 + 4 files changed, 342 insertions(+), 37 deletions(-) create mode 100644 tests/src/core/audio/AudioSourcePendingPlayback.test.ts diff --git a/packages/core/src/audio/AudioManager.ts b/packages/core/src/audio/AudioManager.ts index ad93f5ed59..2885f42220 100644 --- a/packages/core/src/audio/AudioManager.ts +++ b/packages/core/src/audio/AudioManager.ts @@ -7,15 +7,15 @@ export class AudioManager { private static _context: AudioContext; private static _gainNode: GainNode; - private static _resumePromise: Promise = null; private static _needsUserGestureResume = false; + private static _pendingSources = new Set<{ _resumePendingPlayback(): void }>(); /** * Suspend the audio context. * @returns A promise that resolves when the audio context is suspended */ static suspend(): Promise { - return AudioManager._context.suspend(); + return AudioManager._context?.suspend() ?? Promise.resolve(); } /** @@ -24,14 +24,26 @@ export class AudioManager { * @returns A promise that resolves when the audio context is resumed */ static resume(): Promise { - return (AudioManager._resumePromise ??= AudioManager._context + const context = AudioManager._context; + if (!context || context.state === "running") { + return Promise.resolve(); + } + return context .resume() .then(() => { AudioManager._needsUserGestureResume = false; - }) - .finally(() => { - AudioManager._resumePromise = null; - })); + AudioManager._resumePendingSources(); + }); + } + + /** @internal */ + static _registerPendingSource(source: { _resumePendingPlayback(): void }): void { + AudioManager._pendingSources.add(source); + } + + /** @internal */ + static _unregisterPendingSource(source: { _resumePendingPlayback(): void }): void { + AudioManager._pendingSources.delete(source); } /** @@ -41,6 +53,7 @@ export class AudioManager { let context = AudioManager._context; if (!context) { AudioManager._context = context = new window.AudioContext(); + context.onstatechange = AudioManager._onContextStateChange; document.addEventListener("visibilitychange", AudioManager._onVisibilityChange); // iOS Safari requires user gesture to resume AudioContext document.addEventListener("touchstart", AudioManager._resumeAfterInterruption, { passive: true }); @@ -70,22 +83,65 @@ export class AudioManager { return AudioManager.getContext().state === "running"; } + private static _onContextStateChange(): void { + if (AudioManager._context?.state === "running") { + AudioManager._needsUserGestureResume = false; + AudioManager._resumePendingSources(); + } + } + + private static _resumePendingSources(): void { + if (!AudioManager._pendingSources.size || !AudioManager.isAudioContextRunning()) { + return; + } + + const pendingSources = Array.from(AudioManager._pendingSources); + AudioManager._pendingSources.clear(); + + for (let i = 0, n = pendingSources.length; i < n; i++) { + pendingSources[i]._resumePendingPlayback(); + } + } + private static _onVisibilityChange(): void { - if (!document.hidden && AudioManager._playingCount > 0 && !AudioManager.isAudioContextRunning()) { - // iOS WKWebView WebKit bug(Triggered in LingGuang App): AudioContext may be in a "zombie" state where - // state reports "suspended" but resume() alone won't restart audio rendering. - // Calling suspend() first forces a clean internal state reset before user gesture triggers resume. - // Related: https://bugs.webkit.org/show_bug.cgi?id=263627 - AudioManager.suspend(); - AudioManager._needsUserGestureResume = true; + const context = AudioManager._context; + if ( + document.hidden || + !context || + (AudioManager._playingCount === 0 && AudioManager._pendingSources.size === 0) || + context.state === "running" + ) { + return; } + + AudioManager.resume() + .then(() => { + if (AudioManager._context?.state !== "running") { + return AudioManager._prepareGestureResume(); + } + }) + .catch(() => { + return AudioManager._prepareGestureResume(); + }); } private static _resumeAfterInterruption(): void { - if (AudioManager._needsUserGestureResume) { + if (AudioManager._needsUserGestureResume || AudioManager._pendingSources.size > 0) { AudioManager.resume().catch((e) => { console.warn("Failed to resume AudioContext:", e); }); } } + + private static _prepareGestureResume(): Promise { + // iOS WKWebView WebKit bug(Triggered in LingGuang App): AudioContext may be in a "zombie" state where + // state reports "suspended" but resume() alone won't restart audio rendering. + // Calling suspend() first forces a clean internal state reset before user gesture triggers resume. + // Related: https://bugs.webkit.org/show_bug.cgi?id=263627 + return AudioManager.suspend() + .catch(() => {}) + .then(() => { + AudioManager._needsUserGestureResume = true; + }); + } } diff --git a/packages/core/src/audio/AudioSource.ts b/packages/core/src/audio/AudioSource.ts index 29a3ed8bc1..3372f53e7d 100644 --- a/packages/core/src/audio/AudioSource.ts +++ b/packages/core/src/audio/AudioSource.ts @@ -159,27 +159,11 @@ export class AudioSource extends Component { if (AudioManager.isAudioContextRunning()) { this._startPlayback(); } else { - // iOS Safari requires resume() to be called within the same user gesture callback that triggers playback. - // Document-level events won't work - must call resume() directly here in play(). this._pendingPlay = true; - AudioManager.resume().then( - () => { - // Check if cancelled by stop()/pause() - if (!this._pendingPlay) { - return; - } - this._pendingPlay = false; - // Check if still valid to play after async resume - if (this._destroyed || !this.enabled || !this._clip) { - return; - } - this._startPlayback(); - }, - (e) => { - this._pendingPlay = false; - console.warn("Failed to resume AudioContext:", e); - } - ); + AudioManager._registerPendingSource(this); + AudioManager.resume().catch((e) => { + console.warn("Failed to resume AudioContext:", e); + }); } } @@ -187,7 +171,7 @@ export class AudioSource extends Component { * Stops playing the clip. */ stop(): void { - this._pendingPlay = false; + this._cancelPendingPlayback(); if (this._isPlaying) { this._clearSourceNode(); @@ -203,7 +187,7 @@ export class AudioSource extends Component { * Pauses playing the clip. */ pause(): void { - this._pendingPlay = false; + this._cancelPendingPlayback(); if (this._isPlaying) { this._clearSourceNode(); @@ -250,6 +234,21 @@ export class AudioSource extends Component { this.stop(); } + /** @internal */ + _resumePendingPlayback(): void { + if (!this._pendingPlay) { + return; + } + + this._pendingPlay = false; + + if (this._destroyed || !this.enabled || !this._clip?._getAudioSource()) { + return; + } + + this._startPlayback(); + } + private _startPlayback(): void { const startTime = this._pausedTime > 0 ? this._pausedTime - this._playTime : 0; this._initSourceNode(startTime); @@ -280,4 +279,13 @@ export class AudioSource extends Component { this._sourceNode.onended = null; this._sourceNode = null; } + + private _cancelPendingPlayback(): void { + if (!this._pendingPlay) { + return; + } + + this._pendingPlay = false; + AudioManager._unregisterPendingSource(this); + } } diff --git a/tests/src/core/audio/AudioSourcePendingPlayback.test.ts b/tests/src/core/audio/AudioSourcePendingPlayback.test.ts new file mode 100644 index 0000000000..a1e900332b --- /dev/null +++ b/tests/src/core/audio/AudioSourcePendingPlayback.test.ts @@ -0,0 +1,232 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { AudioManager, AudioSource } from "@galacean/engine-core"; + +class MockGainNode { + gain = { + setValueAtTime: vi.fn() + }; + + connect = vi.fn(); +} + +class MockBufferSourceNode { + buffer: unknown = null; + loop = false; + onended: (() => void) | null = null; + playbackRate = { + value: 1 + }; + + connect = vi.fn(); + disconnect = vi.fn(); + start = vi.fn(); + stop = vi.fn(); +} + +class MockAudioContext { + static shouldResumeSucceed = true; + static resumeResultQueue: Array | Error> | null = null; + + currentTime = 0; + destination = {}; + onstatechange: (() => void) | null = null; + state: AudioContextState = "suspended"; + + createBufferSource(): AudioBufferSourceNode { + return new MockBufferSourceNode() as unknown as AudioBufferSourceNode; + } + + createGain(): GainNode { + return new MockGainNode() as unknown as GainNode; + } + + resume(): Promise { + const queuedResult = MockAudioContext.resumeResultQueue?.shift(); + if (queuedResult instanceof Promise) { + return queuedResult; + } + if (queuedResult instanceof Error) { + return Promise.reject(queuedResult); + } + if (!MockAudioContext.shouldResumeSucceed) { + return Promise.reject(new Error("autoplay blocked")); + } + this.state = "running"; + this.onstatechange?.(); + return Promise.resolve(); + } + + suspend(): Promise { + this.state = "suspended"; + this.onstatechange?.(); + return Promise.resolve(); + } +} + +async function flushAsync(): Promise { + await new Promise((resolve) => setTimeout(resolve, 0)); +} + +function createAudioSource(): AudioSource { + const audioSource = new AudioSource({ + _isActiveInHierarchy: true, + _isActiveInScene: true, + _removeComponent() {}, + engine: {} + } as any); + + audioSource.clip = { + _addReferCount() {}, + _getAudioSource() { + return {}; + } + } as any; + + return audioSource; +} + +describe("AudioSource pending playback", () => { + beforeEach(() => { + (window as any).AudioContext = MockAudioContext; + (AudioManager as any)._context = null; + (AudioManager as any)._gainNode = null; + (AudioManager as any)._needsUserGestureResume = false; + (AudioManager as any)._pendingSources = new Set(); + MockAudioContext.shouldResumeSucceed = true; + MockAudioContext.resumeResultQueue = null; + AudioManager._playingCount = 0; + }); + + afterEach(() => { + vi.restoreAllMocks(); + document.replaceChildren(); + }); + + it("replays pending playback on the next user gesture after autoplay blocking", async () => { + const audioSource = createAudioSource(); + + vi.spyOn(console, "warn").mockImplementation(() => {}); + MockAudioContext.shouldResumeSucceed = false; + + audioSource.play(); + await flushAsync(); + + expect((audioSource as any)._pendingPlay).to.be.true; + expect((AudioManager as any)._pendingSources.size).to.equal(1); + expect((AudioManager as any)._needsUserGestureResume).to.be.false; + expect(audioSource.isPlaying).to.be.false; + + MockAudioContext.shouldResumeSucceed = true; + document.dispatchEvent(new Event("click")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.true; + expect((audioSource as any)._pendingPlay).to.be.false; + expect((AudioManager as any)._pendingSources.size).to.equal(0); + expect((AudioManager as any)._needsUserGestureResume).to.be.false; + }); + + it("cancels pending playback before the unlocking gesture arrives", async () => { + const audioSource = createAudioSource(); + + vi.spyOn(console, "warn").mockImplementation(() => {}); + MockAudioContext.shouldResumeSucceed = false; + + audioSource.play(); + await flushAsync(); + + audioSource.stop(); + expect((audioSource as any)._pendingPlay).to.be.false; + expect((AudioManager as any)._pendingSources.size).to.equal(0); + + MockAudioContext.shouldResumeSucceed = true; + document.dispatchEvent(new Event("click")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.false; + expect((audioSource as any)._pendingPlay).to.be.false; + }); + + it("keeps resume a no-op until a context already exists", async () => { + expect((AudioManager as any)._context).to.be.null; + + await AudioManager.resume(); + + expect((AudioManager as any)._context).to.be.null; + }); + + it("resumes automatically when returning to the foreground with active audio", async () => { + createAudioSource(); + const context = (AudioManager as any)._context as MockAudioContext; + + vi.spyOn(document, "hidden", "get").mockReturnValue(false); + const resumeSpy = vi.spyOn(context, "resume"); + const suspendSpy = vi.spyOn(AudioManager, "suspend"); + + context.state = "suspended"; + AudioManager._playingCount = 1; + + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + expect(resumeSpy).toHaveBeenCalledTimes(1); + expect(suspendSpy).not.toHaveBeenCalled(); + expect(context.state).to.equal("running"); + expect((AudioManager as any)._needsUserGestureResume).to.be.false; + }); + + it("falls back to gesture recovery when foreground auto-resume fails", async () => { + createAudioSource(); + const context = (AudioManager as any)._context as MockAudioContext; + + vi.spyOn(document, "hidden", "get").mockReturnValue(false); + const resumeSpy = vi.spyOn(context, "resume"); + const suspendSpy = vi.spyOn(AudioManager, "suspend"); + + MockAudioContext.shouldResumeSucceed = false; + context.state = "suspended"; + AudioManager._playingCount = 1; + + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + expect(resumeSpy).toHaveBeenCalledTimes(1); + expect(suspendSpy).toHaveBeenCalledTimes(1); + expect((AudioManager as any)._needsUserGestureResume).to.be.true; + + MockAudioContext.shouldResumeSucceed = true; + document.dispatchEvent(new Event("click")); + await flushAsync(); + + expect(resumeSpy).toHaveBeenCalledTimes(2); + expect(context.state).to.equal("running"); + expect((AudioManager as any)._needsUserGestureResume).to.be.false; + }); + + it("retries context.resume inside a later user gesture even if an earlier resume is still pending", async () => { + createAudioSource(); + const context = (AudioManager as any)._context as MockAudioContext; + const firstResume = new Promise(() => {}); + + MockAudioContext.resumeResultQueue = [firstResume]; + const resumeSpy = vi.spyOn(context, "resume"); + + AudioManager.resume().catch(() => {}); + await flushAsync(); + + expect(resumeSpy).toHaveBeenCalledTimes(1); + + MockAudioContext.resumeResultQueue = [Promise.resolve().then(() => { + context.state = "running"; + context.onstatechange?.(); + })]; + (AudioManager as any)._needsUserGestureResume = true; + + document.dispatchEvent(new Event("click")); + await flushAsync(); + + expect(resumeSpy).toHaveBeenCalledTimes(2); + expect(context.state).to.equal("running"); + expect((AudioManager as any)._needsUserGestureResume).to.be.false; + }); +}); diff --git a/tests/vitest.config.ts b/tests/vitest.config.ts index 91a68ffbca..e11fe737fa 100644 --- a/tests/vitest.config.ts +++ b/tests/vitest.config.ts @@ -1,9 +1,18 @@ +import glsl from "../rollup-plugin-glsl"; import { defineProject } from "vitest/config"; export default defineProject({ server: { port: 51204 }, + plugins: [ + glsl({ + include: [/\.(glsl|gs)$/] + }) + ], + resolve: { + mainFields: ["debug", "module", "main"] + }, optimizeDeps: { exclude: [ "@galacean/engine", From 27fbf5f5f612dff05578afcac24cd9719340881c Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Wed, 22 Apr 2026 21:56:02 +0800 Subject: [PATCH 054/130] chore: release v0.0.0-experimental-2.0-game.3 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index b361eb29d9..8f47eecf09 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index d347072917..055f81b4cb 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index d9b8179e4d..b65b3c5362 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index cc09093273..569379856a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index b0d8d42a37..86cf2f38cc 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index 9ec0252bc6..023532aa4a 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index c3755a9b32..b8d953a7b5 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 7cc62567d8..c84c6e2f83 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 33b9348145..2bc3c9f369 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 8c7d1d9666..3c425b7a68 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 7623f5fe55..8a8bb72973 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 5b66d09c4f..d76645f898 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index c11971cb88..41b4e5bdac 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index d0ee79e211..6d339a9558 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 498cd4fbce..6abe8ecd25 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 888db89501..23ccd02ccb 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index c43be60498..c2c5618235 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.1", + "version": "0.0.0-experimental-2.0-game.3", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From dd02420acc4696834e0e5b1ed41685fbfe5be337 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 23 Apr 2026 17:23:35 +0800 Subject: [PATCH 055/130] =?UTF-8?q?feat(core):=20GPU=20Instancing=20?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=90=88=E6=89=B9=EF=BC=88cherry-pick=20from?= =?UTF-8?q?=20galacean/engine#2957=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 从 PR https://github.com/galacean/engine/pull/2957 (feat/gpu-instancing) cherry-pick 69 个 commit 到 fix/shaderlab 分支,压缩为单次提交。 ## 核心改动 ### 新增文件 - `InstanceBuffer.ts` — UBO-based 实例数据管理,将多个 renderer 的 worldMatrix/shaderData 打包到一个 UBO 中 - `VertexMergeBatcher.ts` — 替代旧 BatchUtils,统一 2D/3D 合批入口 - `ShaderBlockProperty.ts` — UBO block 属性描述 - `ShaderProgramMap.ts` — 替代旧 ShaderProgramPool,支持 instancing layout 缓存 - `ConstantBufferBindingPoint.ts` — UBO binding point 枚举 ### 修改文件 - `MeshRenderer._canBatch/_batch` — 同 mesh+material+macros 自动合批判定 - `SkinnedMeshRenderer` — 标记不可 GPU instance - `RenderQueue` — 按 material/primitive 排序(替代按距离排序), 渲染时检测 instanced batch 并通过 InstanceBuffer 一次 draw - `RenderElement` — 扁平化(移除 SubRenderElement),支持 instancedRenderers 列表 - `ShaderFactory` — UBO 布局计算、instancing GLSL 注入(RENDERER_GPU_INSTANCE macro) - `ShaderPass` — 编译时检测 GPU instance macro,注入 UBO 声明 - `ShaderProgram` — 存储 instanceLayout - `Renderer` — 移除 batched 相关字段 ### 删除文件 - `BatchUtils.ts` → 替换为 `VertexMergeBatcher.ts` - `SubRenderElement.ts` → 合并到 `RenderElement.ts` - `ShaderProgramPool.ts` → 替换为 `ShaderProgramMap.ts` ## cherry-pick 冲突解决记录 fix/shaderlab 分支和 PR 基线 (dev/2.0) 的差异主要在以下文件: 1. **ShaderPass.ts** — fix/shaderlab 使用 `Shader._shaderLab._parseMacros` 处理 ShaderLab 宏,而 PR 使用 `ShaderMacroProcessor.evaluate`(fix/shaderlab 上不存在)。解决方式:保留 fix/shaderlab 的宏处理,加入 PR 的 instancing UBO 注入逻辑。 2. **Transform.glsl** — fix/shaderlab 已有 `camera_VPMat` 声明,PR 也添加了。 解决方式:合并两边声明。 3. **UIRenderer.ts** — PR 将 `BatchUtils` 重命名为 `VertexMergeBatcher`, `batchFor2D` 重命名为 `batch`。fix/shaderlab 的 UI 包未同步。 解决方式:手动更新 UI 包的 import 和调用。 4. **GLSLIfdefResolver.ts** — PR 早期 commit 新增此文件,后续 commit 删除。 cherry-pick 后 ShaderPass.ts 残留了 import。解决方式:删除无用 import。 ## 验证结果 CarParking 游戏 DrawCall 从 905 降至 ~80(同 mesh+material 的座椅、轮子等 自动合批)。 --- .../examples/shaderlab-05-advance.mdx | 1 - docs/en/graphics/material/shaderAPI.mdx | 1 - docs/en/graphics/material/variables.mdx | 1 - .../examples/shaderlab-05-advance.mdx | 1 - docs/zh/graphics/material/shaderAPI.mdx | 1 - docs/zh/graphics/material/variables.mdx | 1 - e2e/case/gpu-instancing-auto-batch.ts | 68 ++ e2e/case/gpu-instancing-custom-data.ts | 100 +++ e2e/config.ts | 518 ----------- .../Animator_animator-crossfade.jpg | 4 +- .../originImage/Animator_animator-play.jpg | 4 +- .../Camera_camera-opaque-texture.jpg | 4 +- ...PUInstancing_gpu-instancing-auto-batch.jpg | 3 + ...UInstancing_gpu-instancing-custom-data.jpg | 3 + .../originImage/Physics_physx-collision.jpg | 3 - .../originImage/Physics_physx-customUrl.jpg | 3 - .../Physics_physx-mesh-collider-data.jpg | 3 - examples/package.json | 1 + examples/src/gpu-instancing-auto-batch.ts | 194 +++++ examples/src/gpu-instancing-custom-data.ts | 151 ++++ package.json | 13 +- packages/core/src/2d/sprite/SpriteMask.ts | 30 +- packages/core/src/2d/sprite/SpriteRenderer.ts | 23 +- packages/core/src/2d/text/TextRenderer.ts | 36 +- packages/core/src/Engine.ts | 40 +- .../src/RenderPipeline/BasicRenderPipeline.ts | 78 +- .../core/src/RenderPipeline/BatchUtils.ts | 72 -- .../core/src/RenderPipeline/BatcherManager.ts | 59 +- .../core/src/RenderPipeline/InstanceBuffer.ts | 86 ++ .../core/src/RenderPipeline/RenderElement.ts | 53 +- .../core/src/RenderPipeline/RenderQueue.ts | 116 ++- .../src/RenderPipeline/SubRenderElement.ts | 51 -- .../src/RenderPipeline/VertexMergeBatcher.ts | 67 ++ packages/core/src/RenderPipeline/index.ts | 2 +- packages/core/src/Renderer.ts | 89 +- .../src/graphic/TransformFeedbackShader.ts | 9 +- .../core/src/graphic/enums/BufferBindFlag.ts | 4 +- packages/core/src/mesh/MeshRenderer.ts | 43 +- packages/core/src/mesh/SkinnedMeshRenderer.ts | 8 + .../core/src/particle/ParticleRenderer.ts | 11 +- packages/core/src/shader/Shader.ts | 12 +- .../core/src/shader/ShaderBlockProperty.ts | 28 + .../core/src/shader/ShaderMacroCollection.ts | 14 + packages/core/src/shader/ShaderPass.ts | 84 +- packages/core/src/shader/ShaderProgram.ts | 48 ++ ...aderProgramPool.ts => ShaderProgramMap.ts} | 70 +- packages/core/src/shader/ShaderUniform.ts | 32 + .../enums/ConstantBufferBindingPoint.ts | 7 + packages/core/src/shaderlib/ShaderFactory.ts | 345 +++++++- .../src/shaderlib/extra/depthOnly.vs.glsl | 2 - .../src/shaderlib/extra/shadow-map.vs.glsl | 3 +- .../core/src/shaderlib/extra/skybox.vs.glsl | 2 - packages/core/src/shaderlib/normal_vert.glsl | 5 +- .../core/src/shaderlib/transform_declare.glsl | 5 +- packages/core/src/trail/TrailRenderer.ts | 36 +- packages/core/src/ui/IUICanvas.ts | 2 +- packages/core/src/ui/UIUtils.ts | 5 +- .../IHardwareRenderer.ts | 2 + packages/rhi-webgl/src/GLBuffer.ts | 13 +- packages/rhi-webgl/src/WebGLGraphicDevice.ts | 21 + packages/shader/src/shaders/Transform.glsl | 14 +- .../src/shaders/shadingPBR/VertexPBR.glsl | 7 +- packages/ui/src/component/UICanvas.ts | 7 +- packages/ui/src/component/UIRenderer.ts | 14 +- packages/ui/src/component/advanced/Image.ts | 13 +- packages/ui/src/component/advanced/Text.ts | 22 +- pnpm-lock.yaml | 812 +++++++++--------- tests/src/shader-lab/ShaderValidate.ts | 2 +- 68 files changed, 2066 insertions(+), 1516 deletions(-) create mode 100644 e2e/case/gpu-instancing-auto-batch.ts create mode 100644 e2e/case/gpu-instancing-custom-data.ts delete mode 100644 e2e/config.ts create mode 100644 e2e/fixtures/originImage/GPUInstancing_gpu-instancing-auto-batch.jpg create mode 100644 e2e/fixtures/originImage/GPUInstancing_gpu-instancing-custom-data.jpg delete mode 100644 e2e/fixtures/originImage/Physics_physx-collision.jpg delete mode 100644 e2e/fixtures/originImage/Physics_physx-customUrl.jpg delete mode 100644 e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg create mode 100644 examples/src/gpu-instancing-auto-batch.ts create mode 100644 examples/src/gpu-instancing-custom-data.ts delete mode 100644 packages/core/src/RenderPipeline/BatchUtils.ts create mode 100644 packages/core/src/RenderPipeline/InstanceBuffer.ts delete mode 100644 packages/core/src/RenderPipeline/SubRenderElement.ts create mode 100644 packages/core/src/RenderPipeline/VertexMergeBatcher.ts create mode 100644 packages/core/src/shader/ShaderBlockProperty.ts rename packages/core/src/shader/{ShaderProgramPool.ts => ShaderProgramMap.ts} (56%) create mode 100644 packages/core/src/shader/enums/ConstantBufferBindingPoint.ts diff --git a/docs/en/graphics/material/examples/shaderlab-05-advance.mdx b/docs/en/graphics/material/examples/shaderlab-05-advance.mdx index 4a21ce1c25..4e8c257ea1 100644 --- a/docs/en/graphics/material/examples/shaderlab-05-advance.mdx +++ b/docs/en/graphics/material/examples/shaderlab-05-advance.mdx @@ -190,7 +190,6 @@ gl_FragColor = vec4(lighting, 1.0); The engine provides a rich set of built-in variables that can be used directly, such as transformation matrix variables: ```glsl -mat4 renderer_LocalMat; // Local transformation matrix mat4 renderer_ModelMat; // Model matrix mat4 renderer_MVMat; // Model-view matrix mat4 renderer_MVPMat; // MVP matrix diff --git a/docs/en/graphics/material/shaderAPI.mdx b/docs/en/graphics/material/shaderAPI.mdx index f546447e19..63e646c37e 100644 --- a/docs/en/graphics/material/shaderAPI.mdx +++ b/docs/en/graphics/material/shaderAPI.mdx @@ -27,7 +27,6 @@ vec4 fog(vec4 color, vec3 positionVS); Provides system variables for model space, view space, world space, and camera coordinates: ```glsl -mat4 renderer_LocalMat; mat4 renderer_ModelMat; mat4 camera_ViewMat; mat4 camera_ProjMat; diff --git a/docs/en/graphics/material/variables.mdx b/docs/en/graphics/material/variables.mdx index f64f02b3a3..779f86130b 100644 --- a/docs/en/graphics/material/variables.mdx +++ b/docs/en/graphics/material/variables.mdx @@ -25,7 +25,6 @@ Below are the engine's built-in variables for reference when writing Shaders: | Name | Type | Description | | :---------------- | :--- | :--------------------------- | -| renderer_LocalMat | mat4 | Model local coordinate matrix | | renderer_ModelMat | mat4 | Model world coordinate matrix | | renderer_MVMat | mat4 | Model view matrix | | renderer_MVPMat | mat4 | Model view projection matrix | diff --git a/docs/zh/graphics/material/examples/shaderlab-05-advance.mdx b/docs/zh/graphics/material/examples/shaderlab-05-advance.mdx index c2d4d779dc..a9490581ac 100644 --- a/docs/zh/graphics/material/examples/shaderlab-05-advance.mdx +++ b/docs/zh/graphics/material/examples/shaderlab-05-advance.mdx @@ -193,7 +193,6 @@ void frag() { 引擎提供了丰富的内置变量,直接声明使用即可,比如变换矩阵相关变量: ```glsl -mat4 renderer_LocalMat; // 本地变换矩阵 mat4 renderer_ModelMat; // 模型矩阵 mat4 renderer_MVMat; // 模型视图矩阵 mat4 renderer_MVPMat; // MVP矩阵 diff --git a/docs/zh/graphics/material/shaderAPI.mdx b/docs/zh/graphics/material/shaderAPI.mdx index d6fcd4ac25..0c0879054b 100644 --- a/docs/zh/graphics/material/shaderAPI.mdx +++ b/docs/zh/graphics/material/shaderAPI.mdx @@ -29,7 +29,6 @@ vec4 fog(vec4 color, vec3 positionVS); 提供了模型空间、视图空间、世界空间、相机坐标等[系统变量](/docs/graphics/material/variables/): ```glsl -mat4 renderer_LocalMat; mat4 renderer_ModelMat; mat4 camera_ViewMat; mat4 camera_ProjMat; diff --git a/docs/zh/graphics/material/variables.mdx b/docs/zh/graphics/material/variables.mdx index 6059e0d0e1..48deab814b 100644 --- a/docs/zh/graphics/material/variables.mdx +++ b/docs/zh/graphics/material/variables.mdx @@ -25,7 +25,6 @@ Shader 代码中会经常用到内置变量,一种是**逐顶点**的 `attribu | 名字 | 类型 | 解释 | | :----------------- | :--- | ------------------ | -| renderer_LocalMat | mat4 | 模型本地坐标系矩阵 | | renderer_ModelMat | mat4 | 模型世界坐标系矩阵 | | renderer_MVMat | mat4 | 模型视口矩阵 | | renderer_MVPMat | mat4 | 模型视口投影矩阵 | diff --git a/e2e/case/gpu-instancing-auto-batch.ts b/e2e/case/gpu-instancing-auto-batch.ts new file mode 100644 index 0000000000..e9f80e35ab --- /dev/null +++ b/e2e/case/gpu-instancing-auto-batch.ts @@ -0,0 +1,68 @@ +/** + * @title GPU Instancing Auto Batch + * @category Mesh + */ +import { + AmbientLight, + AssetType, + Camera, + Color, + DirectLight, + GLTFResource, + Logger, + Vector3, + WebGLEngine +} from "@galacean/engine"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +Logger.enable(); +WebGLEngine.create({ canvas: "canvas" }).then(async (engine) => { + engine.canvas.resizeByClientSize(2); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity("Root"); + + // Camera + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.transform.setPosition(0, 10, 80); + cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); + const camera = cameraEntity.addComponent(Camera); + camera.farClipPlane = 300; + + // Light + const lightEntity = rootEntity.createChild("Light"); + lightEntity.transform.setRotation(-45, -45, 0); + lightEntity.addComponent(DirectLight).color = new Color(1, 1, 1, 1); + + // Load Duck model and ambient light + const [glTF, ambientLight] = await Promise.all([ + engine.resourceManager.load({ + url: "https://gw.alipayobjects.com/os/bmw-prod/6cb8f543-285c-491a-8cfd-57a1160dc9ab.glb", + type: AssetType.GLTF + }), + engine.resourceManager.load({ + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight", + type: AssetType.AmbientLight + }) + ]); + scene.ambientLight = ambientLight; + + // Clone ducks with fixed seed positions for deterministic output + const count = 500; + const spread = 50; + for (let i = 0; i < count; i++) { + const duck = glTF.instantiateSceneRoot(); + // Use deterministic positions based on index + const t = i / count; + duck.transform.setPosition( + Math.sin(t * 137.5) * spread * 0.5, + Math.cos(t * 97.3) * spread * 0.5, + Math.sin(t * 59.1) * spread * 0.5 + ); + duck.transform.setRotation(t * 360, t * 720, t * 1080); + rootEntity.addChild(duck); + } + + updateForE2E(engine); + initScreenshot(engine, camera); +}); diff --git a/e2e/case/gpu-instancing-custom-data.ts b/e2e/case/gpu-instancing-custom-data.ts new file mode 100644 index 0000000000..f64b01a8f7 --- /dev/null +++ b/e2e/case/gpu-instancing-custom-data.ts @@ -0,0 +1,100 @@ +/** + * @title GPU Instancing Custom Data + * @category Mesh + */ +import { + Camera, + Color, + DirectLight, + Logger, + Material, + MeshRenderer, + PrimitiveMesh, + Shader, + ShaderProperty, + Vector3, + Vector4, + WebGLEngine +} from "@galacean/engine"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +Logger.enable(); + +// Custom shader: uses renderer_CustomColor (per-instance) for fragment output +Shader.create( + "CustomInstanceShader", + ` + #include + attribute vec3 POSITION; + attribute vec3 NORMAL; + + varying vec3 v_normal; + + void main() { + gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); + v_normal = normalize((renderer_NormalMat * vec4(NORMAL, 0.0)).xyz); + } + `, + ` + uniform vec4 renderer_CustomColor; + + varying vec3 v_normal; + + void main() { + vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); + float NdotL = max(dot(v_normal, lightDir), 0.2); + gl_FragColor = vec4(renderer_CustomColor.rgb * NdotL, 1.0); + } + ` +); + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(2); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity("Root"); + + // Camera + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.transform.setPosition(0, 10, 80); + cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); + const camera = cameraEntity.addComponent(Camera); + camera.farClipPlane = 300; + + // Light + const lightEntity = rootEntity.createChild("Light"); + lightEntity.transform.setRotation(-45, -45, 0); + lightEntity.addComponent(DirectLight).color = new Color(1, 1, 1, 1); + + // Shared mesh and material + const mesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1); + const material = new Material(engine, Shader.find("CustomInstanceShader")); + const customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); + + // Create cubes with deterministic positions and colors + const count = 500; + const spread = 50; + for (let i = 0; i < count; i++) { + const entity = rootEntity.createChild("Cube" + i); + const t = i / count; + entity.transform.setPosition( + Math.sin(t * 137.5) * spread * 0.5, + Math.cos(t * 97.3) * spread * 0.5, + Math.sin(t * 59.1) * spread * 0.5 + ); + entity.transform.setRotation(t * 360, t * 720, t * 1080); + + const renderer = entity.addComponent(MeshRenderer); + renderer.mesh = mesh; + renderer.setMaterial(material); + + // Deterministic colors based on index + renderer.shaderData.setVector4( + customColorProperty, + new Vector4(Math.sin(t * 6.28) * 0.5 + 0.5, Math.cos(t * 4.71) * 0.5 + 0.5, Math.sin(t * 3.14) * 0.5 + 0.5, 1.0) + ); + } + + updateForE2E(engine); + initScreenshot(engine, camera); +}); diff --git a/e2e/config.ts b/e2e/config.ts deleted file mode 100644 index e5a9b621cf..0000000000 --- a/e2e/config.ts +++ /dev/null @@ -1,518 +0,0 @@ -export const E2E_CONFIG = { - Animator: { - additive: { - category: "Animator", - caseFileName: "animator-additive", - threshold: 0, - diffPercentage: 0 - }, - blendShape: { - category: "Animator", - caseFileName: "animator-blendShape", - threshold: 0, - diffPercentage: 0.01 - }, - blendShapeQuantization: { - category: "Animator", - caseFileName: "animator-blendShape-quantization", - threshold: 0, - diffPercentage: 0.05 - }, - crossfade: { - category: "Animator", - caseFileName: "animator-crossfade", - threshold: 0, - diffPercentage: 0 - }, - customAnimationClip: { - category: "Animator", - caseFileName: "animator-customAnimationClip", - threshold: 0, - diffPercentage: 0 - }, - customBlendShape: { - category: "Animator", - caseFileName: "animator-customBlendShape", - threshold: 0, - diffPercentage: 0 - }, - multiSubMeshBlendShape: { - category: "Animator", - caseFileName: "animator-multiSubMeshBlendShape", - threshold: 0, - diffPercentage: 0 - }, - event: { - category: "Animator", - caseFileName: "animator-event", - threshold: 0, - diffPercentage: 0.0036 - }, - play: { - category: "Animator", - caseFileName: "animator-play", - threshold: 0, - diffPercentage: 0 - }, - playBackWards: { - category: "Animator", - caseFileName: "animator-play-backwards", - threshold: 0, - diffPercentage: 0 - }, - playBeforeActive: { - category: "Animator", - caseFileName: "animator-play-beforeActive", - threshold: 0, - diffPercentage: 0 - }, - reuse: { - category: "Animator", - caseFileName: "animator-reuse", - threshold: 0, - diffPercentage: 0 - }, - stateMachineScript: { - category: "Animator", - caseFileName: "animator-stateMachineScript", - threshold: 0, - diffPercentage: 0.0036 - }, - stateMachine: { - category: "Animator", - caseFileName: "animator-stateMachine", - threshold: 0, - diffPercentage: 0 - } - }, - GLTF: { - meshopt: { - category: "GLTF", - caseFileName: "gltf-meshopt", - threshold: 0, - diffPercentage: 0.059 - }, - blendShape: { - category: "GLTF", - caseFileName: "gltf-blendshape", - threshold: 0, - diffPercentage: 0.05 - } - }, - - Material: { - blendMode: { - category: "Material", - caseFileName: "material-blendMode", - threshold: 0, - diffPercentage: 0.02 - }, - "blinn-phong": { - category: "Material", - caseFileName: "material-blinn-phong", - threshold: 0, - diffPercentage: 0.36 - }, - "pbr-clearcoat": { - category: "Material", - caseFileName: "material-pbr-clearcoat", - threshold: 0, - diffPercentage: 0.12 - }, - "white-furnace": { - category: "Material", - caseFileName: "material-white-furnace", - threshold: 0, - diffPercentage: 0.0 - }, - "pbr-specular": { - category: "Material", - caseFileName: "material-pbr-specular", - threshold: 0, - diffPercentage: 0.055 - }, - pbr: { - category: "Material", - caseFileName: "material-pbr", - threshold: 0, - diffPercentage: 0.0044 - }, - shaderLab: { - category: "Material", - caseFileName: "material-shaderLab", - threshold: 0, - diffPercentage: 0 - }, - shaderLabMRT: { - category: "Material", - caseFileName: "shaderLab-mrt", - threshold: 0, - diffPercentage: 0 - }, - shaderReplacement: { - category: "Material", - caseFileName: "material-shaderReplacement", - threshold: 0, - diffPercentage: 0.049 - }, - unlit: { - category: "Material", - caseFileName: "material-unlit", - threshold: 0, - diffPercentage: 0.033 - }, - "shaderLab-renderState": { - category: "Material", - caseFileName: "shaderLab-renderState", - threshold: 0, - diffPercentage: 0 - }, - LUT: { - category: "Material", - caseFileName: "material-LUT", - threshold: 0, - diffPercentage: 0 - } - }, - Texture: { - sRGB: { - category: "Texture", - caseFileName: "texture-sRGB-KTX2", - threshold: 0, - diffPercentage: 0.072 - }, - R8G8: { - category: "Texture", - caseFileName: "texture-R8G8", - threshold: 0, - diffPercentage: 0 - }, - KTX2HDR: { - category: "Texture", - caseFileName: "texture-hdr-ktx2", - threshold: 0, - diffPercentage: 0.015 - }, - HDR: { - category: "Texture", - caseFileName: "texture-hdr", - threshold: 0, - diffPercentage: 0.0512 - } - }, - Shadow: { - basic: { - category: "Shadow", - caseFileName: "shadow-basic", - threshold: 0, - diffPercentage: 0.008 - }, - transparent: { - category: "Shadow", - caseFileName: "shadow-transparent", - threshold: 0, - diffPercentage: 0.0552 - } - }, - Primitive: { - capsule: { - category: "Primitive", - caseFileName: "primitive-capsule", - threshold: 0, - diffPercentage: 0.0016 - }, - cone: { - category: "Primitive", - caseFileName: "primitive-cone", - threshold: 0, - diffPercentage: 0.0054 - }, - cuboid: { - category: "Primitive", - caseFileName: "primitive-cuboid", - threshold: 0, - diffPercentage: 0.0016 - }, - cylinder: { - category: "Primitive", - caseFileName: "primitive-cylinder", - threshold: 0, - diffPercentage: 0.0036 - }, - plane: { - category: "Primitive", - caseFileName: "primitive-plane", - threshold: 0, - diffPercentage: 0.0016 - }, - sphere: { - category: "Primitive", - caseFileName: "primitive-sphere", - threshold: 0, - diffPercentage: 0.0058 - }, - torus: { - category: "Primitive", - caseFileName: "primitive-torus", - threshold: 0, - diffPercentage: 0 - } - }, - Camera: { - opaqueTexture: { - category: "Camera", - caseFileName: "camera-opaque-texture", - threshold: 0, - diffPercentage: 0 - }, - fxaa: { - category: "Camera", - caseFileName: "camera-fxaa", - threshold: 0, - diffPercentage: 0.161 - }, - ssao: { - category: "Camera", - caseFileName: "camera-ssao", - threshold: 0, - diffPercentage: 0.12 - } - }, - Physics: { - "physx-collision": { - category: "Physics", - caseFileName: "physx-collision", - threshold: 0, - diffPercentage: 0 - }, - "LitePhysics Collision Group": { - category: "Physics", - caseFileName: "litePhysics-collision-group", - threshold: 0, - diffPercentage: 0 - }, - "PhysXPhysics Collision Group": { - category: "Physics", - caseFileName: "physx-collision-group", - threshold: 0, - diffPercentage: 0 - }, - "PhysXPhysics Custom Url": { - category: "Physics", - caseFileName: "physx-customUrl", - threshold: 0, - diffPercentage: 0 - }, - "PhysX Mesh Collider": { - category: "Physics", - caseFileName: "physx-mesh-collider", - threshold: 0, - diffPercentage: 0.12094 - }, - "PhysX Mesh Collider Data": { - category: "Physics", - caseFileName: "physx-mesh-collider-data", - threshold: 0, - diffPercentage: 0.02 - }, - "PhysX Deferred Contact": { - category: "Physics", - caseFileName: "physx-deferred-contact", - threshold: 0, - diffPercentage: 0.0 - } - }, - Particle: { - particleDream: { - category: "Particle", - caseFileName: "particleRenderer-dream", - threshold: 0.005, - diffPercentage: 0.015 - }, - particleFire: { - category: "Particle", - caseFileName: "particleRenderer-fire", - threshold: 0, - diffPercentage: 0.0707 - }, - forceOverLifetime: { - category: "Particle", - caseFileName: "particleRenderer-force", - threshold: 0, - diffPercentage: 0.1630209 - }, - limitVelocityOverLifetime: { - category: "Particle", - caseFileName: "particleRenderer-limitVelocity", - threshold: 0, - diffPercentage: 0.0364 - }, - textureSheetAnimation: { - category: "Particle", - caseFileName: "particleRenderer-textureSheetAnimation", - threshold: 0, - diffPercentage: 0 - }, - particleShapeMesh: { - category: "Particle", - caseFileName: "particleRenderer-shape-mesh", - threshold: 0, - diffPercentage: 0.01698 - }, - particleEmissive: { - category: "Particle", - caseFileName: "particleRenderer-emissive", - threshold: 0, - diffPercentage: 0 - }, - particleEmitMeshNoShape: { - category: "Particle", - caseFileName: "particleRenderer-emit-mesh-no-shape", - threshold: 0, - diffPercentage: 0.00136 - }, - particleEmitMeshCone: { - category: "Particle", - caseFileName: "particleRenderer-emit-mesh-cone", - threshold: 0, - diffPercentage: 0.00219 - }, - particleEmitMeshConeScaleRotation: { - category: "Particle", - caseFileName: "particleRenderer-emit-mesh-cone-scale-rotation", - threshold: 0, - diffPercentage: 0.0031 - }, - particleEmitMeshConeScaleRotationWorld: { - category: "Particle", - caseFileName: "particleRenderer-emit-mesh-cone-scale-rotation-world", - threshold: 0, - diffPercentage: 0.00928 - }, - particleEmitMeshNoShapeWorld: { - category: "Particle", - caseFileName: "particleRenderer-emit-mesh-no-shape-world", - threshold: 0, - diffPercentage: 0.00146 - }, - particleEmitMeshConeScale3DRotation: { - category: "Particle", - caseFileName: "particleRenderer-emit-mesh-cone-scale-3d-rotation", - threshold: 0, - diffPercentage: 0.0184 - }, - particleEmitMeshConeScaleRotationLife: { - category: "Particle", - caseFileName: "particleRenderer-emit-mesh-cone-scale-rotation-life", - threshold: 0, - diffPercentage: 0.036459 - }, - particleEmitMeshConeScaleRotationLifeSeperate: { - category: "Particle", - caseFileName: "particleRenderer-emit-mesh-cone-scale-rotation-life-seperate", - threshold: 0, - diffPercentage: 0.0068 - }, - particleEmitMeshConeScale3DRotationLifeSeperate: { - category: "Particle", - caseFileName: "particleRenderer-emit-mesh-cone-scale-3d-rotation-life-seperate", - threshold: 0, - diffPercentage: 0.00782 - }, - particleEmitBillboardStretched: { - category: "Particle", - caseFileName: "particleRenderer-emit-billboard-stretched", - threshold: 0, - diffPercentage: 0.0 - }, - particleHorizontalBillboard: { - category: "Particle", - caseFileName: "particleRenderer-horizontal-billboard", - threshold: 0, - diffPercentage: 0.2162 - } - }, - PostProcess: { - HDRBloomACES: { - category: "PostProcess", - caseFileName: "postProcess-HDR-bloom-ACES", - threshold: 0, - diffPercentage: 0.148 - }, - HDRBloomNeutral: { - category: "PostProcess", - caseFileName: "postProcess-HDR-bloom-neutral", - threshold: 0, - diffPercentage: 0.072 - }, - LDRBloomNeutral: { - category: "PostProcess", - caseFileName: "postProcess-LDR-bloom-neutral", - threshold: 0, - diffPercentage: 0.097 - }, - customPass: { - category: "PostProcess", - caseFileName: "postProcess-customPass", - threshold: 0, - diffPercentage: 0.03 - } - }, - SpriteMask: { - CustomStencil: { - category: "SpriteMask", - caseFileName: "spriteMask-customStencil", - threshold: 0, - diffPercentage: 0.0024 - } - }, - Text: { - TypedText: { - category: "Text", - caseFileName: "text-typed", - threshold: 0.016, - diffPercentage: 0.00136 - }, - CharacterSpacing: { - category: "Text", - caseFileName: "text-character-spacing", - threshold: 0.0, - diffPercentage: 0.0 - } - }, - Trail: { - basic: { - category: "Trail", - caseFileName: "trailRenderer-basic", - threshold: 0, - diffPercentage: 0 - } - }, - Other: { - MultiSceneClear: { - category: "Advance", - caseFileName: "multi-scene-clear", - threshold: 0, - diffPercentage: 0 - }, - MultiSceneNoClear: { - category: "Advance", - caseFileName: "multi-scene-no-clear", - threshold: 0, - diffPercentage: 0 - }, - MultiCameraNoClear: { - category: "Advance", - caseFileName: "multi-camera-no-clear", - threshold: 0, - diffPercentage: 0 - }, - - CanvasTransparency: { - category: "Advance", - caseFileName: "canvas-transparency", - threshold: 0, - diffPercentage: 0.044 - } - } -}; diff --git a/e2e/fixtures/originImage/Animator_animator-crossfade.jpg b/e2e/fixtures/originImage/Animator_animator-crossfade.jpg index ab638de414..c71cd431c8 100644 --- a/e2e/fixtures/originImage/Animator_animator-crossfade.jpg +++ b/e2e/fixtures/originImage/Animator_animator-crossfade.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e18021c1e0a3b682259046e812eba199c907ffd9a634dda12379f1f0c3b8b43a -size 45710 +oid sha256:a1dfa2435f26222617b08ce2830ca28d8d84e083678dde1a9d945479c7b6d019 +size 45716 diff --git a/e2e/fixtures/originImage/Animator_animator-play.jpg b/e2e/fixtures/originImage/Animator_animator-play.jpg index 79170b806b..ddfb02c4e7 100644 --- a/e2e/fixtures/originImage/Animator_animator-play.jpg +++ b/e2e/fixtures/originImage/Animator_animator-play.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4910985a371b9022e73d2a7efbd71ca6754bcc7bf4c25edc2a41391f1bb1533b -size 46499 +oid sha256:82d8ba4776b6b27b923d0f1f0094d48d0d4c59e61e8666275690fec70c39e854 +size 46527 diff --git a/e2e/fixtures/originImage/Camera_camera-opaque-texture.jpg b/e2e/fixtures/originImage/Camera_camera-opaque-texture.jpg index bcc2d5f331..1d409ae544 100644 --- a/e2e/fixtures/originImage/Camera_camera-opaque-texture.jpg +++ b/e2e/fixtures/originImage/Camera_camera-opaque-texture.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdde03beb8ead802b61a14e8649102c18ccfe6b9e1e176d3b9f8bc0dfe7f9c76 -size 47500 +oid sha256:a2068be8d0b35c94ec1ddaed9da63494f1a93294ea6128e2036563df1947e854 +size 47482 diff --git a/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-auto-batch.jpg b/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-auto-batch.jpg new file mode 100644 index 0000000000..ecec018d8c --- /dev/null +++ b/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-auto-batch.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc2a26d0ad06c2dfa722484b2e914b43c4c6671c76ec31d18715dc4a89ec1d88 +size 590047 diff --git a/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-custom-data.jpg b/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-custom-data.jpg new file mode 100644 index 0000000000..79399c097c --- /dev/null +++ b/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-custom-data.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:baece2c4437803e1474699eb4e99214b38caa05393f747fe0f7e47bd6469a693 +size 427005 diff --git a/e2e/fixtures/originImage/Physics_physx-collision.jpg b/e2e/fixtures/originImage/Physics_physx-collision.jpg deleted file mode 100644 index b1e9becba9..0000000000 --- a/e2e/fixtures/originImage/Physics_physx-collision.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:55cd5d3035ef575743d7332317b571ea2403f9330c918a3c45e96dbbe853e164 -size 39366 diff --git a/e2e/fixtures/originImage/Physics_physx-customUrl.jpg b/e2e/fixtures/originImage/Physics_physx-customUrl.jpg deleted file mode 100644 index b1e9becba9..0000000000 --- a/e2e/fixtures/originImage/Physics_physx-customUrl.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:55cd5d3035ef575743d7332317b571ea2403f9330c918a3c45e96dbbe853e164 -size 39366 diff --git a/e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg b/e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg deleted file mode 100644 index 6bdbf7360d..0000000000 --- a/e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e62ca32193b97c4a47ce59666a4f7754f0f20486e055ac3998de2e07aa4d1272 -size 128943 diff --git a/examples/package.json b/examples/package.json index 055f81b4cb..3007ed5e9e 100644 --- a/examples/package.json +++ b/examples/package.json @@ -22,6 +22,7 @@ "@galacean/engine-shader": "workspace:*", "@galacean/engine-shaderlab": "workspace:*", "@galacean/engine-toolkit": "latest", + "@galacean/engine-toolkit-stats": "latest", "@galacean/engine-ui": "workspace:*" }, "devDependencies": { diff --git a/examples/src/gpu-instancing-auto-batch.ts b/examples/src/gpu-instancing-auto-batch.ts new file mode 100644 index 0000000000..d7aa1b9f80 --- /dev/null +++ b/examples/src/gpu-instancing-auto-batch.ts @@ -0,0 +1,194 @@ +/** + * @title GPU Instancing Auto Batch + * @category Mesh + * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*jjZMTrp-vU8AAAAAAAAAAAAADiR2AQ/original + */ +import { OrbitControl, Stats } from "@galacean/engine-toolkit"; +import { + AmbientLight, + AssetType, + Camera, + Color, + DirectLight, + GLTFResource, + Logger, + Material, + MeshRenderer, + PrimitiveMesh, + Script, + Shader, + ShaderProperty, + Vector3, + Vector4, + WebGLEngine, + WebGLMode +} from "@galacean/engine"; + +const _customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); + +class SpiralAnimate extends Script { + radius: number = 0; + radiusSpeed: number = 0; + theta: number = 0; + thetaSpeed: number = 0; + phi: number = 0; + phiSpeed: number = 0; + rotateSpeed: Vector3 = new Vector3(); + scaleBase: number = 1; + scaleFreq: number = 0; + colorPhase: number = 0; + colorSpeed: number = 0; + private _time: number = 0; + private _color: Vector4 = new Vector4(); + private _hasColor: boolean = false; + + onUpdate(deltaTime: number): void { + this._time += deltaTime; + const t = this._time; + const transform = this.entity.transform; + + // Spiral breathing motion + const r = this.radius * (0.6 + 0.4 * Math.sin(t * this.radiusSpeed)); + const theta = this.theta + t * this.thetaSpeed; + const phi = this.phi + t * this.phiSpeed; + + const sinTheta = Math.sin(theta); + transform.setPosition(r * sinTheta * Math.cos(phi), r * Math.cos(theta), r * sinTheta * Math.sin(phi)); + + // Rotation + const { rotateSpeed } = this; + transform.rotate(rotateSpeed.x * deltaTime, rotateSpeed.y * deltaTime, rotateSpeed.z * deltaTime); + + // Scale pulse + const s = this.scaleBase * (0.7 + 0.3 * Math.sin(t * this.scaleFreq)); + transform.setScale(s, s, s); + + // Color animation (cubes only) + if (this._hasColor) { + const ct = t * this.colorSpeed + this.colorPhase; + this._color.set( + 0.5 + 0.5 * Math.sin(ct), + 0.5 + 0.5 * Math.sin(ct + 2.094), + 0.5 + 0.5 * Math.sin(ct + 4.189), + 1.0 + ); + this.entity.getComponent(MeshRenderer).shaderData.setVector4(_customColorProperty, this._color); + } + } + + enableColor(): void { + this._hasColor = true; + } +} + +// Custom shader for cubes +Shader.create( + "CustomInstanceShader", + ` + #include + attribute vec3 POSITION; + attribute vec3 NORMAL; + + varying vec3 v_normal; + + void main() { + gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); + v_normal = normalize((renderer_NormalMat * vec4(NORMAL, 0.0)).xyz); + } + `, + ` + uniform vec4 renderer_CustomColor; + + varying vec3 v_normal; + + void main() { + vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); + float NdotL = max(dot(v_normal, lightDir), 0.2); + gl_FragColor = vec4(renderer_CustomColor.rgb * NdotL, 1.0); + } + ` +); + +WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { webGLMode: WebGLMode.WebGL2 } }).then(async (engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity("Root"); + + // Camera + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.transform.setPosition(0, 0, 100); + cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); + const camera = cameraEntity.addComponent(Camera); + camera.farClipPlane = 500; + cameraEntity.addComponent(OrbitControl); + + // Stats + cameraEntity.addComponent(Stats); + + // Light + const lightEntity = rootEntity.createChild("Light"); + lightEntity.transform.setRotation(-45, -45, 0); + lightEntity.addComponent(DirectLight).color = new Color(1, 1, 1, 1); + + // Load Duck model and ambient light + const [glTF, ambientLight] = await Promise.all([ + engine.resourceManager.load({ + url: "https://mdn.alipayobjects.com/rms/afts/file/A*9R-_TY9K_6oAAAAAgIAAAAgAehQnAQ/Avocado.glb", + type: AssetType.GLTF + }), + engine.resourceManager.load({ + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight", + type: AssetType.AmbientLight + }) + ]); + scene.ambientLight = ambientLight; + + // Cube resources + const cubeMesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1); + const cubeMaterial = new Material(engine, Shader.find("CustomInstanceShader")); + const customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); + + // Interleave ducks and cubes to break batching — instancing shines here + const duckCount = 2500; + const cubeCount = 2500; + const totalCount = duckCount + cubeCount; + + for (let i = 0; i < totalCount; i++) { + const ti = i / totalCount; + const isDuck = i % 2 === 0 && i / 2 < duckCount; + const isCube = !isDuck; + + let entity; + if (isDuck) { + entity = glTF.instantiateSceneRoot(); + rootEntity.addChild(entity); + } else { + entity = rootEntity.createChild("Cube" + i); + const renderer = entity.addComponent(MeshRenderer); + renderer.mesh = cubeMesh; + renderer.setMaterial(cubeMaterial); + const initColor = new Vector4(Math.random(), Math.random(), Math.random(), 1.0); + renderer.shaderData.setVector4(customColorProperty, initColor); + } + + const anim = entity.addComponent(SpiralAnimate); + anim.radius = 10 + Math.random() * 40; + anim.radiusSpeed = 0.3 + Math.random() * 0.6; + anim.theta = ti * Math.PI * 2 * 13.7; + anim.phi = ti * Math.PI * 2 * 7.3; + anim.thetaSpeed = (0.2 + Math.random() * 0.4) * (Math.random() > 0.5 ? 1 : -1); + anim.phiSpeed = (0.3 + Math.random() * 0.5) * (Math.random() > 0.5 ? 1 : -1); + anim.rotateSpeed = new Vector3((Math.random() - 0.5) * 60, (Math.random() - 0.5) * 60, (Math.random() - 0.5) * 60); + anim.scaleBase = (isDuck ? 20 : 1) * (0.6 + Math.random() * 0.8); + anim.scaleFreq = 0.5 + Math.random() * 2; + + if (isCube) { + anim.colorPhase = Math.random() * Math.PI * 2; + anim.colorSpeed = 0.5 + Math.random() * 2; + anim.enableColor(); + } + } + + engine.run(); +}); diff --git a/examples/src/gpu-instancing-custom-data.ts b/examples/src/gpu-instancing-custom-data.ts new file mode 100644 index 0000000000..27857d5730 --- /dev/null +++ b/examples/src/gpu-instancing-custom-data.ts @@ -0,0 +1,151 @@ +/** + * @title GPU Instancing Custom Data + * @category Mesh + * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*jjZMTrp-vU8AAAAAAAAAAAAADiR2AQ/original + */ +import { OrbitControl, Stats } from "@galacean/engine-toolkit"; +import { + Camera, + Color, + DirectLight, + Logger, + Material, + MeshRenderer, + PrimitiveMesh, + Script, + Shader, + ShaderProperty, + Vector3, + Vector4, + WebGLEngine, + WebGLMode +} from "@galacean/engine"; + +const _customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); + +class SpiralFlash extends Script { + radius: number = 0; + radiusSpeed: number = 0; + theta: number = 0; + thetaSpeed: number = 0; + phi: number = 0; + phiSpeed: number = 0; + rotateSpeed: Vector3 = new Vector3(); + scaleBase: number = 1; + scaleFreq: number = 0; + colorPhase: number = 0; + colorSpeed: number = 1; + private _time: number = 0; + private _color: Vector4 = new Vector4(); + + onUpdate(deltaTime: number): void { + this._time += deltaTime; + const t = this._time; + const transform = this.entity.transform; + + // Spiral breathing motion + const r = this.radius * (0.6 + 0.4 * Math.sin(t * this.radiusSpeed)); + const theta = this.theta + t * this.thetaSpeed; + const phi = this.phi + t * this.phiSpeed; + + const sinTheta = Math.sin(theta); + transform.setPosition(r * sinTheta * Math.cos(phi), r * Math.cos(theta), r * sinTheta * Math.sin(phi)); + + // Rotation + const { rotateSpeed } = this; + transform.rotate(rotateSpeed.x * deltaTime, rotateSpeed.y * deltaTime, rotateSpeed.z * deltaTime); + + // Scale pulse + const s = this.scaleBase * (0.7 + 0.3 * Math.sin(t * this.scaleFreq)); + transform.setScale(s, s, s); + + // Color cycles through hue based on time + unique phase + const ct = t * this.colorSpeed + this.colorPhase; + this._color.set(0.5 + 0.5 * Math.sin(ct), 0.5 + 0.5 * Math.sin(ct + 2.094), 0.5 + 0.5 * Math.sin(ct + 4.189), 1.0); + this.entity.getComponent(MeshRenderer).shaderData.setVector4(_customColorProperty, this._color); + } +} + +Logger.enable(); + +Shader.create( + "CustomInstanceShader", + ` + #include + attribute vec3 POSITION; + attribute vec3 NORMAL; + + varying vec3 v_normal; + + void main() { + gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); + v_normal = normalize((renderer_NormalMat * vec4(NORMAL, 0.0)).xyz); + } + `, + ` + uniform vec4 renderer_CustomColor; + + varying vec3 v_normal; + + void main() { + vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); + float NdotL = max(dot(v_normal, lightDir), 0.2); + gl_FragColor = vec4(renderer_CustomColor.rgb * NdotL, 1.0); + } + ` +); + +WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { webGLMode: WebGLMode.WebGL2 } }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity("Root"); + + // Camera + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.transform.setPosition(0, 0, 100); + cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); + const camera = cameraEntity.addComponent(Camera); + camera.farClipPlane = 500; + cameraEntity.addComponent(OrbitControl); + + // Stats + cameraEntity.addComponent(Stats); + + // Light + const lightEntity = rootEntity.createChild("Light"); + lightEntity.transform.setRotation(-45, -45, 0); + lightEntity.addComponent(DirectLight).color = new Color(1, 1, 1, 1); + + const mesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1); + const material = new Material(engine, Shader.find("CustomInstanceShader")); + const customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); + + const count = 5000; + for (let i = 0; i < count; i++) { + const entity = rootEntity.createChild("Cube" + i); + const ti = i / count; + + const renderer = entity.addComponent(MeshRenderer); + renderer.mesh = mesh; + renderer.setMaterial(material); + + const initColor = new Vector4(Math.random(), Math.random(), Math.random(), 1.0); + renderer.shaderData.setVector4(customColorProperty, initColor); + + const anim = entity.addComponent(SpiralFlash); + anim.radius = 10 + Math.random() * 40; + anim.radiusSpeed = 0.3 + Math.random() * 0.6; + anim.theta = ti * Math.PI * 2 * 13.7; + anim.phi = ti * Math.PI * 2 * 7.3; + anim.thetaSpeed = (0.2 + Math.random() * 0.4) * (Math.random() > 0.5 ? 1 : -1); + anim.phiSpeed = (0.3 + Math.random() * 0.5) * (Math.random() > 0.5 ? 1 : -1); + anim.rotateSpeed = new Vector3((Math.random() - 0.5) * 60, (Math.random() - 0.5) * 60, (Math.random() - 0.5) * 60); + anim.scaleBase = 0.6 + Math.random() * 0.8; + anim.scaleFreq = 0.5 + Math.random() * 2; + anim.colorPhase = Math.random() * Math.PI * 2; + anim.colorSpeed = 0.5 + Math.random() * 2; + } + + engine.run(); +}); diff --git a/package.json b/package.json index b65b3c5362..32799a4ae5 100644 --- a/package.json +++ b/package.json @@ -41,18 +41,18 @@ "@types/dom-webcodecs": "^0.1.13", "@types/node": "^18.7.16", "@types/webxr": "latest", - "@typescript-eslint/eslint-plugin": "^6.1.0", - "@typescript-eslint/parser": "^6.1.0", + "@typescript-eslint/eslint-plugin": "^8.58.1", + "@typescript-eslint/parser": "^8.58.1", "@vitest/coverage-v8": "2.1.3", "bumpp": "^9.5.2", "cross-env": "^5.2.0", "electron": "^13", - "eslint": "^8.44.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^5.0.0", "fs-extra": "^10.1.0", "husky": "^8.0.0", - "lint-staged": "^10.5.3", + "lint-staged": "^16.4.0", "nyc": "^15.1.0", "odiff-bin": "^2.5.0", "prettier": "^3.0.0", @@ -65,9 +65,8 @@ "vitest": "2.1.3" }, "lint-staged": { - "*.{ts}": [ - "eslint --fix", - "git add" + "**/*.ts": [ + "eslint --fix" ] }, "repository": "git@github.com:galacean/runtime.git" diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts index 141f352d35..1f740081f9 100644 --- a/packages/core/src/2d/sprite/SpriteMask.ts +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -1,12 +1,10 @@ import { BoundingBox } from "@galacean/engine-math"; import { Entity } from "../../Entity"; -import { RenderQueueFlags } from "../../RenderPipeline/BasicRenderPipeline"; -import { BatchUtils } from "../../RenderPipeline/BatchUtils"; +import { VertexMergeBatcher } from "../../RenderPipeline/VertexMergeBatcher"; import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; -import { RenderElement } from "../../RenderPipeline/RenderElement"; import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; -import { SubRenderElement } from "../../RenderPipeline/SubRenderElement"; +import { RenderElement } from "../../RenderPipeline/RenderElement"; import { Renderer, RendererUpdateFlags } from "../../Renderer"; import { assignmentClone, ignoreClone } from "../../clone/CloneManager"; import { SpriteMaskLayer } from "../../enums/SpriteMaskLayer"; @@ -181,16 +179,15 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { this.setMaterial(this._engine._basicResources.spriteMaskDefaultMaterial); this.shaderData.setFloat(SpriteMask._alphaCutoffProperty, this._alphaCutoff); this._renderElement = new RenderElement(); - this._renderElement.addSubRenderElement(new SubRenderElement()); this._onSpriteChange = this._onSpriteChange.bind(this); } /** * @internal */ - override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean, batched: boolean): void { + override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean): void { //@todo: Always update world positions to buffer, should opt - super._updateTransformShaderData(context, onlyMVP, true); + this._updateWorldSpaceTransformShaderData(context, onlyMVP); } /** @@ -204,15 +201,15 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { /** * @internal */ - override _canBatch(elementA: SubRenderElement, elementB: SubRenderElement): boolean { - return BatchUtils.canBatchSpriteMask(elementA, elementB); + override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { + return VertexMergeBatcher.canBatchSpriteMask(preElement, curElement); } /** * @internal */ - override _batch(elementA: SubRenderElement, elementB?: SubRenderElement): void { - BatchUtils.batchFor2D(elementA, elementB); + override _batch(preElement: RenderElement | null, curElement: RenderElement): void { + VertexMergeBatcher.batch(preElement, curElement); } /** @@ -297,14 +294,11 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { } const renderElement = this._renderElement; - const subRenderElement = renderElement.subRenderElements[0]; - renderElement.set(this.priority, this._distanceForSort); - const subChunk = this._subChunk; - subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); - subRenderElement.shaderPasses = material.shader.subShaders[0].passes; - subRenderElement.renderQueueFlags = RenderQueueFlags.All; - renderElement.addSubRenderElement(subRenderElement); + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); + renderElement.priority = this.priority; + renderElement.distanceForSort = this._distanceForSort; + renderElement.subShader = material.shader.subShaders[0]; } /** diff --git a/packages/core/src/2d/sprite/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts index a75dac1510..06bd3d3c6c 100644 --- a/packages/core/src/2d/sprite/SpriteRenderer.ts +++ b/packages/core/src/2d/sprite/SpriteRenderer.ts @@ -1,10 +1,10 @@ import { BoundingBox, Color, MathUtil } from "@galacean/engine-math"; import { Entity } from "../../Entity"; -import { BatchUtils } from "../../RenderPipeline/BatchUtils"; +import { VertexMergeBatcher } from "../../RenderPipeline/VertexMergeBatcher"; import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; -import { SubRenderElement } from "../../RenderPipeline/SubRenderElement"; +import { RenderElement } from "../../RenderPipeline/RenderElement"; import { Renderer, RendererUpdateFlags } from "../../Renderer"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; import { ShaderProperty } from "../../shader/ShaderProperty"; @@ -360,9 +360,9 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer { /** * @internal */ - override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean, batched: boolean): void { + override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean): void { //@todo: Always update world positions to buffer, should opt - super._updateTransformShaderData(context, onlyMVP, true); + this._updateWorldSpaceTransformShaderData(context, onlyMVP); } /** @@ -377,15 +377,15 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer { /** * @internal */ - override _canBatch(elementA: SubRenderElement, elementB: SubRenderElement): boolean { - return BatchUtils.canBatchSprite(elementA, elementB); + override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { + return VertexMergeBatcher.canBatchSprite(preElement, curElement); } /** * @internal */ - override _batch(elementA: SubRenderElement, elementB?: SubRenderElement): void { - BatchUtils.batchFor2D(elementA, elementB); + override _batch(preElement: RenderElement | null, curElement: RenderElement): void { + VertexMergeBatcher.batch(preElement, curElement); } /** @@ -459,11 +459,10 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer { const camera = context.camera; const engine = camera.engine; const renderElement = engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); - const subRenderElement = engine._subRenderElementPool.get(); const subChunk = this._subChunk; - subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); - renderElement.addSubRenderElement(subRenderElement); + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); + renderElement.priority = this.priority; + renderElement.distanceForSort = this._distanceForSort; camera._renderPipeline.pushRenderElement(context, renderElement); } diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 143e46a7da..3649180c79 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -1,11 +1,11 @@ import { BoundingBox, Color, Vector3 } from "@galacean/engine-math"; import { Engine } from "../../Engine"; import { Entity } from "../../Entity"; -import { BatchUtils } from "../../RenderPipeline/BatchUtils"; +import { VertexMergeBatcher } from "../../RenderPipeline/VertexMergeBatcher"; import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; -import { SubRenderElement } from "../../RenderPipeline/SubRenderElement"; +import { RenderElement } from "../../RenderPipeline/RenderElement"; import { Renderer } from "../../Renderer"; import { TransformModifyFlags } from "../../Transform"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; @@ -373,23 +373,23 @@ export class TextRenderer extends Renderer implements ITextRenderer { /** * @internal */ - override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean, batched: boolean): void { + override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean): void { //@todo: Always update world positions to buffer, should opt - super._updateTransformShaderData(context, onlyMVP, true); + this._updateWorldSpaceTransformShaderData(context, onlyMVP); } /** * @internal */ - override _canBatch(elementA: SubRenderElement, elementB: SubRenderElement): boolean { - return BatchUtils.canBatchSprite(elementA, elementB); + override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { + return VertexMergeBatcher.canBatchSprite(preElement, curElement); } /** * @internal */ - override _batch(elementA: SubRenderElement, elementB?: SubRenderElement): void { - BatchUtils.batchFor2D(elementA, elementB); + override _batch(preElement: RenderElement | null, curElement: RenderElement): void { + VertexMergeBatcher.batch(preElement, curElement); } /** @@ -430,20 +430,22 @@ export class TextRenderer extends Renderer implements ITextRenderer { const camera = context.camera; const engine = camera.engine; - const textSubRenderElementPool = engine._textSubRenderElementPool; + const textRenderElementPool = engine._textRenderElementPool; const material = this.getMaterial(); - const renderElement = engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); + const priority = this.priority; + const distanceForSort = this._distanceForSort; + const renderPipeline = camera._renderPipeline; const textChunks = this._textChunks; for (let i = 0, n = textChunks.length; i < n; ++i) { const { subChunk, texture } = textChunks[i]; - const subRenderElement = textSubRenderElementPool.get(); - subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk); - subRenderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); - subRenderElement.shaderData.setTexture(TextRenderer._textureProperty, texture); - renderElement.addSubRenderElement(subRenderElement); + const renderElement = textRenderElementPool.get(); + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk); + renderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); + renderElement.shaderData.setTexture(TextRenderer._textureProperty, texture); + renderElement.priority = priority; + renderElement.distanceForSort = distanceForSort; + renderPipeline.pushRenderElement(context, renderElement); } - camera._renderPipeline.pushRenderElement(context, renderElement); } private _resetSubFont(): void { diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index 55b0ea0b4c..ea6b2d3079 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -15,9 +15,8 @@ import { EngineSettings } from "./EngineSettings"; import { Entity } from "./Entity"; import { BatcherManager } from "./RenderPipeline/BatcherManager"; import { RenderContext } from "./RenderPipeline/RenderContext"; -import { RenderElement } from "./RenderPipeline/RenderElement"; import { RenderTargetPool } from "./RenderPipeline/RenderTargetPool"; -import { SubRenderElement } from "./RenderPipeline/SubRenderElement"; +import { RenderElement } from "./RenderPipeline/RenderElement"; import { Scene } from "./Scene"; import { SceneManager } from "./SceneManager"; import { RenderingStatistics } from "./asset/RenderingStatistics"; @@ -33,7 +32,8 @@ import { Shader } from "./shader/Shader"; import { ShaderMacro } from "./shader/ShaderMacro"; import { ShaderMacroCollection } from "./shader/ShaderMacroCollection"; import { ShaderPool } from "./shader/ShaderPool"; -import { ShaderProgramPool } from "./shader/ShaderProgramPool"; +import { ShaderProgramMap } from "./shader/ShaderProgramMap"; +import { ShaderProgram } from "./shader/ShaderProgram"; import { RenderState } from "./shader/state/RenderState"; import { Texture2D, TextureFormat } from "./texture"; import { UIUtils } from "./ui/UIUtils"; @@ -93,9 +93,7 @@ export class Engine extends EventDispatcher { /* @internal */ _renderElementPool = new ClearableObjectPool(RenderElement); /* @internal */ - _subRenderElementPool = new ClearableObjectPool(SubRenderElement); - /* @internal */ - _textSubRenderElementPool = new ClearableObjectPool(SubRenderElement); + _textRenderElementPool = new ClearableObjectPool(RenderElement); /* @internal */ _charRenderInfoPool = new ReturnableObjectPool(CharRenderInfo, 50); @@ -112,7 +110,7 @@ export class Engine extends EventDispatcher { /* @internal */ _renderCount: number = 0; /* @internal */ - _shaderProgramPools: ShaderProgramPool[] = []; + _shaderProgramMaps: ShaderProgramMap[] = []; /** @internal */ _fontMap: Record = {}; /** @internal */ @@ -329,9 +327,8 @@ export class Engine extends EventDispatcher { const deltaTime = time.deltaTime; this._frameInProcess = true; - this._subRenderElementPool.clear(); - this._textSubRenderElementPool.clear(); this._renderElementPool.clear(); + this._textRenderElementPool.clear(); this.xrManager?._update(); const { inputManager, _physicsInitialized: physicsInitialized } = this; @@ -541,18 +538,18 @@ export class Engine extends EventDispatcher { /** * @internal */ - _getShaderProgramPool(index: number, trackPools?: ShaderProgramPool[]): ShaderProgramPool { - const shaderProgramPools = this._shaderProgramPools; - let pool = shaderProgramPools[index]; - if (!pool) { + _getShaderProgramMap(index: number, trackMaps?: ShaderProgramMap[]): ShaderProgramMap { + const shaderProgramMaps = this._shaderProgramMaps; + let map = shaderProgramMaps[index]; + if (!map) { const length = index + 1; - if (length > shaderProgramPools.length) { - shaderProgramPools.length = length; + if (length > shaderProgramMaps.length) { + shaderProgramMaps.length = length; } - shaderProgramPools[index] = pool = new ShaderProgramPool(this); - trackPools?.push(pool); + shaderProgramMaps[index] = map = new ShaderProgramMap(this); + trackMaps?.push(map); } - return pool; + return map; } /** @@ -674,9 +671,9 @@ export class Engine extends EventDispatcher { private _onDeviceRestored(): void { this._hardwareRenderer.resetState(); this._lastRenderState = new RenderState(); - // Clear shader pools + // Clear shader program maps Shader._clear(this); - this._shaderProgramPools.length = 0; + this._shaderProgramMaps.length = 0; const { resourceManager } = this; // Restore graphic resources @@ -697,9 +694,8 @@ export class Engine extends EventDispatcher { } private _gc(): void { - this._subRenderElementPool.garbageCollection(); - this._textSubRenderElementPool.garbageCollection(); this._renderElementPool.garbageCollection(); + this._textRenderElementPool.garbageCollection(); this._renderContext.garbageCollection(); const scenes = this._sceneManager._scenes.getLoopArray(); for (let i = 0, n = scenes.length; i < n; i++) { diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index dee2231a0a..84f5dcb7c7 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -10,7 +10,7 @@ import { ScalableAmbientObscurancePass } from "../lighting/ambientOcclusion/Scal import { FinalPass } from "../postProcess"; import { Shader } from "../shader/Shader"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; -import { ShaderPass } from "../shader/ShaderPass"; +import { SubShader } from "../shader/SubShader"; import { RenderQueueType } from "../shader/enums/RenderQueueType"; import { RenderState } from "../shader/state/RenderState"; import { CascadedShadowCasterPass } from "../shadow/CascadedShadowCasterPass"; @@ -30,7 +30,6 @@ import { OpaqueTexturePass } from "./OpaqueTexturePass"; import { PipelineUtils } from "./PipelineUtils"; import { ContextRendererUpdateFlag, RenderContext } from "./RenderContext"; import { RenderElement } from "./RenderElement"; -import { SubRenderElement } from "./SubRenderElement"; import { PipelineStage } from "./enums/PipelineStage"; /** * Basic render pipeline. @@ -132,7 +131,6 @@ export class BasicRenderPipeline { this._cascadedShadowCasterPass.onRender(context); } - const batcherManager = engine._batcherManager; cullingResults.reset(); // Depth use camera's view and projection matrix @@ -140,6 +138,7 @@ export class BasicRenderPipeline { context.applyVirtualCamera(camera._virtualCamera, depthPassEnabled); this._prepareRender(context); + const batcherManager = engine._batcherManager; cullingResults.sortBatch(batcherManager); batcherManager.uploadBuffer(); @@ -369,58 +368,53 @@ export class BasicRenderPipeline { * @param renderElement - Render element */ pushRenderElement(context: RenderContext, renderElement: RenderElement): void { - renderElement.renderQueueFlags = RenderQueueFlags.None; - const subRenderElements = renderElement.subRenderElements; - for (let i = 0, n = subRenderElements.length; i < n; ++i) { - const subRenderElement = subRenderElements[i]; - const { material } = subRenderElement; - const { renderStates } = material; - const materialSubShader = material.shader.subShaders[0]; - const replacementShader = context.replacementShader; - if (replacementShader) { - const replacementSubShaders = replacementShader.subShaders; - const { replacementTag } = context; - if (replacementTag) { - let replacementSuccess = false; - for (let j = 0, m = replacementSubShaders.length; j < m; j++) { - const subShader = replacementSubShaders[j]; - if (subShader.getTagValue(replacementTag) === materialSubShader.getTagValue(replacementTag)) { - this.pushRenderElementByType(renderElement, subRenderElement, subShader.passes, renderStates); - replacementSuccess = true; - } + const { material } = renderElement; + const { renderStates } = material; + const materialSubShader = material.shader.subShaders[0]; + const replacementShader = context.replacementShader; + if (replacementShader) { + const replacementSubShaders = replacementShader.subShaders; + const { replacementTag } = context; + if (replacementTag) { + let replacementSuccess = false; + for (let j = 0, m = replacementSubShaders.length; j < m; j++) { + const subShader = replacementSubShaders[j]; + if (subShader.getTagValue(replacementTag) === materialSubShader.getTagValue(replacementTag)) { + this._pushRenderElementByType(renderElement, subShader, renderStates); + replacementSuccess = true; } + } - if ( - !replacementSuccess && - context.replacementFailureStrategy === ReplacementFailureStrategy.KeepOriginalShader - ) { - this.pushRenderElementByType(renderElement, subRenderElement, materialSubShader.passes, renderStates); - } - } else { - this.pushRenderElementByType(renderElement, subRenderElement, replacementSubShaders[0].passes, renderStates); + if ( + !replacementSuccess && + context.replacementFailureStrategy === ReplacementFailureStrategy.KeepOriginalShader + ) { + this._pushRenderElementByType(renderElement, materialSubShader, renderStates); } } else { - this.pushRenderElementByType(renderElement, subRenderElement, materialSubShader.passes, renderStates); + this._pushRenderElementByType(renderElement, replacementSubShaders[0], renderStates); } + } else { + this._pushRenderElementByType(renderElement, materialSubShader, renderStates); } } - private pushRenderElementByType( + private _pushRenderElementByType( renderElement: RenderElement, - subRenderElement: SubRenderElement, - shaderPasses: ReadonlyArray, + subShader: SubShader, renderStates: ReadonlyArray ): void { + const shaderPasses = subShader.passes; const cullingResults = this._cullingResults; + let pushedQueueFlags = RenderQueueFlags.None; for (let i = 0, n = shaderPasses.length; i < n; i++) { - // Get render queue type let renderQueueType: RenderQueueType; const shaderPass = shaderPasses[i]; const renderState = shaderPass._renderState; if (renderState) { renderQueueType = renderState._getRenderQueueByShaderData( shaderPass._renderStateDataMap, - subRenderElement.material.shaderData + renderElement.material.shaderData ); } else { renderQueueType = renderStates[i].renderQueueType; @@ -428,10 +422,9 @@ export class BasicRenderPipeline { const flag = 1 << renderQueueType; - subRenderElement.shaderPasses = shaderPasses; - subRenderElement.renderQueueFlags |= flag; + renderElement.subShader = subShader; - if (renderElement.renderQueueFlags & flag) { + if (pushedQueueFlags & flag) { continue; } @@ -446,7 +439,7 @@ export class BasicRenderPipeline { cullingResults.transparentQueue.pushRenderElement(renderElement); break; } - renderElement.renderQueueFlags |= flag; + pushedQueueFlags |= flag; } } @@ -516,7 +509,10 @@ export class BasicRenderPipeline { continue; } canvas._prepareRender(context); - this.pushRenderElement(context, canvas._renderElement); + const canvasElements = canvas._renderElements; + for (let j = 0, m = canvasElements.length; j < m; j++) { + this.pushRenderElement(context, canvasElements[j]); + } } } } diff --git a/packages/core/src/RenderPipeline/BatchUtils.ts b/packages/core/src/RenderPipeline/BatchUtils.ts deleted file mode 100644 index 387c951f93..0000000000 --- a/packages/core/src/RenderPipeline/BatchUtils.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { SpriteMask, SpriteMaskInteraction, SpriteRenderer } from "../2d"; -import { ShaderTagKey } from "../shader"; -import { SubRenderElement } from "./SubRenderElement"; - -/** - * @internal - */ -export class BatchUtils { - protected static _disableBatchTag: ShaderTagKey = ShaderTagKey.getByName("spriteDisableBatching"); - - static canBatchSprite(elementA: SubRenderElement, elementB: SubRenderElement): boolean { - if (elementB.shaderPasses[0].getTagValue(BatchUtils._disableBatchTag) === true) { - return false; - } - if (elementA.subChunk.chunk !== elementB.subChunk.chunk) { - return false; - } - - const rendererA = elementA.component; - const rendererB = elementB.component; - const maskInteractionA = rendererA.maskInteraction; - - // Compare mask, texture and material - return ( - maskInteractionA === rendererB.maskInteraction && - (maskInteractionA === SpriteMaskInteraction.None || rendererA.maskLayer === rendererB.maskLayer) && - elementA.texture === elementB.texture && - elementA.material === elementB.material - ); - } - - static canBatchSpriteMask(elementA: SubRenderElement, elementB: SubRenderElement): boolean { - if (elementA.subChunk.chunk !== elementB.subChunk.chunk) { - return false; - } - - const alphaCutoffProperty = SpriteMask._alphaCutoffProperty; - - // Compare renderer property - return ( - elementA.texture === elementB.texture && - (elementA.component).shaderData.getFloat(alphaCutoffProperty) === - (elementB.component).shaderData.getFloat(alphaCutoffProperty) - ); - } - - static batchFor2D(elementA: SubRenderElement, elementB?: SubRenderElement): void { - const subChunk = elementB ? elementB.subChunk : elementA.subChunk; - const { chunk, indices: subChunkIndices } = subChunk; - - const length = subChunkIndices.length; - let startIndex = chunk.updateIndexLength; - if (elementB) { - elementA.subChunk.subMesh.count += length; - } else { - // Reset subMesh - const subMesh = subChunk.subMesh; - subMesh.start = startIndex; - subMesh.count = length; - } - - const { start, size } = subChunk.vertexArea; - const vertexOffset = start / 9; - const indices = chunk.indices; - for (let i = 0; i < length; ++i) { - indices[startIndex++] = vertexOffset + subChunkIndices[i]; - } - chunk.updateIndexLength += length; - chunk.updateVertexStart = Math.min(chunk.updateVertexStart, start); - chunk.updateVertexEnd = Math.max(chunk.updateVertexEnd, start + size); - } -} diff --git a/packages/core/src/RenderPipeline/BatcherManager.ts b/packages/core/src/RenderPipeline/BatcherManager.ts index 702a4a01e8..05ec1b7e89 100644 --- a/packages/core/src/RenderPipeline/BatcherManager.ts +++ b/packages/core/src/RenderPipeline/BatcherManager.ts @@ -1,8 +1,9 @@ import { Engine } from "../Engine"; import { Renderer } from "../Renderer"; +import { InstanceBuffer } from "./InstanceBuffer"; import { PrimitiveChunkManager } from "./PrimitiveChunkManager"; import { RenderQueue } from "./RenderQueue"; -import { SubRenderElement } from "./SubRenderElement"; +import { RenderElement } from "./RenderElement"; /** * @internal @@ -11,9 +12,14 @@ export class BatcherManager { private _primitiveChunkManager2D: PrimitiveChunkManager; private _primitiveChunkManagerMask: PrimitiveChunkManager; private _primitiveChunkManagerUI: PrimitiveChunkManager; + private _instanceBuffer: InstanceBuffer; constructor(public engine: Engine) {} + get instanceBuffer(): InstanceBuffer { + return (this._instanceBuffer ||= new InstanceBuffer(this.engine)); + } + get primitiveChunkManager2D(): PrimitiveChunkManager { return (this._primitiveChunkManager2D ||= new PrimitiveChunkManager(this.engine)); } @@ -39,47 +45,40 @@ export class BatcherManager { this._primitiveChunkManagerUI.destroy(); this._primitiveChunkManagerUI = null; } + if (this._instanceBuffer) { + this._instanceBuffer.destroy(); + this._instanceBuffer = null; + } } batch(renderQueue: RenderQueue): void { - const { elements, batchedSubElements, renderQueueType } = renderQueue; - let preSubElement: SubRenderElement; + const { elements, batchedElements } = renderQueue; + + let preElement: RenderElement; let preRenderer: Renderer; let preConstructor: Function; for (let i = 0, n = elements.length; i < n; ++i) { - const subElements = elements[i].subRenderElements; - for (let j = 0, m = subElements.length; j < m; ++j) { - const subElement = subElements[j]; - - // Some sub render elements may not belong to the current render queue - if (!(subElement.renderQueueFlags & (1 << renderQueueType))) { - continue; - } - - const renderer = subElement.component; - const constructor = renderer.constructor; - if (preSubElement) { - if (preConstructor === constructor && preRenderer._canBatch(preSubElement, subElement)) { - preRenderer._batch(preSubElement, subElement); - preSubElement.batched = true; - } else { - batchedSubElements.push(preSubElement); - preSubElement = subElement; - preRenderer = renderer; - preConstructor = constructor; - renderer._batch(subElement); - subElement.batched = false; - } + const curElement = elements[i]; + const renderer = curElement.component; + const constructor = renderer.constructor; + if (preElement) { + if (preConstructor === constructor && preRenderer._canBatch(preElement, curElement)) { + preRenderer._batch(preElement, curElement); } else { - preSubElement = subElement; + batchedElements.push(preElement); + preElement = curElement; preRenderer = renderer; preConstructor = constructor; - renderer._batch(subElement); - subElement.batched = false; + renderer._batch(null, curElement); } + } else { + preElement = curElement; + preRenderer = renderer; + preConstructor = constructor; + renderer._batch(null, curElement); } } - preSubElement && batchedSubElements.push(preSubElement); + preElement && batchedElements.push(preElement); } uploadBuffer() { diff --git a/packages/core/src/RenderPipeline/InstanceBuffer.ts b/packages/core/src/RenderPipeline/InstanceBuffer.ts new file mode 100644 index 0000000000..f62253f78c --- /dev/null +++ b/packages/core/src/RenderPipeline/InstanceBuffer.ts @@ -0,0 +1,86 @@ +import { Engine } from "../Engine"; +import { Buffer } from "../graphic/Buffer"; +import { BufferBindFlag } from "../graphic/enums/BufferBindFlag"; +import { BufferUsage } from "../graphic/enums/BufferUsage"; +import { SetDataOptions } from "../graphic/enums/SetDataOptions"; +import { Renderer } from "../Renderer"; +import { ShaderMacro } from "../shader/ShaderMacro"; +import { InstanceBufferLayout } from "../shaderlib/ShaderFactory"; + +/** + * @internal + * Manages a UBO for GPU instancing, packing per-instance renderer data (ModelMat, Layer, etc.). + */ +export class InstanceBuffer { + static gpuInstanceMacro = ShaderMacro.getByName("RENDERER_GPU_INSTANCE"); + + buffer: Buffer; + + private _engine: Engine; + private _layout: InstanceBufferLayout; + private _data: ArrayBuffer; + private _floatView: Float32Array; + private _intView: Int32Array; + + constructor(engine: Engine) { + this._engine = engine; + } + + /** + * Set UBO layout and allocate buffer if needed. + */ + setLayout(layout: InstanceBufferLayout): void { + this._layout = layout; + const totalBytes = layout.instanceMaxCount * layout.structSize; + // Only reallocate when buffer is too small + if (!this.buffer || totalBytes > this.buffer.byteLength) { + this._data = new ArrayBuffer(totalBytes); + this._floatView = new Float32Array(this._data); + this._intView = new Int32Array(this._data); + this.buffer?.destroy(); + this.buffer = new Buffer(this._engine, BufferBindFlag.ConstantBuffer, totalBytes, BufferUsage.Dynamic); + } + } + + /** + * Pack renderer data into UBO and upload to GPU. + */ + upload(renderers: Renderer[], start: number, count: number): void { + const { instanceFields, structSize } = this._layout; + const elementsPerInstance = structSize / 4; + const { _floatView: floatView, _intView: intView } = this; + const modelMatId = Renderer._worldMatrixProperty._uniqueId; + + for (let i = 0; i < count; i++) { + const renderer = renderers[start + i]; + const propertyValueMap = renderer.shaderData._propertyValueMap; + const baseOffset = i * elementsPerInstance; + + for (let j = 0, n = instanceFields.length; j < n; j++) { + const field = instanceFields[j]; + const fieldOffset = baseOffset + field.offsetInElements; + const propertyId = field.property._uniqueId; + + if (propertyId === modelMatId) { + // Instancing skips _updateTransformShaderData, so worldMatrix is not in propertyValueMap + // Must read from transform getter to trigger lazy update + field.pack(floatView, fieldOffset, renderer.entity.transform.worldMatrix); + } else { + const value = propertyValueMap[propertyId]; + if (value != null) { + field.pack(field.useIntView ? intView : floatView, fieldOffset, value); + } + } + } + } + + this.buffer.setData(floatView, 0, 0, count * elementsPerInstance, SetDataOptions.Discard); + } + + destroy(): void { + this.buffer?.destroy(); + this._data = null; + this._floatView = null; + this._intView = null; + } +} diff --git a/packages/core/src/RenderPipeline/RenderElement.ts b/packages/core/src/RenderPipeline/RenderElement.ts index 582e72f47d..89a67b8fd9 100644 --- a/packages/core/src/RenderPipeline/RenderElement.ts +++ b/packages/core/src/RenderPipeline/RenderElement.ts @@ -1,24 +1,53 @@ +import { Renderer } from "../Renderer"; +import { Primitive, SubMesh } from "../graphic"; +import { Material } from "../material"; +import { ShaderData, SubShader } from "../shader"; +import { Texture2D } from "../texture"; import { IPoolElement } from "../utils/ObjectPool"; -import { RenderQueueFlags } from "./BasicRenderPipeline"; -import { SubRenderElement } from "./SubRenderElement"; +import { SubPrimitiveChunk } from "./SubPrimitiveChunk"; export class RenderElement implements IPoolElement { priority: number; distanceForSort: number; - subRenderElements = Array(); - renderQueueFlags: RenderQueueFlags; + component: Renderer; + primitive: Primitive; + material: Material; + subPrimitive: SubMesh; + subShader: SubShader; + shaderData?: ShaderData; + instancedRenderers: Renderer[] = []; - set(priority: number, distanceForSort: number): void { - this.priority = priority; - this.distanceForSort = distanceForSort; - this.subRenderElements.length = 0; - } + // @todo: maybe should remove later + texture?: Texture2D; + subChunk?: SubPrimitiveChunk; - addSubRenderElement(element: SubRenderElement): void { - this.subRenderElements.push(element); + set( + component: Renderer, + material: Material, + primitive: Primitive, + subPrimitive: SubMesh, + texture?: Texture2D, + subChunk?: SubPrimitiveChunk + ): void { + this.component = component; + this.material = material; + this.primitive = primitive; + this.subPrimitive = subPrimitive; + this.texture = texture; + this.subChunk = subChunk; + this.instancedRenderers.length = 0; } dispose(): void { - this.subRenderElements.length = 0; + this.component = null; + this.material = null; + this.primitive = null; + this.subPrimitive = null; + this.subShader = null; + this.shaderData && (this.shaderData = null); + this.instancedRenderers = null; + + this.texture && (this.texture = null); + this.subChunk && (this.subChunk = null); } } diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 3558679996..2290f33a6d 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -2,11 +2,12 @@ import { SpriteMaskInteraction } from "../2d/enums/SpriteMaskInteraction"; import { BasicResources, RenderStateElementMap } from "../BasicResources"; import { Utils } from "../Utils"; import { RenderQueueType, Shader } from "../shader"; +import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; import { BatcherManager } from "./BatcherManager"; +import { InstanceBuffer } from "./InstanceBuffer"; import { ContextRendererUpdateFlag, RenderContext } from "./RenderContext"; import { RenderElement } from "./RenderElement"; -import { SubRenderElement } from "./SubRenderElement"; import { RenderQueueMaskType } from "./enums/RenderQueueMaskType"; /** @@ -14,7 +15,11 @@ import { RenderQueueMaskType } from "./enums/RenderQueueMaskType"; */ export class RenderQueue { static compareForOpaque(a: RenderElement, b: RenderElement): number { - return a.priority - b.priority || a.distanceForSort - b.distanceForSort; + return ( + a.priority - b.priority || + a.material.instanceId - b.material.instanceId || + a.primitive.instanceId - b.primitive.instanceId + ); } static compareForTransparent(a: RenderElement, b: RenderElement): number { @@ -22,7 +27,7 @@ export class RenderQueue { } readonly elements = new Array(); - readonly batchedSubElements = new Array(); + readonly batchedElements = new Array(); rendererUpdateFlag = ContextRendererUpdateFlag.None; constructor(public renderQueueType: RenderQueueType) {} @@ -45,8 +50,8 @@ export class RenderQueue { pipelineStageTagValue: string, maskType: RenderQueueMaskType = RenderQueueMaskType.No ): void { - const batchedSubElements = this.batchedSubElements; - const length = batchedSubElements.length; + const batchedElements = this.batchedElements; + const length = batchedElements.length; if (length === 0) { return; } @@ -57,34 +62,33 @@ export class RenderQueue { const rhi = engine._hardwareRenderer; const pipelineStageKey = RenderContext.pipelineStageKey; const renderQueueType = this.renderQueueType; + const needMaskType = maskType !== RenderQueueMaskType.No; for (let i = 0; i < length; i++) { - const subElement = batchedSubElements[i]; - const { component: renderer, batched, material } = subElement; - - // @todo: Can optimize update view projection matrix updated - if ( - this.rendererUpdateFlag & ContextRendererUpdateFlag.WorldViewMatrix || - renderer._batchedTransformShaderData != batched - ) { - // Update world matrix and view matrix and model matrix - renderer._updateTransformShaderData(context, false, batched); - renderer._batchedTransformShaderData = batched; - } else if (this.rendererUpdateFlag & ContextRendererUpdateFlag.ProjectionMatrix) { - // Only projection matrix need updated - renderer._updateTransformShaderData(context, true, batched); + const curElement = batchedElements[i]; + const { component, material } = curElement; + const isInstanced = curElement.instancedRenderers.length > 0; + + // Update transform shader data + // Instancing packs per-renderer transforms into the instance UBO at draw time, so skip here + if (!isInstanced) { + if (this.rendererUpdateFlag & ContextRendererUpdateFlag.WorldViewMatrix) { + component._updateTransformShaderData(context, false); + } else if (this.rendererUpdateFlag & ContextRendererUpdateFlag.ProjectionMatrix) { + component._updateTransformShaderData(context, true); + } } - const maskInteraction = renderer._maskInteraction; + // Resolve mask render states + const maskInteraction = component._maskInteraction; const needMaskInteraction = maskInteraction !== SpriteMaskInteraction.None; - const needMaskType = maskType !== RenderQueueMaskType.No; let customStates: RenderStateElementMap = null; if (needMaskType) { customStates = BasicResources.getMaskTypeRenderStates(maskType); } else { if (needMaskInteraction) { - maskManager.drawMask(context, pipelineStageTagValue, subElement.component._maskLayer); + maskManager.drawMask(context, pipelineStageTagValue, component._maskLayer); customStates = BasicResources.getMaskInteractionRenderStates(maskInteraction); } else { maskManager.isReadStencil(material) && maskManager.clearMask(context, pipelineStageTagValue); @@ -92,21 +96,27 @@ export class RenderQueue { maskManager.isStencilWritten(material) && (maskManager.hasStencilWritten = true); } - const compileMacros = Shader._compileMacros; - const { primitive, shaderPasses, shaderData: renderElementShaderData } = subElement; - const { shaderData: rendererData, instanceId: rendererId } = renderer; + const { shaderData: renderElementShaderData } = curElement; + const shaderPasses = curElement.subShader.passes; + const { shaderData: rendererData, instanceId: rendererId } = component; const { shaderData: materialData, instanceId: materialId, renderStates } = material; - // Union render global macro and material self macro - ShaderMacroCollection.unionCollection(renderer._globalShaderMacro, materialData._macroCollection, compileMacros); + // Build compile macros + const compileMacros = Shader._compileMacros; + ShaderMacroCollection.unionCollection(component._globalShaderMacro, materialData._macroCollection, compileMacros); ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); + if (isInstanced) { + compileMacros.enable(InstanceBuffer.gpuInstanceMacro); + } + for (let j = 0, m = shaderPasses.length; j < m; j++) { const shaderPass = shaderPasses[j]; if (shaderPass.getTagValue(pipelineStageKey) !== pipelineStageTagValue) { continue; } + // Pick render state and filter by queue type let renderState = shaderPass._renderState; if (needMaskType) { // Mask don't care render queue type @@ -134,18 +144,23 @@ export class RenderQueue { const switchProgram = program.bind(); const switchRenderCount = renderCount !== program._uploadRenderCount; + // Upload uniforms (cache-aware per block) if (switchRenderCount) { program.groupingOtherUniformBlock(); program.uploadAll(program.sceneUniformBlock, sceneData); program.uploadAll(program.cameraUniformBlock, cameraData); - program.uploadAll(program.rendererUniformBlock, rendererData); + if (isInstanced) { + program._uploadRendererId = -1; + } else { + program.uploadAll(program.rendererUniformBlock, rendererData); + program._uploadRendererId = rendererId; + } program.uploadAll(program.materialUniformBlock, materialData); renderElementShaderData && program.uploadAll(program.renderElementUniformBlock, renderElementShaderData); - // UnGroup textures should upload default value, texture uint maybe change by logic of texture bind. + // UnGroup textures should upload default value, texture uint maybe change by logic of texture bind program.uploadUnGroupTextures(); program._uploadSceneId = sceneId; program._uploadCameraId = cameraId; - program._uploadRendererId = rendererId; program._uploadMaterialId = materialId; program._uploadRenderCount = renderCount; } else { @@ -163,11 +178,13 @@ export class RenderQueue { program.uploadTextures(program.cameraUniformBlock, cameraData); } - if (program._uploadRendererId !== rendererId) { - program.uploadAll(program.rendererUniformBlock, rendererData); - program._uploadRendererId = rendererId; - } else if (switchProgram) { - program.uploadTextures(program.rendererUniformBlock, rendererData); + if (!isInstanced) { + if (program._uploadRendererId !== rendererId) { + program.uploadAll(program.rendererUniformBlock, rendererData); + program._uploadRendererId = rendererId; + } else if (switchProgram) { + program.uploadTextures(program.rendererUniformBlock, rendererData); + } } if (program._uploadMaterialId !== materialId) { @@ -179,20 +196,41 @@ export class RenderQueue { renderElementShaderData && program.uploadAll(program.renderElementUniformBlock, renderElementShaderData); - // We only consider switchProgram case, because UnGroup texture's value is always default. + // We only consider switchProgram case, because UnGroup texture's value is always default if (switchProgram) { program.uploadUnGroupTextures(); } } + // Apply render state renderState._applyStates( engine, - renderer._isFrontFaceInvert(), + component._isFrontFaceInvert(), shaderPass._renderStateDataMap, material.shaderData, customStates ); - rhi.drawPrimitive(primitive, subElement.subPrimitive, program); + + // Draw + const layout = program._instanceLayout; + if (isInstanced && layout) { + const { primitive, subPrimitive, instancedRenderers } = curElement; + const totalCount = instancedRenderers.length; + const maxCount = layout.instanceMaxCount; + const instanceBuffer = engine._batcherManager.instanceBuffer; + + instanceBuffer.setLayout(layout); + rhi.bindUniformBufferBase(ConstantBufferBindingPoint.RendererInstance, instanceBuffer.buffer._platformBuffer); + for (let start = 0; start < totalCount; start += maxCount) { + const count = Math.min(maxCount, totalCount - start); + instanceBuffer.upload(instancedRenderers, start, count); + primitive.instanceCount = count; + rhi.drawPrimitive(primitive, subPrimitive, program); + } + primitive.instanceCount = 0; + } else { + rhi.drawPrimitive(curElement.primitive, curElement.subPrimitive, program); + } } } @@ -201,7 +239,7 @@ export class RenderQueue { clear(): void { this.elements.length = 0; - this.batchedSubElements.length = 0; + this.batchedElements.length = 0; } destroy(): void {} diff --git a/packages/core/src/RenderPipeline/SubRenderElement.ts b/packages/core/src/RenderPipeline/SubRenderElement.ts deleted file mode 100644 index f8c86c114d..0000000000 --- a/packages/core/src/RenderPipeline/SubRenderElement.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Renderer } from "../Renderer"; -import { Primitive, SubMesh } from "../graphic"; -import { Material } from "../material"; -import { ShaderData, ShaderPass } from "../shader"; -import { Texture2D } from "../texture"; -import { IPoolElement } from "../utils/ObjectPool"; -import { RenderQueueFlags } from "./BasicRenderPipeline"; -import { SubPrimitiveChunk } from "./SubPrimitiveChunk"; - -export class SubRenderElement implements IPoolElement { - component: Renderer; - primitive: Primitive; - material: Material; - subPrimitive: SubMesh; - shaderPasses: ReadonlyArray; - shaderData?: ShaderData; - batched: boolean; - renderQueueFlags: RenderQueueFlags; - - // @todo: maybe should remove later - texture?: Texture2D; - subChunk?: SubPrimitiveChunk; - - set( - component: Renderer, - material: Material, - primitive: Primitive, - subPrimitive: SubMesh, - texture?: Texture2D, - subChunk?: SubPrimitiveChunk - ): void { - this.component = component; - this.material = material; - this.primitive = primitive; - this.subPrimitive = subPrimitive; - this.texture = texture; - this.subChunk = subChunk; - } - - dispose(): void { - this.component = null; - this.material = null; - this.primitive = null; - this.subPrimitive = null; - this.shaderPasses = null; - this.shaderData && (this.shaderData = null); - - this.texture && (this.texture = null); - this.subChunk && (this.subChunk = null); - } -} diff --git a/packages/core/src/RenderPipeline/VertexMergeBatcher.ts b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts new file mode 100644 index 0000000000..c337ec21eb --- /dev/null +++ b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts @@ -0,0 +1,67 @@ +import { SpriteMask, SpriteMaskInteraction, SpriteRenderer } from "../2d"; +import { ShaderTagKey } from "../shader"; +import { RenderElement } from "./RenderElement"; + +/** + * @internal + */ +export class VertexMergeBatcher { + protected static _disableBatchTag: ShaderTagKey = ShaderTagKey.getByName("spriteDisableBatching"); + + static canBatchSprite(preElement: RenderElement, curElement: RenderElement): boolean { + const preRenderer = preElement.component; + const renderer = curElement.component; + const maskInteraction = preRenderer.maskInteraction; + + // Order: cheap reference checks → mask state → tag lookup (rare opt-out) + return ( + preElement.subChunk.chunk === curElement.subChunk.chunk && + preElement.texture === curElement.texture && + preElement.material === curElement.material && + maskInteraction === renderer.maskInteraction && + (maskInteraction === SpriteMaskInteraction.None || preRenderer.maskLayer === renderer.maskLayer) && + curElement.subShader.passes[0].getTagValue(VertexMergeBatcher._disableBatchTag) !== true + ); + } + + static canBatchSpriteMask(preElement: RenderElement, curElement: RenderElement): boolean { + if (preElement.subChunk.chunk !== curElement.subChunk.chunk) { + return false; + } + + const alphaCutoffProperty = SpriteMask._alphaCutoffProperty; + + // Compare renderer property + return ( + preElement.texture === curElement.texture && + (preElement.component).shaderData.getFloat(alphaCutoffProperty) === + (curElement.component).shaderData.getFloat(alphaCutoffProperty) + ); + } + + static batch(preElement: RenderElement | null, curElement: RenderElement): void { + const subChunk = curElement.subChunk; + const { chunk, indices: subChunkIndices } = subChunk; + + const length = subChunkIndices.length; + let startIndex = chunk.updateIndexLength; + if (preElement) { + preElement.subChunk.subMesh.count += length; + } else { + // Reset subMesh + const subMesh = subChunk.subMesh; + subMesh.start = startIndex; + subMesh.count = length; + } + + const { start, size } = subChunk.vertexArea; + const vertexOffset = start / 9; + const indices = chunk.indices; + for (let i = 0; i < length; ++i) { + indices[startIndex++] = vertexOffset + subChunkIndices[i]; + } + chunk.updateIndexLength += length; + chunk.updateVertexStart = Math.min(chunk.updateVertexStart, start); + chunk.updateVertexEnd = Math.max(chunk.updateVertexEnd, start + size); + } +} diff --git a/packages/core/src/RenderPipeline/index.ts b/packages/core/src/RenderPipeline/index.ts index 7161b57757..29431e2740 100644 --- a/packages/core/src/RenderPipeline/index.ts +++ b/packages/core/src/RenderPipeline/index.ts @@ -1,5 +1,5 @@ export { BasicRenderPipeline, RenderQueueFlags } from "./BasicRenderPipeline"; -export { BatchUtils } from "./BatchUtils"; +export { VertexMergeBatcher } from "./VertexMergeBatcher"; export { Blitter } from "./Blitter"; export { RenderQueue } from "./RenderQueue"; export { PipelineStage } from "./enums/PipelineStage"; diff --git a/packages/core/src/Renderer.ts b/packages/core/src/Renderer.ts index ab9f743c0c..5af1e71750 100644 --- a/packages/core/src/Renderer.ts +++ b/packages/core/src/Renderer.ts @@ -5,7 +5,7 @@ import { Component } from "./Component"; import { DependentMode, dependentComponents } from "./ComponentsDependencies"; import { Entity } from "./Entity"; import { RenderContext } from "./RenderPipeline/RenderContext"; -import { SubRenderElement } from "./RenderPipeline/SubRenderElement"; +import { RenderElement } from "./RenderPipeline/RenderElement"; import { Transform, TransformModifyFlags } from "./Transform"; import { assignmentClone, deepClone, ignoreClone } from "./clone/CloneManager"; import { SpriteMaskLayer } from "./enums/SpriteMaskLayer"; @@ -23,14 +23,15 @@ import { ShaderDataGroup } from "./shader/enums/ShaderDataGroup"; export class Renderer extends Component { private static _tempVector0 = new Vector3(); + /** @internal */ + static _worldMatrixProperty = ShaderProperty.getByName("renderer_ModelMat"); + /** @internal */ + static _rendererLayerProperty = ShaderProperty.getByName("renderer_Layer"); + private static _receiveShadowMacro = ShaderMacro.getByName("RENDERER_IS_RECEIVE_SHADOWS"); - private static _localMatrixProperty = ShaderProperty.getByName("renderer_LocalMat"); - private static _worldMatrixProperty = ShaderProperty.getByName("renderer_ModelMat"); private static _mvMatrixProperty = ShaderProperty.getByName("renderer_MVMat"); private static _mvpMatrixProperty = ShaderProperty.getByName("renderer_MVPMat"); - private static _mvInvMatrixProperty = ShaderProperty.getByName("renderer_MVInvMat"); private static _normalMatrixProperty = ShaderProperty.getByName("renderer_NormalMat"); - private static _rendererLayerProperty = ShaderProperty.getByName("renderer_Layer"); /** @internal */ @ignoreClone @@ -49,9 +50,6 @@ export class Renderer extends Component { /** @internal */ @assignmentClone _maskInteraction: SpriteMaskInteraction = SpriteMaskInteraction.None; - /** @internal */ - @ignoreClone - _batchedTransformShaderData: boolean = false; @assignmentClone _maskLayer: SpriteMaskLayer = SpriteMaskLayer.Layer0; @@ -74,8 +72,6 @@ export class Renderer extends Component { @ignoreClone private _mvpMatrix: Matrix = new Matrix(); @ignoreClone - private _mvInvMatrix: Matrix = new Matrix(); - @ignoreClone private _normalMatrix: Matrix = new Matrix(); @ignoreClone private _materialsInstanced: boolean[] = []; @@ -384,7 +380,6 @@ export class Renderer extends Component { this._shaderData = null; this._mvMatrix = null; this._mvpMatrix = null; - this._mvInvMatrix = null; this._normalMatrix = null; this._materialsInstanced = null; this._rendererLayer = null; @@ -393,74 +388,66 @@ export class Renderer extends Component { /** * @internal */ - _updateTransformShaderData(context: RenderContext, onlyMVP: boolean, batched: boolean): void { + _updateTransformShaderData(context: RenderContext, onlyMVP: boolean): void { const worldMatrix = this._transformEntity.transform.worldMatrix; + const { shaderData } = this; if (onlyMVP) { - this._updateProjectionRelatedShaderData(context, worldMatrix, batched); + const mvpMatrix = this._mvpMatrix; + Matrix.multiply(context.viewProjectionMatrix, worldMatrix, mvpMatrix); + shaderData.setMatrix(Renderer._mvpMatrixProperty, mvpMatrix); } else { - this._updateWorldViewRelatedShaderData(context, worldMatrix, batched); + const mvMatrix = this._mvMatrix; + const normalMatrix = this._normalMatrix; + + Matrix.multiply(context.viewMatrix, worldMatrix, mvMatrix); + Matrix.invert(worldMatrix, normalMatrix); + normalMatrix.transpose(); + + shaderData.setMatrix(Renderer._worldMatrixProperty, worldMatrix); + shaderData.setMatrix(Renderer._mvMatrixProperty, mvMatrix); + shaderData.setMatrix(Renderer._normalMatrixProperty, normalMatrix); + + const mvpMatrix = this._mvpMatrix; + Matrix.multiply(context.viewProjectionMatrix, worldMatrix, mvpMatrix); + shaderData.setMatrix(Renderer._mvpMatrixProperty, mvpMatrix); } } /** * @internal */ - _canBatch(elementA: SubRenderElement, elementB: SubRenderElement): boolean { + _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { return false; } /** * @internal */ - _batch(elementA: SubRenderElement, elementB?: SubRenderElement): void {} + _batch(preElement: RenderElement | null, curElement: RenderElement): void {} /** - * Update once per frame per renderer, not influenced by batched. + * Update once per frame per renderer. */ protected _update(context: RenderContext): void { const { layer } = this.entity; this._rendererLayer.set(layer & 65535, (layer >>> 16) & 65535, 0, 0); } - protected _updateWorldViewRelatedShaderData(context: RenderContext, worldMatrix: Matrix, batched: boolean): void { - const { shaderData, _mvInvMatrix: mvInvMatrix } = this; - if (batched) { + /** + * Update transform shader data for world-space vertices (2D renderers). + * Vertices are already in world space, so model matrix is identity. + */ + protected _updateWorldSpaceTransformShaderData(context: RenderContext, onlyMVP: boolean): void { + const { shaderData } = this; + if (onlyMVP) { + shaderData.setMatrix(Renderer._mvpMatrixProperty, context.viewProjectionMatrix); + } else { // @ts-ignore const identityMatrix = Matrix._identity; - - Matrix.invert(context.viewMatrix, mvInvMatrix); - - shaderData.setMatrix(Renderer._localMatrixProperty, identityMatrix); shaderData.setMatrix(Renderer._worldMatrixProperty, identityMatrix); shaderData.setMatrix(Renderer._mvMatrixProperty, context.viewMatrix); - shaderData.setMatrix(Renderer._mvInvMatrixProperty, mvInvMatrix); shaderData.setMatrix(Renderer._normalMatrixProperty, identityMatrix); - } else { - const mvMatrix = this._mvMatrix; - const normalMatrix = this._normalMatrix; - - Matrix.multiply(context.viewMatrix, worldMatrix, mvMatrix); - Matrix.invert(mvMatrix, mvInvMatrix); - Matrix.invert(worldMatrix, normalMatrix); - normalMatrix.transpose(); - - shaderData.setMatrix(Renderer._localMatrixProperty, this._transformEntity.transform.localMatrix); - shaderData.setMatrix(Renderer._worldMatrixProperty, worldMatrix); - shaderData.setMatrix(Renderer._mvMatrixProperty, mvMatrix); - shaderData.setMatrix(Renderer._mvInvMatrixProperty, mvInvMatrix); - shaderData.setMatrix(Renderer._normalMatrixProperty, normalMatrix); - } - - this._updateProjectionRelatedShaderData(context, worldMatrix, batched); - } - - protected _updateProjectionRelatedShaderData(context: RenderContext, worldMatrix: Matrix, batched: boolean): void { - if (batched) { - this.shaderData.setMatrix(Renderer._mvpMatrixProperty, context.viewProjectionMatrix); - } else { - const mvpMatrix = this._mvpMatrix; - Matrix.multiply(context.viewProjectionMatrix, worldMatrix, mvpMatrix); - this.shaderData.setMatrix(Renderer._mvpMatrixProperty, mvpMatrix); + shaderData.setMatrix(Renderer._mvpMatrixProperty, context.viewProjectionMatrix); } } diff --git a/packages/core/src/graphic/TransformFeedbackShader.ts b/packages/core/src/graphic/TransformFeedbackShader.ts index c200e41597..64ffd7d114 100644 --- a/packages/core/src/graphic/TransformFeedbackShader.ts +++ b/packages/core/src/graphic/TransformFeedbackShader.ts @@ -28,16 +28,17 @@ export class TransformFeedbackShader { * Get or compile a shader program for the given engine and macro combination. */ getProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram | null { - const pool = engine._getShaderProgramPool(this._id); + const map = engine._getShaderProgramMap(this._id); - let program = pool.get(macroCollection); + let program = map.get(macroCollection); if (program) return program; const { vertexSource, fragmentSource } = ShaderFactory.compilePlatformSource( engine, macroCollection, this.vertexSource, - this.fragmentSource + this.fragmentSource, + false ); program = new ShaderProgram(engine, vertexSource, fragmentSource, this.feedbackVaryings); @@ -47,7 +48,7 @@ export class TransformFeedbackShader { return null; } - pool.cache(program); + map.cache(program); return program; } } diff --git a/packages/core/src/graphic/enums/BufferBindFlag.ts b/packages/core/src/graphic/enums/BufferBindFlag.ts index 152b9d2e54..4616d1eaff 100644 --- a/packages/core/src/graphic/enums/BufferBindFlag.ts +++ b/packages/core/src/graphic/enums/BufferBindFlag.ts @@ -5,5 +5,7 @@ export enum BufferBindFlag { /** Vertex buffer binding flag. */ VertexBuffer, /** Index buffer binding flag. */ - IndexBuffer + IndexBuffer, + /** Constant buffer binding flag (WebGL2 only). */ + ConstantBuffer } diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index e08e51ac4c..7eaf45be7b 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -1,6 +1,7 @@ import { BoundingBox } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; +import { RenderElement } from "../RenderPipeline/RenderElement"; import { Renderer, RendererUpdateFlags } from "../Renderer"; import { Logger } from "../base/Logger"; import { ignoreClone } from "../clone/CloneManager"; @@ -151,9 +152,10 @@ export class MeshRenderer extends Renderer { const { _materials: materials, _engine: engine } = this; const subMeshes = mesh.subMeshes; - const renderElement = engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); - const subRenderElementPool = engine._subRenderElementPool; + const priority = this.priority; + const distanceForSort = this._distanceForSort; + const renderElementPool = engine._renderElementPool; + const renderPipeline = context.camera._renderPipeline; for (let i = 0, n = subMeshes.length; i < n; i++) { let material = materials[i]; if (!material) { @@ -163,11 +165,38 @@ export class MeshRenderer extends Renderer { material = this.engine._basicResources.meshMagentaMaterial; } - const subRenderElement = subRenderElementPool.get(); - subRenderElement.set(this, material, mesh._primitive, subMeshes[i]); - renderElement.addSubRenderElement(subRenderElement); + const renderElement = renderElementPool.get(); + renderElement.set(this, material, mesh._primitive, subMeshes[i]); + renderElement.priority = priority; + renderElement.distanceForSort = distanceForSort; + renderPipeline.pushRenderElement(context, renderElement); } - context.camera._renderPipeline.pushRenderElement(context, renderElement); + } + + /** + * @internal + */ + override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { + if (!this._engine._hardwareRenderer.isWebGL2) return false; + return ( + preElement.primitive === curElement.primitive && + preElement.subPrimitive === curElement.subPrimitive && + preElement.material === curElement.material && + this._isFrontFaceInvert() === (curElement.component)._isFrontFaceInvert() && + this.shaderData._macroCollection.isEqual(curElement.component.shaderData._macroCollection) + ); + } + + /** + * @internal + */ + override _batch(preElement: RenderElement | null, curElement: RenderElement): void { + if (!preElement) return; + const renderers = preElement.instancedRenderers; + if (renderers.length === 0) { + renderers.push(preElement.component); + } + renderers.push(curElement.component); } private _setMesh(mesh: Mesh): void { diff --git a/packages/core/src/mesh/SkinnedMeshRenderer.ts b/packages/core/src/mesh/SkinnedMeshRenderer.ts index d8e83e16bb..4d7f726184 100644 --- a/packages/core/src/mesh/SkinnedMeshRenderer.ts +++ b/packages/core/src/mesh/SkinnedMeshRenderer.ts @@ -1,6 +1,7 @@ import { BoundingBox, Vector2 } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; +import { RenderElement } from "../RenderPipeline/RenderElement"; import { RendererUpdateFlags } from "../Renderer"; import { Logger } from "../base/Logger"; import { deepClone, ignoreClone } from "../clone/CloneManager"; @@ -120,6 +121,13 @@ export class SkinnedMeshRenderer extends MeshRenderer { localBounds.max._onValueChanged = this._onLocalBoundsChanged; } + /** + * @internal + */ + override _canBatch(_preElement: RenderElement, _curElement: RenderElement): boolean { + return false; + } + /** * @internal */ diff --git a/packages/core/src/particle/ParticleRenderer.ts b/packages/core/src/particle/ParticleRenderer.ts index 563648f126..ed6e4a7275 100644 --- a/packages/core/src/particle/ParticleRenderer.ts +++ b/packages/core/src/particle/ParticleRenderer.ts @@ -176,9 +176,9 @@ export class ParticleRenderer extends Renderer { /** * @internal */ - override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean, batched: boolean): void { + override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean): void { //@todo: Don't need to update transform shader data, temp solution - super._updateTransformShaderData(context, onlyMVP, true); + this._updateWorldSpaceTransformShaderData(context, onlyMVP); } protected override _updateBounds(worldBounds: BoundingBox): void { const { generator } = this; @@ -253,10 +253,9 @@ export class ParticleRenderer extends Renderer { const engine = this._engine; const renderElement = engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); - const subRenderElement = engine._subRenderElementPool.get(); - subRenderElement.set(this, material, generator._primitive, generator._subPrimitive); - renderElement.addSubRenderElement(subRenderElement); + renderElement.set(this, material, generator._primitive, generator._subPrimitive); + renderElement.priority = this.priority; + renderElement.distanceForSort = this._distanceForSort; context.camera._renderPipeline.pushRenderElement(context, renderElement); } diff --git a/packages/core/src/shader/Shader.ts b/packages/core/src/shader/Shader.ts index bbdbc23de4..80de735c90 100644 --- a/packages/core/src/shader/Shader.ts +++ b/packages/core/src/shader/Shader.ts @@ -210,12 +210,12 @@ export class Shader implements IReferable { const passes = subShaders[i].passes; for (let j = 0, m = passes.length; j < m; j++) { const pass = passes[j]; - const passShaderProgramPools = pass._shaderProgramPools; - for (let k = passShaderProgramPools.length - 1; k >= 0; k--) { - const shaderProgramPool = passShaderProgramPools[k]; - if (shaderProgramPool.engine !== engine) continue; - shaderProgramPool._destroy(); - passShaderProgramPools.splice(k, 1); + const passShaderProgramMaps = pass._shaderProgramMaps; + for (let k = passShaderProgramMaps.length - 1; k >= 0; k--) { + const map = passShaderProgramMaps[k]; + if (map.engine !== engine) continue; + map.destroy(); + passShaderProgramMaps.splice(k, 1); } } } diff --git a/packages/core/src/shader/ShaderBlockProperty.ts b/packages/core/src/shader/ShaderBlockProperty.ts new file mode 100644 index 0000000000..fd47b4cecc --- /dev/null +++ b/packages/core/src/shader/ShaderBlockProperty.ts @@ -0,0 +1,28 @@ +/** + * @internal + * Shader block property, used to identify uniform blocks by id. + */ +export class ShaderBlockProperty { + private static _counter = 0; + private static _nameMap: Record = Object.create(null); + + /** + * Get shader block property by name. + * @param name - Name of the uniform block + * @returns Shader block property + */ + static getByName(name: string): ShaderBlockProperty { + const nameMap = ShaderBlockProperty._nameMap; + return (nameMap[name] ??= new ShaderBlockProperty(name)); + } + + /** Uniform block name. */ + readonly name: string; + /** @internal */ + readonly _uniqueId: number; + + private constructor(name: string) { + this.name = name; + this._uniqueId = ShaderBlockProperty._counter++; + } +} diff --git a/packages/core/src/shader/ShaderMacroCollection.ts b/packages/core/src/shader/ShaderMacroCollection.ts index 749abc22ee..3efb98b77c 100644 --- a/packages/core/src/shader/ShaderMacroCollection.ts +++ b/packages/core/src/shader/ShaderMacroCollection.ts @@ -158,6 +158,20 @@ export class ShaderMacroCollection { return (this._mask[index] & macro._maskValue) !== 0; } + /** + * Whether this macro collection is equal to another. + * @param other - macro collection to compare + */ + isEqual(other: ShaderMacroCollection): boolean { + if (this._length !== other._length) return false; + const mask = this._mask; + const otherMask = other._mask; + for (let i = 0, n = this._length; i < n; i++) { + if (mask[i] !== otherMask[i]) return false; + } + return true; + } + /** * Clear this macro collection. */ diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 8c11182df7..6dc63f0220 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -1,13 +1,14 @@ import { Engine } from "../Engine"; +import { InstanceBuffer } from "../RenderPipeline/InstanceBuffer"; import { PipelineStage } from "../RenderPipeline/enums/PipelineStage"; import { GLCapabilityType } from "../base/Constant"; -import { ShaderFactory } from "../shaderlib"; +import { ShaderFactory, InstanceBufferLayout } from "../shaderlib/ShaderFactory"; import { Shader } from "./Shader"; import { ShaderMacro } from "./ShaderMacro"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderPart } from "./ShaderPart"; +import { ShaderProgramMap } from "./ShaderProgramMap"; import { ShaderProgram } from "./ShaderProgram"; -import { ShaderProgramPool } from "./ShaderProgramPool"; import { ShaderProperty } from "./ShaderProperty"; import { ShaderLanguage } from "./enums/ShaderLanguage"; import { RenderState } from "./state/RenderState"; @@ -47,7 +48,7 @@ export class ShaderPass extends ShaderPart { /** @internal */ _renderStateDataMap: Record = {}; /** @internal */ - _shaderProgramPools: ShaderProgramPool[] = []; + _shaderProgramMaps: ShaderProgramMap[] = []; private _vertexSource: string; private _fragmentSource: string; @@ -110,15 +111,15 @@ export class ShaderPass extends ShaderPart { * @internal */ _getShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { - const shaderProgramPool = engine._getShaderProgramPool(this._shaderPassId, this._shaderProgramPools); - let shaderProgram = shaderProgramPool.get(macroCollection); + const shaderProgramMap = engine._getShaderProgramMap(this._shaderPassId, this._shaderProgramMaps); + let shaderProgram = shaderProgramMap.get(macroCollection); if (shaderProgram) { return shaderProgram; } - shaderProgram = this._getCanonicalShaderProgram(engine, macroCollection); + shaderProgram = this._compileShaderProgram(engine, macroCollection); - shaderProgramPool.cache(shaderProgram); + shaderProgramMap.cache(shaderProgram); return shaderProgram; } @@ -126,32 +127,46 @@ export class ShaderPass extends ShaderPart { * @internal */ _destroy(): void { - const shaderProgramPools = this._shaderProgramPools; - for (let i = 0, n = shaderProgramPools.length; i < n; i++) { - const shaderProgramPool = shaderProgramPools[i]; - shaderProgramPool._destroy(); - delete shaderProgramPool.engine._shaderProgramPools[this._shaderPassId]; + const shaderProgramMaps = this._shaderProgramMaps; + for (let i = 0, n = shaderProgramMaps.length; i < n; i++) { + const map = shaderProgramMaps[i]; + map.destroy(); + delete map.engine._shaderProgramMaps[this._shaderPassId]; } - // Clear array storing multiple engine shader program pools - shaderProgramPools.length = 0; + shaderProgramMaps.length = 0; } - private _getCanonicalShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { - if (this._platformTarget != undefined) { - return this._getShaderLabProgram(engine, macroCollection); - } + private _compileShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { + const isGPUInstance = macroCollection.isEnable(InstanceBuffer.gpuInstanceMacro); + const { vertexSource, fragmentSource, instanceLayout } = + this._platformTarget != undefined + ? this._compileShaderLabSource(engine, macroCollection, isGPUInstance) + : this._compilePlatformSource(engine, macroCollection, isGPUInstance); + + const program = new ShaderProgram(engine, vertexSource, fragmentSource); + program._instanceLayout = instanceLayout; + return program; + } - const { vertexSource, fragmentSource } = ShaderFactory.compilePlatformSource( + private _compilePlatformSource( + engine: Engine, + macroCollection: ShaderMacroCollection, + isGPUInstance: boolean + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { + return ShaderFactory.compilePlatformSource( engine, macroCollection, this._vertexSource, - this._fragmentSource + this._fragmentSource, + isGPUInstance ); - - return new ShaderProgram(engine, vertexSource, fragmentSource); } - private _getShaderLabProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { + private _compileShaderLabSource( + engine: Engine, + macroCollection: ShaderMacroCollection, + isGPUInstance: boolean + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { const isWebGL2: boolean = engine._hardwareRenderer.isWebGL2; const shaderMacroList = new Array(); ShaderMacro._getMacrosElements(macroCollection, shaderMacroList); @@ -169,6 +184,14 @@ export class ShaderPass extends ShaderPart { noIncludeVertex = Shader._shaderLab._parseMacros(noIncludeVertex, shaderMacroList); noIncludeFrag = Shader._shaderLab._parseMacros(noIncludeFrag, shaderMacroList); + let instanceLayout: InstanceBufferLayout | null = null; + if (isGPUInstance) { + const injected = ShaderFactory.injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag); + noIncludeVertex = injected.vertexSource; + noIncludeFrag = injected.fragmentSource; + instanceLayout = injected.instanceLayout; + } + if (isWebGL2 && this._platformTarget === ShaderLanguage.GLSLES100) { noIncludeVertex = ShaderFactory.convertTo300(noIncludeVertex); noIncludeFrag = ShaderFactory.convertTo300(noIncludeFrag, true); @@ -176,15 +199,16 @@ export class ShaderPass extends ShaderPart { const versionStr = isWebGL2 ? "#version 300 es" : "#version 100"; - const vertexSource = ` ${versionStr} + return { + vertexSource: ` ${versionStr} ${noIncludeVertex} - `; - const fragmentSource = ` ${versionStr} - ${isWebGL2 ? "" : ShaderFactory._shaderExtension} + `, + fragmentSource: ` ${versionStr} + ${isWebGL2 ? "" : ShaderFactory.shaderExtension} ${precisionStr} ${noIncludeFrag} - `; - - return new ShaderProgram(engine, vertexSource, fragmentSource); + `, + instanceLayout + }; } } diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index 22151e5f77..9548548db6 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -7,7 +7,9 @@ import { ShaderData } from "./ShaderData"; import { ShaderProperty } from "./ShaderProperty"; import { ShaderUniform } from "./ShaderUniform"; import { ShaderUniformBlock } from "./ShaderUniformBlock"; +import { ShaderBlockProperty } from "./ShaderBlockProperty"; import { ShaderDataGroup } from "./enums/ShaderDataGroup"; +import { InstanceBufferLayout, ShaderFactory } from "../shaderlib/ShaderFactory"; /** * Shader program, corresponding to the GPU shader program. @@ -52,7 +54,11 @@ export class ShaderProgram { /** @internal */ _uploadMaterialId: number = -1; + /** @internal */ + _instanceLayout: InstanceBufferLayout | null = null; + attributeLocation: Record = Object.create(null); + uniformBlockIds: number[] = []; // @todo: move to RHI. private _isValid: boolean; @@ -339,6 +345,9 @@ export class ShaderProgram { } const location = gl.getUniformLocation(program, name); + // UBO members have no individual location, skip them + if (location === null) return; + shaderUniform.name = name; shaderUniform.propertyId = ShaderProperty.getByName(name)._uniqueId; shaderUniform.location = location; @@ -412,9 +421,33 @@ export class ShaderProgram { shaderUniform.cacheValue = new Vector4(0, 0, 0); } break; + case gl.FLOAT_MAT2: + shaderUniform.applyFunc = shaderUniform.uploadMat2; + break; + case gl.FLOAT_MAT3: + shaderUniform.applyFunc = shaderUniform.uploadMat3; + break; case gl.FLOAT_MAT4: shaderUniform.applyFunc = isArray ? shaderUniform.uploadMat4v : shaderUniform.uploadMat4; break; + case (gl).FLOAT_MAT2x3: + shaderUniform.applyFunc = shaderUniform.uploadMat2x3; + break; + case (gl).FLOAT_MAT2x4: + shaderUniform.applyFunc = shaderUniform.uploadMat2x4; + break; + case (gl).FLOAT_MAT3x2: + shaderUniform.applyFunc = shaderUniform.uploadMat3x2; + break; + case (gl).FLOAT_MAT3x4: + shaderUniform.applyFunc = shaderUniform.uploadMat3x4; + break; + case (gl).FLOAT_MAT4x2: + shaderUniform.applyFunc = shaderUniform.uploadMat4x2; + break; + case (gl).FLOAT_MAT4x3: + shaderUniform.applyFunc = shaderUniform.uploadMat4x3; + break; case gl.SAMPLER_2D: case gl.SAMPLER_CUBE: case (gl).UNSIGNED_INT_SAMPLER_2D: @@ -476,6 +509,21 @@ export class ShaderProgram { attributeInfos.forEach(({ name }) => { this.attributeLocation[name] = gl.getAttribLocation(program, name); }); + + // Record uniform block indices and bind binding points (WebGL2 only) + if (this._engine._hardwareRenderer.isWebGL2) { + const gl2 = gl; + const bindingMap = ShaderFactory.uniformBlockBindingMap; + const blockCount = gl2.getProgramParameter(program, gl2.ACTIVE_UNIFORM_BLOCKS) ?? 0; + for (let i = 0; i < blockCount; i++) { + const id = ShaderBlockProperty.getByName(gl2.getActiveUniformBlockName(program, i))._uniqueId; + this.uniformBlockIds[i] = id; + const bindingPoint = bindingMap[id]; + if (bindingPoint !== undefined) { + gl2.uniformBlockBinding(program, i, bindingPoint); + } + } + } } private _getUniformInfos(): WebGLActiveInfo[] { diff --git a/packages/core/src/shader/ShaderProgramPool.ts b/packages/core/src/shader/ShaderProgramMap.ts similarity index 56% rename from packages/core/src/shader/ShaderProgramPool.ts rename to packages/core/src/shader/ShaderProgramMap.ts index 3b43a07afa..5673708863 100644 --- a/packages/core/src/shader/ShaderProgramPool.ts +++ b/packages/core/src/shader/ShaderProgramMap.ts @@ -2,23 +2,26 @@ import { Engine } from "../Engine"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderProgram } from "./ShaderProgram"; +type Tree = { + [key: number]: Tree | ShaderProgram; +}; + /** - * Shader program pool. + * Map keyed by ShaderMacroCollection bitmask, caching ShaderProgram instances. * @internal */ -export class ShaderProgramPool { +export class ShaderProgramMap { + engine: Engine; + private _cacheHierarchyDepth: number = 1; - private _cacheMap: Tree = Object.create(null); + private _cacheMap: Tree = Object.create(null); private _lastQueryMap: Record; private _lastQueryKey: number; - constructor(public engine: Engine) {} + constructor(engine: Engine) { + this.engine = engine; + } - /** - * Get shader program by macro collection. - * @param macros - macro collection - * @returns shader program - */ get(macros: ShaderMacroCollection): ShaderProgram | null { let cacheMap = this._cacheMap; const maskLength = macros._length; @@ -33,41 +36,31 @@ export class ShaderProgramPool { const maxEndIndex = this._cacheHierarchyDepth - 1; for (let i = 0; i < maxEndIndex; i++) { const subMask = endIndex < i ? 0 : mask[i]; - let subCacheShaders = >cacheMap[subMask]; - subCacheShaders || (cacheMap[subMask] = subCacheShaders = Object.create(null)); - cacheMap = subCacheShaders; + let subCache = cacheMap[subMask]; + subCache || (cacheMap[subMask] = subCache = Object.create(null)); + cacheMap = subCache; } const cacheKey = endIndex < maxEndIndex ? 0 : mask[maxEndIndex]; - const shader = (>cacheMap)[cacheKey]; - if (!shader) { + const value = (>cacheMap)[cacheKey]; + if (!value) { this._lastQueryKey = cacheKey; this._lastQueryMap = >cacheMap; } - return shader; + return value; } - /** - * Cache the shader program. - * - * @remarks - * The method must return an empty value after calling get() to run normally. - * - * @param shaderProgram - shader program - */ - cache(shaderProgram: ShaderProgram): void { - this._lastQueryMap[this._lastQueryKey] = shaderProgram; + cache(value: ShaderProgram): void { + this._lastQueryMap[this._lastQueryKey] = value; } - /** - * @internal - */ - _destroy(): void { - this._recursiveDestroy(0, this._cacheMap); + destroy(): void { + this._recursiveForEach(0, this._cacheMap); this._cacheMap = Object.create(null); + this._cacheHierarchyDepth = 1; } - private _recursiveDestroy(hierarchy: number, cacheMap: Tree): void { + private _recursiveForEach(hierarchy: number, cacheMap: Tree): void { if (hierarchy === this._cacheHierarchyDepth - 1) { for (let k in cacheMap) { (cacheMap[k]).destroy(); @@ -76,35 +69,30 @@ export class ShaderProgramPool { } ++hierarchy; for (let k in cacheMap) { - this._recursiveDestroy(hierarchy, >cacheMap[k]); + this._recursiveForEach(hierarchy, cacheMap[k]); } } private _resizeCacheMapHierarchy( - cacheMap: Tree, + cacheMap: Tree, hierarchy: number, currentHierarchy: number, increaseHierarchy: number ): void { - // Only expand but not shrink if (hierarchy == currentHierarchy - 1) { for (let k in cacheMap) { - const shader = cacheMap[k]; + const value = cacheMap[k]; let subCacheMap = cacheMap; for (let i = 0; i < increaseHierarchy; i++) { subCacheMap[i == 0 ? k : 0] = subCacheMap = Object.create(null); } - subCacheMap[0] = shader; + subCacheMap[0] = value; } } else { hierarchy++; for (let k in cacheMap) { - this._resizeCacheMapHierarchy(>cacheMap[k], hierarchy, currentHierarchy, increaseHierarchy); + this._resizeCacheMapHierarchy(cacheMap[k], hierarchy, currentHierarchy, increaseHierarchy); } } } } - -type Tree = { - [key: number]: Tree | T; -}; diff --git a/packages/core/src/shader/ShaderUniform.ts b/packages/core/src/shader/ShaderUniform.ts index 07017a4abc..90d9233c1b 100644 --- a/packages/core/src/shader/ShaderUniform.ts +++ b/packages/core/src/shader/ShaderUniform.ts @@ -237,6 +237,14 @@ export class ShaderUniform { this._gl.uniform4iv(shaderUniform.location, value); } + uploadMat2(shaderUniform: ShaderUniform, value: Float32Array): void { + this._gl.uniformMatrix2fv(shaderUniform.location, false, value); + } + + uploadMat3(shaderUniform: ShaderUniform, value: Float32Array): void { + this._gl.uniformMatrix3fv(shaderUniform.location, false, value); + } + uploadMat4(shaderUniform: ShaderUniform, value: Matrix): void { this._gl.uniformMatrix4fv(shaderUniform.location, false, value.elements); } @@ -245,6 +253,30 @@ export class ShaderUniform { this._gl.uniformMatrix4fv(shaderUniform.location, false, value); } + uploadMat2x3(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix2x3fv(shaderUniform.location, false, value); + } + + uploadMat2x4(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix2x4fv(shaderUniform.location, false, value); + } + + uploadMat3x2(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix3x2fv(shaderUniform.location, false, value); + } + + uploadMat3x4(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix3x4fv(shaderUniform.location, false, value); + } + + uploadMat4x2(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix4x2fv(shaderUniform.location, false, value); + } + + uploadMat4x3(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix4x3fv(shaderUniform.location, false, value); + } + uploadTexture(shaderUniform: ShaderUniform, value: Texture): void { const rhi = this._rhi; rhi.activeTexture(shaderUniform.textureIndex as GLenum); diff --git a/packages/core/src/shader/enums/ConstantBufferBindingPoint.ts b/packages/core/src/shader/enums/ConstantBufferBindingPoint.ts new file mode 100644 index 0000000000..595d159924 --- /dev/null +++ b/packages/core/src/shader/enums/ConstantBufferBindingPoint.ts @@ -0,0 +1,7 @@ +/** + * @internal + * Constant buffer binding point allocation. + */ +export enum ConstantBufferBindingPoint { + RendererInstance = 0 +} diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index e314e4409d..907d918320 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -1,13 +1,28 @@ +import { Matrix, Vector2, Vector3, Vector4 } from "@galacean/engine-math"; import { GLCapabilityType } from "../base/Constant"; import { Logger } from "../base/Logger"; import { Engine } from "../Engine"; +import { Renderer } from "../Renderer"; +import { ShaderDataGroup } from "../shader/enums/ShaderDataGroup"; import { ShaderMacro } from "../shader/ShaderMacro"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; +import { ShaderProperty } from "../shader/ShaderProperty"; +import { ShaderBlockProperty } from "../shader/ShaderBlockProperty"; +import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { ShaderLib } from "./ShaderLib"; +/** + * @internal + */ export class ShaderFactory { - /** @internal */ - static readonly _shaderExtension = [ + static readonly RENDERER_INSTANCE_BLOCK_NAME = "RendererInstanceData"; + + static readonly uniformBlockBindingMap: Record = { + [ShaderBlockProperty.getByName(ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME)._uniqueId]: + ConstantBufferBindingPoint.RendererInstance + }; + + static readonly shaderExtension = [ "GL_EXT_shader_texture_lod", "GL_OES_standard_derivatives", "GL_EXT_draw_buffers", @@ -16,35 +31,127 @@ export class ShaderFactory { .map((e) => `#extension ${e} : enable\n`) .join(""); - private static readonly _has300OutInFragReg = /\bout\s+(?:\w+\s+)?(?:vec4)\s+(?:\w+)\s*;/; // [layout(location = 0)] out [highp] vec4 [color]; + private static readonly _std140TypeInfoMap: Record = { + float: { size: 4, align: 4 }, + int: { size: 4, align: 4 }, + uint: { size: 4, align: 4 }, + vec2: { size: 8, align: 8 }, + ivec2: { size: 8, align: 8 }, + vec3: { size: 12, align: 16 }, + ivec3: { size: 12, align: 16 }, + vec4: { size: 16, align: 16 }, + ivec4: { size: 16, align: 16 }, + mat4: { size: 64, align: 16 }, + mat3x4: { size: 48, align: 16 } + }; + + private static readonly _has300OutInFragReg = /\bout\s+(?:\w+\s+)?vec4\s+\w+\s*;/; + + private static readonly _precisionStr = ` +#ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + precision highp int; +#else + precision mediump float; + precision mediump int; +#endif +`; + + private static readonly _derivedDefines = `\ +#define renderer_MVMat (camera_ViewMat * renderer_ModelMat) +#define renderer_MVPMat (camera_VPMat * renderer_ModelMat) +#define renderer_NormalMat mat4(transpose(inverse(mat3(renderer_ModelMat))))`; + + // Built-in renderer uniforms. value=true means derived (remove but not added to UBO) + private static readonly _builtinRendererUniforms: Record = { + renderer_ModelMat: false, + renderer_Layer: false, + renderer_MVMat: true, + renderer_MVPMat: true, + renderer_NormalMat: true + }; + + private static readonly _uboUniformRegex = + /^[ \t]*uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*(\[.+?\])?\s*;/gm; + + private static _packFuncMap: Record = (() => { + const packScalar = (v: Float32Array | Int32Array, o: number, val: number) => { + v[o] = val; + }; + const packVec2 = (v: Float32Array | Int32Array, o: number, val: Vector2) => { + v[o] = val.x; + v[o + 1] = val.y; + }; + const packVec3 = (v: Float32Array | Int32Array, o: number, val: Vector3) => { + v[o] = val.x; + v[o + 1] = val.y; + v[o + 2] = val.z; + }; + const packVec4 = (v: Float32Array | Int32Array, o: number, val: Vector4) => { + v[o] = val.x; + v[o + 1] = val.y; + v[o + 2] = val.z; + v[o + 3] = val.w; + }; + return { + float: packScalar, + int: packScalar, + uint: packScalar, + vec2: packVec2, + ivec2: packVec2, + vec3: packVec3, + ivec3: packVec3, + vec4: packVec4, + ivec4: packVec4, + mat4: (v: Float32Array | Int32Array, o: number, val: Matrix) => { + const e = val.elements; + for (let k = 0; k < 16; k++) v[o + k] = e[k]; + }, + // Affine mat4 stored as mat3x4: write 3 transposed rows (row3 is always 0,0,0,1) + mat3x4: (v: Float32Array | Int32Array, o: number, val: Matrix) => { + const e = val.elements; + // Row 0 + v[o] = e[0]; + v[o + 1] = e[4]; + v[o + 2] = e[8]; + v[o + 3] = e[12]; + // Row 1 + v[o + 4] = e[1]; + v[o + 5] = e[5]; + v[o + 6] = e[9]; + v[o + 7] = e[13]; + // Row 2 + v[o + 8] = e[2]; + v[o + 9] = e[6]; + v[o + 10] = e[10]; + v[o + 11] = e[14]; + } + }; + })(); static parseCustomMacros(macros: ShaderMacro[]) { return macros.map((m) => `#define ${m.value ? m.name + ` ` + m.value : m.name}\n`).join(""); } /** - * @internal * Compile vertex and fragment source with standard macros, includes, and version header. - * @param engine - Engine instance - * @param macroCollection - Current macro collection - * @param vertexSource - Raw vertex shader source (may contain #include) - * @param fragmentSource - Raw fragment shader source - * @returns Compiled { vertexSource, fragmentSource } ready for ShaderProgram */ static compilePlatformSource( engine: Engine, macroCollection: ShaderMacroCollection, vertexSource: string, - fragmentSource: string - ): { vertexSource: string; fragmentSource: string } { - const isWebGL2 = engine._hardwareRenderer.isWebGL2; + fragmentSource: string, + isGPUInstance: boolean + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { + const rhi = engine._hardwareRenderer; + const isWebGL2 = rhi.isWebGL2; const shaderMacroList = new Array(); ShaderMacro._getMacrosElements(macroCollection, shaderMacroList); shaderMacroList.push(ShaderMacro.getByName(isWebGL2 ? "GRAPHICS_API_WEBGL2" : "GRAPHICS_API_WEBGL1")); - if (engine._hardwareRenderer.canIUse(GLCapabilityType.shaderTextureLod)) { + if (rhi.canIUse(GLCapabilityType.shaderTextureLod)) { shaderMacroList.push(ShaderMacro.getByName("HAS_TEX_LOD")); } - if (engine._hardwareRenderer.canIUse(GLCapabilityType.standardDerivatives)) { + if (rhi.canIUse(GLCapabilityType.standardDerivatives)) { shaderMacroList.push(ShaderMacro.getByName("HAS_DERIVATIVES")); } @@ -55,28 +162,74 @@ export class ShaderFactory { noIncludeVertex = macroStr + noIncludeVertex; noIncludeFrag = macroStr + noIncludeFrag; + let instanceLayout: InstanceBufferLayout | null = null; + if (isGPUInstance) { + const injected = ShaderFactory.injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag); + noIncludeVertex = injected.vertexSource; + noIncludeFrag = injected.fragmentSource; + instanceLayout = injected.instanceLayout; + } + if (isWebGL2) { noIncludeVertex = ShaderFactory.convertTo300(noIncludeVertex); noIncludeFrag = ShaderFactory.convertTo300(noIncludeFrag, true); } const versionStr = isWebGL2 ? "#version 300 es" : "#version 100"; - const precisionStr = ` -#ifdef GL_FRAGMENT_PRECISION_HIGH - precision highp float; - precision highp int; -#else - precision mediump float; - precision mediump int; -#endif -`; return { vertexSource: `${versionStr}\nprecision highp float;\n${noIncludeVertex}`, - fragmentSource: `${versionStr}\n${isWebGL2 ? "" : ShaderFactory._shaderExtension}${precisionStr}${noIncludeFrag}` + fragmentSource: `${versionStr}\n${isWebGL2 ? "" : ShaderFactory.shaderExtension}${ShaderFactory._precisionStr}${noIncludeFrag}`, + instanceLayout }; } + /** + * Scan VS/FS for renderer-group `uniform` declarations, replace them with a shared + * std140 UBO (instanced array), and emit `#define` remapping so original uniform + * names resolve to `rendererData[instanceID].field`. + */ + static injectInstanceUBO( + engine: Engine, + vertexSource: string, + fragmentSource: string + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { + // 1. Scan & strip renderer uniforms from both stages, collect into fieldMap + const fieldMap: Record = Object.create(null); + vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); + fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); + + // Fast empty check without allocating an array + let hasField = false; + for (const _ in fieldMap) { + hasField = true; + break; + } + if (!hasField) return { vertexSource, fragmentSource, instanceLayout: null }; + + // 2. Compute std140 layout (field offsets, struct size, max instance count) + const instanceLayout = ShaderFactory._buildLayout(engine, fieldMap); + + // 3. Generate GLSL UBO block and inject into both stages + const { instanceFields } = instanceLayout; + const uboDecl = ShaderFactory._buildUBODeclaration(instanceLayout); + const fieldDefinesVS = ShaderFactory._buildFieldDefines(instanceFields, "gl_InstanceID"); + const fieldDefinesFS = ShaderFactory._buildFieldDefines(instanceFields, "v_instanceID"); + const derivedDefines = ShaderFactory._derivedDefines; + + const vsBlock = `${uboDecl}flat out int v_instanceID;\n${fieldDefinesVS}\n${derivedDefines}\n`; + const fsBlock = `${uboDecl}flat in int v_instanceID;\n${fieldDefinesFS}\n${derivedDefines}\n`; + + vertexSource = vsBlock + vertexSource; + vertexSource = vertexSource.replace( + /void\s+main\s*\(\s*\)\s*\{/, + "void main() {\n v_instanceID = gl_InstanceID;" + ); + fragmentSource = fsBlock + fragmentSource; + + return { vertexSource, fragmentSource, instanceLayout }; + } + static registerInclude(includeName: string, includeSource: string) { if (ShaderLib[includeName]) { throw `The "${includeName}" shader include already exist`; @@ -93,25 +246,21 @@ export class ShaderFactory { * since `ShaderLab` use the same parsing function but different syntax for `#include` --- `/^[ \t]*#include +"([\w\d.]+)"/gm` */ static parseIncludes(src: string, regex = /^[ \t]*#include +<([\w\d.]+)>/gm) { - function replace(match, slice) { - var replace = ShaderLib[slice]; - - if (replace === undefined) { + return src.replace(regex, (match, slice) => { + const replacement = ShaderLib[slice]; + if (replacement === undefined) { Logger.error(`Shader slice "${match.trim()}" not founded.`); return ""; } - - return ShaderFactory.parseIncludes(replace, regex); - } - - return src.replace(regex, replace); + return ShaderFactory.parseIncludes(replacement, regex); + }); } /** * Convert lower GLSL version to GLSL 300 es. * @param shader - code * @param isFrag - Whether it is a fragment shader. - * */ + */ static convertTo300(shader: string, isFrag?: boolean) { shader = shader.replace(/\bvarying\b/g, isFrag ? "in" : "out"); shader = shader.replace(/\btexture(2D|Cube)\b/g, "texture"); @@ -124,7 +273,7 @@ export class ShaderFactory { if (isFrag) { shader = shader.replace(/\bgl_FragDepthEXT\b/g, "gl_FragDepth"); - if (!ShaderFactory._has300Output(shader)) { + if (!ShaderFactory._has300OutInFragReg.test(shader)) { const isMRT = /\bgl_FragData\[.+?\]/g.test(shader); if (isMRT) { shader = shader.replace(/\bgl_FragColor\b/g, "gl_FragData[0]"); @@ -142,8 +291,109 @@ export class ShaderFactory { return shader; } - private static _has300Output(fragmentShader: string): boolean { - return ShaderFactory._has300OutInFragReg.test(fragmentShader); + private static _scanInstanceUniforms(source: string, fieldMap: Record): string { + const builtinUniforms = ShaderFactory._builtinRendererUniforms; + return source.replace(ShaderFactory._uboUniformRegex, (match, type, name, arraySize) => { + if (type.includes("sampler")) return match; + const isDerived = builtinUniforms[name]; + if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) + return match; + if (isDerived) return ""; + // Array uniforms not supported in instancing UBO, remove to fail shader compilation + if (arraySize) { + Logger.error(`GPU Instancing does not support array uniform "${name}${arraySize}"`); + return ""; + } + // ModelMat is affine, store as mat3x4 (3 columns) to save 16 bytes per instance + fieldMap[ShaderProperty.getByName(name)._uniqueId] = + type === "mat4" && name === "renderer_ModelMat" ? "mat3x4" : type; + return ""; + }); + } + + private static _buildLayout(engine: Engine, fieldMap: Record): InstanceBufferLayout { + const maxUBOSize = engine._hardwareRenderer.maxUniformBlockSize; + const std140Map = ShaderFactory._std140TypeInfoMap; + const instanceFields: InstanceFieldInfo[] = []; + let currentOffset = 0; + + const packFuncMap = ShaderFactory._packFuncMap; + const addField = (id: number): void => { + const type = fieldMap[id]; + const info = std140Map[type]; + if (!info) return; + currentOffset = Math.ceil(currentOffset / info.align) * info.align; + instanceFields.push({ + property: ShaderProperty._propertyIdMap[id], + type, + offset: currentOffset, + offsetInElements: currentOffset / 4, + useIntView: type[0] === "i" || type[0] === "u", + pack: packFuncMap[type] + }); + currentOffset += info.size; + }; + + // Priority fields first + const modelMatId = Renderer._worldMatrixProperty._uniqueId; + const layerId = Renderer._rendererLayerProperty._uniqueId; + if (modelMatId in fieldMap) { + addField(modelMatId); + delete fieldMap[modelMatId]; + } + if (layerId in fieldMap) { + addField(layerId); + delete fieldMap[layerId]; + } + + // Remaining fields sorted by id + const keys: number[] = []; + for (const k in fieldMap) keys.push(+k); + keys.sort((a, b) => a - b); + for (let i = 0; i < keys.length; i++) addField(keys[i]); + + const structSize = Math.ceil(currentOffset / 16) * 16; + const instanceMaxCount = Math.floor(maxUBOSize / structSize); + + return { instanceFields, instanceMaxCount, structSize }; + } + + private static _buildUBODeclaration(layout: InstanceBufferLayout): string { + const { instanceFields, instanceMaxCount } = layout; + const structLines: string[] = []; + for (let i = 0; i < instanceFields.length; i++) { + const { type, property } = instanceFields[i]; + structLines.push(` ${type} ${property.name};`); + } + return ( + `#define INSTANCE_MAX_COUNT ${instanceMaxCount}\n` + + `struct RendererInstanceStruct {\n${structLines.join("\n")}\n};\n` + + `layout(std140) uniform ${ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME} {\n` + + ` RendererInstanceStruct rendererData[INSTANCE_MAX_COUNT];\n};\n` + ); + } + + private static _buildFieldDefines(fields: InstanceFieldInfo[], idExpr: string): string { + const accessor = `rendererData[${idExpr}]`; + const lines: string[] = []; + for (let i = 0; i < fields.length; i++) { + const { type, property } = fields[i]; + const n = property.name; + if (type === "mat3x4") { + // mat3x4 stores 3 transposed rows; reconstruct column-major mat4 with row3=(0,0,0,1) + const m = `${accessor}.${n}`; + lines.push( + `#define ${n} mat4(` + + `vec4(${m}[0].x,${m}[1].x,${m}[2].x,0.0),` + + `vec4(${m}[0].y,${m}[1].y,${m}[2].y,0.0),` + + `vec4(${m}[0].z,${m}[1].z,${m}[2].z,0.0),` + + `vec4(${m}[0].w,${m}[1].w,${m}[2].w,1.0))` + ); + } else { + lines.push(`#define ${n} ${accessor}.${n}`); + } + } + return lines.join("\n"); } private static _replaceMRTShader(shader: string, result: string[]): string { @@ -166,3 +416,24 @@ export class ShaderFactory { return shader; } } + +export interface InstanceFieldInfo { + property: ShaderProperty; + type: string; + offset: number; + /** offset / 4, precomputed to avoid repeated division in upload loop */ + offsetInElements: number; + useIntView: boolean; + pack: InstancePackFunc; +} + +/** + * @internal + */ +export interface InstanceBufferLayout { + instanceFields: InstanceFieldInfo[]; + instanceMaxCount: number; + structSize: number; +} + +type InstancePackFunc = (view: Float32Array | Int32Array, offset: number, value: any) => void; diff --git a/packages/core/src/shaderlib/extra/depthOnly.vs.glsl b/packages/core/src/shaderlib/extra/depthOnly.vs.glsl index 06ea2dc057..ecaea3e6ce 100644 --- a/packages/core/src/shaderlib/extra/depthOnly.vs.glsl +++ b/packages/core/src/shaderlib/extra/depthOnly.vs.glsl @@ -2,8 +2,6 @@ #include #include #include -uniform mat4 camera_VPMat; - void main() { diff --git a/packages/core/src/shaderlib/extra/shadow-map.vs.glsl b/packages/core/src/shaderlib/extra/shadow-map.vs.glsl index fae6a4b849..2da9dc1092 100644 --- a/packages/core/src/shaderlib/extra/shadow-map.vs.glsl +++ b/packages/core/src/shaderlib/extra/shadow-map.vs.glsl @@ -3,7 +3,6 @@ #include #include #include -uniform mat4 camera_VPMat; uniform vec2 scene_ShadowBias; // x: depth bias, y: normal bias uniform vec3 scene_LightDirection; @@ -26,7 +25,7 @@ void main() { #include #include #include - + vec4 positionWS = renderer_ModelMat * position; positionWS.xyz = applyShadowBias(positionWS.xyz); diff --git a/packages/core/src/shaderlib/extra/skybox.vs.glsl b/packages/core/src/shaderlib/extra/skybox.vs.glsl index 54c7185dd8..de9692f754 100644 --- a/packages/core/src/shaderlib/extra/skybox.vs.glsl +++ b/packages/core/src/shaderlib/extra/skybox.vs.glsl @@ -1,7 +1,5 @@ #include -uniform mat4 camera_VPMat; - varying vec3 v_cubeUV; uniform float material_Rotation; diff --git a/packages/core/src/shaderlib/normal_vert.glsl b/packages/core/src/shaderlib/normal_vert.glsl index 096a4595d1..906be70ef7 100644 --- a/packages/core/src/shaderlib/normal_vert.glsl +++ b/packages/core/src/shaderlib/normal_vert.glsl @@ -1,9 +1,10 @@ #ifndef MATERIAL_OMIT_NORMAL #ifdef RENDERER_HAS_NORMAL - v_normal = normalize( mat3(renderer_NormalMat) * normal ); + mat3 normalMat = mat3(renderer_NormalMat); + v_normal = normalize( normalMat * normal ); #if defined(RENDERER_HAS_TANGENT) && ( defined(MATERIAL_HAS_NORMALTEXTURE) || defined(MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE) || defined(MATERIAL_ENABLE_ANISOTROPY) ) - vec3 tangentW = normalize( mat3(renderer_NormalMat) * tangent.xyz ); + vec3 tangentW = normalize( normalMat * tangent.xyz ); vec3 bitangentW = cross( v_normal, tangentW ) * tangent.w; v_TBN = mat3( tangentW, bitangentW, v_normal ); diff --git a/packages/core/src/shaderlib/transform_declare.glsl b/packages/core/src/shaderlib/transform_declare.glsl index 00e1d5edba..86048dcbde 100644 --- a/packages/core/src/shaderlib/transform_declare.glsl +++ b/packages/core/src/shaderlib/transform_declare.glsl @@ -1,7 +1,8 @@ -uniform mat4 renderer_LocalMat; -uniform mat4 renderer_ModelMat; uniform mat4 camera_ViewMat; uniform mat4 camera_ProjMat; +uniform mat4 camera_VPMat; + +uniform mat4 renderer_ModelMat; uniform mat4 renderer_MVMat; uniform mat4 renderer_MVPMat; uniform mat4 renderer_NormalMat; \ No newline at end of file diff --git a/packages/core/src/trail/TrailRenderer.ts b/packages/core/src/trail/TrailRenderer.ts index b8d97f4365..6485d658ca 100644 --- a/packages/core/src/trail/TrailRenderer.ts +++ b/packages/core/src/trail/TrailRenderer.ts @@ -1,7 +1,6 @@ import { BoundingBox, Color, Vector2, Vector3, Vector4 } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; -import { RenderElement } from "../RenderPipeline/RenderElement"; import { Renderer, RendererUpdateFlags } from "../Renderer"; import { deepClone, ignoreClone } from "../clone/CloneManager"; import { Buffer } from "../graphic/Buffer"; @@ -232,8 +231,9 @@ export class TrailRenderer extends Renderer { const { _firstActiveElement: firstActive, _firstFreeElement: firstFree } = this; - const renderElement = this._engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); + const priority = this.priority; + const distanceForSort = this._distanceForSort; + const renderPipeline = context.camera._renderPipeline; // spansBoundary: active points cross buffer end // wrapped: spansBoundary AND point 0 has been written (need bridge + second segment) @@ -241,13 +241,19 @@ export class TrailRenderer extends Renderer { const wrapped = spansBoundary && firstFree > 0; const mainCount = (spansBoundary ? this._currentPointCapacity - firstActive + (wrapped ? 1 : 0) : firstFree - firstActive) * 2; - this._addSubRenderElement(renderElement, material, this._mainSubPrimitive, firstActive * 2, mainCount); + this._addRenderElement( + context, + material, + this._mainSubPrimitive, + firstActive * 2, + mainCount, + priority, + distanceForSort + ); if (wrapped) { - this._addSubRenderElement(renderElement, material, this._wrapSubPrimitive, 0, firstFree * 2); + this._addRenderElement(context, material, this._wrapSubPrimitive, 0, firstFree * 2, priority, distanceForSort); } - - context.camera._renderPipeline.pushRenderElement(context, renderElement); } protected override _updateBounds(worldBounds: BoundingBox): void { @@ -588,18 +594,22 @@ export class TrailRenderer extends Renderer { this._bufferResized = false; } - private _addSubRenderElement( - renderElement: RenderElement, + private _addRenderElement( + context: RenderContext, material: Material, subPrimitive: SubPrimitive, start: number, - count: number + count: number, + priority: number, + distanceForSort: number ): void { subPrimitive.start = start; subPrimitive.count = count; - const subRenderElement = this._engine._subRenderElementPool.get(); - subRenderElement.set(this, material, this._primitive, subPrimitive); - renderElement.addSubRenderElement(subRenderElement); + const renderElement = this._engine._renderElementPool.get(); + renderElement.set(this, material, this._primitive, subPrimitive); + renderElement.priority = priority; + renderElement.distanceForSort = distanceForSort; + context.camera._renderPipeline.pushRenderElement(context, renderElement); } @ignoreClone diff --git a/packages/core/src/ui/IUICanvas.ts b/packages/core/src/ui/IUICanvas.ts index 3139b6dba8..e90e209aa2 100644 --- a/packages/core/src/ui/IUICanvas.ts +++ b/packages/core/src/ui/IUICanvas.ts @@ -10,7 +10,7 @@ export interface IUICanvas { entity: Entity; sortOrder: number; _canvasIndex: number; - _renderElement: RenderElement; + _renderElements: RenderElement[]; _canRender(camera: Camera): boolean; _prepareRender(renderContext: RenderContext): void; } diff --git a/packages/core/src/ui/UIUtils.ts b/packages/core/src/ui/UIUtils.ts index c57c8bdf1f..a6f7822a03 100644 --- a/packages/core/src/ui/UIUtils.ts +++ b/packages/core/src/ui/UIUtils.ts @@ -44,7 +44,10 @@ export class UIUtils { renderContext.applyVirtualCamera(virtualCamera, false); uiRenderQueue.rendererUpdateFlag |= ContextRendererUpdateFlag.ProjectionMatrix; uiCanvas._prepareRender(renderContext); - uiRenderQueue.pushRenderElement(uiCanvas._renderElement); + const curElements = uiCanvas._renderElements; + for (let j = 0, m = curElements.length; j < m; j++) { + uiRenderQueue.pushRenderElement(curElements[j]); + } uiRenderQueue.batch(batcherManager); batcherManager.uploadBuffer(); uiRenderQueue.render(renderContext, "Forward"); diff --git a/packages/design/src/renderingHardwareInterface/IHardwareRenderer.ts b/packages/design/src/renderingHardwareInterface/IHardwareRenderer.ts index bccb92bf65..193ef7a558 100644 --- a/packages/design/src/renderingHardwareInterface/IHardwareRenderer.ts +++ b/packages/design/src/renderingHardwareInterface/IHardwareRenderer.ts @@ -2,6 +2,8 @@ * Hardware graphics API renderer. */ export interface IHardwareRenderer { + readonly maxUniformBlockSize: number; + // todo: implements [key: string]: any; } diff --git a/packages/rhi-webgl/src/GLBuffer.ts b/packages/rhi-webgl/src/GLBuffer.ts index 87f635dba9..aea7b38842 100644 --- a/packages/rhi-webgl/src/GLBuffer.ts +++ b/packages/rhi-webgl/src/GLBuffer.ts @@ -21,7 +21,18 @@ export class GLBuffer implements IPlatformBuffer { const gl = rhi.gl; const glBuffer = gl.createBuffer(); const glBufferUsage = this._getGLBufferUsage(gl, bufferUsage); - const glBindTarget = type === BufferBindFlag.VertexBuffer ? gl.ARRAY_BUFFER : gl.ELEMENT_ARRAY_BUFFER; + let glBindTarget: number; + switch (type) { + case BufferBindFlag.VertexBuffer: + glBindTarget = gl.ARRAY_BUFFER; + break; + case BufferBindFlag.IndexBuffer: + glBindTarget = gl.ELEMENT_ARRAY_BUFFER; + break; + case BufferBindFlag.ConstantBuffer: + glBindTarget = (gl).UNIFORM_BUFFER; + break; + } this._gl = gl; this._glBuffer = glBuffer; this._glBufferUsage = glBufferUsage; diff --git a/packages/rhi-webgl/src/WebGLGraphicDevice.ts b/packages/rhi-webgl/src/WebGLGraphicDevice.ts index c937742f36..7394b0409a 100644 --- a/packages/rhi-webgl/src/WebGLGraphicDevice.ts +++ b/packages/rhi-webgl/src/WebGLGraphicDevice.ts @@ -89,6 +89,8 @@ export interface WebGLGraphicDeviceOptions { * WebGL graphic device, including WebGL1.0 and WebGL2.0. */ export class WebGLGraphicDevice implements IHardwareRenderer { + maxUniformBlockSize: number; + /** @internal */ _readFrameBuffer: WebGLFramebuffer = null; /** @internal */ @@ -280,6 +282,20 @@ export class WebGLGraphicDevice implements IHardwareRenderer { return new GLTransformFeedbackPrimitive(this._gl); } + bindUniformBufferBase(bindingPoint: number, buffer: IPlatformBuffer): void { + const gl = this._gl; + gl.bindBufferBase(gl.UNIFORM_BUFFER, bindingPoint, (buffer)._glBuffer); + } + + bindUniformBlock(program: WebGLProgram, blockName: string, bindingPoint: number): number { + const gl = this._gl; + const blockIndex = gl.getUniformBlockIndex(program, blockName); + if (blockIndex !== gl.INVALID_INDEX) { + gl.uniformBlockBinding(program, blockIndex, bindingPoint); + } + return blockIndex; + } + /** * Enable GL_RASTERIZER_DISCARD (WebGL2 only). */ @@ -623,6 +639,11 @@ export class WebGLGraphicDevice implements IHardwareRenderer { if (debugRenderInfo != null) { this._renderer = gl.getParameter(debugRenderInfo.UNMASKED_RENDERER_WEBGL); } + if (this._isWebGL2) { + this.maxUniformBlockSize = (gl).getParameter( + (gl).MAX_UNIFORM_BLOCK_SIZE + ); + } } destroy(): void { diff --git a/packages/shader/src/shaders/Transform.glsl b/packages/shader/src/shaders/Transform.glsl index 7fccb94266..1af76a0f0e 100644 --- a/packages/shader/src/shaders/Transform.glsl +++ b/packages/shader/src/shaders/Transform.glsl @@ -1,17 +1,17 @@ #ifndef TRANSFORM_INCLUDED #define TRANSFORM_INCLUDED -mat4 renderer_LocalMat; -mat4 renderer_ModelMat; mat4 camera_ViewMat; mat4 camera_ProjMat; mat4 camera_VPMat; -mat4 renderer_MVMat; -mat4 renderer_MVPMat; -mat4 renderer_NormalMat; vec3 camera_Position; -vec3 camera_Forward; +vec3 camera_Forward; vec4 camera_ProjectionParams; -#endif \ No newline at end of file +mat4 renderer_ModelMat; +mat4 renderer_MVMat; +mat4 renderer_MVPMat; +mat4 renderer_NormalMat; + +#endif diff --git a/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl b/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl index 134f2405f2..c9e964d6ec 100644 --- a/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl +++ b/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl @@ -69,10 +69,11 @@ VertexInputs getVertexInputs(Attributes attributes){ // TBN world space #ifdef RENDERER_HAS_NORMAL - inputs.normalWS = normalize( mat3(renderer_NormalMat) * normal ); + mat3 normalMat = mat3(renderer_NormalMat); + inputs.normalWS = normalize( normalMat * normal ); #ifdef RENDERER_HAS_TANGENT - vec3 tangentWS = normalize( mat3(renderer_NormalMat) * tangent.xyz ); + vec3 tangentWS = normalize( normalMat * tangent.xyz ); vec3 bitangentWS = cross( inputs.normalWS, tangentWS ) * tangent.w; inputs.tangentWS = tangentWS; @@ -85,7 +86,7 @@ VertexInputs getVertexInputs(Attributes attributes){ vec4 positionWS = renderer_ModelMat * position; inputs.positionWS = positionWS.xyz / positionWS.w; - #if SCENE_FOG_MODE != 0 + #if SCENE_FOG_MODE != 0 vec4 positionVS = renderer_MVMat * position; inputs.positionVS = positionVS.xyz / positionVS.w; #endif diff --git a/packages/ui/src/component/UICanvas.ts b/packages/ui/src/component/UICanvas.ts index 9e5b33c405..f4bf699135 100644 --- a/packages/ui/src/component/UICanvas.ts +++ b/packages/ui/src/component/UICanvas.ts @@ -62,7 +62,7 @@ export class UICanvas extends Component implements IElement { _isRootCanvas: boolean = false; /** @internal */ @ignoreClone - _renderElement: any; + _renderElements: any[] = []; /** @internal */ @ignoreClone _sortDistance: number = 0; @@ -307,11 +307,10 @@ export class UICanvas extends Component implements IElement { const { engine, _realRenderMode: mode } = this; const { enableFrustumCulling, cullingMask, _frustum: frustum } = context.camera; const { frameCount } = engine.time; - // @ts-ignore - const renderElement = (this._renderElement = engine._renderElementPool.get()); + const renderElements = this._renderElements; + renderElements.length = 0; const virtualCamera = context.virtualCamera; this._updateSortDistance(virtualCamera.isOrthographic, virtualCamera.position, virtualCamera.forward); - renderElement.set(this.sortOrder, this._sortDistance); const { width, height } = engine.canvas; const renderers = this._getRenderers(); for (let i = 0, n = renderers.length; i < n; i++) { diff --git a/packages/ui/src/component/UIRenderer.ts b/packages/ui/src/component/UIRenderer.ts index 71420f43bb..676a4b241c 100644 --- a/packages/ui/src/component/UIRenderer.ts +++ b/packages/ui/src/component/UIRenderer.ts @@ -1,5 +1,5 @@ import { - BatchUtils, + VertexMergeBatcher, Color, DependentMode, Entity, @@ -113,19 +113,19 @@ export class UIRenderer extends Renderer implements IGraphics { } // @ts-ignore - override _canBatch(elementA, elementB): boolean { - return BatchUtils.canBatchSprite(elementA, elementB); + override _canBatch(preElement, curElement): boolean { + return VertexMergeBatcher.canBatchSprite(preElement, curElement); } // @ts-ignore - override _batch(elementA, elementB?): void { - BatchUtils.batchFor2D(elementA, elementB); + override _batch(preElement, curElement): void { + VertexMergeBatcher.batch(preElement, curElement); } // @ts-ignore - override _updateTransformShaderData(context, onlyMVP: boolean, batched: boolean): void { + override _updateTransformShaderData(context, onlyMVP: boolean): void { // @ts-ignore - super._updateTransformShaderData(context, onlyMVP, true); + this._updateWorldSpaceTransformShaderData(context, onlyMVP); } // @ts-ignore diff --git a/packages/ui/src/component/advanced/Image.ts b/packages/ui/src/component/advanced/Image.ts index 7f074fed75..bac5457c86 100644 --- a/packages/ui/src/component/advanced/Image.ts +++ b/packages/ui/src/component/advanced/Image.ts @@ -5,7 +5,6 @@ import { ISpriteAssembler, ISpriteRenderer, MathUtil, - RenderQueueFlags, RendererUpdateFlags, SimpleSpriteAssembler, SlicedSpriteAssembler, @@ -349,16 +348,16 @@ export class Image extends UIRenderer implements ISpriteRenderer { } this._dirtyUpdateFlag = dirtyUpdateFlag; - // Init sub render element. const { engine } = context.camera; - const subRenderElement = engine._subRenderElementPool.get(); + const renderElement = engine._renderElementPool.get(); const subChunk = this._subChunk; - subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); if (canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay) { - subRenderElement.shaderPasses = material.shader.subShaders[0].passes; - subRenderElement.renderQueueFlags = RenderQueueFlags.All; + renderElement.subShader = material.shader.subShaders[0]; } - canvas._renderElement.addSubRenderElement(subRenderElement); + renderElement.priority = canvas.sortOrder; + renderElement.distanceForSort = canvas._sortDistance; + canvas._renderElements.push(renderElement); } @ignoreClone diff --git a/packages/ui/src/component/advanced/Text.ts b/packages/ui/src/component/advanced/Text.ts index 0eb5edeb93..6f20830716 100644 --- a/packages/ui/src/component/advanced/Text.ts +++ b/packages/ui/src/component/advanced/Text.ts @@ -7,7 +7,6 @@ import { FontStyle, ITextRenderer, OverflowMode, - RenderQueueFlags, RendererUpdateFlags, ShaderData, ShaderDataGroup, @@ -350,23 +349,26 @@ export class Text extends UIRenderer implements ITextRenderer { } const engine = context.camera.engine; - const textSubRenderElementPool = engine._textSubRenderElementPool; + const textRenderElementPool = engine._textRenderElementPool; const material = this.getMaterial(); - const renderElement = canvas._renderElement; + const renderElements = canvas._renderElements; + const priority = canvas.sortOrder; + const distanceForSort = canvas._sortDistance; const textChunks = this._textChunks; const isOverlay = canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay; for (let i = 0, n = textChunks.length; i < n; ++i) { const { subChunk, texture } = textChunks[i]; - const subRenderElement = textSubRenderElementPool.get(); - subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk); + const renderElement = textRenderElementPool.get(); + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk); // @ts-ignore - subRenderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); - subRenderElement.shaderData.setTexture(Text._textTextureProperty, texture); + renderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); + renderElement.shaderData.setTexture(Text._textTextureProperty, texture); if (isOverlay) { - subRenderElement.shaderPasses = material.shader.subShaders[0].passes; - subRenderElement.renderQueueFlags = RenderQueueFlags.All; + renderElement.subShader = material.shader.subShaders[0]; } - renderElement.addSubRenderElement(subRenderElement); + renderElement.priority = priority; + renderElement.distanceForSort = distanceForSort; + renderElements.push(renderElement); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed565e145b..f6dbf1d371 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,11 +45,11 @@ importers: specifier: latest version: 0.5.22 '@typescript-eslint/eslint-plugin': - specifier: ^6.1.0 - version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) + specifier: ^8.58.1 + version: 8.58.1(@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) '@typescript-eslint/parser': - specifier: ^6.1.0 - version: 6.21.0(eslint@8.57.1)(typescript@5.6.3) + specifier: ^8.58.1 + version: 8.58.1(eslint@8.57.1)(typescript@5.6.3) '@vitest/coverage-v8': specifier: 2.1.3 version: 2.1.3(@vitest/browser@2.1.3(@types/node@18.19.64)(@vitest/spy@2.1.3)(typescript@5.6.3)(vite@5.4.11(@types/node@18.19.64)(sass@1.81.0)(terser@5.44.1))(vitest@2.1.3))(vitest@2.1.3(@types/node@18.19.64)(@vitest/browser@2.1.3)(msw@2.6.5(@types/node@18.19.64)(typescript@5.6.3))(sass@1.81.0)(terser@5.44.1)) @@ -63,7 +63,7 @@ importers: specifier: ^13 version: 13.6.9 eslint: - specifier: ^8.44.0 + specifier: ^8.57.1 version: 8.57.1 eslint-config-prettier: specifier: ^8.8.0 @@ -78,8 +78,8 @@ importers: specifier: ^8.0.0 version: 8.0.3 lint-staged: - specifier: ^10.5.3 - version: 10.5.4 + specifier: ^16.4.0 + version: 16.4.0 nyc: specifier: ^15.1.0 version: 15.1.0 @@ -188,6 +188,9 @@ importers: '@galacean/engine-toolkit': specifier: latest version: 1.5.3(@galacean/engine-ui@packages+ui)(@galacean/engine@packages+galacean) + '@galacean/engine-toolkit-stats': + specifier: latest + version: 1.6.0(@galacean/engine@packages+galacean) '@galacean/engine-ui': specifier: workspace:* version: link:../packages/ui @@ -804,10 +807,20 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/eslintrc@2.1.4': resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -935,6 +948,11 @@ packages: peerDependencies: '@galacean/engine': ^1.5.0 + '@galacean/engine-toolkit-stats@1.6.0': + resolution: {integrity: sha512-63LLxTWg15xR000jbtEONnK6lBBMylvl5m+3VqqC7b09YAuMWlm9CuPfaM8dlbctOYT6nmPu9bpQiq3JfdgtWg==} + peerDependencies: + '@galacean/engine': '>=1.6.0-0' + '@galacean/engine-toolkit@1.3.9': resolution: {integrity: sha512-sxE7QfzH61O9Q1wtwnjIEjcg3n0ZZVz9B6CyqBLOWyWgWsZmefcjZLnnH4HIkvc5ZLNA+gMuJ1ekmwJgfkck+g==} @@ -1481,9 +1499,6 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} @@ -1508,9 +1523,6 @@ packages: '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - '@types/semver@7.5.8': - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - '@types/statuses@2.0.5': resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} @@ -1520,63 +1532,64 @@ packages: '@types/webxr@0.5.22': resolution: {integrity: sha512-Vr6Stjv5jPRqH690f5I5GLjVk8GSsoQSYJ2FVd/3jJF7KaqfwPi3ehfBS96mlQ2kPCwZaX6U0rG2+NGHBKkA/A==} - '@typescript-eslint/eslint-plugin@6.21.0': - resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/eslint-plugin@8.58.1': + resolution: {integrity: sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser': ^8.58.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@6.21.0': - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/parser@8.58.1': + resolution: {integrity: sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@6.21.0': - resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/project-service@8.58.1': + resolution: {integrity: sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@6.21.0': - resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/scope-manager@8.58.1': + resolution: {integrity: sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.58.1': + resolution: {integrity: sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@6.21.0': - resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/type-utils@8.58.1': + resolution: {integrity: sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/typescript-estree@6.21.0': - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/types@8.58.1': + resolution: {integrity: sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.58.1': + resolution: {integrity: sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@6.21.0': - resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/utils@8.58.1': + resolution: {integrity: sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@6.21.0': - resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/visitor-keys@8.58.1': + resolution: {integrity: sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -1694,14 +1707,14 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1710,6 +1723,10 @@ packages: resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1722,6 +1739,10 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1748,10 +1769,6 @@ packages: array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} @@ -1760,10 +1777,6 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} @@ -1771,6 +1784,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1785,6 +1802,10 @@ packages: brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1882,13 +1903,13 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} - cli-truncate@2.1.0: - resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} - engines: {node: '>=8'} + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} @@ -1914,13 +1935,13 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} - engines: {node: '>= 6'} - commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -2017,6 +2038,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -2029,9 +2059,6 @@ packages: resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} engines: {node: '>=4'} - dedent@0.7.0: - resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} - deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -2080,10 +2107,6 @@ packages: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} @@ -2113,6 +2136,9 @@ packages: engines: {node: '>= 8.6'} hasBin: true + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2126,14 +2152,14 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -2322,6 +2348,10 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2365,9 +2395,8 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - execa@4.1.0: - resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} - engines: {node: '>=10'} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} @@ -2387,10 +2416,6 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -2411,6 +2436,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2489,13 +2523,14 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} - get-own-enumerable-property-symbols@3.0.2: - resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} - get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -2568,10 +2603,6 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} @@ -2632,10 +2663,6 @@ packages: http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - human-signals@1.1.1: - resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} - engines: {node: '>=8.12.0'} - human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -2649,6 +2676,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + immutable@5.0.2: resolution: {integrity: sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw==} @@ -2693,6 +2724,10 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -2707,10 +2742,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-obj@1.0.1: - resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} - engines: {node: '>=0.10.0'} - is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} @@ -2726,10 +2757,6 @@ packages: is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - is-regexp@1.0.0: - resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} - engines: {node: '>=0.10.0'} - is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -2745,10 +2772,6 @@ packages: is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -2875,18 +2898,14 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lint-staged@10.5.4: - resolution: {integrity: sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg==} + lint-staged@16.4.0: + resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==} + engines: {node: '>=20.17'} hasBin: true - listr2@3.14.0: - resolution: {integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==} - engines: {node: '>=10.0.0'} - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} @@ -2905,13 +2924,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - log-update@4.0.0: - resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} - engines: {node: '>=10'} + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} loupe@3.1.2: resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} @@ -2977,10 +2992,6 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -2990,14 +3001,14 @@ packages: engines: {node: '>=4.0.0'} hasBin: true - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} @@ -3006,13 +3017,13 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -3119,10 +3130,6 @@ packages: resolution: {integrity: sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==} engines: {node: '>=4'} - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3151,14 +3158,14 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -3194,10 +3201,6 @@ packages: resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} engines: {node: '>=8'} - p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -3279,6 +3282,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} @@ -3300,9 +3307,6 @@ packages: engines: {node: '>=18'} hasBin: true - please-upgrade-node@3.2.0: - resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} - postcss@8.4.49: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} @@ -3446,9 +3450,9 @@ packages: responselike@1.0.2: resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} @@ -3514,9 +3518,6 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -3549,6 +3550,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + serialize-error@7.0.1: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} @@ -3593,17 +3599,13 @@ packages: resolution: {integrity: sha512-pEjMUbwJ5Pl/6Vn6FsamXHXItJXSRftcibixDmNCWbWhic0hzHrwkMZo0IZ7fMRH9KxcWDFSkzhccB4285PutA==} engines: {node: '>=4.2'} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - slice-ansi@3.0.0: - resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} - engines: {node: '>=8'} + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} - slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} @@ -3658,10 +3660,6 @@ packages: strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - string-argv@0.3.1: - resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} - engines: {node: '>=0.6.19'} - string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -3674,16 +3672,20 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.0: + resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} + engines: {node: '>=20'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - stringify-object@3.3.0: - resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} - engines: {node: '>=4'} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3692,14 +3694,14 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -3764,10 +3766,18 @@ packages: tinyexec@0.3.1: resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + tinyglobby@0.2.10: resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + tinypool@1.0.2: resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -3800,11 +3810,11 @@ packages: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} - ts-api-utils@1.4.0: - resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} - engines: {node: '>=16'} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} peerDependencies: - typescript: '>=4.2.0' + typescript: '>=4.8.4' ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} @@ -4092,6 +4102,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -4127,6 +4141,11 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -4539,8 +4558,15 @@ snapshots: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.12.2': {} + '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 @@ -4662,6 +4688,10 @@ snapshots: dependencies: '@galacean/engine': link:packages/galacean + '@galacean/engine-toolkit-stats@1.6.0(@galacean/engine@packages+galacean)': + dependencies: + '@galacean/engine': link:packages/galacean + '@galacean/engine-toolkit@1.3.9(@galacean/engine@packages+galacean)': dependencies: '@galacean/engine-toolkit-auxiliary-lines': 1.3.9(@galacean/engine@packages+galacean) @@ -5149,8 +5179,6 @@ snapshots: '@types/estree@1.0.6': {} - '@types/json-schema@7.0.15': {} - '@types/keyv@3.1.4': dependencies: '@types/node': 18.19.64 @@ -5175,99 +5203,102 @@ snapshots: dependencies: '@types/node': 18.19.64 - '@types/semver@7.5.8': {} - '@types/statuses@2.0.5': {} '@types/tough-cookie@4.0.5': {} '@types/webxr@0.5.22': {} - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.7 + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.58.1(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/type-utils': 8.58.1(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 8.58.1(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.58.1 eslint: 8.57.1 - graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 7.0.5 natural-compare: 1.4.0 - semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: + ts-api-utils: 2.5.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.7 + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.58.1 + debug: 4.4.3 eslint: 8.57.1 - optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@6.21.0': + '@typescript-eslint/project-service@8.58.1(typescript@5.6.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.6.3) + '@typescript-eslint/types': 8.58.1 + debug: 4.4.3 + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.58.1': dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/visitor-keys': 8.58.1 - '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/tsconfig-utils@8.58.1(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.6.3) - debug: 4.3.7 + typescript: 5.6.3 + + '@typescript-eslint/type-utils@8.58.1(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.6.3) + '@typescript-eslint/utils': 8.58.1(eslint@8.57.1)(typescript@5.6.3) + debug: 4.4.3 eslint: 8.57.1 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: + ts-api-utils: 2.5.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@6.21.0': {} + '@typescript-eslint/types@8.58.1': {} - '@typescript-eslint/typescript-estree@6.21.0(typescript@5.6.3)': + '@typescript-eslint/typescript-estree@8.58.1(typescript@5.6.3)': dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.7 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: + '@typescript-eslint/project-service': 8.58.1(typescript@5.6.3) + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.6.3) + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/visitor-keys': 8.58.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/utils@8.58.1(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.6.3) eslint: 8.57.1 - semver: 7.6.3 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - - typescript - '@typescript-eslint/visitor-keys@6.21.0': + '@typescript-eslint/visitor-keys@8.58.1': dependencies: - '@typescript-eslint/types': 6.21.0 - eslint-visitor-keys: 3.4.3 + '@typescript-eslint/types': 8.58.1 + eslint-visitor-keys: 5.0.1 '@ungap/structured-clone@1.2.0': {} @@ -5455,16 +5486,20 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ansi-colors@4.1.3: {} - ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -5473,6 +5508,8 @@ snapshots: ansi-styles@6.2.1: {} + ansi-styles@6.2.3: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -5498,18 +5535,16 @@ snapshots: array-ify@1.0.0: {} - array-union@2.1.0: {} - arrify@1.0.1: {} assertion-error@2.0.1: {} - astral-regex@2.0.0: {} - at-least-node@1.0.0: {} balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + binary-extensions@2.3.0: {} boolean@3.2.0: @@ -5524,6 +5559,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -5649,14 +5688,14 @@ snapshots: clean-stack@2.2.0: {} - cli-cursor@3.1.0: + cli-cursor@5.0.0: dependencies: - restore-cursor: 3.1.0 + restore-cursor: 5.1.0 - cli-truncate@2.1.0: + cli-truncate@5.2.0: dependencies: - slice-ansi: 3.0.0 - string-width: 4.2.3 + slice-ansi: 8.0.0 + string-width: 8.2.0 cli-width@4.1.0: {} @@ -5684,11 +5723,11 @@ snapshots: colorette@2.0.20: {} + commander@14.0.3: {} + commander@2.20.3: optional: true - commander@6.2.1: {} - commondir@1.0.1: {} compare-func@2.0.0: @@ -5785,6 +5824,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.3: + dependencies: + ms: 2.1.3 + decamelize-keys@1.1.1: dependencies: decamelize: 1.2.0 @@ -5796,8 +5839,6 @@ snapshots: dependencies: mimic-response: 1.0.1 - dedent@0.7.0: {} - deep-eql@5.0.2: {} deep-is@0.1.4: {} @@ -5838,10 +5879,6 @@ snapshots: diff@4.0.2: {} - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - doctrine@3.0.0: dependencies: esutils: 2.0.3 @@ -5868,6 +5905,8 @@ snapshots: transitivePeerDependencies: - supports-color + emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -5879,13 +5918,10 @@ snapshots: dependencies: once: 1.4.0 - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - env-paths@2.2.1: {} + environment@1.1.0: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -6062,6 +6098,8 @@ snapshots: eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@5.0.1: {} + eslint@8.57.1: dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) @@ -6135,17 +6173,7 @@ snapshots: esutils@2.0.3: {} - execa@4.1.0: - dependencies: - cross-spawn: 7.0.5 - get-stream: 5.2.0 - human-signals: 1.1.1 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 + eventemitter3@5.0.4: {} execa@8.0.1: dependencies: @@ -6174,14 +6202,6 @@ snapshots: fast-diff@1.3.0: {} - fast-glob@3.3.2: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -6198,6 +6218,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -6279,6 +6303,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.5.0: {} + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -6288,8 +6314,6 @@ snapshots: hasown: 2.0.2 optional: true - get-own-enumerable-property-symbols@3.0.2: {} - get-package-type@0.1.0: {} get-stdin@8.0.0: {} @@ -6387,15 +6411,6 @@ snapshots: gopd: 1.0.1 optional: true - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - gopd@1.0.1: dependencies: get-intrinsic: 1.2.4 @@ -6459,14 +6474,14 @@ snapshots: http-cache-semantics@4.1.1: {} - human-signals@1.1.1: {} - human-signals@5.0.0: {} husky@8.0.3: {} ignore@5.3.2: {} + ignore@7.0.5: {} + immutable@5.0.2: {} import-fresh@3.3.0: @@ -6501,6 +6516,10 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.5.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -6511,8 +6530,6 @@ snapshots: is-number@7.0.0: {} - is-obj@1.0.1: {} - is-obj@2.0.0: {} is-path-inside@3.0.3: {} @@ -6523,8 +6540,6 @@ snapshots: dependencies: '@types/estree': 1.0.6 - is-regexp@1.0.0: {} - is-stream@2.0.1: {} is-stream@3.0.0: {} @@ -6535,8 +6550,6 @@ snapshots: is-typedarray@1.0.0: {} - is-unicode-supported@0.1.0: {} - is-windows@1.0.2: {} isarray@1.0.0: {} @@ -6671,38 +6684,23 @@ snapshots: lines-and-columns@1.2.4: {} - lint-staged@10.5.4: + lint-staged@16.4.0: dependencies: - chalk: 4.1.2 - cli-truncate: 2.1.0 - commander: 6.2.1 - cosmiconfig: 7.1.0 - debug: 4.3.7 - dedent: 0.7.0 - enquirer: 2.4.1 - execa: 4.1.0 - listr2: 3.14.0(enquirer@2.4.1) - log-symbols: 4.1.0 - micromatch: 4.0.8 - normalize-path: 3.0.0 - please-upgrade-node: 3.2.0 - string-argv: 0.3.1 - stringify-object: 3.3.0 - transitivePeerDependencies: - - supports-color + commander: 14.0.3 + listr2: 9.0.5 + picomatch: 4.0.4 + string-argv: 0.3.2 + tinyexec: 1.1.1 + yaml: 2.8.3 - listr2@3.14.0(enquirer@2.4.1): + listr2@9.0.5: dependencies: - cli-truncate: 2.1.0 + cli-truncate: 5.2.0 colorette: 2.0.20 - log-update: 4.0.0 - p-map: 4.0.0 + eventemitter3: 5.0.4 + log-update: 6.1.0 rfdc: 1.4.1 - rxjs: 7.8.1 - through: 2.3.8 - wrap-ansi: 7.0.0 - optionalDependencies: - enquirer: 2.4.1 + wrap-ansi: 9.0.2 locate-path@5.0.0: dependencies: @@ -6718,17 +6716,13 @@ snapshots: lodash@4.17.21: {} - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - - log-update@4.0.0: + log-update@6.1.0: dependencies: - ansi-escapes: 4.3.2 - cli-cursor: 3.1.0 - slice-ansi: 4.0.0 - wrap-ansi: 6.2.0 + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.2 loupe@3.1.2: {} @@ -6797,30 +6791,29 @@ snapshots: merge-stream@2.0.0: {} - merge2@1.4.1: {} - micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 + optional: true mime@2.6.0: {} - mimic-fn@2.1.0: {} - mimic-fn@4.0.0: {} + mimic-function@5.0.1: {} + mimic-response@1.0.1: {} min-indent@1.0.1: {} - minimatch@3.1.2: + minimatch@10.2.5: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 5.0.5 - minimatch@9.0.3: + minimatch@3.1.2: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 1.1.11 minimatch@9.0.5: dependencies: @@ -6934,10 +6927,6 @@ snapshots: pify: 3.0.0 optional: true - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - npm-run-path@5.3.0: dependencies: path-key: 4.0.0 @@ -6994,14 +6983,14 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - onetime@6.0.0: dependencies: mimic-fn: 4.0.0 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + opener@1.5.2: {} optionator@0.9.4: @@ -7037,10 +7026,6 @@ snapshots: dependencies: aggregate-error: 3.1.0 - p-map@4.0.0: - dependencies: - aggregate-error: 3.1.0 - p-try@2.2.0: {} package-hash@4.0.0: @@ -7100,6 +7085,8 @@ snapshots: picomatch@4.0.2: {} + picomatch@4.0.4: {} + pify@3.0.0: optional: true @@ -7121,10 +7108,6 @@ snapshots: optionalDependencies: fsevents: 2.3.2 - please-upgrade-node@3.2.0: - dependencies: - semver-compare: 1.0.0 - postcss@8.4.49: dependencies: nanoid: 3.3.7 @@ -7261,10 +7244,10 @@ snapshots: dependencies: lowercase-keys: 1.0.1 - restore-cursor@3.1.0: + restore-cursor@5.1.0: dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 + onetime: 7.0.0 + signal-exit: 4.1.0 reusify@1.0.4: {} @@ -7355,10 +7338,6 @@ snapshots: dependencies: queue-microtask: 1.2.3 - rxjs@7.8.1: - dependencies: - tslib: 2.8.1 - safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -7371,7 +7350,8 @@ snapshots: optionalDependencies: '@parcel/watcher': 2.5.0 - semver-compare@1.0.0: {} + semver-compare@1.0.0: + optional: true semver@5.7.2: {} @@ -7381,6 +7361,8 @@ snapshots: semver@7.6.3: {} + semver@7.7.4: {} + serialize-error@7.0.1: dependencies: type-fest: 0.13.1 @@ -7416,19 +7398,15 @@ snapshots: skip-regex@1.0.2: {} - slash@3.0.0: {} - - slice-ansi@3.0.0: + slice-ansi@7.1.2: dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.1.0 - slice-ansi@4.0.0: + slice-ansi@8.0.0: dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 source-map-js@1.2.1: {} @@ -7482,8 +7460,6 @@ snapshots: strict-event-emitter@0.5.1: {} - string-argv@0.3.1: {} - string-argv@0.3.2: {} string-width@4.2.3: @@ -7498,6 +7474,17 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.1.0 + + string-width@8.2.0: + dependencies: + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -7506,12 +7493,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - stringify-object@3.3.0: - dependencies: - get-own-enumerable-property-symbols: 3.0.2 - is-obj: 1.0.1 - is-regexp: 1.0.0 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -7520,9 +7501,11 @@ snapshots: dependencies: ansi-regex: 6.1.0 - strip-bom@4.0.0: {} + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 - strip-final-newline@2.0.0: {} + strip-bom@4.0.0: {} strip-final-newline@3.0.0: {} @@ -7592,11 +7575,18 @@ snapshots: tinyexec@0.3.1: {} + tinyexec@1.1.1: {} + tinyglobby@0.2.10: dependencies: fdir: 6.4.2(picomatch@4.0.2) picomatch: 4.0.2 + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinypool@1.0.2: {} tinyrainbow@1.2.0: {} @@ -7620,7 +7610,7 @@ snapshots: trim-newlines@3.0.1: {} - ts-api-utils@1.4.0(typescript@5.6.3): + ts-api-utils@2.5.0(typescript@5.6.3): dependencies: typescript: 5.6.3 @@ -7738,7 +7728,7 @@ snapshots: vite-node@2.1.5(@types/node@18.19.64)(sass@1.81.0)(terser@5.44.1): dependencies: cac: 6.7.14 - debug: 4.3.7 + debug: 4.4.3 es-module-lexer: 1.5.4 pathe: 1.1.2 vite: 5.4.11(@types/node@18.19.64)(sass@1.81.0)(terser@5.44.1) @@ -7831,7 +7821,7 @@ snapshots: '@vitest/spy': 2.1.5 '@vitest/utils': 2.1.5 chai: 5.1.2 - debug: 4.3.7 + debug: 4.4.3 expect-type: 1.1.0 magic-string: 0.30.12 pathe: 1.1.2 @@ -7892,6 +7882,12 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + wrappy@1.0.2: {} write-file-atomic@3.0.3: @@ -7913,6 +7909,8 @@ snapshots: yaml@1.10.2: {} + yaml@2.8.3: {} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 diff --git a/tests/src/shader-lab/ShaderValidate.ts b/tests/src/shader-lab/ShaderValidate.ts index da6d83407b..7689c721bd 100644 --- a/tests/src/shader-lab/ShaderValidate.ts +++ b/tests/src/shader-lab/ShaderValidate.ts @@ -65,7 +65,7 @@ export function glslValidate( }); // @ts-ignore - const shaderProgram = shaderPass._getCanonicalShaderProgram(engine, macroMockCollection); + const shaderProgram = shaderPass._compileShaderProgram(engine, macroMockCollection); expect(shaderProgram.isValid).to.be.true; }); }); From 7c2ed5906d160e7d5f73113d4557b53683a54bd5 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 23 Apr 2026 18:08:29 +0800 Subject: [PATCH 056/130] feat: atlas support rotate --- .../src/2d/assembler/SimpleSpriteAssembler.ts | 25 +++-- .../src/2d/assembler/SlicedSpriteAssembler.ts | 7 +- .../src/2d/assembler/TiledSpriteAssembler.ts | 7 +- packages/core/src/2d/sprite/Sprite.ts | 97 ++++++++++++------- packages/loader/src/SpriteAtlasLoader.ts | 2 +- 5 files changed, 89 insertions(+), 49 deletions(-) diff --git a/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts b/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts index 563f812106..6e0e07c368 100644 --- a/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts +++ b/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts @@ -66,20 +66,25 @@ export class SimpleSpriteAssembler { } static updateUVs(renderer: ISpriteRenderer): void { + // sprite._uvs 16 个网格点 (column-major, index=column*4+row),4 corner: [0]=LB / [3]=LT / [12]=RB / [15]=RT + // atlasRotated 由 sprite._updateUVs 内部处理,此处直接用结果,无需关心朝向 const spriteUVs = renderer.sprite._getUVs(); - const { x: left, y: bottom } = spriteUVs[0]; - const { x: right, y: top } = spriteUVs[3]; const subChunk = renderer._subChunk; const vertices = subChunk.chunk.vertices; const offset = subChunk.vertexArea.start + 3; - vertices[offset] = left; - vertices[offset + 1] = bottom; - vertices[offset + 9] = right; - vertices[offset + 10] = bottom; - vertices[offset + 18] = left; - vertices[offset + 19] = top; - vertices[offset + 27] = right; - vertices[offset + 28] = top; + const uvLB = spriteUVs[0]; + const uvLT = spriteUVs[3]; + const uvRB = spriteUVs[12]; + const uvRT = spriteUVs[15]; + // SimpleAssembler 的 vertex 顺序:0=LB, 1=RB, 2=LT, 3=RT (按 _rectangleTriangles 的拓扑) + vertices[offset] = uvLB.x; + vertices[offset + 1] = uvLB.y; + vertices[offset + 9] = uvRB.x; + vertices[offset + 10] = uvRB.y; + vertices[offset + 18] = uvLT.x; + vertices[offset + 19] = uvLT.y; + vertices[offset + 27] = uvRT.x; + vertices[offset + 28] = uvRT.y; } static updateColor(renderer: ISpriteRenderer, alpha: number): void { diff --git a/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts b/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts index 31d19d149c..09abce5be3 100644 --- a/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts +++ b/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts @@ -126,14 +126,15 @@ export class SlicedSpriteAssembler { } static updateUVs(renderer: ISpriteRenderer): void { + // 16 UV 网格 (column-major: index = i*4+j, i=column, j=row),与 vertex 索引一一对应 const subChunk = renderer._subChunk; const vertices = subChunk.chunk.vertices; const spriteUVs = renderer.sprite._getUVs(); for (let i = 0, o = subChunk.vertexArea.start + 3; i < 4; i++) { - const rowU = spriteUVs[i].x; for (let j = 0; j < 4; j++, o += 9) { - vertices[o] = rowU; - vertices[o + 1] = spriteUVs[j].y; + const uv = spriteUVs[i * 4 + j]; + vertices[o] = uv.x; + vertices[o + 1] = uv.y; } } } diff --git a/packages/core/src/2d/assembler/TiledSpriteAssembler.ts b/packages/core/src/2d/assembler/TiledSpriteAssembler.ts index bc765c45f9..448d0543a4 100644 --- a/packages/core/src/2d/assembler/TiledSpriteAssembler.ts +++ b/packages/core/src/2d/assembler/TiledSpriteAssembler.ts @@ -188,7 +188,12 @@ export class TiledSpriteAssembler { const spritePositions = sprite._getPositions(); const { x: left, y: bottom } = spritePositions[0]; const { x: right, y: top } = spritePositions[3]; - const [spriteUV0, spriteUV1, spriteUV2, spriteUV3] = sprite._getUVs(); + // 16 UV column-major: [0]=LB(left,bottom), [5]=border-LB(bLeft,bBottom), [10]=border-RT(bRight,bTop), [15]=RT(right,top) + const allUVs = sprite._getUVs(); + const spriteUV0 = allUVs[0]; + const spriteUV1 = allUVs[5]; + const spriteUV2 = allUVs[10]; + const spriteUV3 = allUVs[15]; const expectWidth = sprite.width * referenceResolutionPerUnit; const expectHeight = sprite.height * referenceResolutionPerUnit; const fixedL = expectWidth * border.x; diff --git a/packages/core/src/2d/sprite/Sprite.ts b/packages/core/src/2d/sprite/Sprite.ts index 5232001c9a..b7f01f342e 100644 --- a/packages/core/src/2d/sprite/Sprite.ts +++ b/packages/core/src/2d/sprite/Sprite.ts @@ -20,7 +20,16 @@ export class Sprite extends ReferResource { private _customHeight: number = undefined; private _positions: Vector2[] = [new Vector2(), new Vector2(), new Vector2(), new Vector2()]; - private _uvs: Vector2[] = [new Vector2(), new Vector2(), new Vector2(), new Vector2()]; + // 16 UV 顶点构成 4×4 网格(含 9-slice 内边界)。column-major:index = i*4 + j,i=column(0=left..3=right),j=row(0=bottom..3=top)。 + // 与 SlicedAssembler/TiledAssembler 的 vertex 索引图一致:[0]=LB, [3]=LT, [12]=RB, [15]=RT。 + // SimpleAssembler 取 [0]/[3]/[12]/[15] 4 个 corner;Sliced/Tiled 用全 16。 + // prettier-ignore + private _uvs: Vector2[] = [ + new Vector2(), new Vector2(), new Vector2(), new Vector2(), + new Vector2(), new Vector2(), new Vector2(), new Vector2(), + new Vector2(), new Vector2(), new Vector2(), new Vector2(), + new Vector2(), new Vector2(), new Vector2(), new Vector2(), + ]; private _bounds: BoundingBox = new BoundingBox(); private _texture: Texture2D = null; @@ -287,16 +296,18 @@ export class Sprite extends ReferResource { private _calDefaultSize(): void { if (this._texture) { - const { _texture, _atlasRegion, _atlasRegionOffset, _region } = this; - const pixelsPerUnitReciprocal = 1.0 / Engine._pixelsPerUnit; + const { _texture, _atlasRegion, _atlasRegionOffset, _region, _atlasRotated } = this; + const ppuReciprocal = 1.0 / Engine._pixelsPerUnit; + // 先算 atlas 中绝对像素(texture 不一定是方形,必须各自乘对应维度) + const atlasPxW = _texture.width * _atlasRegion.width; + const atlasPxH = _texture.height * _atlasRegion.height; + // atlas 顺时针 pack 90°:原图 W×H 在 atlas 中占 H×W 区域,仅交换 atlasPx 的 W/H + const originWidth = _atlasRotated ? atlasPxH : atlasPxW; + const originHeight = _atlasRotated ? atlasPxW : atlasPxH; this._automaticWidth = - ((_texture.width * _atlasRegion.width) / (1 - _atlasRegionOffset.x - _atlasRegionOffset.z)) * - _region.width * - pixelsPerUnitReciprocal; + (originWidth / (1 - _atlasRegionOffset.x - _atlasRegionOffset.z)) * _region.width * ppuReciprocal; this._automaticHeight = - ((_texture.height * _atlasRegion.height) / (1 - _atlasRegionOffset.y - _atlasRegionOffset.w)) * - _region.height * - pixelsPerUnitReciprocal; + (originHeight / (1 - _atlasRegionOffset.y - _atlasRegionOffset.w)) * _region.height * ppuReciprocal; } else { this._automaticWidth = this._automaticHeight = 0; } @@ -332,34 +343,52 @@ export class Sprite extends ReferResource { } private _updateUVs(): void { - const { _uvs: uv, _atlasRegionOffset: atlasRegionOffset } = this; - const { x: regionX, y: regionY, width: regionW, height: regionH } = this._region; - const regionRight = 1 - regionX - regionW; - const regionBottom = 1 - regionY - regionH; + const { _uvs: uvs, _atlasRotated: atlasRotated, _border: border } = this; + const { x: regionLeft, y: regionTop, width: regionW, height: regionH } = this._region; + const regionRight = 1 - regionLeft - regionW; + const regionBottom = 1 - regionTop - regionH; const { x: atlasRegionX, y: atlasRegionY, width: atlasRegionW, height: atlasRegionH } = this._atlasRegion; - const { x: offsetLeft, y: offsetTop, z: offsetRight, w: offsetBottom } = atlasRegionOffset; + const { x: offsetLeft, y: offsetTop, z: offsetRight, w: offsetBottom } = this._atlasRegionOffset; const realWidth = atlasRegionW / (1 - offsetLeft - offsetRight); const realHeight = atlasRegionH / (1 - offsetTop - offsetBottom); - // Coordinates of the four boundaries. - const left = Math.max(regionX - offsetLeft, 0) * realWidth + atlasRegionX; - const top = Math.max(regionBottom - offsetTop, 0) * realHeight + atlasRegionY; - const right = atlasRegionW + atlasRegionX - Math.max(regionRight - offsetRight, 0) * realWidth; - const bottom = atlasRegionH + atlasRegionY - Math.max(regionY - offsetBottom, 0) * realHeight; - const { x: borderLeft, y: borderBottom, z: borderRight, w: borderTop } = this._border; - // Left-Bottom - uv[0].set(left, bottom); - // Border ( Left-Bottom ) - uv[1].set( - (regionX - offsetLeft + borderLeft * regionW) * realWidth + atlasRegionX, - atlasRegionH + atlasRegionY - (regionY - offsetBottom + borderBottom * regionH) * realHeight - ); - // Border ( Right-Top ) - uv[2].set( - atlasRegionW + atlasRegionX - (regionRight - offsetRight + borderRight * regionW) * realWidth, - (regionBottom - offsetTop + borderTop * regionH) * realHeight + atlasRegionY - ); - // Right-Top - uv[3].set(right, top); + // 4 个外边界 + 4 个 9-slice 内边界 + let left: number, top: number, right: number, bottom: number; + let bLeft: number, bTop: number, bRight: number, bBottom: number; + if (atlasRotated) { + // 原图 region/offset (left/top/right/bottom) 在 atlas 中映射为 (bottom/left/top/right) + left = Math.max(regionBottom - offsetLeft, 0) * realWidth + atlasRegionX; + top = Math.max(regionLeft - offsetTop, 0) * realHeight + atlasRegionY; + right = atlasRegionW + atlasRegionX - Math.max(regionTop - offsetRight, 0) * realWidth; + bottom = atlasRegionH + atlasRegionY - Math.max(regionRight - offsetBottom, 0) * realHeight; + bLeft = (regionBottom - offsetLeft + border.y * regionH) * realWidth + atlasRegionX; + bTop = (regionLeft - offsetTop + border.x * regionW) * realHeight + atlasRegionY; + bRight = atlasRegionW + atlasRegionX - (regionTop - offsetRight + border.w * regionH) * realWidth; + bBottom = atlasRegionH + atlasRegionY - (regionRight - offsetBottom + border.z * regionW) * realHeight; + } else { + left = Math.max(regionLeft - offsetLeft, 0) * realWidth + atlasRegionX; + top = Math.max(regionBottom - offsetTop, 0) * realHeight + atlasRegionY; + right = atlasRegionW + atlasRegionX - Math.max(regionRight - offsetRight, 0) * realWidth; + bottom = atlasRegionH + atlasRegionY - Math.max(regionTop - offsetBottom, 0) * realHeight; + bLeft = (regionLeft - offsetLeft + border.x * regionW) * realWidth + atlasRegionX; + bTop = (regionBottom - offsetTop + border.w * regionH) * realHeight + atlasRegionY; + bRight = atlasRegionW + atlasRegionX - (regionRight - offsetRight + border.z * regionW) * realWidth; + bBottom = atlasRegionH + atlasRegionY - (regionTop - offsetBottom + border.y * regionH) * realHeight; + } + + // 16 UV 网格填充(column-major:index=i*4+j,i=column[0=left..3=right],j=row[0=bottom..3=top]) + // - 非 rotated:column 决定 atlas X (left/bLeft/bRight/right),row 决定 atlas Y (bottom/bBottom/bTop/top) + // - rotated 90° 顺时针 packed:display column 对应 atlas Y 的反向(顶→底),display row 对应 atlas X + if (atlasRotated) { + uvs[0].set(left, top), uvs[1].set(bLeft, top), uvs[2].set(bRight, top), uvs[3].set(right, top); + uvs[4].set(left, bTop), uvs[5].set(bLeft, bTop), uvs[6].set(bRight, bTop), uvs[7].set(right, bTop); + uvs[8].set(left, bBottom), uvs[9].set(bLeft, bBottom), uvs[10].set(bRight, bBottom), uvs[11].set(right, bBottom); + uvs[12].set(left, bottom), uvs[13].set(bLeft, bottom), uvs[14].set(bRight, bottom), uvs[15].set(right, bottom); + } else { + uvs[0].set(left, bottom), uvs[1].set(left, bBottom), uvs[2].set(left, bTop), uvs[3].set(left, top); + uvs[4].set(bLeft, bottom), uvs[5].set(bLeft, bBottom), uvs[6].set(bLeft, bTop), uvs[7].set(bLeft, top); + uvs[8].set(bRight, bottom), uvs[9].set(bRight, bBottom), uvs[10].set(bRight, bTop), uvs[11].set(bRight, top); + uvs[12].set(right, bottom), uvs[13].set(right, bBottom), uvs[14].set(right, bTop), uvs[15].set(right, top); + } this._dirtyUpdateFlag &= ~SpriteUpdateFlags.uvs; } diff --git a/packages/loader/src/SpriteAtlasLoader.ts b/packages/loader/src/SpriteAtlasLoader.ts index 0bada4b334..c5a1d6a550 100644 --- a/packages/loader/src/SpriteAtlasLoader.ts +++ b/packages/loader/src/SpriteAtlasLoader.ts @@ -102,7 +102,7 @@ class SpriteAtlasLoader extends Loader { const { x: offsetLeft, y: offsetTop, z: offsetRight, w: offsetBottom } = atlasRegionOffset; sprite.atlasRegionOffset.set(offsetLeft * invW, offsetTop * invH, offsetRight * invW, offsetBottom * invH); } - config.atlasRotated && (sprite.atlasRotated = true); + sprite.atlasRotated = config.atlasRotated ?? false; } width === undefined || (sprite.width = width); height === undefined || (sprite.height = height); From e63413425b96e1785bcc5d6c82d6e763dbe7414d Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 23 Apr 2026 20:59:00 +0800 Subject: [PATCH 057/130] fix(shader): scan instance uniforms with macro awareness for raw GLSL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cherry-pick from galacean/engine#2957 commit 29567302 compilePlatformSource 路径中 #ifdef 块未展开就执行 _scanInstanceUniforms, 导致非活跃分支内的 renderer_JointMatrix 被正则命中报错。 - compilePlatformSource 构建 activeMacros 传入 injectInstanceUBO - 新增 _scanInstanceUniformsWithMacros,逐行追踪 #ifdef/#ifndef/#else/#endif - array uniform 从 return "" 改为 return match(保留为普通 uniform) --- packages/core/src/shaderlib/ShaderFactory.ts | 75 ++++++++++++++++++-- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 907d918320..7b8dc31119 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -10,7 +10,7 @@ import { ShaderProperty } from "../shader/ShaderProperty"; import { ShaderBlockProperty } from "../shader/ShaderBlockProperty"; import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { ShaderLib } from "./ShaderLib"; - +console.log(123); /** * @internal */ @@ -164,7 +164,9 @@ export class ShaderFactory { let instanceLayout: InstanceBufferLayout | null = null; if (isGPUInstance) { - const injected = ShaderFactory.injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag); + const activeMacros = new Set(); + for (let i = 0, len = shaderMacroList.length; i < len; i++) activeMacros.add(shaderMacroList[i].name); + const injected = ShaderFactory.injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag, activeMacros); noIncludeVertex = injected.vertexSource; noIncludeFrag = injected.fragmentSource; instanceLayout = injected.instanceLayout; @@ -192,12 +194,18 @@ export class ShaderFactory { static injectInstanceUBO( engine: Engine, vertexSource: string, - fragmentSource: string + fragmentSource: string, + activeMacros?: Set ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { // 1. Scan & strip renderer uniforms from both stages, collect into fieldMap const fieldMap: Record = Object.create(null); - vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); - fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); + if (activeMacros) { + vertexSource = ShaderFactory._scanInstanceUniformsWithMacros(vertexSource, fieldMap, activeMacros); + fragmentSource = ShaderFactory._scanInstanceUniformsWithMacros(fragmentSource, fieldMap, activeMacros); + } else { + vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); + fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); + } // Fast empty check without allocating an array let hasField = false; @@ -299,10 +307,10 @@ export class ShaderFactory { if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) return match; if (isDerived) return ""; - // Array uniforms not supported in instancing UBO, remove to fail shader compilation + // Array uniforms not supported in instancing UBO, keep as regular uniform if (arraySize) { Logger.error(`GPU Instancing does not support array uniform "${name}${arraySize}"`); - return ""; + return match; } // ModelMat is affine, store as mat3x4 (3 columns) to save 16 bytes per instance fieldMap[ShaderProperty.getByName(name)._uniqueId] = @@ -311,6 +319,59 @@ export class ShaderFactory { }); } + // Matches preprocessor directives at line start. Only `#ifdef / #ifndef / #else / #endif` are + // supported; `#if` with expressions is not recognized (such blocks are treated as always-active). + private static readonly _ifdefRegex = /^[ \t]*#ifdef\s+(\w+)/; + private static readonly _ifndefRegex = /^[ \t]*#ifndef\s+(\w+)/; + private static readonly _elseRegex = /^[ \t]*#else\b/; + private static readonly _endifRegex = /^[ \t]*#endif\b/; + + /** + * Scan with preprocessor awareness, for raw GLSL paths where `#ifdef` blocks are not yet + * expanded. Uniforms inside inactive branches are skipped. + */ + private static _scanInstanceUniformsWithMacros( + source: string, + fieldMap: Record, + activeMacros: Set + ): string { + // Preprocessor branch stack: each frame tracks whether current branch is active + const branchStack: boolean[] = [true]; + const lines = source.split("\n"); + + for (let i = 0, n = lines.length; i < n; i++) { + const line = lines[i]; + + let m = line.match(ShaderFactory._ifdefRegex); + if (m) { + const parentActive = branchStack[branchStack.length - 1]; + branchStack.push(parentActive && activeMacros.has(m[1])); + continue; + } + m = line.match(ShaderFactory._ifndefRegex); + if (m) { + const parentActive = branchStack[branchStack.length - 1]; + branchStack.push(parentActive && !activeMacros.has(m[1])); + continue; + } + if (ShaderFactory._elseRegex.test(line)) { + const parentActive = branchStack.length >= 2 ? branchStack[branchStack.length - 2] : true; + const currentActive = branchStack[branchStack.length - 1]; + branchStack[branchStack.length - 1] = parentActive && !currentActive; + continue; + } + if (ShaderFactory._endifRegex.test(line)) { + if (branchStack.length > 1) branchStack.pop(); + continue; + } + if (!branchStack[branchStack.length - 1]) continue; + + lines[i] = ShaderFactory._scanInstanceUniforms(line, fieldMap); + } + + return lines.join("\n"); + } + private static _buildLayout(engine: Engine, fieldMap: Record): InstanceBufferLayout { const maxUBOSize = engine._hardwareRenderer.maxUniformBlockSize; const std140Map = ShaderFactory._std140TypeInfoMap; From 3c319ecf8f7843b3d970fe5db30222a92a1ceff2 Mon Sep 17 00:00:00 2001 From: "shensi.zxd" Date: Thu, 23 Apr 2026 21:07:05 +0800 Subject: [PATCH 058/130] fix: remove stray console.log in ShaderFactory --- packages/core/src/shaderlib/ShaderFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 7b8dc31119..678eb1b55b 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -10,7 +10,7 @@ import { ShaderProperty } from "../shader/ShaderProperty"; import { ShaderBlockProperty } from "../shader/ShaderBlockProperty"; import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { ShaderLib } from "./ShaderLib"; -console.log(123); + /** * @internal */ From 2d3b2d4526b4a7811e4911ebd2daec83f58dcc2a Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 23 Apr 2026 21:08:05 +0800 Subject: [PATCH 059/130] chore: release v0.0.0-experimental-2.0-game.4 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 8f47eecf09..4b066ecd07 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index 3007ed5e9e..774b811469 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 32799a4ae5..aedb774644 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 569379856a..598a75f584 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 86cf2f38cc..50e43c79ac 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index 023532aa4a..789d728f9d 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index b8d953a7b5..dc9b5cdd8c 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index c84c6e2f83..782a7ecb5e 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 2bc3c9f369..a25727382d 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 3c425b7a68..1bcfc8dc8c 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 8a8bb72973..da400d02c7 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index d76645f898..83402d3f20 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index 41b4e5bdac..17bc8ef49b 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index 6d339a9558..05e24b0c6c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 6abe8ecd25..5819e39dbd 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 23ccd02ccb..39754672c9 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index c2c5618235..ccb7fc4ebd 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.3", + "version": "0.0.0-experimental-2.0-game.4", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From 80b8eef7a4d58e0a28e2f52e7f907ff6bf054830 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 23 Apr 2026 21:10:27 +0800 Subject: [PATCH 060/130] chore: release v0.0.0-experimental-2.0-game.5 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 4b066ecd07..37287758e5 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index 774b811469..117d74a297 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index aedb774644..5263038470 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 598a75f584..bcdf86e846 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 50e43c79ac..4edeffba34 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index 789d728f9d..cbcd4adb3a 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index dc9b5cdd8c..e209f26574 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 782a7ecb5e..b5be3c3133 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index a25727382d..c09a0de543 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 1bcfc8dc8c..787c872cca 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index da400d02c7..37fdd5e46b 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 83402d3f20..c85c5d69af 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index 17bc8ef49b..daff035771 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index 05e24b0c6c..69b2e70765 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 5819e39dbd..a85168d4ff 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 39754672c9..813f4f4c3d 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index ccb7fc4ebd..79579d0f8f 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.4", + "version": "0.0.0-experimental-2.0-game.5", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From 8048ba9f89baad4bc948e22a06169ca032031b09 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Fri, 24 Apr 2026 12:12:10 +0800 Subject: [PATCH 061/130] fix(audio): restore sources after background interruption --- packages/core/src/audio/AudioManager.ts | 100 +++++++++++++++--- packages/core/src/audio/AudioSource.ts | 85 +++++++++++++-- .../audio/AudioSourcePendingPlayback.test.ts | 42 +++++++- 3 files changed, 200 insertions(+), 27 deletions(-) diff --git a/packages/core/src/audio/AudioManager.ts b/packages/core/src/audio/AudioManager.ts index 2885f42220..548e0fa079 100644 --- a/packages/core/src/audio/AudioManager.ts +++ b/packages/core/src/audio/AudioManager.ts @@ -1,3 +1,9 @@ +type ResumableAudioSource = { + _resumePendingPlayback(): void; + _suspendPlaybackForInterruption(): boolean; + _resumeInterruptedPlayback(): void; +}; + /** * Audio Manager for managing global audio context and settings. */ @@ -8,13 +14,16 @@ export class AudioManager { private static _context: AudioContext; private static _gainNode: GainNode; private static _needsUserGestureResume = false; - private static _pendingSources = new Set<{ _resumePendingPlayback(): void }>(); + private static _pendingSources = new Set(); + private static _playingSources = new Set(); + private static _interruptedSources = new Set(); /** * Suspend the audio context. * @returns A promise that resolves when the audio context is suspended */ static suspend(): Promise { + AudioManager._suspendActiveSourcesForInterruption(); return AudioManager._context?.suspend() ?? Promise.resolve(); } @@ -25,27 +34,47 @@ export class AudioManager { */ static resume(): Promise { const context = AudioManager._context; - if (!context || context.state === "running") { + if (!context) { return Promise.resolve(); } - return context - .resume() - .then(() => { - AudioManager._needsUserGestureResume = false; - AudioManager._resumePendingSources(); - }); + if (context.state === "running") { + AudioManager._needsUserGestureResume = false; + AudioManager._resumePendingSources(); + AudioManager._resumeInterruptedSources(); + return Promise.resolve(); + } + return context.resume().then(() => { + AudioManager._needsUserGestureResume = false; + AudioManager._resumePendingSources(); + AudioManager._resumeInterruptedSources(); + }); } /** @internal */ - static _registerPendingSource(source: { _resumePendingPlayback(): void }): void { + static _registerPendingSource(source: ResumableAudioSource): void { AudioManager._pendingSources.add(source); } /** @internal */ - static _unregisterPendingSource(source: { _resumePendingPlayback(): void }): void { + static _unregisterPendingSource(source: ResumableAudioSource): void { AudioManager._pendingSources.delete(source); } + /** @internal */ + static _registerPlayingSource(source: ResumableAudioSource): void { + AudioManager._playingSources.add(source); + } + + /** @internal */ + static _unregisterPlayingSource(source: ResumableAudioSource): void { + AudioManager._playingSources.delete(source); + } + + /** @internal */ + static _unregisterInterruptedSource(source: ResumableAudioSource): void { + AudioManager._interruptedSources.delete(source); + } + /** * @internal */ @@ -87,6 +116,7 @@ export class AudioManager { if (AudioManager._context?.state === "running") { AudioManager._needsUserGestureResume = false; AudioManager._resumePendingSources(); + AudioManager._resumeInterruptedSources(); } } @@ -103,14 +133,52 @@ export class AudioManager { } } + private static _suspendActiveSourcesForInterruption(): void { + if (!AudioManager._playingSources.size) { + return; + } + + const playingSources = Array.from(AudioManager._playingSources); + for (let i = 0, n = playingSources.length; i < n; i++) { + const source = playingSources[i]; + if (source._suspendPlaybackForInterruption()) { + AudioManager._interruptedSources.add(source); + } + } + } + + private static _resumeInterruptedSources(): void { + if (!AudioManager._interruptedSources.size || !AudioManager.isAudioContextRunning()) { + return; + } + + const interruptedSources = Array.from(AudioManager._interruptedSources); + AudioManager._interruptedSources.clear(); + + for (let i = 0, n = interruptedSources.length; i < n; i++) { + interruptedSources[i]._resumeInterruptedPlayback(); + } + } + private static _onVisibilityChange(): void { const context = AudioManager._context; + if (!context) { + return; + } + + if (document.hidden) { + AudioManager.suspend().catch(() => {}); + return; + } + if ( - document.hidden || - !context || - (AudioManager._playingCount === 0 && AudioManager._pendingSources.size === 0) || + (AudioManager._playingCount === 0 && + AudioManager._pendingSources.size === 0 && + AudioManager._interruptedSources.size === 0) || context.state === "running" ) { + AudioManager._resumePendingSources(); + AudioManager._resumeInterruptedSources(); return; } @@ -126,7 +194,11 @@ export class AudioManager { } private static _resumeAfterInterruption(): void { - if (AudioManager._needsUserGestureResume || AudioManager._pendingSources.size > 0) { + if ( + AudioManager._needsUserGestureResume || + AudioManager._pendingSources.size > 0 || + AudioManager._interruptedSources.size > 0 + ) { AudioManager.resume().catch((e) => { console.warn("Failed to resume AudioContext:", e); }); diff --git a/packages/core/src/audio/AudioSource.ts b/packages/core/src/audio/AudioSource.ts index 3372f53e7d..3dacf53ec3 100644 --- a/packages/core/src/audio/AudioSource.ts +++ b/packages/core/src/audio/AudioSource.ts @@ -172,15 +172,18 @@ export class AudioSource extends Component { */ stop(): void { this._cancelPendingPlayback(); + AudioManager._unregisterInterruptedSource(this); if (this._isPlaying) { this._clearSourceNode(); this._isPlaying = false; - this._pausedTime = -1; - this._playTime = -1; AudioManager._playingCount--; + AudioManager._unregisterPlayingSource(this); } + + this._pausedTime = -1; + this._playTime = -1; } /** @@ -188,6 +191,7 @@ export class AudioSource extends Component { */ pause(): void { this._cancelPendingPlayback(); + AudioManager._unregisterInterruptedSource(this); if (this._isPlaying) { this._clearSourceNode(); @@ -195,6 +199,7 @@ export class AudioSource extends Component { this._pausedTime = AudioManager.getContext().currentTime; this._isPlaying = false; AudioManager._playingCount--; + AudioManager._unregisterPlayingSource(this); } } @@ -249,34 +254,96 @@ export class AudioSource extends Component { this._startPlayback(); } + /** @internal */ + _suspendPlaybackForInterruption(): boolean { + if (!this._isPlaying) { + return false; + } + + const pausedTime = AudioManager.getContext().currentTime; + this._clearSourceNode(); + + this._pausedTime = pausedTime; + this._isPlaying = false; + AudioManager._playingCount--; + AudioManager._unregisterPlayingSource(this); + + return true; + } + + /** @internal */ + _resumeInterruptedPlayback(): void { + if ( + this._destroyed || + !this.enabled || + this._isPlaying || + this._pendingPlay || + !this._clip?._getAudioSource() || + this._playTime < 0 + ) { + return; + } + + if (AudioManager.isAudioContextRunning()) { + this._startPlayback(); + } else { + this._pendingPlay = true; + AudioManager._registerPendingSource(this); + } + } + private _startPlayback(): void { const startTime = this._pausedTime > 0 ? this._pausedTime - this._playTime : 0; - this._initSourceNode(startTime); + if (!this._initSourceNode(startTime)) { + this._pausedTime = -1; + this._playTime = -1; + return; + } this._playTime = AudioManager.getContext().currentTime - startTime; this._pausedTime = -1; this._isPlaying = true; AudioManager._playingCount++; + AudioManager._registerPlayingSource(this); } - private _initSourceNode(startTime: number): void { + private _initSourceNode(startTime: number): boolean { const context = AudioManager.getContext(); const sourceNode = context.createBufferSource(); + const audioBuffer = this._clip._getAudioSource(); + const duration = audioBuffer.duration; + let offset = Math.max(0, startTime); + + if (duration > 0) { + if (this._loop) { + offset %= duration; + } else if (offset >= duration) { + return false; + } + } - sourceNode.buffer = this._clip._getAudioSource(); + sourceNode.buffer = audioBuffer; sourceNode.playbackRate.value = this._playbackRate; sourceNode.loop = this._loop; sourceNode.onended = this._onPlayEnd; this._sourceNode = sourceNode; sourceNode.connect(this._gainNode); - sourceNode.start(0, startTime); + sourceNode.start(0, offset); + return true; } private _clearSourceNode(): void { - this._sourceNode.stop(); - this._sourceNode.disconnect(); - this._sourceNode.onended = null; + const sourceNode = this._sourceNode; + if (!sourceNode) { + return; + } + + sourceNode.onended = null; + try { + sourceNode.stop(); + } catch {} + sourceNode.disconnect(); this._sourceNode = null; } diff --git a/tests/src/core/audio/AudioSourcePendingPlayback.test.ts b/tests/src/core/audio/AudioSourcePendingPlayback.test.ts index a1e900332b..edb31b12ef 100644 --- a/tests/src/core/audio/AudioSourcePendingPlayback.test.ts +++ b/tests/src/core/audio/AudioSourcePendingPlayback.test.ts @@ -92,6 +92,8 @@ describe("AudioSource pending playback", () => { (AudioManager as any)._gainNode = null; (AudioManager as any)._needsUserGestureResume = false; (AudioManager as any)._pendingSources = new Set(); + (AudioManager as any)._playingSources = new Set(); + (AudioManager as any)._interruptedSources = new Set(); MockAudioContext.shouldResumeSucceed = true; MockAudioContext.resumeResultQueue = null; AudioManager._playingCount = 0; @@ -175,6 +177,36 @@ describe("AudioSource pending playback", () => { expect((AudioManager as any)._needsUserGestureResume).to.be.false; }); + it("recreates active source nodes after a background interruption", async () => { + const audioSource = createAudioSource(); + const context = (AudioManager as any)._context as MockAudioContext; + + context.state = "running"; + audioSource.play(); + + const firstSourceNode = (audioSource as any)._sourceNode as MockBufferSourceNode; + expect(audioSource.isPlaying).to.be.true; + expect(AudioManager._playingCount).to.equal(1); + + const hiddenSpy = vi.spyOn(document, "hidden", "get").mockReturnValue(true); + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + expect(firstSourceNode.stop).toHaveBeenCalledTimes(1); + expect(audioSource.isPlaying).to.be.false; + expect(AudioManager._playingCount).to.equal(0); + expect((AudioManager as any)._interruptedSources.size).to.equal(1); + + hiddenSpy.mockReturnValue(false); + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.true; + expect(AudioManager._playingCount).to.equal(1); + expect((AudioManager as any)._interruptedSources.size).to.equal(0); + expect((audioSource as any)._sourceNode).not.to.equal(firstSourceNode); + }); + it("falls back to gesture recovery when foreground auto-resume fails", async () => { createAudioSource(); const context = (AudioManager as any)._context as MockAudioContext; @@ -216,10 +248,12 @@ describe("AudioSource pending playback", () => { expect(resumeSpy).toHaveBeenCalledTimes(1); - MockAudioContext.resumeResultQueue = [Promise.resolve().then(() => { - context.state = "running"; - context.onstatechange?.(); - })]; + MockAudioContext.resumeResultQueue = [ + Promise.resolve().then(() => { + context.state = "running"; + context.onstatechange?.(); + }) + ]; (AudioManager as any)._needsUserGestureResume = true; document.dispatchEvent(new Event("click")); From af825f05e4b2275c609b827bbf47e382a54e16fd Mon Sep 17 00:00:00 2001 From: luzhuang Date: Fri, 24 Apr 2026 16:33:44 +0800 Subject: [PATCH 062/130] fix: recover audio after foreground resume --- packages/core/src/audio/AudioManager.ts | 135 ++++++++++++------ .../audio/AudioSourcePendingPlayback.test.ts | 118 +++++++++++++-- 2 files changed, 198 insertions(+), 55 deletions(-) diff --git a/packages/core/src/audio/AudioManager.ts b/packages/core/src/audio/AudioManager.ts index 548e0fa079..b1c4c5651b 100644 --- a/packages/core/src/audio/AudioManager.ts +++ b/packages/core/src/audio/AudioManager.ts @@ -17,6 +17,10 @@ export class AudioManager { private static _pendingSources = new Set(); private static _playingSources = new Set(); private static _interruptedSources = new Set(); + private static _foregroundRestoreDelay = 300; + private static _foregroundRestoreTimer: number | undefined; + private static _hidden = false; + private static _eventsBound = false; /** * Suspend the audio context. @@ -38,12 +42,14 @@ export class AudioManager { return Promise.resolve(); } if (context.state === "running") { + AudioManager._clearForegroundRestore(); AudioManager._needsUserGestureResume = false; AudioManager._resumePendingSources(); AudioManager._resumeInterruptedSources(); return Promise.resolve(); } return context.resume().then(() => { + AudioManager._clearForegroundRestore(); AudioManager._needsUserGestureResume = false; AudioManager._resumePendingSources(); AudioManager._resumeInterruptedSources(); @@ -83,11 +89,11 @@ export class AudioManager { if (!context) { AudioManager._context = context = new window.AudioContext(); context.onstatechange = AudioManager._onContextStateChange; - document.addEventListener("visibilitychange", AudioManager._onVisibilityChange); - // iOS Safari requires user gesture to resume AudioContext - document.addEventListener("touchstart", AudioManager._resumeAfterInterruption, { passive: true }); - document.addEventListener("touchend", AudioManager._resumeAfterInterruption, { passive: true }); - document.addEventListener("click", AudioManager._resumeAfterInterruption); + if (!AudioManager._eventsBound) { + AudioManager._eventsBound = true; + AudioManager._bindLifecycleEvents(); + AudioManager._bindGestureEvents(); + } } return context; } @@ -114,6 +120,9 @@ export class AudioManager { private static _onContextStateChange(): void { if (AudioManager._context?.state === "running") { + if (AudioManager._hidden || AudioManager._needsUserGestureResume) { + return; + } AudioManager._needsUserGestureResume = false; AudioManager._resumePendingSources(); AudioManager._resumeInterruptedSources(); @@ -160,60 +169,102 @@ export class AudioManager { } } - private static _onVisibilityChange(): void { - const context = AudioManager._context; - if (!context) { - return; + private static _bindLifecycleEvents(): void { + const hiddenProp = AudioManager._getHiddenProp(); + const visibilityEvents = [ + "visibilitychange", + "mozvisibilitychange", + "msvisibilitychange", + "webkitvisibilitychange", + "qbrowserVisibilityChange" + ]; + + for (let i = 0, n = visibilityEvents.length; i < n; i++) { + document.addEventListener(visibilityEvents[i], (event) => { + const hidden = hiddenProp ? Boolean((document as any)[hiddenProp] || (event as any)?.hidden) : document.hidden; + hidden ? AudioManager._onHidden() : AudioManager._onShown(); + }); } - if (document.hidden) { - AudioManager.suspend().catch(() => {}); + window.addEventListener("pagehide", AudioManager._onHidden); + window.addEventListener("pageshow", AudioManager._onShown); + document.addEventListener("pagehide", AudioManager._onHidden); + document.addEventListener("pageshow", AudioManager._onShown); + } + + private static _bindGestureEvents(): void { + const gestureEvents = ["pointerdown", "pointerup", "touchstart", "touchend", "mouseup", "click"]; + for (let i = 0, n = gestureEvents.length; i < n; i++) { + document.addEventListener(gestureEvents[i], AudioManager._resumeAfterInterruption, { passive: true }); + } + } + + private static _getHiddenProp(): string { + const doc = document as any; + if (typeof doc.hidden !== "undefined") return "hidden"; + if (typeof doc.mozHidden !== "undefined") return "mozHidden"; + if (typeof doc.msHidden !== "undefined") return "msHidden"; + if (typeof doc.webkitHidden !== "undefined") return "webkitHidden"; + return ""; + } + + private static _hasResumeWork(): boolean { + return ( + AudioManager._needsUserGestureResume || + AudioManager._pendingSources.size > 0 || + AudioManager._interruptedSources.size > 0 + ); + } + + private static _onHidden(): void { + if (AudioManager._hidden) { return; } + AudioManager._hidden = true; + AudioManager._clearForegroundRestore(); + AudioManager.suspend().catch(() => {}); + } - if ( - (AudioManager._playingCount === 0 && - AudioManager._pendingSources.size === 0 && - AudioManager._interruptedSources.size === 0) || - context.state === "running" - ) { - AudioManager._resumePendingSources(); - AudioManager._resumeInterruptedSources(); + private static _onShown(): void { + if (!AudioManager._hidden) { return; } + AudioManager._hidden = false; - AudioManager.resume() - .then(() => { - if (AudioManager._context?.state !== "running") { - return AudioManager._prepareGestureResume(); - } - }) - .catch(() => { - return AudioManager._prepareGestureResume(); - }); + if (AudioManager._hasResumeWork()) { + AudioManager._prepareGestureResume(); + AudioManager._scheduleForegroundRestore(); + } } private static _resumeAfterInterruption(): void { - if ( - AudioManager._needsUserGestureResume || - AudioManager._pendingSources.size > 0 || - AudioManager._interruptedSources.size > 0 - ) { + if (AudioManager._hasResumeWork()) { AudioManager.resume().catch((e) => { console.warn("Failed to resume AudioContext:", e); }); } } + private static _scheduleForegroundRestore(): void { + AudioManager._clearForegroundRestore(); + AudioManager._foregroundRestoreTimer = window.setTimeout(() => { + AudioManager._foregroundRestoreTimer = undefined; + AudioManager.resume().catch(() => AudioManager._prepareGestureResume()); + }, AudioManager._foregroundRestoreDelay); + } + + private static _clearForegroundRestore(): void { + if (AudioManager._foregroundRestoreTimer === undefined) { + return; + } + window.clearTimeout(AudioManager._foregroundRestoreTimer); + AudioManager._foregroundRestoreTimer = undefined; + } + private static _prepareGestureResume(): Promise { - // iOS WKWebView WebKit bug(Triggered in LingGuang App): AudioContext may be in a "zombie" state where - // state reports "suspended" but resume() alone won't restart audio rendering. - // Calling suspend() first forces a clean internal state reset before user gesture triggers resume. - // Related: https://bugs.webkit.org/show_bug.cgi?id=263627 - return AudioManager.suspend() - .catch(() => {}) - .then(() => { - AudioManager._needsUserGestureResume = true; - }); + // iOS WKWebView may report a resumable state while rendering is still frozen. + // Force a clean context edge, then let a gesture or foreground retry restore sources. + AudioManager._needsUserGestureResume = true; + return AudioManager.suspend().catch(() => {}); } } diff --git a/tests/src/core/audio/AudioSourcePendingPlayback.test.ts b/tests/src/core/audio/AudioSourcePendingPlayback.test.ts index edb31b12ef..5e7e4645a1 100644 --- a/tests/src/core/audio/AudioSourcePendingPlayback.test.ts +++ b/tests/src/core/audio/AudioSourcePendingPlayback.test.ts @@ -64,7 +64,8 @@ class MockAudioContext { } async function flushAsync(): Promise { - await new Promise((resolve) => setTimeout(resolve, 0)); + await Promise.resolve(); + await Promise.resolve(); } function createAudioSource(): AudioSource { @@ -94,12 +95,15 @@ describe("AudioSource pending playback", () => { (AudioManager as any)._pendingSources = new Set(); (AudioManager as any)._playingSources = new Set(); (AudioManager as any)._interruptedSources = new Set(); + (AudioManager as any)._foregroundRestoreTimer = undefined; + (AudioManager as any)._hidden = false; MockAudioContext.shouldResumeSucceed = true; MockAudioContext.resumeResultQueue = null; AudioManager._playingCount = 0; }); afterEach(() => { + vi.useRealTimers(); vi.restoreAllMocks(); document.replaceChildren(); }); @@ -157,7 +161,7 @@ describe("AudioSource pending playback", () => { expect((AudioManager as any)._context).to.be.null; }); - it("resumes automatically when returning to the foreground with active audio", async () => { + it("does not resume foreground audio before a hide event", async () => { createAudioSource(); const context = (AudioManager as any)._context as MockAudioContext; @@ -171,13 +175,12 @@ describe("AudioSource pending playback", () => { document.dispatchEvent(new Event("visibilitychange")); await flushAsync(); - expect(resumeSpy).toHaveBeenCalledTimes(1); + expect(resumeSpy).not.toHaveBeenCalled(); expect(suspendSpy).not.toHaveBeenCalled(); - expect(context.state).to.equal("running"); expect((AudioManager as any)._needsUserGestureResume).to.be.false; }); - it("recreates active source nodes after a background interruption", async () => { + it("recreates interrupted source nodes from a foreground gesture", async () => { const audioSource = createAudioSource(); const context = (AudioManager as any)._context as MockAudioContext; @@ -201,36 +204,125 @@ describe("AudioSource pending playback", () => { document.dispatchEvent(new Event("visibilitychange")); await flushAsync(); + expect(audioSource.isPlaying).to.be.false; + expect((AudioManager as any)._interruptedSources.size).to.equal(1); + expect((AudioManager as any)._needsUserGestureResume).to.be.true; + + document.dispatchEvent(new Event("touchend")); + await flushAsync(); + expect(audioSource.isPlaying).to.be.true; expect(AudioManager._playingCount).to.equal(1); expect((AudioManager as any)._interruptedSources.size).to.equal(0); expect((audioSource as any)._sourceNode).not.to.equal(firstSourceNode); }); - it("falls back to gesture recovery when foreground auto-resume fails", async () => { - createAudioSource(); + it("recovers interrupted source nodes from foreground retry after the restore delay", async () => { + vi.useFakeTimers(); + const audioSource = createAudioSource(); const context = (AudioManager as any)._context as MockAudioContext; - vi.spyOn(document, "hidden", "get").mockReturnValue(false); + context.state = "running"; + audioSource.play(); + + const hiddenSpy = vi.spyOn(document, "hidden", "get").mockReturnValue(true); + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + hiddenSpy.mockReturnValue(false); + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.false; + + await vi.advanceTimersByTimeAsync(299); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.false; + + await vi.advanceTimersByTimeAsync(1); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.true; + expect((AudioManager as any)._interruptedSources.size).to.equal(0); + }); + + it("handles document pagehide/pageshow and mouseup recovery", async () => { + const audioSource = createAudioSource(); + const context = (AudioManager as any)._context as MockAudioContext; + + context.state = "running"; + audioSource.play(); + + document.dispatchEvent(new Event("pagehide")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.false; + expect((AudioManager as any)._interruptedSources.size).to.equal(1); + + document.dispatchEvent(new Event("pageshow")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.false; + expect((AudioManager as any)._needsUserGestureResume).to.be.true; + + document.dispatchEvent(new Event("mouseup")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.true; + expect((AudioManager as any)._interruptedSources.size).to.equal(0); + }); + + it("keeps gesture recovery when foreground resume fails", async () => { + vi.useFakeTimers(); + const audioSource = createAudioSource(); + const context = (AudioManager as any)._context as MockAudioContext; + + vi.spyOn(console, "warn").mockImplementation(() => {}); + const hiddenSpy = vi.spyOn(document, "hidden", "get").mockReturnValue(true); const resumeSpy = vi.spyOn(context, "resume"); const suspendSpy = vi.spyOn(AudioManager, "suspend"); - MockAudioContext.shouldResumeSucceed = false; - context.state = "suspended"; - AudioManager._playingCount = 1; + context.state = "running"; + audioSource.play(); + + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + MockAudioContext.shouldResumeSucceed = false; + hiddenSpy.mockReturnValue(false); document.dispatchEvent(new Event("visibilitychange")); await flushAsync(); + expect(resumeSpy).not.toHaveBeenCalled(); + expect(suspendSpy).toHaveBeenCalledTimes(2); + expect((AudioManager as any)._needsUserGestureResume).to.be.true; + + await vi.advanceTimersByTimeAsync(299); + await flushAsync(); + + expect(resumeSpy).not.toHaveBeenCalled(); + expect(suspendSpy).toHaveBeenCalledTimes(2); + expect((AudioManager as any)._needsUserGestureResume).to.be.true; + + await vi.advanceTimersByTimeAsync(1); + await flushAsync(); + expect(resumeSpy).toHaveBeenCalledTimes(1); - expect(suspendSpy).toHaveBeenCalledTimes(1); + expect(suspendSpy).toHaveBeenCalledTimes(3); expect((AudioManager as any)._needsUserGestureResume).to.be.true; - MockAudioContext.shouldResumeSucceed = true; document.dispatchEvent(new Event("click")); await flushAsync(); expect(resumeSpy).toHaveBeenCalledTimes(2); + expect((AudioManager as any)._needsUserGestureResume).to.be.true; + + MockAudioContext.shouldResumeSucceed = true; + document.dispatchEvent(new Event("click")); + await flushAsync(); + + expect(resumeSpy).toHaveBeenCalledTimes(3); expect(context.state).to.equal("running"); expect((AudioManager as any)._needsUserGestureResume).to.be.false; }); From bf57cb1c510af82c019bc7563668e072a95ddcf1 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Fri, 24 Apr 2026 16:39:46 +0800 Subject: [PATCH 063/130] test(loader): cover stripped entity child attachment --- tests/src/loader/StrippedEntity.test.ts | 277 ++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 tests/src/loader/StrippedEntity.test.ts diff --git a/tests/src/loader/StrippedEntity.test.ts b/tests/src/loader/StrippedEntity.test.ts new file mode 100644 index 0000000000..6f078d1d17 --- /dev/null +++ b/tests/src/loader/StrippedEntity.test.ts @@ -0,0 +1,277 @@ +/** + * Tests stripped entities that proxy internal prefab-instance entities. + * + * A stripped entity can resolve to an entity inside a nested prefab instance and + * serve as the parent for newly added entities. + */ +import { expect, beforeAll, afterAll, describe, it } from "vitest"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import type { IHierarchyFile } from "@galacean/engine-loader"; +import { Vector3 } from "@galacean/engine-math"; +import { PrefabParser } from "../../../packages/loader/src/prefab/PrefabParser"; + +let engine: WebGLEngine; + +function getResourceObjectPool(): Record { + return (engine.resourceManager as unknown as { _objectPool: Record })._objectPool; +} + +function cachePrefab(url: string, prefab: unknown): void { + getResourceObjectPool()[url] = prefab; +} + +function removeCachedPrefab(url: string): void { + delete getResourceObjectPool()[url]; +} + +beforeAll(async () => { + const canvas = document.createElement("canvas"); + canvas.width = 256; + canvas.height = 256; + engine = await WebGLEngine.create({ canvas }); +}); + +afterAll(() => { + engine?.destroy(); +}); + +describe("IStrippedEntity as addedEntities mechanism", () => { + it("keeps nested prefab children after instantiation", async () => { + const nestedPrefabData: IHierarchyFile = { + entities: [ + { id: "0", name: "nestedRoot", components: [], children: ["1", "2"] }, + { id: "1", name: "boneHand", parent: "0", components: [] }, + { id: "2", name: "otherChild", parent: "0", components: [] } + ] + }; + const nestedPrefab = await PrefabParser.parse(engine, "sanity.prefab", nestedPrefabData); + const inst = nestedPrefab.instantiate(); + const names = inst.children.map((c) => c.name); + expect(names).toContain("boneHand"); + expect(names).toContain("otherChild"); + inst.destroy(); + }); + + it("should attach new child entity under an internal entity of a nested prefab", async () => { + // Nested prefab: + // root + // - boneHand (proxied by the stripped entity) + // - otherChild + const nestedPrefabData: IHierarchyFile = { + entities: [ + { id: "0", name: "nestedRoot", components: [], children: ["1", "2"] }, + { id: "1", name: "boneHand", parent: "0", components: [] }, + { id: "2", name: "otherChild", parent: "0", components: [] } + ] + }; + const nestedPrefab = await PrefabParser.parse(engine, "char.prefab", nestedPrefabData); + cachePrefab("char.prefab", nestedPrefab); + + // Outer prefab: + // - IRefEntity instantiating the nested prefab (only direct child of outerRoot) + // - IStrippedEntity proxying to boneHand, not in outerRoot.children + // - weaponslot listed in stripped entity's children + const outerPrefabData: IHierarchyFile = { + entities: [ + { + id: "outerRoot", + name: "Character", + components: [], + children: ["charInst"] + }, + { + id: "charInst", + name: "char", + parent: "outerRoot", + components: [], + assetUrl: "char.prefab", + isClone: true, + modifications: [], + removedEntities: [], + removedComponents: [] + } as any, + { + // Do not set `name`: _applyEntityData would override the internal entity's name. + strippedId: "boneHandle", + prefabInstanceId: "charInst", + prefabSource: { assetId: "", entityId: "0" }, + components: [], + children: ["weaponslot"] + } as any, + { + id: "weaponslot", + name: "weaponslot1", + parent: "boneHandle", + components: [], + position: { x: 0.5, y: 0.25, z: 0 } + } + ] + }; + + const outerPrefab = await PrefabParser.parse(engine, "outer.prefab", outerPrefabData); + const root = outerPrefab.instantiate(); + + const charInst = root.children.find((c) => c.name === "char"); + expect(charInst, "char instance should be a child of outerRoot").not.toBeUndefined(); + + const boneHand = charInst!.children.find((c) => c.name === "boneHand"); + expect(boneHand, "boneHand should exist inside nested prefab").not.toBeUndefined(); + + const weaponslot = boneHand!.children.find((c) => c.name === "weaponslot1"); + expect(weaponslot, "weaponslot1 should be attached as child of boneHand via stripped entity").not.toBeUndefined(); + expect(weaponslot!.transform.position.x).toBeCloseTo(0.5, 5); + expect(weaponslot!.transform.position.y).toBeCloseTo(0.25, 5); + expect(weaponslot!.transform.position.z).toBeCloseTo(0, 5); + + root.destroy(); + removeCachedPrefab("char.prefab"); + }); + + it("should support multiple stripped entities targeting different internal entities (LeftHand / RightHand socket case)", async () => { + // Nested prefab: root -> [LeftHand, RightHand] + const nestedPrefabData: IHierarchyFile = { + entities: [ + { id: "0", name: "nestedRoot", components: [], children: ["1", "2"] }, + { id: "1", name: "LeftHand", parent: "0", components: [] }, + { id: "2", name: "RightHand", parent: "0", components: [] } + ] + }; + const nestedPrefab = await PrefabParser.parse(engine, "body.prefab", nestedPrefabData); + cachePrefab("body.prefab", nestedPrefab); + + // entityId is the path string generated by _generateInstanceContext: + // nestedRoot = "" + // nestedRoot.children[0] = LeftHand -> "0" + // nestedRoot.children[1] = RightHand -> "1" + const outerPrefabData: IHierarchyFile = { + entities: [ + { + id: "outerRoot", + name: "Player", + components: [], + children: ["bodyInst"] + }, + { + id: "bodyInst", + name: "body", + parent: "outerRoot", + components: [], + assetUrl: "body.prefab", + isClone: true, + modifications: [], + removedEntities: [], + removedComponents: [] + } as any, + { + strippedId: "lhHandle", + prefabInstanceId: "bodyInst", + prefabSource: { assetId: "", entityId: "0" }, + components: [], + children: ["ws1"] + } as any, + { + strippedId: "rhHandle", + prefabInstanceId: "bodyInst", + prefabSource: { assetId: "", entityId: "1" }, + components: [], + children: ["ws2", "ws3"] + } as any, + { id: "ws1", name: "weaponslot1", parent: "lhHandle", components: [] }, + { id: "ws2", name: "weaponslot2", parent: "rhHandle", components: [] }, + { id: "ws3", name: "weaponslot3", parent: "rhHandle", components: [] } + ] + }; + + const outerPrefab = await PrefabParser.parse(engine, "player.prefab", outerPrefabData); + const root = outerPrefab.instantiate(); + const bodyInst = root.children.find((c) => c.name === "body")!; + const leftHand = bodyInst.children.find((c) => c.name === "LeftHand")!; + const rightHand = bodyInst.children.find((c) => c.name === "RightHand")!; + + const slot1 = leftHand.children.find((c) => c.name === "weaponslot1"); + const slot2 = rightHand.children.find((c) => c.name === "weaponslot2"); + const slot3 = rightHand.children.find((c) => c.name === "weaponslot3"); + + expect(slot1, "weaponslot1 under LeftHand").not.toBeUndefined(); + expect(slot2, "weaponslot2 under RightHand").not.toBeUndefined(); + expect(slot3, "weaponslot3 under RightHand").not.toBeUndefined(); + + // No new entities should remain as direct children of outerRoot after reparenting + expect(root.children.find((c) => c.name === "weaponslot1")).toBeUndefined(); + expect(root.children.find((c) => c.name === "lh-proxy")).toBeUndefined(); + + root.destroy(); + removeCachedPrefab("body.prefab"); + }); + + it("should propagate internal entity transform changes to the attached child (bone-drives-weapon behavior)", async () => { + // When an internal entity moves, the child attached through the stripped + // entity follows through normal hierarchy transforms. + const nestedPrefabData: IHierarchyFile = { + entities: [ + { id: "0", name: "nestedRoot", components: [], children: ["1"] }, + { id: "1", name: "bone", parent: "0", components: [] } + ] + }; + const nestedPrefab = await PrefabParser.parse(engine, "skel.prefab", nestedPrefabData); + cachePrefab("skel.prefab", nestedPrefab); + + const outerPrefabData: IHierarchyFile = { + entities: [ + { + id: "outerRoot", + name: "Actor", + components: [], + children: ["skelInst"] + }, + { + id: "skelInst", + name: "skel", + parent: "outerRoot", + components: [], + assetUrl: "skel.prefab", + isClone: true, + modifications: [], + removedEntities: [], + removedComponents: [] + } as any, + { + strippedId: "boneHandle", + prefabInstanceId: "skelInst", + prefabSource: { assetId: "", entityId: "0" }, + components: [], + children: ["weapon"] + } as any, + { + id: "weapon", + name: "weapon", + parent: "boneHandle", + components: [], + position: { x: 1, y: 0, z: 0 } + } + ] + }; + + const outerPrefab = await PrefabParser.parse(engine, "actor.prefab", outerPrefabData); + const root = outerPrefab.instantiate(); + const bone = root.children.find((c) => c.name === "skel")!.children.find((c) => c.name === "bone")!; + const weapon = bone.children.find((c) => c.name === "weapon")!; + + // Initial: bone at origin, weapon world position = (1, 0, 0) + const worldPos = new Vector3(); + worldPos.copyFrom(weapon.transform.worldPosition); + expect(worldPos.x).toBeCloseTo(1, 5); + expect(worldPos.y).toBeCloseTo(0, 5); + expect(worldPos.z).toBeCloseTo(0, 5); + + // Simulate transform animation: move bone to (10, 5, -3) + bone.transform.setPosition(10, 5, -3); + worldPos.copyFrom(weapon.transform.worldPosition); + expect(worldPos.x).toBeCloseTo(11, 5); // 10 + 1 + expect(worldPos.y).toBeCloseTo(5, 5); // 5 + 0 + expect(worldPos.z).toBeCloseTo(-3, 5); // -3 + 0 + + root.destroy(); + removeCachedPrefab("skel.prefab"); + }); +}); From 779648d657158ec44a4613e03f0d8a202724b85c Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Fri, 24 Apr 2026 17:49:29 +0800 Subject: [PATCH 064/130] fix: ui batch render --- .../src/RenderPipeline/BasicRenderPipeline.ts | 15 +++++++++++---- .../core/src/RenderPipeline/CullingResults.ts | 12 ++++++++++++ packages/core/src/RenderPipeline/RenderQueue.ts | 6 +++++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index 84f5dcb7c7..28049597dd 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -136,10 +136,12 @@ export class BasicRenderPipeline { // Depth use camera's view and projection matrix this._cullingResults.setRenderUpdateFlagTrue(ContextRendererUpdateFlag.viewProjectionMatrix); context.applyVirtualCamera(camera._virtualCamera, depthPassEnabled); - this._prepareRender(context); + this._prepareWorldRender(context); const batcherManager = engine._batcherManager; - cullingResults.sortBatch(batcherManager); + cullingResults.sort(); + this._prepareUIRender(context); + cullingResults.batch(batcherManager); batcherManager.uploadBuffer(); if (depthPassEnabled) { @@ -475,10 +477,10 @@ export class BasicRenderPipeline { rhi.drawPrimitive(mesh._primitive, mesh.subMesh, program); } - private _prepareRender(context: RenderContext): void { + private _prepareWorldRender(context: RenderContext): void { const camera = context.camera; const { engine, enableFrustumCulling, cullingMask, _frustum: frustum } = camera; - const { _renderers: renderers, _canvases: canvases } = camera.scene._componentsManager; + const renderers = camera.scene._componentsManager._renderers; const rendererElements = renderers._elements; for (let i = renderers.length - 1; i >= 0; --i) { @@ -497,7 +499,12 @@ export class BasicRenderPipeline { renderer._prepareRender(context); renderer._renderFrameCount = engine.time.frameCount; } + } + private _prepareUIRender(context: RenderContext): void { + const camera = context.camera; + const { cullingMask } = camera; + const canvases = camera.scene._componentsManager._canvases; const canvasesElements = canvases._elements; for (let i = canvases.length - 1; i >= 0; i--) { const canvas = canvasesElements[i]; diff --git a/packages/core/src/RenderPipeline/CullingResults.ts b/packages/core/src/RenderPipeline/CullingResults.ts index 673e0694d3..6ff5c298a7 100644 --- a/packages/core/src/RenderPipeline/CullingResults.ts +++ b/packages/core/src/RenderPipeline/CullingResults.ts @@ -30,6 +30,18 @@ export class CullingResults { this.transparentQueue.sortBatch(RenderQueue.compareForTransparent, batcherManager); } + sort() { + this.opaqueQueue.sort(RenderQueue.compareForOpaque); + this.alphaTestQueue.sort(RenderQueue.compareForOpaque); + this.transparentQueue.sort(RenderQueue.compareForTransparent); + } + + batch(batcherManager: BatcherManager): void { + this.opaqueQueue.batch(batcherManager); + this.alphaTestQueue.batch(batcherManager); + this.transparentQueue.batch(batcherManager); + } + setRenderUpdateFlagTrue(rendererUpdateFlag: ContextRendererUpdateFlag): void { this.opaqueQueue.rendererUpdateFlag |= rendererUpdateFlag; this.transparentQueue.rendererUpdateFlag |= rendererUpdateFlag; diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 2290f33a6d..f9ee9fb0a4 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -2,8 +2,8 @@ import { SpriteMaskInteraction } from "../2d/enums/SpriteMaskInteraction"; import { BasicResources, RenderStateElementMap } from "../BasicResources"; import { Utils } from "../Utils"; import { RenderQueueType, Shader } from "../shader"; -import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; +import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { BatcherManager } from "./BatcherManager"; import { InstanceBuffer } from "./InstanceBuffer"; import { ContextRendererUpdateFlag, RenderContext } from "./RenderContext"; @@ -41,6 +41,10 @@ export class RenderQueue { this.batch(batcherManager); } + sort(compareFunc: Function): void { + Utils._quickSort(this.elements, 0, this.elements.length, compareFunc); + } + batch(batcherManager: BatcherManager): void { batcherManager.batch(this); } From 5f00d11c62920fac8b07b9211375975551c2c9fa Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Fri, 24 Apr 2026 18:06:35 +0800 Subject: [PATCH 065/130] chore: release v0.0.0-experimental-2.0-game.6 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 37287758e5..10d8108fed 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index 117d74a297..2afee7da55 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 5263038470..dc0b6d4c78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index bcdf86e846..545099402b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 4edeffba34..373541de5a 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index cbcd4adb3a..de2435f362 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index e209f26574..4c22a84ca4 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index b5be3c3133..d197caf7e2 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index c09a0de543..4296c0d9d4 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 787c872cca..c7710f0e7e 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 37fdd5e46b..5e49b49847 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index c85c5d69af..256e0eed35 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index daff035771..c9f1a8bbe1 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index 69b2e70765..e3b8aa207b 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index a85168d4ff..920f0f8044 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 813f4f4c3d..cb7ddc5400 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index 79579d0f8f..72ef5992b1 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.5", + "version": "0.0.0-experimental-2.0-game.6", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From 5c747f0e8df4024e413eef617921586797e829af Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Mon, 27 Apr 2026 06:53:22 +0800 Subject: [PATCH 066/130] fix: filled uv --- .../src/2d/assembler/FilledSpriteAssembler.ts | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/core/src/2d/assembler/FilledSpriteAssembler.ts b/packages/core/src/2d/assembler/FilledSpriteAssembler.ts index 894f1b07c6..b5e9797119 100644 --- a/packages/core/src/2d/assembler/FilledSpriteAssembler.ts +++ b/packages/core/src/2d/assembler/FilledSpriteAssembler.ts @@ -80,13 +80,31 @@ export class FilledSpriteAssembler { this._filledLinear(renderer, modelMatrix, false); break; case SpriteFilledMode.Radial90: - this._filledRadial90(renderer, modelMatrix, renderer.filledOrigin, renderer.filledAmount, renderer.filledClockWise); + this._filledRadial90( + renderer, + modelMatrix, + renderer.filledOrigin, + renderer.filledAmount, + renderer.filledClockWise + ); break; case SpriteFilledMode.Radial180: - this._filledRadial180(renderer, modelMatrix, renderer.filledOrigin, renderer.filledAmount, renderer.filledClockWise); + this._filledRadial180( + renderer, + modelMatrix, + renderer.filledOrigin, + renderer.filledAmount, + renderer.filledClockWise + ); break; case SpriteFilledMode.Radial360: - this._filledRadial360(renderer, modelMatrix, renderer.filledOrigin, renderer.filledAmount, renderer.filledClockWise); + this._filledRadial360( + renderer, + modelMatrix, + renderer.filledOrigin, + renderer.filledAmount, + renderer.filledClockWise + ); break; default: break; @@ -125,7 +143,7 @@ export class FilledSpriteAssembler { const [lPosLB, lPosRB, lPosLT, lPosRT] = sprite._getPositions(); const spriteUVs = sprite._getUVs(); const { x: left, y: bottom } = spriteUVs[0]; - const { x: right, y: top } = spriteUVs[3]; + const { x: right, y: top } = spriteUVs[15]; const subChunk = renderer._subChunk; const vertices = subChunk.chunk.vertices; @@ -210,7 +228,7 @@ export class FilledSpriteAssembler { const [lPosLB, lPosRB, lPosLT, lPosRT] = sprite._getPositions(); const spriteUVs = sprite._getUVs(); const { x: left, y: bottom } = spriteUVs[0]; - const { x: right, y: top } = spriteUVs[3]; + const { x: right, y: top } = spriteUVs[15]; // Transform 4 corners to world space const [wLB, wRB, wLT, wRT] = this._worldPositions; @@ -276,7 +294,7 @@ export class FilledSpriteAssembler { const [lPosLB, lPosRB, lPosLT, lPosRT] = sprite._getPositions(); const spriteUVs = sprite._getUVs(); const { x: left, y: bottom } = spriteUVs[0]; - const { x: right, y: top } = spriteUVs[3]; + const { x: right, y: top } = spriteUVs[15]; // Transform corners and compute edge midpoints const [wLB, wMB, wRB, wLM, , wRM, wLT, wMT, wRT] = this._worldPositions; @@ -402,7 +420,7 @@ export class FilledSpriteAssembler { const [lPosLB, lPosRB, lPosLT, lPosRT] = sprite._getPositions(); const spriteUVs = sprite._getUVs(); const { x: left, y: bottom } = spriteUVs[0]; - const { x: right, y: top } = spriteUVs[3]; + const { x: right, y: top } = spriteUVs[15]; // --------------- // LT - MT - RT From 08682c12fd20e7fa7254a44a291a5cf4f8e2939f Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 28 Apr 2026 15:06:26 +0800 Subject: [PATCH 067/130] feat: support mask --- examples/src/sprite-mask.ts | 87 ++++ examples/src/ui-mask-alpha.ts | 97 +++++ examples/src/ui-mask-overlay.ts | 120 ++++++ examples/src/ui-mask.ts | 85 ++++ examples/src/ui-rect-mask-nested.ts | 76 ++++ examples/src/ui-rect-mask.ts | 135 ++++++ packages/core/src/2d/sprite/MaskRenderable.ts | 398 ++++++++++++++++++ packages/core/src/2d/sprite/SpriteMask.ts | 270 ++---------- .../core/src/2d/sprite/SpriteMaskUtils.ts | 136 ++++++ packages/core/src/2d/sprite/index.ts | 3 + .../core/src/RenderPipeline/MaskManager.ts | 74 +++- .../src/RenderPipeline/VertexMergeBatcher.ts | 30 ++ packages/core/src/RenderPipeline/index.ts | 3 +- packages/core/src/Renderer.ts | 1 - .../core/src/shaderlib/extra/text.fs.glsl | 33 +- .../core/src/shaderlib/extra/text.vs.glsl | 3 + packages/core/src/ui/UIUtils.ts | 80 +++- packages/ui/src/Utils.ts | 59 ++- packages/ui/src/component/UICanvas.ts | 23 +- packages/ui/src/component/UIRenderer.ts | 212 +++++++++- packages/ui/src/component/advanced/Mask.ts | 80 ++++ .../ui/src/component/advanced/RectMask2D.ts | 157 +++++++ packages/ui/src/component/advanced/Text.ts | 11 - packages/ui/src/component/index.ts | 10 +- packages/ui/src/shader/uiDefault.fs.glsl | 31 +- packages/ui/src/shader/uiDefault.vs.glsl | 3 + .../core/particle/ParticleStopResume.test.ts | 142 +++++++ tests/src/ui/Mask.test.ts | 76 ++++ tests/src/ui/RectMask2D.test.ts | 79 ++++ tests/src/ui/UIInteractive.test.ts | 18 +- 30 files changed, 2256 insertions(+), 276 deletions(-) create mode 100644 examples/src/sprite-mask.ts create mode 100644 examples/src/ui-mask-alpha.ts create mode 100644 examples/src/ui-mask-overlay.ts create mode 100644 examples/src/ui-mask.ts create mode 100644 examples/src/ui-rect-mask-nested.ts create mode 100644 examples/src/ui-rect-mask.ts create mode 100644 packages/core/src/2d/sprite/MaskRenderable.ts create mode 100644 packages/core/src/2d/sprite/SpriteMaskUtils.ts create mode 100644 packages/ui/src/component/advanced/Mask.ts create mode 100644 packages/ui/src/component/advanced/RectMask2D.ts create mode 100644 tests/src/core/particle/ParticleStopResume.test.ts create mode 100644 tests/src/ui/Mask.test.ts create mode 100644 tests/src/ui/RectMask2D.test.ts diff --git a/examples/src/sprite-mask.ts b/examples/src/sprite-mask.ts new file mode 100644 index 0000000000..f8dc719105 --- /dev/null +++ b/examples/src/sprite-mask.ts @@ -0,0 +1,87 @@ +/** + * @title Sprite Mask + * @category 2D + */ +import { + AssetType, + Camera, + Sprite, + SpriteMask, + SpriteMaskInteraction, + SpriteMaskLayer, + SpriteRenderer, + Texture2D, + WebGLEngine +} from "@galacean/engine"; + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + scene.background.solidColor.set(0.05, 0.05, 0.07, 1); + const root = scene.createRootEntity("Root"); + + const cameraEntity = root.createChild("Camera"); + cameraEntity.transform.setPosition(0, 0, 50); + cameraEntity.addComponent(Camera); + + engine.resourceManager + .load({ + url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*ApFPTZSqcMkAAAAAAAAAAAAAARQnAQ", + type: AssetType.Texture2D + }) + .then((texture) => { + const sprite = new Sprite(engine, texture); + const maskSprite = new Sprite(engine, createSolidTexture(engine)); + + const spriteWidth = sprite.width; + const spriteHeight = sprite.height; + // Mask covers ~half of the sprite so the cut is obvious. + const maskWidth = spriteWidth * 0.6; + const maskHeight = spriteHeight * 0.6; + // Lay the two characters out side by side based on sprite size. + const groupOffsetX = spriteWidth * 0.6; + + // --- Left: VisibleInsideMask -> only the part covered by the square mask is visible --- + const leftGroup = root.createChild("LeftGroup"); + leftGroup.transform.setPosition(-groupOffsetX, 0, 0); + + const leftMaskEntity = leftGroup.createChild("Mask"); + const leftMask = leftMaskEntity.addComponent(SpriteMask); + leftMask.sprite = maskSprite; + leftMask.width = maskWidth; + leftMask.height = maskHeight; + leftMask.influenceLayers = SpriteMaskLayer.Layer0; + + const leftSpriteEntity = leftGroup.createChild("Sprite"); + const leftSprite = leftSpriteEntity.addComponent(SpriteRenderer); + leftSprite.sprite = sprite; + leftSprite.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + leftSprite.maskLayer = SpriteMaskLayer.Layer0; + + // --- Right: VisibleOutsideMask -> character with a square hole punched out --- + const rightGroup = root.createChild("RightGroup"); + rightGroup.transform.setPosition(groupOffsetX, 0, 0); + + const rightMaskEntity = rightGroup.createChild("Mask"); + const rightMask = rightMaskEntity.addComponent(SpriteMask); + rightMask.sprite = maskSprite; + rightMask.width = maskWidth; + rightMask.height = maskHeight; + rightMask.influenceLayers = SpriteMaskLayer.Layer1; + + const rightSpriteEntity = rightGroup.createChild("Sprite"); + const rightSprite = rightSpriteEntity.addComponent(SpriteRenderer); + rightSprite.sprite = sprite; + rightSprite.maskInteraction = SpriteMaskInteraction.VisibleOutsideMask; + rightSprite.maskLayer = SpriteMaskLayer.Layer1; + }); + + engine.run(); +}); + +function createSolidTexture(engine: WebGLEngine): Texture2D { + const texture = new Texture2D(engine, 1, 1); + texture.setPixelBuffer(new Uint8Array([255, 255, 255, 255])); + return texture; +} diff --git a/examples/src/ui-mask-alpha.ts b/examples/src/ui-mask-alpha.ts new file mode 100644 index 0000000000..9b5f340fd0 --- /dev/null +++ b/examples/src/ui-mask-alpha.ts @@ -0,0 +1,97 @@ +/** + * @title UI Mask Alpha Cutoff + * @category UI + */ +import * as dat from "dat.gui"; +import { Camera, Color, Sprite, SpriteMaskInteraction, Texture2D, TextureFormat, WebGLEngine } from "@galacean/engine"; +import { CanvasRenderMode, Image, Mask, Text, UICanvas, UITransform } from "@galacean/engine-ui"; + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + scene.background.solidColor = new Color(0.03, 0.04, 0.07, 1); + const root = scene.createRootEntity("Root"); + + const cameraEntity = root.createChild("Camera"); + cameraEntity.transform.setPosition(0, 0, 10); + const camera = cameraEntity.addComponent(Camera); + + const canvasEntity = root.createChild("UICanvas"); + const uiCanvas = canvasEntity.addComponent(UICanvas); + uiCanvas.renderMode = CanvasRenderMode.ScreenSpaceCamera; + uiCanvas.camera = camera; + uiCanvas.referenceResolutionPerUnit = 100; + uiCanvas.referenceResolution.set(1200, 800); + + const solidSprite = createSolidSprite(engine); + const circleSprite = createCircleSprite(engine, 256); + + const groupEntity = canvasEntity.createChild("Group"); + (groupEntity.transform).setPosition(0, 40, 0); + + // Circular mask + const maskEntity = groupEntity.createChild("Mask"); + (maskEntity.transform).size.set(300, 300); + const mask = maskEntity.addComponent(Mask); + mask.sprite = circleSprite; + mask.alphaCutoff = 0.5; + + // Background visible inside circle + const insideEntity = groupEntity.createChild("Inside"); + (insideEntity.transform).size.set(500, 500); + const inside = insideEntity.addComponent(Image); + inside.sprite = solidSprite; + inside.color.set(0.95, 0.61, 0.07, 1); + inside.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + + const labelEntity = canvasEntity.createChild("Label"); + (labelEntity.transform).size.set(800, 80); + (labelEntity.transform).setPosition(0, -260, 0); + const label = labelEntity.addComponent(Text); + label.text = "Drag the slider to change alphaCutoff"; + label.fontSize = 28; + label.color.set(0.85, 0.9, 1, 1); + + const gui = new dat.GUI(); + const state = { alphaCutoff: 0.5 }; + gui + .add(state, "alphaCutoff", 0.0, 1.0, 0.01) + .name("Mask Alpha Cutoff") + .onChange((value: number) => { + mask.alphaCutoff = value; + }); + + engine.run(); +}); + +function createSolidSprite(engine: WebGLEngine): Sprite { + const texture = new Texture2D(engine, 1, 1); + texture.setPixelBuffer(new Uint8Array([255, 255, 255, 255])); + return new Sprite(engine, texture); +} + +/** Soft circle: alpha falls off radially so alphaCutoff has visible effect. */ +function createCircleSprite(engine: WebGLEngine, size: number): Sprite { + const buffer = new Uint8Array(size * size * 4); + const cx = size * 0.5; + const cy = size * 0.5; + const radius = size * 0.5; + for (let y = 0; y < size; y++) { + for (let x = 0; x < size; x++) { + const dx = x - cx; + const dy = y - cy; + const dist = Math.sqrt(dx * dx + dy * dy); + const t = Math.max(0, 1 - dist / radius); + const alpha = Math.min(255, Math.floor(t * 255)); + const i = (y * size + x) * 4; + buffer[i] = 255; + buffer[i + 1] = 255; + buffer[i + 2] = 255; + buffer[i + 3] = alpha; + } + } + const texture = new Texture2D(engine, size, size, TextureFormat.R8G8B8A8, false); + texture.setPixelBuffer(buffer); + return new Sprite(engine, texture); +} diff --git a/examples/src/ui-mask-overlay.ts b/examples/src/ui-mask-overlay.ts new file mode 100644 index 0000000000..e7fa6b5096 --- /dev/null +++ b/examples/src/ui-mask-overlay.ts @@ -0,0 +1,120 @@ +/** + * @title UI Mask Overlay + * @category UI + */ +import { Camera, Color, Sprite, SpriteMaskInteraction, Texture2D, WebGLEngine } from "@galacean/engine"; +import { CanvasRenderMode, Image, Mask, RectMask2D, Text, UICanvas, UITransform } from "@galacean/engine-ui"; + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + scene.background.solidColor = new Color(0.03, 0.04, 0.07, 1); + const root = scene.createRootEntity("Root"); + + // Camera is required for default scene rendering even though overlay UI doesn't use it for projection. + const cameraEntity = root.createChild("Camera"); + cameraEntity.transform.setPosition(0, 0, 10); + cameraEntity.addComponent(Camera); + + const canvasEntity = root.createChild("UICanvas"); + const uiCanvas = canvasEntity.addComponent(UICanvas); + uiCanvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; + uiCanvas.referenceResolutionPerUnit = 100; + uiCanvas.referenceResolution.set(1200, 800); + + const solidSprite = createSolidSprite(engine); + + // ===== Left half: SpriteMask (Mask component) ===== + const maskGroup = canvasEntity.createChild("MaskGroup"); + (maskGroup.transform).setPosition(-300, 60, 0); + + const maskEntity = maskGroup.createChild("Mask"); + (maskEntity.transform).size.set(280, 280); + const mask = maskEntity.addComponent(Mask); + mask.sprite = solidSprite; + + const insideEntity = maskGroup.createChild("InsideImage"); + (insideEntity.transform).size.set(440, 440); + const inside = insideEntity.addComponent(Image); + inside.sprite = solidSprite; + inside.color.set(0.91, 0.3, 0.24, 1); + inside.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + + const maskLabelEntity = maskGroup.createChild("Label"); + (maskLabelEntity.transform).size.set(360, 60); + (maskLabelEntity.transform).setPosition(0, -260, 0); + const maskLabel = maskLabelEntity.addComponent(Text); + maskLabel.text = "Mask (Overlay)"; + maskLabel.fontSize = 32; + maskLabel.color.set(1, 1, 1, 1); + + // ===== Right half: RectMask2D ===== + const rectGroup = canvasEntity.createChild("RectGroup"); + (rectGroup.transform).setPosition(300, 60, 0); + + const viewportEntity = rectGroup.createChild("Viewport"); + (viewportEntity.transform).size.set(360, 280); + const viewport = viewportEntity.addComponent(Image); + viewport.sprite = solidSprite; + viewport.color.set(0.17, 0.18, 0.2, 1); + viewportEntity.addComponent(RectMask2D); + + const contentEntity = viewportEntity.createChild("Content"); + (contentEntity.transform).size.set(560, 480); + (contentEntity.transform).setPosition(60, -50, 0); + + const tileColors = [ + new Color(0.91, 0.3, 0.24, 1), + new Color(0.16, 0.5, 0.73, 1), + new Color(0.18, 0.8, 0.44, 1), + new Color(0.95, 0.61, 0.07, 1) + ]; + const tileSize = 220; + const gap = 20; + for (let row = 0; row < 2; row++) { + for (let col = 0; col < 2; col++) { + const i = row * 2 + col; + const tileEntity = contentEntity.createChild(`Tile_${i}`); + const t = tileEntity.transform; + t.size.set(tileSize, tileSize); + t.setPosition(col * (tileSize + gap) - (tileSize + gap) / 2, (tileSize + gap) / 2 - row * (tileSize + gap), 0); + + const tile = tileEntity.addComponent(Image); + tile.sprite = solidSprite; + tile.color = tileColors[i]; + + const labelEntity = tileEntity.createChild("Label"); + (labelEntity.transform).size.set(tileSize, tileSize); + const label = labelEntity.addComponent(Text); + label.text = `${i + 1}`; + label.fontSize = 64; + label.color.set(1, 1, 1, 1); + } + } + + const rectLabelEntity = rectGroup.createChild("Label"); + (rectLabelEntity.transform).size.set(360, 60); + (rectLabelEntity.transform).setPosition(0, -260, 0); + const rectLabel = rectLabelEntity.addComponent(Text); + rectLabel.text = "RectMask2D (Overlay)"; + rectLabel.fontSize = 32; + rectLabel.color.set(1, 1, 1, 1); + + // Top header + const headerEntity = canvasEntity.createChild("Header"); + (headerEntity.transform).size.set(900, 80); + (headerEntity.transform).setPosition(0, 320, 0); + const header = headerEntity.addComponent(Text); + header.text = "ScreenSpaceOverlay · Mask & RectMask2D"; + header.fontSize = 36; + header.color.set(0.85, 0.92, 1, 1); + + engine.run(); +}); + +function createSolidSprite(engine: WebGLEngine): Sprite { + const texture = new Texture2D(engine, 1, 1); + texture.setPixelBuffer(new Uint8Array([255, 255, 255, 255])); + return new Sprite(engine, texture); +} diff --git a/examples/src/ui-mask.ts b/examples/src/ui-mask.ts new file mode 100644 index 0000000000..260392e6e1 --- /dev/null +++ b/examples/src/ui-mask.ts @@ -0,0 +1,85 @@ +/** + * @title UI Mask + * @category UI + */ +import { Camera, Color, Sprite, SpriteMaskInteraction, Texture2D, WebGLEngine } from "@galacean/engine"; +import { CanvasRenderMode, Image, Mask, Text, UICanvas, UITransform } from "@galacean/engine-ui"; + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + scene.background.solidColor = new Color(0.03, 0.04, 0.07, 1); + const root = scene.createRootEntity("Root"); + + const cameraEntity = root.createChild("Camera"); + cameraEntity.transform.setPosition(0, 0, 10); + const camera = cameraEntity.addComponent(Camera); + + const canvasEntity = root.createChild("UICanvas"); + const uiCanvas = canvasEntity.addComponent(UICanvas); + uiCanvas.renderMode = CanvasRenderMode.ScreenSpaceCamera; + uiCanvas.camera = camera; + uiCanvas.referenceResolutionPerUnit = 100; + uiCanvas.referenceResolution.set(1200, 800); + + const solidSprite = createSolidSprite(engine); + + // --- Left group: VisibleInsideMask --- + const leftGroupEntity = canvasEntity.createChild("LeftGroup"); + (leftGroupEntity.transform).setPosition(-300, 0, 0); + + // Square mask + const leftMaskEntity = leftGroupEntity.createChild("Mask"); + (leftMaskEntity.transform).size.set(300, 300); + const leftMask = leftMaskEntity.addComponent(Mask); + leftMask.sprite = solidSprite; + + // Image clipped to inside the mask + const insideImageEntity = leftGroupEntity.createChild("InsideImage"); + (insideImageEntity.transform).size.set(500, 500); + const insideImage = insideImageEntity.addComponent(Image); + insideImage.sprite = solidSprite; + insideImage.color.set(0.91, 0.3, 0.24, 1); + insideImage.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + + const leftLabelEntity = leftGroupEntity.createChild("Label"); + (leftLabelEntity.transform).size.set(300, 60); + (leftLabelEntity.transform).setPosition(0, -210, 0); + const leftLabel = leftLabelEntity.addComponent(Text); + leftLabel.text = "VisibleInsideMask"; + leftLabel.fontSize = 30; + leftLabel.color.set(1, 1, 1, 1); + + // --- Right group: VisibleOutsideMask --- + const rightGroupEntity = canvasEntity.createChild("RightGroup"); + (rightGroupEntity.transform).setPosition(300, 0, 0); + + const rightMaskEntity = rightGroupEntity.createChild("Mask"); + (rightMaskEntity.transform).size.set(300, 300); + const rightMask = rightMaskEntity.addComponent(Mask); + rightMask.sprite = solidSprite; + + const outsideImageEntity = rightGroupEntity.createChild("OutsideImage"); + (outsideImageEntity.transform).size.set(500, 500); + const outsideImage = outsideImageEntity.addComponent(Image); + outsideImage.sprite = solidSprite; + outsideImage.color.set(0.16, 0.5, 0.73, 1); + outsideImage.maskInteraction = SpriteMaskInteraction.VisibleOutsideMask; + + const rightLabelEntity = rightGroupEntity.createChild("Label"); + (rightLabelEntity.transform).size.set(300, 60); + (rightLabelEntity.transform).setPosition(0, -210, 0); + const rightLabel = rightLabelEntity.addComponent(Text); + rightLabel.text = "VisibleOutsideMask"; + rightLabel.fontSize = 30; + rightLabel.color.set(1, 1, 1, 1); + + engine.run(); +}); + +function createSolidSprite(engine: WebGLEngine): Sprite { + const texture = new Texture2D(engine, 1, 1); + texture.setPixelBuffer(new Uint8Array([255, 255, 255, 255])); + return new Sprite(engine, texture); +} diff --git a/examples/src/ui-rect-mask-nested.ts b/examples/src/ui-rect-mask-nested.ts new file mode 100644 index 0000000000..0eae971bfb --- /dev/null +++ b/examples/src/ui-rect-mask-nested.ts @@ -0,0 +1,76 @@ +/** + * @title UI RectMask2D Nested + * @category UI + */ +import { Camera, Color, Sprite, Texture2D, WebGLEngine } from "@galacean/engine"; +import { CanvasRenderMode, Image, RectMask2D, Text, UICanvas, UITransform } from "@galacean/engine-ui"; + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + scene.background.solidColor = new Color(0.03, 0.04, 0.07, 1); + const root = scene.createRootEntity("Root"); + + const cameraEntity = root.createChild("Camera"); + cameraEntity.transform.setPosition(0, 0, 10); + const camera = cameraEntity.addComponent(Camera); + + const canvasEntity = root.createChild("UICanvas"); + const uiCanvas = canvasEntity.addComponent(UICanvas); + uiCanvas.renderMode = CanvasRenderMode.ScreenSpaceCamera; + uiCanvas.camera = camera; + uiCanvas.referenceResolutionPerUnit = 100; + uiCanvas.referenceResolution.set(1200, 800); + + const solidSprite = createSolidSprite(engine); + + // Outer mask (wide, short) + const outerEntity = canvasEntity.createChild("OuterMask"); + (outerEntity.transform).size.set(560, 240); + (outerEntity.transform).setPosition(0, 60, 0); + const outerImage = outerEntity.addComponent(Image); + outerImage.sprite = solidSprite; + outerImage.color.set(0.13, 0.18, 0.28, 1); + outerEntity.addComponent(RectMask2D); + + // Inner mask (tall, narrow), child of outer + const innerEntity = outerEntity.createChild("InnerMask"); + (innerEntity.transform).size.set(240, 480); + (innerEntity.transform).setPosition(0, 0, 0); + const innerImage = innerEntity.addComponent(Image); + innerImage.sprite = solidSprite; + innerImage.color.set(0.2, 0.3, 0.5, 1); + innerEntity.addComponent(RectMask2D); + + // Big colored content under both masks — only the intersection of outer ∩ inner remains visible + const contentEntity = innerEntity.createChild("Content"); + (contentEntity.transform).size.set(800, 800); + const content = contentEntity.addComponent(Image); + content.sprite = solidSprite; + content.color.set(0.95, 0.61, 0.07, 1); + + const labelTopEntity = canvasEntity.createChild("LabelTop"); + (labelTopEntity.transform).size.set(900, 60); + (labelTopEntity.transform).setPosition(0, 220, 0); + const labelTop = labelTopEntity.addComponent(Text); + labelTop.text = "Outer 560x240 ∩ Inner 240x480 → visible: 240x240"; + labelTop.fontSize = 28; + labelTop.color.set(1, 1, 1, 1); + + const labelBottomEntity = canvasEntity.createChild("LabelBottom"); + (labelBottomEntity.transform).size.set(900, 60); + (labelBottomEntity.transform).setPosition(0, -260, 0); + const labelBottom = labelBottomEntity.addComponent(Text); + labelBottom.text = "Nested RectMask2D takes the rect intersection of all ancestor masks."; + labelBottom.fontSize = 24; + labelBottom.color.set(0.77, 0.82, 0.89, 1); + + engine.run(); +}); + +function createSolidSprite(engine: WebGLEngine): Sprite { + const texture = new Texture2D(engine, 1, 1); + texture.setPixelBuffer(new Uint8Array([255, 255, 255, 255])); + return new Sprite(engine, texture); +} diff --git a/examples/src/ui-rect-mask.ts b/examples/src/ui-rect-mask.ts new file mode 100644 index 0000000000..92078d6c6a --- /dev/null +++ b/examples/src/ui-rect-mask.ts @@ -0,0 +1,135 @@ +/** + * @title UI RectMask2D + * @category UI + */ +import * as dat from "dat.gui"; +import { Camera, Color, Sprite, Texture2D, Vector2, WebGLEngine } from "@galacean/engine"; +import { CanvasRenderMode, Image, RectMask2D, Text, UICanvas, UITransform } from "@galacean/engine-ui"; + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + scene.background.solidColor = new Color(0.03, 0.04, 0.07, 1); + const root = scene.createRootEntity("Root"); + + const cameraEntity = root.createChild("Camera"); + cameraEntity.transform.setPosition(0, 0, 10); + const camera = cameraEntity.addComponent(Camera); + + const canvasEntity = root.createChild("UICanvas"); + const uiCanvas = canvasEntity.addComponent(UICanvas); + uiCanvas.renderMode = CanvasRenderMode.ScreenSpaceCamera; + uiCanvas.camera = camera; + uiCanvas.referenceResolutionPerUnit = 100; + uiCanvas.referenceResolution.set(1200, 800); + + const solidSprite = createSolidSprite(engine); + + // Frame card + const frameEntity = canvasEntity.createChild("Frame"); + (frameEntity.transform).size.set(560, 460); + (frameEntity.transform).setPosition(-180, 20, 0); + const frame = frameEntity.addComponent(Image); + frame.sprite = solidSprite; + frame.color.set(0.09, 0.11, 0.15, 1); + + // Viewport with RectMask2D + const viewportEntity = frameEntity.createChild("Viewport"); + (viewportEntity.transform).size.set(440, 320); + (viewportEntity.transform).setPosition(30, -10, 0); + const viewport = viewportEntity.addComponent(Image); + viewport.sprite = solidSprite; + viewport.color.set(0.17, 0.18, 0.2, 1); + const rectMask = viewportEntity.addComponent(RectMask2D); + + // 3x3 colored tiles overflow the viewport + const contentEntity = viewportEntity.createChild("Content"); + (contentEntity.transform).size.set(740, 560); + (contentEntity.transform).setPosition(80, -60, 0); + + const colors = [ + new Color(0.91, 0.3, 0.24, 1), + new Color(0.16, 0.5, 0.73, 1), + new Color(0.18, 0.8, 0.44, 1), + new Color(0.95, 0.61, 0.07, 1), + new Color(0.56, 0.27, 0.68, 1), + new Color(0.2, 0.6, 0.86, 1), + new Color(0.83, 0.33, 0.33, 1), + new Color(0.1, 0.74, 0.61, 1), + new Color(0.93, 0.78, 0.0, 1) + ]; + + const tileWidth = 180; + const tileHeight = 180; + const gap = 10; + for (let row = 0; row < 3; row++) { + for (let col = 0; col < 3; col++) { + const index = row * 3 + col; + const tileEntity = contentEntity.createChild(`Tile_${index}`); + const t = tileEntity.transform; + t.size.set(tileWidth, tileHeight); + t.setPosition(col * (tileWidth + gap) - 170, 170 - row * (tileHeight + gap), 0); + + const tile = tileEntity.addComponent(Image); + tile.sprite = solidSprite; + tile.color = colors[index]; + + const labelEntity = tileEntity.createChild("Label"); + (labelEntity.transform).size.set(tileWidth, tileHeight); + const label = labelEntity.addComponent(Text); + label.text = `${index + 1}`; + label.fontSize = 56; + label.color.set(1, 1, 1, 1); + } + } + + // Right info card + const noteEntity = canvasEntity.createChild("Note"); + (noteEntity.transform).size.set(360, 220); + (noteEntity.transform).setPosition(290, 20, 0); + const note = noteEntity.addComponent(Image); + note.sprite = solidSprite; + note.color.set(0.08, 0.09, 0.12, 1); + + const noteTextEntity = noteEntity.createChild("Copy"); + (noteTextEntity.transform).size.set(320, 180); + const noteText = noteTextEntity.addComponent(Text); + noteText.text = + "RectMask2D clips Image\nand Text by an axis-\naligned rectangle.\n\nUse the GUI to tweak\nsoftness / alphaClip."; + noteText.fontSize = 26; + noteText.color.set(0.77, 0.82, 0.89, 1); + + const gui = new dat.GUI(); + const state = { + softnessX: 0, + softnessY: 0, + alphaClip: false + }; + gui + .add(state, "softnessX", 0, 80, 1) + .name("softness.x") + .onChange((v: number) => { + rectMask.softness = new Vector2(v, state.softnessY); + }); + gui + .add(state, "softnessY", 0, 80, 1) + .name("softness.y") + .onChange((v: number) => { + rectMask.softness = new Vector2(state.softnessX, v); + }); + gui + .add(state, "alphaClip") + .name("alphaClip (discard)") + .onChange((v: boolean) => { + rectMask.alphaClip = v; + }); + + engine.run(); +}); + +function createSolidSprite(engine: WebGLEngine): Sprite { + const texture = new Texture2D(engine, 1, 1); + texture.setPixelBuffer(new Uint8Array([255, 255, 255, 255])); + return new Sprite(engine, texture); +} diff --git a/packages/core/src/2d/sprite/MaskRenderable.ts b/packages/core/src/2d/sprite/MaskRenderable.ts new file mode 100644 index 0000000000..441492ba85 --- /dev/null +++ b/packages/core/src/2d/sprite/MaskRenderable.ts @@ -0,0 +1,398 @@ +import { BoundingBox, Vector2, Vector3 } from "@galacean/engine-math"; +import { RenderElement } from "../../RenderPipeline/RenderElement"; +import { VertexMergeBatcher } from "../../RenderPipeline/VertexMergeBatcher"; +import { Renderer, RendererUpdateFlags } from "../../Renderer"; +import { assignmentClone, ignoreClone } from "../../clone/CloneManager"; +import { SpriteMaskLayer } from "../../enums/SpriteMaskLayer"; +import { ShaderProperty } from "../../shader/ShaderProperty"; +import type { ISpriteRenderer } from "../assembler/ISpriteRenderer"; +import { SimpleSpriteAssembler } from "../assembler/SimpleSpriteAssembler"; +import { SpriteModifyFlags } from "../enums/SpriteModifyFlags"; +import { Sprite } from "./Sprite"; +import { SpriteMaskUtils } from "./SpriteMaskUtils"; + +/** + * Public contract of the MaskRenderable mixin, used for declaration file generation. + */ +export interface IMaskRenderable { + influenceLayers: SpriteMaskLayer; + flipX: boolean; + flipY: boolean; + sprite: Sprite; + alphaCutoff: number; + _renderElement: RenderElement; + _maskIndex: number; + _containsWorldPoint(worldPoint: Vector3): boolean; + _initMask(): void; + _cloneMaskData(target: IMaskRenderable): void; + _destroyMaskResources(): void; + _updateMaskBounds(worldBounds: BoundingBox): void; + _renderMask(distanceForSort: number): void; + _onSpriteChange(type: SpriteModifyFlags): void; + _onSpriteChangeExtra(type: SpriteModifyFlags): void; + _getSpriteWidth(): number; + _getSpriteHeight(): number; + _getSpritePivot(): Vector2; +} + +type RendererConstructor = abstract new (...args: any[]) => Renderer; + +/** + * Mixin that provides shared mask rendering logic for both 2D SpriteMask and UI Mask. + */ +export function MaskRenderable( + Base: T +): (abstract new (...args: any[]) => IMaskRenderable) & T { + abstract class MaskRenderableBase extends Base implements IMaskRenderable { + private static _maskTextureProperty = ShaderProperty.getByName("renderer_MaskTexture"); + private static _alphaCutoffProperty = ShaderProperty.getByName("renderer_MaskAlphaCutoff"); + + @assignmentClone + private _influenceLayers: SpriteMaskLayer = SpriteMaskLayer.Everything; + /** @internal */ + @ignoreClone + _renderElement: RenderElement; + /** @internal */ + @ignoreClone + _maskIndex: number = -1; + @ignoreClone + private _sprite: Sprite = null; + @assignmentClone + private _flipX: boolean = false; + @assignmentClone + private _flipY: boolean = false; + @assignmentClone + private _alphaCutoff: number = 0.5; + + /** + * The mask layers the sprite mask influence to. + */ + get influenceLayers(): SpriteMaskLayer { + return this._influenceLayers; + } + + set influenceLayers(value: SpriteMaskLayer) { + if (this._influenceLayers !== value) { + this._influenceLayers = value; + // @ts-ignore + if (this._phasedActiveInScene) { + // @ts-ignore + this.scene._maskManager.onMaskInfluenceLayersChange(); + } + } + } + + /** + * Flips the sprite on the X axis. + */ + get flipX(): boolean { + return this._flipX; + } + + set flipX(value: boolean) { + if (this._flipX !== value) { + this._flipX = value; + // @ts-ignore + this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; + } + } + + /** + * Flips the sprite on the Y axis. + */ + get flipY(): boolean { + return this._flipY; + } + + set flipY(value: boolean) { + if (this._flipY !== value) { + this._flipY = value; + // @ts-ignore + this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; + } + } + + /** + * The Sprite to render. + */ + get sprite(): Sprite { + return this._sprite; + } + + set sprite(value: Sprite | null) { + const lastSprite = this._sprite; + if (lastSprite !== value) { + if (lastSprite) { + // @ts-ignore + this._addResourceReferCount(lastSprite, -1); + lastSprite._updateFlagManager.removeListener(this._onSpriteChange); + } + // @ts-ignore + this._dirtyUpdateFlag |= MaskDirtyFlags.All; + if (value) { + // @ts-ignore + this._addResourceReferCount(value, 1); + value._updateFlagManager.addListener(this._onSpriteChange); + // @ts-ignore + this.shaderData.setTexture(MaskRenderableBase._maskTextureProperty, value.texture); + } else { + // @ts-ignore + this.shaderData.setTexture(MaskRenderableBase._maskTextureProperty, null); + } + this._sprite = value; + } + } + + /** + * The minimum alpha value used by the mask to select the area of influence defined over the mask's sprite. Value between 0 and 1. + */ + get alphaCutoff(): number { + return this._alphaCutoff; + } + + set alphaCutoff(value: number) { + if (this._alphaCutoff !== value) { + this._alphaCutoff = value; + // @ts-ignore + this.shaderData.setFloat(MaskRenderableBase._alphaCutoffProperty, value); + } + } + + /** + * @internal + */ + // @ts-ignore + override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { + return VertexMergeBatcher.canBatchSpriteMask(preElement, curElement); + } + + /** + * @internal + */ + // @ts-ignore + override _batch(preElement: RenderElement | null, curElement: RenderElement): void { + VertexMergeBatcher.batch(preElement, curElement); + } + + /** + * @internal + */ + // @ts-ignore + override _onEnableInScene(): void { + // @ts-ignore + super._onEnableInScene(); + // @ts-ignore + this.scene._maskManager.addSpriteMask(this); + } + + /** + * @internal + */ + // @ts-ignore + override _onDisableInScene(): void { + // @ts-ignore + super._onDisableInScene(); + // @ts-ignore + this.scene._maskManager.removeSpriteMask(this); + } + + /** + * @internal + */ + _containsWorldPoint(worldPoint: Vector3): boolean { + return SpriteMaskUtils.containsWorldPoint( + worldPoint, + this._sprite, + // @ts-ignore + this._transformEntity.transform.worldMatrix, + this._getSpriteWidth(), + this._getSpriteHeight(), + this._getSpritePivot(), + this._flipX, + this._flipY, + this._alphaCutoff + ); + } + + /** + * @internal + * Initialize shared mask resources. Must be called from subclass constructor. + */ + _initMask(): void { + SimpleSpriteAssembler.resetData(this as unknown as ISpriteRenderer); + // @ts-ignore + this.setMaterial(this._engine._basicResources.spriteMaskDefaultMaterial); + // @ts-ignore + this.shaderData.setFloat(MaskRenderableBase._alphaCutoffProperty, this._alphaCutoff); + this._renderElement = new RenderElement(); + this._onSpriteChange = this._onSpriteChange.bind(this); + } + + /** + * @internal + * Clone mask data to target. Called from subclass _cloneTo. + */ + _cloneMaskData(target: MaskRenderableBase): void { + target.sprite = this._sprite; + } + + /** + * @internal + * Release mask sprite resources. Called from subclass _onDestroy. + */ + _destroyMaskResources(): void { + const sprite = this._sprite; + if (sprite) { + // @ts-ignore + this._addResourceReferCount(sprite, -1); + sprite._updateFlagManager.removeListener(this._onSpriteChange); + } + this._sprite = null; + this._renderElement = null; + } + + /** + * @internal + * Update bounds using SimpleSpriteAssembler directly. + */ + _updateMaskBounds(worldBounds: BoundingBox): void { + const sprite = this._sprite; + if (sprite) { + SimpleSpriteAssembler.updatePositions( + this as unknown as ISpriteRenderer, + // @ts-ignore + this._transformEntity.transform.worldMatrix, + this._getSpriteWidth(), + this._getSpriteHeight(), + this._getSpritePivot(), + this._flipX, + this._flipY + ); + } else { + // @ts-ignore + const { worldPosition } = this._transformEntity.transform; + worldBounds.min.copyFrom(worldPosition); + worldBounds.max.copyFrom(worldPosition); + } + } + + /** + * @internal + * Shared render logic for mask geometry. + */ + _renderMask(distanceForSort: number): void { + const { _sprite: sprite } = this; + const width = this._getSpriteWidth(); + const height = this._getSpriteHeight(); + if (!sprite?.texture || !width || !height) { + return; + } + + // @ts-ignore + let material = this.getMaterial(); + if (!material) { + return; + } + if (material.destroyed) { + // @ts-ignore + material = this._engine._basicResources.spriteMaskDefaultMaterial; + } + + // Update position + // @ts-ignore + if (this._dirtyUpdateFlag & RendererUpdateFlags.WorldVolume) { + SimpleSpriteAssembler.updatePositions( + this as unknown as ISpriteRenderer, + // @ts-ignore + this._transformEntity.transform.worldMatrix, + width, + height, + this._getSpritePivot(), + this._flipX, + this._flipY + ); + // @ts-ignore + this._dirtyUpdateFlag &= ~RendererUpdateFlags.WorldVolume; + } + + // Update uv + // @ts-ignore + if (this._dirtyUpdateFlag & MaskDirtyFlags.UV) { + SimpleSpriteAssembler.updateUVs(this as unknown as ISpriteRenderer); + // @ts-ignore + this._dirtyUpdateFlag &= ~MaskDirtyFlags.UV; + } + + const renderElement = this._renderElement; + const subChunk = (this as any)._subChunk; + // @ts-ignore + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, sprite.texture, subChunk); + // @ts-ignore + renderElement.priority = this.priority; + renderElement.distanceForSort = distanceForSort; + renderElement.subShader = material.shader.subShaders[0]; + } + + /** @internal */ + @ignoreClone + _onSpriteChange(type: SpriteModifyFlags): void { + switch (type) { + case SpriteModifyFlags.texture: + // @ts-ignore + this.shaderData.setTexture(MaskRenderableBase._maskTextureProperty, this.sprite.texture); + break; + case SpriteModifyFlags.region: + case SpriteModifyFlags.atlasRegionOffset: + // @ts-ignore + this._dirtyUpdateFlag |= MaskDirtyFlags.WorldVolumeAndUV; + break; + case SpriteModifyFlags.atlasRegion: + // @ts-ignore + this._dirtyUpdateFlag |= MaskDirtyFlags.UV; + break; + case SpriteModifyFlags.destroy: + this.sprite = null; + break; + default: + this._onSpriteChangeExtra(type); + break; + } + } + + /** + * @internal + * Hook for subclass-specific sprite change handling. + * SpriteMask overrides this to handle size/pivot changes. + */ + _onSpriteChangeExtra(type: SpriteModifyFlags): void {} + + /** @internal */ + _getSpriteWidth(): number { + return 0; + } + /** @internal */ + _getSpriteHeight(): number { + return 0; + } + /** @internal */ + _getSpritePivot(): Vector2 { + return null; + } + } + + return MaskRenderableBase as unknown as (abstract new (...args: any[]) => IMaskRenderable) & T; +} + +/** + * @remarks Extends `RendererUpdateFlags`. + */ +export enum MaskDirtyFlags { + /** UV. */ + UV = 0x2, + /** Automatic Size. */ + AutomaticSize = 0x8, + /** WorldVolume and UV. */ + WorldVolumeAndUV = 0x3, + /** All. */ + All = 0xb +} diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts index 1f740081f9..771faead34 100644 --- a/packages/core/src/2d/sprite/SpriteMask.ts +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -1,44 +1,25 @@ import { BoundingBox } from "@galacean/engine-math"; import { Entity } from "../../Entity"; -import { VertexMergeBatcher } from "../../RenderPipeline/VertexMergeBatcher"; import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; -import { RenderElement } from "../../RenderPipeline/RenderElement"; import { Renderer, RendererUpdateFlags } from "../../Renderer"; import { assignmentClone, ignoreClone } from "../../clone/CloneManager"; -import { SpriteMaskLayer } from "../../enums/SpriteMaskLayer"; import { ShaderProperty } from "../../shader/ShaderProperty"; -import { ISpriteRenderer } from "../assembler/ISpriteRenderer"; -import { SimpleSpriteAssembler } from "../assembler/SimpleSpriteAssembler"; import { SpriteModifyFlags } from "../enums/SpriteModifyFlags"; -import { Sprite } from "./Sprite"; +import { MaskDirtyFlags, MaskRenderable } from "./MaskRenderable"; /** * A component for masking Sprites. */ -export class SpriteMask extends Renderer implements ISpriteRenderer { - /** @internal */ - static _textureProperty: ShaderProperty = ShaderProperty.getByName("renderer_MaskTexture"); +export class SpriteMask extends MaskRenderable(Renderer) { /** @internal */ static _alphaCutoffProperty: ShaderProperty = ShaderProperty.getByName("renderer_MaskAlphaCutoff"); - - /** The mask layers the sprite mask influence to. */ - @assignmentClone - influenceLayers: SpriteMaskLayer = SpriteMaskLayer.Everything; /** @internal */ - @ignoreClone - _renderElement: RenderElement; - + static _textureProperty: ShaderProperty = ShaderProperty.getByName("renderer_MaskTexture"); /** @internal */ @ignoreClone _subChunk: SubPrimitiveChunk; - /** @internal */ - @ignoreClone - _maskIndex: number = -1; - - @ignoreClone - private _sprite: Sprite = null; @ignoreClone private _automaticWidth: number = 0; @@ -48,13 +29,6 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { private _customWidth: number = undefined; @assignmentClone private _customHeight: number = undefined; - @assignmentClone - private _flipX: boolean = false; - @assignmentClone - private _flipY: boolean = false; - - @assignmentClone - private _alphaCutoff: number = 0.5; /** * Render width (in world coordinates). @@ -67,7 +41,7 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { if (this._customWidth !== undefined) { return this._customWidth; } else { - this._dirtyUpdateFlag & SpriteMaskUpdateFlags.AutomaticSize && this._calDefaultSize(); + this._dirtyUpdateFlag & MaskDirtyFlags.AutomaticSize && this._calDefaultSize(); return this._automaticWidth; } } @@ -90,7 +64,7 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { if (this._customHeight !== undefined) { return this._customHeight; } else { - this._dirtyUpdateFlag & SpriteMaskUpdateFlags.AutomaticSize && this._calDefaultSize(); + this._dirtyUpdateFlag & MaskDirtyFlags.AutomaticSize && this._calDefaultSize(); return this._automaticHeight; } } @@ -102,84 +76,12 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { } } - /** - * Flips the sprite on the X axis. - */ - get flipX(): boolean { - return this._flipX; - } - - set flipX(value: boolean) { - if (this._flipX !== value) { - this._flipX = value; - this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; - } - } - - /** - * Flips the sprite on the Y axis. - */ - get flipY(): boolean { - return this._flipY; - } - - set flipY(value: boolean) { - if (this._flipY !== value) { - this._flipY = value; - this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; - } - } - - /** - * The Sprite to render. - */ - get sprite(): Sprite { - return this._sprite; - } - - set sprite(value: Sprite | null) { - const lastSprite = this._sprite; - if (lastSprite !== value) { - if (lastSprite) { - this._addResourceReferCount(lastSprite, -1); - lastSprite._updateFlagManager.removeListener(this._onSpriteChange); - } - this._dirtyUpdateFlag |= SpriteMaskUpdateFlags.All; - if (value) { - this._addResourceReferCount(value, 1); - value._updateFlagManager.addListener(this._onSpriteChange); - this.shaderData.setTexture(SpriteMask._textureProperty, value.texture); - } else { - this.shaderData.setTexture(SpriteMask._textureProperty, null); - } - this._sprite = value; - } - } - - /** - * The minimum alpha value used by the mask to select the area of influence defined over the mask's sprite. Value between 0 and 1. - */ - get alphaCutoff(): number { - return this._alphaCutoff; - } - - set alphaCutoff(value: number) { - if (this._alphaCutoff !== value) { - this._alphaCutoff = value; - this.shaderData.setFloat(SpriteMask._alphaCutoffProperty, value); - } - } - /** * @internal */ constructor(entity: Entity) { super(entity); - SimpleSpriteAssembler.resetData(this); - this.setMaterial(this._engine._basicResources.spriteMaskDefaultMaterial); - this.shaderData.setFloat(SpriteMask._alphaCutoffProperty, this._alphaCutoff); - this._renderElement = new RenderElement(); - this._onSpriteChange = this._onSpriteChange.bind(this); + this._initMask(); } /** @@ -195,37 +97,7 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { */ override _cloneTo(target: SpriteMask): void { super._cloneTo(target); - target.sprite = this._sprite; - } - - /** - * @internal - */ - override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { - return VertexMergeBatcher.canBatchSpriteMask(preElement, curElement); - } - - /** - * @internal - */ - override _batch(preElement: RenderElement | null, curElement: RenderElement): void { - VertexMergeBatcher.batch(preElement, curElement); - } - - /** - * @internal - */ - override _onEnableInScene(): void { - super._onEnableInScene(); - this.scene._maskManager.addSpriteMask(this); - } - - /** - * @internal - */ - override _onDisableInScene(): void { - super._onDisableInScene(); - this.scene._maskManager.removeSpriteMask(this); + this._cloneMaskData(target); } /** @@ -236,144 +108,64 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { } protected override _updateBounds(worldBounds: BoundingBox): void { - const sprite = this._sprite; - if (sprite) { - SimpleSpriteAssembler.updatePositions( - this, - this._transformEntity.transform.worldMatrix, - this.width, - this.height, - sprite.pivot, - this._flipX, - this._flipY - ); - } else { - const { worldPosition } = this._transformEntity.transform; - worldBounds.min.copyFrom(worldPosition); - worldBounds.max.copyFrom(worldPosition); - } + this._updateMaskBounds(worldBounds); } /** * @inheritdoc */ protected override _render(context: RenderContext): void { - const { _sprite: sprite } = this; - if (!sprite?.texture || !this.width || !this.height) { - return; - } - - let material = this.getMaterial(); - if (!material) { - return; - } - const { _engine: engine } = this; - // @todo: This question needs to be raised rather than hidden. - if (material.destroyed) { - material = engine._basicResources.spriteMaskDefaultMaterial; - } - - // Update position - if (this._dirtyUpdateFlag & RendererUpdateFlags.WorldVolume) { - SimpleSpriteAssembler.updatePositions( - this, - this._transformEntity.transform.worldMatrix, - this.width, - this.height, - sprite.pivot, - this._flipX, - this._flipY - ); - this._dirtyUpdateFlag &= ~RendererUpdateFlags.WorldVolume; - } - - // Update uv - if (this._dirtyUpdateFlag & SpriteMaskUpdateFlags.UV) { - SimpleSpriteAssembler.updateUVs(this); - this._dirtyUpdateFlag &= ~SpriteMaskUpdateFlags.UV; - } - - const renderElement = this._renderElement; - const subChunk = this._subChunk; - renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); - renderElement.priority = this.priority; - renderElement.distanceForSort = this._distanceForSort; - renderElement.subShader = material.shader.subShaders[0]; + this._renderMask(this._distanceForSort); } /** * @inheritdoc */ protected override _onDestroy(): void { - const sprite = this._sprite; - if (sprite) { - this._addResourceReferCount(sprite, -1); - sprite._updateFlagManager.removeListener(this._onSpriteChange); - } + this._destroyMaskResources(); super._onDestroy(); - this._sprite = null; if (this._subChunk) { this._getChunkManager().freeSubChunk(this._subChunk); this._subChunk = null; } + } - this._renderElement = null; + override _getSpriteWidth(): number { + return this.width; } - private _calDefaultSize(): void { - const sprite = this._sprite; - if (sprite) { - this._automaticWidth = sprite.width; - this._automaticHeight = sprite.height; - } else { - this._automaticWidth = this._automaticHeight = 0; - } - this._dirtyUpdateFlag &= ~SpriteMaskUpdateFlags.AutomaticSize; + override _getSpriteHeight(): number { + return this.height; } - @ignoreClone - private _onSpriteChange(type: SpriteModifyFlags): void { + override _getSpritePivot() { + return this.sprite?.pivot; + } + + override _onSpriteChangeExtra(type: SpriteModifyFlags): void { switch (type) { - case SpriteModifyFlags.texture: - this.shaderData.setTexture(SpriteMask._textureProperty, this.sprite.texture); - break; case SpriteModifyFlags.size: - this._dirtyUpdateFlag |= SpriteMaskUpdateFlags.AutomaticSize; + this._dirtyUpdateFlag |= MaskDirtyFlags.AutomaticSize; if (this._customWidth === undefined || this._customHeight === undefined) { this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; } break; - case SpriteModifyFlags.region: - case SpriteModifyFlags.atlasRegionOffset: - this._dirtyUpdateFlag |= SpriteMaskUpdateFlags.WorldVolumeAndUV; - break; - case SpriteModifyFlags.atlasRegion: - this._dirtyUpdateFlag |= SpriteMaskUpdateFlags.UV; - break; case SpriteModifyFlags.pivot: this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; break; - case SpriteModifyFlags.destroy: - this.sprite = null; - break; - default: - break; } } -} -/** - * @remarks Extends `RendererUpdateFlags`. - */ -enum SpriteMaskUpdateFlags { - /** UV. */ - UV = 0x2, - /** Automatic Size. */ - AutomaticSize = 0x4, - /** WorldVolume and UV. */ - WorldVolumeAndUV = 0x3, - /** All. */ - All = 0x7 + private _calDefaultSize(): void { + const sprite = this.sprite; + if (sprite) { + this._automaticWidth = sprite.width; + this._automaticHeight = sprite.height; + } else { + this._automaticWidth = this._automaticHeight = 0; + } + this._dirtyUpdateFlag &= ~MaskDirtyFlags.AutomaticSize; + } } diff --git a/packages/core/src/2d/sprite/SpriteMaskUtils.ts b/packages/core/src/2d/sprite/SpriteMaskUtils.ts new file mode 100644 index 0000000000..b7033ee800 --- /dev/null +++ b/packages/core/src/2d/sprite/SpriteMaskUtils.ts @@ -0,0 +1,136 @@ +import { Matrix, Vector2, Vector3 } from "@galacean/engine-math"; +import { Texture2D, TextureFormat } from "../../texture"; +import { Sprite } from "./Sprite"; + +/** + * Internal helpers for sprite mask hit testing. + * @internal + */ +export class SpriteMaskUtils { + private static _tempMat: Matrix = new Matrix(); + private static _tempVec3: Vector3 = new Vector3(); + private static _u8Buffer1 = new Uint8Array(1); + private static _u8Buffer2 = new Uint8Array(2); + private static _u8Buffer4 = new Uint8Array(4); + private static _u16Buffer1 = new Uint16Array(1); + private static _u16Buffer4 = new Uint16Array(4); + private static _f32Buffer4 = new Float32Array(4); + private static _u32Buffer4 = new Uint32Array(4); + + static containsWorldPoint( + worldPoint: Vector3, + sprite: Sprite | null, + worldMatrix: Matrix, + width: number, + height: number, + pivot: Vector2, + flipX: boolean, + flipY: boolean, + alphaCutoff: number = 0 + ): boolean { + if (!sprite || !width || !height) { + return false; + } + + const worldMatrixInv = SpriteMaskUtils._tempMat; + Matrix.invert(worldMatrix, worldMatrixInv); + const localPosition = SpriteMaskUtils._tempVec3; + Vector3.transformCoordinate(worldPoint, worldMatrixInv, localPosition); + + const sx = flipX ? -width : width; + const sy = flipY ? -height : height; + if (!sx || !sy) { + return false; + } + + const spriteX = localPosition.x / sx + pivot.x; + const spriteY = localPosition.y / sy + pivot.y; + const spritePositions = sprite._getPositions(); + const { x: left, y: bottom } = spritePositions[0]; + const { x: right, y: top } = spritePositions[3]; + if (!(spriteX >= left && spriteX <= right && spriteY >= bottom && spriteY <= top)) { + return false; + } + + if (alphaCutoff <= 0) { + return true; + } + + const texture = sprite.texture; + if (!texture) { + return false; + } + + const spriteUVs = sprite._getUVs(); + const leftU = spriteUVs[0].x; + const bottomV = spriteUVs[0].y; + const rightU = spriteUVs[3].x; + const topV = spriteUVs[3].y; + const positionWidth = right - left; + const positionHeight = top - bottom; + if (!positionWidth || !positionHeight) { + return false; + } + + const tx = (spriteX - left) / positionWidth; + const ty = (spriteY - bottom) / positionHeight; + const u = leftU + (rightU - leftU) * tx; + const v = bottomV + (topV - bottomV) * ty; + const x = Math.min(Math.max(Math.floor(u * texture.width), 0), texture.width - 1); + const y = Math.min(Math.max(Math.floor(v * texture.height), 0), texture.height - 1); + return SpriteMaskUtils._sampleTextureAlpha(texture, x, y) >= alphaCutoff; + } + + private static _sampleTextureAlpha(texture: Texture2D, x: number, y: number): number { + try { + switch (texture.format) { + case TextureFormat.R8G8B8A8: { + const buffer = SpriteMaskUtils._u8Buffer4; + texture.getPixelBuffer(x, y, 1, 1, buffer); + return buffer[3] / 255; + } + case TextureFormat.R4G4B4A4: { + const buffer = SpriteMaskUtils._u16Buffer1; + texture.getPixelBuffer(x, y, 1, 1, buffer); + return (buffer[0] & 0xf) / 15; + } + case TextureFormat.R5G5B5A1: { + const buffer = SpriteMaskUtils._u16Buffer1; + texture.getPixelBuffer(x, y, 1, 1, buffer); + return buffer[0] & 0x1; + } + case TextureFormat.Alpha8: + case TextureFormat.R8: { + const buffer = SpriteMaskUtils._u8Buffer1; + texture.getPixelBuffer(x, y, 1, 1, buffer); + return buffer[0] / 255; + } + case TextureFormat.LuminanceAlpha: + case TextureFormat.R8G8: { + const buffer = SpriteMaskUtils._u8Buffer2; + texture.getPixelBuffer(x, y, 1, 1, buffer); + return buffer[1] / 255; + } + case TextureFormat.R16G16B16A16: { + const buffer = SpriteMaskUtils._u16Buffer4; + texture.getPixelBuffer(x, y, 1, 1, buffer); + return buffer[3] / 65535; + } + case TextureFormat.R32G32B32A32: { + const buffer = SpriteMaskUtils._f32Buffer4; + texture.getPixelBuffer(x, y, 1, 1, buffer); + return buffer[3]; + } + case TextureFormat.R32G32B32A32_UInt: { + const buffer = SpriteMaskUtils._u32Buffer4; + texture.getPixelBuffer(x, y, 1, 1, buffer); + return buffer[3] / 4294967295; + } + default: + return 1; + } + } catch { + return 1; + } + } +} diff --git a/packages/core/src/2d/sprite/index.ts b/packages/core/src/2d/sprite/index.ts index 162d016472..c48fb9261b 100644 --- a/packages/core/src/2d/sprite/index.ts +++ b/packages/core/src/2d/sprite/index.ts @@ -1,3 +1,6 @@ +export type { IMaskRenderable } from "./MaskRenderable"; +export { MaskDirtyFlags, MaskRenderable } from "./MaskRenderable"; export { Sprite } from "./Sprite"; export { SpriteMask } from "./SpriteMask"; +export { SpriteMaskUtils } from "./SpriteMaskUtils"; export { SpriteRenderer } from "./SpriteRenderer"; diff --git a/packages/core/src/RenderPipeline/MaskManager.ts b/packages/core/src/RenderPipeline/MaskManager.ts index 8454e1d584..12284ca611 100644 --- a/packages/core/src/RenderPipeline/MaskManager.ts +++ b/packages/core/src/RenderPipeline/MaskManager.ts @@ -1,4 +1,6 @@ -import { SpriteMask } from "../2d"; +import { Vector3 } from "@galacean/engine-math"; +import { SpriteMaskInteraction } from "../2d/enums/SpriteMaskInteraction"; +import { IMaskRenderable } from "../2d/sprite/MaskRenderable"; import { CameraClearFlags } from "../enums/CameraClearFlags"; import { SpriteMaskLayer } from "../enums/SpriteMaskLayer"; import { Material } from "../material"; @@ -28,17 +30,49 @@ export class MaskManager { hasStencilWritten = false; private _preMaskLayer = SpriteMaskLayer.Nothing; - private _allSpriteMasks = new DisorderedArray(); + private _allSpriteMasks = new DisorderedArray(); + private _filteredMasksByLayer = new Map(); + private _isFilteredMasksDirty = true; - addSpriteMask(mask: SpriteMask): void { + addSpriteMask(mask: IMaskRenderable): void { mask._maskIndex = this._allSpriteMasks.length; this._allSpriteMasks.add(mask); + this._setFilteredMasksDirty(); } - removeSpriteMask(mask: SpriteMask): void { + removeSpriteMask(mask: IMaskRenderable): void { const replaced = this._allSpriteMasks.deleteByIndex(mask._maskIndex); replaced && (replaced._maskIndex = mask._maskIndex); mask._maskIndex = -1; + this._setFilteredMasksDirty(); + } + + onMaskInfluenceLayersChange(): void { + this._setFilteredMasksDirty(); + } + + isVisibleByMask(maskInteraction: SpriteMaskInteraction, maskLayer: SpriteMaskLayer, worldPoint: Vector3): boolean { + if (maskInteraction === SpriteMaskInteraction.None) { + return true; + } + + const masks = this._getMasksByLayer(maskLayer); + let insideMask = false; + for (let i = 0, n = masks.length; i < n; i++) { + if (masks[i]._containsWorldPoint(worldPoint)) { + insideMask = true; + break; + } + } + + switch (maskInteraction) { + case SpriteMaskInteraction.VisibleInsideMask: + return insideMask; + case SpriteMaskInteraction.VisibleOutsideMask: + return !insideMask; + default: + return true; + } } drawMask(context: RenderContext, pipelineStageTagValue: string, maskLayer: SpriteMaskLayer): void { @@ -118,6 +152,38 @@ export class MaskManager { const allSpriteMasks = this._allSpriteMasks; allSpriteMasks.length = 0; allSpriteMasks.garbageCollection(); + this._filteredMasksByLayer.clear(); + this._isFilteredMasksDirty = true; + } + + private _setFilteredMasksDirty(): void { + this._isFilteredMasksDirty = true; + } + + private _getMasksByLayer(maskLayer: SpriteMaskLayer): IMaskRenderable[] { + if (maskLayer === SpriteMaskLayer.Nothing) { + return []; + } + + if (this._isFilteredMasksDirty) { + this._filteredMasksByLayer.clear(); + this._isFilteredMasksDirty = false; + } + + let filteredMasks = this._filteredMasksByLayer.get(maskLayer); + if (!filteredMasks) { + filteredMasks = []; + const allMasks = this._allSpriteMasks; + const maskElements = allMasks._elements; + for (let i = 0, n = allMasks.length; i < n; i++) { + const mask = maskElements[i]; + if (mask.influenceLayers & maskLayer) { + filteredMasks.push(mask); + } + } + this._filteredMasksByLayer.set(maskLayer, filteredMasks); + } + return filteredMasks; } private _buildMaskRenderElement( diff --git a/packages/core/src/RenderPipeline/VertexMergeBatcher.ts b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts index c337ec21eb..a627103b29 100644 --- a/packages/core/src/RenderPipeline/VertexMergeBatcher.ts +++ b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts @@ -13,6 +13,36 @@ export class VertexMergeBatcher { const renderer = curElement.component; const maskInteraction = preRenderer.maskInteraction; + const preRendererAny = preRenderer as any; + const curRendererAny = renderer as any; + const rectMaskEnabledA = preRendererAny._rectMaskEnabled; + if (rectMaskEnabledA !== curRendererAny._rectMaskEnabled) { + return false; + } + if (rectMaskEnabledA) { + const rectMaskRectA = preRendererAny._rectMaskRect; + const rectMaskRectB = curRendererAny._rectMaskRect; + const rectMaskSoftnessA = preRendererAny._rectMaskSoftness; + const rectMaskSoftnessB = curRendererAny._rectMaskSoftness; + if ( + !rectMaskRectA || + !rectMaskRectB || + !rectMaskSoftnessA || + !rectMaskSoftnessB || + rectMaskRectA.x !== rectMaskRectB.x || + rectMaskRectA.y !== rectMaskRectB.y || + rectMaskRectA.z !== rectMaskRectB.z || + rectMaskRectA.w !== rectMaskRectB.w || + rectMaskSoftnessA.x !== rectMaskSoftnessB.x || + rectMaskSoftnessA.y !== rectMaskSoftnessB.y || + rectMaskSoftnessA.z !== rectMaskSoftnessB.z || + rectMaskSoftnessA.w !== rectMaskSoftnessB.w || + preRendererAny._rectMaskHardClip !== curRendererAny._rectMaskHardClip + ) { + return false; + } + } + // Order: cheap reference checks → mask state → tag lookup (rare opt-out) return ( preElement.subChunk.chunk === curElement.subChunk.chunk && diff --git a/packages/core/src/RenderPipeline/index.ts b/packages/core/src/RenderPipeline/index.ts index 29431e2740..a95c0f8f46 100644 --- a/packages/core/src/RenderPipeline/index.ts +++ b/packages/core/src/RenderPipeline/index.ts @@ -1,5 +1,6 @@ export { BasicRenderPipeline, RenderQueueFlags } from "./BasicRenderPipeline"; export { VertexMergeBatcher } from "./VertexMergeBatcher"; export { Blitter } from "./Blitter"; -export { RenderQueue } from "./RenderQueue"; export { PipelineStage } from "./enums/PipelineStage"; +export { RenderElement } from "./RenderElement"; +export { RenderQueue } from "./RenderQueue"; diff --git a/packages/core/src/Renderer.ts b/packages/core/src/Renderer.ts index 5af1e71750..c6444d5354 100644 --- a/packages/core/src/Renderer.ts +++ b/packages/core/src/Renderer.ts @@ -47,7 +47,6 @@ export class Renderer extends Component { _globalShaderMacro: ShaderMacroCollection = new ShaderMacroCollection(); @ignoreClone _renderFrameCount: number; - /** @internal */ @assignmentClone _maskInteraction: SpriteMaskInteraction = SpriteMaskInteraction.None; @assignmentClone diff --git a/packages/core/src/shaderlib/extra/text.fs.glsl b/packages/core/src/shaderlib/extra/text.fs.glsl index 8fe1125d69..019d419ba4 100644 --- a/packages/core/src/shaderlib/extra/text.fs.glsl +++ b/packages/core/src/shaderlib/extra/text.fs.glsl @@ -1,15 +1,46 @@ uniform sampler2D renderElement_TextTexture; +uniform vec4 renderer_UIRectClipRect; +uniform float renderer_UIRectClipEnabled; +uniform vec4 renderer_UIRectClipSoftness; +uniform float renderer_UIRectClipHardClip; varying vec2 v_uv; varying vec4 v_color; +varying vec2 v_worldPosition; + +float getUIRectClipAlpha() +{ + vec4 edgeDistance = vec4( + v_worldPosition.x - renderer_UIRectClipRect.x, + v_worldPosition.y - renderer_UIRectClipRect.y, + renderer_UIRectClipRect.z - v_worldPosition.x, + renderer_UIRectClipRect.w - v_worldPosition.y + ); + vec4 hardClipFactor = step(vec4(0.0), edgeDistance); + vec4 softness = max(renderer_UIRectClipSoftness, vec4(1e-5)); + vec4 softClipFactor = clamp(edgeDistance / softness, 0.0, 1.0); + vec4 useSoftness = step(vec4(1e-5), renderer_UIRectClipSoftness); + vec4 clipFactor = mix(hardClipFactor, softClipFactor, useSoftness); + return clipFactor.x * clipFactor.y * clipFactor.z * clipFactor.w; +} void main() { + float rectClipAlpha = 1.0; + if (renderer_UIRectClipEnabled > 0.5) { + rectClipAlpha = getUIRectClipAlpha(); + } + vec4 texColor = texture2D(renderElement_TextTexture, v_uv); #ifdef GRAPHICS_API_WEBGL2 float coverage = texColor.r; #else float coverage = texColor.a; #endif - gl_FragColor = vec4(v_color.rgb, v_color.a * coverage); + vec4 finalColor = vec4(v_color.rgb, v_color.a * coverage); + finalColor.a *= rectClipAlpha; + if (renderer_UIRectClipEnabled > 0.5 && renderer_UIRectClipHardClip > 0.5 && finalColor.a < 0.001) { + discard; + } + gl_FragColor = finalColor; } diff --git a/packages/core/src/shaderlib/extra/text.vs.glsl b/packages/core/src/shaderlib/extra/text.vs.glsl index 37a6b2d333..c3971d0172 100644 --- a/packages/core/src/shaderlib/extra/text.vs.glsl +++ b/packages/core/src/shaderlib/extra/text.vs.glsl @@ -1,4 +1,5 @@ uniform mat4 renderer_MVPMat; +uniform mat4 renderer_ModelMat; attribute vec3 POSITION; attribute vec2 TEXCOORD_0; @@ -6,6 +7,7 @@ attribute vec4 COLOR_0; varying vec2 v_uv; varying vec4 v_color; +varying vec2 v_worldPosition; void main() { @@ -13,4 +15,5 @@ void main() v_uv = TEXCOORD_0; v_color = COLOR_0; + v_worldPosition = POSITION.xy; } diff --git a/packages/core/src/ui/UIUtils.ts b/packages/core/src/ui/UIUtils.ts index a6f7822a03..0e455a135c 100644 --- a/packages/core/src/ui/UIUtils.ts +++ b/packages/core/src/ui/UIUtils.ts @@ -1,14 +1,21 @@ -import { Matrix, Vector4 } from "@galacean/engine-math"; +import { Color, Matrix, Vector4 } from "@galacean/engine-math"; import { Camera } from "../Camera"; import { Engine } from "../Engine"; import { Layer } from "../Layer"; +import { Blitter } from "../RenderPipeline/Blitter"; import { RenderQueue } from "../RenderPipeline"; import { ContextRendererUpdateFlag } from "../RenderPipeline/RenderContext"; import { Scene } from "../Scene"; import { VirtualCamera } from "../VirtualCamera"; import { EngineObject } from "../base"; -import { RenderQueueType, ShaderData, ShaderDataGroup, ShaderMacro } from "../shader"; +import { CameraClearFlags } from "../enums/CameraClearFlags"; +import { Material } from "../material"; +import { RenderQueueType, Shader, ShaderData, ShaderDataGroup, ShaderMacro } from "../shader"; +import { BlendFactor } from "../shader/enums/BlendFactor"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; +import { RenderTarget } from "../texture/RenderTarget"; +import { Texture2D } from "../texture/Texture2D"; +import { TextureFormat } from "../texture/enums/TextureFormat"; import { DisorderedArray } from "../utils/DisorderedArray"; import { IUICanvas } from "./IUICanvas"; @@ -19,6 +26,11 @@ export class UIUtils { private static _virtualCamera: VirtualCamera; private static _viewport: Vector4; private static _overlayCamera: OverlayCamera; + private static _overlayRT: RenderTarget; + private static _overlayBlitMaterial: Material; + private static _clearColor = new Color(0, 0, 0, 0); + /** Flip V so that Y-up RT content maps correctly onto the default framebuffer. */ + private static _flipYScaleOffset = new Vector4(1, -1, 0, 1); static renderOverlay(engine: Engine, scene: Scene, uiCanvases: DisorderedArray): void { engine._macroCollection.enable(UIUtils._shouldSRGBCorrect); @@ -31,10 +43,19 @@ export class UIUtils { camera.engine = engine; camera.scene = scene; renderContext.camera = camera as unknown as Camera; + + const { width, height } = canvas; const { elements: projectE } = virtualCamera.projectionMatrix; const { elements: viewE } = virtualCamera.viewMatrix; - (projectE[0] = 2 / canvas.width), (projectE[5] = 2 / canvas.height), (projectE[10] = 0); - renderContext.setRenderTarget(null, viewport, 0); + (projectE[0] = 2 / width), (projectE[5] = 2 / height), (projectE[10] = 0); + + // Render to an intermediate RT with Depth24Stencil8 so that stencil-based UI Mask works. + // The default canvas framebuffer is created without a stencil buffer + // (see WebGLGraphicDevice._webGLOptions.stencil = false). + const overlayRT = UIUtils._getOverlayRT(engine, width, height); + renderContext.setRenderTarget(overlayRT, viewport, 0); + rhi.clearRenderTarget(engine, CameraClearFlags.All, UIUtils._clearColor); + for (let i = 0, n = uiCanvases.length; i < n; i++) { const uiCanvas = uiCanvases.get(i); if (uiCanvas) { @@ -55,9 +76,60 @@ export class UIUtils { engine._renderCount++; } } + + // Blit overlay RT to default framebuffer with premultiplied alpha blending. + // Blitter.blitTexture picks the non-flipping `blitMesh` when destination is null, + // but the RT contents are written in standard Y-up NDC, so we flip V here via + // sourceScaleOffset to match the default framebuffer orientation. + Blitter.blitTexture( + engine, + overlayRT.getColorTexture(0) as Texture2D, + null, + 0, + viewport, + UIUtils._getOverlayBlitMaterial(engine), + 0, + UIUtils._flipYScaleOffset + ); + renderContext.camera = null; engine._macroCollection.disable(UIUtils._shouldSRGBCorrect); } + + private static _getOverlayRT(engine: Engine, width: number, height: number): RenderTarget { + let rt = UIUtils._overlayRT; + if (!rt || rt.width !== width || rt.height !== height) { + if (rt) { + rt.getColorTexture(0).destroy(); + rt.destroy(); + } + const colorTexture = new Texture2D(engine, width, height, TextureFormat.R8G8B8A8, false); + colorTexture.isGCIgnored = true; + rt = new RenderTarget(engine, width, height, colorTexture, TextureFormat.Depth24Stencil8); + rt.isGCIgnored = true; + UIUtils._overlayRT = rt; + } + return rt; + } + + private static _getOverlayBlitMaterial(engine: Engine): Material { + let material = UIUtils._overlayBlitMaterial; + if (!material) { + material = new Material(engine, Shader.find("blit")); + material.isGCIgnored = true; + const renderState = material.renderState; + renderState.depthState.enabled = false; + renderState.depthState.writeEnabled = false; + const target = renderState.blendState.targetBlendState; + target.enabled = true; + target.sourceColorBlendFactor = BlendFactor.One; + target.destinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha; + target.sourceAlphaBlendFactor = BlendFactor.One; + target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha; + UIUtils._overlayBlitMaterial = material; + } + return material; + } } class OverlayCamera { diff --git a/packages/ui/src/Utils.ts b/packages/ui/src/Utils.ts index f47e0a8053..60040d8e75 100644 --- a/packages/ui/src/Utils.ts +++ b/packages/ui/src/Utils.ts @@ -1,10 +1,67 @@ -import { Entity } from "@galacean/engine"; +import { Entity, Matrix, Plane, Ray, Vector2, Vector3 } from "@galacean/engine"; +import { UITransform } from "./component"; import { RootCanvasModifyFlags, UICanvas } from "./component/UICanvas"; import { GroupModifyFlags, UIGroup } from "./component/UIGroup"; +import { CanvasRenderMode } from "./enums/CanvasRenderMode"; import { IElement } from "./interface/IElement"; import { IGroupAble } from "./interface/IGroupAble"; export class Utils { + static _tempRay: Ray = new Ray(); + static _tempPlane: Plane = new Plane(); + static _tempVec3: Vector3 = new Vector3(); + static _tempMat: Matrix = new Matrix(); + + /** + * Local position of a screen point in the component + */ + static screenToLocalPoint(position: Vector2, transform: UITransform, out: Vector3): Boolean { + const engine = transform.engine; + // Get root canvas + let entity = transform.entity; + let rootCanvas: UICanvas; + while (entity) { + // @ts-ignore + const components = entity._components; + for (let i = 0, n = components.length; i < n; i++) { + const component = components[i]; + if (component.enabled && component instanceof UICanvas && component._isRootCanvas) { + rootCanvas = component; + } + } + entity = entity.parent; + } + if (!rootCanvas) return false; + // Calculate ray + const ray = this._tempRay; + switch (rootCanvas._realRenderMode) { + case CanvasRenderMode.ScreenSpaceOverlay: + // Screen to world ( Assume that world units have a one-to-one relationship with pixel units ) + ray.origin.set(position.x, engine.canvas.height - position.y, 1); + ray.direction.set(0, 0, -1); + break; + case CanvasRenderMode.ScreenSpaceCamera: + rootCanvas.renderCamera.screenPointToRay(position, ray); + break; + default: + // World space not yet supported, see issue #2793 + return false; + } + // Intersect ray with UI plane to get local coordinates + const plane = this._tempPlane; + const normal = plane.normal.copyFrom(transform.worldForward); + plane.distance = -Vector3.dot(normal, transform.worldPosition); + const curDistance = ray.intersectPlane(plane); + if (curDistance >= 0 && curDistance < Number.MAX_SAFE_INTEGER) { + const hitPointWorld = ray.getPoint(curDistance, this._tempVec3); + const worldMatrixInv = this._tempMat; + Matrix.invert(transform.worldMatrix, worldMatrixInv); + Vector3.transformCoordinate(hitPointWorld, worldMatrixInv, out); + return true; + } + return false; + } + static setRootCanvasDirty(element: IElement): void { if (element._isRootCanvasDirty) return; element._isRootCanvasDirty = true; diff --git a/packages/ui/src/component/UICanvas.ts b/packages/ui/src/component/UICanvas.ts index f4bf699135..77dad56014 100644 --- a/packages/ui/src/component/UICanvas.ts +++ b/packages/ui/src/component/UICanvas.ts @@ -25,6 +25,7 @@ import { ResolutionAdaptationMode } from "../enums/ResolutionAdaptationMode"; import { UIHitResult } from "../input/UIHitResult"; import { IElement } from "../interface/IElement"; import { IGroupAble } from "../interface/IGroupAble"; +import { RectMask2D } from "./advanced/RectMask2D"; import { UIGroup } from "./UIGroup"; import { UIRenderer } from "./UIRenderer"; import { UITransform } from "./UITransform"; @@ -39,6 +40,7 @@ export class UICanvas extends Component implements IElement { /** @internal */ static _hierarchyCounter: number = 1; private static _tempGroupAbleList: IGroupAble[] = []; + private static _tempRectMaskList: RectMask2D[] = []; private static _tempVec3: Vector3 = new Vector3(); private static _tempMat: Matrix = new Matrix(); @@ -417,7 +419,8 @@ export class UICanvas extends Component implements IElement { const { _orderedRenderers: renderers, entity } = this; const uiHierarchyVersion = entity._uiHierarchyVersion; if (this._hierarchyVersion !== uiHierarchyVersion) { - renderers.length = this._walk(this.entity, renderers); + UICanvas._tempRectMaskList.length = 0; + renderers.length = this._walk(this.entity, renderers, 0, null, 0); UICanvas._tempGroupAbleList.length = 0; this._hierarchyVersion = uiHierarchyVersion; ++UICanvas._hierarchyCounter; @@ -499,10 +502,18 @@ export class UICanvas extends Component implements IElement { transform.size.set(curWidth / expectX, curHeight / expectY); } - private _walk(entity: Entity, renderers: UIRenderer[], depth = 0, group: UIGroup = null): number { + private _walk( + entity: Entity, + renderers: UIRenderer[], + depth = 0, + group: UIGroup = null, + rectMaskCount: number = 0 + ): number { // @ts-ignore const components: Component[] = entity._components; const tempGroupAbleList = UICanvas._tempGroupAbleList; + const tempRectMaskList = UICanvas._tempRectMaskList; + let rectMask: RectMask2D = null; let groupAbleCount = 0; for (let i = 0, n = components.length; i < n; i++) { const component = components[i]; @@ -514,11 +525,14 @@ export class UICanvas extends Component implements IElement { if (component._isGroupDirty) { tempGroupAbleList[groupAbleCount++] = component; } + component._setRectMasks(tempRectMaskList, rectMaskCount); } else if (component instanceof UIInteractive) { component._isRootCanvasDirty && Utils.setRootCanvas(component, this); if (component._isGroupDirty) { tempGroupAbleList[groupAbleCount++] = component; } + } else if (component instanceof RectMask2D) { + rectMask = component; } else if (component instanceof UIGroup) { component._isRootCanvasDirty && Utils.setRootCanvas(component, this); component._isGroupDirty && Utils.setGroup(component, group); @@ -528,10 +542,13 @@ export class UICanvas extends Component implements IElement { for (let i = 0; i < groupAbleCount; i++) { Utils.setGroup(tempGroupAbleList[i], group); } + if (rectMask) { + tempRectMaskList[rectMaskCount++] = rectMask; + } const children = entity.children; for (let i = 0, n = children.length; i < n; i++) { const child = children[i]; - child.isActive && (depth = this._walk(child, renderers, depth, group)); + child.isActive && (depth = this._walk(child, renderers, depth, group, rectMaskCount)); } return depth; } diff --git a/packages/ui/src/component/UIRenderer.ts b/packages/ui/src/component/UIRenderer.ts index 676a4b241c..f2ceaf6059 100644 --- a/packages/ui/src/component/UIRenderer.ts +++ b/packages/ui/src/component/UIRenderer.ts @@ -11,6 +11,8 @@ import { RendererUpdateFlags, ShaderMacroCollection, ShaderProperty, + SpriteMaskInteraction, + SpriteMaskLayer, Vector3, Vector4, assignmentClone, @@ -21,6 +23,7 @@ import { import { Utils } from "../Utils"; import { UIHitResult } from "../input/UIHitResult"; import { IGraphics } from "../interface/IGraphics"; +import { RectMask2D } from "./advanced/RectMask2D"; import { EntityUIModifyFlags, UICanvas } from "./UICanvas"; import { GroupModifyFlags, UIGroup } from "./UIGroup"; import { UITransform } from "./UITransform"; @@ -37,6 +40,16 @@ export class UIRenderer extends Renderer implements IGraphics { static _tempPlane: Plane = new Plane(); /** @internal */ static _textureProperty: ShaderProperty = ShaderProperty.getByName("renderer_UITexture"); + /** @internal */ + static _rectClipRectProperty: ShaderProperty = ShaderProperty.getByName("renderer_UIRectClipRect"); + /** @internal */ + static _rectClipEnabledProperty: ShaderProperty = ShaderProperty.getByName("renderer_UIRectClipEnabled"); + /** @internal */ + static _rectClipSoftnessProperty: ShaderProperty = ShaderProperty.getByName("renderer_UIRectClipSoftness"); + /** @internal */ + static _rectClipHardClipProperty: ShaderProperty = ShaderProperty.getByName("renderer_UIRectClipHardClip"); + /** @internal */ + static _tempRect: Vector4 = new Vector4(); /** * Custom boundary for raycast detection. @@ -69,6 +82,21 @@ export class UIRenderer extends Renderer implements IGraphics { /** @internal */ @ignoreClone _subChunk; + /** @internal */ + @ignoreClone + _rectMasks: RectMask2D[] = []; + /** @internal */ + @ignoreClone + _rectMaskRect: Vector4 = new Vector4(); + /** @internal */ + @ignoreClone + _rectMaskEnabled: boolean = false; + /** @internal */ + @ignoreClone + _rectMaskSoftness: Vector4 = new Vector4(); + /** @internal */ + @ignoreClone + _rectMaskHardClip: boolean = false; @assignmentClone private _raycastEnabled: boolean = false; @@ -88,6 +116,30 @@ export class UIRenderer extends Renderer implements IGraphics { } } + /** + * The mask layer the ui renderer belongs to. + */ + get maskLayer(): SpriteMaskLayer { + return this._maskLayer; + } + + set maskLayer(value: SpriteMaskLayer) { + this._maskLayer = value; + } + + /** + * Interacts with the masks. + */ + get maskInteraction(): SpriteMaskInteraction { + return this._maskInteraction; + } + + set maskInteraction(value: SpriteMaskInteraction) { + if (this._maskInteraction !== value) { + this._maskInteraction = value; + } + } + /** * Whether this renderer be picked up by raycast. */ @@ -110,6 +162,9 @@ export class UIRenderer extends Renderer implements IGraphics { this._color._onValueChanged = this._onColorChanged; this._groupListener = this._groupListener.bind(this); this._rootCanvasListener = this._rootCanvasListener.bind(this); + this.shaderData.setFloat(UIRenderer._rectClipEnabledProperty, 0); + this.shaderData.setVector4(UIRenderer._rectClipSoftnessProperty, this._rectMaskSoftness); + this.shaderData.setFloat(UIRenderer._rectClipHardClipProperty, 0); } // @ts-ignore @@ -135,6 +190,7 @@ export class UIRenderer extends Renderer implements IGraphics { this._update(context); } + this._updateRectMaskClipState(); this._render(context); // union camera global macro and renderer macro. @@ -237,6 +293,17 @@ export class UIRenderer extends Renderer implements IGraphics { return this.engine._batcherManager.primitiveChunkManagerUI; } + /** + * @internal + */ + _setRectMasks(rectMasks: RectMask2D[], count: number): void { + const targetMasks = this._rectMasks; + targetMasks.length = count; + for (let i = 0; i < count; i++) { + targetMasks[i] = rectMasks[i]; + } + } + /** * @internal */ @@ -252,7 +319,11 @@ export class UIRenderer extends Renderer implements IGraphics { Matrix.invert(transform.worldMatrix, worldMatrixInv); const localPosition = UIRenderer._tempVec31; Vector3.transformCoordinate(hitPointWorld, worldMatrixInv, localPosition); - if (this._hitTest(localPosition)) { + if ( + this._hitTest(localPosition) && + this._isRaycastVisibleByRectMask(hitPointWorld) && + this._isRaycastVisibleByMask(hitPointWorld) + ) { out.component = this; out.distance = curDistance; out.entity = this.entity; @@ -278,6 +349,143 @@ export class UIRenderer extends Renderer implements IGraphics { ); } + private _isRaycastVisibleByMask(hitPointWorld: Vector3): boolean { + const maskInteraction = this._maskInteraction; + if (maskInteraction === SpriteMaskInteraction.None) { + return true; + } + // @ts-ignore + return this.scene._maskManager.isVisibleByMask(maskInteraction, this._maskLayer, hitPointWorld); + } + + private _isRaycastVisibleByRectMask(hitPointWorld: Vector3): boolean { + const rectMasks = this._rectMasks; + for (let i = 0, n = rectMasks.length; i < n; i++) { + const rectMask = rectMasks[i]; + if (!rectMask.enabled || !rectMask.entity.isActiveInHierarchy) { + continue; + } + if (!rectMask._containsWorldPoint(hitPointWorld)) { + return false; + } + } + return true; + } + + private _updateRectMaskClipState(): void { + const rectMasks = this._rectMasks; + const count = rectMasks.length; + if (count <= 0) { + this._resetRectMaskClipState(); + return; + } + + let minX = Number.NEGATIVE_INFINITY; + let minY = Number.NEGATIVE_INFINITY; + let maxX = Number.POSITIVE_INFINITY; + let maxY = Number.POSITIVE_INFINITY; + let clipSoftnessLeft = 0; + let clipSoftnessBottom = 0; + let clipSoftnessRight = 0; + let clipSoftnessTop = 0; + let clipHardClip = false; + let hasActiveMask = false; + const tempRect = UIRenderer._tempRect; + for (let i = 0; i < count; i++) { + const rectMask = rectMasks[i]; + if (!rectMask.enabled || !rectMask.entity.isActiveInHierarchy) { + continue; + } + hasActiveMask = true; + const softness = rectMask.softness; + if (!clipHardClip && rectMask.alphaClip) { + clipHardClip = true; + } + if (!rectMask._getWorldRect(tempRect)) { + minX = 1; + minY = 1; + maxX = 0; + maxY = 0; + break; + } + if (tempRect.x > minX) { + minX = tempRect.x; + clipSoftnessLeft = softness.x; + } + if (tempRect.y > minY) { + minY = tempRect.y; + clipSoftnessBottom = softness.y; + } + if (tempRect.z < maxX) { + maxX = tempRect.z; + clipSoftnessRight = softness.x; + } + if (tempRect.w < maxY) { + maxY = tempRect.w; + clipSoftnessTop = softness.y; + } + } + + if (!hasActiveMask) { + this._resetRectMaskClipState(); + return; + } + + if (minX >= maxX || minY >= maxY) { + minX = 1; + minY = 1; + maxX = 0; + maxY = 0; + clipSoftnessLeft = 0; + clipSoftnessBottom = 0; + clipSoftnessRight = 0; + clipSoftnessTop = 0; + } + + const rectMaskRect = this._rectMaskRect; + if (rectMaskRect.x !== minX || rectMaskRect.y !== minY || rectMaskRect.z !== maxX || rectMaskRect.w !== maxY) { + rectMaskRect.set(minX, minY, maxX, maxY); + this.shaderData.setVector4(UIRenderer._rectClipRectProperty, rectMaskRect); + } + + const rectMaskSoftness = this._rectMaskSoftness; + if ( + rectMaskSoftness.x !== clipSoftnessLeft || + rectMaskSoftness.y !== clipSoftnessBottom || + rectMaskSoftness.z !== clipSoftnessRight || + rectMaskSoftness.w !== clipSoftnessTop + ) { + rectMaskSoftness.set(clipSoftnessLeft, clipSoftnessBottom, clipSoftnessRight, clipSoftnessTop); + this.shaderData.setVector4(UIRenderer._rectClipSoftnessProperty, rectMaskSoftness); + } + + if (this._rectMaskHardClip !== clipHardClip) { + this._rectMaskHardClip = clipHardClip; + this.shaderData.setFloat(UIRenderer._rectClipHardClipProperty, clipHardClip ? 1 : 0); + } + + if (!this._rectMaskEnabled) { + this._rectMaskEnabled = true; + this.shaderData.setFloat(UIRenderer._rectClipEnabledProperty, 1); + } + } + + private _resetRectMaskClipState(): void { + if (this._rectMaskEnabled) { + this._rectMaskEnabled = false; + this.shaderData.setFloat(UIRenderer._rectClipEnabledProperty, 0); + } + const rectMaskSoftness = this._rectMaskSoftness; + if (rectMaskSoftness.x !== 0 || rectMaskSoftness.y !== 0 || rectMaskSoftness.z !== 0 || rectMaskSoftness.w !== 0) { + rectMaskSoftness.set(0, 0, 0, 0); + this.shaderData.setVector4(UIRenderer._rectClipSoftnessProperty, rectMaskSoftness); + } + if (this._rectMaskHardClip) { + this._rectMaskHardClip = false; + this.shaderData.setFloat(UIRenderer._rectClipHardClipProperty, 0); + } + } + protected override _onDestroy(): void { if (this._subChunk) { this._getChunkManager().freeSubChunk(this._subChunk); @@ -287,6 +495,8 @@ export class UIRenderer extends Renderer implements IGraphics { //@ts-ignore this._color._onValueChanged = null; this._color = null; + this._rectMasks = null; + this._rectMaskSoftness = null; } } diff --git a/packages/ui/src/component/advanced/Mask.ts b/packages/ui/src/component/advanced/Mask.ts new file mode 100644 index 0000000000..cff2fd85ab --- /dev/null +++ b/packages/ui/src/component/advanced/Mask.ts @@ -0,0 +1,80 @@ +import { BoundingBox, Entity, MaskRenderable, Vector2 } from "@galacean/engine"; +import type { IMaskRenderable } from "@galacean/engine"; +import { UIRenderer } from "../UIRenderer"; +import { UITransform } from "../UITransform"; + +/** + * UI component that uses a sprite to mask child UI renderers via stencil. + */ +export class Mask extends MaskRenderable(UIRenderer) { + /** + * @internal + */ + override _getChunkManager() { + // @ts-ignore + return this.engine._batcherManager.primitiveChunkManagerMask; + } + + /** + * @internal + */ + constructor(entity: Entity) { + super(entity); + this._initMask(); + this.raycastEnabled = false; + } + + /** + * @internal + */ + // @ts-ignore + _cloneTo(target: Mask): void { + // @ts-ignore + super._cloneTo(target); + this._cloneMaskData(target); + } + + protected override _updateBounds(worldBounds: BoundingBox): void { + const rootCanvas = this._getRootCanvas(); + if (this.sprite && rootCanvas) { + this._updateMaskBounds(worldBounds); + } else { + const { worldPosition } = this._transformEntity.transform; + worldBounds.min.copyFrom(worldPosition); + worldBounds.max.copyFrom(worldPosition); + } + } + + /** + * @inheritdoc + */ + protected override _render(context): void { + this._renderMask(0); + } + + /** + * @inheritdoc + */ + protected override _onDestroy(): void { + this._destroyMaskResources(); + + super._onDestroy(); + + if (this._subChunk) { + this._getChunkManager().freeSubChunk(this._subChunk); + this._subChunk = null; + } + } + + override _getSpriteWidth(): number { + return (this._transformEntity.transform).size.x; + } + + override _getSpriteHeight(): number { + return (this._transformEntity.transform).size.y; + } + + override _getSpritePivot(): Vector2 { + return (this._transformEntity.transform).pivot; + } +} diff --git a/packages/ui/src/component/advanced/RectMask2D.ts b/packages/ui/src/component/advanced/RectMask2D.ts new file mode 100644 index 0000000000..9cba135e84 --- /dev/null +++ b/packages/ui/src/component/advanced/RectMask2D.ts @@ -0,0 +1,157 @@ +import { + Component, + DependentMode, + Entity, + Vector2, + Vector3, + Vector4, + assignmentClone, + deepClone, + dependentComponents +} from "@galacean/engine"; +import { UICanvas } from "../UICanvas"; +import { UITransform } from "../UITransform"; + +/** + * UI component that clips descendant graphics by an axis-aligned rectangle. + */ +@dependentComponents(UITransform, DependentMode.AutoAdd) +export class RectMask2D extends Component { + private static _tempRect: Vector4 = new Vector4(); + private static _tempCorner0: Vector3 = new Vector3(); + private static _tempCorner1: Vector3 = new Vector3(); + private static _tempCorner2: Vector3 = new Vector3(); + private static _tempCorner3: Vector3 = new Vector3(); + + @deepClone + private _softness: Vector2 = new Vector2(0, 0); + @assignmentClone + private _alphaClip: boolean = false; + + /** + * Soft clipping width on X/Y axis in world space. + */ + get softness(): Vector2 { + return this._softness; + } + + set softness(value: Vector2) { + const softness = this._softness; + if (softness === value) { + return; + } + if (softness.x !== value.x || softness.y !== value.y) { + softness.copyFrom(value); + this._clampSoftness(); + } + } + + /** + * Whether to enable hard clip (discard) when outside the rect. + */ + get alphaClip(): boolean { + return this._alphaClip; + } + + set alphaClip(value: boolean) { + this._alphaClip = value; + } + + /** + * @internal + */ + _getWorldRect(out: Vector4): boolean { + const transform = this.entity.transform; + const { x: width, y: height } = transform.size; + if (!width || !height) { + return false; + } + + const { x: pivotX, y: pivotY } = transform.pivot; + const left = -width * pivotX; + const right = width * (1 - pivotX); + const bottom = -height * pivotY; + const top = height * (1 - pivotY); + + const worldMatrix = transform.worldMatrix; + const corner0 = RectMask2D._tempCorner0; + const corner1 = RectMask2D._tempCorner1; + const corner2 = RectMask2D._tempCorner2; + const corner3 = RectMask2D._tempCorner3; + Vector3.transformCoordinate(corner0.set(left, bottom, 0), worldMatrix, corner0); + Vector3.transformCoordinate(corner1.set(left, top, 0), worldMatrix, corner1); + Vector3.transformCoordinate(corner2.set(right, bottom, 0), worldMatrix, corner2); + Vector3.transformCoordinate(corner3.set(right, top, 0), worldMatrix, corner3); + + const minX = Math.min(corner0.x, corner1.x, corner2.x, corner3.x); + const minY = Math.min(corner0.y, corner1.y, corner2.y, corner3.y); + const maxX = Math.max(corner0.x, corner1.x, corner2.x, corner3.x); + const maxY = Math.max(corner0.y, corner1.y, corner2.y, corner3.y); + out.set(minX, minY, maxX, maxY); + return true; + } + + /** + * @internal + */ + _containsWorldPoint(worldPoint: Vector3): boolean { + const worldRect = RectMask2D._tempRect; + if (!this._getWorldRect(worldRect)) { + return false; + } + const { x, y } = worldPoint; + return x >= worldRect.x && x <= worldRect.z && y >= worldRect.y && y <= worldRect.w; + } + + constructor(entity: Entity) { + super(entity); + this._onSoftnessChanged = this._onSoftnessChanged.bind(this); + // @ts-ignore + this._softness._onValueChanged = this._onSoftnessChanged; + } + + // @ts-ignore + override _onEnableInScene(): void { + this.entity._updateUIHierarchyVersion(UICanvas._hierarchyCounter); + } + + // @ts-ignore + override _onDisableInScene(): void { + this.entity._updateUIHierarchyVersion(UICanvas._hierarchyCounter); + } + + // @ts-ignore + override _cloneTo(target: RectMask2D): void { + // RectMask2D extends Component directly; Component.prototype 上没有 _cloneTo, + // 不能 super._cloneTo(target) — 会拿到 undefined 报 "Cannot read properties of undefined (reading 'call')"。 + // (Image/Mask 走 Renderer 链路所以能 super;RectMask2D 不在 Renderer 链路里。) + const targetSoftness = target._softness; + // @ts-ignore + targetSoftness._onValueChanged = null; + targetSoftness.copyFrom(this._softness); + target._clampSoftness(); + // @ts-ignore + targetSoftness._onValueChanged = target._onSoftnessChanged; + } + + protected override _onDestroy(): void { + // @ts-ignore + this._softness._onValueChanged = null; + this._softness = null; + super._onDestroy(); + } + + private _onSoftnessChanged(): void { + this._clampSoftness(); + } + + private _clampSoftness(): void { + const softness = this._softness; + if (softness.x < 0) { + softness.x = 0; + } + if (softness.y < 0) { + softness.y = 0; + } + } +} diff --git a/packages/ui/src/component/advanced/Text.ts b/packages/ui/src/component/advanced/Text.ts index 6f20830716..9f6eb35953 100644 --- a/packages/ui/src/component/advanced/Text.ts +++ b/packages/ui/src/component/advanced/Text.ts @@ -204,17 +204,6 @@ export class Text extends UIRenderer implements ITextRenderer { } } - /** - * The mask layer the sprite renderer belongs to. - */ - get maskLayer(): number { - return this._maskLayer; - } - - set maskLayer(value: number) { - this._maskLayer = value; - } - /** * The bounding volume of the TextRenderer. */ diff --git a/packages/ui/src/component/index.ts b/packages/ui/src/component/index.ts index fc93806fb6..694bf24199 100644 --- a/packages/ui/src/component/index.ts +++ b/packages/ui/src/component/index.ts @@ -1,11 +1,13 @@ -export { UICanvas } from "./UICanvas"; -export { UIGroup } from "./UIGroup"; -export { UIRenderer } from "./UIRenderer"; -export { UITransform } from "./UITransform"; export { Button } from "./advanced/Button"; export { Image, SpriteSizeMode } from "./advanced/Image"; +export { Mask } from "./advanced/Mask"; +export { RectMask2D } from "./advanced/RectMask2D"; export { Text } from "./advanced/Text"; export { ColorTransition } from "./interactive/transition/ColorTransition"; export { ScaleTransition } from "./interactive/transition/ScaleTransition"; export { SpriteTransition } from "./interactive/transition/SpriteTransition"; export { Transition } from "./interactive/transition/Transition"; +export { UICanvas } from "./UICanvas"; +export { UIGroup } from "./UIGroup"; +export { UIRenderer } from "./UIRenderer"; +export { UITransform } from "./UITransform"; diff --git a/packages/ui/src/shader/uiDefault.fs.glsl b/packages/ui/src/shader/uiDefault.fs.glsl index e4028405de..aa4ca2e0e9 100644 --- a/packages/ui/src/shader/uiDefault.fs.glsl +++ b/packages/ui/src/shader/uiDefault.fs.glsl @@ -1,14 +1,43 @@ #include uniform sampler2D renderer_UITexture; +uniform vec4 renderer_UIRectClipRect; +uniform float renderer_UIRectClipEnabled; +uniform vec4 renderer_UIRectClipSoftness; +uniform float renderer_UIRectClipHardClip; varying vec2 v_uv; varying vec4 v_color; +varying vec2 v_worldPosition; + +float getUIRectClipAlpha() { + vec4 edgeDistance = vec4( + v_worldPosition.x - renderer_UIRectClipRect.x, + v_worldPosition.y - renderer_UIRectClipRect.y, + renderer_UIRectClipRect.z - v_worldPosition.x, + renderer_UIRectClipRect.w - v_worldPosition.y + ); + vec4 hardClipFactor = step(vec4(0.0), edgeDistance); + vec4 softness = max(renderer_UIRectClipSoftness, vec4(1e-5)); + vec4 softClipFactor = clamp(edgeDistance / softness, 0.0, 1.0); + vec4 useSoftness = step(vec4(1e-5), renderer_UIRectClipSoftness); + vec4 clipFactor = mix(hardClipFactor, softClipFactor, useSoftness); + return clipFactor.x * clipFactor.y * clipFactor.z * clipFactor.w; +} void main() { + float rectClipAlpha = 1.0; + if (renderer_UIRectClipEnabled > 0.5) { + rectClipAlpha = getUIRectClipAlpha(); + } + vec4 baseColor = texture2DSRGB(renderer_UITexture, v_uv); vec4 finalColor = baseColor * v_color; + finalColor.a *= rectClipAlpha; + if (renderer_UIRectClipEnabled > 0.5 && renderer_UIRectClipHardClip > 0.5 && finalColor.a < 0.001) { + discard; + } #ifdef ENGINE_SHOULD_SRGB_CORRECT finalColor = outputSRGBCorrection(finalColor); #endif gl_FragColor = finalColor; -} \ No newline at end of file +} diff --git a/packages/ui/src/shader/uiDefault.vs.glsl b/packages/ui/src/shader/uiDefault.vs.glsl index 2a6b45be4e..52345d9abf 100644 --- a/packages/ui/src/shader/uiDefault.vs.glsl +++ b/packages/ui/src/shader/uiDefault.vs.glsl @@ -1,4 +1,5 @@ uniform mat4 renderer_MVPMat; +uniform mat4 renderer_ModelMat; attribute vec3 POSITION; attribute vec2 TEXCOORD_0; @@ -6,10 +7,12 @@ attribute vec4 COLOR_0; varying vec2 v_uv; varying vec4 v_color; +varying vec2 v_worldPosition; void main() { gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); v_uv = TEXCOORD_0; v_color = COLOR_0; + v_worldPosition = POSITION.xy; } diff --git a/tests/src/core/particle/ParticleStopResume.test.ts b/tests/src/core/particle/ParticleStopResume.test.ts new file mode 100644 index 0000000000..0e0db6ac05 --- /dev/null +++ b/tests/src/core/particle/ParticleStopResume.test.ts @@ -0,0 +1,142 @@ +import { + Burst, + Camera, + ParticleCompositeCurve, + ParticleRenderer, + ParticleStopMode, + Scene +} from "@galacean/engine-core"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { beforeAll, describe, expect, it } from "vitest"; + +describe("ParticleGenerator stop/resume timeline", () => { + let engine: WebGLEngine; + let scene: Scene; + + beforeAll(async () => { + engine = await WebGLEngine.create({ canvas: document.createElement("canvas") }); + scene = engine.sceneManager.activeScene; + const root = scene.createRootEntity("root"); + const camera = root.createChild("Camera"); + camera.addComponent(Camera); + camera.transform.setPosition(0, 0, 10); + }); + + /** + * Drive `generator._update(dt)` directly so we control the timeline. + * `ParticleRenderer._update` would call this with `engine.time.deltaTime`, + * which is wall-clock and not reproducible. + */ + function tick(generator: any, frames: number, dt: number): void { + for (let i = 0; i < frames; i++) { + generator._update(dt); + } + } + + it("rate-over-time: stop -> idle -> play emits a catch-up batch in one frame", () => { + const entity = scene.createRootEntity("rate"); + const renderer = entity.addComponent(ParticleRenderer); + const generator = renderer.generator; + generator.useAutoRandomSeed = false; + generator.main.duration = 1; + generator.main.startLifetime.constant = 1; + generator.main.maxParticles = 10000; + generator.emission.rateOverTime.constant = 100; + + let totalEmitted = 0; + const origEmit = (generator as any)._emit.bind(generator); + (generator as any)._emit = (playTime: number, count: number) => { + totalEmitted += count; + origEmit(playTime, count); + }; + + generator.play(); + // Run 0.5s at 60fps -> ~50 particles + tick(generator, 30, 1 / 60); + const emittedDuringPlay = totalEmitted; + const playTimeAfterPlay = generator._playTime; + + generator.stop(true, ParticleStopMode.StopEmitting); + const playTimeAtStop = generator._playTime; + const emittedAtStop = totalEmitted; + + // Idle 4.5s while stopped -> _emit must not run, but _playTime drifts + tick(generator, 270, 1 / 60); + const playTimeAfterIdle = generator._playTime; + const emittedDuringIdle = totalEmitted - emittedAtStop; + + generator.play(); + // Single frame after resume + tick(generator, 1, 1 / 60); + const emittedFirstFrameAfterResume = totalEmitted - emittedAtStop; + + entity.destroy(); + + // eslint-disable-next-line no-console + console.log("[bug-repro/rate]", { + emittedDuringPlay, + playTimeAfterPlay, + playTimeAtStop, + playTimeAfterIdle, + emittedDuringIdle, + emittedFirstFrameAfterResume + }); + + expect(emittedDuringIdle).toBe(0); + // Buggy behavior: emits a large catch-up batch (~ idleSeconds * rate). + expect(emittedFirstFrameAfterResume).toBeGreaterThan(100); + expect(playTimeAfterIdle - playTimeAtStop).toBeGreaterThan(4); + }); + + it("burst: stop -> idle -> play replays multiple cycles of bursts", () => { + const entity = scene.createRootEntity("burst"); + const renderer = entity.addComponent(ParticleRenderer); + const generator = renderer.generator; + generator.useAutoRandomSeed = false; + generator.main.duration = 1; + generator.main.isLoop = true; + generator.main.startLifetime.constant = 1; + generator.main.maxParticles = 10000; + generator.emission.rateOverTime.constant = 0; + generator.emission.addBurst(new Burst(0, new ParticleCompositeCurve(10))); + + let totalEmitted = 0; + const origEmit = (generator as any)._emit.bind(generator); + (generator as any)._emit = (playTime: number, count: number) => { + totalEmitted += count; + origEmit(playTime, count); + }; + + generator.play(); + // 1 full cycle -> burst at t=0 fires once (10 particles at frame 0) + tick(generator, 60, 1 / 60); + const emittedAfterOneSecond = totalEmitted; + + generator.stop(true, ParticleStopMode.StopEmitting); + const playTimeAtStop = generator._playTime; + const emittedAtStop = totalEmitted; + + // Idle 4 cycles + tick(generator, 240, 1 / 60); + const playTimeAfterIdle = generator._playTime; + + generator.play(); + tick(generator, 1, 1 / 60); // first frame after resume + const emittedFirstFrameAfterResume = totalEmitted - emittedAtStop; + + entity.destroy(); + + // eslint-disable-next-line no-console + console.log("[bug-repro/burst]", { + emittedAfterOneSecond, + playTimeAtStop, + playTimeAfterIdle, + emittedFirstFrameAfterResume + }); + + // After fix this should be 0 (next burst at t=0 of the new cycle hasn't reached yet). + // Buggy behavior: replays the burst once because `_currentBurstIndex` is 0 and + // `_emitBySubBurst(lastPlayTime, playTime, ...)` sees burst.time === startTime. + expect(emittedFirstFrameAfterResume).toBe(10); + }); +}); diff --git a/tests/src/ui/Mask.test.ts b/tests/src/ui/Mask.test.ts new file mode 100644 index 0000000000..d002fb27d7 --- /dev/null +++ b/tests/src/ui/Mask.test.ts @@ -0,0 +1,76 @@ +import { Sprite, SpriteMaskInteraction, SpriteMaskLayer, Texture2D } from "@galacean/engine-core"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { CanvasRenderMode, Image, Mask, UICanvas, UITransform } from "@galacean/engine-ui"; +import { describe, expect, it } from "vitest"; + +describe("Mask", async () => { + const canvas = document.createElement("canvas"); + const engine = await WebGLEngine.create({ canvas }); + const webCanvas = engine.canvas; + webCanvas.width = 300; + webCanvas.height = 300; + const scene = engine.sceneManager.scenes[0]; + const root = scene.createRootEntity("root"); + + const canvasEntity = root.createChild("canvas"); + const rootCanvas = canvasEntity.addComponent(UICanvas); + rootCanvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; + rootCanvas.referenceResolutionPerUnit = 50; + rootCanvas.referenceResolution.set(300, 300); + + const imageEntity = canvasEntity.createChild("image"); + const image = imageEntity.addComponent(Image); + (imageEntity.transform).size.set(300, 300); + + const maskEntity = canvasEntity.createChild("mask"); + const mask = maskEntity.addComponent(Mask); + (maskEntity.transform).size.set(100, 100); + mask.sprite = createSolidSprite(engine); + + it("Set and Get sprite", () => { + const texture = new Texture2D(engine, 1, 1); + const sprite = new Sprite(engine, texture); + mask.sprite = sprite; + expect(mask.sprite).to.eq(sprite); + + mask.sprite = null; + expect(mask.sprite).to.eq(null); + + mask.sprite = createSolidSprite(engine); + expect(mask.sprite).not.to.eq(null); + }); + + it("Set and Get alphaCutoff", () => { + expect(mask.alphaCutoff).to.eq(0.5); + mask.alphaCutoff = 0.2; + expect(mask.alphaCutoff).to.eq(0.2); + }); + + it("Set and Get influenceLayers", () => { + expect(mask.influenceLayers).to.eq(SpriteMaskLayer.Everything); + mask.influenceLayers = SpriteMaskLayer.Layer1; + expect(mask.influenceLayers).to.eq(SpriteMaskLayer.Layer1); + mask.influenceLayers = SpriteMaskLayer.Everything; + }); + + it("Set and Get flipX/flipY", () => { + mask.flipX = true; + expect(mask.flipX).to.eq(true); + mask.flipY = true; + expect(mask.flipY).to.eq(true); + mask.flipX = false; + mask.flipY = false; + }); + + it("UI image maskInteraction default is None", () => { + expect(image.maskInteraction).to.eq(SpriteMaskInteraction.None); + image.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + expect(image.maskInteraction).to.eq(SpriteMaskInteraction.VisibleInsideMask); + }); +}); + +function createSolidSprite(engine: WebGLEngine): Sprite { + const texture = new Texture2D(engine, 1, 1); + texture.setPixelBuffer(new Uint8Array([255, 255, 255, 255])); + return new Sprite(engine, texture); +} diff --git a/tests/src/ui/RectMask2D.test.ts b/tests/src/ui/RectMask2D.test.ts new file mode 100644 index 0000000000..47286e97eb --- /dev/null +++ b/tests/src/ui/RectMask2D.test.ts @@ -0,0 +1,79 @@ +import { Vector2, Vector3, Vector4 } from "@galacean/engine-math"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { CanvasRenderMode, RectMask2D, UICanvas, UITransform } from "@galacean/engine-ui"; +import { describe, expect, it } from "vitest"; + +describe("RectMask2D", async () => { + const canvas = document.createElement("canvas"); + const engine = await WebGLEngine.create({ canvas }); + const webCanvas = engine.canvas; + webCanvas.width = 300; + webCanvas.height = 300; + const scene = engine.sceneManager.scenes[0]; + const root = scene.createRootEntity("root"); + + const canvasEntity = root.createChild("canvas"); + const rootCanvas = canvasEntity.addComponent(UICanvas); + rootCanvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; + rootCanvas.referenceResolutionPerUnit = 1; + rootCanvas.referenceResolution.set(300, 300); + + it("should return false when mask size is zero", () => { + const maskEntity = canvasEntity.createChild("mask-zero"); + const rectMask = maskEntity.addComponent(RectMask2D); + const transform = maskEntity.transform as UITransform; + transform.size.set(0, 100); + const worldRect = new Vector4(); + + expect(rectMask._getWorldRect(worldRect)).to.eq(false); + }); + + it("should clamp negative softness values", () => { + const rectMask = canvasEntity.createChild("mask-softness").addComponent(RectMask2D); + + rectMask.softness.set(-4, 6); + expect(rectMask.softness.x).to.eq(0); + expect(rectMask.softness.y).to.eq(6); + + rectMask.softness = new Vector2(5, -3); + expect(rectMask.softness.x).to.eq(5); + expect(rectMask.softness.y).to.eq(0); + }); + + it("should toggle alphaClip", () => { + const rectMask = canvasEntity.createChild("mask-alphaclip").addComponent(RectMask2D); + expect(rectMask.alphaClip).to.eq(false); + rectMask.alphaClip = true; + expect(rectMask.alphaClip).to.eq(true); + }); + + it("should compute world rect when size and pivot set", () => { + const maskEntity = canvasEntity.createChild("mask-rect"); + const transform = maskEntity.transform as UITransform; + transform.pivot.set(0.5, 0.5); + transform.size.set(100, 80); + transform.setPosition(0, 0, 0); + const rectMask = maskEntity.addComponent(RectMask2D); + const worldRect = new Vector4(); + expect(rectMask._getWorldRect(worldRect)).to.eq(true); + // The canvas applies adaptation; just verify width/height match + expect(worldRect.z - worldRect.x).to.be.closeTo(100, 1); + expect(worldRect.w - worldRect.y).to.be.closeTo(80, 1); + }); + + it("should test contains world point", () => { + const maskEntity = canvasEntity.createChild("mask-contains"); + const transform = maskEntity.transform as UITransform; + transform.pivot.set(0.5, 0.5); + transform.size.set(100, 100); + transform.setPosition(0, 0, 0); + const rectMask = maskEntity.addComponent(RectMask2D); + + const rect = new Vector4(); + rectMask._getWorldRect(rect); + const cx = (rect.x + rect.z) * 0.5; + const cy = (rect.y + rect.w) * 0.5; + expect(rectMask._containsWorldPoint(new Vector3(cx, cy, 0))).to.eq(true); + expect(rectMask._containsWorldPoint(new Vector3(rect.x - 5, rect.y - 5, 0))).to.eq(false); + }); +}); diff --git a/tests/src/ui/UIInteractive.test.ts b/tests/src/ui/UIInteractive.test.ts index 8ee57381a8..96ab8be99f 100644 --- a/tests/src/ui/UIInteractive.test.ts +++ b/tests/src/ui/UIInteractive.test.ts @@ -1,7 +1,16 @@ import { Camera, PointerEventData, Script, SpriteDrawMode } from "@galacean/engine-core"; import { Color, Vector3 } from "@galacean/engine-math"; import { WebGLEngine } from "@galacean/engine-rhi-webgl"; -import { Button, ColorTransition, Image, ScaleTransition, Text, UICanvas, UIGroup, UITransform } from "@galacean/engine-ui"; +import { + Button, + ColorTransition, + Image, + ScaleTransition, + Text, + UICanvas, + UIGroup, + UITransform +} from "@galacean/engine-ui"; import { describe, expect, it, vi } from "vitest"; class ClickHandler extends Script { @@ -12,7 +21,7 @@ class ClickHandler extends Script { this.callCount++; } - handleClickWithPrefix(prefix: string) { + handleClickWithPrefix(_event: PointerEventData, prefix: string) { this.callCount++; this.lastPrefix = prefix; } @@ -40,7 +49,7 @@ describe("Button", async () => { const canvasEntity = root.createChild("canvas"); canvasEntity.addComponent(UIGroup); - const commonTextEntity = canvasEntity.createChild("commonText") + const commonTextEntity = canvasEntity.createChild("commonText"); const commonText = commonTextEntity.addComponent(Text); // Create button @@ -55,7 +64,6 @@ describe("Button", async () => { text.color.set(0, 0, 0, 1); const button = buttonEntity.addComponent(Button); - it("Set and Get", () => { // Click let clickCount = 0; @@ -135,7 +143,7 @@ describe("Button", async () => { const cloneButton = cloneButtonEntity.getComponent(Button); const cloneTransitions = cloneButton.transitions; const cloneTransition = cloneTransitions[0]; - expect(cloneTransition.target).to.eq(cloneButtonEntity.getComponent(Image)) + expect(cloneTransition.target).to.eq(cloneButtonEntity.getComponent(Image)); const cloneTransitionOne = cloneTransitions[1]; expect(cloneTransitionOne.target).to.eq(cloneButtonEntity.findByName("Text").getComponent(Text)); From b87180b7234f926be9a6da36383ad1a1950912cb Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 28 Apr 2026 22:23:10 +0800 Subject: [PATCH 068/130] chore: release v0.0.0-experimental-2.0-game.7 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 10d8108fed..5bbc3e8f08 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index 2afee7da55..b9f0b085c6 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index dc0b6d4c78..741cfbed26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 545099402b..d40152f859 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 373541de5a..5a840bb739 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index de2435f362..6e893fbd43 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index 4c22a84ca4..ddf336d23c 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index d197caf7e2..8eb3e1afb6 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 4296c0d9d4..3ee40f13bf 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index c7710f0e7e..9d9ab132e5 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 5e49b49847..a2755dcf27 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 256e0eed35..9da5b32cee 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index c9f1a8bbe1..3dc5cb80d2 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index e3b8aa207b..3d3f96a3c2 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 920f0f8044..fc63315185 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index cb7ddc5400..d472127130 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index 72ef5992b1..a0f4ec2503 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.6", + "version": "0.0.0-experimental-2.0-game.7", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From 79b289efa7bc5cb6689fe8f09a5780ea5cd776dd Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Wed, 29 Apr 2026 20:08:41 +0800 Subject: [PATCH 069/130] fix: text dirty flag bug --- packages/core/src/2d/text/TextRenderer.ts | 1 + packages/ui/src/component/advanced/Text.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 3649180c79..557f02171a 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -673,6 +673,7 @@ export class TextRenderer extends Renderer implements ITextRenderer { if (charLength > 0) { this._buildChunk(curTextChunk, charLength); } + this._setDirtyFlagTrue(DirtyFlag.WorldPosition); charRenderInfos.length = 0; } diff --git a/packages/ui/src/component/advanced/Text.ts b/packages/ui/src/component/advanced/Text.ts index 9f6eb35953..0fdc2e0eff 100644 --- a/packages/ui/src/component/advanced/Text.ts +++ b/packages/ui/src/component/advanced/Text.ts @@ -590,6 +590,7 @@ export class Text extends UIRenderer implements ITextRenderer { if (charLength > 0) { this._buildChunk(curTextChunk, charLength); } + this._setDirtyFlagTrue(DirtyFlag.WorldPosition); charRenderInfos.length = 0; } From d33332c0e90a70debd37d58cd00fc0f7adde2251 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Wed, 29 Apr 2026 20:12:53 +0800 Subject: [PATCH 070/130] chore: release v0.0.0-experimental-2.0-game.8 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 5bbc3e8f08..55a90325aa 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index b9f0b085c6..75864ae90b 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 741cfbed26..41afbc01ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index d40152f859..78a6bffc11 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 5a840bb739..10889b218d 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index 6e893fbd43..b377bc1fa7 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index ddf336d23c..247cb3f0c3 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 8eb3e1afb6..e48e0f9a01 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 3ee40f13bf..e04c37a07e 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 9d9ab132e5..0b039d25cf 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index a2755dcf27..5cc537a8a3 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 9da5b32cee..8ff0bd7258 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index 3dc5cb80d2..796d3f0b6a 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index 3d3f96a3c2..d83df651a0 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index fc63315185..15e2345727 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index d472127130..bf3446ab77 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index a0f4ec2503..08254d8298 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.7", + "version": "0.0.0-experimental-2.0-game.8", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From aa2442dc3c22d529d9d113eeae4d213a9a735b7d Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 30 Apr 2026 22:07:25 +0800 Subject: [PATCH 071/130] feat: support text outline --- examples/src/ui-text-outline.ts | 121 ++++++++++++++++++ packages/core/src/2d/atlas/FontAtlas.ts | 8 +- packages/core/src/2d/text/TextRenderer.ts | 82 ++++++++++-- packages/core/src/ComponentsManager.ts | 2 - .../src/RenderPipeline/VertexMergeBatcher.ts | 24 ++++ packages/core/src/asset/ResourceManager.ts | 28 ++-- .../core/src/shaderlib/extra/text.fs.glsl | 44 ++++++- .../loader/src/gltf/parser/GLTFSkinParser.ts | 3 +- packages/rhi-webgl/src/GLPrimitive.ts | 2 +- packages/ui/src/component/UIRenderer.ts | 5 +- packages/ui/src/component/advanced/Text.ts | 79 +++++++++++- 11 files changed, 350 insertions(+), 48 deletions(-) create mode 100644 examples/src/ui-text-outline.ts diff --git a/examples/src/ui-text-outline.ts b/examples/src/ui-text-outline.ts new file mode 100644 index 0000000000..fae8865a9f --- /dev/null +++ b/examples/src/ui-text-outline.ts @@ -0,0 +1,121 @@ +/** + * @title UI Text Outline + * @category UI + */ +import * as dat from "dat.gui"; +import { Camera, Color, WebGLEngine } from "@galacean/engine"; +import { CanvasRenderMode, Text, UICanvas, UITransform } from "@galacean/engine-ui"; + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + scene.background.solidColor = new Color(0.05, 0.07, 0.1, 1); + const root = scene.createRootEntity("Root"); + + const cameraEntity = root.createChild("Camera"); + cameraEntity.transform.setPosition(0, 0, 10); + const camera = cameraEntity.addComponent(Camera); + + const canvasEntity = root.createChild("UICanvas"); + const uiCanvas = canvasEntity.addComponent(UICanvas); + uiCanvas.renderMode = CanvasRenderMode.ScreenSpaceCamera; + uiCanvas.camera = camera; + uiCanvas.referenceResolutionPerUnit = 100; + uiCanvas.referenceResolution.set(1280, 800); + + // ---------- Matrix grid ---------- + // Rows = outlineWidth (0 / 1 / 2 / 4 / 8 px) + // Cols = (text color, outline color) presets + const widthCases = [0, 1, 2, 4, 8]; + const colorCases: { name: string; fill: Color; outline: Color }[] = [ + { name: "white / black", fill: new Color(1, 1, 1, 1), outline: new Color(0, 0, 0, 1) }, + { name: "black / white", fill: new Color(0, 0, 0, 1), outline: new Color(1, 1, 1, 1) }, + { name: "yellow / red", fill: new Color(1, 0.85, 0.1, 1), outline: new Color(0.85, 0.1, 0.1, 1) } + ]; + + const cellW = 380; + const cellH = 110; + const startX = -cellW * (colorCases.length - 1) * 0.5; + const startY = 220; + + // Column headers + for (let c = 0; c < colorCases.length; c++) { + const headerEntity = canvasEntity.createChild(`header-${c}`); + const ht = headerEntity.transform; + ht.size.set(cellW, 32); + ht.setPosition(startX + c * cellW, startY + cellH * 0.5 + 20, 0); + const header = headerEntity.addComponent(Text); + header.text = `text/outline = ${colorCases[c].name}`; + header.fontSize = 18; + header.color.set(0.7, 0.78, 0.9, 1); + } + + for (let r = 0; r < widthCases.length; r++) { + const w = widthCases[r]; + + // Row label + const labelEntity = canvasEntity.createChild(`row-label-${r}`); + const lt = labelEntity.transform; + lt.size.set(120, cellH); + lt.setPosition(startX - cellW * 0.5 - 60, startY - r * cellH, 0); + const label = labelEntity.addComponent(Text); + label.text = `width = ${w}px`; + label.fontSize = 18; + label.color.set(0.7, 0.78, 0.9, 1); + + for (let c = 0; c < colorCases.length; c++) { + const { fill, outline } = colorCases[c]; + + const cell = canvasEntity.createChild(`cell-${r}-${c}`); + const ct = cell.transform; + ct.size.set(cellW, cellH); + ct.setPosition(startX + c * cellW, startY - r * cellH, 0); + const text = cell.addComponent(Text); + text.text = "Hello 描边 Outline"; + text.fontSize = 36; + text.color = fill; + text.outlineWidth = w; + text.outlineColor = outline; + } + } + + // ---------- Live preview controlled by dat.gui ---------- + const previewEntity = canvasEntity.createChild("preview"); + const previewTransform = previewEntity.transform; + previewTransform.size.set(900, 160); + previewTransform.setPosition(0, -300, 0); + const preview = previewEntity.addComponent(Text); + preview.text = "实时 Live 预览 Preview"; + preview.fontSize = 72; + preview.color = new Color(1, 1, 1, 1); + preview.outlineWidth = 3; + preview.outlineColor = new Color(0, 0, 0, 1); + + const state = { + fontSize: preview.fontSize, + outlineWidth: preview.outlineWidth, + fillColor: [255, 255, 255], + outlineColor: [0, 0, 0], + text: preview.text + }; + + const gui = new dat.GUI(); + gui.add(state, "text").onChange((v: string) => { + preview.text = v; + }); + gui.add(state, "fontSize", 12, 120, 1).onChange((v: number) => { + preview.fontSize = v; + }); + gui.add(state, "outlineWidth", 0, 8, 0.1).onChange((v: number) => { + preview.outlineWidth = v; + }); + gui.addColor(state, "fillColor").onChange((rgb: number[]) => { + preview.color.set(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255, 1); + }); + gui.addColor(state, "outlineColor").onChange((rgb: number[]) => { + preview.outlineColor.set(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255, 1); + }); + + engine.run(); +}); diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts index 36170f2f49..102c9620cc 100644 --- a/packages/core/src/2d/atlas/FontAtlas.ts +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -12,10 +12,10 @@ export class FontAtlas extends ReferResource { texture: Texture2D; _charInfoMap: Record = {}; - private _space: number = 1; - private _curX: number = 1; - private _curY: number = 1; - private _nextY: number = 1; + private _space: number = 4; + private _curX: number = 4; + private _curY: number = 4; + private _nextY: number = 4; constructor(engine: Engine) { super(engine); diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 557f02171a..81fb1c582b 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -1,14 +1,15 @@ -import { BoundingBox, Color, Vector3 } from "@galacean/engine-math"; +import { BoundingBox, Color, Vector2, Vector3 } from "@galacean/engine-math"; import { Engine } from "../../Engine"; import { Entity } from "../../Entity"; -import { VertexMergeBatcher } from "../../RenderPipeline/VertexMergeBatcher"; import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; -import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; import { RenderElement } from "../../RenderPipeline/RenderElement"; +import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; +import { VertexMergeBatcher } from "../../RenderPipeline/VertexMergeBatcher"; import { Renderer } from "../../Renderer"; import { TransformModifyFlags } from "../../Transform"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; +import { SpriteMaskLayer } from "../../enums/SpriteMaskLayer"; import { ShaderData, ShaderProperty } from "../../shader"; import { ShaderDataGroup } from "../../shader/enums/ShaderDataGroup"; import { Texture2D } from "../../texture"; @@ -21,15 +22,18 @@ import { Font } from "./Font"; import { ITextRenderer } from "./ITextRenderer"; import { SubFont } from "./SubFont"; import { TextUtils } from "./TextUtils"; -import { SpriteMaskLayer } from "../../enums/SpriteMaskLayer"; /** * Renders a text for 2D graphics. */ export class TextRenderer extends Renderer implements ITextRenderer { - private static _textureProperty = ShaderProperty.getByName("renderElement_TextTexture"); + private static _tempVec20 = new Vector2(); private static _tempVec30 = new Vector3(); private static _tempVec31 = new Vector3(); + private static _textureProperty = ShaderProperty.getByName("renderElement_TextTexture"); + private static _textTextureSizeProperty = ShaderProperty.getByName("renderElement_TextTextureSize"); + private static _outlineColorProperty = ShaderProperty.getByName("renderer_OutlineColor"); + private static _outlineWidthProperty = ShaderProperty.getByName("renderer_OutlineWidth"); private static _worldPositions = [new Vector3(), new Vector3(), new Vector3(), new Vector3()]; private static _charRenderInfos: CharRenderInfo[] = []; @@ -69,6 +73,10 @@ export class TextRenderer extends Renderer implements ITextRenderer { private _enableWrapping = false; @assignmentClone private _overflowMode = OverflowMode.Overflow; + @deepClone + private _outlineColor = new Color(0, 0, 0, 1); + @ignoreClone + private _outlineWidth = 0; /** * Rendering color for the Text. @@ -255,6 +263,35 @@ export class TextRenderer extends Renderer implements ITextRenderer { } } + /** + * The outline width in pixels. 0 means outline is disabled. Clamped to [0, 8]. + */ + get outlineWidth(): number { + return this._outlineWidth; + } + + set outlineWidth(value: number) { + value = Math.max(0, Math.min(value, 3)); + if (this._outlineWidth !== value) { + this._outlineWidth = value; + this.shaderData.setFloat(TextRenderer._outlineWidthProperty, value); + this._setDirtyFlagTrue(DirtyFlag.Position); + } + } + + /** + * The outline color. Only effective when outlineWidth > 0. + */ + get outlineColor(): Color { + return this._outlineColor; + } + + set outlineColor(value: Color) { + if (this._outlineColor !== value) { + this._outlineColor.copyFrom(value); + } + } + /** * Interacts with the masks. */ @@ -311,6 +348,8 @@ export class TextRenderer extends Renderer implements ITextRenderer { this.setMaterial(engine._basicResources.textDefaultMaterial); //@ts-ignore this._color._onValueChanged = this._onColorChanged.bind(this); + // @ts-ignore + this._outlineColor._onValueChanged = this._onOutlineColorChanged.bind(this); } /** @@ -337,6 +376,7 @@ export class TextRenderer extends Renderer implements ITextRenderer { super._cloneTo(target); target.font = this._font; target._subFont = this._subFont; + target.outlineWidth = this._outlineWidth; } /** @@ -382,7 +422,7 @@ export class TextRenderer extends Renderer implements ITextRenderer { * @internal */ override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { - return VertexMergeBatcher.canBatchSprite(preElement, curElement); + return VertexMergeBatcher.canBatchText(preElement, curElement); } /** @@ -436,12 +476,17 @@ export class TextRenderer extends Renderer implements ITextRenderer { const distanceForSort = this._distanceForSort; const renderPipeline = camera._renderPipeline; const textChunks = this._textChunks; + const textTextureSize = TextRenderer._tempVec20; for (let i = 0, n = textChunks.length; i < n; ++i) { const { subChunk, texture } = textChunks[i]; const renderElement = textRenderElementPool.get(); renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk); renderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); renderElement.shaderData.setTexture(TextRenderer._textureProperty, texture); + renderElement.shaderData.setVector2( + TextRenderer._textTextureSizeProperty, + textTextureSize.set(texture.width, texture.height) + ); renderElement.priority = priority; renderElement.distanceForSort = distanceForSort; renderPipeline.pushRenderElement(context, renderElement); @@ -606,10 +651,11 @@ export class TextRenderer extends Renderer implements ITextRenderer { charRenderInfo.texture = charFont._getTextureByIndex(charInfo.index); charRenderInfo.uvs = charInfo.uvs; const { w, ascent, descent } = charInfo; - const left = startX * pixelsPerUnitReciprocal; - const right = (startX + w) * pixelsPerUnitReciprocal; - const top = (startY + ascent) * pixelsPerUnitReciprocal; - const bottom = (startY - descent) * pixelsPerUnitReciprocal; + const ow = this._outlineWidth * pixelsPerUnitReciprocal; + const left = startX * pixelsPerUnitReciprocal - ow; + const right = (startX + w) * pixelsPerUnitReciprocal + ow; + const top = (startY + ascent) * pixelsPerUnitReciprocal + ow; + const bottom = (startY - descent) * pixelsPerUnitReciprocal - ow; localPositions.set(left, top, right, bottom); i === firstLine && (maxY = Math.max(maxY, top)); minY = Math.min(minY, bottom); @@ -701,6 +747,10 @@ export class TextRenderer extends Renderer implements ITextRenderer { const vertices = subChunk.chunk.vertices; const indices = (subChunk.indices = []); const charRenderInfos = textChunk.charRenderInfos; + const ow = this._outlineWidth; + const texture = textChunk.texture; + const owU = ow > 0 ? ow / texture.width : 0; + const owV = ow > 0 ? ow / texture.height : 0; for (let i = 0, ii = 0, io = 0, vo = subChunk.vertexArea.start + 3; i < count; ++i, io += 4) { const charRenderInfo = charRenderInfos[i]; charRenderInfo.indexInChunk = i; @@ -710,10 +760,13 @@ export class TextRenderer extends Renderer implements ITextRenderer { indices[ii++] = tempIndices[j] + io; } - // Set uv and color for vertices + // Set uv and color for vertices, expand uv outward by outline width for (let j = 0; j < 4; ++j, vo += 9) { const uv = charRenderInfo.uvs[j]; - uv.copyToArray(vertices, vo); + const su = j === 1 || j === 2 ? 1 : -1; + const sv = j >= 2 ? 1 : -1; + vertices[vo] = uv.x + owU * su; + vertices[vo + 1] = uv.y + owV * sv; vertices[vo + 2] = r; vertices[vo + 3] = g; vertices[vo + 4] = b; @@ -746,6 +799,11 @@ export class TextRenderer extends Renderer implements ITextRenderer { private _onColorChanged(): void { this._setDirtyFlagTrue(DirtyFlag.Color); } + + @ignoreClone + private _onOutlineColorChanged(): void { + this.shaderData.setColor(TextRenderer._outlineColorProperty, this._outlineColor); + } } class TextChunk { diff --git a/packages/core/src/ComponentsManager.ts b/packages/core/src/ComponentsManager.ts index 96924c1a60..7df837a336 100644 --- a/packages/core/src/ComponentsManager.ts +++ b/packages/core/src/ComponentsManager.ts @@ -36,7 +36,6 @@ export class ComponentsManager { // Render private _onUpdateRenderers = new DisorderedArray(); - addCamera(camera: Camera) { camera._cameraIndex = this._activeCameras.length; this._activeCameras.add(camera); @@ -268,7 +267,6 @@ export class ComponentsManager { ); } - /** * @internal */ diff --git a/packages/core/src/RenderPipeline/VertexMergeBatcher.ts b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts index a627103b29..67565f8071 100644 --- a/packages/core/src/RenderPipeline/VertexMergeBatcher.ts +++ b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts @@ -54,6 +54,30 @@ export class VertexMergeBatcher { ); } + /** + * Text-specific batch check: extends sprite check with outline parity. + * Different outlineWidth or outlineColor must split into separate draw calls, + * because outline uniforms are shared per draw call. + */ + static canBatchText(preElement: RenderElement, curElement: RenderElement): boolean { + if (!VertexMergeBatcher.canBatchSprite(preElement, curElement)) { + return false; + } + const preRendererAny = preElement.component as any; + const curRendererAny = curElement.component as any; + if (preRendererAny._outlineWidth !== curRendererAny._outlineWidth) { + return false; + } + if (preRendererAny._outlineWidth > 0) { + const a = preRendererAny._outlineColor; + const b = curRendererAny._outlineColor; + if (a.r !== b.r || a.g !== b.g || a.b !== b.b || a.a !== b.a) { + return false; + } + } + return true; + } + static canBatchSpriteMask(preElement: RenderElement, curElement: RenderElement): boolean { if (preElement.subChunk.chunk !== curElement.subChunk.chunk) { return false; diff --git a/packages/core/src/asset/ResourceManager.ts b/packages/core/src/asset/ResourceManager.ts index b83a9360c4..94cb7ee946 100644 --- a/packages/core/src/asset/ResourceManager.ts +++ b/packages/core/src/asset/ResourceManager.ts @@ -58,7 +58,7 @@ export class ResourceManager { * Create a ResourceManager. * @param engine - Engine to which the current ResourceManager belongs */ - constructor(public readonly engine: Engine) { } + constructor(public readonly engine: Engine) {} /** * Load the asset asynchronously by asset item information. @@ -626,7 +626,7 @@ export class ResourceManager { * @param extNames - Name of file extension */ export function resourceLoader(assetType: string, extNames: string[], useCache: boolean = true) { - return >(Target: { new(useCache: boolean): T }) => { + return >(Target: { new (useCache: boolean): T }) => { const loader = new Target(useCache); ResourceManager._addLoader(assetType, loader, extNames); }; @@ -637,18 +637,18 @@ const reEscapeChar = /\\(\\)?/g; const rePropName = RegExp( // Match anything that isn't a dot or bracket. "[^.[\\]]+" + - "|" + - // Or match property names within brackets. - "\\[(?:" + - // Match a non-string expression. - "([^\"'][^[]*)" + - "|" + - // Or match strings (supports escaping characters). - "([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2" + - ")\\]" + - "|" + - // Or match "" as the space between consecutive dots or empty brackets. - "(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))", + "|" + + // Or match property names within brackets. + "\\[(?:" + + // Match a non-string expression. + "([^\"'][^[]*)" + + "|" + + // Or match strings (supports escaping characters). + "([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2" + + ")\\]" + + "|" + + // Or match "" as the space between consecutive dots or empty brackets. + "(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))", "g" ); diff --git a/packages/core/src/shaderlib/extra/text.fs.glsl b/packages/core/src/shaderlib/extra/text.fs.glsl index 019d419ba4..84693a6773 100644 --- a/packages/core/src/shaderlib/extra/text.fs.glsl +++ b/packages/core/src/shaderlib/extra/text.fs.glsl @@ -1,4 +1,8 @@ uniform sampler2D renderElement_TextTexture; +uniform vec2 renderElement_TextTextureSize; +uniform vec4 renderer_OutlineColor; +uniform float renderer_OutlineWidth; + uniform vec4 renderer_UIRectClipRect; uniform float renderer_UIRectClipEnabled; uniform vec4 renderer_UIRectClipSoftness; @@ -24,6 +28,16 @@ float getUIRectClipAlpha() return clipFactor.x * clipFactor.y * clipFactor.z * clipFactor.w; } +float sampleCoverage(vec2 uv) +{ + vec4 c = texture2D(renderElement_TextTexture, uv); + #ifdef GRAPHICS_API_WEBGL2 + return c.r; + #else + return c.a; + #endif +} + void main() { float rectClipAlpha = 1.0; @@ -31,13 +45,29 @@ void main() rectClipAlpha = getUIRectClipAlpha(); } - vec4 texColor = texture2D(renderElement_TextTexture, v_uv); - #ifdef GRAPHICS_API_WEBGL2 - float coverage = texColor.r; - #else - float coverage = texColor.a; - #endif - vec4 finalColor = vec4(v_color.rgb, v_color.a * coverage); + float coverage = sampleCoverage(v_uv); + vec4 finalColor; + + if (renderer_OutlineWidth > 0.0) { + vec2 texelSize = 1.0 / renderElement_TextTextureSize; + vec2 step = texelSize * renderer_OutlineWidth; + float outlineCoverage = coverage; + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2( step.x, 0.0))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2(-step.x, 0.0))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2( 0.0, step.y))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2( 0.0, -step.y))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2( step.x * 0.7071, step.y * 0.7071))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2(-step.x * 0.7071, step.y * 0.7071))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2( step.x * 0.7071, -step.y * 0.7071))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2(-step.x * 0.7071, -step.y * 0.7071))); + + vec3 rgb = mix(renderer_OutlineColor.rgb, v_color.rgb, coverage); + float alpha = max(coverage * v_color.a, outlineCoverage * renderer_OutlineColor.a); + finalColor = vec4(rgb, alpha); + } else { + finalColor = vec4(v_color.rgb, v_color.a * coverage); + } + finalColor.a *= rectClipAlpha; if (renderer_UIRectClipEnabled > 0.5 && renderer_UIRectClipHardClip > 0.5 && finalColor.a < 0.001) { discard; diff --git a/packages/loader/src/gltf/parser/GLTFSkinParser.ts b/packages/loader/src/gltf/parser/GLTFSkinParser.ts index 978dd4780e..b97825fa6a 100644 --- a/packages/loader/src/gltf/parser/GLTFSkinParser.ts +++ b/packages/loader/src/gltf/parser/GLTFSkinParser.ts @@ -39,7 +39,8 @@ export class GLTFSkinParser extends GLTFParser { const rootBone = entities[skeleton]; skin.rootBone = rootBone; } else { - const rootBone = this._findSceneRootBone(context, joints, entities) ?? this._findSkeletonRootBone(joints, entities); + const rootBone = + this._findSceneRootBone(context, joints, entities) ?? this._findSkeletonRootBone(joints, entities); if (rootBone) { skin.rootBone = rootBone; } else { diff --git a/packages/rhi-webgl/src/GLPrimitive.ts b/packages/rhi-webgl/src/GLPrimitive.ts index b679bc4699..5a7890d735 100644 --- a/packages/rhi-webgl/src/GLPrimitive.ts +++ b/packages/rhi-webgl/src/GLPrimitive.ts @@ -118,7 +118,7 @@ export class GLPrimitive implements IPlatformPrimitive { const element = attributes[name]; if (element) { - if(!vertexBufferBindings[element.bindingIndex]) continue; + if (!vertexBufferBindings[element.bindingIndex]) continue; const { buffer, stride } = vertexBufferBindings[element.bindingIndex]; vbo = buffer._platformBuffer._glBuffer; // prevent binding the vbo which already bound at the last loop, e.g. a buffer with multiple attributes. diff --git a/packages/ui/src/component/UIRenderer.ts b/packages/ui/src/component/UIRenderer.ts index f2ceaf6059..527ad3bd23 100644 --- a/packages/ui/src/component/UIRenderer.ts +++ b/packages/ui/src/component/UIRenderer.ts @@ -18,7 +18,8 @@ import { assignmentClone, deepClone, dependentComponents, - ignoreClone + ignoreClone, + Vector2 } from "@galacean/engine"; import { Utils } from "../Utils"; import { UIHitResult } from "../input/UIHitResult"; @@ -30,6 +31,8 @@ import { UITransform } from "./UITransform"; @dependentComponents(UITransform, DependentMode.AutoAdd) export class UIRenderer extends Renderer implements IGraphics { + /** @internal */ + static _tempVec20: Vector2 = new Vector2(); /** @internal */ static _tempVec30: Vector3 = new Vector3(); /** @internal */ diff --git a/packages/ui/src/component/advanced/Text.ts b/packages/ui/src/component/advanced/Text.ts index 0fdc2e0eff..fbfbd03fc2 100644 --- a/packages/ui/src/component/advanced/Text.ts +++ b/packages/ui/src/component/advanced/Text.ts @@ -1,6 +1,7 @@ import { BoundingBox, CharRenderInfo, + Color, Engine, Entity, Font, @@ -17,7 +18,9 @@ import { TextVerticalAlignment, Texture2D, Vector3, + VertexMergeBatcher, assignmentClone, + deepClone, ignoreClone } from "@galacean/engine"; import { CanvasRenderMode } from "../../enums/CanvasRenderMode"; @@ -30,6 +33,9 @@ import { UITransform, UITransformModifyFlags } from "../UITransform"; */ export class Text extends UIRenderer implements ITextRenderer { private static _textTextureProperty = ShaderProperty.getByName("renderElement_TextTexture"); + private static _textTextureSizeProperty = ShaderProperty.getByName("renderElement_TextTextureSize"); + private static _outlineColorProperty = ShaderProperty.getByName("renderer_OutlineColor"); + private static _outlineWidthProperty = ShaderProperty.getByName("renderer_OutlineWidth"); private static _worldPositions = [new Vector3(), new Vector3(), new Vector3(), new Vector3()]; private static _charRenderInfos: CharRenderInfo[] = []; @@ -59,6 +65,10 @@ export class Text extends UIRenderer implements ITextRenderer { private _enableWrapping: boolean = false; @assignmentClone private _overflowMode: OverflowMode = OverflowMode.Overflow; + @deepClone + private _outlineColor: Color = new Color(0, 0, 0, 1); + @ignoreClone + private _outlineWidth: number = 0; /** * Rendering string for the Text. @@ -204,6 +214,35 @@ export class Text extends UIRenderer implements ITextRenderer { } } + /** + * The outline width in pixels. 0 means outline is disabled. Clamped to [0, 8]. + */ + get outlineWidth(): number { + return this._outlineWidth; + } + + set outlineWidth(value: number) { + value = Math.max(0, Math.min(value, 3)); + if (this._outlineWidth !== value) { + this._outlineWidth = value; + this.shaderData.setFloat(Text._outlineWidthProperty, value); + this._setDirtyFlagTrue(DirtyFlag.Position); + } + } + + /** + * The outline color. Only effective when outlineWidth > 0. + */ + get outlineColor(): Color { + return this._outlineColor; + } + + set outlineColor(value: Color) { + if (this._outlineColor !== value) { + this._outlineColor.copyFrom(value); + } + } + /** * The bounding volume of the TextRenderer. */ @@ -235,6 +274,8 @@ export class Text extends UIRenderer implements ITextRenderer { this.raycastEnabled = false; // @ts-ignore this.setMaterial(engine._basicResources.textDefaultMaterial); + // @ts-ignore + this._outlineColor._onValueChanged = this._onOutlineColorChanged.bind(this); } /** @@ -260,6 +301,7 @@ export class Text extends UIRenderer implements ITextRenderer { super._cloneTo(target); target.font = this._font; target._subFont = this._subFont; + target.outlineWidth = this._outlineWidth; } /** @@ -302,6 +344,13 @@ export class Text extends UIRenderer implements ITextRenderer { } } + /** + * @internal + */ + override _canBatch(preElement, curElement): boolean { + return VertexMergeBatcher.canBatchText(preElement, curElement); + } + protected override _updateBounds(worldBounds: BoundingBox): void { const transform = this._transformEntity.transform; const { x: width, y: height } = transform.size; @@ -345,6 +394,7 @@ export class Text extends UIRenderer implements ITextRenderer { const distanceForSort = canvas._sortDistance; const textChunks = this._textChunks; const isOverlay = canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay; + const textTextureSize = Text._tempVec20; for (let i = 0, n = textChunks.length; i < n; ++i) { const { subChunk, texture } = textChunks[i]; const renderElement = textRenderElementPool.get(); @@ -352,6 +402,10 @@ export class Text extends UIRenderer implements ITextRenderer { // @ts-ignore renderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); renderElement.shaderData.setTexture(Text._textTextureProperty, texture); + renderElement.shaderData.setVector2( + Text._textTextureSizeProperty, + textTextureSize.set(texture.width, texture.height) + ); if (isOverlay) { renderElement.subShader = material.shader.subShaders[0]; } @@ -523,10 +577,11 @@ export class Text extends UIRenderer implements ITextRenderer { charRenderInfo.texture = charFont._getTextureByIndex(charInfo.index); charRenderInfo.uvs = charInfo.uvs; const { w, ascent, descent } = charInfo; - const left = (startX + offsetWidth) * pixelsPerUnitReciprocal; - const right = (startX + w + offsetWidth) * pixelsPerUnitReciprocal; - const top = (startY + ascent + offsetHeight) * pixelsPerUnitReciprocal; - const bottom = (startY - descent + offsetHeight) * pixelsPerUnitReciprocal; + const ow = this._outlineWidth * pixelsPerUnitReciprocal; + const left = (startX + offsetWidth) * pixelsPerUnitReciprocal - ow; + const right = (startX + w + offsetWidth) * pixelsPerUnitReciprocal + ow; + const top = (startY + ascent + offsetHeight) * pixelsPerUnitReciprocal + ow; + const bottom = (startY - descent + offsetHeight) * pixelsPerUnitReciprocal - ow; localPositions.set(left, top, right, bottom); i === firstLine && (maxY = Math.max(maxY, top)); minY = Math.min(minY, bottom); @@ -624,6 +679,10 @@ export class Text extends UIRenderer implements ITextRenderer { const vertices = subChunk.chunk.vertices; const indices = (subChunk.indices = []); const charRenderInfos = textChunk.charRenderInfos; + const ow = this._outlineWidth; + const texture = textChunk.texture; + const owU = ow > 0 ? ow / texture.width : 0; + const owV = ow > 0 ? ow / texture.height : 0; for (let i = 0, ii = 0, io = 0, vo = subChunk.vertexArea.start + 3; i < count; ++i, io += 4) { const charRenderInfo = charRenderInfos[i]; charRenderInfo.indexInChunk = i; @@ -633,10 +692,13 @@ export class Text extends UIRenderer implements ITextRenderer { indices[ii++] = tempIndices[j] + io; } - // Set uv and color for vertices + // Set uv and color for vertices, expand uv outward by outline width for (let j = 0; j < 4; ++j, vo += 9) { const uv = charRenderInfo.uvs[j]; - uv.copyToArray(vertices, vo); + const su = j === 1 || j === 2 ? 1 : -1; + const sv = j >= 2 ? 1 : -1; + vertices[vo] = uv.x + owU * su; + vertices[vo + 1] = uv.y + owV * sv; vertices[vo + 2] = r; vertices[vo + 3] = g; vertices[vo + 4] = b; @@ -665,6 +727,11 @@ export class Text extends UIRenderer implements ITextRenderer { } textChunks.length = 0; } + + @ignoreClone + private _onOutlineColorChanged(): void { + this.shaderData.setColor(Text._outlineColorProperty, this._outlineColor); + } } class TextChunk { From 9a1a34b4533b0f1b911e15583f08a937c869277a Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Sat, 2 May 2026 17:21:46 +0800 Subject: [PATCH 072/130] fix: init outline width --- packages/core/src/2d/text/TextRenderer.ts | 3 +++ packages/ui/src/component/advanced/Text.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 81fb1c582b..520c4824a7 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -346,6 +346,9 @@ export class TextRenderer extends Renderer implements ITextRenderer { this._font = engine._textDefaultFont; this._addResourceReferCount(this._font, 1); this.setMaterial(engine._basicResources.textDefaultMaterial); + const shaderData = this.shaderData; + shaderData.setFloat(TextRenderer._outlineWidthProperty, this._outlineWidth); + shaderData.setColor(TextRenderer._outlineColorProperty, this._outlineColor); //@ts-ignore this._color._onValueChanged = this._onColorChanged.bind(this); // @ts-ignore diff --git a/packages/ui/src/component/advanced/Text.ts b/packages/ui/src/component/advanced/Text.ts index fbfbd03fc2..a3ff96fa79 100644 --- a/packages/ui/src/component/advanced/Text.ts +++ b/packages/ui/src/component/advanced/Text.ts @@ -274,6 +274,9 @@ export class Text extends UIRenderer implements ITextRenderer { this.raycastEnabled = false; // @ts-ignore this.setMaterial(engine._basicResources.textDefaultMaterial); + const shaderData = this.shaderData; + shaderData.setFloat(Text._outlineWidthProperty, this._outlineWidth); + shaderData.setColor(Text._outlineColorProperty, this._outlineColor); // @ts-ignore this._outlineColor._onValueChanged = this._onOutlineColorChanged.bind(this); } From d09c43f7f657baa79e1db45ffe30582ee8c1ced4 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Sun, 3 May 2026 17:03:03 +0800 Subject: [PATCH 073/130] chore: release v0.0.0-experimental-2.0-game.9 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 55a90325aa..cb10bfdfd6 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index 75864ae90b..9dc1f6b070 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 41afbc01ef..f697d668e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 78a6bffc11..79f12c87f6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 10889b218d..50cb707b76 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index b377bc1fa7..96aafcc7f0 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index 247cb3f0c3..e599fa4eb5 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index e48e0f9a01..8a0877767b 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index e04c37a07e..e13751d0fb 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 0b039d25cf..b68019f6d7 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 5cc537a8a3..2d98df1306 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 8ff0bd7258..46e8c2d052 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index 796d3f0b6a..5f847737e5 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index d83df651a0..f00c52c271 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 15e2345727..d066d441fa 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index bf3446ab77..44c5dc068c 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index 08254d8298..97ba73a4bb 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.8", + "version": "0.0.0-experimental-2.0-game.9", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From 27b4bc2bb2403752753110c1d0d5bb75cb3e6fdd Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Mon, 4 May 2026 09:52:04 +0800 Subject: [PATCH 074/130] Revert "feat(ui): add SpriteSizeMode to Image component" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 13052d2f98a01eadcc1efee015f4a0a922e74427. Reasons: 1. Unit mismatch: `Sprite.width/height` are world units (px / referenceResolutionPerUnit, 1/100 by default). `_applySpriteSize` writes them directly into `UITransform.size`, producing a ~0px-sized image at the default referenceResolutionPerUnit=100. 2. Incomplete cocos parity: Cocos has 3 modes (Custom / Trimmed / Raw); this commit covers only Automatic (≈Trimmed) — and with the wrong unit. 3. Cocos parity is the migration-agent compat layer's responsibility, not the engine's. The compat polyfill at packages/script-agent/facts/compat/ui/image/image-size-mode.ts already implements all 3 Cocos modes correctly with atlas-region pixel math. 4. The native sizeMode setter is shadowed at runtime by the migration-agent polyfill (Object.defineProperty on Image.prototype), so the broken behavior is masked in production but still ships in the engine. Affected: 0 tests / 0 e2e cases / 0 follow-up commits referenced SpriteSizeMode in this repo. Mask + RectMask2D + filled* exports preserved. --- packages/ui/src/component/advanced/Image.ts | 42 --------------------- packages/ui/src/component/index.ts | 2 +- 2 files changed, 1 insertion(+), 43 deletions(-) diff --git a/packages/ui/src/component/advanced/Image.ts b/packages/ui/src/component/advanced/Image.ts index bac5457c86..cfb2cf41ff 100644 --- a/packages/ui/src/component/advanced/Image.ts +++ b/packages/ui/src/component/advanced/Image.ts @@ -23,16 +23,6 @@ import { RootCanvasModifyFlags } from "../UICanvas"; import { UIRenderer, UIRendererUpdateFlags } from "../UIRenderer"; import { UITransform, UITransformModifyFlags } from "../UITransform"; -/** - * Determines how the Image element's size is controlled relative to the sprite. - */ -export enum SpriteSizeMode { - /** The image size is controlled manually via UITransform (default, existing behavior). */ - Custom = 0, - /** The image size is automatically set to the sprite's natural dimensions when the sprite changes. */ - Automatic = 1 -} - /** * UI element that renders an image. */ @@ -55,8 +45,6 @@ export class Image extends UIRenderer implements ISpriteRenderer { private _filledOrigin: SpriteFilledOrigin = SpriteFilledOrigin.Bottom; @assignmentClone private _filledClockWise: boolean = true; - @assignmentClone - private _sizeMode: SpriteSizeMode = SpriteSizeMode.Custom; /** * The draw mode of the image. @@ -89,23 +77,6 @@ export class Image extends UIRenderer implements ISpriteRenderer { } } - /** - * The size mode of the image. When set to `Automatic`, the UITransform size - * is automatically synchronized to the sprite's natural dimensions. - */ - get sizeMode(): SpriteSizeMode { - return this._sizeMode; - } - - set sizeMode(value: SpriteSizeMode) { - if (this._sizeMode !== value) { - this._sizeMode = value; - if (value === SpriteSizeMode.Automatic && this._sprite) { - this._applySpriteSize(); - } - } - } - /** * The tiling mode of the image. (Only works in tiled mode.) */ @@ -231,9 +202,6 @@ export class Image extends UIRenderer implements ISpriteRenderer { this.shaderData.setTexture(UIRenderer._textureProperty, null); } this._sprite = value; - if (this._sizeMode === SpriteSizeMode.Automatic && value) { - this._applySpriteSize(); - } } } @@ -386,9 +354,6 @@ export class Image extends UIRenderer implements ISpriteRenderer { this.shaderData.setTexture(UIRenderer._textureProperty, this.sprite.texture); break; case SpriteModifyFlags.size: - if (this._sizeMode === SpriteSizeMode.Automatic) { - this._applySpriteSize(); - } switch (this._drawMode) { case SpriteDrawMode.Sliced: this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; @@ -427,13 +392,6 @@ export class Image extends UIRenderer implements ISpriteRenderer { break; } } - - private _applySpriteSize(): void { - const sprite = this._sprite; - if (sprite) { - (this._transformEntity.transform).size.set(sprite.width, sprite.height); - } - } } /** diff --git a/packages/ui/src/component/index.ts b/packages/ui/src/component/index.ts index 694bf24199..8329d1f39a 100644 --- a/packages/ui/src/component/index.ts +++ b/packages/ui/src/component/index.ts @@ -1,5 +1,5 @@ export { Button } from "./advanced/Button"; -export { Image, SpriteSizeMode } from "./advanced/Image"; +export { Image } from "./advanced/Image"; export { Mask } from "./advanced/Mask"; export { RectMask2D } from "./advanced/RectMask2D"; export { Text } from "./advanced/Text"; From 0fb25c247fc7c3c9d37957c545bc8ab611c304e2 Mon Sep 17 00:00:00 2001 From: ChenMo Date: Wed, 1 Apr 2026 23:19:50 +0800 Subject: [PATCH 075/130] refactor: unify shader file extension from .gs/.gsl to .shader (#2951) refactor: Align with Unity convention by using .shader as the standard extension for Galacean shader files instead of .gs/.gsl. --- docs/en/script/edit.mdx | 4 ++-- docs/zh/script/edit.mdx | 4 ++-- e2e/case/shaderLab-mrt.ts | 2 +- packages/loader/src/ShaderLoader.ts | 2 +- packages/shader/src/global.d.ts | 2 +- packages/shader/src/shaders/{PBR.gs => PBR.shader} | 0 packages/shader/src/shaders/index.ts | 2 +- packages/ui/src/shader/global.d.ts | 2 +- rollup-plugin-glsl.js | 2 +- rollup.config.js | 6 +++--- tests/src/shader-lab/shaders/mrt-struct.shader | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) rename packages/shader/src/shaders/{PBR.gs => PBR.shader} (100%) diff --git a/docs/en/script/edit.mdx b/docs/en/script/edit.mdx index 60feec35e5..95c8561a11 100644 --- a/docs/en/script/edit.mdx +++ b/docs/en/script/edit.mdx @@ -24,14 +24,14 @@ For more information about the code editor, please check [Code Editor](/en/docs/ After creating a script asset in the scene editor, double-click the script to open the code editor. Scripts in Galacean need to be written in [Typescript](https://www.typescriptlang.org/), and new scripts are created based on built-in templates by default. Additionally, the Galacean code editor is based on Monaco, with shortcuts similar to VSCode. After modifying the script, press `Ctrl/⌘ + S` to save, and the real-time preview area on the right will show the latest scene effects. -> Tip: The Galacean code editor currently supports `.ts`, `.gs`, and `.glsl` file editing. +> Tip: The Galacean code editor currently supports `.ts`, `.shader`, and `.glsl` file editing. ## File Preview 1. **File Search** Quickly search for files in the project -2. **Code Filter** Whether to display only code files ( `.ts`, `.gs`, `.glsl` ) in the file tree +2. **Code Filter** Whether to display only code files ( `.ts`, `.shader`, `.glsl` ) in the file tree 3. **Built-in Files** Used to display which files are non-editable internal files 4. **Expand/Hide** Toggle the expansion or hiding of folders 5. **Code Files** Editable code files will display corresponding file type thumbnails diff --git a/docs/zh/script/edit.mdx b/docs/zh/script/edit.mdx index 28cdd108a5..4cb0255eff 100644 --- a/docs/zh/script/edit.mdx +++ b/docs/zh/script/edit.mdx @@ -24,14 +24,14 @@ Galacean Editor 提供了一个功能强大的代码编辑器,提供了代码 在场景编辑器中创建脚本资产后,双击该脚本即可打开代码编辑器。Galacean 中的脚本需使用 [Typescript](https://www.typescriptlang.org/) 语言编写,同时新脚本默认基于内置模板创建。另外,Galacean 的代码编辑器基于 Monaco,快捷键与 VSCode 类似。修改脚本后,按 `Ctrl/⌘ + S` 保存,右侧实时预览区展现最新场景效果。 -> 提示:Galacean 代码编辑器目前支持 `.ts` `.gs` 和 `.glsl` 的文件编辑 +> 提示:Galacean 代码编辑器目前支持 `.ts` `.shader` 和 `.glsl` 的文件编辑 ## 文件预览 1. **文件搜索** 可快速搜索项目中的文件 -2. **代码筛选** 文件树是否仅显示代码文件 ( `.ts` `.gs` `.glsl` ) +2. **代码筛选** 文件树是否仅显示代码文件 ( `.ts` `.shader` `.glsl` ) 3. **内置文件** 用来显示哪些文件是不可编辑的内部文件 4. **展开/隐藏** 可切换文件夹的展开或隐藏 5. **代码文件** 可编辑的代码文件会显示对应的文件类型的缩略图标 diff --git a/e2e/case/shaderLab-mrt.ts b/e2e/case/shaderLab-mrt.ts index 54afaf6494..a8f3a2c7cd 100644 --- a/e2e/case/shaderLab-mrt.ts +++ b/e2e/case/shaderLab-mrt.ts @@ -9,7 +9,7 @@ import { initScreenshot, updateForE2E } from "./.mockForE2E"; const shaderLab = new ShaderLab(); -const shaderSource = `Shader "/custom.gs" { +const shaderSource = `Shader "/custom.shader" { SubShader "Default" { UsePass "pbr/Default/ShadowCaster" diff --git a/packages/loader/src/ShaderLoader.ts b/packages/loader/src/ShaderLoader.ts index 3380ff297e..d7e714af50 100644 --- a/packages/loader/src/ShaderLoader.ts +++ b/packages/loader/src/ShaderLoader.ts @@ -9,7 +9,7 @@ import { } from "@galacean/engine-core"; import { ShaderChunkLoader } from "./ShaderChunkLoader"; -@resourceLoader(AssetType.Shader, ["gs", "gsl"]) +@resourceLoader(AssetType.Shader, ["shader"]) class ShaderLoader extends Loader { private static _builtinRegex = /^\s*\/\/\s*@builtin\s+(\w+)/; diff --git a/packages/shader/src/global.d.ts b/packages/shader/src/global.d.ts index d0abf1234f..8ce5f33757 100644 --- a/packages/shader/src/global.d.ts +++ b/packages/shader/src/global.d.ts @@ -3,7 +3,7 @@ declare module "*.glsl" { export default value; } -declare module "*.gs" { +declare module "*.shader" { const value: string; export default value; } diff --git a/packages/shader/src/shaders/PBR.gs b/packages/shader/src/shaders/PBR.shader similarity index 100% rename from packages/shader/src/shaders/PBR.gs rename to packages/shader/src/shaders/PBR.shader diff --git a/packages/shader/src/shaders/index.ts b/packages/shader/src/shaders/index.ts index de23d2e392..3a0430c975 100644 --- a/packages/shader/src/shaders/index.ts +++ b/packages/shader/src/shaders/index.ts @@ -3,7 +3,7 @@ import Common from "./Common.glsl"; import Fog from "./Fog.glsl"; import Light from "./Light.glsl"; import Normal from "./Normal.glsl"; -import PBRSource from "./PBR.gs"; +import PBRSource from "./PBR.shader"; import Shadow from "./Shadow.glsl"; import ShadowSampleTent from "./ShadowSampleTent.glsl"; import Skin from "./Skin.glsl"; diff --git a/packages/ui/src/shader/global.d.ts b/packages/ui/src/shader/global.d.ts index d0abf1234f..8ce5f33757 100644 --- a/packages/ui/src/shader/global.d.ts +++ b/packages/ui/src/shader/global.d.ts @@ -3,7 +3,7 @@ declare module "*.glsl" { export default value; } -declare module "*.gs" { +declare module "*.shader" { const value: string; export default value; } diff --git a/rollup-plugin-glsl.js b/rollup-plugin-glsl.js index cc545c623b..3a48c3efc9 100644 --- a/rollup-plugin-glsl.js +++ b/rollup-plugin-glsl.js @@ -35,7 +35,7 @@ function compressShader(code) { export default function glsl(userOptions = {}) { const options = Object.assign( { - include: ["**/*.vs", "**/*.fs", "**/*.vert", "**/*.frag", "**/*.glsl"] + include: ["**/*.vs", "**/*.fs", "**/*.vert", "**/*.frag", "**/*.glsl", "**/*.shader"] }, userOptions ); diff --git a/rollup.config.js b/rollup.config.js index b72def7cb8..dd57ae8a1b 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -32,7 +32,7 @@ const extensions = [".js", ".jsx", ".ts", ".tsx"]; const mainFields = NODE_ENV === "development" ? ["debug", "module", "main"] : undefined; const glslPlugin = glsl({ - include: [/\.(glsl|gs)$/], + include: [/\.(glsl|shader)$/], compress: false }); @@ -90,7 +90,7 @@ function config({ location, pkgJson, verboseMode }) { glslifyPluginIdx, 1, glsl({ - include: [/\.(glsl|gs)$/], + include: [/\.(glsl|shader)$/], compress: true }) ); @@ -157,7 +157,7 @@ function config({ location, pkgJson, verboseMode }) { glslifyPluginIdx, 1, glsl({ - include: [/\.(glsl|gs)$/], + include: [/\.(glsl|shader)$/], compress: true }) ); diff --git a/tests/src/shader-lab/shaders/mrt-struct.shader b/tests/src/shader-lab/shaders/mrt-struct.shader index f29056199e..85a47193c9 100644 --- a/tests/src/shader-lab/shaders/mrt-struct.shader +++ b/tests/src/shader-lab/shaders/mrt-struct.shader @@ -1,4 +1,4 @@ -Shader "/mrt-test.gs" { +Shader "/mrt-test.shader" { SubShader "Default" { From 8ef104d545ce16b97d9c075e2adf0a7b304ca8b4 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 12 May 2026 17:38:40 +0800 Subject: [PATCH 076/130] fix: get components order --- packages/core/src/Entity.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/core/src/Entity.ts b/packages/core/src/Entity.ts index 35c6fac424..5b0c929ac1 100644 --- a/packages/core/src/Entity.ts +++ b/packages/core/src/Entity.ts @@ -684,14 +684,16 @@ export class Entity extends EngineObject { } private _getComponentsInChildren(type: ComponentConstructor, results: T[]): void { - for (let i = this._components.length - 1; i >= 0; i--) { - const component = this._components[i]; + const components = this._components; + for (let i = 0, n = components.length; i < n; i++) { + const component = components[i]; if (component instanceof type) { results.push(component); } } - for (let i = this._children.length - 1; i >= 0; i--) { - this._children[i]._getComponentsInChildren(type, results); + const children = this._children; + for (let i = 0, n = children.length; i < n; i++) { + children[i]._getComponentsInChildren(type, results); } } From 8b0cabd403424b151c578325ee52f6b0bb6e7a4b Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 14 May 2026 11:50:50 +0800 Subject: [PATCH 077/130] feat: opt pointer event data --- packages/core/src/input/pointer/Pointer.ts | 2 ++ .../src/input/pointer/PointerEventData.ts | 7 +++++++ .../core/src/input/pointer/PointerManager.ts | 1 + .../emitter/PhysicsPointerEventEmitter.ts | 16 +++++++------- .../pointer/emitter/PointerEventEmitter.ts | 4 +++- .../ui/src/input/UIPointerEventEmitter.ts | 21 ++++++++----------- 6 files changed, 30 insertions(+), 21 deletions(-) diff --git a/packages/core/src/input/pointer/Pointer.ts b/packages/core/src/input/pointer/Pointer.ts index a7412bc2ed..bb0d9fb904 100644 --- a/packages/core/src/input/pointer/Pointer.ts +++ b/packages/core/src/input/pointer/Pointer.ts @@ -23,6 +23,8 @@ export class Pointer { pressedButtons: PointerButton; /** The position of the pointer in screen space pixel coordinates. */ position: Vector2 = new Vector2(); + /** The position of the pointer when it was last pressed down (in screen space pixel coordinates). */ + pressedPosition: Vector2 = new Vector2(); /** The change of the pointer. */ deltaPosition: Vector2 = new Vector2(); /** @internal */ diff --git a/packages/core/src/input/pointer/PointerEventData.ts b/packages/core/src/input/pointer/PointerEventData.ts index 89f9ae09b6..33dc41b001 100644 --- a/packages/core/src/input/pointer/PointerEventData.ts +++ b/packages/core/src/input/pointer/PointerEventData.ts @@ -1,4 +1,5 @@ import { Vector3 } from "@galacean/engine-math"; +import { Entity } from "../../Entity"; import { IPoolElement } from "../../utils/ObjectPool"; import { Pointer } from "./Pointer"; @@ -10,11 +11,17 @@ export class PointerEventData implements IPoolElement { pointer: Pointer; /** The position of the event trigger (in world space). */ worldPosition: Vector3 = new Vector3(); + /** The hit-tested target entity (deepest entity on the bubble path). */ + target: Entity = null; + /** The entity currently handling the event (same as target on non-bubbling fire, varies on bubble). */ + currentTarget: Entity = null; /** * @internal */ dispose() { this.pointer = null; + this.target = null; + this.currentTarget = null; } } diff --git a/packages/core/src/input/pointer/PointerManager.ts b/packages/core/src/input/pointer/PointerManager.ts index fc6d8a2010..83b2310c83 100644 --- a/packages/core/src/input/pointer/PointerManager.ts +++ b/packages/core/src/input/pointer/PointerManager.ts @@ -245,6 +245,7 @@ export class PointerManager implements IInput { pointer._downMap[button] = frameCount; pointer._frameEvents |= PointerEventType.Down; pointer.phase = PointerPhase.Down; + pointer.pressedPosition.copyFrom(pointer.position); break; } case "pointerup": { diff --git a/packages/core/src/input/pointer/emitter/PhysicsPointerEventEmitter.ts b/packages/core/src/input/pointer/emitter/PhysicsPointerEventEmitter.ts index 5778f924a9..5cfc2ec8cd 100644 --- a/packages/core/src/input/pointer/emitter/PhysicsPointerEventEmitter.ts +++ b/packages/core/src/input/pointer/emitter/PhysicsPointerEventEmitter.ts @@ -53,13 +53,13 @@ export class PhysicsPointerEventEmitter extends PointerEventEmitter { override processDrag(pointer: Pointer): void { const entity = this._draggedEntity; - entity && this._fireDrag(entity, this._createEventData(pointer)); + entity && this._fireDrag(entity, this._createEventData(pointer, entity, entity)); } override processDown(pointer: Pointer): void { const entity = (this._pressedEntity = this._draggedEntity = this._enteredEntity); if (entity) { - const eventData = this._createEventData(pointer); + const eventData = this._createEventData(pointer, entity, entity); this._fireDown(entity, eventData); this._fireBeginDrag(entity, eventData); } @@ -69,14 +69,14 @@ export class PhysicsPointerEventEmitter extends PointerEventEmitter { const { _enteredEntity: enteredEntity, _draggedEntity: draggedEntity } = this; if (enteredEntity) { const sameTarget = this._pressedEntity === enteredEntity; - const eventData = this._createEventData(pointer); + const eventData = this._createEventData(pointer, enteredEntity, enteredEntity); this._fireUp(enteredEntity, eventData); sameTarget && this._fireClick(enteredEntity, eventData); this._fireDrop(enteredEntity, eventData); } this._pressedEntity = null; if (draggedEntity) { - this._fireEndDrag(draggedEntity, this._createEventData(pointer)); + this._fireEndDrag(draggedEntity, this._createEventData(pointer, draggedEntity, draggedEntity)); this._draggedEntity = null; } } @@ -84,13 +84,13 @@ export class PhysicsPointerEventEmitter extends PointerEventEmitter { override processLeave(pointer: Pointer): void { const enteredEntity = this._enteredEntity; if (enteredEntity) { - this._fireExit(enteredEntity, this._createEventData(pointer)); + this._fireExit(enteredEntity, this._createEventData(pointer, enteredEntity, enteredEntity)); this._enteredEntity = null; } const draggedEntity = this._draggedEntity; if (draggedEntity) { - this._fireEndDrag(draggedEntity, this._createEventData(pointer)); + this._fireEndDrag(draggedEntity, this._createEventData(pointer, draggedEntity, draggedEntity)); this._draggedEntity = null; } this._pressedEntity = null; @@ -108,10 +108,10 @@ export class PhysicsPointerEventEmitter extends PointerEventEmitter { const enteredEntity = this._enteredEntity; if (entity !== enteredEntity) { if (enteredEntity) { - this._fireExit(enteredEntity, this._createEventData(pointer)); + this._fireExit(enteredEntity, this._createEventData(pointer, enteredEntity, enteredEntity)); } if (entity) { - this._fireEnter(entity, this._createEventData(pointer)); + this._fireEnter(entity, this._createEventData(pointer, entity, entity)); } this._enteredEntity = entity; } diff --git a/packages/core/src/input/pointer/emitter/PointerEventEmitter.ts b/packages/core/src/input/pointer/emitter/PointerEventEmitter.ts index 6f6c62cdfb..98188891e6 100644 --- a/packages/core/src/input/pointer/emitter/PointerEventEmitter.ts +++ b/packages/core/src/input/pointer/emitter/PointerEventEmitter.ts @@ -32,10 +32,12 @@ export abstract class PointerEventEmitter { protected abstract _init(): void; - protected _createEventData(pointer: Pointer): PointerEventData { + protected _createEventData(pointer: Pointer, target: Entity, currentTarget: Entity = target): PointerEventData { const data = this._pool.get(); data.pointer = pointer; data.worldPosition.copyFrom(this._hitResult.point); + data.target = target; + data.currentTarget = currentTarget; return data; } diff --git a/packages/ui/src/input/UIPointerEventEmitter.ts b/packages/ui/src/input/UIPointerEventEmitter.ts index 83bf8f1484..d36e529876 100644 --- a/packages/ui/src/input/UIPointerEventEmitter.ts +++ b/packages/ui/src/input/UIPointerEventEmitter.ts @@ -91,11 +91,11 @@ export class UIPointerEventEmitter extends PointerEventEmitter { } } if (camera.clearFlags & CameraClearFlags.Color) { - this._updateRaycast(null); + this._updateRaycast(null, pointer); return; } } - this._updateRaycast(null); + this._updateRaycast(null, pointer); } } @@ -128,10 +128,7 @@ export class UIPointerEventEmitter extends PointerEventEmitter { if (pressedPath.length > 0) { const common = UIPointerEventEmitter._tempArray0; if (this._findCommonInPath(enteredPath, pressedPath, common)) { - const eventData = this._createEventData(pointer); - for (let i = 0, n = common.length; i < n; i++) { - this._fireClick(common[i], eventData); - } + this._bubble(common, pointer, this._fireClick); common.length = 0; } } @@ -170,18 +167,17 @@ export class UIPointerEventEmitter extends PointerEventEmitter { this._enteredPath.length = this._pressedPath.length = this._draggedPath.length = 0; } - private _updateRaycast(element: UIRenderer, pointer: Pointer = null): void { + private _updateRaycast(element: UIRenderer | null, pointer: Pointer): void { const enteredPath = this._enteredPath; const curPath = this._composedPath(element, UIPointerEventEmitter._path); const add = UIPointerEventEmitter._tempArray0; const del = UIPointerEventEmitter._tempArray1; if (this._findDiffInPath(enteredPath, curPath, add, del)) { - const eventData = this._createEventData(pointer); for (let i = 0, n = add.length; i < n; i++) { - this._fireEnter(add[i], eventData); + this._fireEnter(add[i], this._createEventData(pointer, add[i], add[i])); } for (let i = 0, n = del.length; i < n; i++) { - this._fireExit(del[i], eventData); + this._fireExit(del[i], this._createEventData(pointer, del[i], del[i])); } const length = (enteredPath.length = curPath.length); @@ -193,7 +189,7 @@ export class UIPointerEventEmitter extends PointerEventEmitter { curPath.length = 0; } - private _composedPath(element: UIRenderer, path: Entity[]): Entity[] { + private _composedPath(element: UIRenderer | null, path: Entity[]): Entity[] { if (!element) { path.length = 0; return path; @@ -256,8 +252,9 @@ export class UIPointerEventEmitter extends PointerEventEmitter { private _bubble(path: Entity[], pointer: Pointer, fireEvent: FireEvent): void { const length = path.length; if (length <= 0) return; - const eventData = this._createEventData(pointer); + const eventData = this._createEventData(pointer, path[0]); for (let i = 0; i < length; i++) { + eventData.currentTarget = path[i]; fireEvent(path[i], eventData); } } From cf8c320846ba87658c4d5bbfebfa0f3a62a90bdc Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 14 May 2026 16:30:25 +0800 Subject: [PATCH 078/130] chore: release v0.0.0-experimental-2.0-game.10 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index cb10bfdfd6..d02b608399 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index 9dc1f6b070..124a7cbae0 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index f697d668e1..aed2fe3a5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 79f12c87f6..4cb3aea7c8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 50cb707b76..32bd579bd3 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index 96aafcc7f0..d5e4104bf3 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index e599fa4eb5..537f3a9dcc 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 8a0877767b..5c50c1e1b1 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index e13751d0fb..7d31d2444f 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index b68019f6d7..1ac1ad81af 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 2d98df1306..d37704d9f6 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 46e8c2d052..6b99686561 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index 5f847737e5..dc3b0e906d 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index f00c52c271..b1e1450098 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index d066d441fa..f6431c27a8 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 44c5dc068c..2c4a07a858 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index 97ba73a4bb..af188dbfb3 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.9", + "version": "0.0.0-experimental-2.0-game.10", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From 9fad2049e5f62ce68bcfbccf9e8f74e1c96d8ec6 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 14 May 2026 16:57:36 +0800 Subject: [PATCH 079/130] fix: outline shader --- packages/core/src/2d/text/TextRenderer.ts | 1 - packages/core/src/shaderlib/extra/text.fs.glsl | 2 +- packages/ui/src/component/advanced/Text.ts | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 520c4824a7..215a762869 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -722,7 +722,6 @@ export class TextRenderer extends Renderer implements ITextRenderer { if (charLength > 0) { this._buildChunk(curTextChunk, charLength); } - this._setDirtyFlagTrue(DirtyFlag.WorldPosition); charRenderInfos.length = 0; } diff --git a/packages/core/src/shaderlib/extra/text.fs.glsl b/packages/core/src/shaderlib/extra/text.fs.glsl index 84693a6773..0ea05d15d2 100644 --- a/packages/core/src/shaderlib/extra/text.fs.glsl +++ b/packages/core/src/shaderlib/extra/text.fs.glsl @@ -62,7 +62,7 @@ void main() outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2(-step.x * 0.7071, -step.y * 0.7071))); vec3 rgb = mix(renderer_OutlineColor.rgb, v_color.rgb, coverage); - float alpha = max(coverage * v_color.a, outlineCoverage * renderer_OutlineColor.a); + float alpha = max(coverage, outlineCoverage * renderer_OutlineColor.a) * v_color.a; finalColor = vec4(rgb, alpha); } else { finalColor = vec4(v_color.rgb, v_color.a * coverage); diff --git a/packages/ui/src/component/advanced/Text.ts b/packages/ui/src/component/advanced/Text.ts index a3ff96fa79..638fc2e97d 100644 --- a/packages/ui/src/component/advanced/Text.ts +++ b/packages/ui/src/component/advanced/Text.ts @@ -343,7 +343,7 @@ export class Text extends UIRenderer implements ITextRenderer { */ _onRootCanvasModify(flag: RootCanvasModifyFlags): void { if (flag === RootCanvasModifyFlags.ReferenceResolutionPerUnit) { - this._setDirtyFlagTrue(DirtyFlag.LocalPositionBounds); + this._setDirtyFlagTrue(DirtyFlag.Position); } } @@ -648,7 +648,6 @@ export class Text extends UIRenderer implements ITextRenderer { if (charLength > 0) { this._buildChunk(curTextChunk, charLength); } - this._setDirtyFlagTrue(DirtyFlag.WorldPosition); charRenderInfos.length = 0; } From 3b8193cbc07009cb2296d8bf329f39a90466753e Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 14 May 2026 16:58:33 +0800 Subject: [PATCH 080/130] chore: release v0.0.0-experimental-2.0-game.11 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index d02b608399..51157d1df3 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index 124a7cbae0..19bde9054f 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index aed2fe3a5d..2dd931dc8d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 4cb3aea7c8..5c21ee1ff0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 32bd579bd3..55e657be34 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index d5e4104bf3..ee346f5e47 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index 537f3a9dcc..85c4319eeb 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 5c50c1e1b1..4c3593c07b 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 7d31d2444f..a03c4b3371 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 1ac1ad81af..02fbc772e0 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index d37704d9f6..1fdd107dd2 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 6b99686561..9847e05fb0 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index dc3b0e906d..768788a7f3 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index b1e1450098..405a75b24f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index f6431c27a8..8abb3f4522 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 2c4a07a858..cf3fc01a1c 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index af188dbfb3..56b37bae11 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.10", + "version": "0.0.0-experimental-2.0-game.11", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From 273d18151f56d17d0052acae10c92ea9248a598f Mon Sep 17 00:00:00 2001 From: luzhuang Date: Thu, 14 May 2026 20:05:50 +0800 Subject: [PATCH 081/130] fix(physics): resolve clone-bypass and runtime native handle desync bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four independent bugs all share the same root pattern — "JS field is the source of truth, native handle synced via setter side-effects" — fails whenever a code path bypasses the setter: - **R0** kinematic + CCD warning: setRigidBodyFlag(eENABLE_CCD,true) on a kinematic actor prints a PhysX warning and the flag becomes undefined when switching back to dynamic. Cache `_isKinematic` and `_collisionDetectionMode`; apply CCD flags only in dynamic state; on kinematic→dynamic transition, reapply the cached mode. - **R5** addForce/addTorque silently ignored on sleeping actors: PhysX wasm wrapper omits the `autowake` parameter (C++ default true, wasm default false). Explicitly `wakeUp()` before adding force/torque; guard kinematic to avoid triggering a separate PhysX warning. - **R6** MeshColliderShape async cook & prefab clone: `_cookMesh` can fail transiently when mesh data is not yet accessible; the cloned shape's `_nativeShape` is `@ignoreClone` so prefab instantiation produces a useless shape. Add `_pendingNativeShapeCreation` flag; retry every physics tick via new `ColliderShape._onPhysicsUpdate` hook (driven by `Collider._onUpdate`); override `_cloneTo` to cook a fresh native shape from already-cloned buffers. - **R8** PhysicsMaterial clone bypasses setters: CloneManager deep-copies `_bounciness/_friction/_combine` fields directly without invoking setters, so the target's freshly-constructed PxMaterial keeps its default values while JS fields show source values. Result: PhysX uses default `b=0` while logs and `material.bounciness` read source values — fully invisible divergence. Mark `_nativeMaterial` `@ignoreClone`; add `_cloneTo` → `_syncNative()` that re-writes all 5 fields to native via setter API. Side fix: PhysicsMaterial constructor was passing `bounceCombine` and `frictionCombine` to `createPhysicsMaterial` in reverse order — the bug was hidden because R8 kept native stuck at default Average (both reversed arguments are 0) and users only ever read JS fields. Fixed in the same patch. Test: `PhysicsMaterial.test.ts > cloned collider shape material keeps native values` verifies a cloned dynamic body bounces off a wall by simulating 40 frames and asserting vx flips negative — fails without the R8 fix. --- packages/core/src/physics/Collider.ts | 9 +- packages/core/src/physics/PhysicsMaterial.ts | 25 +++++- .../core/src/physics/shape/ColliderShape.ts | 12 +++ .../src/physics/shape/MeshColliderShape.ts | 61 +++++++++++++- .../physics-physx/src/PhysXDynamicCollider.ts | 84 +++++++++++++++---- .../src/core/physics/PhysicsMaterial.test.ts | 50 +++++++++++ 6 files changed, 216 insertions(+), 25 deletions(-) diff --git a/packages/core/src/physics/Collider.ts b/packages/core/src/physics/Collider.ts index a8a591fe56..40abda9e00 100644 --- a/packages/core/src/physics/Collider.ts +++ b/packages/core/src/physics/Collider.ts @@ -108,6 +108,7 @@ export class Collider extends Component implements ICustomClone { * @internal */ _onUpdate(): void { + const shapes = this._shapes; if (this._updateFlag.flag) { const { transform } = this.entity; (this._nativeCollider).setWorldTransform( @@ -116,12 +117,18 @@ export class Collider extends Component implements ICustomClone { ); const worldScale = transform.lossyWorldScale; - const shapes = this._shapes; for (let i = 0, n = shapes.length; i < n; i++) { shapes[i]._nativeShape?.setWorldScale(worldScale); } this._updateFlag.flag = false; } + + // Drive per-shape physics update (e.g. MeshColliderShape retries pending + // native shape creation when mesh data becomes accessible asynchronously). + // No-op for shape types that don't override `_onPhysicsUpdate`. + for (let i = 0, n = shapes.length; i < n; i++) { + shapes[i]._onPhysicsUpdate(); + } } /** diff --git a/packages/core/src/physics/PhysicsMaterial.ts b/packages/core/src/physics/PhysicsMaterial.ts index 3ac16e66d8..aa22bc8c24 100644 --- a/packages/core/src/physics/PhysicsMaterial.ts +++ b/packages/core/src/physics/PhysicsMaterial.ts @@ -1,6 +1,7 @@ import { IPhysicsMaterial } from "@galacean/engine-design"; import { Engine } from "../Engine"; import { PhysicsMaterialCombineMode } from "./enums/PhysicsMaterialCombineMode"; +import { ignoreClone } from "../clone/CloneManager"; /** * Material class to represent a set of surface properties. @@ -14,6 +15,7 @@ export class PhysicsMaterial { private _destroyed: boolean; /** @internal */ + @ignoreClone _nativeMaterial: IPhysicsMaterial; constructor() { @@ -21,8 +23,8 @@ export class PhysicsMaterial { this._staticFriction, this._dynamicFriction, this._bounciness, - this._bounceCombine, - this._frictionCombine + this._frictionCombine, + this._bounceCombine ); } @@ -103,4 +105,23 @@ export class PhysicsMaterial { !this._destroyed && this._nativeMaterial.destroy(); this._destroyed = true; } + + /** + * @internal + */ + _cloneTo(target: PhysicsMaterial): void { + target._syncNative(); + } + + /** + * @internal + */ + _syncNative(): void { + const nativeMaterial = this._nativeMaterial; + nativeMaterial.setStaticFriction(this._staticFriction); + nativeMaterial.setDynamicFriction(this._dynamicFriction); + nativeMaterial.setBounciness(this._bounciness); + nativeMaterial.setFrictionCombine(this._frictionCombine); + nativeMaterial.setBounceCombine(this._bounceCombine); + } } diff --git a/packages/core/src/physics/shape/ColliderShape.ts b/packages/core/src/physics/shape/ColliderShape.ts index 2ac82319fd..921612f5f5 100644 --- a/packages/core/src/physics/shape/ColliderShape.ts +++ b/packages/core/src/physics/shape/ColliderShape.ts @@ -168,6 +168,18 @@ export abstract class ColliderShape implements ICustomClone { target._syncNative(); } + /** + * @internal + * + * Called once per physics update tick by `Collider._onUpdate`. Base no-op. + * + * Subclasses can override for frame-driven maintenance. Currently used by + * `MeshColliderShape` to retry native shape creation when mesh data becomes + * accessible later or PhysX cooking previously failed due to transient state + * (async resource loading, cook pipeline warmup, etc.). + */ + _onPhysicsUpdate(): void {} + /** * @internal */ diff --git a/packages/core/src/physics/shape/MeshColliderShape.ts b/packages/core/src/physics/shape/MeshColliderShape.ts index 51a46ab0e0..83935fe10c 100644 --- a/packages/core/src/physics/shape/MeshColliderShape.ts +++ b/packages/core/src/physics/shape/MeshColliderShape.ts @@ -16,6 +16,12 @@ export class MeshColliderShape extends ColliderShape { private _indices: Uint8Array | Uint16Array | Uint32Array | null = null; private _cookingFlags = MeshColliderShapeCookingFlag.Cleaning | MeshColliderShapeCookingFlag.VertexWelding; private _isShapeAttached = false; + /** + * `true` if a native shape creation was attempted but failed (mesh data not yet + * accessible, PhysX cooking transient failure, etc.). The `_onPhysicsUpdate` hook + * will keep retrying every frame until creation succeeds. + */ + private _pendingNativeShapeCreation = false; /** * Cooking flags for this mesh collider shape. @@ -74,15 +80,25 @@ export class MeshColliderShape extends ColliderShape { this._mesh?._addReferCount(-1); value?._addReferCount(1); this._mesh = value; - if (value && this._extractMeshData(value)) { - if (this._nativeShape) { - this._updateNativeShapeData(); + if (value) { + if (this._extractMeshData(value)) { + if (this._nativeShape) { + this._updateNativeShapeData(); + } else { + this._createNativeShape(); + } + // _createNativeShape can fail silently (cookMesh transient failure); mark pending if so + this._pendingNativeShapeCreation = !this._nativeShape; } else { - this._createNativeShape(); + // Mesh not yet accessible — keep pending so `_onPhysicsUpdate` retries later + this._destroyNativeShape(); + this._clearMeshData(); + this._pendingNativeShapeCreation = true; } } else { this._destroyNativeShape(); this._clearMeshData(); + this._pendingNativeShapeCreation = false; } } } @@ -197,10 +213,13 @@ export class MeshColliderShape extends ColliderShape { ); if (!nativeShape) { + // Cook failed — `_onPhysicsUpdate` will retry next frame + this._pendingNativeShapeCreation = true; return; } this._nativeShape = nativeShape; + this._pendingNativeShapeCreation = false; // Sync base class properties (position, rotation, contactOffset, isTrigger, material) super._syncNative(); @@ -211,4 +230,38 @@ export class MeshColliderShape extends ColliderShape { this._attachToCollider(); } } + + /** + * @internal + * Retry hook: keep attempting `_createNativeShape` until it succeeds. + * + * Triggered every physics update tick by `Collider._onUpdate`. Handles the case + * where `_cookMesh` fails on first attempt due to PhysX cooking pipeline timing + * (the mesh data was extracted successfully at `set mesh` time, but the native + * cook call returned null). No-op once a valid native shape exists. + * + * We DO NOT re-call `_extractMeshData` here — once `set mesh` finished, either: + * - extraction succeeded → `_positions` is populated and we reuse it + * - extraction failed → `_clearMeshData` cleared `_positions`, and `mesh.accessible` + * won't recover (GPU upload is one-way), so re-extracting would fail again + */ + override _onPhysicsUpdate(): void { + if (!this._pendingNativeShapeCreation || !this._mesh || !this._positions) return; + this._createNativeShape(); + } + + /** + * @internal + * After CloneManager deep-copies `_positions` / `_indices` / `_mesh` and remaps `_collider`, + * the cloned shape still has no native PhysX shape because `_nativeShape` is `@ignoreClone`. + * Cook a fresh native shape now using the already-cloned vertex/index buffers; on transient + * cook failure `_createNativeShape` sets `_pendingNativeShapeCreation = true` and + * `_onPhysicsUpdate` will retry next tick. + */ + override _cloneTo(target: MeshColliderShape): void { + super._cloneTo(target); + if (target._positions) { + target._createNativeShape(); + } + } } diff --git a/packages/physics-physx/src/PhysXDynamicCollider.ts b/packages/physics-physx/src/PhysXDynamicCollider.ts index 31510ae2de..70800f5a21 100644 --- a/packages/physics-physx/src/PhysXDynamicCollider.ts +++ b/packages/physics-physx/src/PhysXDynamicCollider.ts @@ -24,6 +24,20 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli private static _tempTranslation = new Vector3(); private static _tempRotation = new Quaternion(); + /** + * Whether actor is currently kinematic. + * PhysX 拒绝在 kinematic actor 上启用 CCD(会打印警告并忽略), + * 所以 setCollisionDetectionMode 在 kinematic 状态下只缓存目标值, + * 等切回 dynamic 时再真正写到 PhysX。 + */ + private _isKinematic: boolean = false; + + /** + * Cached collision detection mode. Always reflects user's intent. + * 实际 PhysX CCD flag 可能跟这个不一致(kinematic 时强制 Discrete)。 + */ + private _collisionDetectionMode: number = CollisionDetectionMode.Discrete; + constructor(physXPhysics: PhysXPhysics, position: Vector3, rotation: Quaternion) { super(physXPhysics); const transform = this._transform(position, rotation); @@ -158,10 +172,52 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli /** * {@inheritDoc IDynamicCollider.setCollisionDetectionMode } + * + * PhysX 在 kinematic actor 上调用 setRigidBodyFlag(eENABLE_CCD, true) 会触发警告: + * "kinematic bodies with CCD enabled are not supported! CCD will be ignored" + * 虽然 PhysX 会忽略这次调用而非真的拒绝(切回 dynamic 时 flag 不会自动恢复), + * 但每次 setIsKinematic 切换都会让这个 warning 重复打印,污染日志, + * 同时让 actor 在 dynamic 状态下 CCD flag 状态不确定。 + * + * 解决: 只在 dynamic 状态时立即 apply CCD flags。kinematic 时仅缓存到 + * `_collisionDetectionMode`,等切回 dynamic 时由 setIsKinematic 重新 apply。 */ setCollisionDetectionMode(value: number): void { + this._collisionDetectionMode = value; + if (!this._isKinematic) { + this._applyCollisionDetectionFlags(value); + } + } + + /** + * {@inheritDoc IDynamicCollider.setUseGravity } + */ + setUseGravity(value: boolean): void { + this._pxActor.setActorFlag(this._physXPhysics._physX.PxActorFlag.eDISABLE_GRAVITY, !value); + } + + /** + * {@inheritDoc IDynamicCollider.setIsKinematic } + * + * 切换 kinematic 状态时同步处理 CCD flag: + * - 切到 kinematic 前先关 CCD(避免 PhysX 警告 + 让状态显式) + * - 切回 dynamic 后恢复用户期望的 CCD mode(来自 `_collisionDetectionMode` 缓存) + */ + setIsKinematic(value: boolean): void { + if (this._isKinematic === value) return; const physX = this._physXPhysics._physX; + if (value) { + this._applyCollisionDetectionFlags(CollisionDetectionMode.Discrete); + this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eKINEMATIC, true); + } else { + this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eKINEMATIC, false); + this._applyCollisionDetectionFlags(this._collisionDetectionMode); + } + this._isKinematic = value; + } + private _applyCollisionDetectionFlags(value: number): void { + const physX = this._physXPhysics._physX; switch (value) { case CollisionDetectionMode.Continuous: this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD, true); @@ -186,24 +242,6 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli } } - /** - * {@inheritDoc IDynamicCollider.setUseGravity } - */ - setUseGravity(value: boolean): void { - this._pxActor.setActorFlag(this._physXPhysics._physX.PxActorFlag.eDISABLE_GRAVITY, !value); - } - - /** - * {@inheritDoc IDynamicCollider.setIsKinematic } - */ - setIsKinematic(value: boolean): void { - if (value) { - this._pxActor.setRigidBodyFlag(this._physXPhysics._physX.PxRigidBodyFlag.eKINEMATIC, true); - } else { - this._pxActor.setRigidBodyFlag(this._physXPhysics._physX.PxRigidBodyFlag.eKINEMATIC, false); - } - } - /** * {@inheritDoc IDynamicCollider.setConstraints } */ @@ -213,8 +251,16 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli /** * {@inheritDoc IDynamicCollider.addForce } + * + * PhysX wasm wrapper 的 addForce 没传 autowake 参数,sleeping actor 上调用 + * 会被静默忽略(force 永远不生效)。这里显式 wakeUp 保证 force 真正生效。 + * + * kinematic actor 不响应 force 且 wakeUp 在 kinematic 上调用会触发 PhysX 警告, + * 提前 return 双重避免。 */ addForce(force: Vector3) { + if (this._isKinematic) return; + this._pxActor.wakeUp(); this._pxActor.addForce({ x: force.x, y: force.y, z: force.z }); } @@ -222,6 +268,8 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli * {@inheritDoc IDynamicCollider.addTorque } */ addTorque(torque: Vector3) { + if (this._isKinematic) return; + this._pxActor.wakeUp(); this._pxActor.addTorque({ x: torque.x, y: torque.y, z: torque.z }); } diff --git a/tests/src/core/physics/PhysicsMaterial.test.ts b/tests/src/core/physics/PhysicsMaterial.test.ts index 7433197ade..7bfd0e0e88 100644 --- a/tests/src/core/physics/PhysicsMaterial.test.ts +++ b/tests/src/core/physics/PhysicsMaterial.test.ts @@ -79,6 +79,56 @@ describe("PhysicsMaterial", () => { expect(formatValue(boxEntity2.transform.position.y)).eq(0); }); + it("cloned collider shape material keeps native values", () => { + const scene = engine.sceneManager.activeScene; + const originalGravity = scene.physics.gravity.clone(); + const originalFixedTimeStep = scene.physics.fixedTimeStep; + scene.physics.gravity = new Vector3(0, 0, 0); + scene.physics.fixedTimeStep = 1 / 60; + + try { + const wallEntity = addBox(new Vector3(1, 8, 8), StaticCollider, new Vector3(0, 0, 0)); + const wallMaterial = wallEntity.getComponent(StaticCollider).shapes[0].material; + wallMaterial.bounciness = 1; + wallMaterial.dynamicFriction = 0; + wallMaterial.staticFriction = 0; + wallMaterial.bounceCombine = PhysicsMaterialCombineMode.Multiply; + wallMaterial.frictionCombine = PhysicsMaterialCombineMode.Multiply; + + const sourceEntity = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(-3, 0, 0)); + const sourceCollider = sourceEntity.getComponent(DynamicCollider); + sourceCollider.linearDamping = 0; + sourceCollider.angularDamping = 0; + sourceCollider.automaticCenterOfMass = false; + sourceCollider.automaticInertiaTensor = false; + + const sourceMaterial = sourceCollider.shapes[0].material; + sourceMaterial.bounciness = 1; + sourceMaterial.dynamicFriction = 0; + sourceMaterial.staticFriction = 0; + sourceMaterial.bounceCombine = PhysicsMaterialCombineMode.Multiply; + sourceMaterial.frictionCombine = PhysicsMaterialCombineMode.Multiply; + + const cloneEntity = sourceEntity.clone(); + sourceEntity.destroy(); + rootEntity.addChild(cloneEntity); + cloneEntity.transform.setPosition(-3, 0, 0); + + const cloneCollider = cloneEntity.getComponent(DynamicCollider); + cloneCollider.linearVelocity = new Vector3(10, 0, 0); + + for (let i = 0; i < 40; i++) { + // @ts-ignore + scene.physics._update(scene.physics.fixedTimeStep); + } + + expect(cloneCollider.linearVelocity.x).lessThan(-1); + } finally { + scene.physics.gravity = originalGravity; + scene.physics.fixedTimeStep = originalFixedTimeStep; + } + }); + it("bounceCombine Average", () => { const boxEntity = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(0, 5, 0)); const ground = addPlane(0, -0.5, 0); From c96cefc2c2182d3034fda016bd5657db1f9e0573 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Thu, 14 May 2026 20:06:33 +0800 Subject: [PATCH 082/130] feat(physics): add DynamicCollider.applyForceAtPosition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Unity-style applyForceAtPosition(force, worldPosition) to apply a force at an arbitrary world-space point, producing both linear acceleration through the center of mass and angular acceleration about it. Internally decomposes into the textbook (F, τ = r × F) pair: worldCoM = entity.worldPos + entity.worldRot · localCoM (from native) r = worldPosition - worldCoM addForce(F); addTorque(r × F) Equivalent to Cocos `RigidBody.applyForce(force, relativePoint)` (Bullet's implementation is the same cross product); enables direct migration of billiards-style "hit at an offset point" gameplay without manual r × F math. Pure TS-layer composition over existing addForce/addTorque — no design interface change, no native binding change (physX.js untouched). Tests (6 cases, orthogonal coverage of all worldCoM computation terms): 1. force at CoM → only linear acceleration 2. r ≠ 0 produces torque = r × F, validated via differential vs applyTorque(τ) 3. CoM offset + force at world CoM → r = 0 → no torque 4. Entity rotated 90° + CoM offset → worldRot correctly transforms localCoM 5. (position + rotation + CoM offset + r ≠ 0) — full stress test catching any missing term in worldCoM = pos + rot·localCoM 6. After entity.clone() — defends prefab instantiation path (R6/R8 territory) --- packages/core/src/physics/DynamicCollider.ts | 29 ++++ .../src/core/physics/DynamicCollider.test.ts | 157 ++++++++++++++++++ 2 files changed, 186 insertions(+) diff --git a/packages/core/src/physics/DynamicCollider.ts b/packages/core/src/physics/DynamicCollider.ts index 380a3b927f..d78fd98121 100644 --- a/packages/core/src/physics/DynamicCollider.ts +++ b/packages/core/src/physics/DynamicCollider.ts @@ -13,6 +13,8 @@ import { MeshColliderShape } from "./shape/MeshColliderShape"; */ export class DynamicCollider extends Collider { private static _tempVector3 = new Vector3(); + private static _tempVector3_1 = new Vector3(); + private static _tempVector3_2 = new Vector3(); private static _tempQuat = new Quaternion(); private _linearDamping = 0; @@ -367,6 +369,33 @@ export class DynamicCollider extends Collider { this._phasedActiveInScene && (this._nativeCollider).addTorque(torque); } + /** + * Apply a force to the DynamicCollider at a given position in world space. + * The force generates both linear acceleration through the center of mass and angular + * acceleration about it (torque = (position - centerOfMass) × force). + * @param force - The force to apply, in world space + * @param position - The position where the force is applied, in world space + */ + applyForceAtPosition(force: Vector3, position: Vector3): void { + if (!this._phasedActiveInScene) return; + const nativeCollider = this._nativeCollider; + + const localCoM = DynamicCollider._tempVector3; + nativeCollider.getCenterOfMass(localCoM); + + const transform = this.entity.transform; + const worldCoM = DynamicCollider._tempVector3_1; + Vector3.transformByQuat(localCoM, transform.worldRotationQuaternion, worldCoM); + worldCoM.add(transform.worldPosition); + + const torque = DynamicCollider._tempVector3_2; + Vector3.subtract(position, worldCoM, torque); + Vector3.cross(torque, force, torque); + + nativeCollider.addForce(force); + nativeCollider.addTorque(torque); + } + /** * Moves the kinematic collider to the specified position. * @remarks Only available when {@link isKinematic} is true. diff --git a/tests/src/core/physics/DynamicCollider.test.ts b/tests/src/core/physics/DynamicCollider.test.ts index ae6771d05a..eae4d964c8 100644 --- a/tests/src/core/physics/DynamicCollider.test.ts +++ b/tests/src/core/physics/DynamicCollider.test.ts @@ -278,6 +278,163 @@ describe("DynamicCollider", function () { expect(formatValue(boxCollider.inertiaTensor.y)).eq(1); }); + it("applyForceAtPosition - at center of mass produces only linear acceleration", function () { + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.mass = 1; + boxCollider.useGravity = false; + boxCollider.centerOfMass = new Vector3(0, 0, 0); + boxCollider.inertiaTensor = new Vector3(1, 1, 1); + + boxCollider.applyForceAtPosition(new Vector3(1, 0, 0), new Vector3(0, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(boxCollider.linearVelocity.x)).eq(0.01667); + expect(formatValue(boxCollider.angularVelocity.x)).eq(0); + expect(formatValue(boxCollider.angularVelocity.y)).eq(0); + expect(formatValue(boxCollider.angularVelocity.z)).eq(0); + }); + + it("applyForceAtPosition - offset produces torque = (position - CoM) × force", function () { + // Reference: same physical setup but using applyForce + applyTorque(τ) directly. + // applyForceAtPosition(F, P) must produce the same result. + const setupBox = () => { + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const collider = box.getComponent(DynamicCollider); + collider.mass = 1; + collider.useGravity = false; + collider.centerOfMass = new Vector3(0, 0, 0); + collider.inertiaTensor = new Vector3(1, 1, 1); + return collider; + }; + + const force = new Vector3(1, 0, 0); + const worldPos = new Vector3(0, 1, 0); // r = (0,1,0) - (0,0,0) = (0,1,0); r × F = (0,0,-1) + + const reference = setupBox(); + reference.applyForce(force); + reference.applyTorque(new Vector3(0, 0, -1)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + const refLinear = reference.linearVelocity.clone(); + const refAngular = reference.angularVelocity.clone(); + + rootEntity.clearChildren(); + + const target = setupBox(); + target.applyForceAtPosition(force, worldPos); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(target.linearVelocity.x)).eq(formatValue(refLinear.x)); + expect(formatValue(target.linearVelocity.y)).eq(formatValue(refLinear.y)); + expect(formatValue(target.linearVelocity.z)).eq(formatValue(refLinear.z)); + expect(formatValue(target.angularVelocity.x)).eq(formatValue(refAngular.x)); + expect(formatValue(target.angularVelocity.y)).eq(formatValue(refAngular.y)); + expect(formatValue(target.angularVelocity.z)).eq(formatValue(refAngular.z)); + expect(formatValue(target.angularVelocity.z)).lessThan(0); + }); + + it("applyForceAtPosition - respects centerOfMass offset (no torque when applied at CoM)", function () { + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.mass = 1; + boxCollider.useGravity = false; + // Shift CoM to local (1, 0, 0). With entity at origin and identity rotation, world CoM = (1, 0, 0). + boxCollider.centerOfMass = new Vector3(1, 0, 0); + boxCollider.inertiaTensor = new Vector3(1, 1, 1); + + // Applying force at world (1,0,0) means r = 0 → no torque. + boxCollider.applyForceAtPosition(new Vector3(0, 0, 1), new Vector3(1, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(boxCollider.linearVelocity.z)).eq(0.01667); + expect(formatValue(boxCollider.angularVelocity.x)).eq(0); + expect(formatValue(boxCollider.angularVelocity.y)).eq(0); + expect(formatValue(boxCollider.angularVelocity.z)).eq(0); + }); + + it("applyForceAtPosition - respects entity world rotation when transforming local CoM", function () { + // entity rotated 90° around Y, CoM local (1, 0, 0) → world CoM offset (0, 0, -1) + // Apply force at world (0,0,-1), r=0, expect no torque. + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + box.transform.rotate(new Vector3(0, 90, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.mass = 1; + boxCollider.useGravity = false; + boxCollider.centerOfMass = new Vector3(1, 0, 0); + boxCollider.inertiaTensor = new Vector3(1, 1, 1); + + boxCollider.applyForceAtPosition(new Vector3(1, 0, 0), new Vector3(0, 0, -1)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(boxCollider.linearVelocity.x)).eq(0.01667); + expect(formatValue(boxCollider.angularVelocity.x)).eq(0); + expect(formatValue(boxCollider.angularVelocity.y)).eq(0); + expect(formatValue(boxCollider.angularVelocity.z)).eq(0); + }); + + it("applyForceAtPosition - position + rotation + CoM offset + r != 0 (full worldCoM coverage)", function () { + // Stress-test all three terms of worldCoM = entity.worldPos + worldRot * localCoM: + // entity position (5, 0, 0) — exercises the translation term + // entity rotation 90° around Y — exercises the rotation term (localCoM (1,0,0) → world (0,0,-1)) + // CoM local (1, 0, 0) — exercises the local CoM lookup + // worldCoM = (5,0,0) + (0,0,-1) = (5, 0, -1) + // force F = (1, 0, 0) at P = (5, 1, -1) + // r = P - worldCoM = (0, 1, 0) + // τ = r × F = (0, 0, -1) + // If any single term in worldCoM is wrong (missing translate / wrong quat / wrong localCoM), + // r becomes non-(0,1,0) and τ has spurious x/y components → catches the bug. + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(5, 0, 0)); + box.transform.rotate(new Vector3(0, 90, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.mass = 1; + boxCollider.useGravity = false; + boxCollider.centerOfMass = new Vector3(1, 0, 0); + boxCollider.inertiaTensor = new Vector3(1, 1, 1); + + boxCollider.applyForceAtPosition(new Vector3(1, 0, 0), new Vector3(5, 1, -1)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(boxCollider.linearVelocity.x)).eq(0.01667); + expect(formatValue(boxCollider.angularVelocity.x)).eq(0); + expect(formatValue(boxCollider.angularVelocity.y)).eq(0); + expect(formatValue(boxCollider.angularVelocity.z)).lessThan(0); + }); + + it("applyForceAtPosition - works after entity.clone() (defends prefab clone path)", function () { + // R6/R8 both surfaced because clone() bypasses setters and leaves native state inconsistent. + // applyForceAtPosition reads native getCenterOfMass + entity.transform.worldRotationQuaternion. + // If a future change adds @ignoreClone fields or relies on setter side-effects, this test + // will catch the regression by exercising the API on a cloned collider. + const source = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const sourceCollider = source.getComponent(DynamicCollider); + sourceCollider.mass = 1; + sourceCollider.useGravity = false; + sourceCollider.centerOfMass = new Vector3(0, 0, 0); + sourceCollider.inertiaTensor = new Vector3(1, 1, 1); + + const cloneEntity = source.clone(); + source.destroy(); + rootEntity.addChild(cloneEntity); + cloneEntity.transform.setPosition(0, 0, 0); + + const cloneCollider = cloneEntity.getComponent(DynamicCollider); + // r = (0,1,0), F = (1,0,0), τ = (0,0,-1) → expect negative angular z + cloneCollider.applyForceAtPosition(new Vector3(1, 0, 0), new Vector3(0, 1, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(cloneCollider.linearVelocity.x)).eq(0.01667); + expect(formatValue(cloneCollider.angularVelocity.z)).lessThan(0); + expect(formatValue(cloneCollider.angularVelocity.x)).eq(0); + expect(formatValue(cloneCollider.angularVelocity.y)).eq(0); + }); + it("maxAngularVelocity", function () { const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); const boxCollider = box.getComponent(DynamicCollider); From 9c32650c07478494aec0bdaa8e0947c9eaca7e53 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Thu, 14 May 2026 20:25:51 +0800 Subject: [PATCH 083/130] fix(physics): remove redundant wakeUp in addForce/addTorque + correct root cause MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous commit 273d18151 claimed "PhysX wasm wrapper's addForce doesn't pass the autowake parameter, so calls on sleeping actors are silently ignored". This claim is incorrect — the wasm binding explicitly passes autowake=true: body.addForce(force, PxForceMode::eFORCE, true); PhysX 4.1.1 doc confirms: "If true, this call wakes up the actor if it is sleeping." Two new tests verify autowake works as documented in wasm: - "applyForce on sleeping actor must wake up and apply force" → sleep() → applyForce (no explicit wakeUp) → force is applied, actor wakes - "applyForce after kinematic→dynamic switch (mimic billiards game break flow)" → reproduces the exact game scenario where 'force seemed lost' originally Both pass without the explicit wakeUp() call. The original 'applyForce lost' symptom in the billiards game was actually caused by other concurrent bugs (R1 collisionMatrix, R8 PhysicsMaterial, fixedTimeStep mismatch) that were each masking the real cause, not by sleeping-actor behavior. Keep the `_isKinematic` guard: PhysX makes addForce a no-op on kinematic actors (documented), but the guard avoids the wasm boundary cross. --- .../physics-physx/src/PhysXDynamicCollider.ts | 12 +++--- .../src/core/physics/DynamicCollider.test.ts | 41 +++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/packages/physics-physx/src/PhysXDynamicCollider.ts b/packages/physics-physx/src/PhysXDynamicCollider.ts index 70800f5a21..a972712d84 100644 --- a/packages/physics-physx/src/PhysXDynamicCollider.ts +++ b/packages/physics-physx/src/PhysXDynamicCollider.ts @@ -252,24 +252,24 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli /** * {@inheritDoc IDynamicCollider.addForce } * - * PhysX wasm wrapper 的 addForce 没传 autowake 参数,sleeping actor 上调用 - * 会被静默忽略(force 永远不生效)。这里显式 wakeUp 保证 force 真正生效。 + * PhysX 在 kinematic actor 上调 addForce 是 no-op(doc: "kinematic bodies don't + * respond to forces")。提前 return 避免无意义的 wasm boundary cross。 * - * kinematic actor 不响应 force 且 wakeUp 在 kinematic 上调用会触发 PhysX 警告, - * 提前 return 双重避免。 + * Sleeping actor 不需要显式 wakeUp — wasm binding 调用 `addForce(force, eFORCE, + * autowake=true)`,PhysX 自动唤醒(已通过 `applyForce on sleeping actor` 测试验证)。 */ addForce(force: Vector3) { if (this._isKinematic) return; - this._pxActor.wakeUp(); this._pxActor.addForce({ x: force.x, y: force.y, z: force.z }); } /** * {@inheritDoc IDynamicCollider.addTorque } + * + * 同 addForce — kinematic 提前 return,sleeping 由 PhysX autowake 自动处理。 */ addTorque(torque: Vector3) { if (this._isKinematic) return; - this._pxActor.wakeUp(); this._pxActor.addTorque({ x: torque.x, y: torque.y, z: torque.z }); } diff --git a/tests/src/core/physics/DynamicCollider.test.ts b/tests/src/core/physics/DynamicCollider.test.ts index eae4d964c8..37fafd3598 100644 --- a/tests/src/core/physics/DynamicCollider.test.ts +++ b/tests/src/core/physics/DynamicCollider.test.ts @@ -435,6 +435,47 @@ describe("DynamicCollider", function () { expect(formatValue(cloneCollider.angularVelocity.y)).eq(0); }); + it("applyForce on sleeping actor must wake up and apply force", function () { + // Validates whether PhysX wasm `addForce(force, eFORCE, autowake=true)` actually wakes a + // sleeping actor on its own — or whether the engine's explicit wakeUp() call is required. + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.mass = 1; + boxCollider.useGravity = false; + boxCollider.linearDamping = 0; + + boxCollider.sleep(); + expect(boxCollider.isSleeping()).toBe(true); + + boxCollider.applyForce(new Vector3(1, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + expect(formatValue(boxCollider.linearVelocity.x)).eq(0.01667); + expect(boxCollider.isSleeping()).toBe(false); + }); + + it("applyForce after kinematic→dynamic switch (mimic billiards game break flow)", function () { + // Game pattern: all balls set kinematic at init, switched back to dynamic on break, + // then applyForce. Verifies the original 'force lost' bug was actually from this path. + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.mass = 1; + boxCollider.useGravity = false; + boxCollider.linearDamping = 0; + + boxCollider.isKinematic = true; + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + boxCollider.isKinematic = false; + + boxCollider.applyForce(new Vector3(1, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + expect(formatValue(boxCollider.linearVelocity.x)).eq(0.01667); + }); + it("maxAngularVelocity", function () { const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); const boxCollider = box.getComponent(DynamicCollider); From e1c0eb5b808983074b2e7a8921e48ba1f50b53b7 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Thu, 14 May 2026 20:54:41 +0800 Subject: [PATCH 084/130] fix(physics): repair 8 pre-existing test failures (Lite material + PhysX scene queries) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These tests were red on dev/2.0 before any of the recent physics work landed. Diagnosed and fixed at the root, no test-skipping or assertion weakening. **LitePhysicsMaterial — 1 failure** `ColliderShape Lite > clone` broke after R8 added `PhysicsMaterial._cloneTo → _syncNative()` which now calls every native setter on a freshly-cloned material. Lite's setters threw "Physics-lite don't support physics material", violating the no-op-with-log convention already used by `LiteColliderShape.setMaterial / setIsTrigger / setContactOffset`. Change all 5 setters to silent no-op + a one-time `console.log` warning, matching the existing convention. **PhysXPhysicsScene — 7 failures (raycast/boxCast/sphereCast/capsuleCast + overlapBoxAll/overlapSphereAll/overlapCapsuleAll)** Two underlying problems both surface in the JS filter-callback layer: 1. `_pxFilterData` had `POST_FILTER` flag enabled but `_overlapMultiple` reused it with a callback object that only defined `preFilter`. When PhysX tried to invoke `postFilter`, the wasm boundary errored with `Emval.toValue(...)[getStringOrSymbol(...)] is not a function`. 2. The 4 cast tests asserted the old "origin-inside-collider returns true with distance=0" behavior. The wasm side was already switched to the Unity-style "skip initial overlap" design (commit bdf54903b / PR #2998 on dev branch ahead of us), but the tests still encoded the rejected behavior. Mirror PR #2998's JS-side fix already merged on dev: - Split filter data into `_pxFilterData` (PRE only, for overlap) and `_pxRaycastSweepFilterData` (PRE + POST, for raycast/sweep). - Replace per-call `PxQueryFilterCallback.implement` with a single persistent `_pxQueryCallback` defining both `preFilter` and `postFilter`. User callbacks dispatch via a `_currentOnQuery` slot saved/restored around each native call for reentrancy safety. - Cache the `PxSweepHit` instance instead of creating per-call. - Add `QueryHitType` enum. Update 4 stale test assertions in PhysicsScene.test.ts to match the already-shipped "skip initial overlap" wasm contract. Use strictly-inside origin (2.9,2.9,2.9) for the raycast case to avoid the corner-tolerance flake that PR \#2998 already addressed. Verification: 11 physics test files, 213/213 passing locally. --- .../physics-lite/src/LitePhysicsMaterial.ts | 24 ++- .../physics-physx/src/PhysXPhysicsScene.ts | 151 +++++++++--------- tests/src/core/physics/PhysicsScene.test.ts | 35 ++-- 3 files changed, 113 insertions(+), 97 deletions(-) diff --git a/packages/physics-lite/src/LitePhysicsMaterial.ts b/packages/physics-lite/src/LitePhysicsMaterial.ts index 7983beb394..de1a0dee53 100644 --- a/packages/physics-lite/src/LitePhysicsMaterial.ts +++ b/packages/physics-lite/src/LitePhysicsMaterial.ts @@ -2,8 +2,16 @@ import { IPhysicsMaterial } from "@galacean/engine-design"; /** * Physics material describes how to handle colliding objects (friction, bounciness). + * + * Physics-lite does not implement material effects; setters are no-ops (matching + * the convention used by `LiteColliderShape.setMaterial/setContactOffset/setIsTrigger`). + * This lets engine-side state changes (including clone `_syncNative` re-writes) flow + * through without crashing while still leaving a one-time hint via console.log on + * the first write so users notice the limitation. */ export class LitePhysicsMaterial implements IPhysicsMaterial { + private static _warned = false; + constructor( staticFriction: number, dynamicFriction: number, @@ -16,39 +24,45 @@ export class LitePhysicsMaterial implements IPhysicsMaterial { * {@inheritDoc IPhysicsMaterial.setBounciness } */ setBounciness(value: number): void { - throw "Physics-lite don't support physics material. Use Physics-PhysX instead!"; + LitePhysicsMaterial._warnOnce(); } /** * {@inheritDoc IPhysicsMaterial.setDynamicFriction } */ setDynamicFriction(value: number): void { - throw "Physics-lite don't support physics material. Use Physics-PhysX instead!"; + LitePhysicsMaterial._warnOnce(); } /** * {@inheritDoc IPhysicsMaterial.setStaticFriction } */ setStaticFriction(value: number): void { - throw "Physics-lite don't support physics material. Use Physics-PhysX instead!"; + LitePhysicsMaterial._warnOnce(); } /** * {@inheritDoc IPhysicsMaterial.setBounceCombine } */ setBounceCombine(value: number): void { - throw "Physics-lite don't support physics material. Use Physics-PhysX instead!"; + LitePhysicsMaterial._warnOnce(); } /** * {@inheritDoc IPhysicsMaterial.setFrictionCombine } */ setFrictionCombine(value: number): void { - throw "Physics-lite don't support physics material. Use Physics-PhysX instead!"; + LitePhysicsMaterial._warnOnce(); } /** * {@inheritDoc IPhysicsMaterial.destroy } */ destroy(): void {} + + private static _warnOnce(): void { + if (LitePhysicsMaterial._warned) return; + LitePhysicsMaterial._warned = true; + console.log("Physics-lite don't support physics material. Use Physics-PhysX instead!"); + } } diff --git a/packages/physics-physx/src/PhysXPhysicsScene.ts b/packages/physics-physx/src/PhysXPhysicsScene.ts index b42c51c8a8..d026304e4d 100644 --- a/packages/physics-physx/src/PhysXPhysicsScene.ts +++ b/packages/physics-physx/src/PhysXPhysicsScene.ts @@ -29,11 +29,24 @@ export class PhysXPhysicsScene implements IPhysicsScene { private _physXPhysics: PhysXPhysics; private _physXManager: PhysXPhysicsManager; private _pxRaycastHit: any; + private _pxSweepHit: any; private _pxFilterData: any; + private _pxRaycastSweepFilterData: any; private _pxScene: any; private _physXSimulationCallbackInstance: any; + // A single persistent PhysX query filter callback is shared by raycast, + // sweep and overlap. PhysX SDK guarantees that `postFilter` is only invoked + // when `PxQueryFlag::ePOSTFILTER` is set on the query's filter data, so + // overlap (whose filter data omits POST_FILTER) safely uses the same + // callback that also handles the raycast/sweep initial-overlap skip. + // The user-supplied predicate is stored in `_currentOnQuery`; reentrant + // calls save the previous value on the call stack via a local in each + // query method, recreating C++-style RAII without an explicit stack array. + private _pxQueryCallback: any; + private _currentOnQuery: (obj: number) => boolean = null; + private _activeTriggers: DisorderedArray = new DisorderedArray(); private _contactEvents: ContactEvent[] = []; private _contactEventCount = 0; @@ -49,8 +62,11 @@ export class PhysXPhysicsScene implements IPhysicsScene { const physX = physXPhysics._physX; this._pxRaycastHit = new physX.PxRaycastHit(); + this._pxSweepHit = new physX.PxSweepHit(); this._pxFilterData = new physX.PxQueryFilterData(); - this._pxFilterData.flags = new physX.PxQueryFlags( + this._pxFilterData.flags = new physX.PxQueryFlags(QueryFlag.STATIC | QueryFlag.DYNAMIC | QueryFlag.PRE_FILTER); + this._pxRaycastSweepFilterData = new physX.PxQueryFilterData(); + this._pxRaycastSweepFilterData.flags = new physX.PxQueryFlags( QueryFlag.STATIC | QueryFlag.DYNAMIC | QueryFlag.PRE_FILTER | QueryFlag.POST_FILTER ); @@ -93,6 +109,14 @@ export class PhysXPhysicsScene implements IPhysicsScene { ); this._pxScene = pxPhysics.createScene(sceneDesc); sceneDesc.delete(); + + this._pxQueryCallback = physX.PxQueryFilterCallback.implement({ + preFilter: (_filterData: any, index: number, _actor: any) => + this._currentOnQuery(index) ? QueryHitType.BLOCK : QueryHitType.NONE, + // distance <= 0 means initial overlap — drop the hit so subsequent hits can be considered. + // Only invoked when the query's filter data includes POST_FILTER (raycast/sweep, not overlap). + postFilter: (_filterData: any, distance: number) => (distance <= 0 ? QueryHitType.NONE : QueryHitType.BLOCK) + }); } /** @@ -209,33 +233,21 @@ export class PhysXPhysicsScene implements IPhysicsScene { const { _pxRaycastHit: pxHitResult } = this; distance = Math.min(distance, 3.4e38); // float32 max value limit in physX raycast. - const raycastCallback = { - preFilter: (filterData, index, actor) => { - if (onRaycast(index)) { - return 2; // eBLOCK - } else { - return 0; // eNONE - } - }, - postFilter: (filterData, distance) => { - if (distance <= 0) { - return 0; // eNONE — skip initial overlap - } - return 2; // eBLOCK - } - }; - - const pxRaycastCallback = this._physXPhysics._physX.PxQueryFilterCallback.implement(raycastCallback); - const result = this._pxScene.raycastSingle( - ray.origin, - ray.direction, - distance, - pxHitResult, - this._pxFilterData, - pxRaycastCallback - ); - - pxRaycastCallback.delete(); + const prevOnQuery = this._currentOnQuery; + this._currentOnQuery = onRaycast; + let result: boolean; + try { + result = this._pxScene.raycastSingle( + ray.origin, + ray.direction, + distance, + pxHitResult, + this._pxRaycastSweepFilterData, + this._pxQueryCallback + ); + } finally { + this._currentOnQuery = prevOnQuery; + } if (result && hit != undefined) { const { _tempPosition: position, _tempNormal: normal } = PhysXPhysicsScene; @@ -398,8 +410,12 @@ export class PhysXPhysicsScene implements IPhysicsScene { this._physXSimulationCallbackInstance.delete(); this._pxRaycastHit.delete(); + this._pxSweepHit.delete(); this._pxFilterData.flags.delete(); this._pxFilterData.delete(); + this._pxRaycastSweepFilterData.flags.delete(); + this._pxRaycastSweepFilterData.delete(); + this._pxQueryCallback.delete(); // Need to release the controller manager before release the scene. this._pxControllerManager?.release(); this._pxScene.release(); @@ -451,35 +467,25 @@ export class PhysXPhysicsScene implements IPhysicsScene { onSweep: (obj: number) => boolean, outHitResult?: (shapeUniqueID: number, distance: number, position: Vector3, normal: Vector3) => void ): boolean { + const { _pxSweepHit: pxSweepHit } = this; distance = Math.min(distance, 3.4e38); // float32 max value limit in physx sweep - const sweepCallback = { - preFilter: (filterData, index, actor) => { - if (onSweep(index)) { - return 2; // eBLOCK - } else { - return 0; // eNONE - } - }, - postFilter: (filterData, distance) => { - if (distance <= 0) { - return 0; // eNONE — skip initial overlap - } - return 2; // eBLOCK - } - }; - - const pxSweepCallback = this._physXPhysics._physX.PxQueryFilterCallback.implement(sweepCallback); - const pxSweepHit = new this._physXPhysics._physX.PxSweepHit(); - const result = this._pxScene.sweepSingle( - geometry, - pose, - direction, - distance, - pxSweepHit, - this._pxFilterData, - pxSweepCallback - ); + const prevOnQuery = this._currentOnQuery; + this._currentOnQuery = onSweep; + let result: boolean; + try { + result = this._pxScene.sweepSingle( + geometry, + pose, + direction, + distance, + pxSweepHit, + this._pxRaycastSweepFilterData, + this._pxQueryCallback + ); + } finally { + this._currentOnQuery = prevOnQuery; + } if (result && outHitResult != undefined) { const { _tempPosition: position, _tempNormal: normal } = PhysXPhysicsScene; @@ -488,10 +494,6 @@ export class PhysXPhysicsScene implements IPhysicsScene { normal.set(pxNormal.x, pxNormal.y, pxNormal.z); outHitResult(pxSweepHit.getShape().getUUID(), pxSweepHit.distance, position, normal); } - - pxSweepCallback.delete(); - pxSweepHit.delete(); - return result; } @@ -500,19 +502,15 @@ export class PhysXPhysicsScene implements IPhysicsScene { pose: { translation: Vector3; rotation: Quaternion }, onOverlap: (obj: number) => boolean ): number[] { - const overlapCallback = { - preFilter: (filterData, index, actor) => (onOverlap(index) ? 2 : 0) - }; - - const pxOverlapCallback = this._physXPhysics._physX.PxQueryFilterCallback.implement(overlapCallback); + const prevOnQuery = this._currentOnQuery; + this._currentOnQuery = onOverlap; const maxHits = 256; - const hits: any = (this._pxScene as any).overlapMultiple( - geometry, - pose, - maxHits, - this._pxFilterData, - pxOverlapCallback - ); + let hits: any; + try { + hits = (this._pxScene as any).overlapMultiple(geometry, pose, maxHits, this._pxFilterData, this._pxQueryCallback); + } finally { + this._currentOnQuery = prevOnQuery; + } const result = PhysXPhysicsScene._tempShapeIDs; result.length = 0; @@ -523,7 +521,6 @@ export class PhysXPhysicsScene implements IPhysicsScene { } } - pxOverlapCallback.delete(); hits?.delete(); return result; } @@ -584,6 +581,16 @@ enum QueryFlag { NO_BLOCK = 1 << 5 } +/** + * Result returned from a PhysX query filter callback (mirrors `PxQueryHitType`). + */ +enum QueryHitType { + /** Filter the hit out (no further processing). */ + NONE = 0, + /** Treat the hit as a blocking hit (terminates query for single-hit modes). */ + BLOCK = 2 +} + enum PhysicsEventState { Enter = 0, Stay = 1, diff --git a/tests/src/core/physics/PhysicsScene.test.ts b/tests/src/core/physics/PhysicsScene.test.ts index fb28579400..c50102a0d8 100644 --- a/tests/src/core/physics/PhysicsScene.test.ts +++ b/tests/src/core/physics/PhysicsScene.test.ts @@ -12,8 +12,7 @@ import { Scene, Script, SphereColliderShape, - StaticCollider, - OverlapHitResult + StaticCollider } from "@galacean/engine-core"; import { Ray, Vector3, Quaternion } from "@galacean/engine-math"; import { LitePhysics } from "@galacean/engine-physics-lite"; @@ -469,16 +468,15 @@ describe("Physics Test", () => { expect(outHitResult.normal).to.be.deep.include({ x: 0, y: 0, z: 0 }); expect(outHitResult.entity).to.be.null; - // Test that return origin point if origin is inside collider. + // Test that initial overlap is skipped when ray origin is inside collider. + // Use a strictly-inside origin (2.9,2.9,2.9) rather than the box corner + // (3,3,3), which is a boundary point whose hit/miss depends on PhysX edge + // tolerance and can flake regardless of the initial-overlap-skip behavior. boxShape.size = new Vector3(6, 6, 6); - ray = new Ray(new Vector3(3, 3, 3), new Vector3(0, -1, 0).normalize()); - expect(physicsScene.raycast(ray, outHitResult)).to.eq(true); + ray = new Ray(new Vector3(2.9, 2.9, 2.9), new Vector3(0, -1, 0).normalize()); + expect(physicsScene.raycast(ray, outHitResult)).to.eq(false); expect(outHitResult.distance).to.be.eq(0); - expect(outHitResult.point).to.be.deep.include({ x: 3, y: 3, z: 3 }); - expect(outHitResult.normal.x).to.be.eq(0); - expect(outHitResult.normal.y).to.be.eq(1); - expect(outHitResult.normal.z).to.be.eq(0); - expect(outHitResult.entity).to.be.eq(raycastTestRoot); + expect(outHitResult.entity).to.be.null; // Test that raycast works correctly if shape is not at origin of coordinate. boxShape.size = new Vector3(1, 1, 1); @@ -555,7 +553,7 @@ describe("Physics Test", () => { const halfExtents = new Vector3(0.5, 0.5, 0.5); const direction = new Vector3(0, 1, 0); const orientation = new Quaternion(); - expect(physicsScene.boxCast(center, halfExtents, direction, orientation)).to.eq(false); + expect(physicsScene.boxCast(center, halfExtents, direction)).to.eq(false); // Test boxCast with hit direction.set(-1, -1, -1); @@ -585,7 +583,7 @@ describe("Physics Test", () => { physicsScene.boxCast(center, halfExtents, direction, orientation, 0.1, Layer.Everything, outHitResult) ).to.eq(false); - // Test boxCast when box is inside collider + // Test that initial overlap is skipped when sweep starts inside a collider. center.set(0, 0, 0); expect( physicsScene.boxCast( @@ -597,8 +595,7 @@ describe("Physics Test", () => { Layer.Everything, outHitResult ) - ).to.eq(true); - expect(outHitResult.distance).to.be.eq(0); + ).to.eq(false); // Test boxCast with rotation Quaternion.rotationEuler(0, Math.PI / 4, 0, orientation); @@ -729,12 +726,11 @@ describe("Physics Test", () => { // Test sphereCast with distance limit expect(physicsScene.sphereCast(center, radius, direction, 0.1, Layer.Everything, outHitResult)).to.eq(false); - // Test sphereCast when sphere is inside collider + // Test that initial overlap is skipped when sphere starts inside a collider. center.set(0, 0, 0); expect( physicsScene.sphereCast(center, radius, direction, Number.MAX_VALUE, Layer.Everything, outHitResult) - ).to.eq(true); - expect(outHitResult.distance).to.be.eq(0); + ).to.eq(false); // Test sphereCast with multiple colliders const collider2 = sweepTestRoot.addComponent(StaticCollider); @@ -856,7 +852,7 @@ describe("Physics Test", () => { physicsScene.capsuleCast(center, radius, height, direction, orientation, 0.1, Layer.Everything, outHitResult) ).to.eq(false); - // Test capsuleCast when capsule is inside collider + // Test that initial overlap is skipped when capsule starts inside a collider. center.set(0, 0, 0); expect( physicsScene.capsuleCast( @@ -869,8 +865,7 @@ describe("Physics Test", () => { Layer.Everything, outHitResult ) - ).to.eq(true); - expect(outHitResult.distance).to.be.eq(0); + ).to.eq(false); // Test capsuleCast with rotation Quaternion.rotationEuler(0, Math.PI / 4, 0, orientation); From f8ad6bf73befc20608f58c84905059734d2bd783 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Thu, 14 May 2026 20:55:31 +0800 Subject: [PATCH 085/130] test(physics): add regression tests for r0 ccd handling and r6 mesh clone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After fixing R0/R5/R6/R8 in commits 273d18151 and 9c32650c0, run a RED-GREEN verification on each: temporarily revert the fix, run the test, confirm it fails; restore the fix, confirm it passes. Outcome reshaped what we thought the root causes were. R6 (MeshColliderShape clone) — real bug fix, RED verified `R6: cloned MeshColliderShape rebuilds its native PhysX shape` — With `_cloneTo` override removed, `clonedShape._nativeShape` is null and the cloned ground entity is not physically present (sphere falls through). With the fix, sphere lands on the cloned ground (sphereY > -1). R0 (kinematic + CCD) — defensive, not a bug fix `R0: CCD mode survives kinematic toggle (PhysX rejects CCD on kinematic)` and `R0: setCollisionDetectionMode in kinematic state defers application` — Both pass even with the R0 fix reverted. PhysX 4.1.1 already manages the CCD↔kinematic flag relation correctly on its own. R0's real value is: 1. Suppressing the PhysX warning that fires when CCD-on actors switch to kinematic (or vice-versa). 2. Preserving user intent — `collisionDetectionMode` getter returns the last user-set mode even while temporarily kinematic; on dynamic restore, CCD is reapplied without the user having to remember. These tests are kept as contract tests, not red-green regression tests. R5's wakeUp call was already removed in 9c32650c0 after its red test showed PhysX wasm `addForce(force, eFORCE, autowake=true)` wakes sleeping actors on its own — no separate test needed here. Verification: full physics suite 216/216 passing. --- .../src/core/physics/DynamicCollider.test.ts | 46 +++++++++++ .../core/physics/MeshColliderShape.test.ts | 82 +++++++++++++------ 2 files changed, 105 insertions(+), 23 deletions(-) diff --git a/tests/src/core/physics/DynamicCollider.test.ts b/tests/src/core/physics/DynamicCollider.test.ts index 37fafd3598..3346c9469f 100644 --- a/tests/src/core/physics/DynamicCollider.test.ts +++ b/tests/src/core/physics/DynamicCollider.test.ts @@ -640,6 +640,52 @@ describe("DynamicCollider", function () { ).toBeTruthy(); }); + it("R0: CCD mode survives kinematic toggle (PhysX rejects CCD on kinematic)", function () { + // RED verification for R0 fix: + // PhysX 4.1.1 forbids CCD on kinematic actors. The fix caches the user-intended mode + // and re-applies on kinematic→dynamic. Without the fix, switching to kinematic loses + // the CCD flag and a subsequent dynamic switch does not restore it. + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + // @ts-ignore + const physX = boxCollider._nativeCollider._physXPhysics._physX; + const ccdFlag = () => + // @ts-ignore + boxCollider._nativeCollider._pxActor.getRigidBodyFlags(physX.PxRigidBodyFlag.eENABLE_CCD); + + boxCollider.collisionDetectionMode = CollisionDetectionMode.Continuous; + expect(ccdFlag()).toBeTruthy(); + + boxCollider.isKinematic = true; + expect(ccdFlag()).toBeFalsy(); + + boxCollider.isKinematic = false; + expect(ccdFlag()).toBeTruthy(); + expect(boxCollider.collisionDetectionMode).toEqual(CollisionDetectionMode.Continuous); + }); + + it("R0: setCollisionDetectionMode in kinematic state defers application", function () { + // RED verification: while kinematic, the CCD flag should not be touched (PhysX warns). + // User's intent is cached and applied on next dynamic switch. + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + // @ts-ignore + const physX = boxCollider._nativeCollider._physXPhysics._physX; + const ccdFlag = () => + // @ts-ignore + boxCollider._nativeCollider._pxActor.getRigidBodyFlags(physX.PxRigidBodyFlag.eENABLE_CCD); + + boxCollider.isKinematic = true; + expect(ccdFlag()).toBeFalsy(); + + boxCollider.collisionDetectionMode = CollisionDetectionMode.Continuous; + expect(ccdFlag()).toBeFalsy(); + expect(boxCollider.collisionDetectionMode).toEqual(CollisionDetectionMode.Continuous); + + boxCollider.isKinematic = false; + expect(ccdFlag()).toBeTruthy(); + }); + it("sleep", function () { const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); const boxCollider = box.getComponent(DynamicCollider); diff --git a/tests/src/core/physics/MeshColliderShape.test.ts b/tests/src/core/physics/MeshColliderShape.test.ts index dfb12cf5c0..1b85709d29 100644 --- a/tests/src/core/physics/MeshColliderShape.test.ts +++ b/tests/src/core/physics/MeshColliderShape.test.ts @@ -31,11 +31,7 @@ class CollisionScript extends Script { * @param indices - Optional triangle indices * @returns A ModelMesh with readable data */ -function createModelMesh( - engine: WebGLEngine, - positions: number[], - indices?: number[] -): ModelMesh { +function createModelMesh(engine: WebGLEngine, positions: number[], indices?: number[]): ModelMesh { const mesh = new ModelMesh(engine); const vec3Positions: Vector3[] = []; for (let i = 0; i < positions.length; i += 3) { @@ -107,11 +103,7 @@ describe("MeshColliderShape PhysX", () => { const meshShape = new MeshColliderShape(); const meshMaterial = meshShape.material; // Ground plane at y=0, CCW winding -> normal +Y - const mesh = createModelMesh( - engine, - [-10, 0, -10, 10, 0, -10, -10, 0, 10, 10, 0, 10], - [0, 2, 1, 1, 2, 3] - ); + const mesh = createModelMesh(engine, [-10, 0, -10, 10, 0, -10, -10, 0, 10, 10, 0, 10], [0, 2, 1, 1, 2, 3]); meshShape.mesh = mesh; groundCollider.addShape(meshShape); @@ -190,6 +182,57 @@ describe("MeshColliderShape PhysX", () => { defaultMaterial?.destroy(); material?.destroy(); }); + + it("R6: cloned MeshColliderShape rebuilds its native PhysX shape", async () => { + // RED verification for R6 fix: + // MeshColliderShape's `_nativeShape` is `@ignoreClone` — without an + // override of `_cloneTo` cooking a fresh shape from cloned vertex/index + // buffers, the cloned entity has no physical surface (sphere falls + // straight through). + const groundEntity = root.createChild("meshGroundForClone"); + groundEntity.transform.setPosition(0, 0, 0); + const groundCollider = groundEntity.addComponent(StaticCollider); + const meshShape = new MeshColliderShape(); + const meshMaterial = meshShape.material; + const mesh = createModelMesh(engine, [-10, 0, -10, 10, 0, -10, -10, 0, 10, 10, 0, 10], [0, 2, 1, 1, 2, 3]); + meshShape.mesh = mesh; + groundCollider.addShape(meshShape); + + const clonedGround = groundEntity.clone(); + // Move the original aside so the cloned ground is the only surface below the sphere. + groundEntity.transform.setPosition(1000, 0, 0); + root.addChild(clonedGround); + clonedGround.transform.setPosition(0, 0, 0); + + const clonedShape = clonedGround.getComponent(StaticCollider).shapes[0] as MeshColliderShape; + // @ts-ignore — inspect that the cloned shape actually has a usable native PhysX handle + expect(clonedShape._nativeShape).not.toBeNull(); + // @ts-ignore + expect(clonedShape._nativeShape._pxShape).toBeDefined(); + + const sphereEntity = root.createChild("sphereForClone"); + sphereEntity.transform.setPosition(0, 2, 0); + const dynamicCollider = sphereEntity.addComponent(DynamicCollider); + const sphereShape = new SphereColliderShape(); + const sphereMaterial = sphereShape.material; + sphereShape.radius = 0.5; + dynamicCollider.addShape(sphereShape); + + for (let i = 0; i < 60; i++) { + physicsScene._update(1 / 60); + } + + // Sphere lands on cloned ground (y > -1), not falls forever (y < -10). + const sphereY = sphereEntity.transform.position.y; + expect(sphereY).toBeGreaterThan(-1); + expect(sphereY).toBeLessThan(2); + + groundEntity.destroy(); + clonedGround.destroy(); + sphereEntity.destroy(); + meshMaterial?.destroy(); + sphereMaterial?.destroy(); + }); }); describe("Convex Mesh (Dynamic)", () => { @@ -265,9 +308,10 @@ describe("MeshColliderShape PhysX", () => { const meshShape = new MeshColliderShape(); const meshMaterial = meshShape.material; meshShape.isConvex = true; - const mesh = createModelMesh(engine, [ - -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1 - ]); + const mesh = createModelMesh( + engine, + [-1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1] + ); meshShape.mesh = mesh; meshShape.isTrigger = true; triggerCollider.addShape(meshShape); @@ -417,11 +461,7 @@ describe("MeshColliderShape PhysX", () => { staticCollider.addShape(meshShape); // Update mesh - const mesh2 = createModelMesh( - engine, - [0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0], - [0, 1, 2, 3, 4, 5] - ); + const mesh2 = createModelMesh(engine, [0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0], [0, 1, 2, 3, 4, 5]); meshShape.mesh = mesh2; expect(staticCollider.shapes.length).toBe(1); @@ -695,11 +735,7 @@ describe("MeshColliderShape PhysX", () => { expect(meshShape._nativeShape).toBeNull(); // Re-enable with new mesh - const mesh2 = createModelMesh( - engine, - [0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0], - [0, 1, 2, 3, 4, 5] - ); + const mesh2 = createModelMesh(engine, [0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0], [0, 1, 2, 3, 4, 5]); meshShape.mesh = mesh2; // @ts-ignore expect(meshShape._nativeShape).not.toBeNull(); From 5ec7e3f13cca36ceb8ea47f8e879d36db66c0701 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Fri, 15 May 2026 18:30:17 +0800 Subject: [PATCH 086/130] fix(physics): kinematic transform sync via setKinematicTarget; fix Transform clone dirty flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two latent bugs surfaced jointly while fixing kinematic-pair contact callbacks (3D billiard aim-line): 1. setGlobalPose vs setKinematicTarget routing (PhysX 4.x API misuse) PhysX docs require setKinematicTarget for moving kinematic actors if collision detection is needed; setGlobalPose is teleport and skips contact detection. SceneBinding already sets kineKineFilteringMode = staticKineFilteringMode = eKEEP, but every transform sync went through setWorldTransform → setGlobalPose, silently short-circuiting that intent. Split Collider's native-sync responsibility into two virtual hooks: - _teleportToEntityTransform(pos, rot): init/clone path, always setGlobalPose - _syncEntityTransformToNative(pos, rot): per-frame runtime path DynamicCollider overrides the runtime hook to call nativeCollider.move() (= setKinematicTarget) when _isKinematic. CharacterController overrides the teleport hook to use setWorldPosition (its native API lacks setWorldTransform). PhysXDynamicCollider.move() also normalizes the rotation quaternion (PhysX requirement). 2. Transform._cloneTo missed world-flag dispatch Earlier components on a cloned entity (e.g. DynamicCollider's native ctor) may query transform.worldPosition, which clears WorldPosition / WorldMatrix dirty flags as a side effect of caching. _cloneTo then writes new local values but never re-dirties or dispatches the world flag set, so subsequent worldPosition reads return stale (0,0,0) and registered listeners (Collider._updateFlag, etc.) never fire. Added _worldAssociatedChange(WmWpWeWqWsWus) at the end of _cloneTo. This bug was hidden under the old setGlobalPose path (first _onUpdate teleported the actor regardless of cache freshness). Switching to setKinematicTarget exposed it as Joint/HingeJoint/SpringJoint clone regressions where box2 flew at 299 m/s (implied velocity from native actor at default (0,0,0) being kinematic-animated to entity's true (0,5,0) over one 1/60s substep). Test coverage: - tests/src/core/Transform.test.ts: 2 new cases targeting _cloneTo dirty-flag dispatch (worldPosition cache invalidation + listener fire). Both fail without the Transform.ts fix. - tests/src/core/physics/Collision.test.ts: 9 new cases covering kine-kine / kine-dyn / dyn-dyn / dyn+frozen pairs via transform.setPosition. Confirms callback firing under the new routing. - tests/src/core/physics/PhysicsScene.test.ts: updated 2 assertions ("Dynamic Kinematic vs Static", "Dynamic Trigger Kinematic vs Dynamic Kinematic") from expecting 0 callbacks to expecting callbacks fire — aligning with SceneDesc.kineKineFilteringMode = eKEEP design intent. - tests/src/core/physics/DynamicCollider.test.ts: additional kinematic-mode coverage. All 231 physics + Transform tests pass. Joint / HingeJoint / SpringJoint / CharacterController clone tests all GREEN. --- packages/core/src/Transform.ts | 7 +- .../core/src/physics/CharacterController.ts | 6 +- packages/core/src/physics/Collider.ts | 32 +++- packages/core/src/physics/DynamicCollider.ts | 27 +++ .../physics-physx/src/PhysXDynamicCollider.ts | 18 +- tests/src/core/Transform.test.ts | 68 ++++++- tests/src/core/physics/Collision.test.ts | 171 +++++++++++++++++- .../src/core/physics/DynamicCollider.test.ts | 52 ++++++ tests/src/core/physics/PhysicsScene.test.ts | 16 +- 9 files changed, 378 insertions(+), 19 deletions(-) diff --git a/packages/core/src/Transform.ts b/packages/core/src/Transform.ts index fe9fc720b1..2c9821426c 100644 --- a/packages/core/src/Transform.ts +++ b/packages/core/src/Transform.ts @@ -616,8 +616,13 @@ export class Transform extends Component { // @ts-ignore scale._onValueChanged = target._onScaleChanged; - // When cloning, other components may obtain properties such as `rotationQuaternion` in the constructor, local related dirty flags need to be corrected + // When cloning, other components may obtain properties such as `rotationQuaternion` in the constructor, local related dirty flags need to be corrected. + // Earlier in this Entity's construction other components (e.g. DynamicCollider) may have queried + // `worldPosition`, which clears the WorldPosition / WorldMatrix dirty flags as a side effect of caching + // the computed value. After this _cloneTo writes new local values, those world-derived caches are stale, + // so re-dirty them and notify listeners (Collider._updateFlag etc.) so subsequent reads recompute correctly. target._setDirtyFlagTrue(TransformModifyFlags.LocalQuat | TransformModifyFlags.LocalMatrix); + target._worldAssociatedChange(TransformModifyFlags.WmWpWeWqWsWus); } protected _onLocalMatrixChanging(): void { diff --git a/packages/core/src/physics/CharacterController.ts b/packages/core/src/physics/CharacterController.ts index f3c9e6f048..ea6961b70f 100644 --- a/packages/core/src/physics/CharacterController.ts +++ b/packages/core/src/physics/CharacterController.ts @@ -1,5 +1,5 @@ import { ICharacterController } from "@galacean/engine-design"; -import { Vector3 } from "@galacean/engine-math"; +import { Quaternion, Vector3 } from "@galacean/engine-math"; import { Engine } from "../Engine"; import { Entity } from "../Entity"; import { Collider } from "./Collider"; @@ -162,6 +162,10 @@ export class CharacterController extends Collider { (this._nativeCollider).setSlopeLimit(this._slopeLimit); } + protected override _teleportToEntityTransform(worldPosition: Vector3, _worldRotation: Quaternion): void { + (this._nativeCollider).setWorldPosition(worldPosition); + } + private _syncWorldPositionFromPhysicalSpace(): void { (this._nativeCollider).getWorldPosition(this.entity.transform.worldPosition); } diff --git a/packages/core/src/physics/Collider.ts b/packages/core/src/physics/Collider.ts index 40abda9e00..173725b5c5 100644 --- a/packages/core/src/physics/Collider.ts +++ b/packages/core/src/physics/Collider.ts @@ -1,4 +1,5 @@ import { ICollider, IStaticCollider } from "@galacean/engine-design"; +import { Quaternion, Vector3 } from "@galacean/engine-math"; import { BoolUpdateFlag } from "../BoolUpdateFlag"; import { deepClone, ignoreClone } from "../clone/CloneManager"; import { ICustomClone } from "../clone/ComponentCloner"; @@ -111,10 +112,7 @@ export class Collider extends Component implements ICustomClone { const shapes = this._shapes; if (this._updateFlag.flag) { const { transform } = this.entity; - (this._nativeCollider).setWorldTransform( - transform.worldPosition, - transform.worldRotationQuaternion - ); + this._syncEntityTransformToNative(transform.worldPosition, transform.worldRotationQuaternion); const worldScale = transform.lossyWorldScale; for (let i = 0, n = shapes.length; i < n; i++) { @@ -171,6 +169,32 @@ export class Collider extends Component implements ICustomClone { this._addNativeShape(this.shapes[i]); } this._setCollisionLayer(); + // Teleport native actor to entity's current world pose. + // The native actor was created in constructor() with the entity's then-current + // worldPosition/Rotation. On clone, the entity's transform fields are deep-cloned + // AFTER the Component (and its native actor) are constructed, so the native actor's + // pose lags behind the cloned entity transform until this sync. + const { transform } = this.entity; + this._teleportToEntityTransform(transform.worldPosition, transform.worldRotationQuaternion); + } + + /** + * Teleport native actor to a world pose (instant, no implied velocity). + * Used during initialization paths (clone) where the native actor must be re-aligned + * with the entity transform after construction-time pose was based on stale defaults. + */ + protected _teleportToEntityTransform(worldPosition: Vector3, worldRotation: Quaternion): void { + (this._nativeCollider).setWorldTransform(worldPosition, worldRotation); + } + + /** + * Sync entity world transform to native actor for per-frame updates. + * Default semantics: teleport (setGlobalPose). Subclasses override to express + * physics-aware movement (e.g. DynamicCollider routes kinematic actors through + * setKinematicTarget to generate contact events on swept motion). + */ + protected _syncEntityTransformToNative(worldPosition: Vector3, worldRotation: Quaternion): void { + (this._nativeCollider).setWorldTransform(worldPosition, worldRotation); } /** diff --git a/packages/core/src/physics/DynamicCollider.ts b/packages/core/src/physics/DynamicCollider.ts index d78fd98121..8a2912f8f8 100644 --- a/packages/core/src/physics/DynamicCollider.ts +++ b/packages/core/src/physics/DynamicCollider.ts @@ -462,6 +462,33 @@ export class DynamicCollider extends Collider { super.addShape(shape); } + /** + * Route per-frame entity → native transform sync to the correct physics API based + * on kinematic state. + * + * PhysX 4.x docs (PxRigidDynamic): + * "If you intend to move a kinematic actor with [setGlobalPose] and want + * collision detection, use setKinematicTarget() instead." + * + * setGlobalPose is a teleport: PhysX skips contact detection between the old + * and new pose, so two kinematic actors moved onto each other via entity.transform + * mutation would NOT produce onCollisionEnter / onCollisionStay events even when + * scene.kineKineFilteringMode = eKEEP. Routing the per-frame sync through + * IDynamicCollider.move() (which the PhysX backend implements as + * setKinematicTarget) tells PhysX the actor is animating to the target during the + * next simulate(), enabling sweep contact detection for kine-kine and kine-dynamic + * pairs alike. + * + * @internal + */ + protected override _syncEntityTransformToNative(worldPosition: Vector3, worldRotation: Quaternion): void { + if (this._isKinematic) { + (this._nativeCollider).move(worldPosition, worldRotation); + } else { + super._syncEntityTransformToNative(worldPosition, worldRotation); + } + } + /** * @internal */ diff --git a/packages/physics-physx/src/PhysXDynamicCollider.ts b/packages/physics-physx/src/PhysXDynamicCollider.ts index a972712d84..05614dd5bf 100644 --- a/packages/physics-physx/src/PhysXDynamicCollider.ts +++ b/packages/physics-physx/src/PhysXDynamicCollider.ts @@ -275,20 +275,28 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli /** * {@inheritDoc IDynamicCollider.move } + * + * PhysX 要求 setKinematicTarget 的 rotation 是 normalized quaternion,否则会触发 + * 内部 assertion / 警告,并把 actor 转到错误的姿态。所以在写入 wasm 边界前统一 normalize。 */ move(positionOrRotation: Vector3 | Quaternion, rotation?: Quaternion): void { + const tempTranslation = PhysXDynamicCollider._tempTranslation; + const tempRotation = PhysXDynamicCollider._tempRotation; + if (rotation) { - this._pxActor.setKinematicTarget(positionOrRotation, rotation); + tempRotation.copyFrom(rotation).normalize(); + this._pxActor.setKinematicTarget(positionOrRotation, tempRotation); return; } - const tempTranslation = PhysXDynamicCollider._tempTranslation; - const tempRotation = PhysXDynamicCollider._tempRotation; - this.getWorldTransform(tempTranslation, tempRotation); if (positionOrRotation instanceof Vector3) { + this.getWorldTransform(tempTranslation, tempRotation); + // current rotation read from PhysX is already normalized; no extra work needed this._pxActor.setKinematicTarget(positionOrRotation, tempRotation); } else { - this._pxActor.setKinematicTarget(tempTranslation, positionOrRotation); + this.getWorldTransform(tempTranslation, tempRotation); + tempRotation.copyFrom(positionOrRotation).normalize(); + this._pxActor.setKinematicTarget(tempTranslation, tempRotation); } } diff --git a/tests/src/core/Transform.test.ts b/tests/src/core/Transform.test.ts index dc14acbf42..bdc8dbc884 100644 --- a/tests/src/core/Transform.test.ts +++ b/tests/src/core/Transform.test.ts @@ -130,7 +130,9 @@ describe("Transform test", function () { // @ts-ignore expect(child.transform._dirtyFlag & TransformModifyFlags.WorldMatrix).to.equal(0); // @ts-ignore - expect(parent.transform._dirtyFlag & TransformModifyFlags.WmWpWeWqWsWus).to.equal(TransformModifyFlags.WmWpWeWqWsWus); + expect(parent.transform._dirtyFlag & TransformModifyFlags.WmWpWeWqWsWus).to.equal( + TransformModifyFlags.WmWpWeWqWsWus + ); // 3) Reparent `parent` under a positioned root (triggers _parentChange on parent). const root = scene.createRootEntity("reparent-root"); @@ -176,6 +178,70 @@ describe("Transform test", function () { expect(after.elements[14]).to.equal(730); // 700 + 30 }); + it("_cloneTo invalidates world cache cleared by other components' ctor reads", () => { + // Reproduces the Transform._cloneTo dirty-flag bug surfaced by the + // billiard-aim-line fix (see notes/3D台球游戏联机版/2026-05-15-billiard-aim-line-kinematic-pair.md). + // + // The cloneComponent sequence calls Component._cloneTo for each component + // in order. Components added BEFORE Transform on a cloned entity may read + // `entity.transform.worldPosition` in their ctor (DynamicCollider does this + // to seed the native PxRigidDynamic pose) — that getter clears WorldPosition + // & WorldMatrix dirty flags as a side effect of caching the computed value. + // + // When Transform._cloneTo later writes new local values, those world-derived + // caches are stale (they still hold the pre-clone defaults). The fix re-dirties + // & dispatches the world flag set so subsequent reads recompute correctly. + const source = new Entity(engine, "source"); + source.transform.setPosition(1, 2, 3); + + // Target entity in pre-_cloneTo state: default transform, but a "Component ctor" + // already queried worldPosition (simulates DynamicCollider native ctor side effect). + const target = new Entity(engine, "target"); + const beforeClone = target.transform.worldPosition; + expect(beforeClone.x).to.equal(0); + // White-box: WorldPosition dirty flag was cleared by the getter call above. + // @ts-ignore + expect(target.transform._dirtyFlag & TransformModifyFlags.WorldPosition).to.equal(0); + + // Exercise _cloneTo (the same call the cloneComponent path makes). + // @ts-ignore - internal API + source.transform._cloneTo(target.transform); + + // After _cloneTo, target.worldPosition must reflect cloned (1,2,3), not stale (0,0,0). + const afterClone = target.transform.worldPosition; + expect(afterClone.x).to.equal(1); + expect(afterClone.y).to.equal(2); + expect(afterClone.z).to.equal(3); + }); + + it("_cloneTo dispatches world-flag change to registered listeners (Collider._updateFlag fires)", () => { + // Reproduces the listener-side of the same bug. Collider._updateFlag is a + // BoolUpdateFlag registered via entity.registerWorldChangeFlag(); Collider._onUpdate + // checks it to decide whether to push transform to the native physics actor. + // + // If Transform._cloneTo only re-dirties local flags but doesn't dispatch the + // world flag set, the listener stays `false` after clone → Collider never + // re-syncs the native actor pose → physics state mismatch (Bug A / Joint clone + // box2 flies at 299 m/s). + const source = new Entity(engine, "source"); + source.transform.setPosition(7, 8, 9); + + const target = new Entity(engine, "target"); + // Simulate Component ctor: register a world-change listener BEFORE _cloneTo. + const updateFlag = target.registerWorldChangeFlag(); + // Then read worldPosition (mirrors DynamicCollider native ctor) — this clears + // both the dirty flag AND the listener's `flag` if it was true. + target.transform.worldPosition; + updateFlag.flag = false; + + // @ts-ignore - internal API + source.transform._cloneTo(target.transform); + + // The listener MUST observe the change; otherwise downstream consumers + // (Collider._onUpdate, animation rigs, etc.) silently miss the clone update. + expect(updateFlag.flag).to.equal(true); + }); + it("Subclasses of Transform", () => { // Create by constructor const entity0 = new Entity(engine, "entity"); diff --git a/tests/src/core/physics/Collision.test.ts b/tests/src/core/physics/Collision.test.ts index 870bc546bc..218b2273aa 100644 --- a/tests/src/core/physics/Collision.test.ts +++ b/tests/src/core/physics/Collision.test.ts @@ -1,4 +1,12 @@ -import { BoxColliderShape, DynamicCollider, Entity, Engine, Script, StaticCollider } from "@galacean/engine-core"; +import { + BoxColliderShape, + DynamicCollider, + DynamicColliderConstraints, + Entity, + Engine, + Script, + StaticCollider +} from "@galacean/engine-core"; import { Vector3 } from "@galacean/engine-math"; import { PhysXPhysics } from "@galacean/engine-physics-physx"; import { WebGLEngine } from "@galacean/engine-rhi-webgl"; @@ -164,4 +172,165 @@ describe("Collision", function () { engine.sceneManager.activeScene.physics._update(1); }); }); + + // ────────────────────────────────────────────────────────────────────────────── + // Kinematic-pair collision callback (3D billiard aim-line use case). + // PhysX 4.x defaults suppress kineKine + staticKine pairs. SceneBinding already + // sets kineKineFilteringMode = eKEEP and staticKineFilteringMode = eKEEP, and + // filter shader returns eNOTIFY_TOUCH_FOUND/PERSISTS/LOST for all pairs. + // Question: does the kinematic pair actually fire onCollisionEnter when a + // kinematic actor is moved into another actor's volume via setWorldTransform + // (i.e. setGlobalPose teleport, NOT setKinematicTarget)? + // + // Cocos parity expectation: yes — Cocos PhysX backend fires onCollisionEnter + // for kinematic↔dynamic and kinematic↔kinematic pairs on overlap. + // ────────────────────────────────────────────────────────────────────────────── + + function probeKinematicCallback(opts: { + aKine: boolean; + bKine: boolean; + timeoutMs?: number; + }): Promise<{ fired: boolean }> { + return new Promise((resolve) => { + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, 0, 0); + const boxA = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(-3, 0, 0)); + const boxB = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(3, 0, 0)); + const colA = boxA.getComponent(DynamicCollider); + const colB = boxB.getComponent(DynamicCollider); + colA.useGravity = false; + colB.useGravity = false; + colA.isKinematic = opts.aKine; + colB.isKinematic = opts.bKine; + + let fired = false; + boxA.addComponent( + class extends Script { + onCollisionEnter(_other: Collision): void { + fired = true; + resolve({ fired: true }); + } + } + ); + + // Step a few frames to let PhysX settle initial state. + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // Teleport B onto A → expect onCollisionEnter. + boxB.transform.setPosition(-3, 0, 0); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + if (!fired) resolve({ fired: false }); + }); + } + + // Probes that the standard transform→PhysX sync path routes correctly for + // kinematic actors. With the fix in PhysXDynamicCollider.setWorldTransform, + // moving a kinematic actor via transform.setPosition() goes through + // setKinematicTarget(), which lets PhysX detect contact and fire the callback. + it("kinematic-kinematic overlap via transform.setPosition fires onCollisionEnter", async function () { + const r = await probeKinematicCallback({ aKine: true, bKine: true }); + expect(r.fired).toBe(true); + }); + + it("kinematic-dynamic overlap via transform.setPosition fires onCollisionEnter", async function () { + const r = await probeKinematicCallback({ aKine: true, bKine: false }); + expect(r.fired).toBe(true); + }); + + it("dynamic-dynamic overlap via transform.setPosition fires onCollisionEnter", async function () { + const r = await probeKinematicCallback({ aKine: false, bKine: false }); + expect(r.fired).toBe(true); + }); + + // Probe whether "dynamic actor + freeze 6 constraints + teleport via setGlobalPose" + // can substitute for a kinematic actor and still trigger contact callbacks. + // This is the proposed fix path for the 3D billiard hitBall: ditch kinematic, + // use a fully-frozen dynamic actor that is moved via setWorldPosition. + it("HYPOTHESIS: kine-kine fires onCollisionEnter when moved via setKinematicTarget (not setGlobalPose)", function () { + return new Promise((resolve, reject) => { + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, 0, 0); + const boxA = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(-3, 0, 0)); + const boxB = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(3, 0, 0)); + const colA = boxA.getComponent(DynamicCollider); + const colB = boxB.getComponent(DynamicCollider); + colA.useGravity = false; + colB.useGravity = false; + colA.isKinematic = true; + colB.isKinematic = true; + + let fired = false; + boxA.addComponent( + class extends Script { + onCollisionEnter(_other: Collision): void { + fired = true; + resolve(); + } + } + ); + + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // Move B onto A via DynamicCollider.move() — this internally calls setKinematicTarget. + colB.move(new Vector3(-3, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + if (!fired) reject(new Error("kine-kine setKinematicTarget did NOT fire onCollisionEnter")); + }); + }); + + it("dynamic + frozen-6 + teleport: overlap fires onCollisionEnter (fix candidate)", function () { + return new Promise((resolve) => { + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, 0, 0); + const boxA = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(-3, 0, 0)); + const boxB = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(3, 0, 0)); + const colA = boxA.getComponent(DynamicCollider); + const colB = boxB.getComponent(DynamicCollider); + // Both fully frozen — emulates the cocos kinematic semantics (no gravity, no movement). + const FREEZE_ALL = + DynamicColliderConstraints.FreezePositionX | + DynamicColliderConstraints.FreezePositionY | + DynamicColliderConstraints.FreezePositionZ | + DynamicColliderConstraints.FreezeRotationX | + DynamicColliderConstraints.FreezeRotationY | + DynamicColliderConstraints.FreezeRotationZ; + colA.constraints = FREEZE_ALL; + colB.constraints = FREEZE_ALL; + colA.useGravity = false; + colB.useGravity = false; + colA.isKinematic = false; + colB.isKinematic = false; + + let fired = false; + boxA.addComponent( + class extends Script { + onCollisionEnter(_other: Collision): void { + fired = true; + resolve(); + } + } + ); + + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + boxB.transform.setPosition(-3, 0, 0); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + if (!fired) { + expect.fail("expected onCollisionEnter to fire for dynamic-frozen pair after teleport"); + } + }); + }); }); diff --git a/tests/src/core/physics/DynamicCollider.test.ts b/tests/src/core/physics/DynamicCollider.test.ts index 3346c9469f..78cbe1f381 100644 --- a/tests/src/core/physics/DynamicCollider.test.ts +++ b/tests/src/core/physics/DynamicCollider.test.ts @@ -476,6 +476,58 @@ describe("DynamicCollider", function () { expect(formatValue(boxCollider.linearVelocity.x)).eq(0.01667); }); + it("fixedTimeStep 1/60 vs 1/480: PhysX applyForce delivers 8x smaller dv at finer step", function () { + // ultrathink probe: does cocos-style `fixedTimeStep(true)→1/480` actually give + // *more force* than `fixedTimeStep(false)→1/60` in PhysX? + // + // Theory: + // Bullet (Cocos): clearForces runs ONCE after all substeps → dv = F·frame_dt/m + // → substep count doesn't affect dv. + // PhysX (Galacean): force cleared per simulate() call → only the first substep + // in a frame applies the force → dv = F·fixedTimeStep/m + // → 1/480 gives dv 8x smaller than 1/60. + // + // This test confirms the PhysX behavior empirically and quantifies the gap. + const scene = engine.sceneManager.activeScene; + const originalFTS = scene.physics.fixedTimeStep; + + const probe = (fts: number) => { + rootEntity.clearChildren(); + scene.physics.fixedTimeStep = fts; + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const c = box.getComponent(DynamicCollider); + c.mass = 1; + c.useGravity = false; + c.linearDamping = 0; + c.angularDamping = 0; + c.applyForce(new Vector3(100, 0, 0)); + // Advance exactly one *frame* of wall time. Galacean's _update loops simulate + // until frame_dt accumulates: 1/60 → 1 substep; 1/480 → 8 substeps. + // @ts-ignore + scene.physics._update(1 / 60); + return c.linearVelocity.x; + }; + + const dv_1_60 = probe(1 / 60); + const dv_1_480 = probe(1 / 480); + + console.info( + `[fixedTimeStep probe] applyForce(F=100) over 1 frame (1/60s):\n` + + ` 1/60 step → dv = ${dv_1_60.toFixed(4)} m/s\n` + + ` 1/480 step → dv = ${dv_1_480.toFixed(4)} m/s\n` + + ` ratio (1/60 / 1/480) = ${(dv_1_60 / dv_1_480).toFixed(3)} (theory: 8)` + ); + + scene.physics.fixedTimeStep = originalFTS; + + // Theory: dv_1_60 = F·(1/60)/m = 100/60 ≈ 1.667 + expect(dv_1_60).toBeCloseTo(100 / 60, 2); + // Theory: dv_1_480 = F·(1/480)/m = 100/480 ≈ 0.208 + expect(dv_1_480).toBeCloseTo(100 / 480, 2); + // Ratio must be 8 (PhysX clears force per simulate) + expect(dv_1_60 / dv_1_480).toBeCloseTo(8, 1); + }); + it("maxAngularVelocity", function () { const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); const boxCollider = box.getComponent(DynamicCollider); diff --git a/tests/src/core/physics/PhysicsScene.test.ts b/tests/src/core/physics/PhysicsScene.test.ts index c50102a0d8..a62067db59 100644 --- a/tests/src/core/physics/PhysicsScene.test.ts +++ b/tests/src/core/physics/PhysicsScene.test.ts @@ -1507,14 +1507,16 @@ describe("Physics Test", () => { collisionTestScript.useLite = false; // Test that collision works correctly, A is dynamic and kinematic, B is static. + // SceneDesc.staticKineFilteringMode = eKEEP + Collider.move() routing kinematic + // to setKinematicTarget make static-kinematic pairs generate contact events. resetSpy(); setColliderProps(entity1, true, false, true); setColliderProps(entity2, false, false, false); updatePhysics(physicsMgr); - expect(collisionTestScript.onCollisionEnter).not.toHaveBeenCalled(); - expect(collisionTestScript.onCollisionStay).not.toHaveBeenCalled(); - expect(collisionTestScript.onCollisionExit).not.toHaveBeenCalled(); + expect(collisionTestScript.onCollisionEnter).toHaveBeenCalled(); + expect(collisionTestScript.onCollisionStay).toHaveBeenCalled(); + expect(collisionTestScript.onCollisionExit).toHaveBeenCalled(); expect(collisionTestScript.onTriggerEnter).not.toHaveBeenCalled(); expect(collisionTestScript.onTriggerStay).not.toHaveBeenCalled(); expect(collisionTestScript.onTriggerExit).not.toHaveBeenCalled(); @@ -1627,14 +1629,16 @@ describe("Physics Test", () => { collisionTestScript.useLite = false; // Test that collision works correctly, both A,B are dynamic, kinematic. + // SceneDesc.kineKineFilteringMode = eKEEP + Collider.move() routing kinematic + // to setKinematicTarget make kine-kine pairs generate contact events. resetSpy(); setColliderProps(entity1, true, false, true); setColliderProps(entity2, true, false, true); updatePhysics(physicsMgr); - expect(collisionTestScript.onCollisionEnter).not.toHaveBeenCalled(); - expect(collisionTestScript.onCollisionStay).not.toHaveBeenCalled(); - expect(collisionTestScript.onCollisionExit).not.toHaveBeenCalled(); + expect(collisionTestScript.onCollisionEnter).toHaveBeenCalled(); + expect(collisionTestScript.onCollisionStay).toHaveBeenCalled(); + expect(collisionTestScript.onCollisionExit).toHaveBeenCalled(); expect(collisionTestScript.onTriggerEnter).not.toHaveBeenCalled(); expect(collisionTestScript.onTriggerStay).not.toHaveBeenCalled(); expect(collisionTestScript.onTriggerExit).not.toHaveBeenCalled(); From ab190accb682d9fd487ede252d7ac62c7b01f1c1 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Fri, 15 May 2026 18:43:34 +0800 Subject: [PATCH 087/130] chore: release v0.0.0-experimental-2.0-game.12 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 51157d1df3..74c7f5ac8d 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index 19bde9054f..92d67e5b13 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 2dd931dc8d..17f1389f7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 5c21ee1ff0..a360507405 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 55e657be34..acca2d9ca6 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index ee346f5e47..8d205d8912 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index 85c4319eeb..a6fae3addd 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 4c3593c07b..c9e3396034 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index a03c4b3371..e0d0cfc6a8 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 02fbc772e0..7e1f9505b0 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 1fdd107dd2..af68abc7de 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 9847e05fb0..54fb32b0b9 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index 768788a7f3..434df88749 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index 405a75b24f..f01a653619 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 8abb3f4522..5dd9aa8b8c 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index cf3fc01a1c..041a72dec9 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index 56b37bae11..c9aa753c23 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.11", + "version": "0.0.0-experimental-2.0-game.12", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From 4a8342b7d395762bb659c83ee498247e8c4d7bb3 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Sat, 16 May 2026 16:23:21 +0800 Subject: [PATCH 088/130] fix(loader): include skinned mesh nodes in skin root LCA --- .../src/gltf/parser/GLTFSkinParser.test.ts | 44 +++++++++++ .../loader/src/gltf/parser/GLTFSkinParser.ts | 74 +++++++------------ tests/src/loader/GLTFLoader.test.ts | 5 +- 3 files changed, 73 insertions(+), 50 deletions(-) create mode 100644 packages/loader/src/gltf/parser/GLTFSkinParser.test.ts diff --git a/packages/loader/src/gltf/parser/GLTFSkinParser.test.ts b/packages/loader/src/gltf/parser/GLTFSkinParser.test.ts new file mode 100644 index 0000000000..bb5714b847 --- /dev/null +++ b/packages/loader/src/gltf/parser/GLTFSkinParser.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from "vitest"; + +describe("GLTFSkinParser rootBone resolution", () => { + async function createParser(): Promise { + (globalThis as any).window = { AudioContext: undefined, TextMetrics: undefined }; + const { GLTFSkinParser } = await import("./GLTFSkinParser"); + return new GLTFSkinParser(); + } + + it("includes skinned mesh nodes when resolving missing skin.skeleton", async () => { + const parser = await createParser(); + const sceneRoot = { name: "GLTF_ROOT" }; + const meshRoot = { name: "Character_Man", parent: sceneRoot }; + const hips = { name: "mixamorig:Hips", parent: sceneRoot }; + const spine = { name: "mixamorig:Spine", parent: hips }; + + const rootBone = (parser as any)._findSkinRootBoneByLCA( + 0, + [1, 2], + [meshRoot, hips, spine], + [{ name: "Character_Man", skin: 0 }, { name: "mixamorig:Hips" }, { name: "mixamorig:Spine" }] + ); + + expect(rootBone).toBe(sceneRoot); + }); + + it("does not promote to the scene wrapper for unrelated top-level siblings", async () => { + const parser = await createParser(); + const sceneRoot = { name: "GLTF_ROOT" }; + const characterRoot = { name: "Character_Root", parent: sceneRoot }; + const mesh = { name: "Character_Mesh", parent: characterRoot }; + const hips = { name: "mixamorig:Hips", parent: characterRoot }; + const light = { name: "Light", parent: sceneRoot }; + + const rootBone = (parser as any)._findSkinRootBoneByLCA( + 0, + [3], + [characterRoot, mesh, light, hips], + [{ name: "Character_Root" }, { name: "Character_Mesh", skin: 0 }, { name: "Light" }, { name: "mixamorig:Hips" }] + ); + + expect(rootBone).toBe(characterRoot); + }); +}); diff --git a/packages/loader/src/gltf/parser/GLTFSkinParser.ts b/packages/loader/src/gltf/parser/GLTFSkinParser.ts index b97825fa6a..3f11f487e5 100644 --- a/packages/loader/src/gltf/parser/GLTFSkinParser.ts +++ b/packages/loader/src/gltf/parser/GLTFSkinParser.ts @@ -39,8 +39,7 @@ export class GLTFSkinParser extends GLTFParser { const rootBone = entities[skeleton]; skin.rootBone = rootBone; } else { - const rootBone = - this._findSceneRootBone(context, joints, entities) ?? this._findSkeletonRootBone(joints, entities); + const rootBone = this._findSkinRootBoneByLCA(index, joints, entities, glTF.nodes); if (rootBone) { skin.rootBone = rootBone; } else { @@ -54,71 +53,50 @@ export class GLTFSkinParser extends GLTFParser { return AssetPromise.resolve(skinPromise); } - private _findSceneRootBone(context: GLTFParserContext, joints: number[], entities: Entity[]): Entity | null { - const { glTF, glTFResource } = context; - const scenes = glTF.scenes; - const sceneRoots = glTFResource._sceneRoots; - - if (!scenes?.length || !sceneRoots?.length) { - return null; - } - - for (let i = 0, n = scenes.length; i < n; i++) { - const sceneNodes = scenes[i].nodes ?? []; - if (sceneNodes.length <= 1) { - continue; - } - - const sceneRoot = sceneRoots[i]; - if (!sceneRoot) { - continue; - } - - const sceneRootChildren = new Set(sceneNodes.map((nodeIndex) => entities[nodeIndex])); - let allJointsUnderSceneRoot = true; - - for (let j = 0, m = joints.length; j < m; j++) { - let entity = entities[joints[j]]; - while (entity?.parent) { - entity = entity.parent; - } - - if (!sceneRootChildren.has(entity)) { - allJointsUnderSceneRoot = false; - break; - } - } - - if (allJointsUnderSceneRoot) { - return sceneRoot; + private _findSkinRootBoneByLCA( + skinIndex: number, + joints: number[], + entities: Entity[], + nodes: Array<{ skin?: number }> = [] + ): Entity | null { + const nodeIndices = joints.slice(); + for (let i = 0, n = nodes.length; i < n; i++) { + if (nodes[i]?.skin === skinIndex) { + nodeIndices.push(i); } } - return null; + return this._findRootBoneByLCA(nodeIndices, entities); } - private _findSkeletonRootBone(joints: number[], entities: Entity[]): Entity { - const paths = >{}; - for (const index of joints) { + private _findRootBoneByLCA(nodeIndices: number[], entities: Entity[]): Entity | null { + const paths: Entity[][] = []; + for (const index of nodeIndices) { const path = new Array(); let entity = entities[index]; while (entity) { path.unshift(entity); entity = entity.parent; } - paths[index] = path; + if (path.length) { + paths.push(path); + } + } + + if (!paths.length) { + return null; } - let rootNode = null; + let rootNode: Entity | null = null; for (let i = 0; ; i++) { - let path = paths[joints[0]]; + let path = paths[0]; if (i >= path.length) { return rootNode; } const entity = path[i]; - for (let j = 1, m = joints.length; j < m; j++) { - path = paths[joints[j]]; + for (let j = 1, m = paths.length; j < m; j++) { + path = paths[j]; if (i >= path.length || entity !== path[i]) { return rootNode; } diff --git a/tests/src/loader/GLTFLoader.test.ts b/tests/src/loader/GLTFLoader.test.ts index 3c8aa822a7..086eff3969 100644 --- a/tests/src/loader/GLTFLoader.test.ts +++ b/tests/src/loader/GLTFLoader.test.ts @@ -53,7 +53,8 @@ beforeAll(async function () { ], nodes: [ { - name: "Character_Man" + name: "Character_Man", + skin: 0 }, { name: "mixamorig:Hips", @@ -447,7 +448,7 @@ beforeAll(async function () { afterAll(() => { @registerGLTFParser(GLTFParserType.Schema) - class test extends GLTFSchemaParser { } + class test extends GLTFSchemaParser {} }); describe("glTF Loader test", function () { From b7bf4c058a308b896a16885abc507ee41a01a9c0 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Sat, 16 May 2026 18:45:59 +0800 Subject: [PATCH 089/130] fix(animation): use per-instance speed during cross fade --- packages/core/src/animation/Animator.ts | 4 +-- tests/src/core/Animator.test.ts | 37 +++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/packages/core/src/animation/Animator.ts b/packages/core/src/animation/Animator.ts index c25c30e3c8..544d40b27e 100644 --- a/packages/core/src/animation/Animator.ts +++ b/packages/core/src/animation/Animator.ts @@ -771,8 +771,8 @@ export class Animator extends Component { return; } - const srcPlaySpeed = srcState.speed * speed; - const dstPlaySpeed = destState.speed * speed; + const srcPlaySpeed = srcPlayData.speed * speed; + const dstPlaySpeed = destPlayData.speed * speed; const dstPlayDeltaTime = dstPlaySpeed * deltaTime; srcPlayData && srcPlayData.updateOrientation(srcPlaySpeed * deltaTime); diff --git a/tests/src/core/Animator.test.ts b/tests/src/core/Animator.test.ts index 6cb74ea8e2..cb2bfa05d1 100644 --- a/tests/src/core/Animator.test.ts +++ b/tests/src/core/Animator.test.ts @@ -238,6 +238,37 @@ describe("Animator test", function () { expect(layerState).to.eq(2); }); + it("crossFade advances with per-instance playData speed instead of shared AnimatorState speed", () => { + const sharedStates = animator.animatorController.layers[0].stateMachine.states; + const sharedWalkState = sharedStates.find((state) => state.name === "Walk"); + const sharedRunState = sharedStates.find((state) => state.name === "Run"); + const oldWalkSpeed = sharedWalkState.speed; + const oldRunSpeed = sharedRunState.speed; + + try { + animator.play("Walk"); + animator.crossFade("Run", 1.0, 0); + + const layerData = animator["_animatorLayersData"][0]; + layerData.srcPlayData.speed = 0.25; + layerData.destPlayData.speed = 0.25; + sharedWalkState.speed = 10; + sharedRunState.speed = 10; + + const srcPlayedTime = layerData.srcPlayData.playedTime; + const destPlayedTime = layerData.destPlayData.playedTime; + // @ts-ignore + animator.engine.time._frameCount++; + animator.update(0.2); + + expect(layerData.srcPlayData.playedTime - srcPlayedTime).toBeCloseTo(0.05, 5); + expect(layerData.destPlayData.playedTime - destPlayedTime).toBeCloseTo(0.05, 5); + } finally { + sharedWalkState.speed = oldWalkSpeed; + sharedRunState.speed = oldRunSpeed; + } + }); + it("cross fade in fixed time", () => { const runState = animator.findAnimatorState("Run"); animator.play("Walk"); @@ -350,7 +381,7 @@ describe("Animator test", function () { animator.play("Walk"); class TestScript extends Script { - event0(): void { } + event0(): void {} } const testScript = animator.entity.addComponent(TestScript); @@ -792,8 +823,8 @@ describe("Animator test", function () { animator.animatorController = animatorController; class TestScript extends StateMachineScript { - onStateEnter(animator) { } - onStateExit(animator) { } + onStateEnter(animator) {} + onStateExit(animator) {} } const testScript = state1.addStateMachineScript(TestScript); From 4a568170c57381880cb46bc4980b7bb0d647d9ba Mon Sep 17 00:00:00 2001 From: luzhuang Date: Sun, 17 May 2026 01:13:03 +0800 Subject: [PATCH 090/130] fix(loader): compute skinned bounds from non-joint rootBone --- .../loader/src/gltf/parser/GLTFSceneParser.ts | 46 +------ tests/src/loader/GLTFLoader.test.ts | 112 ++++++++++++++++++ 2 files changed, 115 insertions(+), 43 deletions(-) diff --git a/packages/loader/src/gltf/parser/GLTFSceneParser.ts b/packages/loader/src/gltf/parser/GLTFSceneParser.ts index b74424d2e6..553bf177f2 100644 --- a/packages/loader/src/gltf/parser/GLTFSceneParser.ts +++ b/packages/loader/src/gltf/parser/GLTFSceneParser.ts @@ -196,49 +196,9 @@ export class GLTFSceneParser extends GLTFParser { if (rootBoneIndex !== -1) { BoundingBox.transform(mesh.bounds, inverseBindMatrices[rootBoneIndex], skinnedMeshRenderer.localBounds); } else { - // Root bone is not in joints list, we can only compute approximate inverse bind matrix - // Average all root bone's children inverse bind matrix - const approximateBindMatrix = new Matrix(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - let subRootBoneCount = this._computeApproximateBindMatrix( - bones, - inverseBindMatrices, - rootBone, - approximateBindMatrix - ); - - if (subRootBoneCount !== 0) { - Matrix.multiplyScalar(approximateBindMatrix, 1.0 / subRootBoneCount, approximateBindMatrix); - BoundingBox.transform(mesh.bounds, approximateBindMatrix, skinnedMeshRenderer.localBounds); - } else { - skinnedMeshRenderer.localBounds.copyFrom(mesh.bounds); - } - } - } - - private _computeApproximateBindMatrix( - jointEntities: ReadonlyArray, - inverseBindMatrices: Matrix[], - rootEntity: Entity, - approximateBindMatrix: Matrix - ): number { - let subRootBoneCount = 0; - const children = rootEntity.children; - for (let i = 0, n = children.length; i < n; i++) { - const rootChild = children[i]; - const index = jointEntities.indexOf(rootChild); - if (index !== -1) { - Matrix.add(approximateBindMatrix, inverseBindMatrices[index], approximateBindMatrix); - subRootBoneCount++; - } else { - subRootBoneCount += this._computeApproximateBindMatrix( - jointEntities, - inverseBindMatrices, - rootChild, - approximateBindMatrix - ); - } + const inverseRootBoneWorld = new Matrix(); + Matrix.invert(rootBone.transform.worldMatrix, inverseRootBoneWorld); + BoundingBox.transform(mesh.bounds, inverseRootBoneWorld, skinnedMeshRenderer.localBounds); } - - return subRootBoneCount; } } diff --git a/tests/src/loader/GLTFLoader.test.ts b/tests/src/loader/GLTFLoader.test.ts index 086eff3969..164dd788aa 100644 --- a/tests/src/loader/GLTFLoader.test.ts +++ b/tests/src/loader/GLTFLoader.test.ts @@ -94,6 +94,101 @@ beforeAll(async function () { }); } + if (context.glTFResource.url.endsWith("testSkinRootBounds.gltf")) { + const buffer = new ArrayBuffer(152); + const floats = new Float32Array(buffer); + // Inverse bind matrices for Hips and Spine. Their bind pose world x is + // Character_Group(3) + Hips(10), so inverse bind translates by -13. + floats.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -13, 0, 0, 1], 0); + floats.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -13, 0, 0, 1], 16); + floats.set([9, -1, -1, 11, 1, 1], 32); + context.buffers = [buffer]; + return Promise.resolve({ + asset: { + version: "2.0" + }, + scene: 0, + scenes: [ + { + nodes: [0] + } + ], + nodes: [ + { + name: "Character_Group", + translation: [3, 0, 0], + children: [1, 2] + }, + { + name: "Character_Man", + mesh: 0, + skin: 0 + }, + { + name: "mixamorig:Hips", + translation: [10, 0, 0], + children: [3] + }, + { + name: "mixamorig:Spine" + } + ], + skins: [ + { + inverseBindMatrices: 0, + joints: [2, 3] + } + ], + meshes: [ + { + primitives: [ + { + attributes: { + POSITION: 1 + }, + mode: 4 + } + ] + } + ], + accessors: [ + { + bufferView: 0, + byteOffset: 0, + componentType: 5126, + count: 2, + type: "MAT4" + }, + { + bufferView: 1, + byteOffset: 0, + componentType: 5126, + count: 2, + type: "VEC3", + min: [9, -1, -1], + max: [11, 1, 1] + } + ], + bufferViews: [ + { + buffer: 0, + byteOffset: 0, + byteLength: 128 + }, + { + buffer: 0, + byteOffset: 128, + byteLength: 24 + } + ], + buffers: [ + { + byteLength: 152 + } + ] + }); + } + const glTF = { buffers: [ { @@ -608,6 +703,23 @@ describe("glTF scene root structure", function () { expect(defaultSceneRoot.children.length).to.equal(2); expect(skins[0].rootBone).to.equal(defaultSceneRoot); }); + + it("Skinned mesh bounds should stay in rootBone space when inferred rootBone is outside joints", async () => { + const glTFResource: GLTFResource = await engine.resourceManager.load({ + type: AssetType.GLTF, + url: "mock/path/testSkinRootBounds.gltf" + }); + const { defaultSceneRoot, skins } = glTFResource; + const characterGroup = defaultSceneRoot.children[0]; + const characterMesh = characterGroup.children[0]; + const renderer = characterMesh.getComponent(SkinnedMeshRenderer); + + expect(skins[0].rootBone).to.equal(characterGroup); + expect(renderer.localBounds.min.x).to.be.closeTo(6, 1e-5); + expect(renderer.localBounds.max.x).to.be.closeTo(8, 1e-5); + expect(renderer.bounds.min.x).to.be.closeTo(9, 1e-5); + expect(renderer.bounds.max.x).to.be.closeTo(11, 1e-5); + }); }); describe("glTF instance test", function () { From 5fa24b134295fa16d823c0c5751e14b337644a65 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Tue, 19 May 2026 16:28:57 +0800 Subject: [PATCH 091/130] fix(physics): correct collision contact normal orientation --- .gitignore | 2 +- packages/core/src/physics/Collision.ts | 3 +- tests/src/core/physics/Collision.test.ts | 89 ++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 249ab8c2d5..d8f73e6c8e 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,7 @@ playwright-report/ # Claude Code files .claude/ CLAUDE.md - +docs/superpowers .husky/post-checkout .husky/post-commit .husky/pre-push diff --git a/packages/core/src/physics/Collision.ts b/packages/core/src/physics/Collision.ts index 16edfd5bb3..440864e4fe 100644 --- a/packages/core/src/physics/Collision.ts +++ b/packages/core/src/physics/Collision.ts @@ -29,8 +29,7 @@ export class Collision { */ getContacts(outContacts: ContactPoint[]): number { const nativeCollision = this._nativeCollision; - const smallerShapeId = Math.min(nativeCollision.shape0Id, nativeCollision.shape1Id); - const factor = this.shape.id === smallerShapeId ? 1 : -1; + const factor = this.shape.id === nativeCollision.shape1Id ? 1 : -1; const nativeContactPoints = nativeCollision.getContacts(); const length = nativeContactPoints.size(); for (let i = 0; i < length; i++) { diff --git a/tests/src/core/physics/Collision.test.ts b/tests/src/core/physics/Collision.test.ts index 218b2273aa..6f7e886df3 100644 --- a/tests/src/core/physics/Collision.test.ts +++ b/tests/src/core/physics/Collision.test.ts @@ -5,6 +5,7 @@ import { Entity, Engine, Script, + SphereColliderShape, StaticCollider } from "@galacean/engine-core"; import { Vector3 } from "@galacean/engine-math"; @@ -30,6 +31,20 @@ describe("Collision", function () { return boxEntity; } + function addSphere(radius: number, pos: Vector3) { + const sphereEntity = rootEntity.createChild("SphereEntity"); + sphereEntity.transform.setPosition(pos.x, pos.y, pos.z); + + const sphereShape = new SphereColliderShape(); + sphereShape.material.dynamicFriction = 0; + sphereShape.material.staticFriction = 0; + sphereShape.radius = radius; + const sphereCollider = sphereEntity.addComponent(DynamicCollider); + sphereCollider.addShape(sphereShape); + sphereCollider.useGravity = false; + return sphereEntity; + } + function formatValue(value: number) { return Math.round(value * 100000) / 100000; } @@ -173,6 +188,80 @@ describe("Collision", function () { }); }); + it("reports contact normal from static other shape to dynamic self shape", function () { + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, 0, 0); + const dynamicBox = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(-3, 0, 0)); + const staticBox = addBox(new Vector3(1, 1, 1), StaticCollider, new Vector3(0, 0, 0)); + + return new Promise((done) => { + dynamicBox.addComponent( + class extends Script { + onCollisionEnter(other: Collision): void { + expect(other.shape).toBe(staticBox.getComponent(StaticCollider).shapes[0]); + const contacts = []; + other.getContacts(contacts); + expect(contacts.length).toBeGreaterThan(0); + expect(formatValue(contacts[0].normal.x)).toBe(-1); + + done(); + } + } + ); + + dynamicBox.getComponent(DynamicCollider).applyForce(new Vector3(1000, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + }); + }); + + it("reports billiard hitBall sphere normal from kinematic other to dynamic target self", function () { + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, 0, 0); + const targetBall = addSphere(0.5, new Vector3(0, 0, 0)); + const hitBall = addSphere(0.5, new Vector3(-3, 0, 0)); + const hitCollider = hitBall.getComponent(DynamicCollider); + hitCollider.isKinematic = true; + + return new Promise((done) => { + targetBall.addComponent( + class extends Script { + onCollisionEnter(other: Collision): void { + expect(other.shape).toBe(hitCollider.shapes[0]); + const contacts = []; + other.getContacts(contacts); + expect(contacts.length).toBeGreaterThan(0); + + const contactNormal = contacts[0].normal; + expect(formatValue(contactNormal.x)).toBe(1); + expect(formatValue(contactNormal.y)).toBe(0); + expect(formatValue(contactNormal.z)).toBe(0); + + const hitToTarget = new Vector3(); + Vector3.subtract(targetBall.transform.worldPosition, hitBall.transform.worldPosition, hitToTarget); + hitToTarget.normalize(); + expect(formatValue(Vector3.dot(contactNormal, hitToTarget))).toBe(1); + + const scaledNormal = new Vector3(); + Vector3.scale(contactNormal, 2 * Vector3.dot(hitToTarget, contactNormal), scaledNormal); + const reflected = new Vector3(); + Vector3.subtract(hitToTarget, scaledNormal, reflected); + reflected.normalize(); + expect(formatValue(reflected.x)).toBe(-1); + + done(); + } + } + ); + + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + hitCollider.move(new Vector3(-0.9, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + }); + }); + // ────────────────────────────────────────────────────────────────────────────── // Kinematic-pair collision callback (3D billiard aim-line use case). // PhysX 4.x defaults suppress kineKine + staticKine pairs. SceneBinding already From 489238ee6a926a44d34edb7805cb3f117f7e57d6 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 19 May 2026 12:11:22 +0800 Subject: [PATCH 092/130] feat(loader): recognize .m4a extension for audioClip loader add "m4a" to AudioLoader's resourceLoader extension list so that loading audio via url without explicit type resolves to AssetType.Audio for .m4a files. also update AssetType.Audio doc comment to mention m4a. decoding already works out of the box: AudioContext.decodeAudioData natively supports MP4-container audio (AAC) on Chromium / WebKit / Gecko. before: resourceManager.load("clip.m4a") -> "asset type should be specified" after: resourceManager.load("clip.m4a") -> resolves AudioClip --- packages/core/src/asset/AssetType.ts | 2 +- packages/loader/src/AudioLoader.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/asset/AssetType.ts b/packages/core/src/asset/AssetType.ts index 17ee100043..39c83b0d82 100644 --- a/packages/core/src/asset/AssetType.ts +++ b/packages/core/src/asset/AssetType.ts @@ -46,7 +46,7 @@ export enum AssetType { Font = "Font", /** Source Font, include ttf, otf and woff. */ SourceFont = "SourceFont", - /** AudioClip, include ogg, wav and mp3. */ + /** AudioClip, include ogg, wav, mp3 and m4a. */ Audio = "Audio", /** Project asset. */ Project = "project", diff --git a/packages/loader/src/AudioLoader.ts b/packages/loader/src/AudioLoader.ts index 7233fb8e0f..3f382282d3 100644 --- a/packages/loader/src/AudioLoader.ts +++ b/packages/loader/src/AudioLoader.ts @@ -9,7 +9,7 @@ import { ResourceManager, resourceLoader } from "@galacean/engine-core"; -@resourceLoader(AssetType.Audio, ["mp3", "ogg", "wav", "audio"]) +@resourceLoader(AssetType.Audio, ["mp3", "ogg", "wav", "audio", "m4a"]) class AudioLoader extends Loader { load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { return new AssetPromise((resolve, reject) => { From 9de8ab093acd284ddb55b5fa1a37f64c8237859e Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 19 May 2026 21:36:39 +0800 Subject: [PATCH 093/130] fix(instancing): use cofactor-form normal matrix to survive singular models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `renderer_NormalMat` was emitted as `mat4(transpose(inverse(mat3(renderer_ModelMat))))`. GLSL `inverse()` is undefined for singular matrices, and any zero scale axis (e.g. an animation hiding an entity via `scale = (1, 0, 1)`) makes the 3x3 model matrix singular — drivers typically return NaN/Inf, which then contaminates the normal, the lighting, and finally the whole fragment. The plain uniform path is protected by `Matrix.invert`'s `if (!det) return null` early-out, but instancing recomputes the matrix on the GPU each draw with no such guard, so the same scene that renders (with stale-but-finite normals) without instancing went all-black with instancing. Cofactor (cross-product) form equals `det(M) · transpose(inverse(M))`, so it matches the classic formula in direction after `normalize()` but avoids the divide-by-det entirely. Aligned with Filament / Unreal. --- packages/core/src/shaderlib/ShaderFactory.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 678eb1b55b..9e62a3fc25 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -57,10 +57,23 @@ export class ShaderFactory { #endif `; + // Derived built-ins re-exposed on top of `renderer_ModelMat`. + // `renderer_NormalMat` uses the cofactor (cross-product) form, which algebraically equals + // `det(M) · transpose(inverse(M))`. After `normalize()` it's directionally identical to the + // classic `transpose(inverse(M))`, but stays NaN-free when `M` is singular (e.g. any scale + // axis is 0 — common in animations that pop / hide via scale). `sign(det)` (`s` below) + // keeps mirrored matrices facing the right way private static readonly _derivedDefines = `\ +mat3 _normalMatFromModel(mat3 m) { + vec3 c0 = cross(m[1], m[2]); + vec3 c1 = cross(m[2], m[0]); + vec3 c2 = cross(m[0], m[1]); + float s = (dot(m[0], c0) < 0.0) ? -1.0 : 1.0; + return mat3(c0 * s, c1 * s, c2 * s); +} #define renderer_MVMat (camera_ViewMat * renderer_ModelMat) #define renderer_MVPMat (camera_VPMat * renderer_ModelMat) -#define renderer_NormalMat mat4(transpose(inverse(mat3(renderer_ModelMat))))`; +#define renderer_NormalMat mat4(_normalMatFromModel(mat3(renderer_ModelMat)))`; // Built-in renderer uniforms. value=true means derived (remove but not added to UBO) private static readonly _builtinRendererUniforms: Record = { From 7c521e2a00e6f6e0dc095d4dc782db99590cab0e Mon Sep 17 00:00:00 2001 From: ChenMo Date: Tue, 19 May 2026 19:07:56 +0800 Subject: [PATCH 094/130] feat(loader): recognize .aac and .flac extensions for audioClip loader (#3009) add "aac" and "flac" to AudioLoader's resourceLoader extension list so --- packages/core/src/asset/AssetType.ts | 2 +- packages/loader/src/AudioLoader.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/asset/AssetType.ts b/packages/core/src/asset/AssetType.ts index 39c83b0d82..fad0e75043 100644 --- a/packages/core/src/asset/AssetType.ts +++ b/packages/core/src/asset/AssetType.ts @@ -46,7 +46,7 @@ export enum AssetType { Font = "Font", /** Source Font, include ttf, otf and woff. */ SourceFont = "SourceFont", - /** AudioClip, include ogg, wav, mp3 and m4a. */ + /** AudioClip, include ogg, wav, mp3, m4a, aac and flac. */ Audio = "Audio", /** Project asset. */ Project = "project", diff --git a/packages/loader/src/AudioLoader.ts b/packages/loader/src/AudioLoader.ts index 3f382282d3..29cd7d40b7 100644 --- a/packages/loader/src/AudioLoader.ts +++ b/packages/loader/src/AudioLoader.ts @@ -9,7 +9,7 @@ import { ResourceManager, resourceLoader } from "@galacean/engine-core"; -@resourceLoader(AssetType.Audio, ["mp3", "ogg", "wav", "audio", "m4a"]) +@resourceLoader(AssetType.Audio, ["mp3", "ogg", "wav", "audio", "m4a", "aac", "flac"]) class AudioLoader extends Loader { load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { return new AssetPromise((resolve, reject) => { From e659773e439c94df84259248891157dc30c57f88 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Wed, 20 May 2026 15:10:54 +0800 Subject: [PATCH 095/130] chore: release v0.0.0-experimental-2.0-game.13 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 74c7f5ac8d..e72651ca60 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index 92d67e5b13..ff62fe13c8 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 17f1389f7c..e9fda21825 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index a360507405..2fe70866ab 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index acca2d9ca6..dbdb0635e9 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index 8d205d8912..8d9add4530 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index a6fae3addd..cef97e1b6e 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index c9e3396034..0289f7a4f2 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index e0d0cfc6a8..964f090e35 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 7e1f9505b0..b1acc44e84 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index af68abc7de..66f820e31e 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 54fb32b0b9..5d7a5f85e4 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index 434df88749..bfc54146f5 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index f01a653619..977f417636 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 5dd9aa8b8c..0d311abba0 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 041a72dec9..84016aea03 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index c9aa753c23..5ca3acc209 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.12", + "version": "0.0.0-experimental-2.0-game.13", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From d56aa982a7c0c80258181cba64a4d19c86388a57 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Wed, 20 May 2026 17:42:23 +0800 Subject: [PATCH 096/130] feat: update animator --- packages/core/src/animation/Animator.ts | 18 ++- .../internal/AnimatorStatePlayData.ts | 12 +- tests/src/core/Animator.test.ts | 104 ++++++++++++++++++ 3 files changed, 121 insertions(+), 13 deletions(-) diff --git a/packages/core/src/animation/Animator.ts b/packages/core/src/animation/Animator.ts index 544d40b27e..ecaddd19ed 100644 --- a/packages/core/src/animation/Animator.ts +++ b/packages/core/src/animation/Animator.ts @@ -19,6 +19,7 @@ import { AnimatorCullingMode } from "./enums/AnimatorCullingMode"; import { AnimatorLayerBlendingMode } from "./enums/AnimatorLayerBlendingMode"; import { AnimatorStatePlayState } from "./enums/AnimatorStatePlayState"; import { LayerState } from "./enums/LayerState"; +import { WrapMode } from "./enums/WrapMode"; import { AnimationCurveLayerOwner } from "./internal/AnimationCurveLayerOwner"; import { AnimationEventHandler } from "./internal/AnimationEventHandler"; import { AnimatorLayerData } from "./internal/AnimatorLayerData"; @@ -220,7 +221,7 @@ export class Animator extends Component { */ /** * Find the per-instance play data for a state by name. - * The returned object's `speed` is per-instance and safe to modify without affecting other Animator instances. + * The returned object's `speed` and `wrapMode` are per-instance and safe to modify without affecting other Animator instances. * @param stateName - The state name * @param layerIndex - The layer index (default -1, searches all layers) * @returns Per-instance AnimatorStatePlayData, or null if not found @@ -1470,23 +1471,28 @@ export class Animator extends Component { lastClipTime: number, deltaTime: number ): void { - const { state, isForward, clipTime } = playData; + const { state, isForward, clipTime, wrapMode } = playData; const startTime = state._getClipActualStartTime(); const endTime = state._getClipActualEndTime(); + const canWrap = wrapMode === WrapMode.Loop; if (isForward) { if (lastClipTime + deltaTime >= endTime) { this._fireSubAnimationEvents(playData, eventHandlers, lastClipTime, endTime); - playData.currentEventIndex = 0; - this._fireSubAnimationEvents(playData, eventHandlers, startTime, clipTime); + if (canWrap) { + playData.currentEventIndex = 0; + this._fireSubAnimationEvents(playData, eventHandlers, startTime, clipTime); + } } else { this._fireSubAnimationEvents(playData, eventHandlers, lastClipTime, clipTime); } } else { if (lastClipTime + deltaTime <= startTime) { this._fireBackwardSubAnimationEvents(playData, eventHandlers, lastClipTime, startTime); - playData.currentEventIndex = eventHandlers.length - 1; - this._fireBackwardSubAnimationEvents(playData, eventHandlers, endTime, clipTime); + if (canWrap) { + playData.currentEventIndex = eventHandlers.length - 1; + this._fireBackwardSubAnimationEvents(playData, eventHandlers, endTime, clipTime); + } } else { this._fireBackwardSubAnimationEvents(playData, eventHandlers, lastClipTime, clipTime); } diff --git a/packages/core/src/animation/internal/AnimatorStatePlayData.ts b/packages/core/src/animation/internal/AnimatorStatePlayData.ts index 5cfa2fd3eb..7adc90c2f4 100644 --- a/packages/core/src/animation/internal/AnimatorStatePlayData.ts +++ b/packages/core/src/animation/internal/AnimatorStatePlayData.ts @@ -9,7 +9,7 @@ import { AnimatorStateData } from "./AnimatorStateData"; /** * Per-instance runtime data for an AnimatorState. * Proxies read-only properties from the shared AnimatorState asset, - * while providing per-instance mutable properties (e.g. speed). + * while providing per-instance mutable properties (e.g. speed, wrapMode). */ export class AnimatorStatePlayData { /** @internal */ @@ -29,6 +29,8 @@ export class AnimatorStatePlayData { offsetFrameTime: number; /** Per-instance speed. Initialized from AnimatorState.speed, safe to modify without affecting other instances. */ speed: number = 1.0; + /** Per-instance wrap mode. Initialized from AnimatorState.wrapMode, safe to modify without affecting other instances. */ + wrapMode: WrapMode = WrapMode.Loop; // ── Proxy properties from AnimatorState (read-only) ── @@ -42,11 +44,6 @@ export class AnimatorStatePlayData { return this.state.clip; } - /** The wrap mode. */ - get wrapMode(): WrapMode { - return this.state.wrapMode; - } - /** The transitions going out of this state. */ get transitions(): Readonly { return this.state.transitions; @@ -71,6 +68,7 @@ export class AnimatorStatePlayData { this.currentEventIndex = 0; this.isForward = true; this.speed = state.speed; + this.wrapMode = state.wrapMode; this.state._transitionCollection.needResetCurrentCheckIndex = true; } @@ -91,7 +89,7 @@ export class AnimatorStatePlayData { let time = this.playedTime + this.offsetFrameTime; const duration = state._getDuration(); this.playState = AnimatorStatePlayState.Playing; - if (state.wrapMode === WrapMode.Loop) { + if (this.wrapMode === WrapMode.Loop) { time = duration ? time % duration : 0; } else { if (Math.abs(time) >= duration) { diff --git a/tests/src/core/Animator.test.ts b/tests/src/core/Animator.test.ts index cb2bfa05d1..69026b5a1e 100644 --- a/tests/src/core/Animator.test.ts +++ b/tests/src/core/Animator.test.ts @@ -269,6 +269,58 @@ describe("Animator test", function () { } }); + it("playData wrapMode overrides shared AnimatorState wrapMode per instance", () => { + const sharedWalkState = animator.animatorController.layers[0].stateMachine.states.find( + (state) => state.name === "Walk" + ); + const oldWrapMode = sharedWalkState.wrapMode; + + try { + sharedWalkState.wrapMode = WrapMode.Loop; + animator.play("Walk"); + + const layerData = animator["_animatorLayersData"][0]; + const playData = layerData.srcPlayData; + playData.wrapMode = WrapMode.Once; + + expect(sharedWalkState.wrapMode).to.eq(WrapMode.Loop); + + // @ts-ignore + animator.engine.time._frameCount++; + animator.update(playData.state.clip.length + 0.1); + + expect(layerData.layerState).to.eq(LayerState.Finished); + } finally { + sharedWalkState.wrapMode = oldWrapMode; + } + }); + + it("playData wrapMode does not leak between animators sharing one controller", () => { + const sharedWalkState = animator.animatorController.layers[0].stateMachine.states.find( + (state) => state.name === "Walk" + ); + const oldWrapMode = sharedWalkState.wrapMode; + const otherEntity = new Entity(engine); + const otherAnimator = otherEntity.addComponent(Animator); + otherAnimator.animatorController = animator.animatorController; + + try { + sharedWalkState.wrapMode = WrapMode.Loop; + animator.play("Walk"); + otherAnimator.play("Walk"); + + const playData = animator["_animatorLayersData"][0].srcPlayData; + const otherPlayData = otherAnimator["_animatorLayersData"][0].srcPlayData; + playData.wrapMode = WrapMode.Once; + + expect(otherPlayData.wrapMode).to.eq(WrapMode.Loop); + expect(sharedWalkState.wrapMode).to.eq(WrapMode.Loop); + } finally { + sharedWalkState.wrapMode = oldWrapMode; + otherEntity.destroy(); + } + }); + it("cross fade in fixed time", () => { const runState = animator.findAnimatorState("Run"); animator.play("Walk"); @@ -397,6 +449,58 @@ describe("Animator test", function () { expect(testScriptSpy).toHaveBeenCalledTimes(1); }); + it("does not refire animation events when a once clip reaches the end", () => { + const entity = new Entity(engine); + const onceAnimator = entity.addComponent(Animator); + const controller = new AnimatorController(engine); + const layer = new AnimatorControllerLayer("Base Layer"); + controller.addLayer(layer); + + const state = layer.stateMachine.addState("once"); + state.wrapMode = WrapMode.Once; + + const clip = new AnimationClip("once-clip"); + const curve = new AnimationFloatCurve(); + const start = new Keyframe(); + const end = new Keyframe(); + start.time = 0; + start.value = 0; + end.time = 1; + end.value = 1; + curve.addKey(start); + curve.addKey(end); + clip.addCurveBinding("", Transform, "position.x", curve); + + class TestScript extends Script { + event0(): void {} + } + + const event0 = new AnimationEvent(); + event0.functionName = "event0"; + event0.time = 0.5; + clip.addEvent(event0); + state.clip = clip; + onceAnimator.animatorController = controller; + + const testScript = entity.addComponent(TestScript); + const testScriptSpy = vi.spyOn(testScript, "event0"); + + try { + onceAnimator.play("once"); + // @ts-ignore + onceAnimator.engine.time._frameCount++; + onceAnimator.update(0.75); + expect(testScriptSpy).toHaveBeenCalledTimes(1); + + // @ts-ignore + onceAnimator.engine.time._frameCount++; + onceAnimator.update(0.5); + expect(testScriptSpy).toHaveBeenCalledTimes(1); + } finally { + entity.destroy(); + } + }); + it("stateMachine", () => { animator.animatorController.addParameter("playerSpeed", 1); const stateMachine = animator.animatorController.layers[0].stateMachine; From 1c3318c7bccb53552c5ed740bb687e34fdaa7d7e Mon Sep 17 00:00:00 2001 From: hhhhkrx Date: Thu, 21 May 2026 16:08:30 +0800 Subject: [PATCH 097/130] fix(particle): only accumulate rotation-over-lifetime onto Z axis Previously the per-frame rotation delta was added to all components of the rotation vector, causing unintended rotation on X/Y. Restrict the accumulation to the Z component to match the intended behavior. --- .../src/shaderlib/particle/rotation_over_lifetime_module.glsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/shaderlib/particle/rotation_over_lifetime_module.glsl b/packages/core/src/shaderlib/particle/rotation_over_lifetime_module.glsl index 226848bba8..66e4740d54 100644 --- a/packages/core/src/shaderlib/particle/rotation_over_lifetime_module.glsl +++ b/packages/core/src/shaderlib/particle/rotation_over_lifetime_module.glsl @@ -77,7 +77,7 @@ vec3 computeParticleRotationVec3(in vec3 rotation, in float age, in float normal #else float ageRot = renderer_ROLMaxConst.z * age; #endif - rotation += ageRot; + rotation.z += ageRot; #endif #ifdef RENDERER_ROL_CURVE_MODE @@ -86,7 +86,7 @@ vec3 computeParticleRotationVec3(in vec3 rotation, in float age, in float normal #ifdef RENDERER_ROL_IS_RANDOM_TWO lifeRotation = mix(evaluateParticleCurveCumulative(renderer_ROLMinCurveZ, normalizedAge, currentValue), lifeRotation, a_Random0.w); #endif - rotation += lifeRotation * a_ShapePositionStartLifeTime.w; + rotation.z += lifeRotation * a_ShapePositionStartLifeTime.w; #endif #endif return rotation; From 8c07cb8a139071694e3018c6fc16546865d1f01a Mon Sep 17 00:00:00 2001 From: luzhuang Date: Thu, 21 May 2026 16:43:04 +0800 Subject: [PATCH 098/130] fix(physics): support kinematic transform teleport sync --- packages/core/src/physics/DynamicCollider.ts | 42 ++++++++++++++++---- packages/core/src/physics/index.ts | 7 +++- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/core/src/physics/DynamicCollider.ts b/packages/core/src/physics/DynamicCollider.ts index 8a2912f8f8..2a7d6de009 100644 --- a/packages/core/src/physics/DynamicCollider.ts +++ b/packages/core/src/physics/DynamicCollider.ts @@ -35,6 +35,8 @@ export class DynamicCollider extends Collider { private _isKinematic = false; private _constraints: DynamicColliderConstraints = 0; private _collisionDetectionMode: CollisionDetectionMode = CollisionDetectionMode.Discrete; + private _kinematicTransformSyncMode: DynamicColliderKinematicTransformSyncMode = + DynamicColliderKinematicTransformSyncMode.Target; private _sleepThreshold = 5e-3; private _automaticCenterOfMass = true; private _automaticInertiaTensor = true; @@ -327,6 +329,22 @@ export class DynamicCollider extends Collider { } } + /** + * Controls how entity transform changes are synchronized to a kinematic native actor. + * + * @remarks + * `Target` routes transform changes through {@link move}, so PhysX treats the + * actor as moving between frames and can generate swept contacts. `Teleport` + * writes the native pose directly and does not imply velocity. + */ + get kinematicTransformSyncMode(): DynamicColliderKinematicTransformSyncMode { + return this._kinematicTransformSyncMode; + } + + set kinematicTransformSyncMode(value: DynamicColliderKinematicTransformSyncMode) { + this._kinematicTransformSyncMode = value; + } + /** * @internal */ @@ -471,18 +489,15 @@ export class DynamicCollider extends Collider { * collision detection, use setKinematicTarget() instead." * * setGlobalPose is a teleport: PhysX skips contact detection between the old - * and new pose, so two kinematic actors moved onto each other via entity.transform - * mutation would NOT produce onCollisionEnter / onCollisionStay events even when - * scene.kineKineFilteringMode = eKEEP. Routing the per-frame sync through - * IDynamicCollider.move() (which the PhysX backend implements as - * setKinematicTarget) tells PhysX the actor is animating to the target during the - * next simulate(), enabling sweep contact detection for kine-kine and kine-dynamic - * pairs alike. + * and new pose. setKinematicTarget tells PhysX the actor is animating to the + * target during the next simulate(), enabling swept contacts. Some compatibility + * layers need transform writes to stay teleport-like, so the sync mode is + * explicit while {@link move} always keeps target semantics. * * @internal */ protected override _syncEntityTransformToNative(worldPosition: Vector3, worldRotation: Quaternion): void { - if (this._isKinematic) { + if (this._isKinematic && this._kinematicTransformSyncMode === DynamicColliderKinematicTransformSyncMode.Target) { (this._nativeCollider).move(worldPosition, worldRotation); } else { super._syncEntityTransformToNative(worldPosition, worldRotation); @@ -516,6 +531,7 @@ export class DynamicCollider extends Collider { target._angularVelocity.copyFrom(this.angularVelocity); target._centerOfMass.copyFrom(this.centerOfMass); target._inertiaTensor.copyFrom(this.inertiaTensor); + target._kinematicTransformSyncMode = this._kinematicTransformSyncMode; super._cloneTo(target); } @@ -611,6 +627,16 @@ export enum CollisionDetectionMode { ContinuousSpeculative } +/** + * Kinematic transform synchronization mode. + */ +export enum DynamicColliderKinematicTransformSyncMode { + /** Synchronize transform changes through PhysX setKinematicTarget. */ + Target, + /** Synchronize transform changes by directly teleporting the native actor. */ + Teleport +} + /** * Use these flags to constrain motion of dynamic collider. */ diff --git a/packages/core/src/physics/index.ts b/packages/core/src/physics/index.ts index 537bd367d6..f2d4cdf576 100644 --- a/packages/core/src/physics/index.ts +++ b/packages/core/src/physics/index.ts @@ -1,6 +1,11 @@ export { CharacterController } from "./CharacterController"; export { Collider } from "./Collider"; -export { CollisionDetectionMode, DynamicCollider, DynamicColliderConstraints } from "./DynamicCollider"; +export { + CollisionDetectionMode, + DynamicCollider, + DynamicColliderConstraints, + DynamicColliderKinematicTransformSyncMode +} from "./DynamicCollider"; export { HitResult } from "./HitResult"; export { PhysicsMaterial } from "./PhysicsMaterial"; export { PhysicsScene } from "./PhysicsScene"; From d9b724bbe4ec559a4ad7fde297789c5905f0f095 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Thu, 21 May 2026 17:38:20 +0800 Subject: [PATCH 099/130] fix(loader): apply scene physics settings --- packages/loader/src/SceneLoader.ts | 9 ++++ .../resources/schema/SceneSchema.ts | 4 ++ tests/src/loader/ScenePhysics.test.ts | 50 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 tests/src/loader/ScenePhysics.test.ts diff --git a/packages/loader/src/SceneLoader.ts b/packages/loader/src/SceneLoader.ts index 069b2db6e2..b16fb2cc92 100644 --- a/packages/loader/src/SceneLoader.ts +++ b/packages/loader/src/SceneLoader.ts @@ -128,6 +128,15 @@ class SceneLoader extends Loader { if (fog.fogColor != undefined) scene.fogColor.copyFrom(fog.fogColor); } + // parse physics + const physics = data.scene.physics; + // PhysicsScene has a native backing only when the engine was created with a physics backend. + // Keep scene files loadable for render-only engines by ignoring serialized physics settings there. + if (physics && (engine as any)._physicsInitialized) { + if (physics.gravity != undefined) scene.physics.gravity.copyFrom(physics.gravity); + if (physics.fixedTimeStep != undefined) scene.physics.fixedTimeStep = physics.fixedTimeStep; + } + // Post Process const postProcessData = data.scene.postProcess; if (postProcessData) { diff --git a/packages/loader/src/resource-deserialize/resources/schema/SceneSchema.ts b/packages/loader/src/resource-deserialize/resources/schema/SceneSchema.ts index 81aaff21d6..1f64dcb87c 100644 --- a/packages/loader/src/resource-deserialize/resources/schema/SceneSchema.ts +++ b/packages/loader/src/resource-deserialize/resources/schema/SceneSchema.ts @@ -54,6 +54,10 @@ export interface IScene extends IHierarchyFile { fogDensity: number; fogColor: IColor; }; + physics?: { + gravity?: IVector3; + fixedTimeStep?: number; + }; postProcess?: { isActive: boolean; bloom: { diff --git a/tests/src/loader/ScenePhysics.test.ts b/tests/src/loader/ScenePhysics.test.ts new file mode 100644 index 0000000000..74a1a452cf --- /dev/null +++ b/tests/src/loader/ScenePhysics.test.ts @@ -0,0 +1,50 @@ +import { AssetType, BackgroundMode, Scene } from "@galacean/engine"; +import "@galacean/engine-loader"; +import { PhysXPhysics } from "@galacean/engine-physics-physx"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; + +let engine: WebGLEngine; + +beforeAll(async () => { + engine = await WebGLEngine.create({ + canvas: document.createElement("canvas"), + physics: new PhysXPhysics() + }); +}); + +afterAll(() => { + engine?.destroy(); +}); + +describe("SceneLoader physics settings", () => { + it("applies serialized scene physics settings to PhysicsScene", async () => { + const sceneData = { + name: "physics-scene", + entities: [], + scene: { + background: { + mode: BackgroundMode.SolidColor, + color: { r: 0, g: 0, b: 0, a: 1 } + }, + physics: { + gravity: { x: 0, y: -3200, z: 0 }, + fixedTimeStep: 1 / 120 + } + }, + files: [] + }; + const sceneUrl = + URL.createObjectURL(new Blob([JSON.stringify(sceneData)], { type: "application/json" })) + "#.scene"; + + const scene = await engine.resourceManager.load({ + url: sceneUrl, + type: AssetType.Scene + }); + + expect(scene.physics.gravity.x).toBe(0); + expect(scene.physics.gravity.y).toBe(-3200); + expect(scene.physics.gravity.z).toBe(0); + expect(scene.physics.fixedTimeStep).toBe(1 / 120); + }); +}); From bab0ae0a245d35617e52c260c4d106f2fbbb65c0 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 21 May 2026 23:38:13 +0800 Subject: [PATCH 100/130] fix(physics): teleport collider on re-enable instead of sweeping A disabled collider's native actor is detached from the simulation scene but not destroyed, so on re-enable it still holds its pre-disable pose. The first per-frame sync would route a kinematic actor through a swept move from that stale pose, dragging it across the scene and firing spurious contact events along the path. Mark a pending teleport in _onEnableInScene and consume it on the next _onUpdate, so the first sync after (re-)enable teleports instead of sweeps. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/core/src/physics/Collider.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/core/src/physics/Collider.ts b/packages/core/src/physics/Collider.ts index 173725b5c5..befac2db52 100644 --- a/packages/core/src/physics/Collider.ts +++ b/packages/core/src/physics/Collider.ts @@ -29,6 +29,16 @@ export class Collider extends Component implements ICustomClone { protected _shapes: ColliderShape[] = []; protected _collisionLayerIndex: number = 0; + /** + * Disabling a collider only detaches its native actor from the simulation + * scene; the actor is not destroyed, so on re-enable it still holds its + * pre-disable pose. The first transform sync after (re-)enable must teleport, + * never sweep — otherwise a kinematic actor drags across the scene from that + * stale pose and fires spurious contacts along the path. + */ + @ignoreClone + private _pendingReenterSync: boolean = false; + /** * The shapes of this collider. */ @@ -110,9 +120,14 @@ export class Collider extends Component implements ICustomClone { */ _onUpdate(): void { const shapes = this._shapes; - if (this._updateFlag.flag) { + if (this._pendingReenterSync || this._updateFlag.flag) { const { transform } = this.entity; - this._syncEntityTransformToNative(transform.worldPosition, transform.worldRotationQuaternion); + if (this._pendingReenterSync) { + this._teleportToEntityTransform(transform.worldPosition, transform.worldRotationQuaternion); + this._pendingReenterSync = false; + } else { + this._syncEntityTransformToNative(transform.worldPosition, transform.worldRotationQuaternion); + } const worldScale = transform.lossyWorldScale; for (let i = 0, n = shapes.length; i < n; i++) { @@ -139,6 +154,7 @@ export class Collider extends Component implements ICustomClone { */ override _onEnableInScene(): void { this.scene.physics._addCollider(this); + this._pendingReenterSync = true; } /** From 0819f3e1b3104468497da1689be9c23fb63c8e14 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Thu, 21 May 2026 23:10:00 +0800 Subject: [PATCH 101/130] fix(physics): support PhysX tolerance scale --- notes/2026-05-21-physx-tolerances-scale.md | 32 +++++++++ packages/core/src/physics/DynamicCollider.ts | 8 ++- .../core/src/physics/shape/ColliderShape.ts | 8 ++- packages/design/src/physics/IPhysics.ts | 10 +++ packages/physics-physx/src/PhysXPhysics.ts | 67 ++++++++++++++++++- .../src/shape/PhysXColliderShape.ts | 1 + 6 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 notes/2026-05-21-physx-tolerances-scale.md diff --git a/notes/2026-05-21-physx-tolerances-scale.md b/notes/2026-05-21-physx-tolerances-scale.md new file mode 100644 index 0000000000..1d886b55c9 --- /dev/null +++ b/notes/2026-05-21-physx-tolerances-scale.md @@ -0,0 +1,32 @@ +# PhysX tolerance scale configuration + +## Context + +Cocos 2D migration can run physics in pixel-like units. PhysX supports non-meter +unit systems through `PxTolerancesScale`, but Galacean previously always created +PhysX with default `length=1` and `speed=10`. + +## Code facts + +- `PhysXPhysics._init()` creates one `PxTolerancesScale`, then passes it to both + `PxCreatePhysics` and `PxCookingParams`. +- PhysX requires scene scale to match the scale used to create `PxPhysics`. +- PhysX derives defaults such as contact offset and sleep threshold from this scale. +- Galacean core previously stored fixed defaults: + - `ColliderShape._contactOffset = 0.02` + - `DynamicCollider._sleepThreshold = 0.005` + and synced them unconditionally to native, overriding scaled PhysX defaults. + +## Fix + +- `PhysXPhysics` now accepts `PhysXPhysicsOptions.tolerancesScale`. +- The scale is applied before creating `PxPhysics` and `PxCookingParams`. +- `PhysXPhysics` exposes scaled default contact offset and sleep threshold. +- Core `ColliderShape` and `DynamicCollider` leave native defaults untouched unless + the user explicitly sets `contactOffset` or `sleepThreshold`. + +## Boundary + +This change does not scale damping or solver iterations. Those are not PhysX +tolerance defaults. Callers should still set explicit damping only when the +source project authored it. diff --git a/packages/core/src/physics/DynamicCollider.ts b/packages/core/src/physics/DynamicCollider.ts index 2a7d6de009..debf078b14 100644 --- a/packages/core/src/physics/DynamicCollider.ts +++ b/packages/core/src/physics/DynamicCollider.ts @@ -37,7 +37,7 @@ export class DynamicCollider extends Collider { private _collisionDetectionMode: CollisionDetectionMode = CollisionDetectionMode.Discrete; private _kinematicTransformSyncMode: DynamicColliderKinematicTransformSyncMode = DynamicColliderKinematicTransformSyncMode.Target; - private _sleepThreshold = 5e-3; + private _sleepThreshold: number | undefined; private _automaticCenterOfMass = true; private _automaticInertiaTensor = true; @@ -227,7 +227,7 @@ export class DynamicCollider extends Collider { * The mass-normalized energy threshold, below which objects start going to sleep. */ get sleepThreshold(): number { - return this._sleepThreshold; + return this._sleepThreshold ?? Engine._nativePhysics?.getDefaultSleepThreshold?.() ?? 5e-3; } set sleepThreshold(value: number) { @@ -561,7 +561,9 @@ export class DynamicCollider extends Collider { } (this._nativeCollider).setMaxAngularVelocity(this._maxAngularVelocity); (this._nativeCollider).setMaxDepenetrationVelocity(this._maxDepenetrationVelocity); - (this._nativeCollider).setSleepThreshold(this._sleepThreshold); + if (this._sleepThreshold !== undefined) { + (this._nativeCollider).setSleepThreshold(this._sleepThreshold); + } (this._nativeCollider).setSolverIterations(this._solverIterations); (this._nativeCollider).setUseGravity(this._useGravity); (this._nativeCollider).setIsKinematic(this._isKinematic); diff --git a/packages/core/src/physics/shape/ColliderShape.ts b/packages/core/src/physics/shape/ColliderShape.ts index 921612f5f5..e74736ff64 100644 --- a/packages/core/src/physics/shape/ColliderShape.ts +++ b/packages/core/src/physics/shape/ColliderShape.ts @@ -27,7 +27,7 @@ export abstract class ColliderShape implements ICustomClone { private _rotation: Vector3 = new Vector3(); @deepClone private _position: Vector3 = new Vector3(); - private _contactOffset: number = 0.02; + private _contactOffset: number | undefined; /** * @internal @@ -55,7 +55,7 @@ export abstract class ColliderShape implements ICustomClone { * @defaultValue 0.02 */ get contactOffset(): number { - return this._contactOffset; + return this._contactOffset ?? Engine._nativePhysics?.getDefaultContactOffset?.() ?? 0.02; } set contactOffset(value: number) { @@ -195,7 +195,9 @@ export abstract class ColliderShape implements ICustomClone { if (!this._nativeShape) return; this._nativeShape.setPosition(this._position); this._nativeShape.setRotation(this._rotation); - this._nativeShape.setContactOffset(this._contactOffset); + if (this._contactOffset !== undefined) { + this._nativeShape.setContactOffset(this._contactOffset); + } this._nativeShape.setIsTrigger(this._isTrigger); this._nativeShape.setMaterial(this._material._nativeMaterial); diff --git a/packages/design/src/physics/IPhysics.ts b/packages/design/src/physics/IPhysics.ts index 17c9b6b466..91a6c1c7c6 100644 --- a/packages/design/src/physics/IPhysics.ts +++ b/packages/design/src/physics/IPhysics.ts @@ -36,6 +36,16 @@ export interface IPhysics { */ createPhysicsScene(physicsManager: IPhysicsManager): IPhysicsScene; + /** + * Get the default contact offset for collider shapes. + */ + getDefaultContactOffset?(): number; + + /** + * Get the default sleep threshold for dynamic colliders. + */ + getDefaultSleepThreshold?(): number; + /** * Create dynamic collider. * @param position - The global position diff --git a/packages/physics-physx/src/PhysXPhysics.ts b/packages/physics-physx/src/PhysXPhysics.ts index 6c5d788aa1..867ee68dff 100644 --- a/packages/physics-physx/src/PhysXPhysics.ts +++ b/packages/physics-physx/src/PhysXPhysics.ts @@ -57,13 +57,26 @@ export class PhysXPhysics implements IPhysics { private _tolerancesScale: any; private _wasmSIMDModeUrl: string; private _wasmModeUrl: string; + private _tolerancesScaleOptions: PhysXTolerancesScale | undefined; + private _defaultContactOffset = 0.02; + private _defaultSleepThreshold = 5e-3; /** * Create a PhysXPhysics instance. * @param runtimeMode - Runtime mode, `Auto` prefers WebAssembly SIMD if supported @see {@link PhysXRuntimeMode} * @param runtimeUrls - Manually specify the runtime URLs + * @param options - PhysX options. */ - constructor(runtimeMode: PhysXRuntimeMode = PhysXRuntimeMode.Auto, runtimeUrls?: PhysXRuntimeUrls) { + constructor(runtimeMode?: PhysXRuntimeMode, runtimeUrls?: PhysXRuntimeUrls, options?: PhysXPhysicsOptions); + constructor(options?: PhysXPhysicsOptions); + constructor( + runtimeModeOrOptions: PhysXRuntimeMode | PhysXPhysicsOptions = PhysXRuntimeMode.Auto, + runtimeUrls?: PhysXRuntimeUrls, + options?: PhysXPhysicsOptions + ) { + const isOptionsObject = typeof runtimeModeOrOptions === "object"; + const runtimeMode = isOptionsObject ? PhysXRuntimeMode.Auto : (runtimeModeOrOptions ?? PhysXRuntimeMode.Auto); + const resolvedOptions = isOptionsObject ? runtimeModeOrOptions : options; this._runTimeMode = runtimeMode; this._wasmSIMDModeUrl = runtimeUrls?.wasmSIMDModeUrl ?? @@ -71,6 +84,8 @@ export class PhysXPhysics implements IPhysics { this._wasmModeUrl = runtimeUrls?.wasmModeUrl ?? "https://mdn.alipayobjects.com/rms/afts/file/A*DFuvR6Mv5C0AAAAAQ4AAAAgAehQnAQ/physx.release.js"; + this._tolerancesScaleOptions = resolvedOptions?.tolerancesScale; + this._updateScaledDefaults(this._tolerancesScaleOptions?.length ?? 1, this._tolerancesScaleOptions?.speed ?? 10); } /** @@ -156,6 +171,20 @@ export class PhysXPhysics implements IPhysics { return scene; } + /** + * {@inheritDoc IPhysics.getDefaultContactOffset } + */ + getDefaultContactOffset(): number { + return this._defaultContactOffset; + } + + /** + * {@inheritDoc IPhysics.getDefaultSleepThreshold } + */ + getDefaultSleepThreshold(): number { + return this._defaultSleepThreshold; + } + /** * {@inheritDoc IPhysics.createStaticCollider } */ @@ -279,6 +308,7 @@ export class PhysXPhysics implements IPhysics { const allocator = new physX.PxDefaultAllocator(); const pxFoundation = physX.PxCreateFoundation(version, allocator, defaultErrorCallback); const tolerancesScale = new physX.PxTolerancesScale(); + this._applyTolerancesScale(tolerancesScale); const pxPhysics = physX.PxCreatePhysics(version, pxFoundation, tolerancesScale, false, null); physX.PxInitExtensions(pxPhysics, null); @@ -302,6 +332,29 @@ export class PhysXPhysics implements IPhysics { this._allocator = allocator; this._tolerancesScale = tolerancesScale; } + + private _applyTolerancesScale(tolerancesScale: any): void { + const length = this._tolerancesScaleOptions?.length ?? tolerancesScale.length; + const speed = this._tolerancesScaleOptions?.speed ?? tolerancesScale.speed; + + this._assertPositiveFinite(length, "tolerancesScale.length"); + this._assertPositiveFinite(speed, "tolerancesScale.speed"); + + tolerancesScale.length = length; + tolerancesScale.speed = speed; + this._updateScaledDefaults(length, speed); + } + + private _assertPositiveFinite(value: number, name: string): void { + if (!Number.isFinite(value) || value <= 0) { + throw new Error(`PhysXPhysics ${name} must be a positive finite number.`); + } + } + + private _updateScaledDefaults(length: number, speed: number): void { + this._defaultContactOffset = 0.02 * length; + this._defaultSleepThreshold = 5e-5 * speed * speed; + } } enum InitializeState { @@ -316,3 +369,15 @@ interface PhysXRuntimeUrls { /*** The URL of `PhysXRuntimeMode.WebAssemblySIMD` mode. */ wasmSIMDModeUrl?: string; } + +export interface PhysXTolerancesScale { + /** Approximate object length in the simulation unit. PhysX default is 1. */ + length?: number; + /** Typical object speed in the simulation unit. PhysX default is 10. */ + speed?: number; +} + +export interface PhysXPhysicsOptions { + /** PhysX world unit scale used before PxPhysics, PxSceneDesc and PxCookingParams are created. */ + tolerancesScale?: PhysXTolerancesScale; +} diff --git a/packages/physics-physx/src/shape/PhysXColliderShape.ts b/packages/physics-physx/src/shape/PhysXColliderShape.ts index ea5a3df8b4..61b9fdf6a3 100644 --- a/packages/physics-physx/src/shape/PhysXColliderShape.ts +++ b/packages/physics-physx/src/shape/PhysXColliderShape.ts @@ -56,6 +56,7 @@ export abstract class PhysXColliderShape implements IColliderShape { constructor(physXPhysics: PhysXPhysics) { this._physXPhysics = physXPhysics; + this._contractOffset = physXPhysics.getDefaultContactOffset(); } /** From 72b0f0112a8dbbd4e02058b3bfbb9e3996c3a697 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Fri, 22 May 2026 10:56:48 +0800 Subject: [PATCH 102/130] test(physics): cover collider re-enable teleport --- .../2026-05-22-collider-reenable-teleport.md | 27 ++++++++++++ .../src/core/physics/DynamicCollider.test.ts | 43 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 notes/2026-05-22-collider-reenable-teleport.md diff --git a/notes/2026-05-22-collider-reenable-teleport.md b/notes/2026-05-22-collider-reenable-teleport.md new file mode 100644 index 0000000000..14568eaef4 --- /dev/null +++ b/notes/2026-05-22-collider-reenable-teleport.md @@ -0,0 +1,27 @@ +# Collider re-enable teleport regression test + +## Context + +Commit `bab0ae0a2` fixes a PhysX lifecycle bug where a disabled collider's native +actor is removed from the simulation scene but keeps its old native pose. When a +kinematic dynamic collider is re-enabled after its entity transform changed, the +first transform sync must not use `setKinematicTarget`, otherwise PhysX treats it +as a swept move from the stale pose and can emit spurious contacts. + +## Test added + +- `tests/src/core/physics/DynamicCollider.test.ts` now covers a kinematic + `DynamicCollider` in `Target` sync mode. +- The test disables the entity, moves its transform while inactive, re-enables it, + then verifies the first physics update calls native `setWorldTransform` exactly + once and does not call native `move`. +- A temporary local red-check that removed the pending re-enter teleport failed + with `expected 1 to equal +0` for `moveCalls`, proving the test catches the old + sweep path. + +## Verification + +- `pnpm vitest run tests/src/core/physics/DynamicCollider.test.ts -t "teleports kinematic target collider"` +- `pnpm vitest run tests/src/core/physics/DynamicCollider.test.ts` + +Both passed after restoring the engine fix. diff --git a/tests/src/core/physics/DynamicCollider.test.ts b/tests/src/core/physics/DynamicCollider.test.ts index 78cbe1f381..b441f300d4 100644 --- a/tests/src/core/physics/DynamicCollider.test.ts +++ b/tests/src/core/physics/DynamicCollider.test.ts @@ -6,6 +6,7 @@ import { DynamicCollider, DynamicColliderConstraints, CollisionDetectionMode, + DynamicColliderKinematicTransformSyncMode, StaticCollider, PlaneColliderShape } from "@galacean/engine-core"; @@ -648,6 +649,48 @@ describe("DynamicCollider", function () { expect(box.transform.position.y).below(1); }); + it("teleports kinematic target collider on re-enable instead of sweeping from stale native pose", function () { + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(-10, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.useGravity = false; + boxCollider.isKinematic = true; + boxCollider.kinematicTransformSyncMode = DynamicColliderKinematicTransformSyncMode.Target; + + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + // @ts-ignore - intentionally observe the native boundary used by Collider sync. + const nativeCollider = boxCollider._nativeCollider; + const originalMove = nativeCollider.move.bind(nativeCollider); + const originalSetWorldTransform = nativeCollider.setWorldTransform.bind(nativeCollider); + let moveCalls = 0; + let setWorldTransformCalls = 0; + nativeCollider.move = (...args: Parameters) => { + moveCalls++; + return originalMove(...args); + }; + nativeCollider.setWorldTransform = (...args: Parameters) => { + setWorldTransformCalls++; + return originalSetWorldTransform(...args); + }; + + try { + box.isActive = false; + box.transform.setPosition(10, 0, 0); + box.isActive = true; + + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + expect(moveCalls).eq(0); + expect(setWorldTransformCalls).eq(1); + expect(formatValue(box.transform.position.x)).eq(10); + } finally { + nativeCollider.move = originalMove; + nativeCollider.setWorldTransform = originalSetWorldTransform; + } + }); + it("constraints", function () { const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); const boxCollider = box.getComponent(DynamicCollider); From a1691038f5a90bfcae1b429ec9bd8740d4859748 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Fri, 22 May 2026 10:59:33 +0800 Subject: [PATCH 103/130] refactor(physics): clarify collider re-enter teleport state --- notes/2026-05-22-collider-reenable-teleport.md | 7 +++++++ packages/core/src/physics/Collider.ts | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/notes/2026-05-22-collider-reenable-teleport.md b/notes/2026-05-22-collider-reenable-teleport.md index 14568eaef4..6fa3356cba 100644 --- a/notes/2026-05-22-collider-reenable-teleport.md +++ b/notes/2026-05-22-collider-reenable-teleport.md @@ -19,6 +19,13 @@ as a swept move from the stale pose and can emit spurious contacts. with `expected 1 to equal +0` for `moveCalls`, proving the test catches the old sweep path. +## Naming cleanup + +The transient collider state is named `_pendingReenterTeleport` rather than +`_pendingReenterSync` because the important behavior is not generic transform +sync. It specifically forces the first sync after re-entering the physics scene +to use teleport semantics instead of swept kinematic target movement. + ## Verification - `pnpm vitest run tests/src/core/physics/DynamicCollider.test.ts -t "teleports kinematic target collider"` diff --git a/packages/core/src/physics/Collider.ts b/packages/core/src/physics/Collider.ts index befac2db52..5afa164403 100644 --- a/packages/core/src/physics/Collider.ts +++ b/packages/core/src/physics/Collider.ts @@ -37,7 +37,7 @@ export class Collider extends Component implements ICustomClone { * stale pose and fires spurious contacts along the path. */ @ignoreClone - private _pendingReenterSync: boolean = false; + private _pendingReenterTeleport: boolean = false; /** * The shapes of this collider. @@ -120,11 +120,11 @@ export class Collider extends Component implements ICustomClone { */ _onUpdate(): void { const shapes = this._shapes; - if (this._pendingReenterSync || this._updateFlag.flag) { + if (this._pendingReenterTeleport || this._updateFlag.flag) { const { transform } = this.entity; - if (this._pendingReenterSync) { + if (this._pendingReenterTeleport) { this._teleportToEntityTransform(transform.worldPosition, transform.worldRotationQuaternion); - this._pendingReenterSync = false; + this._pendingReenterTeleport = false; } else { this._syncEntityTransformToNative(transform.worldPosition, transform.worldRotationQuaternion); } @@ -154,7 +154,7 @@ export class Collider extends Component implements ICustomClone { */ override _onEnableInScene(): void { this.scene.physics._addCollider(this); - this._pendingReenterSync = true; + this._pendingReenterTeleport = true; } /** From 2ea4515d7a6dbf932aa97d30842bbc542e203be6 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Fri, 22 May 2026 11:00:39 +0800 Subject: [PATCH 104/130] chore: ignore local notes --- .gitignore | 1 + notes/2026-05-21-physx-tolerances-scale.md | 32 ----------------- .../2026-05-22-collider-reenable-teleport.md | 34 ------------------- 3 files changed, 1 insertion(+), 66 deletions(-) delete mode 100644 notes/2026-05-21-physx-tolerances-scale.md delete mode 100644 notes/2026-05-22-collider-reenable-teleport.md diff --git a/.gitignore b/.gitignore index d8f73e6c8e..7b036d513c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ playwright-report/ .claude/ CLAUDE.md docs/superpowers +notes/ .husky/post-checkout .husky/post-commit .husky/pre-push diff --git a/notes/2026-05-21-physx-tolerances-scale.md b/notes/2026-05-21-physx-tolerances-scale.md deleted file mode 100644 index 1d886b55c9..0000000000 --- a/notes/2026-05-21-physx-tolerances-scale.md +++ /dev/null @@ -1,32 +0,0 @@ -# PhysX tolerance scale configuration - -## Context - -Cocos 2D migration can run physics in pixel-like units. PhysX supports non-meter -unit systems through `PxTolerancesScale`, but Galacean previously always created -PhysX with default `length=1` and `speed=10`. - -## Code facts - -- `PhysXPhysics._init()` creates one `PxTolerancesScale`, then passes it to both - `PxCreatePhysics` and `PxCookingParams`. -- PhysX requires scene scale to match the scale used to create `PxPhysics`. -- PhysX derives defaults such as contact offset and sleep threshold from this scale. -- Galacean core previously stored fixed defaults: - - `ColliderShape._contactOffset = 0.02` - - `DynamicCollider._sleepThreshold = 0.005` - and synced them unconditionally to native, overriding scaled PhysX defaults. - -## Fix - -- `PhysXPhysics` now accepts `PhysXPhysicsOptions.tolerancesScale`. -- The scale is applied before creating `PxPhysics` and `PxCookingParams`. -- `PhysXPhysics` exposes scaled default contact offset and sleep threshold. -- Core `ColliderShape` and `DynamicCollider` leave native defaults untouched unless - the user explicitly sets `contactOffset` or `sleepThreshold`. - -## Boundary - -This change does not scale damping or solver iterations. Those are not PhysX -tolerance defaults. Callers should still set explicit damping only when the -source project authored it. diff --git a/notes/2026-05-22-collider-reenable-teleport.md b/notes/2026-05-22-collider-reenable-teleport.md deleted file mode 100644 index 6fa3356cba..0000000000 --- a/notes/2026-05-22-collider-reenable-teleport.md +++ /dev/null @@ -1,34 +0,0 @@ -# Collider re-enable teleport regression test - -## Context - -Commit `bab0ae0a2` fixes a PhysX lifecycle bug where a disabled collider's native -actor is removed from the simulation scene but keeps its old native pose. When a -kinematic dynamic collider is re-enabled after its entity transform changed, the -first transform sync must not use `setKinematicTarget`, otherwise PhysX treats it -as a swept move from the stale pose and can emit spurious contacts. - -## Test added - -- `tests/src/core/physics/DynamicCollider.test.ts` now covers a kinematic - `DynamicCollider` in `Target` sync mode. -- The test disables the entity, moves its transform while inactive, re-enables it, - then verifies the first physics update calls native `setWorldTransform` exactly - once and does not call native `move`. -- A temporary local red-check that removed the pending re-enter teleport failed - with `expected 1 to equal +0` for `moveCalls`, proving the test catches the old - sweep path. - -## Naming cleanup - -The transient collider state is named `_pendingReenterTeleport` rather than -`_pendingReenterSync` because the important behavior is not generic transform -sync. It specifically forces the first sync after re-entering the physics scene -to use teleport semantics instead of swept kinematic target movement. - -## Verification - -- `pnpm vitest run tests/src/core/physics/DynamicCollider.test.ts -t "teleports kinematic target collider"` -- `pnpm vitest run tests/src/core/physics/DynamicCollider.test.ts` - -Both passed after restoring the engine fix. From 5a53718a780cfbaf9fc7f623d832e7e4dd40ed62 Mon Sep 17 00:00:00 2001 From: hhhhkrx Date: Fri, 22 May 2026 13:39:51 +0800 Subject: [PATCH 105/130] feat(particle): implement rateOverDistance emission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cherry-picked from galacean/engine#3011 (e2e parts dropped). - Wire up EmissionModule.rateOverDistance: each frame accumulates the delta of the emitter's world position and emits ratePerUnit × distance particles (Unity-aligned). - Sub-interval distance fragment carried across frames; floor-based count instead of subtract-loop to avoid float drift dropping a particle at exact boundaries. - Distribute the N per-frame emissions spatially along [lastPos → currentPos] in World simulation space, and interpolate emit time the same way so age-driven modules (COL/SOL/FOL) render a smooth gradient instead of a uniform block. - Clamp _emit at the maxParticles budget and return the actual count, so a setPosition teleport on a rateOverDistance emitter can't expand into millions of no-op iterations. - Reset baseline + accumulator on stop(StopEmitting*) so a play-after- clear re-syncs from the current position. Includes unit tests covering zero-rate, ratePerUnit × distance, sub-interval accumulation across frames, static emitter, stop+clear reset, spatial distribution, per-particle emit-time spacing, and teleport-induced burst guard. --- .../core/src/particle/ParticleGenerator.ts | 81 +++-- .../src/particle/modules/EmissionModule.ts | 81 ++++- .../core/particle/RateOverDistance.test.ts | 326 ++++++++++++++++++ 3 files changed, 454 insertions(+), 34 deletions(-) create mode 100644 tests/src/core/particle/RateOverDistance.test.ts diff --git a/packages/core/src/particle/ParticleGenerator.ts b/packages/core/src/particle/ParticleGenerator.ts index 75c50b5cf6..ecb488d8bf 100644 --- a/packages/core/src/particle/ParticleGenerator.ts +++ b/packages/core/src/particle/ParticleGenerator.ts @@ -236,6 +236,8 @@ export class ParticleGenerator { } } else { this._isPlaying = false; + // Invalidate the rateOverDistance baseline so emitter movement during the stop + // interval doesn't burst on resume. if (stopMode === ParticleStopMode.StopEmittingAndClear) { this._clearActiveParticles(); this._playTime = 0; @@ -243,6 +245,8 @@ export class ParticleGenerator { this._firstActiveTransformedBoundingBox = this._firstFreeTransformedBoundingBox; this.emission._reset(); + } else { + this.emission._invalidateDistanceBaseline(); } } } @@ -258,37 +262,41 @@ export class ParticleGenerator { /** * @internal */ - _emit(playTime: number, count: number): void { - const { emission } = this; - if (emission.enabled) { - const { main } = this; - // Wait the existing particles to be retired - const notRetireParticleCount = this._getNotRetiredParticleCount(); - if (notRetireParticleCount >= main.maxParticles) { - return; - } - const position = ParticleGenerator._tempVector30; - const direction = ParticleGenerator._tempVector31; - const transform = this._renderer.entity.transform; - const shape = emission.shape; - const positionScale = main._getPositionScale(); - for (let i = 0; i < count; i++) { - if (shape?.enabled) { - shape._generatePositionAndDirection(emission._shapeRand, playTime, position, direction); - position.multiply(positionScale); - direction.normalize().multiply(positionScale); - } else { - position.set(0, 0, 0); - direction.set(0, 0, -1); - // Speed is scaled by shape scale in world simulation space - // So if no shape and in world simulation space, we shouldn't scale the speed - if (main.simulationSpace === ParticleSimulationSpace.Local) { - direction.multiply(positionScale); - } + _emit(playTime: number, count: number, emitWorldPositionOverride?: Vector3): number { + const { emission, main } = this; + if (!emission.enabled) { + return 0; + } + const budget = main.maxParticles - this._getNotRetiredParticleCount(); + if (count > budget) { + count = budget; + } + if (count <= 0) { + return 0; + } + + const position = ParticleGenerator._tempVector30; + const direction = ParticleGenerator._tempVector31; + const transform = this._renderer.entity.transform; + const shape = emission.shape; + const positionScale = main._getPositionScale(); + for (let i = 0; i < count; i++) { + if (shape?.enabled) { + shape._generatePositionAndDirection(emission._shapeRand, playTime, position, direction); + position.multiply(positionScale); + direction.normalize().multiply(positionScale); + } else { + position.set(0, 0, 0); + direction.set(0, 0, -1); + // Speed is scaled by shape scale in world simulation space + // So if no shape and in world simulation space, we shouldn't scale the speed + if (main.simulationSpace === ParticleSimulationSpace.Local) { + direction.multiply(positionScale); } - this._addNewParticle(position, direction, transform, playTime); } + this._addNewParticle(position, direction, transform, playTime, emitWorldPositionOverride); } + return count; } /** @@ -832,7 +840,13 @@ export class ParticleGenerator { } } - private _addNewParticle(position: Vector3, direction: Vector3, transform: Transform, playTime: number): void { + private _addNewParticle( + position: Vector3, + direction: Vector3, + transform: Transform, + playTime: number, + emitWorldPositionOverride?: Vector3 + ): void { const firstFreeElement = this._firstFreeElement; let nextFreeElement = firstFreeElement + 1; if (nextFreeElement >= this._currentParticleCount) { @@ -864,7 +878,7 @@ export class ParticleGenerator { let pos: Vector3, rot: Quaternion; if (main.simulationSpace === ParticleSimulationSpace.World) { - pos = transform.worldPosition; + pos = emitWorldPositionOverride ?? transform.worldPosition; rot = transform.worldRotationQuaternion; } @@ -1023,7 +1037,7 @@ export class ParticleGenerator { // Initialize feedback buffer for this particle if (this._useTransformFeedback) { - this._addFeedbackParticle(firstFreeElement, position, direction, startSpeed, transform); + this._addFeedbackParticle(firstFreeElement, position, direction, startSpeed, transform, pos); } this._firstFreeElement = nextFreeElement; @@ -1034,7 +1048,8 @@ export class ParticleGenerator { shapePosition: Vector3, direction: Vector3, startSpeed: number, - transform: Transform + transform: Transform, + emitWorldPosition?: Vector3 ): void { let position: Vector3; if (this.main.simulationSpace === ParticleSimulationSpace.Local) { @@ -1042,7 +1057,7 @@ export class ParticleGenerator { } else { position = ParticleGenerator._tempVector32; Vector3.transformByQuat(shapePosition, transform.worldRotationQuaternion, position); - position.add(transform.worldPosition); + position.add(emitWorldPosition ?? transform.worldPosition); } this._feedbackSimulator.writeParticleData( diff --git a/packages/core/src/particle/modules/EmissionModule.ts b/packages/core/src/particle/modules/EmissionModule.ts index 108832c38c..292205f0f1 100644 --- a/packages/core/src/particle/modules/EmissionModule.ts +++ b/packages/core/src/particle/modules/EmissionModule.ts @@ -1,7 +1,8 @@ -import { Rand } from "@galacean/engine-math"; +import { MathUtil, Rand, Vector3 } from "@galacean/engine-math"; import { deepClone, ignoreClone } from "../../clone/CloneManager"; import { ShaderMacro } from "../../shader/ShaderMacro"; import { ParticleRandomSubSeeds } from "../enums/ParticleRandomSubSeeds"; +import { ParticleSimulationSpace } from "../enums/ParticleSimulationSpace"; import { Burst } from "./Burst"; import { ParticleCompositeCurve } from "./ParticleCompositeCurve"; import { ParticleGeneratorModule } from "./ParticleGeneratorModule"; @@ -14,6 +15,8 @@ export class EmissionModule extends ParticleGeneratorModule { /** @internal */ static readonly _emissionShapeMacro = ShaderMacro.getByName("RENDERER_EMISSION_SHAPE"); + private static _tempEmitPosition = new Vector3(); + /** The rate of particle emission. */ @deepClone rateOverTime: ParticleCompositeCurve = new ParticleCompositeCurve(10); @@ -29,6 +32,13 @@ export class EmissionModule extends ParticleGeneratorModule { /** @internal */ _frameRateTime: number = 0; + @ignoreClone + private _distanceAccumulator = 0; + @ignoreClone + private _lastEmitPosition = new Vector3(); + @ignoreClone + private _hasLastEmitPosition = false; + @deepClone private _bursts: Burst[] = []; @@ -130,6 +140,7 @@ export class EmissionModule extends ParticleGeneratorModule { */ _emit(lastPlayTime: number, playTime: number): void { this._emitByRateOverTime(playTime); + this._emitByRateOverDistance(lastPlayTime, playTime); this._emitByBurst(lastPlayTime, playTime); } @@ -147,6 +158,15 @@ export class EmissionModule extends ParticleGeneratorModule { _reset(): void { this._frameRateTime = 0; this._currentBurstIndex = 0; + this._invalidateDistanceBaseline(); + } + + /** + * @internal + */ + _invalidateDistanceBaseline(): void { + this._hasLastEmitPosition = false; + this._distanceAccumulator = 0; } /** @@ -171,6 +191,65 @@ export class EmissionModule extends ParticleGeneratorModule { } } + private _emitByRateOverDistance(lastPlayTime: number, playTime: number): void { + const ratePerUnit = this.rateOverDistance.evaluate(undefined, undefined); + const generator = this._generator; + + if (ratePerUnit <= 0) { + this._invalidateDistanceBaseline(); + return; + } + if (!this._hasLastEmitPosition) { + this._lastEmitPosition.copyFrom(generator._renderer.entity.transform.worldPosition); + this._hasLastEmitPosition = true; + return; + } + + const lastPos = this._lastEmitPosition; + const currentPos = generator._renderer.entity.transform.worldPosition; + const dx = currentPos.x - lastPos.x; + const dy = currentPos.y - lastPos.y; + const dz = currentPos.z - lastPos.z; + const moveLength = Math.sqrt(dx * dx + dy * dy + dz * dz); + this._distanceAccumulator += moveLength; + + const emitInterval = 1.0 / ratePerUnit; + // `+ zeroTolerance` absorbs float divide error so an exact `N*interval` accumulator doesn't drop 1 + const count = Math.floor(this._distanceAccumulator / emitInterval + MathUtil.zeroTolerance); + + if (count > 0) { + this._distanceAccumulator -= count * emitInterval; + // `subFrameAge ∈ [0, 1]`: how far back into the frame a particle was born + // (0 = newest at currentPos/playTime, 1 = oldest at lastPos/lastPlayTime). + // The initial clamp protects two edges — moveLength ≈ 0 (collapse to frame-end + // emit) and a tiny moveLength near the emitInterval edge (would put age > 1). + // Local simulation space ignores the position override but still uses emitTime. + const isWorld = generator.main.simulationSpace === ParticleSimulationSpace.World; + const invMoveLength = moveLength > MathUtil.zeroTolerance ? 1.0 / moveLength : 0; + const ageStep = emitInterval * invMoveLength; + const dt = playTime - lastPlayTime; + let subFrameAge = Math.min(this._distanceAccumulator * invMoveLength, 1.0); + const emitPos = EmissionModule._tempEmitPosition; + for (let i = 0; i < count; i++) { + if (isWorld) { + emitPos.set( + currentPos.x - dx * subFrameAge, + currentPos.y - dy * subFrameAge, + currentPos.z - dz * subFrameAge + ); + } + if (generator._emit(playTime - dt * subFrameAge, 1, isWorld ? emitPos : undefined) === 0) { + // Buffer full: settle the frame's distance budget instead of carrying it over + this._distanceAccumulator = 0; + break; + } + subFrameAge += ageStep; + } + } + + lastPos.copyFrom(currentPos); + } + private _emitByBurst(lastPlayTime: number, playTime: number): void { const main = this._generator.main; const duration = main.duration; diff --git a/tests/src/core/particle/RateOverDistance.test.ts b/tests/src/core/particle/RateOverDistance.test.ts new file mode 100644 index 0000000000..483892f109 --- /dev/null +++ b/tests/src/core/particle/RateOverDistance.test.ts @@ -0,0 +1,326 @@ +import { + Camera, + Engine, + Entity, + ParticleMaterial, + ParticleRenderer, + ParticleSimulationSpace, + ParticleStopMode +} from "@galacean/engine-core"; +import { Color, Vector3 } from "@galacean/engine-math"; +import { WebGLEngine } from "@galacean/engine"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; + +function tick(engine: Engine, times: { value: number }, deltaMs: number = 100): void { + //@ts-ignore + engine._vSyncCount = Infinity; + //@ts-ignore + engine._time._lastSystemTime = 0; + performance.now = function () { + times.value += deltaMs; + return times.value; + }; + engine.update(); +} + +function buildEmitter(engine: Engine, name: string): { entity: Entity; renderer: ParticleRenderer } { + const scene = engine.sceneManager.activeScene; + const entity = scene.createRootEntity(name); + const renderer = entity.addComponent(ParticleRenderer); + const material = new ParticleMaterial(engine); + material.baseColor = new Color(1, 1, 1, 1); + renderer.setMaterial(material); + + const generator = renderer.generator; + generator.useAutoRandomSeed = false; + generator.main.duration = 5; + generator.main.isLoop = false; + generator.main.maxParticles = 1000; + generator.main.startLifetime.constant = 10; + // Zero out the time-based path so only rateOverDistance contributes. + generator.emission.rateOverTime.constant = 0; + return { entity, renderer }; +} + +describe("EmissionModule rateOverDistance", () => { + let engine: Engine; + let elapsed: { value: number }; + + beforeAll(async function () { + engine = await WebGLEngine.create({ + canvas: document.createElement("canvas") + }); + + const scene = engine.sceneManager.activeScene; + const cameraEntity = scene.createRootEntity("Camera"); + cameraEntity.addComponent(Camera); + cameraEntity.transform.setPosition(0, 0, 10); + + engine.run(); + elapsed = { value: 0 }; + }); + + afterAll(function () { + engine.destroy(); + }); + + it("defaults to 0 (no emission triggered by movement)", () => { + const { entity, renderer } = buildEmitter(engine, "default-zero"); + const generator = renderer.generator; + expect(generator.emission.rateOverDistance.evaluate(undefined, undefined)).to.eq(0); + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + + tick(engine, elapsed); + entity.transform.setPosition(10, 0, 0); + tick(engine, elapsed); + + expect(generator._getAliveParticleCount()).to.eq(0); + entity.destroy(); + }); + + it("emits ratePerUnit × distance particles", () => { + const { entity, renderer } = buildEmitter(engine, "rate-times-distance"); + const generator = renderer.generator; + generator.emission.rateOverDistance.constant = 10; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + + // First tick syncs the baseline position. + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(0); + + // Move 2 units → 10 * 2 = 20 particles. + entity.transform.setPosition(2, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(20); + + entity.destroy(); + }); + + it("accumulates sub-interval movement across frames", () => { + const { entity, renderer } = buildEmitter(engine, "subinterval-carry"); + const generator = renderer.generator; + // 1 particle per 0.5 units → 2 particles per unit. + generator.emission.rateOverDistance.constant = 2; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + tick(engine, elapsed); // baseline sync + + // 3 moves of 0.3 units each = 0.9 total. With interval 0.5, that's + // exactly 1 emission (at 0.5) plus 0.4 carried forward. + entity.transform.setPosition(0.3, 0, 0); + tick(engine, elapsed); + entity.transform.setPosition(0.6, 0, 0); + tick(engine, elapsed); + entity.transform.setPosition(0.9, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(1); + + // Move another 0.6 units → accumulator hits 1.0, then 0 carried fwd. Emit 2 more. + entity.transform.setPosition(1.5, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(3); + + entity.destroy(); + }); + + it("static emitter never emits via distance", () => { + const { entity, renderer } = buildEmitter(engine, "static-emitter"); + const generator = renderer.generator; + generator.emission.rateOverDistance.constant = 100; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + tick(engine, elapsed); + tick(engine, elapsed); + tick(engine, elapsed); + + expect(generator._getAliveParticleCount()).to.eq(0); + entity.destroy(); + }); + + it("stop+clear resets the distance accumulator", () => { + const { entity, renderer } = buildEmitter(engine, "reset-on-clear"); + const generator = renderer.generator; + generator.emission.rateOverDistance.constant = 10; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + tick(engine, elapsed); + entity.transform.setPosition(0.5, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(5); + + // After clear+play, the next tick must re-sync from the *current* position, + // not diff against the pre-clear baseline — so jumping back to origin + // should not emit anything until the next move. + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + entity.transform.setPosition(0, 0, 0); + generator.play(); + tick(engine, elapsed); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(0); + + entity.destroy(); + }); + + it("distributes particles along the movement path in World space", () => { + const { entity, renderer } = buildEmitter(engine, "world-space-distribution"); + const generator = renderer.generator; + generator.main.simulationSpace = ParticleSimulationSpace.World; + // 1 particle per unit; move 4 units → 4 particles spaced at world x = 1, 2, 3, 4 + generator.emission.rateOverDistance.constant = 1; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + + tick(engine, elapsed); // baseline sync at (0,0,0) + + entity.transform.setPosition(4, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(4); + + // Verify each particle's stored world position is along [0,4] on x axis, not all at x=4. + // Particles are written sequentially starting at firstActiveElement=0. + //@ts-ignore - test reaches into instance buffer to verify spatial distribution + const verts = (generator as any)._instanceVertices as Float32Array; + // Per-instance stride = 168 bytes / 4 = 42 floats; world position lives at offset 27. + const stride = 42; + const xs: number[] = []; + for (let i = 0; i < 4; i++) { + xs.push(verts[i * stride + 27]); + } + xs.sort((a, b) => a - b); + // Expect roughly [1, 2, 3, 4] — accept loose tolerance for float ops. + expect(xs[0]).to.be.closeTo(1, 1e-4); + expect(xs[1]).to.be.closeTo(2, 1e-4); + expect(xs[2]).to.be.closeTo(3, 1e-4); + expect(xs[3]).to.be.closeTo(4, 1e-4); + + entity.destroy(); + }); + + it("distributes emit time along the movement path in World space", () => { + const { entity, renderer } = buildEmitter(engine, "world-space-time-distribution"); + const generator = renderer.generator; + generator.main.simulationSpace = ParticleSimulationSpace.World; + // 1 particle per unit; move 4 units across one 100ms tick → 4 particles, + // each born at a sub-frame offset. + generator.emission.rateOverDistance.constant = 1; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + + tick(engine, elapsed); // baseline sync at (0,0,0) + entity.transform.setPosition(4, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(4); + + //@ts-ignore - reach into instance buffer to read per-particle emit time + const verts = (generator as any)._instanceVertices as Float32Array; + const stride = 42; + // a_DirectionTime is at byte 16 → float 4; the .w slot (emit time) is float 4+3=7. + const timeFloatOffset = 7; + const times: number[] = []; + for (let i = 0; i < 4; i++) { + times.push(verts[i * stride + timeFloatOffset]); + } + times.sort((a, b) => a - b); + + // The 4 emit times must form an arithmetic sequence (constant step = sStep * dt), + // and they must not all be equal — that's exactly the bug we're guarding against, + // where COL / SOL / FOL would otherwise render them as a uniform stamp. + const diff0 = times[1] - times[0]; + const diff1 = times[2] - times[1]; + const diff2 = times[3] - times[2]; + expect(diff0).to.be.greaterThan(1e-4); + expect(diff1).to.be.closeTo(diff0, 1e-4); + expect(diff2).to.be.closeTo(diff0, 1e-4); + + entity.destroy(); + }); + + it("clamps count and discards accumulator on teleport-sized moves", () => { + const { entity, renderer } = buildEmitter(engine, "teleport-clamp"); + const generator = renderer.generator; + generator.main.maxParticles = 50; + // Rate 10/unit × 10000 unit jump would otherwise demand 100,000 emissions + // in one frame — millions of `_addNewParticle` calls hitting the buffer-full + // early return. + generator.emission.rateOverDistance.constant = 10; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + tick(engine, elapsed); // baseline sync at (0,0,0) + + entity.transform.setPosition(10000, 0, 0); // teleport + tick(engine, elapsed); + + // Alive count must not exceed the configured cap. + expect(generator._getAliveParticleCount()).to.be.lessThanOrEqual(50); + + // Next frame without movement: accumulator should have been reset to 0 + // (residue dropped), so no further emission. + const aliveAfterTeleport = generator._getAliveParticleCount(); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(aliveAfterTeleport); + + entity.destroy(); + }); + + it("does not burst on play() after emitter moves while stopped", () => { + const { entity, renderer } = buildEmitter(engine, "no-burst-on-replay"); + const generator = renderer.generator; + generator.emission.rateOverDistance.constant = 10; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + tick(engine, elapsed); // baseline sync at (0,0,0) + + // Stop *without* clear — accumulator/baseline retained by the old impl. + generator.stop(true, ParticleStopMode.StopEmitting); + // Emitter teleports a few units while stopped (kept inside the camera frustum + // so renderer culling doesn't mask the test). + entity.transform.setPosition(3, 0, 0); + generator.play(); + + // First tick after play() must resync the baseline at the new position, + // not diff (3 - 0) and dump 30 particles in one shot. + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(0); + + // Subsequent movement still emits normally — diffed against the resynced baseline. + entity.transform.setPosition(4, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(10); + + entity.destroy(); + }); + + it("returns actual emitted count when buffer is full", () => { + // Documents the `_emit` return-value contract that lets distance emission detect + // mid-loop buffer exhaustion (and drop residual accumulator to avoid spinning + // through millions of no-op iterations on a teleport). + const { entity, renderer } = buildEmitter(engine, "emit-returns-actual"); + const generator = renderer.generator; + generator.main.maxParticles = 10; + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + tick(engine, elapsed); + + // Request 100, buffer only has 10 slots. + //@ts-ignore — reach into the internal _emit contract + const emitted = generator._emit(generator._playTime, 100); + expect(emitted).to.eq(10); + + // Subsequent request returns 0 — buffer is saturated. + //@ts-ignore + expect(generator._emit(generator._playTime, 5)).to.eq(0); + + entity.destroy(); + }); +}); From f8d49791a650908f181e13bb5591010e5b44cc39 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Fri, 22 May 2026 18:27:05 +0800 Subject: [PATCH 106/130] chore: release v0.0.0-experimental-2.0-game.14 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index e72651ca60..8f8cde0b5f 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index ff62fe13c8..cf39d4de73 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index e9fda21825..4e741f9f78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 2fe70866ab..bbd150d689 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index dbdb0635e9..3bbffa9096 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index 8d9add4530..82a1865fc0 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index cef97e1b6e..5e69bbe4e3 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 0289f7a4f2..82055446bd 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 964f090e35..9eaa1e74c4 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index b1acc44e84..d97107856f 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 66f820e31e..87e0b17c18 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 5d7a5f85e4..0d6baf8ea5 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index bfc54146f5..87f12b1510 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index 977f417636..a225b732c3 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 0d311abba0..f87fbc528a 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 84016aea03..14945b47d9 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index 5ca3acc209..54d04b4aa5 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.13", + "version": "0.0.0-experimental-2.0-game.14", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From bde8c614d72c499fdf7515091b96908151a365e6 Mon Sep 17 00:00:00 2001 From: luzhuang Date: Fri, 22 May 2026 11:55:06 +0800 Subject: [PATCH 107/130] feat(animation): expose clip sampling and gate animation events --- packages/core/src/animation/AnimationClip.ts | 9 +++ packages/core/src/animation/Animator.ts | 7 ++- tests/src/core/Animator.test.ts | 60 ++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/packages/core/src/animation/AnimationClip.ts b/packages/core/src/animation/AnimationClip.ts index adb0c07c91..9f7b6779fc 100644 --- a/packages/core/src/animation/AnimationClip.ts +++ b/packages/core/src/animation/AnimationClip.ts @@ -204,6 +204,15 @@ export class AnimationClip extends EngineObject { this._length = 0; } + /** + * Samples an animation at a given time. + * @param entity - The animated entity + * @param time - The time to sample an animation + */ + sampleAnimation(entity: Entity, time: number): void { + this._sampleAnimation(entity, time); + } + /** * @internal * Samples an animation at a given time. diff --git a/packages/core/src/animation/Animator.ts b/packages/core/src/animation/Animator.ts index ecaddd19ed..2c1638abf1 100644 --- a/packages/core/src/animation/Animator.ts +++ b/packages/core/src/animation/Animator.ts @@ -38,6 +38,9 @@ export class Animator extends Component { /** The playback speed of the Animator, 1.0 is normal playback speed. */ @assignmentClone speed = 1.0; + /** Whether the Animator sends AnimationEvent callbacks. */ + @assignmentClone + fireEvents = true; /** @internal */ _playFrameCount = -1; @@ -1589,7 +1592,9 @@ export class Animator extends Component { deltaTime: number ) { const { eventHandlers } = playData.stateData; - eventHandlers.length && this._fireAnimationEvents(playData, eventHandlers, lastClipTime, deltaTime); + this.fireEvents && + eventHandlers.length && + this._fireAnimationEvents(playData, eventHandlers, lastClipTime, deltaTime); if (lastPlayState === AnimatorStatePlayState.UnStarted) { state._callOnEnter(this, layerIndex); diff --git a/tests/src/core/Animator.test.ts b/tests/src/core/Animator.test.ts index 69026b5a1e..22fe8b110b 100644 --- a/tests/src/core/Animator.test.ts +++ b/tests/src/core/Animator.test.ts @@ -449,6 +449,32 @@ describe("Animator test", function () { expect(testScriptSpy).toHaveBeenCalledTimes(1); }); + it("fireEvents gates AnimationEvent dispatch without consuming the event", () => { + animator.play("Walk"); + + class TestScript extends Script { + event0(): void {} + } + + const testScript = animator.entity.addComponent(TestScript); + const testScriptSpy = vi.spyOn(testScript, "event0"); + + const event0 = new AnimationEvent(); + event0.functionName = "event0"; + event0.time = 0; + + const state = animator.findAnimatorState("Walk"); + state.clip.addEvent(event0); + + animator.fireEvents = false; + animator.update(0); + expect(testScriptSpy).not.toHaveBeenCalled(); + + animator.fireEvents = true; + animator.update(0.1); + expect(testScriptSpy).toHaveBeenCalledTimes(1); + }); + it("does not refire animation events when a once clip reaches the end", () => { const entity = new Entity(engine); const onceAnimator = entity.addComponent(Animator); @@ -1223,6 +1249,40 @@ describe("Animator test", function () { expect(spine.transform.position.y).to.eq(1); }); + it("sampleAnimation samples clip curves without firing AnimationEvents", () => { + const entity = new Entity(engine, "sample-root"); + const clip = new AnimationClip("sample"); + const curve = new AnimationFloatCurve(); + const start = new Keyframe(); + const end = new Keyframe(); + start.time = 0; + start.value = 0; + end.time = 1; + end.value = 3; + curve.addKey(start); + curve.addKey(end); + clip.addCurveBinding("", Transform, "position.x", curve); + + class TestScript extends Script { + event0(): void {} + } + + const script = entity.addComponent(TestScript); + const eventSpy = vi.spyOn(script, "event0"); + const event0 = new AnimationEvent(); + event0.functionName = "event0"; + event0.time = 0; + clip.addEvent(event0); + + try { + clip.sampleAnimation(entity, 1); + expect(entity.transform.position.x).to.eq(3); + expect(eventSpy).not.toHaveBeenCalled(); + } finally { + entity.destroy(); + } + }); + it("anyState transition interrupts crossFade", () => { const { animatorController } = animator; animatorController.addParameter("interrupt", false); From 8ac7f05a8c57c74e29ccf969287781aa2197098b Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 26 May 2026 19:37:01 +0800 Subject: [PATCH 108/130] fix(animation): remove clip changed listener on Animator destroy Animator added a clipChangedListener to each AnimatorState's update flag manager but never removed it. After destroy, the listener kept the state and the destroyed Animator graph reachable, leaking memory across reloads. Track the listener on AnimatorStateData and unsubscribe in _reset; call _reset from _onDestroy. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/core/src/animation/Animator.ts | 16 ++++++++++++++++ .../src/animation/internal/AnimatorStateData.ts | 3 +++ 2 files changed, 19 insertions(+) diff --git a/packages/core/src/animation/Animator.ts b/packages/core/src/animation/Animator.ts index 2c1638abf1..ee4e299fbb 100644 --- a/packages/core/src/animation/Animator.ts +++ b/packages/core/src/animation/Animator.ts @@ -343,6 +343,19 @@ export class Animator extends Component { } } + const layersData = this._animatorLayersData; + for (let i = 0, n = layersData.length; i < n; i++) { + const stateDataMap = layersData[i].animatorStateDataMap; + for (const stateName in stateDataMap) { + const stateData = stateDataMap[stateName]; + if (stateData.clipChangedListener && stateData.state) { + stateData.state._updateFlagManager.removeListener(stateData.clipChangedListener); + stateData.clipChangedListener = null; + stateData.state = null; + } + } + } + this._animatorLayersData.length = 0; this._curveOwnerPool = Object.create(null); this._parametersValueMap = Object.create(null); @@ -366,6 +379,7 @@ export class Animator extends Component { protected override _onDestroy(): void { super._onDestroy(); + this._reset(); const controller = this._animatorController; if (controller) { this._addResourceReferCount(controller, -1); @@ -517,6 +531,8 @@ export class Animator extends Component { }; clipChangedListener(); state._updateFlagManager.addListener(clipChangedListener); + animatorStateData.state = state; + animatorStateData.clipChangedListener = clipChangedListener; } private _clearCrossData(animatorLayerData: AnimatorLayerData): void { diff --git a/packages/core/src/animation/internal/AnimatorStateData.ts b/packages/core/src/animation/internal/AnimatorStateData.ts index 146c0bb52f..42b50edd87 100644 --- a/packages/core/src/animation/internal/AnimatorStateData.ts +++ b/packages/core/src/animation/internal/AnimatorStateData.ts @@ -1,3 +1,4 @@ +import { AnimatorState } from "../AnimatorState"; import { AnimationCurveLayerOwner } from "./AnimationCurveLayerOwner"; import { AnimationEventHandler } from "./AnimationEventHandler"; @@ -7,4 +8,6 @@ import { AnimationEventHandler } from "./AnimationEventHandler"; export class AnimatorStateData { curveLayerOwner: AnimationCurveLayerOwner[] = []; eventHandlers: AnimationEventHandler[] = []; + state: AnimatorState = null; + clipChangedListener: () => void = null; } From 5337f4d983d3e20ddbdf55786609db7aa80cece9 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 26 May 2026 19:37:17 +0800 Subject: [PATCH 109/130] refactor(clone): use ReferResource to decide deep vs assignment clone Previously plain objects took the deep branch and everything else fell to assignment, which mistakenly shared user-defined value classes between source and clone. Switch the rule: assignment only for ReferResource (refCount-managed engine resources like Material/Texture/Mesh/Shader); deep clone for everything else, so nested Entity/Component refs in user data are correctly remapped. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/core/src/clone/CloneManager.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/core/src/clone/CloneManager.ts b/packages/core/src/clone/CloneManager.ts index b6f94ce248..d827140224 100644 --- a/packages/core/src/clone/CloneManager.ts +++ b/packages/core/src/clone/CloneManager.ts @@ -1,5 +1,6 @@ import { Entity } from "../Entity"; import { TypedArray } from "../base/Constant"; +import { ReferResource } from "../asset/ReferResource"; import { ICustomClone } from "./ComponentCloner"; import { CloneMode } from "./enums/CloneMode"; @@ -275,11 +276,15 @@ export class CloneManager { // Value types with copyFrom (math types like Vector3, Color, etc.) if ((sourceProperty).copyFrom) return CloneMode.Deep; - // Plain objects - deep clone (may contain Entity/Component refs) - if (sourceProperty.constructor === Object) return CloneMode.Deep; + // Engine resources (Material, Texture, Mesh, Shader, ...) — refCount-managed, + // shared by reference. Anything outside this contract is treated as a value-like + // user class and gets a deep clone (so nested Entity/Component refs are remapped). + if (sourceProperty instanceof ReferResource) return CloneMode.Assignment; - // Other class instances (engine resources like Material, Texture) - shared reference - return CloneMode.Assignment; + // Default: deep clone. Covers plain objects {...} and user-defined value classes + // (EventHandler, business data containers). Internal Entity/Component refs reach + // line 109's _remap branch and are correctly rebound to the target tree. + return CloneMode.Deep; } static deepCloneObject(source: Object, target: Object, deepInstanceMap: Map): void { From fb1f71a988031f7438824cb3ed3b097d615542df Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 26 May 2026 19:37:30 +0800 Subject: [PATCH 110/130] refactor(ui): simplify Text._updateBounds via cached local bounds Recomputing min/max from UITransform's size and pivot duplicates the bounds derivation done elsewhere. Transform the cached _localBounds by the world matrix instead, matching the pattern used by other UIRenderer subclasses. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/ui/src/component/advanced/Text.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/ui/src/component/advanced/Text.ts b/packages/ui/src/component/advanced/Text.ts index 638fc2e97d..553912d64b 100644 --- a/packages/ui/src/component/advanced/Text.ts +++ b/packages/ui/src/component/advanced/Text.ts @@ -355,12 +355,7 @@ export class Text extends UIRenderer implements ITextRenderer { } protected override _updateBounds(worldBounds: BoundingBox): void { - const transform = this._transformEntity.transform; - const { x: width, y: height } = transform.size; - const { x: pivotX, y: pivotY } = transform.pivot; - worldBounds.min.set(-width * pivotX, -height * pivotY, 0); - worldBounds.max.set(width * (1 - pivotX), height * (1 - pivotY), 0); - BoundingBox.transform(worldBounds, this._transformEntity.transform.worldMatrix, worldBounds); + BoundingBox.transform(this._localBounds, this._transformEntity.transform.worldMatrix, worldBounds); } protected override _render(context): void { From 07abbc4be6c3e1e45fdeb9c5c94673c34355a50d Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 26 May 2026 19:42:46 +0800 Subject: [PATCH 111/130] chore: release v0.0.0-experimental-2.0-game.15 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 8f8cde0b5f..9bfbc4ed01 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index cf39d4de73..c448171671 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 4e741f9f78..326f1b930f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index bbd150d689..6466fa89bb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 3bbffa9096..6999242358 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index 82a1865fc0..d1cf993717 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index 5e69bbe4e3..ed601f5c66 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 82055446bd..4626879fc4 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 9eaa1e74c4..e26c861ad9 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index d97107856f..cae2a4f85d 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 87e0b17c18..2e27502fcb 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 0d6baf8ea5..92d6602d35 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index 87f12b1510..24302397bd 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index a225b732c3..8d015aa938 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index f87fbc528a..4cc93f7496 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 14945b47d9..192168f865 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index 54d04b4aa5..1cbca7fa97 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.14", + "version": "0.0.0-experimental-2.0-game.15", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From d0d8453e70423a83104fcce0b271c3d14c3f5517 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 26 May 2026 17:39:26 +0800 Subject: [PATCH 112/130] perf(pipeline): share internal RT across cameras via per-frame pool lease Each camera held its own `_internalColorTarget` for the lifetime of its `BasicRenderPipeline`, so a scene with N on-screen cameras pinned N full-canvas RTs. On a 1078x2249 canvas with MSAA 4x that is 2 * 74 MB = 148 MB just for the scratch buffers (see investigation in galacean/migration-agent#304). Convert `_internalColorTarget` and `_copyBackgroundTexture` to per-frame leases: `BasicRenderPipeline._drawRenderPass` returns both to `RenderTargetPool` at end of every frame, so the next camera in the frame finds a matching free entry and reuses the same underlying RT. Cameras with mismatched format / MSAA / depth still get their own entries -- the pool's existing match key handles that. `RenderTargetPool` gains three bounded-growth strategies so the free list cannot leak across canvas resizes or shape churn: * `tick(currentFrame)` -- destroys entries idle longer than `maxFreeAgeFrames` (default 60). Engine calls this once per `update()`. * `evictBySize(width, height)` -- destroys entries matching the given dimensions. Engine subscribes to canvas size changes and evicts at the previous canvas size, so old full-canvas RTs do not linger. * `maxFreeBytes` -- when a `free*` push would exceed the cap, the oldest entries (by `lastUsedFrame`) are destroyed until the total fits. Scoped to free-list contents only (not total GPU memory), so the cap is device-independent. `RenderTarget._memorySize` becomes `@internal` so the pool can compute per-entry byte size without re-deriving from format/aa. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/core/src/Engine.ts | 27 +++ .../src/RenderPipeline/BasicRenderPipeline.ts | 26 +- .../src/RenderPipeline/RenderTargetPool.ts | 226 ++++++++++++++++-- packages/core/src/RenderPipeline/index.ts | 1 + packages/core/src/texture/RenderTarget.ts | 3 +- .../RenderPipeline/RenderTargetPool.test.ts | 177 ++++++++++++++ 6 files changed, 433 insertions(+), 27 deletions(-) create mode 100644 tests/src/core/RenderPipeline/RenderTargetPool.test.ts diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index ea6b2d3079..2454bf2049 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -138,6 +138,25 @@ export class Engine extends EventDispatcher { private _waitingGC: boolean = false; private _postProcessPasses = new Array(); private _activePostProcessPasses = new Array(); + private _lastCanvasWidth: number = -1; + private _lastCanvasHeight: number = -1; + + /** + * Evict pool entries dimensioned to the previous canvas size when the canvas resizes, + * so full-canvas internal RTs cached at the old resolution don't linger until `tick()`. + */ + private _onCanvasResize = (): void => { + const canvas = this._canvas; + const newWidth = canvas.width; + const newHeight = canvas.height; + if (this._lastCanvasWidth !== newWidth || this._lastCanvasHeight !== newHeight) { + if (this._lastCanvasWidth >= 0) { + this._renderTargetPool.evictBySize(this._lastCanvasWidth, this._lastCanvasHeight); + } + this._lastCanvasWidth = newWidth; + this._lastCanvasHeight = newHeight; + } + }; private _animate = () => { if (this._vSyncCount) { @@ -256,6 +275,9 @@ export class Engine extends EventDispatcher { this._batcherManager = new BatcherManager(this); this._renderTargetPool = new RenderTargetPool(this); + this._lastCanvasWidth = canvas.width; + this._lastCanvasHeight = canvas.height; + canvas._sizeUpdateFlagManager.addListener(this._onCanvasResize); this.inputManager = new InputManager(this, configuration.input); const { xrDevice } = configuration; @@ -324,6 +346,9 @@ export class Engine extends EventDispatcher { const time = this._time; time._update(); + // Evict pool entries idle past `maxFreeAgeFrames`; cheap linear scan over the (small) free list. + this._renderTargetPool.tick(time.frameCount); + const deltaTime = time.deltaTime; this._frameInProcess = true; @@ -501,6 +526,8 @@ export class Engine extends EventDispatcher { this._destroyed = true; this._waitingDestroy = false; + this._canvas._sizeUpdateFlagManager.removeListener(this._onCanvasResize); + this._sceneManager._destroyAllScene(); this._resourceManager._destroy(); diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index 28049597dd..370b3ec0e8 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -199,19 +199,9 @@ export class BasicRenderPipeline { } this._internalColorTarget = internalColorTarget; - } else { - const internalColorTarget = this._internalColorTarget; - const copyBackgroundTexture = this._copyBackgroundTexture; - const pool = engine._renderTargetPool; - if (internalColorTarget) { - pool.freeRenderTarget(internalColorTarget); - this._internalColorTarget = null; - } - if (copyBackgroundTexture) { - pool.freeTexture(copyBackgroundTexture); - this._copyBackgroundTexture = null; - } } + // No `else` branch needed: `_drawRenderPass` releases both the internal RT and the copy-background + // texture back to the pool at end of frame, so this method always starts with both fields null. // Scalable ambient obscurance pass // Before opaque pass so materials can sample ambient occlusion in BRDF @@ -362,6 +352,18 @@ export class BasicRenderPipeline { cameraRenderTarget?._blitRenderTarget(); cameraRenderTarget?.generateMipmaps(); + + // Release per-frame leased resources back to the pool so concurrent cameras with matching shape + // share a single underlying RT through the pool instead of each holding its own across frames. + const pool = engine._renderTargetPool; + if (this._internalColorTarget) { + pool.freeRenderTarget(this._internalColorTarget); + this._internalColorTarget = null; + } + if (this._copyBackgroundTexture) { + pool.freeTexture(this._copyBackgroundTexture); + this._copyBackgroundTexture = null; + } } /** diff --git a/packages/core/src/RenderPipeline/RenderTargetPool.ts b/packages/core/src/RenderPipeline/RenderTargetPool.ts index 3eae130d62..5654f106cd 100644 --- a/packages/core/src/RenderPipeline/RenderTargetPool.ts +++ b/packages/core/src/RenderPipeline/RenderTargetPool.ts @@ -1,13 +1,45 @@ import { Engine } from "../Engine"; -import { RenderTarget, Texture2D, TextureFilterMode, TextureFormat, TextureWrapMode } from "../texture"; +import { RenderTarget, Texture, Texture2D, TextureFilterMode, TextureFormat, TextureWrapMode } from "../texture"; /** + * Pool of `RenderTarget`s and `Texture2D`s used internally by the render pipeline. + * + * Entries returned via `freeRenderTarget`/`freeTexture` stay in the free list and are matched + * (by shape) for the next `allocate*` request. Three eviction strategies keep the free list bounded: + * + * 1. **Frame-age** — entries unused for more than `maxFreeAgeFrames` engine ticks are destroyed by `tick()`. + * 2. **Size-matched** — `evictBySize(w, h)` removes entries whose dimensions match the given size, called + * by the engine on canvas resize to clear out old full-canvas RTs. + * 3. **Memory cap** — `freeRenderTarget`/`freeTexture` evict the oldest entries (by `lastUsedFrame`) until the + * free-list footprint is at or below `maxFreeBytes`. Caps the pool against pathological churn. + * * @internal */ export class RenderTargetPool { + /** + * Maximum number of engine frames an entry may sit unused in the free list before `tick()` destroys it. + * Defaults to ~1 second at 60fps so reflection probes / periodic off-frame passes survive a short gap. + */ + maxFreeAgeFrames: number = 60; + + /** + * Soft cap on the total bytes pooled in the free list. When `freeRenderTarget`/`freeTexture` push an entry + * that would exceed this, the oldest entries (by `lastUsedFrame`) are destroyed until the total fits. + * Tracks only entries currently in the free list — entries actively leased by a pipeline are not counted. + */ + maxFreeBytes: number = 64 * 1024 * 1024; + + private _engine: Engine; + private _freeRenderTargets: RenderTarget[] = []; + private _freeRenderTargetFrames: number[] = []; + private _freeRenderTargetBytes: number[] = []; + private _freeRenderTargetByteTotal: number = 0; + private _freeTextures: Texture2D[] = []; - private _engine: Engine; + private _freeTextureFrames: number[] = []; + private _freeTextureBytes: number[] = []; + private _freeTextureByteTotal: number = 0; constructor(engine: Engine) { this._engine = engine; @@ -41,8 +73,7 @@ export class RenderTargetPool { antiAliasing ) ) { - freeRenderTargets[i] = freeRenderTargets[freeRenderTargets.length - 1]; - freeRenderTargets.length--; + this._removeFreeRenderTargetAt(i); const colorTexture = renderTarget.getColorTexture(0) as Texture2D; if (colorTexture) { colorTexture.wrapModeU = colorTexture.wrapModeV = wrapMode; @@ -103,8 +134,7 @@ export class RenderTargetPool { texture.mipmapCount > 1 === mipmap && texture.isSRGBColorSpace === isSRGBColorSpace ) { - freeTextures[i] = freeTextures[freeTextures.length - 1]; - freeTextures.length--; + this._removeFreeTextureAt(i); texture.wrapModeU = texture.wrapModeV = wrapMode; texture.filterMode = filterMode; return texture; @@ -121,33 +151,201 @@ export class RenderTargetPool { freeRenderTarget(renderTarget: RenderTarget): void { if (!renderTarget || renderTarget.destroyed) return; + const bytes = RenderTargetPool._computeRtBytes(renderTarget); this._freeRenderTargets.push(renderTarget); + this._freeRenderTargetFrames.push(this._engine.time.frameCount); + this._freeRenderTargetBytes.push(bytes); + this._freeRenderTargetByteTotal += bytes; + this._enforceRenderTargetMemoryCap(); } freeTexture(texture: Texture2D): void { if (!texture || texture.destroyed) return; + const bytes = texture._memorySize; this._freeTextures.push(texture); + this._freeTextureFrames.push(this._engine.time.frameCount); + this._freeTextureBytes.push(bytes); + this._freeTextureByteTotal += bytes; + this._enforceTextureMemoryCap(); + } + + /** + * Destroy entries that have been idle in the free list for longer than `maxFreeAgeFrames`. + * Called once per engine frame. + */ + tick(currentFrame: number): void { + const maxAge = this.maxFreeAgeFrames; + const rtFrames = this._freeRenderTargetFrames; + for (let i = rtFrames.length - 1; i >= 0; i--) { + if (currentFrame - rtFrames[i] > maxAge) { + this._destroyFreeRenderTargetAt(i); + } + } + const texFrames = this._freeTextureFrames; + for (let i = texFrames.length - 1; i >= 0; i--) { + if (currentFrame - texFrames[i] > maxAge) { + this._destroyFreeTextureAt(i); + } + } + } + + /** + * Destroy free-list entries whose dimensions exactly match the given size. Called when the canvas + * resizes so full-canvas RTs cached at the previous resolution don't linger waiting for `tick()`. + */ + evictBySize(width: number, height: number): void { + const freeRenderTargets = this._freeRenderTargets; + for (let i = freeRenderTargets.length - 1; i >= 0; i--) { + const rt = freeRenderTargets[i]; + if (rt.width === width && rt.height === height) { + this._destroyFreeRenderTargetAt(i); + } + } + const freeTextures = this._freeTextures; + for (let i = freeTextures.length - 1; i >= 0; i--) { + const tex = freeTextures[i]; + if (tex.width === width && tex.height === height) { + this._destroyFreeTextureAt(i); + } + } + } + + /** + * Total bytes currently held in the free list (RT entries + standalone Texture2D entries). + * Active leased entries are not included. + */ + get freeListByteSize(): number { + return this._freeRenderTargetByteTotal + this._freeTextureByteTotal; } gc(): void { const freeRenderTargets = this._freeRenderTargets; for (let i = 0, n = freeRenderTargets.length; i < n; i++) { - const renderTarget = freeRenderTargets[i]; - const colorTexture = renderTarget.getColorTexture(0); - const depthTexture = renderTarget.depthTexture; - renderTarget.destroy(true); - colorTexture?.destroy(true); - if (depthTexture && depthTexture !== colorTexture) { - depthTexture.destroy(true); - } + RenderTargetPool._destroyRenderTargetResource(freeRenderTargets[i]); } freeRenderTargets.length = 0; + this._freeRenderTargetFrames.length = 0; + this._freeRenderTargetBytes.length = 0; + this._freeRenderTargetByteTotal = 0; const freeTextures = this._freeTextures; for (let i = 0, n = freeTextures.length; i < n; i++) { freeTextures[i].destroy(true); } freeTextures.length = 0; + this._freeTextureFrames.length = 0; + this._freeTextureBytes.length = 0; + this._freeTextureByteTotal = 0; + } + + /** + * Swap-pop helper: remove free RT entry at `index` without destroying the RT (used when handing it out). + */ + private _removeFreeRenderTargetAt(index: number): void { + const rts = this._freeRenderTargets; + const frames = this._freeRenderTargetFrames; + const bytes = this._freeRenderTargetBytes; + const last = rts.length - 1; + this._freeRenderTargetByteTotal -= bytes[index]; + if (index !== last) { + rts[index] = rts[last]; + frames[index] = frames[last]; + bytes[index] = bytes[last]; + } + rts.length = last; + frames.length = last; + bytes.length = last; + } + + /** + * Swap-pop helper for the texture free list. + */ + private _removeFreeTextureAt(index: number): void { + const texs = this._freeTextures; + const frames = this._freeTextureFrames; + const bytes = this._freeTextureBytes; + const last = texs.length - 1; + this._freeTextureByteTotal -= bytes[index]; + if (index !== last) { + texs[index] = texs[last]; + frames[index] = frames[last]; + bytes[index] = bytes[last]; + } + texs.length = last; + frames.length = last; + bytes.length = last; + } + + /** + * Destroy free RT entry at `index` (called when evicting, not when leasing out). + */ + private _destroyFreeRenderTargetAt(index: number): void { + const rt = this._freeRenderTargets[index]; + this._removeFreeRenderTargetAt(index); + RenderTargetPool._destroyRenderTargetResource(rt); + } + + private _destroyFreeTextureAt(index: number): void { + const tex = this._freeTextures[index]; + this._removeFreeTextureAt(index); + tex.destroy(true); + } + + /** + * Evict oldest RT entries (by `lastUsedFrame`) until the free list is at or below the cap. + */ + private _enforceRenderTargetMemoryCap(): void { + while (this._freeRenderTargetByteTotal > this.maxFreeBytes && this._freeRenderTargets.length > 0) { + const oldest = this._findOldestIndex(this._freeRenderTargetFrames); + this._destroyFreeRenderTargetAt(oldest); + } + } + + private _enforceTextureMemoryCap(): void { + while (this._freeTextureByteTotal > this.maxFreeBytes && this._freeTextures.length > 0) { + const oldest = this._findOldestIndex(this._freeTextureFrames); + this._destroyFreeTextureAt(oldest); + } + } + + private _findOldestIndex(frames: number[]): number { + let oldestIdx = 0; + let oldestFrame = frames[0]; + for (let i = 1, n = frames.length; i < n; i++) { + if (frames[i] < oldestFrame) { + oldestFrame = frames[i]; + oldestIdx = i; + } + } + return oldestIdx; + } + + /** + * Sum the bytes "owned" by an RT entry: the RT's own MSAA/depth-RBO accounting plus any + * color/depth textures it references. Mirrors what `_renderingStatistics._textureMemory` + * would drop if this entry were destroyed. + */ + private static _computeRtBytes(rt: RenderTarget): number { + let bytes = rt._memorySize; + const color = rt.getColorTexture(0) as Texture | null; + if (color) { + bytes += color._memorySize; + } + const depth = rt.depthTexture as Texture | null; + if (depth && depth !== color) { + bytes += depth._memorySize; + } + return bytes; + } + + private static _destroyRenderTargetResource(rt: RenderTarget): void { + const colorTexture = rt.getColorTexture(0); + const depthTexture = rt.depthTexture; + rt.destroy(true); + colorTexture?.destroy(true); + if (depthTexture && depthTexture !== colorTexture) { + depthTexture.destroy(true); + } } private static _matchRenderTarget( diff --git a/packages/core/src/RenderPipeline/index.ts b/packages/core/src/RenderPipeline/index.ts index a95c0f8f46..1caab356b5 100644 --- a/packages/core/src/RenderPipeline/index.ts +++ b/packages/core/src/RenderPipeline/index.ts @@ -4,3 +4,4 @@ export { Blitter } from "./Blitter"; export { PipelineStage } from "./enums/PipelineStage"; export { RenderElement } from "./RenderElement"; export { RenderQueue } from "./RenderQueue"; +export { RenderTargetPool } from "./RenderTargetPool"; diff --git a/packages/core/src/texture/RenderTarget.ts b/packages/core/src/texture/RenderTarget.ts index f0c888be34..04fdb6e54d 100644 --- a/packages/core/src/texture/RenderTarget.ts +++ b/packages/core/src/texture/RenderTarget.ts @@ -21,13 +21,14 @@ export class RenderTarget extends GraphicsResource { _antiAliasing: number; /** @internal */ _depthFormat: TextureFormat | null = null; + /** @internal */ + _memorySize: number = 0; private _autoGenerateMipmaps: boolean = true; private _width: number; private _height: number; private _colorTextures: Texture[]; private _depthTexture: Texture | null = null; - private _memorySize: number = 0; /** * Whether to automatically generate multi-level textures. diff --git a/tests/src/core/RenderPipeline/RenderTargetPool.test.ts b/tests/src/core/RenderPipeline/RenderTargetPool.test.ts new file mode 100644 index 0000000000..2feaa48694 --- /dev/null +++ b/tests/src/core/RenderPipeline/RenderTargetPool.test.ts @@ -0,0 +1,177 @@ +import { + RenderTargetPool, + TextureFilterMode, + TextureFormat, + TextureWrapMode +} from "@galacean/engine-core"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { beforeAll, beforeEach, describe, expect, it } from "vitest"; + +/** + * Helper: allocate an RT through the pool with sane defaults; varies only the bits that affect matching. + */ +function alloc( + pool: RenderTargetPool, + width: number, + height: number, + opts: { colorFormat?: TextureFormat; depthFormat?: TextureFormat | null; aa?: number } = {} +) { + return pool.allocateRenderTarget( + width, + height, + opts.colorFormat ?? TextureFormat.R8G8B8A8, + opts.depthFormat === undefined ? TextureFormat.Depth24Stencil8 : opts.depthFormat, + false, + false, + false, + opts.aa ?? 1, + TextureWrapMode.Clamp, + TextureFilterMode.Bilinear + ); +} + +describe("RenderTargetPool", () => { + const canvas = document.createElement("canvas"); + let engine: WebGLEngine; + let pool: RenderTargetPool; + + beforeAll(async () => { + engine = await WebGLEngine.create({ canvas }); + }); + + beforeEach(() => { + // Each test gets a fresh pool so leaked entries from earlier tests don't bleed across. + pool = new RenderTargetPool(engine); + }); + + describe("matching reuse", () => { + it("returns the same RT instance when the next allocate matches a freed entry's shape", () => { + const a = alloc(pool, 512, 512); + pool.freeRenderTarget(a); + const b = alloc(pool, 512, 512); + expect(b).to.equal(a); + }); + + it("allocates a fresh RT when shape does not match any freed entry", () => { + const a = alloc(pool, 512, 512); + pool.freeRenderTarget(a); + const b = alloc(pool, 256, 256); + expect(b).to.not.equal(a); + }); + + it("simulates multi-camera frame-internal reuse: A free → B alloc returns A's RT", () => { + // Camera A renders at full canvas, then releases + const a = alloc(pool, 1024, 768); + pool.freeRenderTarget(a); + // Camera B renders next at the same shape and finds A's RT in the pool + const b = alloc(pool, 1024, 768); + expect(b).to.equal(a); + pool.freeRenderTarget(b); + // Pool is back to one entry after both cameras returned the same RT + // (we can't directly observe size, but the next match-alloc must return it too) + const c = alloc(pool, 1024, 768); + expect(c).to.equal(a); + }); + }); + + describe("frame-age eviction via tick()", () => { + it("does not evict entries within maxFreeAgeFrames", () => { + pool.maxFreeAgeFrames = 5; + const a = alloc(pool, 256, 256); + pool.freeRenderTarget(a); + const baseFrame = engine.time.frameCount; + pool.tick(baseFrame + 3); + const b = alloc(pool, 256, 256); + expect(b).to.equal(a); + }); + + it("destroys entries idle longer than maxFreeAgeFrames", () => { + pool.maxFreeAgeFrames = 5; + const a = alloc(pool, 256, 256); + const baseFrame = engine.time.frameCount; + pool.freeRenderTarget(a); + pool.tick(baseFrame + 100); + // Entry was destroyed; next allocate produces a fresh RT + const b = alloc(pool, 256, 256); + expect(b).to.not.equal(a); + expect(a.destroyed).to.equal(true); + }); + }); + + describe("evictBySize for canvas resize", () => { + it("destroys free-list entries matching the given size", () => { + const a = alloc(pool, 800, 600); + const b = alloc(pool, 1024, 768); + pool.freeRenderTarget(a); + pool.freeRenderTarget(b); + + pool.evictBySize(800, 600); + expect(a.destroyed).to.equal(true); + expect(b.destroyed).to.equal(false); + + // Re-allocating at the other size still returns the survivor + const reused = alloc(pool, 1024, 768); + expect(reused).to.equal(b); + }); + + it("ignores entries whose dimensions do not match", () => { + const a = alloc(pool, 800, 600); + pool.freeRenderTarget(a); + pool.evictBySize(1024, 768); + expect(a.destroyed).to.equal(false); + const b = alloc(pool, 800, 600); + expect(b).to.equal(a); + }); + }); + + describe("free-list memory cap", () => { + it("destroys entries while the free list exceeds maxFreeBytes", () => { + // Cap below the size of even one 256×256 RGBA8+D24S8 RT, so any push immediately overflows. + pool.maxFreeBytes = 1; + const a = alloc(pool, 256, 256); + pool.freeRenderTarget(a); + expect(a.destroyed).to.equal(true); + expect(pool.freeListByteSize).to.equal(0); + }); + + it("keeps total free-list bytes at or below maxFreeBytes after each push", () => { + pool.maxFreeBytes = 1024 * 1024; // 1 MB + const a = alloc(pool, 256, 256); + const b = alloc(pool, 256, 256); + const c = alloc(pool, 256, 256); + pool.freeRenderTarget(a); + pool.freeRenderTarget(b); + pool.freeRenderTarget(c); + expect(pool.freeListByteSize).to.be.at.most(pool.maxFreeBytes); + }); + + it("freeListByteSize reflects current free-list contents", () => { + pool.maxFreeBytes = Infinity; + const a = alloc(pool, 128, 128); + pool.freeRenderTarget(a); + const sizeAfterFirst = pool.freeListByteSize; + expect(sizeAfterFirst).to.be.greaterThan(0); + + const b = alloc(pool, 128, 128); // matches, lease out + expect(b).to.equal(a); + expect(pool.freeListByteSize).to.equal(0); + pool.freeRenderTarget(b); + expect(pool.freeListByteSize).to.equal(sizeAfterFirst); + }); + }); + + describe("gc()", () => { + it("destroys all free-list entries and zeros the byte total", () => { + const a = alloc(pool, 256, 256); + const b = alloc(pool, 512, 512); + pool.freeRenderTarget(a); + pool.freeRenderTarget(b); + expect(pool.freeListByteSize).to.be.greaterThan(0); + + pool.gc(); + expect(a.destroyed).to.equal(true); + expect(b.destroyed).to.equal(true); + expect(pool.freeListByteSize).to.equal(0); + }); + }); +}); From 75d8044207408dd24a29c08186040a8f63d3735d Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 26 May 2026 18:07:56 +0800 Subject: [PATCH 113/130] =?UTF-8?q?perf(pipeline):=20address=20CR=20?= =?UTF-8?q?=E2=80=94=20unify=20memory=20cap,=20document=20byte=20contract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three review fixes on top of the previous commit: 1. `maxFreeBytes` now applies to the combined free-list total (RT + Texture) instead of each list independently. Previously, with the default 64 MB cap, the pool could actually hold up to 128 MB (64 MB RT + 64 MB Texture) — inconsistent with what `freeListByteSize` reports. The unified `_enforceMemoryCap` picks the older entry across both pools by `lastUsedFrame` and evicts until the combined sum is at or below the cap. 2. `_computeRtBytes` now documents the contract it depends on: that `RenderTarget._memorySize` covers only the RT's own renderbuffers (MSAA + depth RBO) and excludes the attached `colorTexture` / `depthTexture`, whose bytes live on `Texture._memorySize`. So the sum does not double-count. 3. `RenderTargetPool` is no longer re-exported from `RenderPipeline/index.ts` — it stays `@internal`. The test imports it via a relative source path instead, keeping the public surface unchanged. Added a 12th unit test verifying the unified cap actually bounds the combined total. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/RenderPipeline/RenderTargetPool.ts | 58 ++++++++++++++----- packages/core/src/RenderPipeline/index.ts | 1 - .../RenderPipeline/RenderTargetPool.test.ts | 38 ++++++++++-- 3 files changed, 74 insertions(+), 23 deletions(-) diff --git a/packages/core/src/RenderPipeline/RenderTargetPool.ts b/packages/core/src/RenderPipeline/RenderTargetPool.ts index 5654f106cd..b6585c5e09 100644 --- a/packages/core/src/RenderPipeline/RenderTargetPool.ts +++ b/packages/core/src/RenderPipeline/RenderTargetPool.ts @@ -156,7 +156,7 @@ export class RenderTargetPool { this._freeRenderTargetFrames.push(this._engine.time.frameCount); this._freeRenderTargetBytes.push(bytes); this._freeRenderTargetByteTotal += bytes; - this._enforceRenderTargetMemoryCap(); + this._enforceMemoryCap(); } freeTexture(texture: Texture2D): void { @@ -166,7 +166,7 @@ export class RenderTargetPool { this._freeTextureFrames.push(this._engine.time.frameCount); this._freeTextureBytes.push(bytes); this._freeTextureByteTotal += bytes; - this._enforceTextureMemoryCap(); + this._enforceMemoryCap(); } /** @@ -292,19 +292,36 @@ export class RenderTargetPool { } /** - * Evict oldest RT entries (by `lastUsedFrame`) until the free list is at or below the cap. + * Evict the oldest entry across BOTH free lists (by `lastUsedFrame`) until the combined byte + * total is at or below `maxFreeBytes`. The cap applies to the sum reported by `freeListByteSize`, + * not to each list independently. */ - private _enforceRenderTargetMemoryCap(): void { - while (this._freeRenderTargetByteTotal > this.maxFreeBytes && this._freeRenderTargets.length > 0) { - const oldest = this._findOldestIndex(this._freeRenderTargetFrames); - this._destroyFreeRenderTargetAt(oldest); - } - } + private _enforceMemoryCap(): void { + const cap = this.maxFreeBytes; + while ( + this._freeRenderTargetByteTotal + this._freeTextureByteTotal > cap && + (this._freeRenderTargets.length > 0 || this._freeTextures.length > 0) + ) { + const rtFrames = this._freeRenderTargetFrames; + const texFrames = this._freeTextureFrames; + const rtOldest = rtFrames.length > 0 ? this._findOldestIndex(rtFrames) : -1; + const texOldest = texFrames.length > 0 ? this._findOldestIndex(texFrames) : -1; + + let evictRt: boolean; + if (rtOldest < 0) { + evictRt = false; + } else if (texOldest < 0) { + evictRt = true; + } else { + // Tie-break toward RT: pool entries dominate the byte budget, so this converges faster. + evictRt = rtFrames[rtOldest] <= texFrames[texOldest]; + } - private _enforceTextureMemoryCap(): void { - while (this._freeTextureByteTotal > this.maxFreeBytes && this._freeTextures.length > 0) { - const oldest = this._findOldestIndex(this._freeTextureFrames); - this._destroyFreeTextureAt(oldest); + if (evictRt) { + this._destroyFreeRenderTargetAt(rtOldest); + } else { + this._destroyFreeTextureAt(texOldest); + } } } @@ -321,9 +338,18 @@ export class RenderTargetPool { } /** - * Sum the bytes "owned" by an RT entry: the RT's own MSAA/depth-RBO accounting plus any - * color/depth textures it references. Mirrors what `_renderingStatistics._textureMemory` - * would drop if this entry were destroyed. + * Sum the bytes "owned" by an RT entry. Mirrors what `_renderingStatistics._textureMemory` + * would drop if this entry were fully destroyed. + * + * Contract relied on here (see `RenderTarget.ts` ctor): + * `RenderTarget._memorySize` accounts ONLY for the RT's own renderbuffers — + * - MSAA: `(colorRBO + depthRBO) * antiAliasing` (the multisampled side; resolves into the + * attached `colorTexture`, which is tracked separately on the Texture itself) + * - Non-MSAA: just the depth RBO when depth is given as a format, else 0 + * It does NOT include the bytes of the attached `colorTexture` / `depthTexture`, which each + * carry their own `_memorySize` from the `Texture` ctor. + * So summing `rt._memorySize + colorTexture._memorySize + depthTexture._memorySize` does not + * double-count. */ private static _computeRtBytes(rt: RenderTarget): number { let bytes = rt._memorySize; diff --git a/packages/core/src/RenderPipeline/index.ts b/packages/core/src/RenderPipeline/index.ts index 1caab356b5..a95c0f8f46 100644 --- a/packages/core/src/RenderPipeline/index.ts +++ b/packages/core/src/RenderPipeline/index.ts @@ -4,4 +4,3 @@ export { Blitter } from "./Blitter"; export { PipelineStage } from "./enums/PipelineStage"; export { RenderElement } from "./RenderElement"; export { RenderQueue } from "./RenderQueue"; -export { RenderTargetPool } from "./RenderTargetPool"; diff --git a/tests/src/core/RenderPipeline/RenderTargetPool.test.ts b/tests/src/core/RenderPipeline/RenderTargetPool.test.ts index 2feaa48694..58c7e5dd6b 100644 --- a/tests/src/core/RenderPipeline/RenderTargetPool.test.ts +++ b/tests/src/core/RenderPipeline/RenderTargetPool.test.ts @@ -1,9 +1,7 @@ -import { - RenderTargetPool, - TextureFilterMode, - TextureFormat, - TextureWrapMode -} from "@galacean/engine-core"; +import { TextureFilterMode, TextureFormat, TextureWrapMode } from "@galacean/engine-core"; +// `RenderTargetPool` is `@internal` and intentionally not re-exported from the core barrel. +// Import directly from the source file for test access. +import { RenderTargetPool } from "../../../../packages/core/src/RenderPipeline/RenderTargetPool"; import { WebGLEngine } from "@galacean/engine-rhi-webgl"; import { beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -158,6 +156,34 @@ describe("RenderTargetPool", () => { pool.freeRenderTarget(b); expect(pool.freeListByteSize).to.equal(sizeAfterFirst); }); + + it("maxFreeBytes covers RT + Texture combined, not each pool independently", () => { + // Allocate two distinct shapes so they can't dedupe via match. + const rt = alloc(pool, 256, 256); + const tex = pool.allocateTexture( + 256, + 256, + TextureFormat.R8G8B8A8, + false, + false, + TextureWrapMode.Clamp, + TextureFilterMode.Bilinear + ); + pool.maxFreeBytes = Infinity; + pool.freeRenderTarget(rt); + pool.freeTexture(tex); + const total = pool.freeListByteSize; + expect(total).to.be.greaterThan(0); + + // Set the cap just below the combined total — one of the two must be evicted. + pool.maxFreeBytes = total - 1; + // Trigger a re-check by pushing and immediately re-leasing an entry of the same shape as `rt`. + // Easiest path: free a no-op entry. Instead, directly observe by allocating a tiny RT and freeing it + // to force the cap to be re-evaluated. + const probe = alloc(pool, 1, 1); + pool.freeRenderTarget(probe); + expect(pool.freeListByteSize).to.be.at.most(pool.maxFreeBytes); + }); }); describe("gc()", () => { From d3ccd969f983333e418402158acbebe29c04af65 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 26 May 2026 18:21:34 +0800 Subject: [PATCH 114/130] perf(pipeline): tick() re-evaluates memory cap; test cleans up engine CR follow-ups: * `tick()` now calls `_enforceMemoryCap()` at the end, so a mid-run reduction of `maxFreeBytes` takes effect within one frame instead of waiting for the next `free*` call. Cost is one extra scan per frame over an already-tiny free list. * Test file adds `afterAll(() => engine.destroy())` to release the WebGL context between test files. * New test locks in the tick-re-enforces-cap behavior. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/RenderPipeline/RenderTargetPool.ts | 6 ++++-- .../RenderPipeline/RenderTargetPool.test.ts | 20 ++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/core/src/RenderPipeline/RenderTargetPool.ts b/packages/core/src/RenderPipeline/RenderTargetPool.ts index b6585c5e09..bdb247b3f9 100644 --- a/packages/core/src/RenderPipeline/RenderTargetPool.ts +++ b/packages/core/src/RenderPipeline/RenderTargetPool.ts @@ -170,8 +170,9 @@ export class RenderTargetPool { } /** - * Destroy entries that have been idle in the free list for longer than `maxFreeAgeFrames`. - * Called once per engine frame. + * Destroy entries that have been idle in the free list for longer than `maxFreeAgeFrames`, + * then re-evaluate the memory cap so a mid-run change to `maxFreeBytes` takes effect within + * one frame (rather than waiting for the next `free*` call). Called once per engine frame. */ tick(currentFrame: number): void { const maxAge = this.maxFreeAgeFrames; @@ -187,6 +188,7 @@ export class RenderTargetPool { this._destroyFreeTextureAt(i); } } + this._enforceMemoryCap(); } /** diff --git a/tests/src/core/RenderPipeline/RenderTargetPool.test.ts b/tests/src/core/RenderPipeline/RenderTargetPool.test.ts index 58c7e5dd6b..5fe51a87e8 100644 --- a/tests/src/core/RenderPipeline/RenderTargetPool.test.ts +++ b/tests/src/core/RenderPipeline/RenderTargetPool.test.ts @@ -3,7 +3,7 @@ import { TextureFilterMode, TextureFormat, TextureWrapMode } from "@galacean/eng // Import directly from the source file for test access. import { RenderTargetPool } from "../../../../packages/core/src/RenderPipeline/RenderTargetPool"; import { WebGLEngine } from "@galacean/engine-rhi-webgl"; -import { beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; /** * Helper: allocate an RT through the pool with sane defaults; varies only the bits that affect matching. @@ -37,6 +37,10 @@ describe("RenderTargetPool", () => { engine = await WebGLEngine.create({ canvas }); }); + afterAll(() => { + engine.destroy(); + }); + beforeEach(() => { // Each test gets a fresh pool so leaked entries from earlier tests don't bleed across. pool = new RenderTargetPool(engine); @@ -94,6 +98,20 @@ describe("RenderTargetPool", () => { expect(b).to.not.equal(a); expect(a.destroyed).to.equal(true); }); + + it("tick re-enforces maxFreeBytes after a mid-run cap reduction", () => { + pool.maxFreeAgeFrames = Infinity; // isolate from age-based eviction + pool.maxFreeBytes = Infinity; + const a = alloc(pool, 256, 256); + pool.freeRenderTarget(a); + expect(a.destroyed).to.equal(false); + + // User lowers the cap below the entry's size; tick should evict. + pool.maxFreeBytes = 1; + pool.tick(engine.time.frameCount); + expect(a.destroyed).to.equal(true); + expect(pool.freeListByteSize).to.equal(0); + }); }); describe("evictBySize for canvas resize", () => { From b41b8f7367239d8e26ccada70696944a9727bc3a Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 26 May 2026 23:31:03 +0800 Subject: [PATCH 115/130] perf(pipeline): drop maxFreeBytes cap; frame-age + resize-evict are enough MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The byte cap was defending against pathological churn within the age window — a scenario covered in practice by canvas-resize eviction (shape coupled to canvas) and frame-age (steady state). With the default 64 MB cap, a single full-canvas MSAA 4x RT (~86 MB on a 1078x2249 RGBA8+D24S8 canvas) was larger than the cap. Every free immediately destroyed the just-pushed RT, defeating the multi-camera sharing this PR exists to enable. The abstraction was unfortunately calibrated against a fictional worst case; the realistic worst cases are already bounded. Dropping it removes a tunable that's hard to set well (device-dependent, no single number works) and a sizable chunk of byte-tracking machinery (`_freeRenderTargetBytes`, `_freeRenderTargetByteTotal`, the combined-pool LRU in `_enforceMemoryCap`, `_computeRtBytes`, `_findOldestIndex`, `freeListByteSize`). `RenderTarget._memorySize` reverts to `private` — pool no longer reads it. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/RenderPipeline/RenderTargetPool.ts | 124 +----------------- packages/core/src/texture/RenderTarget.ts | 3 +- .../RenderPipeline/RenderTargetPool.test.ts | 82 +----------- 3 files changed, 6 insertions(+), 203 deletions(-) diff --git a/packages/core/src/RenderPipeline/RenderTargetPool.ts b/packages/core/src/RenderPipeline/RenderTargetPool.ts index bdb247b3f9..2920d6e377 100644 --- a/packages/core/src/RenderPipeline/RenderTargetPool.ts +++ b/packages/core/src/RenderPipeline/RenderTargetPool.ts @@ -1,17 +1,15 @@ import { Engine } from "../Engine"; -import { RenderTarget, Texture, Texture2D, TextureFilterMode, TextureFormat, TextureWrapMode } from "../texture"; +import { RenderTarget, Texture2D, TextureFilterMode, TextureFormat, TextureWrapMode } from "../texture"; /** * Pool of `RenderTarget`s and `Texture2D`s used internally by the render pipeline. * * Entries returned via `freeRenderTarget`/`freeTexture` stay in the free list and are matched - * (by shape) for the next `allocate*` request. Three eviction strategies keep the free list bounded: + * (by shape) for the next `allocate*` request. Two eviction strategies keep the free list bounded: * * 1. **Frame-age** — entries unused for more than `maxFreeAgeFrames` engine ticks are destroyed by `tick()`. * 2. **Size-matched** — `evictBySize(w, h)` removes entries whose dimensions match the given size, called * by the engine on canvas resize to clear out old full-canvas RTs. - * 3. **Memory cap** — `freeRenderTarget`/`freeTexture` evict the oldest entries (by `lastUsedFrame`) until the - * free-list footprint is at or below `maxFreeBytes`. Caps the pool against pathological churn. * * @internal */ @@ -22,24 +20,13 @@ export class RenderTargetPool { */ maxFreeAgeFrames: number = 60; - /** - * Soft cap on the total bytes pooled in the free list. When `freeRenderTarget`/`freeTexture` push an entry - * that would exceed this, the oldest entries (by `lastUsedFrame`) are destroyed until the total fits. - * Tracks only entries currently in the free list — entries actively leased by a pipeline are not counted. - */ - maxFreeBytes: number = 64 * 1024 * 1024; - private _engine: Engine; private _freeRenderTargets: RenderTarget[] = []; private _freeRenderTargetFrames: number[] = []; - private _freeRenderTargetBytes: number[] = []; - private _freeRenderTargetByteTotal: number = 0; private _freeTextures: Texture2D[] = []; private _freeTextureFrames: number[] = []; - private _freeTextureBytes: number[] = []; - private _freeTextureByteTotal: number = 0; constructor(engine: Engine) { this._engine = engine; @@ -151,28 +138,19 @@ export class RenderTargetPool { freeRenderTarget(renderTarget: RenderTarget): void { if (!renderTarget || renderTarget.destroyed) return; - const bytes = RenderTargetPool._computeRtBytes(renderTarget); this._freeRenderTargets.push(renderTarget); this._freeRenderTargetFrames.push(this._engine.time.frameCount); - this._freeRenderTargetBytes.push(bytes); - this._freeRenderTargetByteTotal += bytes; - this._enforceMemoryCap(); } freeTexture(texture: Texture2D): void { if (!texture || texture.destroyed) return; - const bytes = texture._memorySize; this._freeTextures.push(texture); this._freeTextureFrames.push(this._engine.time.frameCount); - this._freeTextureBytes.push(bytes); - this._freeTextureByteTotal += bytes; - this._enforceMemoryCap(); } /** - * Destroy entries that have been idle in the free list for longer than `maxFreeAgeFrames`, - * then re-evaluate the memory cap so a mid-run change to `maxFreeBytes` takes effect within - * one frame (rather than waiting for the next `free*` call). Called once per engine frame. + * Destroy entries that have been idle in the free list for longer than `maxFreeAgeFrames`. + * Called once per engine frame. */ tick(currentFrame: number): void { const maxAge = this.maxFreeAgeFrames; @@ -188,7 +166,6 @@ export class RenderTargetPool { this._destroyFreeTextureAt(i); } } - this._enforceMemoryCap(); } /** @@ -212,14 +189,6 @@ export class RenderTargetPool { } } - /** - * Total bytes currently held in the free list (RT entries + standalone Texture2D entries). - * Active leased entries are not included. - */ - get freeListByteSize(): number { - return this._freeRenderTargetByteTotal + this._freeTextureByteTotal; - } - gc(): void { const freeRenderTargets = this._freeRenderTargets; for (let i = 0, n = freeRenderTargets.length; i < n; i++) { @@ -227,8 +196,6 @@ export class RenderTargetPool { } freeRenderTargets.length = 0; this._freeRenderTargetFrames.length = 0; - this._freeRenderTargetBytes.length = 0; - this._freeRenderTargetByteTotal = 0; const freeTextures = this._freeTextures; for (let i = 0, n = freeTextures.length; i < n; i++) { @@ -236,8 +203,6 @@ export class RenderTargetPool { } freeTextures.length = 0; this._freeTextureFrames.length = 0; - this._freeTextureBytes.length = 0; - this._freeTextureByteTotal = 0; } /** @@ -246,17 +211,13 @@ export class RenderTargetPool { private _removeFreeRenderTargetAt(index: number): void { const rts = this._freeRenderTargets; const frames = this._freeRenderTargetFrames; - const bytes = this._freeRenderTargetBytes; const last = rts.length - 1; - this._freeRenderTargetByteTotal -= bytes[index]; if (index !== last) { rts[index] = rts[last]; frames[index] = frames[last]; - bytes[index] = bytes[last]; } rts.length = last; frames.length = last; - bytes.length = last; } /** @@ -265,17 +226,13 @@ export class RenderTargetPool { private _removeFreeTextureAt(index: number): void { const texs = this._freeTextures; const frames = this._freeTextureFrames; - const bytes = this._freeTextureBytes; const last = texs.length - 1; - this._freeTextureByteTotal -= bytes[index]; if (index !== last) { texs[index] = texs[last]; frames[index] = frames[last]; - bytes[index] = bytes[last]; } texs.length = last; frames.length = last; - bytes.length = last; } /** @@ -293,79 +250,6 @@ export class RenderTargetPool { tex.destroy(true); } - /** - * Evict the oldest entry across BOTH free lists (by `lastUsedFrame`) until the combined byte - * total is at or below `maxFreeBytes`. The cap applies to the sum reported by `freeListByteSize`, - * not to each list independently. - */ - private _enforceMemoryCap(): void { - const cap = this.maxFreeBytes; - while ( - this._freeRenderTargetByteTotal + this._freeTextureByteTotal > cap && - (this._freeRenderTargets.length > 0 || this._freeTextures.length > 0) - ) { - const rtFrames = this._freeRenderTargetFrames; - const texFrames = this._freeTextureFrames; - const rtOldest = rtFrames.length > 0 ? this._findOldestIndex(rtFrames) : -1; - const texOldest = texFrames.length > 0 ? this._findOldestIndex(texFrames) : -1; - - let evictRt: boolean; - if (rtOldest < 0) { - evictRt = false; - } else if (texOldest < 0) { - evictRt = true; - } else { - // Tie-break toward RT: pool entries dominate the byte budget, so this converges faster. - evictRt = rtFrames[rtOldest] <= texFrames[texOldest]; - } - - if (evictRt) { - this._destroyFreeRenderTargetAt(rtOldest); - } else { - this._destroyFreeTextureAt(texOldest); - } - } - } - - private _findOldestIndex(frames: number[]): number { - let oldestIdx = 0; - let oldestFrame = frames[0]; - for (let i = 1, n = frames.length; i < n; i++) { - if (frames[i] < oldestFrame) { - oldestFrame = frames[i]; - oldestIdx = i; - } - } - return oldestIdx; - } - - /** - * Sum the bytes "owned" by an RT entry. Mirrors what `_renderingStatistics._textureMemory` - * would drop if this entry were fully destroyed. - * - * Contract relied on here (see `RenderTarget.ts` ctor): - * `RenderTarget._memorySize` accounts ONLY for the RT's own renderbuffers — - * - MSAA: `(colorRBO + depthRBO) * antiAliasing` (the multisampled side; resolves into the - * attached `colorTexture`, which is tracked separately on the Texture itself) - * - Non-MSAA: just the depth RBO when depth is given as a format, else 0 - * It does NOT include the bytes of the attached `colorTexture` / `depthTexture`, which each - * carry their own `_memorySize` from the `Texture` ctor. - * So summing `rt._memorySize + colorTexture._memorySize + depthTexture._memorySize` does not - * double-count. - */ - private static _computeRtBytes(rt: RenderTarget): number { - let bytes = rt._memorySize; - const color = rt.getColorTexture(0) as Texture | null; - if (color) { - bytes += color._memorySize; - } - const depth = rt.depthTexture as Texture | null; - if (depth && depth !== color) { - bytes += depth._memorySize; - } - return bytes; - } - private static _destroyRenderTargetResource(rt: RenderTarget): void { const colorTexture = rt.getColorTexture(0); const depthTexture = rt.depthTexture; diff --git a/packages/core/src/texture/RenderTarget.ts b/packages/core/src/texture/RenderTarget.ts index 04fdb6e54d..f0c888be34 100644 --- a/packages/core/src/texture/RenderTarget.ts +++ b/packages/core/src/texture/RenderTarget.ts @@ -21,14 +21,13 @@ export class RenderTarget extends GraphicsResource { _antiAliasing: number; /** @internal */ _depthFormat: TextureFormat | null = null; - /** @internal */ - _memorySize: number = 0; private _autoGenerateMipmaps: boolean = true; private _width: number; private _height: number; private _colorTextures: Texture[]; private _depthTexture: Texture | null = null; + private _memorySize: number = 0; /** * Whether to automatically generate multi-level textures. diff --git a/tests/src/core/RenderPipeline/RenderTargetPool.test.ts b/tests/src/core/RenderPipeline/RenderTargetPool.test.ts index 5fe51a87e8..6403a41884 100644 --- a/tests/src/core/RenderPipeline/RenderTargetPool.test.ts +++ b/tests/src/core/RenderPipeline/RenderTargetPool.test.ts @@ -98,20 +98,6 @@ describe("RenderTargetPool", () => { expect(b).to.not.equal(a); expect(a.destroyed).to.equal(true); }); - - it("tick re-enforces maxFreeBytes after a mid-run cap reduction", () => { - pool.maxFreeAgeFrames = Infinity; // isolate from age-based eviction - pool.maxFreeBytes = Infinity; - const a = alloc(pool, 256, 256); - pool.freeRenderTarget(a); - expect(a.destroyed).to.equal(false); - - // User lowers the cap below the entry's size; tick should evict. - pool.maxFreeBytes = 1; - pool.tick(engine.time.frameCount); - expect(a.destroyed).to.equal(true); - expect(pool.freeListByteSize).to.equal(0); - }); }); describe("evictBySize for canvas resize", () => { @@ -140,82 +126,16 @@ describe("RenderTargetPool", () => { }); }); - describe("free-list memory cap", () => { - it("destroys entries while the free list exceeds maxFreeBytes", () => { - // Cap below the size of even one 256×256 RGBA8+D24S8 RT, so any push immediately overflows. - pool.maxFreeBytes = 1; - const a = alloc(pool, 256, 256); - pool.freeRenderTarget(a); - expect(a.destroyed).to.equal(true); - expect(pool.freeListByteSize).to.equal(0); - }); - - it("keeps total free-list bytes at or below maxFreeBytes after each push", () => { - pool.maxFreeBytes = 1024 * 1024; // 1 MB - const a = alloc(pool, 256, 256); - const b = alloc(pool, 256, 256); - const c = alloc(pool, 256, 256); - pool.freeRenderTarget(a); - pool.freeRenderTarget(b); - pool.freeRenderTarget(c); - expect(pool.freeListByteSize).to.be.at.most(pool.maxFreeBytes); - }); - - it("freeListByteSize reflects current free-list contents", () => { - pool.maxFreeBytes = Infinity; - const a = alloc(pool, 128, 128); - pool.freeRenderTarget(a); - const sizeAfterFirst = pool.freeListByteSize; - expect(sizeAfterFirst).to.be.greaterThan(0); - - const b = alloc(pool, 128, 128); // matches, lease out - expect(b).to.equal(a); - expect(pool.freeListByteSize).to.equal(0); - pool.freeRenderTarget(b); - expect(pool.freeListByteSize).to.equal(sizeAfterFirst); - }); - - it("maxFreeBytes covers RT + Texture combined, not each pool independently", () => { - // Allocate two distinct shapes so they can't dedupe via match. - const rt = alloc(pool, 256, 256); - const tex = pool.allocateTexture( - 256, - 256, - TextureFormat.R8G8B8A8, - false, - false, - TextureWrapMode.Clamp, - TextureFilterMode.Bilinear - ); - pool.maxFreeBytes = Infinity; - pool.freeRenderTarget(rt); - pool.freeTexture(tex); - const total = pool.freeListByteSize; - expect(total).to.be.greaterThan(0); - - // Set the cap just below the combined total — one of the two must be evicted. - pool.maxFreeBytes = total - 1; - // Trigger a re-check by pushing and immediately re-leasing an entry of the same shape as `rt`. - // Easiest path: free a no-op entry. Instead, directly observe by allocating a tiny RT and freeing it - // to force the cap to be re-evaluated. - const probe = alloc(pool, 1, 1); - pool.freeRenderTarget(probe); - expect(pool.freeListByteSize).to.be.at.most(pool.maxFreeBytes); - }); - }); - describe("gc()", () => { - it("destroys all free-list entries and zeros the byte total", () => { + it("destroys all free-list entries", () => { const a = alloc(pool, 256, 256); const b = alloc(pool, 512, 512); pool.freeRenderTarget(a); pool.freeRenderTarget(b); - expect(pool.freeListByteSize).to.be.greaterThan(0); pool.gc(); expect(a.destroyed).to.equal(true); expect(b.destroyed).to.equal(true); - expect(pool.freeListByteSize).to.equal(0); }); }); }); From 2a1bd2f422520698e6042a240248dac6c86e631a Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 26 May 2026 23:38:03 +0800 Subject: [PATCH 116/130] style(pipeline): trim verbose RT pool comments Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/core/src/Engine.ts | 6 +--- .../src/RenderPipeline/BasicRenderPipeline.ts | 6 ++-- .../src/RenderPipeline/RenderTargetPool.ts | 33 +++---------------- 3 files changed, 7 insertions(+), 38 deletions(-) diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index 2454bf2049..69bceed1f3 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -141,10 +141,7 @@ export class Engine extends EventDispatcher { private _lastCanvasWidth: number = -1; private _lastCanvasHeight: number = -1; - /** - * Evict pool entries dimensioned to the previous canvas size when the canvas resizes, - * so full-canvas internal RTs cached at the old resolution don't linger until `tick()`. - */ + /** Evict pool entries sized to the previous canvas dimensions. */ private _onCanvasResize = (): void => { const canvas = this._canvas; const newWidth = canvas.width; @@ -346,7 +343,6 @@ export class Engine extends EventDispatcher { const time = this._time; time._update(); - // Evict pool entries idle past `maxFreeAgeFrames`; cheap linear scan over the (small) free list. this._renderTargetPool.tick(time.frameCount); const deltaTime = time.deltaTime; diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index 370b3ec0e8..1ecda00b41 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -200,8 +200,7 @@ export class BasicRenderPipeline { this._internalColorTarget = internalColorTarget; } - // No `else` branch needed: `_drawRenderPass` releases both the internal RT and the copy-background - // texture back to the pool at end of frame, so this method always starts with both fields null. + // Both fields are released at the end of `_drawRenderPass`, so they're null on every entry here. // Scalable ambient obscurance pass // Before opaque pass so materials can sample ambient occlusion in BRDF @@ -353,8 +352,7 @@ export class BasicRenderPipeline { cameraRenderTarget?._blitRenderTarget(); cameraRenderTarget?.generateMipmaps(); - // Release per-frame leased resources back to the pool so concurrent cameras with matching shape - // share a single underlying RT through the pool instead of each holding its own across frames. + // Release per-frame leases so the next camera with matching shape can reuse them. const pool = engine._renderTargetPool; if (this._internalColorTarget) { pool.freeRenderTarget(this._internalColorTarget); diff --git a/packages/core/src/RenderPipeline/RenderTargetPool.ts b/packages/core/src/RenderPipeline/RenderTargetPool.ts index 2920d6e377..2e918e53ea 100644 --- a/packages/core/src/RenderPipeline/RenderTargetPool.ts +++ b/packages/core/src/RenderPipeline/RenderTargetPool.ts @@ -3,21 +3,11 @@ import { RenderTarget, Texture2D, TextureFilterMode, TextureFormat, TextureWrapM /** * Pool of `RenderTarget`s and `Texture2D`s used internally by the render pipeline. - * - * Entries returned via `freeRenderTarget`/`freeTexture` stay in the free list and are matched - * (by shape) for the next `allocate*` request. Two eviction strategies keep the free list bounded: - * - * 1. **Frame-age** — entries unused for more than `maxFreeAgeFrames` engine ticks are destroyed by `tick()`. - * 2. **Size-matched** — `evictBySize(w, h)` removes entries whose dimensions match the given size, called - * by the engine on canvas resize to clear out old full-canvas RTs. - * + * Entries are matched by shape on `allocate*`; bounded by frame-age (`tick`) and size-match (`evictBySize`). * @internal */ export class RenderTargetPool { - /** - * Maximum number of engine frames an entry may sit unused in the free list before `tick()` destroys it. - * Defaults to ~1 second at 60fps so reflection probes / periodic off-frame passes survive a short gap. - */ + /** Frames an entry may sit idle before `tick()` destroys it. */ maxFreeAgeFrames: number = 60; private _engine: Engine; @@ -148,10 +138,7 @@ export class RenderTargetPool { this._freeTextureFrames.push(this._engine.time.frameCount); } - /** - * Destroy entries that have been idle in the free list for longer than `maxFreeAgeFrames`. - * Called once per engine frame. - */ + /** Destroy entries idle longer than `maxFreeAgeFrames`. Called once per engine frame. */ tick(currentFrame: number): void { const maxAge = this.maxFreeAgeFrames; const rtFrames = this._freeRenderTargetFrames; @@ -168,10 +155,7 @@ export class RenderTargetPool { } } - /** - * Destroy free-list entries whose dimensions exactly match the given size. Called when the canvas - * resizes so full-canvas RTs cached at the previous resolution don't linger waiting for `tick()`. - */ + /** Destroy entries whose dimensions match `(width, height)`. Used on canvas resize. */ evictBySize(width: number, height: number): void { const freeRenderTargets = this._freeRenderTargets; for (let i = freeRenderTargets.length - 1; i >= 0; i--) { @@ -205,9 +189,6 @@ export class RenderTargetPool { this._freeTextureFrames.length = 0; } - /** - * Swap-pop helper: remove free RT entry at `index` without destroying the RT (used when handing it out). - */ private _removeFreeRenderTargetAt(index: number): void { const rts = this._freeRenderTargets; const frames = this._freeRenderTargetFrames; @@ -220,9 +201,6 @@ export class RenderTargetPool { frames.length = last; } - /** - * Swap-pop helper for the texture free list. - */ private _removeFreeTextureAt(index: number): void { const texs = this._freeTextures; const frames = this._freeTextureFrames; @@ -235,9 +213,6 @@ export class RenderTargetPool { frames.length = last; } - /** - * Destroy free RT entry at `index` (called when evicting, not when leasing out). - */ private _destroyFreeRenderTargetAt(index: number): void { const rt = this._freeRenderTargets[index]; this._removeFreeRenderTargetAt(index); From 1d6e3a83beac4b7b449fd6a9687cba94ef5bf5cd Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Tue, 26 May 2026 23:39:49 +0800 Subject: [PATCH 117/130] style(pipeline): hoist static helpers, drop @internal-class member docs Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/RenderPipeline/RenderTargetPool.ts | 118 +++++++++--------- 1 file changed, 57 insertions(+), 61 deletions(-) diff --git a/packages/core/src/RenderPipeline/RenderTargetPool.ts b/packages/core/src/RenderPipeline/RenderTargetPool.ts index 2e918e53ea..b5d6ecb382 100644 --- a/packages/core/src/RenderPipeline/RenderTargetPool.ts +++ b/packages/core/src/RenderPipeline/RenderTargetPool.ts @@ -2,11 +2,66 @@ import { Engine } from "../Engine"; import { RenderTarget, Texture2D, TextureFilterMode, TextureFormat, TextureWrapMode } from "../texture"; /** - * Pool of `RenderTarget`s and `Texture2D`s used internally by the render pipeline. - * Entries are matched by shape on `allocate*`; bounded by frame-age (`tick`) and size-match (`evictBySize`). * @internal */ export class RenderTargetPool { + private static _destroyRenderTargetResource(rt: RenderTarget): void { + const colorTexture = rt.getColorTexture(0); + const depthTexture = rt.depthTexture; + rt.destroy(true); + colorTexture?.destroy(true); + if (depthTexture && depthTexture !== colorTexture) { + depthTexture.destroy(true); + } + } + + private static _matchRenderTarget( + renderTarget: RenderTarget, + width: number, + height: number, + colorFormat: TextureFormat | null, + depthFormat: TextureFormat | null, + needDepthTexture: boolean, + mipmap: boolean, + isSRGBColorSpace: boolean, + antiAliasing: number + ): boolean { + if (renderTarget.width !== width || renderTarget.height !== height || renderTarget.antiAliasing !== antiAliasing) { + return false; + } + + const colorTexture = renderTarget.getColorTexture(0) as Texture2D; + if (colorFormat != null) { + if ( + !colorTexture || + colorTexture.format !== colorFormat || + colorTexture.mipmapCount > 1 !== mipmap || + colorTexture.isSRGBColorSpace !== isSRGBColorSpace + ) { + return false; + } + } else if (colorTexture) { + return false; + } + + const depthTexture = renderTarget.depthTexture; + if (needDepthTexture) { + if (depthFormat) { + if (!depthTexture || (depthTexture as Texture2D).format !== depthFormat) { + return false; + } + } else if (depthTexture) { + return false; + } + } else { + if (renderTarget._depthFormat !== depthFormat) { + return false; + } + } + + return true; + } + /** Frames an entry may sit idle before `tick()` destroys it. */ maxFreeAgeFrames: number = 60; @@ -138,7 +193,6 @@ export class RenderTargetPool { this._freeTextureFrames.push(this._engine.time.frameCount); } - /** Destroy entries idle longer than `maxFreeAgeFrames`. Called once per engine frame. */ tick(currentFrame: number): void { const maxAge = this.maxFreeAgeFrames; const rtFrames = this._freeRenderTargetFrames; @@ -155,7 +209,6 @@ export class RenderTargetPool { } } - /** Destroy entries whose dimensions match `(width, height)`. Used on canvas resize. */ evictBySize(width: number, height: number): void { const freeRenderTargets = this._freeRenderTargets; for (let i = freeRenderTargets.length - 1; i >= 0; i--) { @@ -224,61 +277,4 @@ export class RenderTargetPool { this._removeFreeTextureAt(index); tex.destroy(true); } - - private static _destroyRenderTargetResource(rt: RenderTarget): void { - const colorTexture = rt.getColorTexture(0); - const depthTexture = rt.depthTexture; - rt.destroy(true); - colorTexture?.destroy(true); - if (depthTexture && depthTexture !== colorTexture) { - depthTexture.destroy(true); - } - } - - private static _matchRenderTarget( - renderTarget: RenderTarget, - width: number, - height: number, - colorFormat: TextureFormat | null, - depthFormat: TextureFormat | null, - needDepthTexture: boolean, - mipmap: boolean, - isSRGBColorSpace: boolean, - antiAliasing: number - ): boolean { - if (renderTarget.width !== width || renderTarget.height !== height || renderTarget.antiAliasing !== antiAliasing) { - return false; - } - - const colorTexture = renderTarget.getColorTexture(0) as Texture2D; - if (colorFormat != null) { - if ( - !colorTexture || - colorTexture.format !== colorFormat || - colorTexture.mipmapCount > 1 !== mipmap || - colorTexture.isSRGBColorSpace !== isSRGBColorSpace - ) { - return false; - } - } else if (colorTexture) { - return false; - } - - const depthTexture = renderTarget.depthTexture; - if (needDepthTexture) { - if (depthFormat) { - if (!depthTexture || (depthTexture as Texture2D).format !== depthFormat) { - return false; - } - } else if (depthTexture) { - return false; - } - } else { - if (renderTarget._depthFormat !== depthFormat) { - return false; - } - } - - return true; - } } From bc025b3b81b11242929db0c5d44fedbc11464cb7 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Mon, 25 May 2026 16:46:52 +0800 Subject: [PATCH 118/130] fix(pipeline): isolate per-queue batch state when a RE crosses queues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A multi-pass material whose passes target different queues (e.g. Opaque + Transparent) pushed the same `RenderElement` object into both queues. `RenderElement` carries the batch result (`instancedRenderers`, `_isBatched`), so the queue that batches first stamped those fields on the shared object and the next queue's `BatcherManager.batch` then short-circuited the polluted leader straight into its output — the opaque instance list got drawn under the transparent pass, and the remaining members were re-batched into a second leader and drawn again (producing the "1 olive + 5 greener" signature on N-cube repros). Clone the element once per additional queue: the first target keeps the original (zero overhead), subsequent ones get a pool-allocated copy with its own batch state via `_cloneFrom`. The pool is `ClearableObjectPool` so no GC pressure. Identity fields are shared by copy, batch state stays per-queue. --- .../src/RenderPipeline/BasicRenderPipeline.ts | 21 +++++++++++++------ .../core/src/RenderPipeline/RenderElement.ts | 15 +++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index 1ecda00b41..98522ef90e 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -408,6 +408,7 @@ export class BasicRenderPipeline { ): void { const shaderPasses = subShader.passes; const cullingResults = this._cullingResults; + renderElement.subShader = subShader; let pushedQueueFlags = RenderQueueFlags.None; for (let i = 0, n = shaderPasses.length; i < n; i++) { let renderQueueType: RenderQueueType; @@ -423,22 +424,30 @@ export class BasicRenderPipeline { } const flag = 1 << renderQueueType; - - renderElement.subShader = subShader; - if (pushedQueueFlags & flag) { continue; } + // First queue keeps the original element; subsequent queues each get an isolated + // clone so per-queue batch state (`_isBatched`, `instancedRenderers`) doesn't + // leak across queues for multi-pass materials (e.g. Opaque + Transparent) + let elementForQueue: RenderElement; + if (pushedQueueFlags === RenderQueueFlags.None) { + elementForQueue = renderElement; + } else { + elementForQueue = renderElement.component.engine._renderElementPool.get(); + elementForQueue._cloneFrom(renderElement); + } + switch (renderQueueType) { case RenderQueueType.Opaque: - cullingResults.opaqueQueue.pushRenderElement(renderElement); + cullingResults.opaqueQueue.pushRenderElement(elementForQueue); break; case RenderQueueType.AlphaTest: - cullingResults.alphaTestQueue.pushRenderElement(renderElement); + cullingResults.alphaTestQueue.pushRenderElement(elementForQueue); break; case RenderQueueType.Transparent: - cullingResults.transparentQueue.pushRenderElement(renderElement); + cullingResults.transparentQueue.pushRenderElement(elementForQueue); break; } pushedQueueFlags |= flag; diff --git a/packages/core/src/RenderPipeline/RenderElement.ts b/packages/core/src/RenderPipeline/RenderElement.ts index 89a67b8fd9..64a25c3500 100644 --- a/packages/core/src/RenderPipeline/RenderElement.ts +++ b/packages/core/src/RenderPipeline/RenderElement.ts @@ -38,6 +38,21 @@ export class RenderElement implements IPoolElement { this.instancedRenderers.length = 0; } + /** + * @internal + * Copy identity fields from `source` and reset per-queue batch state, so the same + * logical element can live in multiple queues (e.g. an Opaque + Transparent multi-pass + * material) without one queue's batching corrupting another's `instancedRenderers`. + */ + _cloneFrom(source: RenderElement): void { + this.set(source.component, source.material, source.primitive, source.subPrimitive, source.texture, source.subChunk); + this.priority = source.priority; + this.distanceForSort = source.distanceForSort; + this.subShader = source.subShader; + this.shaderData = source.shaderData; + this.subDistancePriority = source.subDistancePriority; + } + dispose(): void { this.component = null; this.material = null; From 98738db3328e22b7188ac7c6f42f24a5a67458da Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 28 May 2026 20:08:25 +0800 Subject: [PATCH 119/130] perf(transform): cache _isFrontFaceInvert via dirty bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_isFrontFaceInvert` previously read `lossyWorldScale`, which lazily triggers `_getScaleMatrix`' polar decomposition — expensive for a function whose answer is just `sign(det(worldMatrix)) < 0`. That sign equals the XOR of every ancestor's local-scale sign bits, so compute it recursively from `_scale` directly and cache the boolean. Invalidate via a new `WorldFrontFaceInvert` bit added to every dirty-flag combo that already carries `WorldScale` (so existing dispatch paths cover it; no new dispatch sites). The bit sits at `0x800` to avoid colliding with `UITransformModifyFlags.Size`/`Pivot` at `0x200`/`0x400`. `_canBatch` and `RenderQueue` call this per element per frame, so hit-rate is effectively 100% in non-mirror scenes. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/core/src/Transform.ts | 48 ++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/core/src/Transform.ts b/packages/core/src/Transform.ts index 2c9821426c..f408814c93 100644 --- a/packages/core/src/Transform.ts +++ b/packages/core/src/Transform.ts @@ -48,6 +48,8 @@ export class Transform extends Component { private _worldRight: Vector3 = null; @ignoreClone private _worldUp: Vector3 = null; + @ignoreClone + private _frontFaceInvert: boolean = false; @ignoreClone protected _isParentDirty: boolean = true; @@ -589,11 +591,16 @@ export class Transform extends Component { * @internal */ _isFrontFaceInvert(): boolean { - const scale = this.lossyWorldScale; - let isInvert = scale.x < 0; - scale.y < 0 && (isInvert = !isInvert); - scale.z < 0 && (isInvert = !isInvert); - return isInvert; + if (this._isContainDirtyFlag(TransformModifyFlags.WorldFrontFaceInvert)) { + const s = this._scale; + let invert = s.x < 0; + s.y < 0 && (invert = !invert); + s.z < 0 && (invert = !invert); + const parent = this._getParentTransform(); + this._frontFaceInvert = parent ? invert !== parent._isFrontFaceInvert() : invert; + this._setDirtyFlagFalse(TransformModifyFlags.WorldFrontFaceInvert); + } + return this._frontFaceInvert; } /** @@ -933,24 +940,27 @@ export enum TransformModifyFlags { */ IsWorldUniformScaling = 0x100, + // Note: 0x200 (UITransformModifyFlags.Size) and 0x400 (Pivot) are reserved by UITransform — do not reuse. + WorldFrontFaceInvert = 0x800, + /** WorldMatrix | WorldPosition */ WmWp = 0x84, /** WorldMatrix | WorldEuler | WorldQuat */ WmWeWq = 0x98, - /** WorldMatrix | WorldEuler | WorldQuat | WorldScale*/ - WmWeWqWs = 0xb8, + /** WorldMatrix | WorldEuler | WorldQuat | WorldScale | WorldFrontFaceInvert */ + WmWeWqWs = 0x8b8, /** WorldMatrix | WorldPosition | WorldEuler | WorldQuat */ WmWpWeWq = 0x9c, - /** WorldMatrix | WorldScale */ - WmWs = 0xa0, - /** WorldMatrix | WorldScale | WorldUniformScaling */ - WmWsWus = 0x1a0, - /** WorldMatrix | WorldPosition | WorldScale */ - WmWpWs = 0xa4, - /** WorldMatrix | WorldPosition | WorldEuler | WorldQuat | WorldScale */ - WmWpWeWqWs = 0xbc, - /** WorldMatrix | WorldPosition | WorldEuler | WorldQuat | WorldScale | WorldUniformScaling */ - WmWpWeWqWsWus = 0x1bc, - /** LocalQuat | LocalMatrix | WorldMatrix | WorldPosition | WorldEuler | WorldQuat | WorldScale | WorldUniformScaling */ - LqLmWmWpWeWqWsWus = 0x1fe + /** WorldMatrix | WorldScale | WorldFrontFaceInvert */ + WmWs = 0x8a0, + /** WorldMatrix | WorldScale | WorldUniformScaling | WorldFrontFaceInvert */ + WmWsWus = 0x9a0, + /** WorldMatrix | WorldPosition | WorldScale | WorldFrontFaceInvert */ + WmWpWs = 0x8a4, + /** WorldMatrix | WorldPosition | WorldEuler | WorldQuat | WorldScale | WorldFrontFaceInvert */ + WmWpWeWqWs = 0x8bc, + /** WorldMatrix | WorldPosition | WorldEuler | WorldQuat | WorldScale | WorldUniformScaling | WorldFrontFaceInvert */ + WmWpWeWqWsWus = 0x9bc, + /** LocalQuat | LocalMatrix | WorldMatrix | WorldPosition | WorldEuler | WorldQuat | WorldScale | WorldUniformScaling | WorldFrontFaceInvert */ + LqLmWmWpWeWqWsWus = 0x9fe } From 34d6ff242cbbdff8c00ad9d8fc4cbcfacc84761d Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 28 May 2026 20:09:10 +0800 Subject: [PATCH 120/130] fix(animation): switch instanceId records to weakmaps and guard _reset cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related leaks the previous `Record` layout caused: 1. `Animator._curveOwnerPool` and `AnimatorLayerData.curveOwnerPool` were keyed by `Component.instanceId`. Destroying the bound component left the entry pinned for the Animator's lifetime, so the curve owners (and the entity-tree defaults they captured) couldn't be collected. `AnimationClipCurveBinding._tempCurveOwner` had the same shape keyed by `Entity.instanceId`. Re-key all three by the live object via `WeakMap` so dead keys release the entry. 2. `Animator._reset` reverted default values for every owner *before* removing each state's `clipChangedListener`. If any owner threw on a dead target, the listener-remove block was skipped, leaving the polyfill listener on a shared `AnimatorState`; the listener closure captures `this` (the Animator), which pins the whole entity tree until the AnimatorState itself dies. Move listener cleanup into the inner loop, wrap each `revertDefaultValue` in its own `try/catch`, so listener cleanup always runs. (Root-cause fix would be a destroyed-target guard inside `AnimationCurveOwner.revertDefaultValue` itself — left as a follow-up.) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../animation/AnimationClipCurveBinding.ts | 11 ++--- packages/core/src/animation/Animator.ts | 45 ++++++++++--------- .../animation/internal/AnimatorLayerData.ts | 3 +- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/core/src/animation/AnimationClipCurveBinding.ts b/packages/core/src/animation/AnimationClipCurveBinding.ts index 906c154087..e6e4066181 100644 --- a/packages/core/src/animation/AnimationClipCurveBinding.ts +++ b/packages/core/src/animation/AnimationClipCurveBinding.ts @@ -33,7 +33,7 @@ export class AnimationClipCurveBinding { /** The animation curve. */ curve: AnimationCurve; - private _tempCurveOwner: Record> = {}; + private _tempCurveOwner = new WeakMap>(); /** * @internal @@ -63,10 +63,11 @@ export class AnimationClipCurveBinding { * @internal */ _getTempCurveOwner(entity: Entity, component: Component): AnimationCurveOwner { - const { instanceId } = entity; - if (!this._tempCurveOwner[instanceId]) { - this._tempCurveOwner[instanceId] = this._createCurveOwner(entity, component); + let owner = this._tempCurveOwner.get(entity); + if (!owner) { + owner = this._createCurveOwner(entity, component); + this._tempCurveOwner.set(entity, owner); } - return this._tempCurveOwner[instanceId]; + return owner; } } diff --git a/packages/core/src/animation/Animator.ts b/packages/core/src/animation/Animator.ts index ee4e299fbb..0bb5fea0c0 100644 --- a/packages/core/src/animation/Animator.ts +++ b/packages/core/src/animation/Animator.ts @@ -57,7 +57,7 @@ export class Animator extends Component { @ignoreClone private _animatorLayersData = new Array(); @ignoreClone - private _curveOwnerPool: Record>> = Object.create(null); + private _curveOwnerPool: WeakMap>> = new WeakMap(); @ignoreClone private _animationEventHandlerPool = new ClearableObjectPool(AnimationEventHandler); @ignoreClone @@ -334,20 +334,22 @@ export class Animator extends Component { * @internal */ _reset(): void { - const { _curveOwnerPool: animationCurveOwners } = this; - for (let instanceId in animationCurveOwners) { - const propertyOwners = animationCurveOwners[instanceId]; - for (let property in propertyOwners) { - const owner = propertyOwners[property]; - owner.revertDefaultValue(); - } - } - + // revertDefaultValue 可能在死 target 上抛;listener 清理必须始终发生,否则 + // polyfill 的 clipChangedListener 会留在共享的 AnimatorState 上,捕获 this(Animator)→Entity 整树。 + // 用 try/catch 隔离 revert,listener 块紧跟其后无条件执行。 const layersData = this._animatorLayersData; for (let i = 0, n = layersData.length; i < n; i++) { const stateDataMap = layersData[i].animatorStateDataMap; for (const stateName in stateDataMap) { const stateData = stateDataMap[stateName]; + const layerOwners = stateData.curveLayerOwner; + for (let j = 0, m = layerOwners.length; j < m; j++) { + try { + layerOwners[j]?.curveOwner?.revertDefaultValue(); + } catch (e) { + Logger.warn("Animator._reset: revertDefaultValue threw", e); + } + } if (stateData.clipChangedListener && stateData.state) { stateData.state._updateFlagManager.removeListener(stateData.clipChangedListener); stateData.clipChangedListener = null; @@ -357,7 +359,7 @@ export class Animator extends Component { } this._animatorLayersData.length = 0; - this._curveOwnerPool = Object.create(null); + this._curveOwnerPool = new WeakMap(); this._parametersValueMap = Object.create(null); this._animationEventHandlerPool.clear(); @@ -478,17 +480,20 @@ export class Animator extends Component { } const { property } = curve; - const { instanceId } = component; - // Get owner - const propertyOwners = (curveOwnerPool[instanceId] ||= >>( - Object.create(null) - )); + // Get owner — WeakMap keyed by Component so dead components let entries GC. + let propertyOwners = curveOwnerPool.get(component); + if (!propertyOwners) { + propertyOwners = >>Object.create(null); + curveOwnerPool.set(component, propertyOwners); + } const owner = (propertyOwners[property] ||= curve._createCurveOwner(targetEntity, component)); - // Get layer owner - const layerPropertyOwners = (layerCurveOwnerPool[instanceId] ||= >( - Object.create(null) - )); + // Get layer owner — same WeakMap-by-Component pattern (was Record which kept dead components pinned for the Animator's lifetime). + let layerPropertyOwners = layerCurveOwnerPool.get(component); + if (!layerPropertyOwners) { + layerPropertyOwners = >Object.create(null); + layerCurveOwnerPool.set(component, layerPropertyOwners); + } const layerOwner = (layerPropertyOwners[property] ||= curve._createCurveLayerOwner(owner)); if (mask && mask.pathMasks.length) { diff --git a/packages/core/src/animation/internal/AnimatorLayerData.ts b/packages/core/src/animation/internal/AnimatorLayerData.ts index a7bd04f1a5..959aadf208 100644 --- a/packages/core/src/animation/internal/AnimatorLayerData.ts +++ b/packages/core/src/animation/internal/AnimatorLayerData.ts @@ -1,3 +1,4 @@ +import { Component } from "../../Component"; import { AnimatorControllerLayer } from "../AnimatorControllerLayer"; import { AnimatorStateTransition } from "../AnimatorStateTransition"; import { LayerState } from "../enums/LayerState"; @@ -11,7 +12,7 @@ import { AnimatorStatePlayData } from "./AnimatorStatePlayData"; export class AnimatorLayerData { layerIndex: number; layer: AnimatorControllerLayer; - curveOwnerPool: Record> = Object.create(null); + curveOwnerPool: WeakMap> = new WeakMap(); animatorStateDataMap: Record = {}; srcPlayData: AnimatorStatePlayData = new AnimatorStatePlayData(); destPlayData: AnimatorStatePlayData = new AnimatorStatePlayData(); From 6c242db489c50512c9e162653c86ad3c8de551ef Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 28 May 2026 20:09:32 +0800 Subject: [PATCH 121/130] fix(instancing): refuse instanced batches when renderer-group samplers/arrays differ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GLSL forbids samplers and per-instance arrays inside UBOs, so they live in the renderer-group block as plain uniforms bound from the leader. The previous `MeshRenderer._canBatch` only checked macros, so instances disagreeing on which sampler/array was bound were silently batched and the leader's binding was applied to every follower — the wrong texture ended up on every other instance. `ShaderData` now tracks renderer-group sampler/array property ids in a sorted `_instanceBatchFields` (binary insert / linear remove on set/unset) and exposes `_matchesRendererInstanceBatch` for O(K) compare. `MeshRenderer._canBatch` consults it. `RenderQueue`'s instanced path now also re-uploads the renderer block on every draw (leader changes between batches), and clears `_uploadRendererId` so the cache can't shortcut past the new leader. `RenderElement._cloneFrom` drops `subDistancePriority` from the copied fields (the cloned element re-enters sort/scheduling via its own path). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../core/src/RenderPipeline/RenderElement.ts | 1 - .../core/src/RenderPipeline/RenderQueue.ts | 28 ++++---- packages/core/src/mesh/MeshRenderer.ts | 5 +- packages/core/src/shader/ShaderData.ts | 64 ++++++++++++++++++- 4 files changed, 81 insertions(+), 17 deletions(-) diff --git a/packages/core/src/RenderPipeline/RenderElement.ts b/packages/core/src/RenderPipeline/RenderElement.ts index 64a25c3500..50cbf8ffe9 100644 --- a/packages/core/src/RenderPipeline/RenderElement.ts +++ b/packages/core/src/RenderPipeline/RenderElement.ts @@ -50,7 +50,6 @@ export class RenderElement implements IPoolElement { this.distanceForSort = source.distanceForSort; this.subShader = source.subShader; this.shaderData = source.shaderData; - this.subDistancePriority = source.subDistancePriority; } dispose(): void { diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index f9ee9fb0a4..5f4a4d89af 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -148,17 +148,15 @@ export class RenderQueue { const switchProgram = program.bind(); const switchRenderCount = renderCount !== program._uploadRenderCount; - // Upload uniforms (cache-aware per block) + // Upload uniforms (cache-aware per block). Renderer block carries plain samplers/arrays + // even on the instanced path (GLSL forbids them in UBOs); `_canBatch` ensures the whole + // batch agrees on those, so the leader's values are correct for the draw call if (switchRenderCount) { program.groupingOtherUniformBlock(); program.uploadAll(program.sceneUniformBlock, sceneData); program.uploadAll(program.cameraUniformBlock, cameraData); - if (isInstanced) { - program._uploadRendererId = -1; - } else { - program.uploadAll(program.rendererUniformBlock, rendererData); - program._uploadRendererId = rendererId; - } + program.uploadAll(program.rendererUniformBlock, rendererData); + program._uploadRendererId = isInstanced ? -1 : rendererId; program.uploadAll(program.materialUniformBlock, materialData); renderElementShaderData && program.uploadAll(program.renderElementUniformBlock, renderElementShaderData); // UnGroup textures should upload default value, texture uint maybe change by logic of texture bind @@ -182,13 +180,15 @@ export class RenderQueue { program.uploadTextures(program.cameraUniformBlock, cameraData); } - if (!isInstanced) { - if (program._uploadRendererId !== rendererId) { - program.uploadAll(program.rendererUniformBlock, rendererData); - program._uploadRendererId = rendererId; - } else if (switchProgram) { - program.uploadTextures(program.rendererUniformBlock, rendererData); - } + if (isInstanced) { + // Different batches may have different leaders, re-upload every time + program.uploadAll(program.rendererUniformBlock, rendererData); + program._uploadRendererId = -1; + } else if (program._uploadRendererId !== rendererId) { + program.uploadAll(program.rendererUniformBlock, rendererData); + program._uploadRendererId = rendererId; + } else if (switchProgram) { + program.uploadTextures(program.rendererUniformBlock, rendererData); } if (program._uploadMaterialId !== materialId) { diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index 7eaf45be7b..3cac98586a 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -178,12 +178,15 @@ export class MeshRenderer extends Renderer { */ override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { if (!this._engine._hardwareRenderer.isWebGL2) return false; + const curShaderData = curElement.component.shaderData; return ( preElement.primitive === curElement.primitive && preElement.subPrimitive === curElement.subPrimitive && preElement.material === curElement.material && this._isFrontFaceInvert() === (curElement.component)._isFrontFaceInvert() && - this.shaderData._macroCollection.isEqual(curElement.component.shaderData._macroCollection) + this.shaderData._macroCollection.isEqual(curShaderData._macroCollection) && + // Renderer-group samplers/arrays are shared across the whole instanced draw call + this.shaderData._matchesRendererInstanceBatch(curShaderData) ); } diff --git a/packages/core/src/shader/ShaderData.ts b/packages/core/src/shader/ShaderData.ts index f2a8a7ccd5..a25f85c7ca 100644 --- a/packages/core/src/shader/ShaderData.ts +++ b/packages/core/src/shader/ShaderData.ts @@ -22,6 +22,14 @@ export class ShaderData implements IReferable, IClone { /** @internal */ @ignoreClone _macroCollection: ShaderMacroCollection = new ShaderMacroCollection(); + /** + * @internal + * Sorted (ascending) property ids of renderer-group samplers and arrays. These values + * can't differ across instances inside an instanced draw call — `_canBatch` uses this + * list to refuse batching renderers that would disagree on what to bind + */ + @ignoreClone + _instanceBatchFields: number[] = []; @ignoreClone private _macroMap: Record = Object.create(null); @@ -669,7 +677,61 @@ export class ShaderData implements IReferable, IClone { } } - this._propertyValueMap[property._uniqueId] = value; + const id = property._uniqueId; + // Track renderer-group samplers/arrays so `_canBatch` can refuse instanced batches + // whose leader and follower would disagree on what to bind + if ( + this._group === ShaderDataGroup.Renderer && + (type === ShaderPropertyType.Texture || + type === ShaderPropertyType.TextureArray || + type === ShaderPropertyType.FloatArray || + type === ShaderPropertyType.IntArray) + ) { + const oldHas = this._propertyValueMap[id] != null; + const newHas = value != null; + if (oldHas !== newHas) { + const fields = this._instanceBatchFields; + if (newHas) { + // Insert keeping ascending order so exact compare is index-by-index + let lo = 0; + let hi = fields.length; + while (lo < hi) { + const mid = (lo + hi) >>> 1; + if (fields[mid] < id) { + lo = mid + 1; + } else { + hi = mid; + } + } + fields.splice(lo, 0, id); + } else { + fields.splice(fields.indexOf(id), 1); + } + } + } + + this._propertyValueMap[id] = value; + } + + /** + * @internal + */ + _matchesRendererInstanceBatch(other: ShaderData): boolean { + const selfFields = this._instanceBatchFields; + const otherFields = other._instanceBatchFields; + const fieldCount = selfFields.length; + if (fieldCount !== otherFields.length) { + return false; + } + const selfMap = this._propertyValueMap; + const otherMap = other._propertyValueMap; + for (let i = 0; i < fieldCount; i++) { + const id = selfFields[i]; + if (id !== otherFields[i] || selfMap[id] !== otherMap[id]) { + return false; + } + } + return true; } /** From 8c5a070037977b532cf17fa65950ab4e2555a8ce Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 28 May 2026 20:24:58 +0800 Subject: [PATCH 122/130] chore: release v0.0.0-experimental-2.0-game.16 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 9bfbc4ed01..6f84d82b9b 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index c448171671..ba58befdd4 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 326f1b930f..45a6e8eb55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 6466fa89bb..18ef59cc98 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 6999242358..0d578e26a1 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index d1cf993717..aebf756107 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index ed601f5c66..4d81f6b6ae 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 4626879fc4..6dac7b7d51 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index e26c861ad9..ecf6007d8b 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index cae2a4f85d..02b06f6f7d 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 2e27502fcb..36c592d045 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 92d6602d35..605fccf8c0 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index 24302397bd..c8f4424173 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index 8d015aa938..fcb87a15a6 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 4cc93f7496..a5176b34c5 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 192168f865..6f550d10b4 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index 1cbca7fa97..bb61b76aa9 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.15", + "version": "0.0.0-experimental-2.0-game.16", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From a9a92f1bd227ed92c516e629bd963cff0d1a9eff Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 11 Jun 2026 23:22:37 +0800 Subject: [PATCH 123/130] fix(shader-lab): release LALR construction scaffold and add releaseCompilationCache API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the parse table is built, the State/StateItem closure graph (pinned by the static State.closureMap/State.pool) is never read at runtime — action/goto tables are self-contained numeric-id maps. Clear it at the end of LALR1.generate(). Production.pool is kept: reduce actions resolve productions by id while parsing. Also add ShaderLab.releaseCompilationCache() / ShaderLabUtils.releaseAllShaderLabObjectPool() to truly release token/AST object pools (clearAllShaderLabObjectPool only resets the used counter), for applications to call once compilation has converged (e.g. after warm-up). Real-project measurements (iOS/JSC): Set instances 12,858 -> 589, Set/Map backing (Cell Butterfly) 38.6MB -> 8.0MB, live heap -30.6MB; shader variants still compile correctly afterwards. Ref: https://github.com/galacean/engine/issues/3028 Co-Authored-By: Claude Fable 5 --- packages/shader-lab/src/ShaderLab.ts | 14 ++++++++++++++ packages/shader-lab/src/ShaderLabUtils.ts | 13 +++++++++++++ packages/shader-lab/src/lalr/LALR1.ts | 5 +++++ packages/shader-lab/src/lalr/State.ts | 14 ++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/packages/shader-lab/src/ShaderLab.ts b/packages/shader-lab/src/ShaderLab.ts index 16dab2619e..7a608c640c 100644 --- a/packages/shader-lab/src/ShaderLab.ts +++ b/packages/shader-lab/src/ShaderLab.ts @@ -37,6 +37,20 @@ export class ShaderLab implements IShaderLab { return range; } + /** + * Release compilation caches: the token/AST object pools, which otherwise stay + * at their compilation-time peak for the whole engine lifetime (~9MB plus GC + * mark cost in real projects; the LALR table-construction scaffold is already + * released automatically once the parse table is built). + * + * Call when no further shader compilation is expected soon — typically after + * shader warm-up. Compiling again afterwards is still fully supported: pools + * simply re-allocate on demand. + */ + static releaseCompilationCache(): void { + ShaderLabUtils.releaseAllShaderLabObjectPool(); + } + _parseShaderSource(sourceCode: string): IShaderSource { ShaderLabUtils.clearAllShaderLabObjectPool(); const shaderSource = ShaderSourceParser.parse(sourceCode); diff --git a/packages/shader-lab/src/ShaderLabUtils.ts b/packages/shader-lab/src/ShaderLabUtils.ts index 230e190494..5a9d39094d 100644 --- a/packages/shader-lab/src/ShaderLabUtils.ts +++ b/packages/shader-lab/src/ShaderLabUtils.ts @@ -21,6 +21,19 @@ export class ShaderLabUtils { } } + /** + * Truly release all pooled objects, in contrast to `clearAllShaderLabObjectPool` + * which only resets the used counter and keeps every element referenced (pool + * capacity stays at the compilation-time peak — ~150k token/AST objects in real + * projects). Call when compilation has converged (e.g. after shader warm-up); + * pools transparently re-allocate on demand if compilation happens again. + */ + static releaseAllShaderLabObjectPool() { + for (let i = 0, n = ShaderLabUtils._shaderLabObjectPoolSet.length; i < n; i++) { + ShaderLabUtils._shaderLabObjectPoolSet[i].garbageCollection(); + } + } + static createGSError( message: string, errorName: GSErrorName, diff --git a/packages/shader-lab/src/lalr/LALR1.ts b/packages/shader-lab/src/lalr/LALR1.ts index a333c67ca9..cda6dd2cb6 100644 --- a/packages/shader-lab/src/lalr/LALR1.ts +++ b/packages/shader-lab/src/lalr/LALR1.ts @@ -29,6 +29,11 @@ export class LALR1 { generate() { this.computeFirstSet(); this.buildStateTable(); + // The action/goto tables are self-contained (numeric state/production ids), + // so the State/StateItem closure graph used to build them is never read at + // runtime. Release it here. `Production.pool` must stay alive: reduce + // actions resolve productions by id while parsing. + State.clearPool(); } private buildStateTable() { diff --git a/packages/shader-lab/src/lalr/State.ts b/packages/shader-lab/src/lalr/State.ts index 6ef782249e..63796ad4fc 100644 --- a/packages/shader-lab/src/lalr/State.ts +++ b/packages/shader-lab/src/lalr/State.ts @@ -7,6 +7,20 @@ export default class State { static pool: Map = new Map(); static _id = 0; + /** + * Release the table-construction scaffold (the whole State/StateItem closure graph). + * Runtime parsing only reads the action/goto tables, whose entries are numeric + * state/production ids, so this graph is garbage once `LALR1.generate` finishes — + * yet it pins 10k+ objects (each `StateItem.lookaheadSet` costs ~2KB of hash + * buckets on JavaScriptCore, ~30MB total in real projects). + * Note: `printStatePool` (_VERBOSE debug helper) dumps nothing after this runs; + * break before `LALR1.generate` returns if you need the state graph. + */ + static clearPool() { + this.closureMap.clear(); + this.pool.clear(); + } + readonly id: number; readonly cores: Set; private _items: Set; From 455c7cd380f6e8e93b8bc06d223ece645c4932f8 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Thu, 11 Jun 2026 23:31:19 +0800 Subject: [PATCH 124/130] chore: release v0.0.0-experimental-2.0-game.17 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 6f84d82b9b..3084e6e56a 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index ba58befdd4..4df065b8ba 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 45a6e8eb55..3383e122bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 18ef59cc98..8f12267856 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 0d578e26a1..a7b0455c4a 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index aebf756107..39b9b028d4 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index 4d81f6b6ae..165e4d846f 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 6dac7b7d51..a63fcf190c 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index ecf6007d8b..018c630269 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 02b06f6f7d..6b312e3651 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 36c592d045..103e89df5c 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 605fccf8c0..9485cd5e40 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index c8f4424173..2686d81cd1 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index fcb87a15a6..ad3f37872d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index a5176b34c5..6ae03142e4 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 6f550d10b4..37e37e62a1 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index bb61b76aa9..040c3c34b7 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.16", + "version": "0.0.0-experimental-2.0-game.17", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From b57440b7f0c43077827897db9013514dcba8699c Mon Sep 17 00:00:00 2001 From: luzhuang Date: Mon, 1 Jun 2026 17:14:20 +0800 Subject: [PATCH 125/130] fix: optimize physics contact event demand --- packages/core/src/physics/PhysicsScene.ts | 23 +++++ packages/design/src/physics/IPhysicsScene.ts | 6 ++ packages/physics-lite/src/LitePhysicsScene.ts | 7 ++ .../physics-physx/src/PhysXPhysicsScene.ts | 17 ++++ tests/src/core/physics/PhysicsScene.test.ts | 92 +++++++++++++++++++ 5 files changed, 145 insertions(+) diff --git a/packages/core/src/physics/PhysicsScene.ts b/packages/core/src/physics/PhysicsScene.ts index ce6bf2e4e8..5fccc08246 100644 --- a/packages/core/src/physics/PhysicsScene.ts +++ b/packages/core/src/physics/PhysicsScene.ts @@ -646,6 +646,7 @@ export class PhysicsScene { for (let i = 0; i < step; i++) { componentsManager.callScriptOnPhysicsUpdate(); this._callColliderOnUpdate(); + nativePhysicsManager.setContactEventEnabled(this._hasCollisionEventConsumers()); nativePhysicsManager.update(fixedTimeStep); this._callColliderOnLateUpdate(); this._dispatchEvents(nativePhysicsManager.updateEvents()); @@ -825,6 +826,28 @@ export class PhysicsScene { } } + private _hasCollisionEventConsumers(): boolean { + const { _elements: colliders } = this._colliders; + const { onCollisionEnter, onCollisionExit, onCollisionStay } = Script.prototype; + + for (let i = this._colliders.length - 1; i >= 0; --i) { + const scripts = colliders[i].entity._scripts; + const scriptElements = scripts._elements; + for (let j = scripts.length - 1; j >= 0; --j) { + const script = scriptElements[j]; + if ( + script.onCollisionEnter !== onCollisionEnter || + script.onCollisionExit !== onCollisionExit || + script.onCollisionStay !== onCollisionStay + ) { + return true; + } + } + } + + return false; + } + private _setGravity(): void { this._nativePhysicsScene.setGravity(this._gravity); } diff --git a/packages/design/src/physics/IPhysicsScene.ts b/packages/design/src/physics/IPhysicsScene.ts index 5520114104..e5b8d9f335 100644 --- a/packages/design/src/physics/IPhysicsScene.ts +++ b/packages/design/src/physics/IPhysicsScene.ts @@ -43,6 +43,12 @@ export interface IPhysicsScene { */ update(elapsedTime: number): void; + /** + * Enable contact event buffering. + * @param enabled - Whether collision contact events should be buffered for dispatch. + */ + setContactEventEnabled(enabled: boolean): void; + /** * Collect buffered collision and trigger events. * Must be called after update() and after syncing transforms back from physics. diff --git a/packages/physics-lite/src/LitePhysicsScene.ts b/packages/physics-lite/src/LitePhysicsScene.ts index 0b6b6eb3e8..58742b5069 100644 --- a/packages/physics-lite/src/LitePhysicsScene.ts +++ b/packages/physics-lite/src/LitePhysicsScene.ts @@ -115,6 +115,13 @@ export class LitePhysicsScene implements IPhysicsScene { } } + /** + * {@inheritDoc IPhysicsScene.setContactEventEnabled } + */ + setContactEventEnabled(_enabled: boolean): void { + // Physics-lite only produces trigger events, so there is no contact buffer to toggle. + } + /** * {@inheritDoc IPhysicsScene.updateEvents } */ diff --git a/packages/physics-physx/src/PhysXPhysicsScene.ts b/packages/physics-physx/src/PhysXPhysicsScene.ts index d026304e4d..bee2ab2dc2 100644 --- a/packages/physics-physx/src/PhysXPhysicsScene.ts +++ b/packages/physics-physx/src/PhysXPhysicsScene.ts @@ -50,6 +50,7 @@ export class PhysXPhysicsScene implements IPhysicsScene { private _activeTriggers: DisorderedArray = new DisorderedArray(); private _contactEvents: ContactEvent[] = []; private _contactEventCount = 0; + private _contactEventEnabled = true; private _triggerEvents: TriggerEvent[] = []; private _physicsEvents: IPhysicsEvents = { contactEvents: [], contactEventCount: 0, triggerEvents: [] }; @@ -191,6 +192,20 @@ export class PhysXPhysicsScene implements IPhysicsScene { this._fetchResults(); } + /** + * {@inheritDoc IPhysicsScene.setContactEventEnabled } + */ + setContactEventEnabled(enabled: boolean): void { + if (this._contactEventEnabled === enabled) { + return; + } + + this._contactEventEnabled = enabled; + if (!enabled) { + this._contactEventCount = 0; + } + } + /** * {@inheritDoc IPhysicsScene.updateEvents } */ @@ -547,6 +562,8 @@ export class PhysXPhysicsScene implements IPhysicsScene { } private _bufferContactEvent(collision: ICollision, state: number): void { + if (!this._contactEventEnabled) return; + const index = this._contactEventCount++; const event = (this._contactEvents[index] ||= new ContactEvent()); event.shape0Id = collision.shape0Id; diff --git a/tests/src/core/physics/PhysicsScene.test.ts b/tests/src/core/physics/PhysicsScene.test.ts index a62067db59..9f258a1e2d 100644 --- a/tests/src/core/physics/PhysicsScene.test.ts +++ b/tests/src/core/physics/PhysicsScene.test.ts @@ -73,12 +73,44 @@ class CollisionTestScript extends Script { } } +class CollisionDemandScript extends Script { + onCollisionEnter(): void {} +} + +class TriggerDemandScript extends Script { + onTriggerEnter(): void {} +} + function updatePhysics(physics) { for (let i = 0; i < 5; ++i) { physics._update(8); } } +function watchNativeContactEventDemand(physicsScene: PhysicsScene) { + const nativeScene = (physicsScene as any)._nativePhysicsScene; + const original = nativeScene.setContactEventEnabled; + const calls: boolean[] = []; + nativeScene.setContactEventEnabled = (enabled: boolean) => { + calls.push(enabled); + original?.call(nativeScene, enabled); + }; + return { + calls, + restore() { + if (original) { + nativeScene.setContactEventEnabled = original; + } else { + delete nativeScene.setContactEventEnabled; + } + } + }; +} + +function getLastContactEventDemandCall(calls: boolean[]): boolean { + return calls[calls.length - 1]; +} + function resetSpy() { // reset spy on collision test script. CollisionTestScript.prototype.onCollisionEnter = vi.fn(CollisionTestScript.prototype.onCollisionEnter); @@ -421,6 +453,66 @@ describe("Physics Test", () => { expect(enginePhysX.sceneManager.scenes[0].physics.fixedTimeStep).to.eq(fixedTimeStep); }); + it("auto-disables native contact events when no active collision callback exists", () => { + const scene = enginePhysX.sceneManager.activeScene; + const physicsScene = scene.physics; + const root = scene.createRootEntity("contact-demand-disabled"); + const entity = root.createChild("body"); + const collider = entity.addComponent(StaticCollider); + collider.addShape(new BoxColliderShape()); + const contactEventDemand = watchNativeContactEventDemand(physicsScene); + + try { + physicsScene._update(physicsScene.fixedTimeStep); + expect(getLastContactEventDemandCall(contactEventDemand.calls)).to.eq(false); + } finally { + contactEventDemand.restore(); + root.destroy(); + } + }); + + it("auto-enables native contact events only while an active collision callback exists", () => { + const scene = enginePhysX.sceneManager.activeScene; + const physicsScene = scene.physics; + const root = scene.createRootEntity("contact-demand-enabled"); + const entity = root.createChild("body"); + const collider = entity.addComponent(StaticCollider); + collider.addShape(new BoxColliderShape()); + const script = entity.addComponent(CollisionDemandScript); + const contactEventDemand = watchNativeContactEventDemand(physicsScene); + + try { + physicsScene._update(physicsScene.fixedTimeStep); + expect(getLastContactEventDemandCall(contactEventDemand.calls)).to.eq(true); + + script.enabled = false; + physicsScene._update(physicsScene.fixedTimeStep); + expect(getLastContactEventDemandCall(contactEventDemand.calls)).to.eq(false); + } finally { + contactEventDemand.restore(); + root.destroy(); + } + }); + + it("keeps native contact events disabled when only trigger callbacks exist", () => { + const scene = enginePhysX.sceneManager.activeScene; + const physicsScene = scene.physics; + const root = scene.createRootEntity("contact-demand-trigger-only"); + const entity = root.createChild("body"); + const collider = entity.addComponent(StaticCollider); + collider.addShape(new BoxColliderShape()); + entity.addComponent(TriggerDemandScript); + const contactEventDemand = watchNativeContactEventDemand(physicsScene); + + try { + physicsScene._update(physicsScene.fixedTimeStep); + expect(getLastContactEventDemandCall(contactEventDemand.calls)).to.eq(false); + } finally { + contactEventDemand.restore(); + root.destroy(); + } + }); + it("raycast", () => { const scene = enginePhysX.sceneManager.activeScene; const physicsScene = scene.physics; From fd5bd08da3d9183b66f7128c17776bf079228caf Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Mon, 29 Jun 2026 21:54:39 +0800 Subject: [PATCH 126/130] feat(text): add SHRINK overflow mode for Text/TextRenderer - TextOverflow: add Shrink=2 enum - TextUtils.measureTextWithShrink: binary-search the largest font size that fits the bounds; measure-only during the search (skip atlas upload for the intermediate font sizes), upload only the final fitted size - Text/TextRenderer: route SHRINK overflow through measureTextWithShrink - tests: measureTextWithShrink (wrap/no-wrap/multi-line) + _getCharInfo upload flag Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/core/src/2d/enums/TextOverflow.ts | 6 +- packages/core/src/2d/text/TextRenderer.ts | 64 +++++++++----- packages/core/src/2d/text/TextUtils.ts | 99 ++++++++++++++++++++-- packages/ui/src/component/advanced/Text.ts | 68 +++++++++++---- tests/src/core/2d/text/TextUtils.test.ts | 86 ++++++++++++++++++- 5 files changed, 276 insertions(+), 47 deletions(-) diff --git a/packages/core/src/2d/enums/TextOverflow.ts b/packages/core/src/2d/enums/TextOverflow.ts index adef863f1f..bf8fdf8aa4 100644 --- a/packages/core/src/2d/enums/TextOverflow.ts +++ b/packages/core/src/2d/enums/TextOverflow.ts @@ -1,9 +1,11 @@ /** - * The way to handle the situation where wrapped text is too tall to fit in the height. + * The way to handle the situation where the text is too large to fit in the bounds. */ export enum OverflowMode { /** Overflow when the text is too tall */ Overflow = 0, /** Truncate with height when the text is too tall */ - Truncate = 1 + Truncate = 1, + /** Shrink the font size until the text fits within the bounds (both width and height) */ + Shrink = 2 } diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 215a762869..7d2c26b2e0 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -502,6 +502,16 @@ export class TextRenderer extends Renderer implements ITextRenderer { this._subFont.nativeFontString = TextUtils.getNativeFontString(font.name, this.fontSize, this.fontStyle); } + /** + * Switch the sub font to a specific font size, used by the SHRINK overflow measurement. + */ + private _applyFontSizeForShrink(fontSize: number): void { + const font = this._font; + const subFont = font._getSubFont(fontSize, this._fontStyle); + subFont.nativeFontString = TextUtils.getNativeFontString(font.name, fontSize, this._fontStyle); + this._subFont = subFont; + } + private _updatePosition(): void { const { transform } = this.entity; const e = transform.worldMatrix.elements; @@ -575,22 +585,37 @@ export class TextRenderer extends Renderer implements ITextRenderer { const { _pixelsPerUnit } = Engine; const { min, max } = this._localBounds; const charRenderInfos = TextRenderer._charRenderInfos; + const rendererWidth = this.width * _pixelsPerUnit; + const rendererHeight = this.height * _pixelsPerUnit; + let fontSize = this._fontSize; + let textMetrics: ReturnType; + if (this._overflowMode === OverflowMode.Shrink) { + const result = TextUtils.measureTextWithShrink( + this, + rendererWidth, + rendererHeight, + this._fontSize, + this._lineSpacing, + this._characterSpacing, + this.enableWrapping, + (size) => this._applyFontSizeForShrink(size) + ); + fontSize = result.fontSize; + textMetrics = result.metrics; + } else { + const characterSpacing = this._characterSpacing * fontSize; + textMetrics = this.enableWrapping + ? TextUtils.measureTextWithWrap( + this, + rendererWidth, + rendererHeight, + this._lineSpacing * fontSize, + characterSpacing + ) + : TextUtils.measureTextWithoutWrap(this, rendererHeight, this._lineSpacing * fontSize, characterSpacing); + } const charFont = this._getSubFont(); - const characterSpacing = this._characterSpacing * this._fontSize; - const textMetrics = this.enableWrapping - ? TextUtils.measureTextWithWrap( - this, - this.width * _pixelsPerUnit, - this.height * _pixelsPerUnit, - this._lineSpacing * this._fontSize, - characterSpacing - ) - : TextUtils.measureTextWithoutWrap( - this, - this.height * _pixelsPerUnit, - this._lineSpacing * this._fontSize, - characterSpacing - ); + const characterSpacing = this._characterSpacing * fontSize; const { height, lines, lineWidths, lineHeight, lineMaxSizes } = textMetrics; const charRenderInfoPool = this.engine._charRenderInfoPool; const linesLen = lines.length; @@ -599,9 +624,7 @@ export class TextRenderer extends Renderer implements ITextRenderer { if (linesLen > 0) { const { horizontalAlignment } = this; const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; - const rendererWidth = this._width * _pixelsPerUnit; const halfRendererWidth = rendererWidth * 0.5; - const rendererHeight = this._height * _pixelsPerUnit; const halfLineHeight = lineHeight * 0.5; let startY = 0; @@ -612,7 +635,10 @@ export class TextRenderer extends Renderer implements ITextRenderer { startY = rendererHeight * 0.5 - halfLineHeight + topDiff; break; case TextVerticalAlignment.Center: - startY = height * 0.5 - halfLineHeight - (bottomDiff - topDiff) * 0.5; + // Center the text block (lineHeight * lineCount) within the renderer, independent of + // `height` — which equals the renderer height for Truncate/Shrink and would otherwise + // push the text upward by (rendererHeight - blockHeight) / 2 when the box is taller. + startY = lineHeight * linesLen * 0.5 - halfLineHeight - (bottomDiff - topDiff) * 0.5; break; case TextVerticalAlignment.Bottom: startY = height - rendererHeight * 0.5 - halfLineHeight - bottomDiff; @@ -737,7 +763,7 @@ export class TextRenderer extends Renderer implements ITextRenderer { this._text === "" || this._fontSize === 0 || (this.enableWrapping && this.width <= 0) || - (this.overflowMode === OverflowMode.Truncate && this.height <= 0) + ((this.overflowMode === OverflowMode.Truncate || this.overflowMode === OverflowMode.Shrink) && this.height <= 0) ); } diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index ce2440573f..5305049fa9 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -100,7 +100,8 @@ export class TextUtils { rendererWidth: number, rendererHeight: number, lineSpacing: number, - characterSpacing: number + characterSpacing: number, + uploadCharTexture: boolean = true ): TextMetrics { const subFont = renderer._getSubFont(); const fontString = subFont.nativeFontString; @@ -137,7 +138,7 @@ export class TextUtils { for (let j = 0, m = subText.length; j < m; ++j) { const char = subText[j]; - const charInfo = TextUtils._getCharInfo(char, fontString, subFont); + const charInfo = TextUtils._getCharInfo(char, fontString, subFont, uploadCharTexture); const charCode = char.charCodeAt(0); const isSpace = charCode === 32; @@ -284,7 +285,8 @@ export class TextUtils { renderer: ITextRenderer, rendererHeight: number, lineSpacing: number, - characterSpacing: number + characterSpacing: number, + uploadCharTexture: boolean = true ): TextMetrics { const subFont = renderer._getSubFont(); const fontString = subFont.nativeFontString; @@ -306,7 +308,7 @@ export class TextUtils { let maxDescent = 0; for (let j = 0; j < lineLength; ++j) { - const charInfo = TextUtils._getCharInfo(line[j], fontString, subFont); + const charInfo = TextUtils._getCharInfo(line[j], fontString, subFont, uploadCharTexture); curWidth += charInfo.xAdvance; const { offsetY } = charInfo; const halfH = charInfo.h * 0.5; @@ -337,6 +339,85 @@ export class TextUtils { }; } + /** + * Measure text in SHRINK overflow mode: keep shrinking the font size until the text fits + * within the bounds (both width and height). Mirrors Cocos Creator's `Overflow.SHRINK`. + * + * @param renderer - The text renderer + * @param rendererWidth - The width of the bounds in pixels + * @param rendererHeight - The height of the bounds in pixels + * @param originalFontSize - The font size set on the renderer (the upper bound, never enlarged) + * @param lineSpacing - The line spacing ratio (relative to font size) + * @param characterSpacing - The character spacing ratio (relative to font size) + * @param enableWrapping - Whether wrapping is enabled + * @param applyFontSize - Callback that switches the renderer's sub font to the given font size + * @returns The fitted text metrics and the actual font size used for layout + */ + static measureTextWithShrink( + renderer: ITextRenderer, + rendererWidth: number, + rendererHeight: number, + originalFontSize: number, + lineSpacing: number, + characterSpacing: number, + enableWrapping: boolean, + applyFontSize: (fontSize: number) => void + ): { metrics: TextMetrics; fontSize: number } { + // During the binary search we only need the text dimensions, so pass `uploadCharTexture=false` + // to avoid building GPU font atlases for the intermediate font sizes that won't be used. + // The fitted size is then re-measured once with upload=true to populate its atlas. This trades + // one extra full measure pass (CPU) for skipping ~log2(size) throwaway atlas textures (GPU); + // we can't reuse the search's last measure because its char bitmaps were never cached. + const measureAt = (fontSize: number, uploadCharTexture: boolean): TextMetrics => { + applyFontSize(fontSize); + return enableWrapping + ? TextUtils.measureTextWithWrap( + renderer, + rendererWidth, + rendererHeight, + lineSpacing * fontSize, + characterSpacing * fontSize, + uploadCharTexture + ) + : TextUtils.measureTextWithoutWrap( + renderer, + rendererHeight, + lineSpacing * fontSize, + characterSpacing * fontSize, + uploadCharTexture + ); + }; + // The content height is `lineHeight * lineCount`; `metrics.height` is clamped to the bounds + // unless overflowMode is Overflow, so it can't be used to detect vertical overflow here. + const isFit = (metrics: TextMetrics): boolean => + metrics.width <= rendererWidth && metrics.lineHeight * metrics.lines.length <= rendererHeight; + + // If the text already fits at the original size, keep it (SHRINK only shrinks, never enlarges). + let metrics = measureAt(originalFontSize, false); + if (isFit(metrics)) { + metrics = measureAt(originalFontSize, true); + return { metrics, fontSize: originalFontSize }; + } + + // Binary search for the largest integer font size that fits (measure only, no atlas upload). + let low = 1; + let high = Math.floor(originalFontSize); + let fitFontSize = low; + while (low <= high) { + const mid = (low + high) >> 1; + metrics = measureAt(mid, false); + if (isFit(metrics)) { + fitFontSize = mid; + low = mid + 1; + } else { + high = mid - 1; + } + } + // Re-measure the fitted size with upload=true so the sub font / atlas correspond to it. + metrics = measureAt(fitFontSize, true); + return { metrics, fontSize: fitFontSize }; + } + /** * Get native font hash. * @param fontName - The font name @@ -462,12 +543,16 @@ export class TextUtils { /** * @internal */ - static _getCharInfo(char: string, fontString: string, font: SubFont): CharInfo { + static _getCharInfo(char: string, fontString: string, font: SubFont, uploadCharTexture: boolean = true): CharInfo { let charInfo = font._getCharInfo(char); if (!charInfo) { charInfo = TextUtils.measureChar(char, fontString); - font._uploadCharTexture(charInfo); - font._addCharInfo(char, charInfo); + // SHRINK 的二分阶段只需字符尺寸(measureChar 已给出),传 uploadCharTexture=false 跳过 GPU + // 字形图集上传与缓存,避免给用不到的中间字号建字体 atlas 纹理。 + if (uploadCharTexture) { + font._uploadCharTexture(charInfo); + font._addCharInfo(char, charInfo); + } } return charInfo; diff --git a/packages/ui/src/component/advanced/Text.ts b/packages/ui/src/component/advanced/Text.ts index 553912d64b..f1c329e6ef 100644 --- a/packages/ui/src/component/advanced/Text.ts +++ b/packages/ui/src/component/advanced/Text.ts @@ -420,6 +420,17 @@ export class Text extends UIRenderer implements ITextRenderer { this._subFont.nativeFontString = TextUtils.getNativeFontString(font.name, this.fontSize, this.fontStyle); } + /** + * Switch the sub font to a specific font size, used by the SHRINK overflow measurement. + */ + private _applyFontSizeForShrink(fontSize: number): void { + const font = this._font; + // @ts-ignore + const subFont = font._getSubFont(fontSize, this._fontStyle); + subFont.nativeFontString = TextUtils.getNativeFontString(font.name, fontSize, this._fontStyle); + this._subFont = subFont; + } + private _updatePosition(): void { const e = this._transformEntity.transform.worldMatrix.elements; @@ -490,27 +501,45 @@ export class Text extends UIRenderer implements ITextRenderer { const pixelsPerResolution = Engine._pixelsPerUnit / this._getRootCanvas().referenceResolutionPerUnit; const { min, max } = this._localBounds; const charRenderInfos = Text._charRenderInfos; - const charFont = this._getSubFont(); const { size, pivot } = this._transformEntity.transform; let rendererWidth = size.x; let rendererHeight = size.y; const offsetWidth = rendererWidth * (0.5 - pivot.x); const offsetHeight = rendererHeight * (0.5 - pivot.y); - const characterSpacing = this._characterSpacing * this._fontSize; - const textMetrics = this.enableWrapping - ? TextUtils.measureTextWithWrap( - this, - rendererWidth * pixelsPerResolution, - rendererHeight * pixelsPerResolution, - this._lineSpacing * this._fontSize, - characterSpacing - ) - : TextUtils.measureTextWithoutWrap( - this, - rendererHeight * pixelsPerResolution, - this._lineSpacing * this._fontSize, - characterSpacing - ); + let fontSize = this._fontSize; + let textMetrics: ReturnType; + if (this._overflowMode === OverflowMode.Shrink) { + const result = TextUtils.measureTextWithShrink( + this, + rendererWidth * pixelsPerResolution, + rendererHeight * pixelsPerResolution, + this._fontSize, + this._lineSpacing, + this._characterSpacing, + this.enableWrapping, + (sizeValue) => this._applyFontSizeForShrink(sizeValue) + ); + fontSize = result.fontSize; + textMetrics = result.metrics; + } else { + const characterSpacing = this._characterSpacing * fontSize; + textMetrics = this.enableWrapping + ? TextUtils.measureTextWithWrap( + this, + rendererWidth * pixelsPerResolution, + rendererHeight * pixelsPerResolution, + this._lineSpacing * fontSize, + characterSpacing + ) + : TextUtils.measureTextWithoutWrap( + this, + rendererHeight * pixelsPerResolution, + this._lineSpacing * fontSize, + characterSpacing + ); + } + const charFont = this._getSubFont(); + const characterSpacing = this._characterSpacing * fontSize; const { height, lines, lineWidths, lineHeight, lineMaxSizes } = textMetrics; // @ts-ignore const charRenderInfoPool = this.engine._charRenderInfoPool; @@ -533,7 +562,10 @@ export class Text extends UIRenderer implements ITextRenderer { startY = rendererHeight * 0.5 - halfLineHeight + topDiff; break; case TextVerticalAlignment.Center: - startY = height * 0.5 - halfLineHeight - (bottomDiff - topDiff) * 0.5; + // Center the text block (lineHeight * lineCount) within the renderer, independent of + // `height` — which equals the renderer height for Truncate/Shrink and would otherwise + // push the text upward by (rendererHeight - blockHeight) / 2 when the box is taller. + startY = lineHeight * linesLen * 0.5 - halfLineHeight - (bottomDiff - topDiff) * 0.5; break; case TextVerticalAlignment.Bottom: startY = height - rendererHeight * 0.5 - halfLineHeight - bottomDiff; @@ -662,7 +694,7 @@ export class Text extends UIRenderer implements ITextRenderer { this._text === "" || this._fontSize === 0 || (this.enableWrapping && size.x <= 0) || - (this.overflowMode === OverflowMode.Truncate && size.y <= 0) || + ((this.overflowMode === OverflowMode.Truncate || this.overflowMode === OverflowMode.Shrink) && size.y <= 0) || !this._getRootCanvas() ); } diff --git a/tests/src/core/2d/text/TextUtils.test.ts b/tests/src/core/2d/text/TextUtils.test.ts index 972b3f593c..68accf6ff8 100644 --- a/tests/src/core/2d/text/TextUtils.test.ts +++ b/tests/src/core/2d/text/TextUtils.test.ts @@ -359,7 +359,7 @@ describe("TextUtils", () => { ); expect(result.width).to.be.equal(23); expect(result.height).to.be.equal(135); - expect(result.lines).to.be.deep.equal([' ', ' ', 'W', 'or', 'ld']); + expect(result.lines).to.be.deep.equal([" ", " ", "W", "or", "ld"]); expect(result.lineHeight).to.be.equal(27); wrap1TextRenderer.enableWrapping = true; @@ -564,6 +564,90 @@ describe("TextUtils", () => { ); }); + it("measureTextWithShrink", () => { + // @ts-ignore + const { _pixelsPerUnit } = Engine; + const r = textRendererTruncate; + r.overflowMode = OverflowMode.Shrink; + r.enableWrapping = false; + r.lineSpacing = 0; + r.characterSpacing = 0; + r.text = "15"; + // applyFontSize: switch the sub font for each measurement during the shrink search. + const apply = (fs: number) => { + // @ts-ignore + r._applyFontSizeForShrink(fs); + }; + const measure = (w: number, h: number, fontSize: number) => { + r.width = w; + r.height = h; + r.fontSize = fontSize; + r.bounds; + return TextUtils.measureTextWithShrink( + r, + w * _pixelsPerUnit, + h * _pixelsPerUnit, + fontSize, + r.lineSpacing, + r.characterSpacing, + r.enableWrapping, + apply + ); + }; + + // Fits at original size → keep it (never shrinks unnecessarily). + let res = measure(3, 1, 24); + expect(res.fontSize).to.be.equal(24); + expect(res.metrics.width).to.be.at.most(300); + expect(res.metrics.lineHeight * res.metrics.lines.length).to.be.at.most(100); + + // Box too short → shrink the font size until the content height fits. + res = measure(3, 0.3, 80); + expect(res.fontSize).to.be.below(80); + expect(res.metrics.lineHeight * res.metrics.lines.length).to.be.at.most(30); + + // Box too narrow (no wrap) → shrink the font size until the width fits. + res = measure(0.3, 3, 80); + expect(res.fontSize).to.be.below(80); + expect(res.metrics.width).to.be.at.most(30); + + // Only shrinks, never enlarges: even a huge box keeps the original font size. + res = measure(10, 10, 24); + expect(res.fontSize).to.be.equal(24); + + // Wrapping path: a long text that wraps to multiple lines and overflows the height + // must shrink until the real content height (lineHeight * lineCount) fits the box. + r.enableWrapping = true; + r.text = "这是一段会换行的较长文本"; + res = measure(2, 0.5, 60); // 200x50px box + expect(res.fontSize).to.be.below(60); + expect(res.metrics.width).to.be.at.most(200); + expect(res.metrics.lineHeight * res.metrics.lines.length).to.be.at.most(50); + }); + + it("_getCharInfo writes the atlas only when uploadTexture is true (used by SHRINK search)", () => { + const r = wrap2TextRenderer; + // @ts-ignore + const subFont = r._getSubFont(); + const fontString = subFont.nativeFontString; + const char = "Q"; // a glyph not measured by the other cases above + + // Sanity: the glyph is not in the atlas yet. + // @ts-ignore + expect(subFont._getCharInfo(char)).to.be.null; + + // measure-only path: returns valid metrics but must NOT upload to the atlas. + const info = TextUtils._getCharInfo(char, fontString, subFont, false); + expect(info.w).to.be.greaterThan(0); + // @ts-ignore + expect(subFont._getCharInfo(char)).to.be.null; + + // full path: uploads the glyph to the atlas. + TextUtils._getCharInfo(char, fontString, subFont, true); + // @ts-ignore + expect(subFont._getCharInfo(char)).to.not.be.null; + }); + afterAll(() => { engine.destroy(); }); From eaf168f714c6cbd17d448eac47b8be0593914e49 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Mon, 29 Jun 2026 21:54:40 +0800 Subject: [PATCH 127/130] fix(core): make AssetPromise.onProgress onTaskComplete optional Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/core/src/asset/AssetPromise.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/asset/AssetPromise.ts b/packages/core/src/asset/AssetPromise.ts index 99f00e86f2..9e9dc261c0 100644 --- a/packages/core/src/asset/AssetPromise.ts +++ b/packages/core/src/asset/AssetPromise.ts @@ -164,16 +164,16 @@ export class AssetPromise implements PromiseLike { * @returns AssetPromise */ onProgress( - onTaskComplete: (loaded: number, total: number) => void, + onTaskComplete?: (loaded: number, total: number) => void, onTaskDetail?: (identifier: string, loaded: number, total: number) => void ): AssetPromise { const completeProgress = this._taskCompleteProgress; const detailProgress = this._taskDetailProgress; - if (completeProgress) { + if (completeProgress && onTaskComplete) { onTaskComplete(completeProgress.loaded, completeProgress.total); } - if (detailProgress) { + if (detailProgress && onTaskDetail) { for (let url in detailProgress) { const { loaded, total } = detailProgress[url]; onTaskDetail(url, loaded, total); From 91660c73cafcfaeafc8ad4b4990431d5054c8462 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Mon, 29 Jun 2026 22:06:36 +0800 Subject: [PATCH 128/130] chore: release v0.0.0-experimental-2.0-game.18 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 3084e6e56a..6558caf6a3 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index 4df065b8ba..444aef9f77 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 3383e122bf..72b04ffdf8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 8f12267856..499a6fc8e7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index a7b0455c4a..2e0f6672c8 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index 39b9b028d4..1f3f7bf203 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index 165e4d846f..d223f1dd28 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index a63fcf190c..7c26d40c61 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 018c630269..1fdb4a552d 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 6b312e3651..9193c74b00 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 103e89df5c..0d1d1ea9f5 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 9485cd5e40..633fbd0f37 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index 2686d81cd1..ea35689543 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index ad3f37872d..9405e8b330 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 6ae03142e4..cf6921a9d7 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 37e37e62a1..9a3e2244cb 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index 040c3c34b7..1e4c4a4c62 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.17", + "version": "0.0.0-experimental-2.0-game.18", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", From 6817044fa5ad561f8689c2603e8e476c39eccd27 Mon Sep 17 00:00:00 2001 From: AZhan Date: Fri, 10 Jul 2026 14:49:09 +0800 Subject: [PATCH 129/130] feat(spine): add Spine runtime for fix/shaderlab (#3063) * feat(spine): add Spine runtime for fix/shaderlab --- e2e/.dev/public/spineboy.atlas | 94 + e2e/.dev/public/spineboy.json | 8723 +++++++++++++++++ e2e/.dev/public/spineboy.png | Bin 0 -> 245321 bytes e2e/.dev/public/tank-pro.atlas | 65 + e2e/.dev/public/tank-pro.json | 1 + e2e/.dev/public/tank-pro.png | Bin 0 -> 533353 bytes e2e/case/spine-spineboy.ts | 36 + e2e/case/spine-tint-black.ts | 41 + e2e/config.ts | 532 + .../originImage/Spine_spine-spineboy.jpg | 3 + .../originImage/Spine_spine-tint-black.jpg | 3 + e2e/package.json | 2 + examples/package.json | 5 +- examples/src/spine-keli-4.2.ts | 60 + examples/src/spine-otakugirl-3.8.ts | 55 + packages/core/src/shader/ShaderPool.ts | 29 + .../core/src/shaderlib/extra/spine.fs.glsl | 29 + .../core/src/shaderlib/extra/spine.vs.glsl | 28 + packages/spine-core-3.8/package.json | 40 + packages/spine-core-3.8/src/Spine38Runtime.ts | 94 + packages/spine-core-3.8/src/SpineGenerator.ts | 358 + packages/spine-core-3.8/src/SpineTexture.ts | 50 + packages/spine-core-3.8/src/index.ts | 17 + .../src/spine-core/Animation.ts | 1828 ++++ .../src/spine-core/AnimationState.ts | 1193 +++ .../src/spine-core/AnimationStateData.ts | 48 + .../src/spine-core/AtlasAttachmentLoader.ts | 55 + .../src/spine-core/BlendMode.ts | 7 + .../spine-core-3.8/src/spine-core/Bone.ts | 391 + .../spine-core-3.8/src/spine-core/BoneData.ts | 66 + .../src/spine-core/ConstraintData.ts | 8 + .../spine-core-3.8/src/spine-core/Event.ts | 22 + .../src/spine-core/EventData.ts | 16 + .../src/spine-core/IkConstraint.ts | 346 + .../src/spine-core/IkConstraintData.ts | 37 + .../src/spine-core/PathConstraint.ts | 500 + .../src/spine-core/PathConstraintData.ts | 68 + .../spine-core-3.8/src/spine-core/Skeleton.ts | 592 ++ .../src/spine-core/SkeletonBinary.ts | 942 ++ .../src/spine-core/SkeletonBounds.ts | 215 + .../src/spine-core/SkeletonClipping.ts | 363 + .../src/spine-core/SkeletonData.ts | 198 + .../src/spine-core/SkeletonJson.ts | 906 ++ .../spine-core-3.8/src/spine-core/Skin.ts | 182 + .../spine-core-3.8/src/spine-core/Slot.ts | 86 + .../spine-core-3.8/src/spine-core/SlotData.ts | 38 + .../spine-core-3.8/src/spine-core/Texture.ts | 96 + .../src/spine-core/TextureAtlas.ts | 187 + .../src/spine-core/TransformConstraint.ts | 296 + .../src/spine-core/TransformConstraintData.ts | 50 + .../src/spine-core/Triangulator.ts | 269 + .../src/spine-core/Updatable.ts | 10 + .../spine-core-3.8/src/spine-core/Utils.ts | 417 + .../src/spine-core/VertexEffect.ts | 8 + .../src/spine-core/attachments/Attachment.ts | 147 + .../attachments/AttachmentLoader.ts | 31 + .../spine-core/attachments/AttachmentType.ts | 9 + .../attachments/BoundingBoxAttachment.ts | 22 + .../attachments/ClippingAttachment.ts | 27 + .../spine-core/attachments/MeshAttachment.ts | 175 + .../spine-core/attachments/PathAttachment.ts | 36 + .../spine-core/attachments/PointAttachment.ts | 46 + .../attachments/RegionAttachment.ts | 211 + .../spine-core-3.8/src/spine-core/index.ts | 50 + .../src/spine-core/polyfills.ts | 13 + .../spine-core/vertexeffects/JitterEffect.ts | 22 + .../spine-core/vertexeffects/SwirlEffect.ts | 38 + .../spine-core-3.8/src/util/ClearablePool.ts | 26 + .../spine-core-3.8/src/util/ReturnablePool.ts | 25 + packages/spine-core-3.8/tsconfig.json | 18 + packages/spine-core-4.2/package.json | 41 + packages/spine-core-4.2/src/Spine42Runtime.ts | 81 + packages/spine-core-4.2/src/SpineGenerator.ts | 342 + packages/spine-core-4.2/src/SpineTexture.ts | 49 + packages/spine-core-4.2/src/index.ts | 15 + .../spine-core-4.2/src/util/ClearablePool.ts | 26 + .../spine-core-4.2/src/util/ReturnablePool.ts | 25 + packages/spine-core-4.2/tsconfig.json | 18 + packages/spine/package.json | 37 + packages/spine/src/SpineConstant.ts | 13 + packages/spine/src/enums/SpineBlendMode.ts | 14 + packages/spine/src/index.ts | 21 + packages/spine/src/loader/LoaderUtils.ts | 96 + packages/spine/src/loader/SpineAtlasLoader.ts | 107 + packages/spine/src/loader/SpineLoader.ts | 144 + packages/spine/src/loader/SpineResource.ts | 112 + packages/spine/src/loader/index.ts | 5 + .../src/renderer/SpineAnimationRenderer.ts | 422 + packages/spine/src/renderer/SpineMaterial.ts | 106 + packages/spine/src/renderer/index.ts | 2 + .../spine/src/runtime/ISpineRenderTarget.ts | 30 + packages/spine/src/runtime/ISpineRuntime.ts | 52 + .../spine/src/runtime/SpineRuntimeRegistry.ts | 25 + packages/spine/tsconfig.json | 18 + pnpm-lock.yaml | 58 + tests/package.json | 4 +- tests/src/spine/LoaderUtils.test.ts | 16 + tests/src/spine/Pool.test.ts | 41 + .../src/spine/SpineAnimationRenderer.test.ts | 72 + tests/src/spine/SpineConstant.test.ts | 20 + tests/src/spine/SpineLoader.test.ts | 18 + tests/src/spine/SpineMaterial.test.ts | 36 + tests/src/spine/SpineResource.test.ts | 33 + tests/src/spine/SpineRuntimeRegistry.test.ts | 24 + tests/src/spine/SpineTexture.test.ts | 44 + 105 files changed, 22766 insertions(+), 2 deletions(-) create mode 100644 e2e/.dev/public/spineboy.atlas create mode 100644 e2e/.dev/public/spineboy.json create mode 100644 e2e/.dev/public/spineboy.png create mode 100644 e2e/.dev/public/tank-pro.atlas create mode 100644 e2e/.dev/public/tank-pro.json create mode 100644 e2e/.dev/public/tank-pro.png create mode 100644 e2e/case/spine-spineboy.ts create mode 100644 e2e/case/spine-tint-black.ts create mode 100644 e2e/config.ts create mode 100644 e2e/fixtures/originImage/Spine_spine-spineboy.jpg create mode 100644 e2e/fixtures/originImage/Spine_spine-tint-black.jpg create mode 100644 examples/src/spine-keli-4.2.ts create mode 100644 examples/src/spine-otakugirl-3.8.ts create mode 100644 packages/core/src/shaderlib/extra/spine.fs.glsl create mode 100644 packages/core/src/shaderlib/extra/spine.vs.glsl create mode 100644 packages/spine-core-3.8/package.json create mode 100644 packages/spine-core-3.8/src/Spine38Runtime.ts create mode 100644 packages/spine-core-3.8/src/SpineGenerator.ts create mode 100644 packages/spine-core-3.8/src/SpineTexture.ts create mode 100644 packages/spine-core-3.8/src/index.ts create mode 100644 packages/spine-core-3.8/src/spine-core/Animation.ts create mode 100644 packages/spine-core-3.8/src/spine-core/AnimationState.ts create mode 100644 packages/spine-core-3.8/src/spine-core/AnimationStateData.ts create mode 100644 packages/spine-core-3.8/src/spine-core/AtlasAttachmentLoader.ts create mode 100644 packages/spine-core-3.8/src/spine-core/BlendMode.ts create mode 100644 packages/spine-core-3.8/src/spine-core/Bone.ts create mode 100644 packages/spine-core-3.8/src/spine-core/BoneData.ts create mode 100644 packages/spine-core-3.8/src/spine-core/ConstraintData.ts create mode 100644 packages/spine-core-3.8/src/spine-core/Event.ts create mode 100644 packages/spine-core-3.8/src/spine-core/EventData.ts create mode 100644 packages/spine-core-3.8/src/spine-core/IkConstraint.ts create mode 100644 packages/spine-core-3.8/src/spine-core/IkConstraintData.ts create mode 100644 packages/spine-core-3.8/src/spine-core/PathConstraint.ts create mode 100644 packages/spine-core-3.8/src/spine-core/PathConstraintData.ts create mode 100644 packages/spine-core-3.8/src/spine-core/Skeleton.ts create mode 100644 packages/spine-core-3.8/src/spine-core/SkeletonBinary.ts create mode 100644 packages/spine-core-3.8/src/spine-core/SkeletonBounds.ts create mode 100644 packages/spine-core-3.8/src/spine-core/SkeletonClipping.ts create mode 100644 packages/spine-core-3.8/src/spine-core/SkeletonData.ts create mode 100644 packages/spine-core-3.8/src/spine-core/SkeletonJson.ts create mode 100644 packages/spine-core-3.8/src/spine-core/Skin.ts create mode 100644 packages/spine-core-3.8/src/spine-core/Slot.ts create mode 100644 packages/spine-core-3.8/src/spine-core/SlotData.ts create mode 100644 packages/spine-core-3.8/src/spine-core/Texture.ts create mode 100644 packages/spine-core-3.8/src/spine-core/TextureAtlas.ts create mode 100644 packages/spine-core-3.8/src/spine-core/TransformConstraint.ts create mode 100644 packages/spine-core-3.8/src/spine-core/TransformConstraintData.ts create mode 100644 packages/spine-core-3.8/src/spine-core/Triangulator.ts create mode 100644 packages/spine-core-3.8/src/spine-core/Updatable.ts create mode 100644 packages/spine-core-3.8/src/spine-core/Utils.ts create mode 100644 packages/spine-core-3.8/src/spine-core/VertexEffect.ts create mode 100644 packages/spine-core-3.8/src/spine-core/attachments/Attachment.ts create mode 100644 packages/spine-core-3.8/src/spine-core/attachments/AttachmentLoader.ts create mode 100644 packages/spine-core-3.8/src/spine-core/attachments/AttachmentType.ts create mode 100644 packages/spine-core-3.8/src/spine-core/attachments/BoundingBoxAttachment.ts create mode 100644 packages/spine-core-3.8/src/spine-core/attachments/ClippingAttachment.ts create mode 100644 packages/spine-core-3.8/src/spine-core/attachments/MeshAttachment.ts create mode 100644 packages/spine-core-3.8/src/spine-core/attachments/PathAttachment.ts create mode 100644 packages/spine-core-3.8/src/spine-core/attachments/PointAttachment.ts create mode 100644 packages/spine-core-3.8/src/spine-core/attachments/RegionAttachment.ts create mode 100644 packages/spine-core-3.8/src/spine-core/index.ts create mode 100644 packages/spine-core-3.8/src/spine-core/polyfills.ts create mode 100644 packages/spine-core-3.8/src/spine-core/vertexeffects/JitterEffect.ts create mode 100644 packages/spine-core-3.8/src/spine-core/vertexeffects/SwirlEffect.ts create mode 100644 packages/spine-core-3.8/src/util/ClearablePool.ts create mode 100644 packages/spine-core-3.8/src/util/ReturnablePool.ts create mode 100644 packages/spine-core-3.8/tsconfig.json create mode 100644 packages/spine-core-4.2/package.json create mode 100644 packages/spine-core-4.2/src/Spine42Runtime.ts create mode 100644 packages/spine-core-4.2/src/SpineGenerator.ts create mode 100644 packages/spine-core-4.2/src/SpineTexture.ts create mode 100644 packages/spine-core-4.2/src/index.ts create mode 100644 packages/spine-core-4.2/src/util/ClearablePool.ts create mode 100644 packages/spine-core-4.2/src/util/ReturnablePool.ts create mode 100644 packages/spine-core-4.2/tsconfig.json create mode 100644 packages/spine/package.json create mode 100644 packages/spine/src/SpineConstant.ts create mode 100644 packages/spine/src/enums/SpineBlendMode.ts create mode 100644 packages/spine/src/index.ts create mode 100644 packages/spine/src/loader/LoaderUtils.ts create mode 100644 packages/spine/src/loader/SpineAtlasLoader.ts create mode 100644 packages/spine/src/loader/SpineLoader.ts create mode 100644 packages/spine/src/loader/SpineResource.ts create mode 100644 packages/spine/src/loader/index.ts create mode 100644 packages/spine/src/renderer/SpineAnimationRenderer.ts create mode 100644 packages/spine/src/renderer/SpineMaterial.ts create mode 100644 packages/spine/src/renderer/index.ts create mode 100644 packages/spine/src/runtime/ISpineRenderTarget.ts create mode 100644 packages/spine/src/runtime/ISpineRuntime.ts create mode 100644 packages/spine/src/runtime/SpineRuntimeRegistry.ts create mode 100644 packages/spine/tsconfig.json create mode 100644 tests/src/spine/LoaderUtils.test.ts create mode 100644 tests/src/spine/Pool.test.ts create mode 100644 tests/src/spine/SpineAnimationRenderer.test.ts create mode 100644 tests/src/spine/SpineConstant.test.ts create mode 100644 tests/src/spine/SpineLoader.test.ts create mode 100644 tests/src/spine/SpineMaterial.test.ts create mode 100644 tests/src/spine/SpineResource.test.ts create mode 100644 tests/src/spine/SpineRuntimeRegistry.test.ts create mode 100644 tests/src/spine/SpineTexture.test.ts diff --git a/e2e/.dev/public/spineboy.atlas b/e2e/.dev/public/spineboy.atlas new file mode 100644 index 0000000000..eca542b711 --- /dev/null +++ b/e2e/.dev/public/spineboy.atlas @@ -0,0 +1,94 @@ +spineboy.png + size: 1024, 256 + filter: Linear, Linear + scale: 0.5 +crosshair + bounds: 352, 7, 45, 45 +eye-indifferent + bounds: 862, 105, 47, 45 +eye-surprised + bounds: 505, 79, 47, 45 +front-bracer + bounds: 826, 66, 29, 40 +front-fist-closed + bounds: 786, 65, 38, 41 +front-fist-open + bounds: 710, 51, 43, 44 + rotate: 90 +front-foot + bounds: 210, 6, 63, 35 +front-shin + bounds: 665, 128, 41, 92 + rotate: 90 +front-thigh + bounds: 2, 2, 23, 56 + rotate: 90 +front-upper-arm + bounds: 250, 205, 23, 49 +goggles + bounds: 665, 171, 131, 83 +gun + bounds: 798, 152, 105, 102 +head + bounds: 2, 27, 136, 149 +hoverboard-board + bounds: 2, 178, 246, 76 +hoverboard-thruster + bounds: 722, 96, 30, 32 + rotate: 90 +hoverglow-small + bounds: 275, 81, 137, 38 +mouth-grind + bounds: 614, 97, 47, 30 +mouth-oooo + bounds: 612, 65, 47, 30 +mouth-smile + bounds: 661, 64, 47, 30 +muzzle-glow + bounds: 382, 54, 25, 25 +muzzle-ring + bounds: 275, 54, 25, 105 + rotate: 90 +muzzle01 + bounds: 911, 95, 67, 40 + rotate: 90 +muzzle02 + bounds: 792, 108, 68, 42 +muzzle03 + bounds: 956, 171, 83, 53 + rotate: 90 +muzzle04 + bounds: 275, 7, 75, 45 +muzzle05 + bounds: 140, 3, 68, 38 +neck + bounds: 250, 182, 18, 21 +portal-bg + bounds: 140, 43, 133, 133 +portal-flare1 + bounds: 554, 65, 56, 30 +portal-flare2 + bounds: 759, 112, 57, 31 + rotate: 90 +portal-flare3 + bounds: 554, 97, 58, 30 +portal-shade + bounds: 275, 121, 133, 133 +portal-streaks1 + bounds: 410, 126, 126, 128 +portal-streaks2 + bounds: 538, 129, 125, 125 +rear-bracer + bounds: 857, 67, 28, 36 +rear-foot + bounds: 663, 96, 57, 30 +rear-shin + bounds: 414, 86, 38, 89 + rotate: 90 +rear-thigh + bounds: 756, 63, 28, 47 +rear-upper-arm + bounds: 60, 5, 20, 44 + rotate: 90 +torso + bounds: 905, 164, 49, 90 diff --git a/e2e/.dev/public/spineboy.json b/e2e/.dev/public/spineboy.json new file mode 100644 index 0000000000..c0eb0ae927 --- /dev/null +++ b/e2e/.dev/public/spineboy.json @@ -0,0 +1,8723 @@ +{ + "skeleton": { + "hash": "dr3Kr/vMgPA", + "spine": "4.2.22", + "x": -188.63, + "y": -7.94, + "width": 418.45, + "height": 686.2, + "images": "./images/", + "audio": "" + }, + "bones": [ + { "name": "root", "rotation": 0.05 }, + { "name": "hip", "parent": "root", "y": 247.27 }, + { "name": "crosshair", "parent": "root", "x": 302.83, "y": 569.45, "color": "ff3f00ff", "icon": "circle" }, + { + "name": "aim-constraint-target", + "parent": "hip", + "length": 26.24, + "rotation": 19.61, + "x": 1.02, + "y": 5.62, + "color": "abe323ff" + }, + { "name": "rear-foot-target", "parent": "root", "x": 61.91, "y": 0.42, "color": "ff3f00ff", "icon": "ik" }, + { "name": "rear-leg-target", "parent": "rear-foot-target", "x": -33.91, "y": 37.34, "color": "ff3f00ff", "icon": "ik" }, + { + "name": "rear-thigh", + "parent": "hip", + "length": 85.72, + "rotation": -72.54, + "x": 8.91, + "y": -5.63, + "color": "ff000dff" + }, + { + "name": "rear-shin", + "parent": "rear-thigh", + "length": 121.88, + "rotation": -19.83, + "x": 86.1, + "y": -1.33, + "color": "ff000dff" + }, + { + "name": "rear-foot", + "parent": "rear-shin", + "length": 51.58, + "rotation": 45.78, + "x": 121.46, + "y": -0.76, + "color": "ff000dff" + }, + { + "name": "back-foot-tip", + "parent": "rear-foot", + "length": 50.3, + "rotation": -0.85, + "x": 51.17, + "y": 0.24, + "inherit": "noRotationOrReflection", + "color": "ff000dff" + }, + { "name": "board-ik", "parent": "root", "x": -131.78, "y": 69.09, "color": "4c56ffff", "icon": "arrows" }, + { "name": "clipping", "parent": "root" }, + { + "name": "hoverboard-controller", + "parent": "root", + "rotation": -0.28, + "x": -329.69, + "y": 69.82, + "color": "ff0004ff", + "icon": "arrowsB" + }, + { "name": "exhaust1", "parent": "hoverboard-controller", "rotation": 3.02, "x": -249.68, "y": 53.39 }, + { "name": "exhaust2", "parent": "hoverboard-controller", "rotation": 26.34, "x": -191.6, "y": -22.92 }, + { + "name": "exhaust3", + "parent": "hoverboard-controller", + "rotation": -12.34, + "x": -236.03, + "y": 80.54, + "scaleX": 0.7847, + "scaleY": 0.7847 + }, + { "name": "portal-root", "parent": "root", "x": 12.9, "y": 328.54, "scaleX": 2.0334, "scaleY": 2.0334 }, + { "name": "flare1", "parent": "portal-root", "x": -6.34, "y": -161.57, "icon": "particles" }, + { "name": "flare10", "parent": "portal-root", "x": -6.34, "y": -161.57, "icon": "particles" }, + { "name": "flare2", "parent": "portal-root", "x": -6.34, "y": -161.57, "icon": "particles" }, + { "name": "flare3", "parent": "portal-root", "x": -6.34, "y": -161.57, "icon": "particles" }, + { "name": "flare4", "parent": "portal-root", "x": -6.34, "y": -161.57, "icon": "particles" }, + { "name": "flare5", "parent": "portal-root", "x": -6.34, "y": -161.57, "icon": "particles" }, + { "name": "flare6", "parent": "portal-root", "x": -6.34, "y": -161.57, "icon": "particles" }, + { "name": "flare7", "parent": "portal-root", "x": -6.34, "y": -161.57, "icon": "particles" }, + { "name": "flare8", "parent": "portal-root", "x": -6.34, "y": -161.57, "icon": "particles" }, + { "name": "flare9", "parent": "portal-root", "x": -6.34, "y": -161.57, "icon": "particles" }, + { + "name": "torso", + "parent": "hip", + "length": 42.52, + "rotation": 103.82, + "x": -1.62, + "y": 4.9, + "color": "e0da19ff" + }, + { "name": "torso2", "parent": "torso", "length": 42.52, "x": 42.52, "color": "e0da19ff" }, + { "name": "torso3", "parent": "torso2", "length": 42.52, "x": 42.52, "color": "e0da19ff" }, + { "name": "front-shoulder", "parent": "torso3", "rotation": 255.89, "x": 18.72, "y": 19.33, "color": "00ff04ff" }, + { "name": "front-upper-arm", "parent": "front-shoulder", "length": 69.45, "rotation": -87.51, "color": "00ff04ff" }, + { + "name": "front-bracer", + "parent": "front-upper-arm", + "length": 40.57, + "rotation": 18.3, + "x": 68.8, + "y": -0.68, + "color": "00ff04ff" + }, + { + "name": "front-fist", + "parent": "front-bracer", + "length": 65.39, + "rotation": 12.43, + "x": 40.57, + "y": 0.2, + "color": "00ff04ff" + }, + { "name": "front-foot-target", "parent": "root", "x": -13.53, "y": 0.04, "color": "ff3f00ff", "icon": "ik" }, + { "name": "front-leg-target", "parent": "front-foot-target", "x": -28.4, "y": 29.06, "color": "ff3f00ff", "icon": "ik" }, + { + "name": "front-thigh", + "parent": "hip", + "length": 74.81, + "rotation": -95.51, + "x": -17.46, + "y": -11.64, + "color": "00ff04ff" + }, + { + "name": "front-shin", + "parent": "front-thigh", + "length": 128.77, + "rotation": -2.21, + "x": 78.69, + "y": 1.6, + "color": "00ff04ff" + }, + { + "name": "front-foot", + "parent": "front-shin", + "length": 41.01, + "rotation": 51.27, + "x": 128.76, + "y": -0.34, + "color": "00ff04ff" + }, + { + "name": "front-foot-tip", + "parent": "front-foot", + "length": 56.03, + "rotation": -1.68, + "x": 41.42, + "y": -0.09, + "inherit": "noRotationOrReflection", + "color": "00ff04ff" + }, + { "name": "back-shoulder", "parent": "torso3", "rotation": -104.11, "x": 7.32, "y": -19.22, "color": "ff000dff" }, + { "name": "rear-upper-arm", "parent": "back-shoulder", "length": 51.94, "rotation": -65.45, "color": "ff000dff" }, + { "name": "rear-bracer", "parent": "rear-upper-arm", "length": 34.56, "rotation": 23.15, "x": 51.36, "color": "ff000dff" }, + { + "name": "gun", + "parent": "rear-bracer", + "length": 43.11, + "rotation": -5.43, + "x": 34.42, + "y": -0.45, + "color": "ff000dff" + }, + { "name": "gun-tip", "parent": "gun", "rotation": 7.1, "x": 200.78, "y": 52.5, "color": "ff0000ff" }, + { + "name": "neck", + "parent": "torso3", + "length": 25.45, + "rotation": -31.54, + "x": 42.46, + "y": -0.31, + "color": "e0da19ff" + }, + { + "name": "head", + "parent": "neck", + "length": 131.79, + "rotation": 26.1, + "x": 27.66, + "y": -0.26, + "color": "e0da19ff" + }, + { + "name": "hair1", + "parent": "head", + "length": 47.23, + "rotation": -49.1, + "x": 149.83, + "y": -59.77, + "color": "e0da19ff" + }, + { + "name": "hair2", + "parent": "hair1", + "length": 55.57, + "rotation": 50.42, + "x": 47.23, + "y": 0.19, + "color": "e0da19ff" + }, + { + "name": "hair3", + "parent": "head", + "length": 62.22, + "rotation": -32.17, + "x": 164.14, + "y": 3.68, + "color": "e0da19ff" + }, + { + "name": "hair4", + "parent": "hair3", + "length": 80.28, + "rotation": 83.71, + "x": 62.22, + "y": -0.04, + "color": "e0da19ff" + }, + { "name": "hoverboard-thruster-front", "parent": "hoverboard-controller", "rotation": -29.2, "x": 95.77, "y": -2.99, "inherit": "noRotationOrReflection" }, + { "name": "hoverboard-thruster-rear", "parent": "hoverboard-controller", "rotation": -29.2, "x": -76.47, "y": -4.88, "inherit": "noRotationOrReflection" }, + { "name": "hoverglow-front", "parent": "hoverboard-thruster-front", "rotation": 0.17, "x": -1.78, "y": -37.79 }, + { "name": "hoverglow-rear", "parent": "hoverboard-thruster-rear", "rotation": 0.17, "x": 1.06, "y": -35.66 }, + { + "name": "muzzle", + "parent": "rear-bracer", + "rotation": 3.06, + "x": 242.34, + "y": 34.26, + "color": "ffb900ff", + "icon": "muzzleFlash" + }, + { "name": "muzzle-ring", "parent": "muzzle", "color": "ffb900ff" }, + { "name": "muzzle-ring2", "parent": "muzzle", "color": "ffb900ff" }, + { "name": "muzzle-ring3", "parent": "muzzle", "color": "ffb900ff" }, + { "name": "muzzle-ring4", "parent": "muzzle", "color": "ffb900ff" }, + { "name": "portal", "parent": "portal-root" }, + { "name": "portal-shade", "parent": "portal-root" }, + { "name": "portal-streaks1", "parent": "portal-root" }, + { "name": "portal-streaks2", "parent": "portal-root" }, + { "name": "side-glow1", "parent": "hoverboard-controller", "x": -110.56, "y": 2.62, "color": "000effff" }, + { + "name": "side-glow2", + "parent": "hoverboard-controller", + "x": -110.56, + "y": 2.62, + "scaleX": 0.738, + "scaleY": 0.738, + "color": "000effff" + }, + { "name": "head-control", "parent": "head", "x": 110.21, "color": "00a220ff", "icon": "arrows" } + ], + "slots": [ + { "name": "portal-bg", "bone": "portal" }, + { "name": "portal-shade", "bone": "portal-shade" }, + { "name": "portal-streaks2", "bone": "portal-streaks2", "blend": "additive" }, + { "name": "portal-streaks1", "bone": "portal-streaks1", "blend": "additive" }, + { "name": "portal-flare8", "bone": "flare8", "color": "c3cbffff", "blend": "additive" }, + { "name": "portal-flare9", "bone": "flare9", "color": "c3cbffff", "blend": "additive" }, + { "name": "portal-flare10", "bone": "flare10", "color": "c3cbffff", "blend": "additive" }, + { "name": "clipping", "bone": "clipping" }, + { "name": "exhaust3", "bone": "exhaust3", "color": "5eb4ffff", "blend": "additive" }, + { "name": "hoverboard-thruster-rear", "bone": "hoverboard-thruster-rear" }, + { "name": "hoverboard-thruster-front", "bone": "hoverboard-thruster-front" }, + { "name": "hoverboard-board", "bone": "hoverboard-controller" }, + { "name": "side-glow1", "bone": "side-glow1", "color": "ff8686ff", "blend": "additive" }, + { "name": "side-glow3", "bone": "side-glow1", "color": "ff8686ff", "blend": "additive" }, + { "name": "side-glow2", "bone": "side-glow2", "color": "ff8686ff", "blend": "additive" }, + { "name": "hoverglow-front", "bone": "hoverglow-front", "color": "5eb4ffff", "blend": "additive" }, + { "name": "hoverglow-rear", "bone": "hoverglow-rear", "color": "5eb4ffff", "blend": "additive" }, + { "name": "exhaust1", "bone": "exhaust2", "color": "5eb4ffff", "blend": "additive" }, + { "name": "exhaust2", "bone": "exhaust1", "color": "5eb4ffff", "blend": "additive" }, + { "name": "rear-upper-arm", "bone": "rear-upper-arm", "attachment": "rear-upper-arm" }, + { "name": "rear-bracer", "bone": "rear-bracer", "attachment": "rear-bracer" }, + { "name": "gun", "bone": "gun", "attachment": "gun" }, + { "name": "rear-foot", "bone": "rear-foot", "attachment": "rear-foot" }, + { "name": "rear-thigh", "bone": "rear-thigh", "attachment": "rear-thigh" }, + { "name": "rear-shin", "bone": "rear-shin", "attachment": "rear-shin" }, + { "name": "neck", "bone": "neck", "attachment": "neck" }, + { "name": "torso", "bone": "torso", "attachment": "torso" }, + { "name": "front-upper-arm", "bone": "front-upper-arm", "attachment": "front-upper-arm" }, + { "name": "head", "bone": "head", "attachment": "head" }, + { "name": "eye", "bone": "head", "attachment": "eye-indifferent" }, + { "name": "front-thigh", "bone": "front-thigh", "attachment": "front-thigh" }, + { "name": "front-foot", "bone": "front-foot", "attachment": "front-foot" }, + { "name": "front-shin", "bone": "front-shin", "attachment": "front-shin" }, + { "name": "mouth", "bone": "head", "attachment": "mouth-smile" }, + { "name": "goggles", "bone": "head", "attachment": "goggles" }, + { "name": "front-bracer", "bone": "front-bracer", "attachment": "front-bracer" }, + { "name": "front-fist", "bone": "front-fist", "attachment": "front-fist-closed" }, + { "name": "muzzle", "bone": "muzzle" }, + { "name": "head-bb", "bone": "head" }, + { "name": "portal-flare1", "bone": "flare1", "color": "c3cbffff", "blend": "additive" }, + { "name": "portal-flare2", "bone": "flare2", "color": "c3cbffff", "blend": "additive" }, + { "name": "portal-flare3", "bone": "flare3", "color": "c3cbffff", "blend": "additive" }, + { "name": "portal-flare4", "bone": "flare4", "color": "c3cbffff", "blend": "additive" }, + { "name": "portal-flare5", "bone": "flare5", "color": "c3cbffff", "blend": "additive" }, + { "name": "portal-flare6", "bone": "flare6", "color": "c3cbffff", "blend": "additive" }, + { "name": "portal-flare7", "bone": "flare7", "color": "c3cbffff", "blend": "additive" }, + { "name": "crosshair", "bone": "crosshair" }, + { "name": "muzzle-glow", "bone": "gun-tip", "color": "ffffff00", "blend": "additive" }, + { "name": "muzzle-ring", "bone": "muzzle-ring", "color": "d8baffff", "blend": "additive" }, + { "name": "muzzle-ring2", "bone": "muzzle-ring2", "color": "d8baffff", "blend": "additive" }, + { "name": "muzzle-ring3", "bone": "muzzle-ring3", "color": "d8baffff", "blend": "additive" }, + { "name": "muzzle-ring4", "bone": "muzzle-ring4", "color": "d8baffff", "blend": "additive" } + ], + "ik": [ + { + "name": "aim-ik", + "order": 13, + "bones": [ "rear-upper-arm" ], + "target": "crosshair", + "mix": 0 + }, + { + "name": "aim-torso-ik", + "order": 8, + "bones": [ "aim-constraint-target" ], + "target": "crosshair" + }, + { + "name": "board-ik", + "order": 1, + "bones": [ "hoverboard-controller" ], + "target": "board-ik" + }, + { + "name": "front-foot-ik", + "order": 6, + "bones": [ "front-foot" ], + "target": "front-foot-target" + }, + { + "name": "front-leg-ik", + "order": 4, + "bones": [ "front-thigh", "front-shin" ], + "target": "front-leg-target", + "bendPositive": false + }, + { + "name": "rear-foot-ik", + "order": 7, + "bones": [ "rear-foot" ], + "target": "rear-foot-target" + }, + { + "name": "rear-leg-ik", + "order": 5, + "bones": [ "rear-thigh", "rear-shin" ], + "target": "rear-leg-target", + "bendPositive": false + } + ], + "transform": [ + { + "name": "aim-front-arm-transform", + "order": 11, + "bones": [ "front-upper-arm" ], + "target": "aim-constraint-target", + "rotation": -180, + "mixRotate": 0, + "mixX": 0, + "mixScaleX": 0, + "mixShearY": 0 + }, + { + "name": "aim-head-transform", + "order": 10, + "bones": [ "head" ], + "target": "aim-constraint-target", + "rotation": 84.3, + "mixRotate": 0, + "mixX": 0, + "mixScaleX": 0, + "mixShearY": 0 + }, + { + "name": "aim-rear-arm-transform", + "order": 12, + "bones": [ "rear-upper-arm" ], + "target": "aim-constraint-target", + "x": 57.7, + "y": 56.4, + "mixRotate": 0, + "mixX": 0, + "mixScaleX": 0, + "mixShearY": 0 + }, + { + "name": "aim-torso-transform", + "order": 9, + "bones": [ "torso" ], + "target": "aim-constraint-target", + "rotation": 69.5, + "shearY": -36, + "mixRotate": 0, + "mixX": 0, + "mixScaleX": 0, + "mixShearY": 0 + }, + { + "name": "front-foot-board-transform", + "order": 2, + "bones": [ "front-foot-target" ], + "target": "hoverboard-controller", + "x": -69.8, + "y": 20.7, + "mixRotate": 0, + "mixX": 0, + "mixScaleX": 0, + "mixShearY": 0 + }, + { + "name": "rear-foot-board-transform", + "order": 3, + "bones": [ "rear-foot-target" ], + "target": "hoverboard-controller", + "x": 86.6, + "y": 21.3, + "mixRotate": 0, + "mixX": 0, + "mixScaleX": 0, + "mixShearY": 0 + }, + { + "name": "shoulder", + "bones": [ "back-shoulder" ], + "target": "front-shoulder", + "x": 40.17, + "y": -1.66, + "mixRotate": 0, + "mixX": -1, + "mixScaleX": 0, + "mixShearY": 0 + }, + { + "name": "toes-board", + "order": 14, + "bones": [ "front-foot-tip", "back-foot-tip" ], + "target": "hoverboard-controller", + "mixRotate": 0, + "mixX": 0, + "mixScaleX": 0, + "mixShearY": 0 + } + ], + "skins": [ + { + "name": "default", + "attachments": { + "clipping": { + "clipping": { + "type": "clipping", + "end": "head-bb", + "vertexCount": 9, + "vertices": [ 66.76, 509.48, 19.98, 434.54, 5.34, 336.28, 22.19, 247.93, 77.98, 159.54, 182.21, -97.56, 1452.26, -99.8, 1454.33, 843.61, 166.57, 841.02 ], + "color": "ce3a3aff" + } + }, + "crosshair": { + "crosshair": { "width": 89, "height": 89 } + }, + "exhaust1": { + "hoverglow-small": { "scaleX": 0.4629, "scaleY": 0.8129, "rotation": -83.07, "width": 274, "height": 75 } + }, + "exhaust2": { + "hoverglow-small": { + "x": 0.01, + "y": -0.76, + "scaleX": 0.4208, + "scaleY": 0.8403, + "rotation": -89.25, + "width": 274, + "height": 75 + } + }, + "exhaust3": { + "hoverglow-small": { "scaleX": 0.4629, "scaleY": 0.8129, "rotation": -83.07, "width": 274, "height": 75 } + }, + "eye": { + "eye-indifferent": { + "type": "mesh", + "uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ], + "triangles": [ 1, 3, 0, 1, 2, 3 ], + "vertices": [ 2, 66, -36.8, -91.35, 0.3, 46, 73.41, -91.35, 0.7, 2, 66, -87.05, -13.11, 0.70968, 46, 23.16, -13.11, 0.29032, 2, 66, -12.18, 34.99, 0.82818, 46, 98.03, 34.99, 0.17182, 2, 66, 38.07, -43.25, 0.59781, 46, 148.28, -43.25, 0.40219 ], + "hull": 4, + "edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ], + "width": 93, + "height": 89 + }, + "eye-surprised": { + "type": "mesh", + "uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ], + "triangles": [ 1, 2, 3, 1, 3, 0 ], + "vertices": [ 2, 66, -46.74, -89.7, 0.3, 46, 63.47, -89.7, 0.7, 2, 66, -77.58, -1.97, 0.71, 46, 32.63, -1.97, 0.29, 2, 66, 6.38, 27.55, 0.83, 46, 116.59, 27.55, 0.17, 2, 66, 37.22, -60.19, 0.6, 46, 147.44, -60.19, 0.4 ], + "hull": 4, + "edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ], + "width": 93, + "height": 89 + } + }, + "front-bracer": { + "front-bracer": { "x": 12.03, "y": -1.68, "rotation": 79.6, "width": 58, "height": 80 } + }, + "front-fist": { + "front-fist-closed": { "x": 35.5, "y": 6, "rotation": 67.16, "width": 75, "height": 82 }, + "front-fist-open": { "x": 39.57, "y": 7.76, "rotation": 67.16, "width": 86, "height": 87 } + }, + "front-foot": { + "front-foot": { + "type": "mesh", + "uvs": [ 0.59417, 0.23422, 0.62257, 0.30336, 0.6501, 0.37036, 0.67637, 0.38404, 0.72068, 0.4071, 0.76264, 0.42894, 1, 0.70375, 1, 1, 0.65517, 1, 0.46923, 0.99999, 0, 1, 0, 0.39197, 0.17846, 0, 0.49796, 0 ], + "triangles": [ 8, 9, 3, 4, 8, 3, 5, 8, 4, 6, 8, 5, 8, 6, 7, 11, 1, 10, 0, 12, 13, 0, 11, 12, 0, 1, 11, 9, 2, 3, 1, 2, 10, 9, 10, 2 ], + "vertices": [ 2, 38, 18.17, 41.57, 0.7896, 39, 12.46, 46.05, 0.2104, 2, 38, 24.08, 40.76, 0.71228, 39, 16.12, 41.34, 0.28772, 2, 38, 29.81, 39.98, 0.55344, 39, 19.67, 36.78, 0.44656, 2, 38, 32.81, 41.67, 0.38554, 39, 23, 35.89, 0.61446, 2, 38, 37.86, 44.52, 0.25567, 39, 28.61, 34.4, 0.74433, 2, 38, 42.65, 47.22, 0.17384, 39, 33.92, 32.99, 0.82616, 1, 39, 64.15, 14.56, 1, 1, 39, 64.51, -5.87, 1, 1, 39, 21.08, -6.64, 1, 2, 38, 44.67, -6.77, 0.5684, 39, -2.34, -6.97, 0.4316, 1, 38, 3.1, -48.81, 1, 1, 38, -26.73, -19.31, 1, 1, 38, -30.15, 15.69, 1, 1, 38, -1.84, 44.32, 1 ], + "hull": 14, + "edges": [ 14, 16, 16, 18, 18, 20, 4, 18, 20, 22, 24, 26, 22, 24, 12, 14, 10, 12, 2, 4, 2, 20, 4, 6, 6, 16, 2, 0, 0, 26, 6, 8, 8, 10 ], + "width": 126, + "height": 69 + } + }, + "front-shin": { + "front-shin": { + "type": "mesh", + "uvs": [ 0.90031, 0.05785, 1, 0.12828, 1, 0.21619, 0.9025, 0.31002, 0.78736, 0.35684, 0.78081, 0.39874, 0.77215, 0.45415, 0.77098, 0.51572, 0.84094, 0.63751, 0.93095, 0.7491, 0.95531, 0.7793, 0.78126, 0.87679, 0.5613, 1, 0.2687, 1, 0, 1, 0.00279, 0.96112, 0.01358, 0.81038, 0.02822, 0.60605, 0.08324, 0.45142, 0.18908, 0.31882, 0.29577, 0.2398, 0.30236, 0.14941, 0.37875, 0.05902, 0.53284, 0, 0.70538, 0, 0.41094, 0.71968, 0.40743, 0.54751, 0.41094, 0.4536, 0.4724, 0.35186, 0.33367, 0.27829, 0.50226, 0.31664, 0.65328, 0.67507, 0.60762, 0.52716, 0.6006, 0.45125, 0.62747, 0.37543, 0.6573, 0.3385, 0.27843, 0.32924, 0.18967, 0.45203, 0.16509, 0.58586, 0.18265, 0.7682, 0.50532, 0.24634, 0.59473, 0.17967, 0.60161, 0.10611, 0.51392, 0.04327, 0.72198, 0.28849, 0.82343, 0.20266, 0.86814, 0.11377, 0.79592, 0.04634, 0.44858, 0.15515, 0.25466, 0.96219, 0.53169, 0.9448, 0.7531, 0.8324 ], + "triangles": [ 24, 0, 47, 43, 23, 24, 47, 43, 24, 43, 22, 23, 42, 43, 47, 46, 47, 0, 42, 47, 46, 46, 0, 1, 48, 22, 43, 48, 43, 42, 21, 22, 48, 41, 48, 42, 45, 42, 46, 41, 42, 45, 46, 1, 2, 45, 46, 2, 40, 48, 41, 48, 20, 21, 29, 48, 40, 29, 20, 48, 44, 41, 45, 40, 41, 44, 3, 45, 2, 44, 45, 3, 30, 29, 40, 35, 30, 40, 36, 19, 20, 36, 20, 29, 44, 35, 40, 28, 29, 30, 4, 44, 3, 35, 44, 4, 34, 30, 35, 5, 35, 4, 34, 28, 30, 33, 28, 34, 37, 19, 36, 18, 19, 37, 27, 29, 28, 27, 28, 33, 36, 29, 27, 37, 36, 27, 5, 34, 35, 6, 34, 5, 33, 34, 6, 6, 32, 33, 7, 32, 6, 26, 37, 27, 38, 18, 37, 38, 37, 26, 17, 18, 38, 31, 32, 7, 31, 7, 8, 32, 25, 26, 38, 26, 25, 27, 33, 32, 32, 26, 27, 39, 38, 25, 17, 38, 39, 16, 17, 39, 51, 31, 8, 51, 8, 9, 11, 51, 9, 11, 9, 10, 31, 50, 25, 31, 25, 32, 50, 31, 51, 49, 39, 25, 49, 25, 50, 15, 16, 39, 49, 15, 39, 13, 49, 50, 14, 15, 49, 13, 14, 49, 12, 50, 51, 12, 51, 11, 13, 50, 12 ], + "vertices": [ -23.66, 19.37, -11.73, 28.98, 4.34, 30.83, 22.41, 24.87, 32.05, 16.48, 39.77, 16.83, 49.98, 17.3, 61.25, 18.5, 82.85, 26.78, 102.4, 36.46, 107.69, 39.09, 127.15, 26.97, 151.74, 11.65, 154.49, -12.18, 157.02, -34.07, 149.89, -34.66, 122.23, -36.97, 84.75, -40.09, 55.97, -38.88, 30.73, -33.05, 15.29, -26.03, -1.3, -27.41, -18.54, -23.09, -30.78, -11.79, -32.4, 2.27, 101.92, -6.52, 70.48, -10.44, 53.28, -12.14, 34.11, -9.28, 21.96, -22.13, 27.39, -7.59, 91.48, 12.28, 64.88, 5.44, 51.07, 3.26, 36.95, 3.85, 29.92, 5.5, 31.8, -25.56, 55.08, -30.19, 79.77, -29.37, 112.93, -24.09, 14.51, -8.83, 1.48, -2.95, -12.03, -3.94, -22.69, -12.41, 20.17, 9.71, 3.53, 16.16, -13.14, 17.93, -24.78, 10.62, -1.62, -15.37, 147.71, -14.13, 141.93, 8.07, 119.3, 23.74 ], + "hull": 25, + "edges": [ 8, 6, 6, 4, 4, 2, 2, 0, 0, 48, 46, 48, 46, 44, 44, 42, 42, 40, 40, 38, 38, 36, 36, 34, 32, 34, 50, 52, 52, 54, 54, 56, 40, 58, 58, 60, 8, 10, 20, 22, 22, 24, 62, 64, 64, 66, 66, 68, 8, 70, 70, 60, 68, 70, 58, 72, 72, 74, 74, 76, 76, 78, 24, 26, 26, 28, 58, 80, 80, 82, 82, 84, 84, 86, 86, 44, 70, 88, 88, 90, 90, 92, 92, 94, 94, 48, 80, 88, 88, 6, 82, 90, 90, 4, 84, 92, 92, 2, 86, 94, 94, 0, 56, 60, 10, 12, 12, 14, 14, 16, 28, 30, 30, 32, 26, 98, 98, 78, 30, 98, 24, 100, 100, 50, 98, 100, 22, 102, 102, 62, 100, 102, 16, 18, 18, 20, 102, 18 ], + "width": 82, + "height": 184 + } + }, + "front-thigh": { + "front-thigh": { "x": 42.48, "y": 4.45, "rotation": 84.87, "width": 45, "height": 112 } + }, + "front-upper-arm": { + "front-upper-arm": { "x": 28.31, "y": 7.37, "rotation": 97.9, "width": 46, "height": 97 } + }, + "goggles": { + "goggles": { + "type": "mesh", + "uvs": [ 0.53653, 0.04114, 0.72922, 0.16036, 0.91667, 0.33223, 0.97046, 0.31329, 1, 0.48053, 0.95756, 0.5733, 0.88825, 0.6328, 0.86878, 0.78962, 0.77404, 0.8675, 0.72628, 1, 0.60714, 0.93863, 0.49601, 0.88138, 0.41558, 0.75027, 0.32547, 0.70084, 0.2782, 0.58257, 0.1721, 0.63281, 0.17229, 0.75071, 0.10781, 0.79898, 0, 0.32304, 0, 0.12476, 0.07373, 0.07344, 0.15423, 0.10734, 0.23165, 0.13994, 0.30313, 0.02256, 0.34802, 0, 0.42979, 0.69183, 0.39476, 0.51042, 0.39488, 0.31512, 0.45878, 0.23198, 0.56501, 0.28109, 0.69961, 0.39216, 0.82039, 0.54204, 0.85738, 0.62343, 0.91107, 0.51407, 0.72639, 0.32147, 0.58764, 0.19609, 0.48075, 0.11269, 0.37823, 0.05501, 0.3287, 0.17866, 0.319, 0.305, 0.36036, 0.53799, 0.40327, 0.70072, 0.30059, 0.55838, 0.21957, 0.2815, 0.09963, 0.28943, 0.56863, 0.4368, 0.4911, 0.37156, 0.51185, 0.52093, 0.67018, 0.59304, 0.7619, 0.68575, 0.73296, 0.43355 ], + "triangles": [ 18, 44, 15, 21, 19, 20, 17, 18, 15, 44, 19, 21, 2, 3, 4, 18, 19, 44, 2, 33, 34, 33, 2, 4, 5, 33, 4, 5, 6, 33, 7, 32, 6, 31, 50, 33, 32, 31, 33, 6, 32, 33, 31, 49, 50, 49, 31, 32, 49, 32, 7, 8, 49, 7, 33, 50, 34, 17, 15, 16, 9, 48, 8, 49, 48, 50, 50, 48, 45, 47, 45, 48, 50, 45, 30, 45, 47, 46, 45, 46, 29, 30, 45, 29, 30, 29, 34, 30, 34, 50, 47, 26, 46, 25, 10, 11, 12, 25, 11, 41, 12, 42, 42, 44, 43, 43, 21, 22, 41, 40, 25, 41, 42, 40, 29, 35, 34, 40, 26, 25, 25, 26, 47, 37, 24, 0, 36, 37, 0, 42, 43, 39, 42, 39, 40, 28, 38, 36, 40, 39, 26, 28, 27, 38, 26, 39, 27, 37, 38, 23, 39, 43, 38, 38, 37, 36, 27, 39, 38, 43, 22, 38, 37, 23, 24, 22, 23, 38, 36, 0, 35, 28, 36, 35, 29, 28, 35, 27, 28, 46, 26, 27, 46, 35, 0, 1, 34, 35, 1, 12, 41, 25, 47, 10, 25, 44, 21, 43, 42, 14, 44, 14, 15, 44, 13, 14, 42, 12, 13, 42, 46, 28, 29, 47, 48, 10, 48, 9, 10, 49, 8, 48, 2, 34, 1 ], + "vertices": [ 2, 66, 61.88, 22.81, 0.832, 46, 172.09, 22.81, 0.168, 2, 66, 59.89, -31.19, 0.6855, 46, 170.1, -31.19, 0.3145, 2, 66, 49.2, -86.8, 0.32635, 46, 159.41, -86.8, 0.67365, 2, 66, 56.82, -99.01, 0.01217, 46, 167.03, -99.01, 0.98783, 1, 46, 143.4, -115.48, 1, 2, 66, 15, -110.14, 0.0041, 46, 125.21, -110.14, 0.9959, 2, 66, -0.32, -96.36, 0.07948, 46, 109.89, -96.36, 0.92052, 2, 66, -26.56, -100.19, 0.01905, 46, 83.65, -100.19, 0.98095, 2, 66, -46.96, -81.16, 0.4921, 46, 63.26, -81.16, 0.50791, 2, 66, -71.84, -76.69, 0.56923, 46, 38.37, -76.69, 0.43077, 2, 66, -72.54, -43.98, 0.74145, 46, 37.67, -43.98, 0.25855, 2, 66, -73.2, -13.47, 0.87929, 46, 37.01, -13.47, 0.12071, 2, 66, -59.63, 13.55, 0.864, 46, 50.58, 13.55, 0.136, 2, 66, -59.69, 38.45, 0.85289, 46, 50.52, 38.45, 0.14711, 2, 66, -45.26, 56.6, 0.74392, 46, 64.95, 56.6, 0.25608, 2, 66, -62.31, 79.96, 0.624, 46, 47.9, 79.96, 0.376, 2, 66, -80.76, 73.42, 0.616, 46, 29.45, 73.42, 0.384, 2, 66, -93.9, 86.64, 0.288, 46, 16.31, 86.64, 0.712, 1, 46, 81.51, 139.38, 1, 1, 46, 112.56, 150.3, 1, 2, 66, 16.76, 134.97, 0.02942, 46, 126.97, 134.97, 0.97058, 2, 66, 18.42, 113.28, 0.36147, 46, 128.63, 113.28, 0.63853, 2, 66, 20.02, 92.43, 0.7135, 46, 130.23, 92.43, 0.2865, 2, 66, 44.58, 81.29, 0.69603, 46, 154.79, 81.29, 0.30397, 2, 66, 52, 71.48, 0.848, 46, 162.21, 71.48, 0.152, 2, 66, -49.25, 13.27, 0.8, 46, 60.96, 13.27, 0.2, 2, 66, -23.88, 31.88, 0.896, 46, 86.33, 31.88, 0.104, 2, 66, 6.72, 42.6, 0.928, 46, 116.93, 42.6, 0.072, 2, 66, 25.26, 31.44, 0.8, 46, 135.47, 31.44, 0.2, 2, 66, 26.77, 2.59, 0.75, 46, 136.98, 2.59, 0.25, 2, 66, 21.02, -36.66, 0.54887, 46, 131.23, -36.66, 0.45113, 2, 66, 8.01, -74.65, 0.36029, 46, 118.22, -74.65, 0.63971, 2, 66, -1.52, -88.24, 0.1253, 46, 108.69, -88.24, 0.8747, 2, 66, 20.25, -95.44, 0.08687, 46, 130.46, -95.44, 0.91313, 2, 66, 34.42, -39.36, 0.72613, 46, 144.63, -39.36, 0.27387, 2, 66, 42.03, 1.7, 0.824, 46, 152.25, 1.7, 0.176, 2, 66, 45.85, 32.6, 0.856, 46, 156.06, 32.6, 0.144, 1, 66, 46.01, 61.02, 1, 1, 66, 22.35, 66.41, 1, 1, 66, 1.73, 61.84, 1, 2, 66, -31.17, 38.83, 0.928, 46, 79.04, 38.83, 0.072, 2, 66, -52.94, 19.31, 0.79073, 46, 57.27, 19.31, 0.20927, 2, 66, -39.54, 52.42, 0.912, 46, 70.67, 52.42, 0.088, 2, 66, -3.2, 87.61, 0.744, 46, 107.02, 87.61, 0.256, 2, 66, -14.82, 116.7, 0.6368, 46, 95.4, 116.7, 0.3632, 2, 66, 2.7, -6.87, 0.856, 46, 112.91, -6.87, 0.144, 2, 66, 6.21, 15.8, 0.744, 46, 116.42, 15.8, 0.256, 2, 66, -15.39, 2.47, 0.856, 46, 94.82, 2.47, 0.144, 2, 66, -12.98, -40.48, 0.72102, 46, 97.24, -40.48, 0.27898, 2, 66, -19.55, -68.16, 0.59162, 46, 90.66, -68.16, 0.40838, 2, 66, 17.44, -47.15, 0.53452, 46, 127.65, -47.15, 0.46548 ], + "hull": 25, + "edges": [ 36, 34, 34, 32, 32, 30, 30, 28, 28, 26, 26, 24, 24, 22, 18, 16, 16, 14, 14, 12, 12, 10, 10, 8, 8, 6, 6, 4, 4, 2, 2, 0, 0, 48, 48, 46, 46, 44, 36, 38, 40, 38, 24, 50, 50, 52, 52, 54, 54, 56, 56, 58, 58, 60, 62, 64, 64, 12, 8, 66, 66, 68, 68, 70, 70, 72, 72, 74, 74, 76, 76, 78, 78, 80, 80, 82, 82, 24, 24, 84, 84, 86, 86, 44, 40, 42, 42, 44, 42, 88, 88, 30, 58, 90, 90, 92, 92, 94, 18, 20, 20, 22, 94, 20, 18, 96, 96, 98, 60, 100, 100, 62, 98, 100 ], + "width": 261, + "height": 166 + } + }, + "gun": { + "gun": { "x": 77.3, "y": 16.4, "rotation": 60.83, "width": 210, "height": 203 } + }, + "head": { + "head": { + "type": "mesh", + "uvs": [ 0.75919, 0.06107, 0.88392, 0.17893, 0.90174, 0.30856, 0.94224, 0.1966, 1, 0.26584, 1, 0.422, 0.95864, 0.46993, 0.92118, 0.51333, 0.85957, 0.5347, 0.78388, 0.65605, 0.74384, 0.74838, 0.85116, 0.75151, 0.84828, 0.82564, 0.81781, 0.85367, 0.75599, 0.85906, 0.76237, 0.90468, 0.65875, 1, 0.38337, 1, 0.1858, 0.85404, 0.12742, 0.81091, 0.06025, 0.69209, 0, 0.58552, 0, 0.41021, 0.0853, 0.20692, 0.24243, 0.14504, 0.5, 0.1421, 0.50324, 0.07433, 0.41738, 0, 0.57614, 0, 0.85059, 0.36087, 0.73431, 0.43206, 0.68481, 0.31271, 0.72165, 0.16718, 0.55931, 0.04154, 0.44764, 0.22895, 0.23926, 0.26559, 0.71272, 0.44036, 0.56993, 0.383, 0.41678, 0.33511, 0.293, 0.31497, 0.70802, 0.44502, 0.56676, 0.38976, 0.41521, 0.34416, 0.28754, 0.33017, 0.88988, 0.50177, 0.30389, 0.73463, 0.2646, 0.65675, 0.21414, 0.61584, 0.14613, 0.62194, 0.10316, 0.66636, 0.10358, 0.72557, 0.14505, 0.79164, 0.20263, 0.81355, 0.27873, 0.80159, 0.34947, 0.7376, 0.23073, 0.57073, 0.08878, 0.60707, 0.29461, 0.8129, 0.73006, 0.87883, 0.69805, 0.87348, 0.66166, 0.79681, 0.22468, 0.69824, 0.14552, 0.67405 ], + "triangles": [ 50, 49, 62, 34, 25, 31, 39, 35, 34, 38, 39, 34, 37, 38, 34, 42, 39, 38, 43, 39, 42, 32, 2, 31, 31, 37, 34, 42, 38, 37, 41, 42, 37, 43, 22, 39, 30, 31, 29, 36, 37, 31, 30, 36, 31, 40, 41, 37, 36, 40, 37, 36, 30, 44, 55, 22, 43, 55, 48, 56, 47, 48, 55, 46, 55, 54, 42, 55, 43, 47, 55, 46, 62, 49, 48, 61, 47, 46, 62, 48, 47, 61, 62, 47, 46, 54, 45, 42, 41, 55, 61, 46, 45, 55, 41, 54, 61, 51, 50, 61, 50, 62, 60, 41, 40, 54, 41, 60, 53, 61, 45, 52, 51, 61, 57, 53, 45, 57, 45, 54, 53, 52, 61, 52, 19, 51, 57, 18, 52, 57, 52, 53, 17, 54, 60, 57, 54, 17, 18, 57, 17, 19, 50, 51, 33, 27, 28, 26, 27, 33, 0, 33, 28, 32, 33, 0, 32, 0, 1, 33, 25, 26, 33, 32, 25, 31, 25, 32, 2, 32, 1, 2, 3, 4, 29, 31, 2, 2, 4, 5, 29, 2, 5, 6, 29, 5, 30, 29, 6, 44, 30, 6, 18, 19, 52, 49, 56, 48, 34, 24, 25, 35, 23, 24, 35, 24, 34, 39, 22, 35, 22, 23, 35, 7, 44, 6, 8, 36, 44, 40, 36, 8, 8, 44, 7, 56, 21, 22, 55, 56, 22, 9, 40, 8, 20, 21, 56, 20, 56, 49, 9, 60, 40, 10, 60, 9, 20, 50, 19, 12, 10, 11, 13, 10, 12, 14, 60, 10, 13, 14, 10, 59, 60, 14, 58, 59, 14, 58, 14, 15, 16, 17, 60, 59, 16, 60, 15, 16, 59, 15, 59, 58, 20, 49, 50 ], + "vertices": [ 2, 50, 41.97, -41.8, 0.94074, 66, 165.41, -22.6, 0.05926, 4, 48, 73.47, 27.54, 0.26795, 50, -5.75, -51.71, 0.4738, 49, 112.99, -11.41, 0.12255, 66, 143.5, -66.13, 0.1357, 4, 48, 38.23, 10.99, 0.6831, 50, -41.01, -35.22, 0.07866, 49, 92.73, -44.66, 0.04872, 66, 108.65, -83.49, 0.18952, 2, 48, 73.35, 10.89, 0.8455, 66, 143.77, -82.78, 0.1545, 2, 48, 58.59, -10.38, 0.91607, 66, 129.5, -104.39, 0.08393, 3, 46, 195.82, -119.82, 0.104, 47, 75.49, -4.55, 0.09191, 48, 14.36, -24.8, 0.80409, 4, 46, 178.62, -113.98, 0.19022, 47, 59.82, -13.72, 0.33409, 48, -2.7, -18.57, 0.46643, 66, 68.41, -113.98, 0.00926, 4, 46, 163.06, -108.69, 0.18724, 47, 45.64, -22.03, 0.3133, 48, -18.14, -12.93, 0.47469, 66, 52.84, -108.69, 0.02477, 2, 46, 151.52, -95.05, 0.91122, 66, 41.31, -95.05, 0.08878, 2, 46, 110.61, -87.69, 0.70564, 66, 0.4, -87.69, 0.29436, 2, 46, 81.05, -86.58, 0.63951, 66, -29.16, -86.58, 0.36049, 2, 46, 89.82, -114.32, 0.57, 66, -20.39, -114.32, 0.43, 2, 46, 68.72, -120.91, 0.57, 66, -41.49, -120.91, 0.43, 2, 46, 58.1, -115.9, 0.57, 66, -52.11, -115.9, 0.43, 2, 46, 51.03, -100.63, 0.64242, 66, -59.18, -100.63, 0.35758, 2, 46, 38.79, -106.76, 0.81659, 66, -71.43, -106.76, 0.18341, 2, 46, 2.68, -89.7, 0.77801, 66, -107.53, -89.7, 0.22199, 2, 46, -22.07, -19.3, 0.823, 66, -132.28, -19.3, 0.177, 2, 46, 1.2, 45.63, 0.51204, 66, -109.01, 45.63, 0.48796, 2, 46, 8.07, 64.81, 0.60869, 66, -102.14, 64.81, 0.39131, 2, 46, 35.44, 93.73, 0.80009, 66, -74.77, 93.73, 0.19991, 2, 46, 59.98, 119.66, 0.93554, 66, -50.23, 119.66, 0.06446, 2, 46, 109.26, 136.99, 0.99895, 66, -0.95, 136.99, 0.00105, 1, 46, 174.07, 135.27, 1, 3, 46, 205.59, 101.22, 0.80778, 49, -16.84, 104.63, 0.15658, 66, 95.38, 101.22, 0.03564, 3, 50, 58.94, 30.5, 0.43491, 49, 38.36, 61.89, 0.28116, 66, 119.35, 35.65, 0.28393, 2, 50, 75.56, 19.01, 0.92164, 66, 138.68, 41.52, 0.07836, 1, 50, 106.7, 26.9, 1, 1, 50, 83.79, -9.51, 1, 5, 47, 44.51, 27.24, 0.15139, 48, 19.12, 19.33, 0.44847, 50, -46.82, -15.19, 0.05757, 49, 72.19, -48.24, 0.1149, 66, 89.35, -75.58, 0.22767, 3, 47, 7.42, 19.08, 0.37772, 49, 34.32, -45.24, 0.09918, 66, 58.9, -52.89, 0.52311, 2, 49, 45.94, -9.07, 0.4826, 66, 87.99, -28.45, 0.5174, 2, 50, 20.62, -16.35, 0.7435, 66, 132.21, -23.49, 0.2565, 2, 50, 75.74, 0.94, 0.97172, 66, 152.95, 30.42, 0.02828, 4, 46, 200.45, 40.46, 0.18809, 50, 44.6, 56.29, 0.05831, 49, 11.15, 50.46, 0.14366, 66, 90.24, 40.46, 0.60994, 2, 46, 171.41, 90.12, 0.48644, 66, 61.2, 90.12, 0.51356, 2, 46, 164.84, -48.18, 0.43217, 66, 54.62, -48.18, 0.56783, 4, 46, 168.13, -6.02, 0.01949, 47, -28.65, 49.02, 0.02229, 49, 8.54, -6.09, 0.12791, 66, 57.92, -6.02, 0.83031, 2, 46, 167.84, 37.87, 0.15, 66, 57.63, 37.87, 0.85, 2, 46, 162.36, 71.5, 0.24107, 66, 52.15, 71.5, 0.75893, 2, 46, 163.11, -47.44, 0.41951, 66, 52.9, -47.44, 0.58049, 2, 46, 165.94, -5.87, 0.16355, 66, 55.73, -5.87, 0.83645, 2, 46, 165.14, 37.38, 0.15, 66, 54.93, 37.38, 0.85, 2, 46, 157.6, 71.4, 0.21735, 66, 47.39, 71.4, 0.78265, 3, 46, 163.5, -99.54, 0.61812, 47, 39.01, -15.71, 0.30445, 66, 53.29, -99.54, 0.07744, 2, 46, 45.38, 27.24, 0.16741, 66, -64.83, 27.24, 0.83259, 2, 46, 63.74, 44.98, 0.15, 66, -46.47, 44.98, 0.85, 2, 46, 70.7, 61.92, 0.22175, 66, -39.51, 61.92, 0.77825, 2, 46, 62.88, 78.71, 0.38, 66, -47.34, 78.71, 0.62, 2, 46, 46.53, 85.3, 0.51, 66, -63.68, 85.3, 0.49, 2, 46, 29.92, 79.34, 0.388, 66, -80.29, 79.34, 0.612, 2, 46, 15.08, 62.21, 0.38, 66, -95.13, 62.21, 0.62, 2, 46, 14.09, 45.32, 0.41, 66, -96.12, 45.32, 0.59, 2, 46, 24.3, 27.06, 0.192, 66, -85.91, 27.06, 0.808, 1, 66, -61.57, 15.3, 1, 2, 46, 84.87, 62.14, 0.16757, 66, -25.34, 62.14, 0.83243, 2, 46, 61.9, 94.84, 0.68145, 66, -48.31, 94.84, 0.31855, 2, 46, 22.54, 21.88, 0.16, 66, -87.67, 21.88, 0.84, 2, 46, 43.15, -95.95, 0.73445, 66, -67.06, -95.95, 0.26555, 2, 46, 41.77, -87.24, 0.67858, 66, -68.44, -87.24, 0.32142, 2, 46, 60.05, -70.36, 0.50195, 66, -50.16, -70.36, 0.49805, 2, 46, 48.49, 51.09, 0.25, 66, -61.72, 51.09, 0.75, 2, 46, 48.17, 73.71, 0.15634, 66, -62.04, 73.71, 0.84366 ], + "hull": 29, + "edges": [ 10, 8, 8, 6, 6, 4, 4, 2, 2, 0, 0, 56, 54, 56, 54, 52, 52, 50, 50, 48, 48, 46, 46, 44, 42, 44, 32, 34, 4, 58, 58, 60, 62, 64, 64, 66, 66, 54, 50, 68, 68, 70, 70, 44, 60, 72, 62, 74, 72, 74, 74, 76, 76, 78, 78, 44, 16, 80, 80, 82, 82, 84, 84, 86, 86, 44, 14, 88, 88, 72, 14, 16, 10, 12, 12, 14, 12, 60, 90, 92, 92, 94, 94, 96, 96, 98, 98, 100, 100, 102, 102, 104, 104, 106, 106, 90, 108, 110, 110, 112, 38, 40, 40, 42, 112, 40, 34, 36, 36, 38, 36, 114, 114, 108, 30, 32, 30, 28, 24, 26, 28, 26, 22, 24, 22, 20, 20, 18, 18, 16, 28, 116, 116, 118, 118, 120, 120, 20 ], + "width": 271, + "height": 298 + } + }, + "head-bb": { + "head": { + "type": "boundingbox", + "vertexCount": 6, + "vertices": [ -19.14, -70.3, 40.8, -118.08, 257.78, -115.62, 285.17, 57.18, 120.77, 164.95, -5.07, 76.95 ] + } + }, + "hoverboard-board": { + "hoverboard-board": { + "type": "mesh", + "uvs": [ 0.13865, 0.56624, 0.11428, 0.51461, 0.07619, 0.52107, 0.02364, 0.52998, 0.01281, 0.53182, 0, 0.37979, 0, 0.2206, 0.00519, 0.10825, 0.01038, 0.10726, 0.03834, 0.10194, 0.05091, 0, 0.08326, 0, 0.10933, 0.04206, 0.1382, 0.08865, 0.18916, 0.24067, 0.22234, 0.4063, 0.23886, 0.44063, 0.83412, 0.44034, 0.88444, 0.38296, 0.92591, 0.32639, 0.95996, 0.28841, 0.98612, 0.28542, 1, 0.38675, 0.99494, 0.47104, 0.97883, 0.53251, 0.94409, 0.62135, 0.90206, 0.69492, 0.86569, 0.71094, 0.82822, 0.70791, 0.81286, 0.77127, 0.62931, 0.77266, 0.61364, 0.70645, 0.47166, 0.70664, 0.45901, 0.77827, 0.27747, 0.76986, 0.2658, 0.70372, 0.24976, 0.71381, 0.24601, 0.77827, 0.23042, 0.84931, 0.20926, 0.90956, 0.17299, 1, 0.15077, 0.99967, 0.12906, 0.90192, 0.10369, 0.73693, 0.10198, 0.62482, 0.09131, 0.47272, 0.09133, 0.41325, 0.15082, 0.41868, 0.21991, 0.51856, 0.06331, 0.10816, 0.08383, 0.21696, 0.08905, 0.37532, 0.15903, 0.58726, 0.17538, 0.65706, 0.20118, 0.8029, 0.17918, 0.55644, 0.22166, 0.5802, 0.86259, 0.57962, 0.92346, 0.48534, 0.96691, 0.36881, 0.0945, 0.13259, 0.12688, 0.17831, 0.15986, 0.24682, 0.18036, 0.31268, 0.20607, 0.4235, 0.16074, 0.85403, 0.13624, 0.70122, 0.12096, 0.64049, 0.02396, 0.21811, 0.02732, 0.37839, 0.02557, 0.4972, 0.14476, 0.45736, 0.18019, 0.51689, 0.19692, 0.56636 ], + "triangles": [ 10, 11, 12, 9, 10, 12, 49, 9, 12, 60, 49, 12, 13, 60, 12, 61, 60, 13, 50, 49, 60, 50, 60, 61, 68, 8, 9, 68, 9, 49, 68, 49, 50, 7, 8, 68, 6, 7, 68, 61, 13, 14, 62, 61, 14, 50, 61, 62, 63, 62, 14, 59, 20, 21, 19, 20, 59, 51, 50, 62, 51, 62, 63, 51, 69, 68, 51, 68, 50, 6, 68, 69, 5, 6, 69, 18, 19, 59, 15, 63, 14, 59, 21, 22, 47, 51, 63, 47, 46, 51, 47, 63, 64, 15, 64, 63, 64, 15, 16, 71, 46, 47, 23, 59, 22, 69, 51, 70, 45, 46, 71, 70, 51, 2, 58, 18, 59, 58, 59, 23, 17, 18, 58, 70, 5, 69, 2, 51, 46, 1, 45, 71, 47, 48, 71, 47, 64, 48, 48, 72, 71, 1, 71, 72, 16, 48, 64, 45, 2, 46, 2, 45, 1, 70, 4, 5, 3, 70, 2, 3, 4, 70, 24, 58, 23, 72, 0, 1, 73, 55, 72, 55, 0, 72, 48, 73, 72, 57, 17, 58, 25, 57, 58, 56, 48, 16, 73, 48, 56, 56, 16, 17, 56, 17, 57, 52, 0, 55, 24, 25, 58, 44, 0, 52, 67, 44, 52, 52, 56, 53, 73, 52, 55, 56, 52, 73, 67, 52, 53, 26, 57, 25, 66, 67, 53, 56, 32, 35, 53, 56, 35, 56, 57, 32, 28, 31, 57, 57, 31, 32, 57, 27, 28, 26, 27, 57, 36, 53, 35, 43, 44, 67, 43, 67, 66, 34, 35, 32, 29, 31, 28, 30, 31, 29, 53, 54, 66, 53, 36, 54, 33, 34, 32, 37, 54, 36, 65, 43, 66, 38, 54, 37, 54, 65, 66, 39, 65, 54, 42, 43, 65, 38, 39, 54, 40, 42, 65, 40, 41, 42, 65, 39, 40 ], + "vertices": [ -189.36, 15.62, -201.35, 23.47, -220.09, 22.49, -245.95, 21.13, -251.28, 20.86, -257.58, 43.96, -257.57, 68.16, -255.02, 85.24, -252.47, 85.39, -238.71, 86.2, -232.52, 101.69, -216.61, 101.69, -203.78, 95.3, -189.58, 88.21, -164.51, 65.1, -148.19, 39.93, -140.06, 34.71, 152.82, 34.73, 177.57, 43.45, 197.97, 52.05, 214.72, 57.82, 227.6, 58.27, 234.42, 42.87, 231.94, 30.06, 224.01, 20.72, 206.91, 7.21, 186.23, -3.97, 168.34, -6.4, 149.9, -5.94, 142.35, -15.57, 52.04, -15.77, 44.33, -5.71, -25.52, -5.73, -31.75, -16.62, -121.07, -15.34, -126.81, -5.28, -134.7, -6.81, -136.54, -16.61, -144.22, -27.41, -154.63, -36.57, -172.47, -50.31, -183.41, -50.26, -194.09, -35.4, -206.56, -10.32, -207.4, 6.72, -212.65, 29.84, -212.64, 38.88, -183.37, 38.05, -149.38, 22.86, -226.43, 85.25, -216.33, 68.71, -213.76, 44.64, -179.34, 12.42, -171.29, 1.81, -158.6, -20.36, -169.42, 17.11, -148.52, 13.49, 166.82, 13.56, 196.76, 27.89, 218.14, 45.6, -211.08, 81.54, -195.15, 74.59, -178.93, 64.17, -168.84, 54.16, -156.19, 37.31, -178.5, -28.13, -190.55, -4.9, -198.07, 4.33, -245.79, 68.54, -244.14, 44.18, -245, 26.12, -186.36, 32.17, -168.92, 23.12, -160.69, 15.6 ], + "hull": 45, + "edges": [ 0, 2, 8, 10, 10, 12, 12, 14, 18, 20, 20, 22, 26, 28, 28, 30, 30, 32, 32, 34, 34, 36, 36, 38, 38, 40, 40, 42, 42, 44, 44, 46, 46, 48, 48, 50, 50, 52, 52, 54, 54, 56, 56, 58, 58, 60, 60, 62, 62, 64, 64, 66, 66, 68, 68, 70, 70, 72, 72, 74, 80, 82, 82, 84, 84, 86, 86, 88, 0, 88, 2, 90, 90, 92, 92, 94, 94, 96, 96, 32, 18, 98, 98, 100, 100, 102, 2, 4, 102, 4, 92, 102, 0, 104, 104, 106, 106, 108, 78, 80, 108, 78, 74, 76, 76, 78, 62, 56, 64, 70, 0, 110, 112, 114, 114, 116, 116, 118, 118, 42, 50, 116, 114, 34, 98, 120, 120, 122, 22, 24, 24, 26, 120, 24, 122, 124, 124, 126, 126, 128, 128, 96, 80, 130, 130, 132, 132, 134, 134, 88, 14, 16, 16, 18, 136, 16, 136, 138, 138, 140, 4, 6, 6, 8, 140, 6, 96, 112, 92, 142, 142, 144, 110, 146, 146, 112, 144, 146 ], + "width": 492, + "height": 152 + } + }, + "hoverboard-thruster-front": { + "hoverboard-thruster": { "x": 0.02, "y": -7.08, "rotation": 0.17, "width": 60, "height": 64 } + }, + "hoverboard-thruster-rear": { + "hoverboard-thruster": { "x": 1.1, "y": -6.29, "rotation": 0.17, "width": 60, "height": 64 } + }, + "hoverglow-front": { + "hoverglow-small": { + "x": 2.13, + "y": -2, + "scaleX": 0.303, + "scaleY": 0.495, + "rotation": 0.15, + "width": 274, + "height": 75 + } + }, + "hoverglow-rear": { + "hoverglow-small": { + "x": 1.39, + "y": -2.09, + "scaleX": 0.303, + "scaleY": 0.495, + "rotation": 0.61, + "width": 274, + "height": 75 + } + }, + "mouth": { + "mouth-grind": { + "type": "mesh", + "uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ], + "triangles": [ 1, 3, 0, 1, 2, 3 ], + "vertices": [ 2, 66, -98.93, -85.88, 0.22, 46, 11.28, -85.88, 0.78, 2, 66, -129.77, 1.84, 0.6, 46, -19.56, 1.84, 0.4, 2, 66, -74.12, 21.41, 0.6, 46, 36.09, 21.41, 0.4, 2, 66, -43.28, -66.32, 0.4, 46, 66.93, -66.32, 0.6 ], + "hull": 4, + "edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ], + "width": 93, + "height": 59 + }, + "mouth-oooo": { + "type": "mesh", + "uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ], + "triangles": [ 1, 3, 0, 1, 2, 3 ], + "vertices": [ 2, 46, 11.28, -85.89, 0.22, 66, -98.93, -85.89, 0.78, 2, 46, -19.56, 1.85, 0.6, 66, -129.78, 1.85, 0.4, 2, 46, 36.1, 21.42, 0.6, 66, -74.12, 21.42, 0.4, 2, 46, 66.94, -66.32, 0.4, 66, -43.27, -66.32, 0.6 ], + "hull": 4, + "edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ], + "width": 93, + "height": 59 + }, + "mouth-smile": { + "type": "mesh", + "uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ], + "triangles": [ 1, 3, 0, 1, 2, 3 ], + "vertices": [ 2, 66, -98.93, -85.89, 0.21075, 46, 11.28, -85.89, 0.78925, 2, 66, -129.77, 1.85, 0.6, 46, -19.56, 1.85, 0.4, 2, 66, -74.11, 21.42, 0.6, 46, 36.1, 21.42, 0.4, 2, 66, -43.27, -66.32, 0.40772, 46, 66.94, -66.32, 0.59228 ], + "hull": 4, + "edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ], + "width": 93, + "height": 59 + } + }, + "muzzle": { + "muzzle01": { + "x": 151.97, + "y": 5.81, + "scaleX": 3.7361, + "scaleY": 3.7361, + "rotation": 0.15, + "width": 133, + "height": 79 + }, + "muzzle02": { + "x": 187.25, + "y": 5.9, + "scaleX": 4.0623, + "scaleY": 4.0623, + "rotation": 0.15, + "width": 135, + "height": 84 + }, + "muzzle03": { + "x": 231.96, + "y": 6.02, + "scaleX": 4.1325, + "scaleY": 4.1325, + "rotation": 0.15, + "width": 166, + "height": 106 + }, + "muzzle04": { + "x": 231.96, + "y": 6.02, + "scaleX": 4.0046, + "scaleY": 4.0046, + "rotation": 0.15, + "width": 149, + "height": 90 + }, + "muzzle05": { + "x": 293.8, + "y": 6.19, + "scaleX": 4.4673, + "scaleY": 4.4673, + "rotation": 0.15, + "width": 135, + "height": 75 + } + }, + "muzzle-glow": { + "muzzle-glow": { "width": 50, "height": 50 } + }, + "muzzle-ring": { + "muzzle-ring": { "x": -1.3, "y": 0.32, "scaleX": 0.3147, "scaleY": 0.3147, "width": 49, "height": 209 } + }, + "muzzle-ring2": { + "muzzle-ring": { "x": -1.3, "y": 0.32, "scaleX": 0.3147, "scaleY": 0.3147, "width": 49, "height": 209 } + }, + "muzzle-ring3": { + "muzzle-ring": { "x": -1.3, "y": 0.32, "scaleX": 0.3147, "scaleY": 0.3147, "width": 49, "height": 209 } + }, + "muzzle-ring4": { + "muzzle-ring": { "x": -1.3, "y": 0.32, "scaleX": 0.3147, "scaleY": 0.3147, "width": 49, "height": 209 } + }, + "neck": { + "neck": { "x": 9.77, "y": -3.01, "rotation": -55.22, "width": 36, "height": 41 } + }, + "portal-bg": { + "portal-bg": { "x": -3.1, "y": 7.25, "scaleX": 1.0492, "scaleY": 1.0492, "width": 266, "height": 266 } + }, + "portal-flare1": { + "portal-flare1": { "width": 111, "height": 60 }, + "portal-flare2": { "width": 114, "height": 61 }, + "portal-flare3": { "width": 115, "height": 59 } + }, + "portal-flare2": { + "portal-flare1": { "width": 111, "height": 60 }, + "portal-flare2": { "width": 114, "height": 61 }, + "portal-flare3": { "width": 115, "height": 59 } + }, + "portal-flare3": { + "portal-flare1": { "width": 111, "height": 60 }, + "portal-flare2": { "width": 114, "height": 61 }, + "portal-flare3": { "width": 115, "height": 59 } + }, + "portal-flare4": { + "portal-flare1": { "width": 111, "height": 60 }, + "portal-flare2": { "width": 114, "height": 61 }, + "portal-flare3": { "width": 115, "height": 59 } + }, + "portal-flare5": { + "portal-flare1": { "width": 111, "height": 60 }, + "portal-flare2": { "width": 114, "height": 61 }, + "portal-flare3": { "width": 115, "height": 59 } + }, + "portal-flare6": { + "portal-flare1": { "width": 111, "height": 60 }, + "portal-flare2": { "width": 114, "height": 61 }, + "portal-flare3": { "width": 115, "height": 59 } + }, + "portal-flare7": { + "portal-flare1": { "width": 111, "height": 60 }, + "portal-flare2": { "width": 114, "height": 61 }, + "portal-flare3": { "width": 115, "height": 59 } + }, + "portal-flare8": { + "portal-flare1": { "width": 111, "height": 60 }, + "portal-flare2": { "width": 114, "height": 61 }, + "portal-flare3": { "width": 115, "height": 59 } + }, + "portal-flare9": { + "portal-flare1": { "width": 111, "height": 60 }, + "portal-flare2": { "width": 114, "height": 61 }, + "portal-flare3": { "width": 115, "height": 59 } + }, + "portal-flare10": { + "portal-flare1": { "width": 111, "height": 60 }, + "portal-flare2": { "width": 114, "height": 61 }, + "portal-flare3": { "width": 115, "height": 59 } + }, + "portal-shade": { + "portal-shade": { "width": 266, "height": 266 } + }, + "portal-streaks1": { + "portal-streaks1": { "scaleX": 0.9774, "scaleY": 0.9774, "width": 252, "height": 256 } + }, + "portal-streaks2": { + "portal-streaks2": { "x": -1.64, "y": 2.79, "width": 250, "height": 249 } + }, + "rear-bracer": { + "rear-bracer": { "x": 11.15, "y": -2.2, "rotation": 66.17, "width": 56, "height": 72 } + }, + "rear-foot": { + "rear-foot": { + "type": "mesh", + "uvs": [ 0.48368, 0.1387, 0.51991, 0.21424, 0.551, 0.27907, 0.58838, 0.29816, 0.63489, 0.32191, 0.77342, 0.39267, 1, 0.73347, 1, 1, 0.54831, 0.99883, 0.31161, 1, 0, 1, 0, 0.41397, 0.13631, 0, 0.41717, 0 ], + "triangles": [ 8, 3, 4, 8, 4, 5, 8, 5, 6, 8, 6, 7, 11, 1, 10, 3, 9, 2, 2, 10, 1, 12, 13, 0, 0, 11, 12, 1, 11, 0, 2, 9, 10, 3, 8, 9 ], + "vertices": [ 2, 8, 10.45, 29.41, 0.90802, 9, -6.74, 49.62, 0.09198, 2, 8, 16.56, 29.27, 0.84259, 9, -2.65, 45.09, 0.15741, 2, 8, 21.8, 29.15, 0.69807, 9, 0.85, 41.2, 0.30193, 2, 8, 25.53, 31.43, 0.52955, 9, 5.08, 40.05, 0.47045, 2, 8, 30.18, 34.27, 0.39303, 9, 10.33, 38.62, 0.60697, 2, 8, 44.02, 42.73, 0.27525, 9, 25.98, 34.36, 0.72475, 2, 8, 76.47, 47.28, 0.21597, 9, 51.56, 13.9, 0.78403, 2, 8, 88.09, 36.29, 0.28719, 9, 51.55, -2.09, 0.71281, 2, 8, 52.94, -0.73, 0.47576, 9, 0.52, -1.98, 0.52424, 2, 8, 34.63, -20.23, 0.68757, 9, -26.23, -2.03, 0.31243, 2, 8, 10.44, -45.81, 0.84141, 9, -61.43, -2, 0.15859, 2, 8, -15.11, -21.64, 0.93283, 9, -61.4, 33.15, 0.06717, 1, 8, -22.57, 6.61, 1, 1, 8, -0.76, 29.67, 1 ], + "hull": 14, + "edges": [ 14, 12, 10, 12, 14, 16, 16, 18, 18, 20, 4, 18, 20, 22, 24, 26, 22, 24, 4, 2, 2, 20, 4, 6, 6, 16, 6, 8, 8, 10, 2, 0, 0, 26 ], + "width": 113, + "height": 60 + } + }, + "rear-shin": { + "rear-shin": { "x": 58.29, "y": -2.75, "rotation": 92.37, "width": 75, "height": 178 } + }, + "rear-thigh": { + "rear-thigh": { "x": 33.11, "y": -4.11, "rotation": 72.54, "width": 55, "height": 94 } + }, + "rear-upper-arm": { + "rear-upper-arm": { "x": 21.13, "y": 4.09, "rotation": 89.33, "width": 40, "height": 87 } + }, + "side-glow1": { + "hoverglow-small": { "x": 2.09, "scaleX": 0.2353, "scaleY": 0.4132, "width": 274, "height": 75 } + }, + "side-glow2": { + "hoverglow-small": { "x": 2.09, "scaleX": 0.2353, "scaleY": 0.4132, "width": 274, "height": 75 } + }, + "side-glow3": { + "hoverglow-small": { "x": 2.09, "scaleX": 0.3586, "scaleY": 0.6297, "width": 274, "height": 75 } + }, + "torso": { + "torso": { + "type": "mesh", + "uvs": [ 0.6251, 0.12672, 1, 0.26361, 1, 0.28871, 1, 0.66021, 1, 0.68245, 0.92324, 0.69259, 0.95116, 0.84965, 0.77124, 1, 0.49655, 1, 0.27181, 1, 0.13842, 0.77196, 0.09886, 0.6817, 0.05635, 0.58471, 0, 0.45614, 0, 0.33778, 0, 0.19436, 0.14463, 0, 0.27802, 0, 0.72525, 0.27835, 0.76091, 0.46216, 0.84888, 0.67963, 0.68257, 0.63249, 0.53986, 0.3847, 0.25443, 0.3217, 0.30063, 0.55174, 0.39553, 0.79507, 0.26389, 0.17007, 0.5241, 0.18674, 0.71492, 0.76655, 0.82151, 0.72956, 0.27626, 0.4304, 0.62327, 0.52952, 0.3455, 0.66679, 0.53243, 0.2914 ], + "triangles": [ 18, 1, 2, 19, 2, 3, 18, 0, 1, 23, 15, 26, 27, 26, 16, 14, 15, 23, 15, 16, 26, 17, 27, 16, 13, 14, 23, 0, 27, 17, 13, 23, 30, 11, 12, 24, 21, 31, 19, 12, 13, 30, 24, 22, 31, 31, 22, 19, 12, 30, 24, 32, 24, 31, 24, 30, 22, 3, 20, 19, 32, 31, 21, 11, 24, 32, 4, 5, 3, 8, 28, 7, 7, 29, 6, 7, 28, 29, 9, 25, 8, 8, 25, 28, 9, 10, 25, 29, 5, 6, 10, 32, 25, 25, 21, 28, 25, 32, 21, 10, 11, 32, 28, 21, 29, 29, 20, 5, 29, 21, 20, 5, 20, 3, 20, 21, 19, 33, 26, 27, 22, 18, 19, 19, 18, 2, 33, 27, 18, 30, 23, 22, 22, 33, 18, 23, 33, 22, 33, 23, 26, 27, 0, 18 ], + "vertices": [ 2, 29, 44.59, -10.39, 0.88, 40, -17.65, 33.99, 0.12, 3, 28, 59.65, -45.08, 0.12189, 29, 17.13, -45.08, 0.26811, 40, 22.68, 15.82, 0.61, 3, 28, 55.15, -44.72, 0.1345, 29, 12.63, -44.72, 0.2555, 40, 23.43, 11.37, 0.61, 3, 27, 31.01, -39.45, 0.51133, 28, -11.51, -39.45, 0.30867, 40, 34.58, -54.57, 0.18, 3, 27, 27.01, -39.14, 0.53492, 28, -15.5, -39.14, 0.28508, 40, 35.25, -58.52, 0.18, 2, 27, 25.79, -31.5, 0.75532, 28, -16.73, -31.5, 0.24468, 1, 27, -2.61, -32, 1, 1, 27, -28.2, -12.29, 1, 1, 27, -26.08, 14.55, 1, 1, 27, -24.35, 36.5, 1, 2, 27, 17.6, 46.3, 0.8332, 28, -24.92, 46.3, 0.1668, 2, 27, 34.1, 48.89, 0.59943, 28, -8.42, 48.89, 0.40058, 3, 27, 51.83, 51.67, 0.29262, 28, 9.32, 51.67, 0.63181, 29, -33.2, 51.67, 0.07557, 3, 27, 75.34, 55.35, 0.06656, 28, 32.82, 55.35, 0.62298, 29, -9.7, 55.35, 0.31046, 2, 28, 54.06, 53.67, 0.37296, 29, 11.54, 53.67, 0.62704, 2, 28, 79.79, 51.64, 0.10373, 29, 37.27, 51.64, 0.89627, 1, 29, 71.04, 34.76, 1, 1, 29, 70.01, 21.72, 1, 1, 30, 36.74, 7.06, 1, 3, 30, 45.7, -24.98, 0.67, 28, 25.87, -18.9, 0.3012, 29, -16.65, -18.9, 0.0288, 2, 27, 28.69, -24.42, 0.77602, 28, -13.83, -24.42, 0.22398, 3, 30, 43.24, -56.49, 0.064, 27, 38.43, -8.84, 0.67897, 28, -4.09, -8.84, 0.25703, 3, 30, 22.02, -14.85, 0.29, 28, 41.48, 1.59, 0.53368, 29, -1.04, 1.59, 0.17632, 3, 30, -7.45, -8.33, 0.76, 28, 54.98, 28.59, 0.06693, 29, 12.46, 28.59, 0.17307, 3, 30, 3.91, -48.4, 0.25, 27, 55.87, 27.33, 0.15843, 28, 13.35, 27.33, 0.59157, 1, 27, 11.47, 21.51, 1, 2, 30, -11.09, 18.74, 0.416, 29, 39.6, 25.51, 0.584, 2, 30, 14.56, 20.03, 0.53, 29, 34.6, 0.33, 0.47, 1, 27, 14.12, -10.1, 1, 2, 27, 19.94, -21.03, 0.92029, 28, -22.58, -21.03, 0.07971, 3, 30, -2.08, -27.26, 0.29, 28, 35.31, 27.99, 0.49582, 29, -7.21, 27.99, 0.21418, 2, 30, 34.42, -39.19, 0.25, 28, 14.84, -4.5, 0.75, 2, 27, 34.87, 24.58, 0.67349, 28, -7.64, 24.58, 0.32651, 2, 30, 18.5, 1.59, 0.76, 29, 15.76, 1, 0.24 ], + "hull": 18, + "edges": [ 14, 12, 12, 10, 10, 8, 18, 20, 32, 34, 30, 32, 2, 4, 36, 4, 36, 38, 38, 40, 4, 6, 6, 8, 40, 6, 40, 42, 14, 16, 16, 18, 50, 16, 46, 52, 54, 36, 2, 0, 0, 34, 54, 0, 54, 32, 20, 50, 14, 56, 56, 42, 50, 56, 56, 58, 58, 40, 58, 10, 46, 60, 60, 48, 26, 60, 60, 44, 24, 26, 24, 48, 42, 62, 62, 44, 48, 62, 48, 64, 64, 50, 42, 64, 20, 22, 22, 24, 64, 22, 26, 28, 28, 30, 28, 46, 44, 66, 66, 54, 46, 66, 66, 36, 62, 38 ], + "width": 98, + "height": 180 + } + } + } + } + ], + "events": { + "footstep": {} + }, + "animations": { + "aim": { + "slots": { + "crosshair": { + "attachment": [ + { "name": "crosshair" } + ] + } + }, + "bones": { + "front-fist": { + "rotate": [ + { "value": 36.08 } + ] + }, + "rear-bracer": { + "rotate": [ + { "value": -26.55 } + ] + }, + "rear-upper-arm": { + "rotate": [ + { "value": 62.31 } + ] + }, + "front-bracer": { + "rotate": [ + { "value": 9.11 } + ] + }, + "gun": { + "rotate": [ + { "value": -0.31 } + ] + } + }, + "ik": { + "aim-ik": [ + { "mix": 0.995 } + ] + }, + "transform": { + "aim-front-arm-transform": [ + { "mixRotate": 0.784, "mixX": 0, "mixScaleX": 0, "mixShearY": 0 } + ], + "aim-head-transform": [ + { "mixRotate": 0.659, "mixX": 0, "mixScaleX": 0, "mixShearY": 0 } + ], + "aim-torso-transform": [ + { "mixRotate": 0.423, "mixX": 0, "mixScaleX": 0, "mixShearY": 0 } + ] + } + }, + "death": { + "slots": { + "eye": { + "attachment": [ + { "name": "eye-surprised" }, + { "time": 0.5333, "name": "eye-indifferent" }, + { "time": 2.2, "name": "eye-surprised" }, + { "time": 4.6, "name": "eye-indifferent" } + ] + }, + "front-fist": { + "attachment": [ + { "name": "front-fist-open" } + ] + }, + "mouth": { + "attachment": [ + { "name": "mouth-oooo" }, + { "time": 0.5333, "name": "mouth-grind" }, + { "time": 1.4, "name": "mouth-oooo" }, + { "time": 2.1667, "name": "mouth-grind" }, + { "time": 4.5333, "name": "mouth-oooo" } + ] + } + }, + "bones": { + "head": { + "rotate": [ + { + "value": -2.83, + "curve": [ 0.015, -2.83, 0.036, 12.72 ] + }, + { + "time": 0.0667, + "value": 12.19, + "curve": [ 0.096, 11.68, 0.119, -1.14 ] + }, + { + "time": 0.1333, + "value": -6.86, + "curve": [ 0.149, -13.27, 0.21, -37.28 ] + }, + { + "time": 0.3, + "value": -36.86, + "curve": [ 0.354, -36.61, 0.412, -32.35 ] + }, + { + "time": 0.4667, + "value": -23.49, + "curve": [ 0.49, -19.87, 0.512, -3.29 ] + }, + { + "time": 0.5333, + "value": -3.24, + "curve": [ 0.56, -3.39, 0.614, -67.25 ] + }, + { + "time": 0.6333, + "value": -74.4, + "curve": [ 0.652, -81.58, 0.702, -88.94 ] + }, + { + "time": 0.7333, + "value": -88.93, + "curve": [ 0.805, -88.91, 0.838, -80.87 ] + }, + { + "time": 0.8667, + "value": -81.03, + "curve": [ 0.922, -81.32, 0.976, -85.29 ] + }, + { "time": 1, "value": -85.29, "curve": "stepped" }, + { + "time": 2.2333, + "value": -85.29, + "curve": [ 2.314, -85.29, 2.382, -68.06 ] + }, + { + "time": 2.4667, + "value": -63.48, + "curve": [ 2.57, -57.87, 2.916, -55.24 ] + }, + { + "time": 3.2, + "value": -55.1, + "curve": [ 3.447, -54.98, 4.135, -56.61 ] + }, + { + "time": 4.2667, + "value": -58.23, + "curve": [ 4.672, -63.24, 4.646, -82.69 ] + }, + { "time": 4.9333, "value": -85.29 } + ], + "scale": [ + { + "time": 0.4667, + "curve": [ 0.469, 1.005, 0.492, 1.065, 0.475, 1.018, 0.492, 0.94 ] + }, + { + "time": 0.5, + "x": 1.065, + "y": 0.94, + "curve": [ 0.517, 1.065, 0.541, 0.991, 0.517, 0.94, 0.542, 1.026 ] + }, + { + "time": 0.5667, + "x": 0.99, + "y": 1.025, + "curve": [ 0.593, 0.988, 0.609, 1.002, 0.595, 1.024, 0.607, 1.001 ] + }, + { "time": 0.6333 } + ] + }, + "neck": { + "rotate": [ + { + "value": -2.83, + "curve": [ 0.114, 1.33, 0.195, 4.13 ] + }, + { + "time": 0.2667, + "value": 4.13, + "curve": [ 0.351, 4.14, 0.444, -24.5 ] + }, + { + "time": 0.5, + "value": -24.69, + "curve": [ 0.571, -23.89, 0.55, 34.22 ] + }, + { + "time": 0.6667, + "value": 35.13, + "curve": [ 0.713, 34.81, 0.756, 22.76 ] + }, + { + "time": 0.8333, + "value": 22.82, + "curve": [ 0.868, 22.84, 0.916, 47.95 ] + }, + { "time": 0.9667, "value": 47.95, "curve": "stepped" }, + { + "time": 2.2333, + "value": 47.95, + "curve": [ 2.3, 47.95, 2.617, 18.72 ] + }, + { + "time": 2.6667, + "value": 18.51, + "curve": [ 3.172, 16.58, 4.06, 16.79 ] + }, + { + "time": 4.5333, + "value": 18.51, + "curve": [ 4.707, 19.13, 4.776, 41.11 ] + }, + { "time": 4.8, "value": 47.95 } + ] + }, + "torso": { + "rotate": [ + { + "value": -8.62, + "curve": [ 0.01, -16.71, 0.032, -33.6 ] + }, + { + "time": 0.0667, + "value": -33.37, + "curve": [ 0.182, -32.61, 0.298, 123.07 ] + }, + { + "time": 0.4667, + "value": 122.77, + "curve": [ 0.511, 122.69, 0.52, 100.2 ] + }, + { + "time": 0.5667, + "value": 88.96, + "curve": [ 0.588, 83.89, 0.667, 75.34 ] + }, + { + "time": 0.7, + "value": 75.34, + "curve": [ 0.767, 75.34, 0.9, 76.03 ] + }, + { "time": 0.9667, "value": 76.03 } + ] + }, + "front-upper-arm": { + "rotate": [ + { + "value": -38.86, + "curve": [ 0.022, -40.38, 0.096, -41.92 ] + }, + { + "time": 0.1333, + "value": -41.92, + "curve": [ 0.176, -41.92, 0.216, -16.92 ] + }, + { + "time": 0.2333, + "value": -4.35, + "curve": [ 0.258, 13.69, 0.308, 60.35 ] + }, + { + "time": 0.4, + "value": 60.17, + "curve": [ 0.496, 59.98, 0.539, 33.63 ] + }, + { + "time": 0.5667, + "value": 23.06, + "curve": [ 0.595, 32.71, 0.675, 53.71 ] + }, + { + "time": 0.7333, + "value": 53.61, + "curve": [ 0.797, 53.51, 0.926, 30.98 ] + }, + { "time": 0.9333, "value": 19.57, "curve": "stepped" }, + { + "time": 1.9667, + "value": 19.57, + "curve": [ 2.245, 19.57, 2.702, 77.03 ] + }, + { + "time": 3.0667, + "value": 77.06, + "curve": [ 3.209, 77.33, 3.291, 67.99 ] + }, + { + "time": 3.4333, + "value": 67.96, + "curve": [ 3.608, 68.34, 3.729, 73.88 ] + }, + { + "time": 3.8333, + "value": 73.42, + "curve": [ 4.152, 73.91, 4.46, 71.98 ] + }, + { + "time": 4.6333, + "value": 64.77, + "curve": [ 4.688, 62.5, 4.847, 26.42 ] + }, + { "time": 4.8667, "value": 10.94 } + ] + }, + "rear-upper-arm": { + "rotate": [ + { + "value": -44.7, + "curve": [ 0.033, -44.7, 0.12, 54.89 ] + }, + { + "time": 0.1333, + "value": 64.62, + "curve": [ 0.154, 79.18, 0.214, 79.42 ] + }, + { + "time": 0.2667, + "value": 63.4, + "curve": [ 0.293, 55.19, 0.332, 30.13 ] + }, + { + "time": 0.3667, + "value": 30.13, + "curve": [ 0.4, 30.13, 0.441, 39.87 ] + }, + { + "time": 0.4667, + "value": 55.13, + "curve": [ 0.488, 68.18, 0.52, 100.72 ] + }, + { + "time": 0.5333, + "value": 111.96, + "curve": [ 0.551, 126.88, 0.627, 185.97 ] + }, + { + "time": 0.6667, + "value": 185.97, + "curve": [ 0.692, 185.97, 0.736, 162.43 ] + }, + { + "time": 0.8, + "value": 158.01, + "curve": [ 0.9, 151.12, 1.017, 144.01 ] + }, + { "time": 1.1, "value": 144.01, "curve": "stepped" }, + { + "time": 2.3667, + "value": 144.01, + "curve": [ 2.492, 144.01, 2.742, 138.63 ] + }, + { + "time": 2.8667, + "value": 138.63, + "curve": [ 3.067, 138.63, 3.467, 138.63 ] + }, + { + "time": 3.6667, + "value": 138.63, + "curve": [ 3.883, 138.63, 4.317, 135.18 ] + }, + { + "time": 4.5333, + "value": 135.18, + "curve": [ 4.575, 135.18, 4.692, 131.59 ] + }, + { + "time": 4.7333, + "value": 131.59, + "curve": [ 4.758, 131.59, 4.517, 144.01 ] + }, + { "time": 4.8333, "value": 144.01 } + ], + "translate": [ + { + "time": 0.4667, + "curve": [ 0.517, 0, 0.617, -34.96, 0.517, 0, 0.617, -16.59 ] + }, + { "time": 0.6667, "x": -35.02, "y": -16.62 } + ] + }, + "front-bracer": { + "rotate": [ + { + "value": 21.88, + "curve": [ 0.033, 21.88, 0.099, 20.44 ] + }, + { + "time": 0.1333, + "value": 9.43, + "curve": [ 0.164, -0.29, 0.162, -38.26 ] + }, + { + "time": 0.2, + "value": -38.05, + "curve": [ 0.24, -37.96, 0.228, -17.82 ] + }, + { + "time": 0.3333, + "value": -9.73, + "curve": [ 0.372, -6.76, 0.431, -0.74 ] + }, + { + "time": 0.4667, + "value": 6.47, + "curve": [ 0.489, 11.05, 0.503, 19.09 ] + }, + { + "time": 0.5333, + "value": 19.09, + "curve": [ 0.571, 19.09, 0.554, -42.67 ] + }, + { + "time": 0.6, + "value": -42.67, + "curve": [ 0.653, -42.67, 0.691, -13.8 ] + }, + { + "time": 0.7, + "value": -3.54, + "curve": [ 0.707, 3.8, 0.719, 24.94 ] + }, + { + "time": 0.8, + "value": 25.31, + "curve": [ 0.902, 24.75, 0.992, -0.34 ] + }, + { "time": 1, "value": -32.16, "curve": "stepped" }, + { + "time": 2.2333, + "value": -32.16, + "curve": [ 2.6, -32.16, 2.638, -5.3 ] + }, + { + "time": 2.7, + "value": -1.96, + "curve": [ 2.707, -1.56, 2.775, 1.67 ] + }, + { + "time": 2.8, + "value": 1.67, + "curve": [ 2.825, 1.67, 2.875, -0.39 ] + }, + { + "time": 2.9, + "value": -0.39, + "curve": [ 2.925, -0.39, 2.975, 0.26 ] + }, + { + "time": 3, + "value": 0.26, + "curve": [ 3.025, 0.26, 3.075, -1.81 ] + }, + { + "time": 3.1, + "value": -1.81, + "curve": [ 3.125, -1.81, 3.175, -0.52 ] + }, + { + "time": 3.2, + "value": -0.52, + "curve": [ 3.225, -0.52, 3.275, -2.41 ] + }, + { + "time": 3.3, + "value": -2.41, + "curve": [ 3.333, -2.41, 3.4, -0.38 ] + }, + { + "time": 3.4333, + "value": -0.38, + "curve": [ 3.467, -0.38, 3.533, -2.25 ] + }, + { + "time": 3.5667, + "value": -2.25, + "curve": [ 3.592, -2.25, 3.642, -0.33 ] + }, + { + "time": 3.6667, + "value": -0.33, + "curve": [ 3.7, -0.33, 3.767, -1.34 ] + }, + { + "time": 3.8, + "value": -1.34, + "curve": [ 3.825, -1.34, 3.862, -0.77 ] + }, + { + "time": 3.9, + "value": -0.77, + "curve": [ 3.942, -0.77, 3.991, -1.48 ] + }, + { + "time": 4, + "value": -1.87, + "curve": [ 4.167, -1.87, 4.5, -1.96 ] + }, + { + "time": 4.6667, + "value": -1.96, + "curve": [ 4.709, 18.05, 4.767, 34.55 ] + }, + { + "time": 4.8, + "value": 34.55, + "curve": [ 4.84, 34.24, 4.902, 12.03 ] + }, + { "time": 4.9333, "value": -18.75 } + ] + }, + "front-fist": { + "rotate": [ + { + "value": -2.33, + "curve": [ 0.019, 4.43, 0.069, 10.82 ] + }, + { + "time": 0.1, + "value": 10.6, + "curve": [ 0.148, 10.6, 0.123, -15.24 ] + }, + { + "time": 0.2, + "value": -15.35, + "curve": [ 0.266, -15.44, 0.316, -6.48 ] + }, + { + "time": 0.3333, + "value": -3.9, + "curve": [ 0.362, 0.43, 0.479, 22.36 ] + }, + { + "time": 0.5667, + "value": 22.01, + "curve": [ 0.61, 21.84, 0.627, 12.85 ] + }, + { + "time": 0.6333, + "value": 9.05, + "curve": [ 0.643, 2.77, 0.622, -39.43 ] + }, + { + "time": 0.7, + "value": -39.5, + "curve": [ 0.773, -39.57, 0.814, 14.77 ] + }, + { + "time": 0.8667, + "value": 14.81, + "curve": [ 0.965, 14.88, 1.1, 5.64 ] + }, + { "time": 1.1, "value": -6.08, "curve": "stepped" }, + { + "time": 2.2333, + "value": -6.08, + "curve": [ 2.307, -6.08, 2.427, -25.89 ] + }, + { + "time": 2.5333, + "value": -22.42, + "curve": [ 2.598, -20.38, 2.657, 5.73 ] + }, + { + "time": 2.7, + "value": 5.73, + "curve": [ 2.77, 5.73, 2.851, -5.38 ] + }, + { + "time": 2.9333, + "value": -5.38, + "curve": [ 3.008, -5.38, 3.087, -4.54 ] + }, + { + "time": 3.1667, + "value": -4.17, + "curve": [ 3.223, -3.91, 4.486, 5.73 ] + }, + { + "time": 4.6667, + "value": 5.73, + "curve": [ 4.733, 5.73, 4.886, -2.47 ] + }, + { "time": 4.9333, "value": -6.52 } + ] + }, + "rear-bracer": { + "rotate": [ + { + "value": 10.36, + "curve": [ 0.033, 10.36, 0.1, -32.89 ] + }, + { + "time": 0.1333, + "value": -32.89, + "curve": [ 0.183, -32.89, 0.283, -4.45 ] + }, + { + "time": 0.3333, + "value": -4.45, + "curve": [ 0.367, -4.45, 0.438, -6.86 ] + }, + { + "time": 0.4667, + "value": -8.99, + "curve": [ 0.529, -13.62, 0.605, -20.58 ] + }, + { + "time": 0.6333, + "value": -23.2, + "curve": [ 0.708, -30.18, 0.758, -35.56 ] + }, + { + "time": 0.8, + "value": -35.56, + "curve": [ 0.875, -35.56, 1.025, -23.2 ] + }, + { "time": 1.1, "value": -23.2 } + ] + }, + "gun": { + "rotate": [ + { + "value": -2.79, + "curve": [ 0.033, -2.79, 0.12, -7.22 ] + }, + { + "time": 0.1333, + "value": -8.52, + "curve": [ 0.168, -11.87, 0.29, -23.71 ] + }, + { + "time": 0.3333, + "value": -26.24, + "curve": [ 0.369, -28.31, 0.436, -29.75 ] + }, + { + "time": 0.5, + "value": -29.66, + "curve": [ 0.552, -29.58, 0.611, -25.47 ] + }, + { + "time": 0.6333, + "value": -22.68, + "curve": [ 0.656, -19.76, 0.68, -10.02 ] + }, + { + "time": 0.7, + "value": -6.49, + "curve": [ 0.722, -2.6, 0.75, -1.22 ] + }, + { + "time": 0.7667, + "value": -1.35, + "curve": [ 0.792, -1.55, 0.842, -19.74 ] + }, + { "time": 0.8667, "value": -19.8 } + ] + }, + "hip": { + "translate": [ + { + "curve": [ 0.098, -42.62, 0.166, -79.85, 0.029, 84.97, 0.109, 155.93 ] + }, + { + "time": 0.2667, + "x": -133.79, + "y": 152.44, + "curve": [ 0.361, -184.63, 0.392, -203.69, 0.42, 149.12, 0.467, -15.7 ] + }, + { + "time": 0.4667, + "x": -230.02, + "y": -113.87, + "curve": [ 0.523, -249.86, 0.565, -261.7, 0.473, -133.1, 0.583, -203.43 ] + }, + { + "time": 0.6, + "x": -268.57, + "y": -203.43, + "curve": [ 0.663, -280.98, 0.816, -290.05, 0.708, -203.43, 0.892, -203.5 ] + }, + { "time": 1, "x": -290.42, "y": -203.5 } + ] + }, + "front-thigh": { + "rotate": [ + { + "curve": [ 0.06, 1.02, 0.151, 45.23 ] + }, + { + "time": 0.1667, + "value": 54.01, + "curve": [ 0.19, 66.85, 0.358, 169.85 ] + }, + { + "time": 0.5, + "value": 169.51, + "curve": [ 0.628, 169.85, 0.692, 108.85 ] + }, + { + "time": 0.7, + "value": 97.74, + "curve": [ 0.723, 102.6, 0.805, 111.6 ] + }, + { + "time": 0.8667, + "value": 111.69, + "curve": [ 0.899, 111.83, 1.015, 109.15 ] + }, + { "time": 1.0667, "value": 95.8 } + ] + }, + "front-shin": { + "rotate": [ + { + "curve": [ 0.086, -0.02, 0.191, -24.25 ] + }, + { + "time": 0.2, + "value": -26.5, + "curve": [ 0.214, -29.92, 0.249, -40.51 ] + }, + { + "time": 0.3333, + "value": -40.57, + "curve": [ 0.431, -40.7, 0.459, -11.34 ] + }, + { + "time": 0.4667, + "value": -8.71, + "curve": [ 0.477, -5.16, 0.524, 17.13 ] + }, + { + "time": 0.6, + "value": 16.98, + "curve": [ 0.632, 17.09, 0.625, 2.76 ] + }, + { + "time": 0.6333, + "value": 2.76, + "curve": [ 0.648, 2.76, 0.653, 2.75 ] + }, + { + "time": 0.6667, + "value": 2.59, + "curve": [ 0.678, 2.39, 0.733, 2.53 ] + }, + { + "time": 0.7333, + "value": -9.43, + "curve": [ 0.745, -2.48, 0.782, 3.12 ] + }, + { + "time": 0.8, + "value": 4.28, + "curve": [ 0.832, 6.32, 0.895, 8.46 ] + }, + { + "time": 0.9333, + "value": 8.49, + "curve": [ 0.986, 8.53, 1.051, 6.38 ] + }, + { + "time": 1.0667, + "value": 2.28, + "curve": [ 1.078, 4.17, 1.103, 5.86 ] + }, + { + "time": 1.1333, + "value": 5.88, + "curve": [ 1.191, 5.93, 1.209, 4.56 ] + }, + { "time": 1.2333, "value": 2.52 } + ] + }, + "rear-thigh": { + "rotate": [ + { + "curve": [ 0.033, 0, 0.12, 50.26 ] + }, + { + "time": 0.1333, + "value": 57.3, + "curve": [ 0.164, 73.34, 0.274, 147.18 ] + }, + { + "time": 0.3333, + "value": 147.1, + "curve": [ 0.475, 146.45, 0.583, 95.72 ] + }, + { + "time": 0.6, + "value": 79.66, + "curve": [ 0.62, 94.74, 0.732, 103.15 ] + }, + { + "time": 0.7667, + "value": 103.02, + "curve": [ 0.812, 102.85, 0.897, 95.75 ] + }, + { "time": 0.9333, "value": 83.01 } + ] + }, + "rear-shin": { + "rotate": [ + { + "curve": [ 0.021, -16.65, 0.091, -54.82 ] + }, + { + "time": 0.1667, + "value": -55.29, + "curve": [ 0.187, -55.42, 0.213, -52.52 ] + }, + { + "time": 0.2333, + "value": -45.98, + "curve": [ 0.242, -43.1, 0.311, -12.73 ] + }, + { + "time": 0.3333, + "value": -6.32, + "curve": [ 0.356, 0.13, 0.467, 24.5 ] + }, + { + "time": 0.5, + "value": 24.5, + "curve": [ 0.543, 24.5, 0.56, 3.78 ] + }, + { + "time": 0.5667, + "value": -3.53, + "curve": [ 0.585, 3.86, 0.659, 16.63 ] + }, + { + "time": 0.7, + "value": 16.56, + "curve": [ 0.782, 16.43, 0.896, 8.44 ] + }, + { + "time": 0.9333, + "value": 4.04, + "curve": [ 0.956, 6.84, 1.008, 8.41 ] + }, + { + "time": 1.0333, + "value": 8.41, + "curve": [ 1.067, 8.41, 1.122, 8.14 ] + }, + { "time": 1.1667, "value": 5.8 } + ] + }, + "rear-foot": { + "rotate": [ + { + "value": -0.28, + "curve": [ 0.033, -0.28, 0.256, -66.71 ] + }, + { + "time": 0.3667, + "value": -66.84, + "curve": [ 0.418, -66.91, 0.499, -21.79 ] + }, + { + "time": 0.6, + "value": -21.52, + "curve": [ 0.652, -21.38, 0.665, -53.96 ] + }, + { + "time": 0.7, + "value": -54.26, + "curve": [ 0.757, -53.96, 0.843, -2.07 ] + }, + { + "time": 0.9333, + "value": -1.47, + "curve": [ 0.968, -2.07, 0.975, -19.96 ] + }, + { + "time": 1, + "value": -19.96, + "curve": [ 1.025, -19.96, 1.075, -12.42 ] + }, + { + "time": 1.1, + "value": -12.42, + "curve": [ 1.133, -12.42, 1.2, -18.34 ] + }, + { "time": 1.2333, "value": -18.34 } + ] + }, + "front-foot": { + "rotate": [ + { + "curve": [ 0.008, -11.33, 0.108, -57.71 ] + }, + { + "time": 0.1333, + "value": -57.71, + "curve": [ 0.175, -57.71, 0.229, 19.73 ] + }, + { + "time": 0.3, + "value": 19.34, + "curve": [ 0.354, 19.34, 0.4, -57.76 ] + }, + { + "time": 0.4333, + "value": -57.76, + "curve": [ 0.458, -57.76, 0.511, -3.56 ] + }, + { + "time": 0.5333, + "value": 3.7, + "curve": [ 0.563, 13.29, 0.633, 15.79 ] + }, + { + "time": 0.6667, + "value": 15.79, + "curve": [ 0.7, 15.79, 0.767, -48.75 ] + }, + { + "time": 0.8, + "value": -48.75, + "curve": [ 0.842, -48.75, 0.925, 4.7 ] + }, + { + "time": 0.9667, + "value": 4.7, + "curve": [ 1, 4.7, 1.067, -22.9 ] + }, + { + "time": 1.1, + "value": -22.9, + "curve": [ 1.142, -22.9, 1.225, -13.28 ] + }, + { "time": 1.2667, "value": -13.28 } + ] + }, + "rear-foot-target": { + "rotate": [ + { "value": -0.28 } + ] + }, + "front-foot-tip": { + "rotate": [ + { + "value": -0.28, + "curve": [ 0.008, -0.28, 0.003, -66.62 ] + }, + { + "time": 0.0667, + "value": -65.75, + "curve": [ 0.166, -64.42, 0.234, 14.35 ] + }, + { + "time": 0.2667, + "value": 38.25, + "curve": [ 0.294, 57.91, 0.392, 89.79 ] + }, + { + "time": 0.4667, + "value": 90.73, + "curve": [ 0.483, 90.73, 0.55, 177.66 ] + }, + { + "time": 0.5667, + "value": 177.66, + "curve": [ 0.733, 176.24, 0.75, 11.35 ] + }, + { + "time": 0.8, + "value": 11.35, + "curve": [ 0.886, 12.29, 0.911, 47.88 ] + }, + { + "time": 0.9333, + "value": 56.77, + "curve": [ 0.967, 70.59, 1.05, 86.46 ] + }, + { + "time": 1.1, + "value": 86.46, + "curve": [ 1.187, 86.46, 1.214, 66.44 ] + }, + { "time": 1.3333, "value": 64.55 } + ] + }, + "back-foot-tip": { + "rotate": [ + { + "value": -0.28, + "curve": [ 0, -7.97, 0.027, -18.69 ] + }, + { + "time": 0.0667, + "value": -19, + "curve": [ 0.166, -19.3, 0.208, 15.58 ] + }, + { + "time": 0.2667, + "value": 45.95, + "curve": [ 0.306, 66.24, 0.378, 99.08 ] + }, + { + "time": 0.4333, + "value": 99.08, + "curve": [ 0.497, 98.62, 0.488, -1.2 ] + }, + { + "time": 0.5667, + "value": -1.32, + "curve": [ 0.637, -0.84, 0.687, 94.41 ] + }, + { + "time": 0.7333, + "value": 94.33, + "curve": [ 0.832, 94.16, 0.895, 29.6 ] + }, + { + "time": 0.9667, + "value": 28.67, + "curve": [ 1.026, 28.67, 1.045, 53.14 ] + }, + { "time": 1.1, "value": 53.38 } + ] + }, + "hair4": { + "rotate": [ + { + "curve": [ 0.011, 4.5, 0.05, 11.42 ] + }, + { + "time": 0.0667, + "value": 11.42, + "curve": [ 0.1, 11.42, 0.136, -5.92 ] + }, + { + "time": 0.1667, + "value": -10.54, + "curve": [ 0.206, -16.51, 0.327, -22 ] + }, + { + "time": 0.3667, + "value": -24.47, + "curve": [ 0.413, -27.37, 0.467, -43.99 ] + }, + { + "time": 0.5, + "value": -43.99, + "curve": [ 0.533, -43.99, 0.552, 12.12 ] + }, + { + "time": 0.6333, + "value": 11.85, + "curve": [ 0.714, 11.59, 0.758, -34.13 ] + }, + { + "time": 0.8, + "value": -34.13, + "curve": [ 0.858, -34.13, 1.015, -12.47 ] + }, + { + "time": 1.0667, + "value": -8.85, + "curve": [ 1.121, -5.07, 1.219, -0.02 ] + }, + { + "time": 1.3333, + "value": 1.29, + "curve": [ 1.509, 3.3, 1.763, 2.75 ] + }, + { + "time": 1.8667, + "value": 2.78, + "curve": [ 1.974, 2.81, 2.108, 2.81 ] + }, + { + "time": 2.2, + "value": 2.78, + "curve": [ 2.315, 2.74, 2.374, 1.22 ] + }, + { + "time": 2.4667, + "value": 1.18, + "curve": [ 2.525, 1.18, 2.608, 10.79 ] + }, + { + "time": 2.6667, + "value": 10.79, + "curve": [ 2.725, 10.79, 2.893, 4.72 ] + }, + { + "time": 3.0333, + "value": 4.72, + "curve": [ 3.117, 4.72, 3.283, 7.93 ] + }, + { + "time": 3.3667, + "value": 7.93, + "curve": [ 3.492, 7.93, 3.775, 6.93 ] + }, + { + "time": 3.9, + "value": 6.93, + "curve": [ 3.981, 6.93, 4.094, 6.9 ] + }, + { + "time": 4.2, + "value": 8.44, + "curve": [ 4.267, 9.42, 4.401, 16.61 ] + }, + { + "time": 4.5, + "value": 16.33, + "curve": [ 4.582, 16.12, 4.709, 9.94 ] + }, + { + "time": 4.7333, + "value": 6.51, + "curve": [ 4.747, 4.57, 4.779, -1.76 ] + }, + { + "time": 4.8, + "value": -1.75, + "curve": [ 4.823, -1.73, 4.82, 4.47 ] + }, + { + "time": 4.8667, + "value": 6.04, + "curve": [ 4.899, 7.14, 4.913, 6.93 ] + }, + { "time": 4.9333, "value": 6.93 } + ] + }, + "hair2": { + "rotate": [ + { + "value": 10.61, + "curve": [ 0.075, 10.61, 0.05, 12.67 ] + }, + { + "time": 0.0667, + "value": 12.67, + "curve": [ 0.123, 12.67, 0.194, -16.51 ] + }, + { + "time": 0.2, + "value": -19.87, + "curve": [ 0.207, -23.48, 0.236, -31.68 ] + }, + { + "time": 0.3, + "value": -31.8, + "curve": [ 0.356, -31.9, 0.437, -25.61 ] + }, + { + "time": 0.4667, + "value": -19.29, + "curve": [ 0.485, -15.33, 0.529, 6.48 ] + }, + { + "time": 0.5667, + "value": 6.67, + "curve": [ 0.628, 6.97, 0.65, -46.39 ] + }, + { + "time": 0.7333, + "value": -46.3, + "curve": [ 0.843, -46.17, 0.941, -33.37 ] + }, + { + "time": 0.9667, + "value": -23.17, + "curve": [ 0.972, -20.98, 1.047, 15.21 ] + }, + { + "time": 1.1, + "value": 15.21, + "curve": [ 1.142, 15.21, 1.183, 10.73 ] + }, + { + "time": 1.2667, + "value": 10.61, + "curve": [ 1.45, 10.34, 1.817, 10.61 ] + }, + { + "time": 2, + "value": 10.61, + "curve": [ 2.075, 10.61, 2.225, 16.9 ] + }, + { + "time": 2.3, + "value": 16.9, + "curve": [ 2.327, 16.9, 2.347, 6.81 ] + }, + { + "time": 2.4, + "value": 6.83, + "curve": [ 2.492, 6.87, 2.602, 17.39 ] + }, + { + "time": 2.6667, + "value": 17.39, + "curve": [ 2.742, 17.39, 2.892, 10.67 ] + }, + { + "time": 2.9667, + "value": 10.64, + "curve": [ 3.187, 10.57, 3.344, 10.73 ] + }, + { + "time": 3.6, + "value": 11.4, + "curve": [ 3.766, 11.83, 3.874, 14.87 ] + }, + { + "time": 3.9333, + "value": 14.83, + "curve": [ 4.022, 14.76, 4.208, 9.49 ] + }, + { + "time": 4.3, + "value": 9.54, + "curve": [ 4.391, 9.58, 4.441, 14.82 ] + }, + { + "time": 4.5333, + "value": 14.84, + "curve": [ 4.642, 14.88, 4.692, 1.17 ] + }, + { + "time": 4.7667, + "value": 1.24, + "curve": [ 4.823, 1.3, 4.818, 18.35 ] + }, + { + "time": 4.8667, + "value": 18.38, + "curve": [ 4.905, 18.41, 4.901, 10.61 ] + }, + { "time": 4.9333, "value": 10.61 } + ] + }, + "torso2": { + "rotate": [ + { + "curve": [ 0.048, 0, 0.129, -12.73 ] + }, + { + "time": 0.1667, + "value": -15.95, + "curve": [ 0.221, -20.66, 0.254, -21.62 ] + }, + { + "time": 0.3, + "value": -21.59, + "curve": [ 0.458, -21.46, 0.46, -1.67 ] + }, + { + "time": 0.6333, + "value": -1.71, + "curve": [ 0.71, -1.73, 0.715, -4 ] + }, + { + "time": 0.7667, + "value": -3.97, + "curve": [ 0.866, -3.92, 0.84, 0.02 ] + }, + { "time": 1, "curve": "stepped" }, + { + "time": 2, + "curve": [ 2.275, 0, 2.867, -5.8 ] + }, + { + "time": 3.1, + "value": -6.44, + "curve": [ 3.327, -7.06, 3.71, -6.23 ] + }, + { + "time": 3.9333, + "value": -5.41, + "curve": [ 4.168, -4.53, 4.488, -2.83 ] + }, + { "time": 4.8 } + ] + }, + "torso3": { + "rotate": [ + { + "curve": [ 0.025, 0, 0.09, -3.66 ] + }, + { + "time": 0.1, + "value": -4.55, + "curve": [ 0.143, -8.4, 0.223, -17.07 ] + }, + { + "time": 0.2333, + "value": -18.31, + "curve": [ 0.282, -24.44, 0.35, -29 ] + }, + { + "time": 0.3667, + "value": -30.07, + "curve": [ 0.405, -32.58, 0.442, -33.03 ] + }, + { + "time": 0.4667, + "value": -32.99, + "curve": [ 0.491, -33.04, 0.505, -23.56 ] + }, + { + "time": 0.5333, + "value": -23.55, + "curve": [ 0.571, -23.67, 0.599, -27.21 ] + }, + { + "time": 0.6333, + "value": -27.21, + "curve": [ 0.669, -27.2, 0.742, -10.43 ] + }, + { + "time": 0.7667, + "value": -7.79, + "curve": [ 0.788, -5.53, 0.796, -4.42 ] + }, + { + "time": 0.8333, + "value": -2.9, + "curve": [ 0.875, -1.21, 0.933, 0 ] + }, + { "time": 0.9667, "curve": "stepped" }, + { + "time": 2.4333, + "curve": [ 2.517, 0, 2.683, 4.63 ] + }, + { + "time": 2.7667, + "value": 4.66, + "curve": [ 3.084, 4.76, 3.248, 4.37 ] + }, + { + "time": 3.4, + "value": 3.74, + "curve": [ 3.596, 2.92, 3.755, 2.18 ] + }, + { + "time": 3.8667, + "value": 1.72, + "curve": [ 4.136, 0.59, 4.471, 0 ] + }, + { "time": 4.8 } + ] + }, + "hair3": { + "rotate": [ + { + "curve": [ 0, 0, 0.041, 10.74 ] + }, + { + "time": 0.0667, + "value": 14.16, + "curve": [ 0.075, 15.22, 0.148, 18.04 ] + }, + { + "time": 0.2, + "value": 18.13, + "curve": [ 0.251, 18.23, 0.307, -4.75 ] + }, + { + "time": 0.3667, + "value": -5.06, + "curve": [ 0.412, -5.3, 0.47, -0.96 ] + }, + { + "time": 0.5, + "value": 2.21, + "curve": [ 0.512, 3.48, 0.595, 20.31 ] + }, + { + "time": 0.6333, + "value": 24.87, + "curve": [ 0.647, 26.53, 0.719, 29.33 ] + }, + { + "time": 0.8, + "value": 29.22, + "curve": [ 0.859, 29.14, 0.9, 28.48 ] + }, + { + "time": 0.9333, + "value": 26.11, + "curve": [ 0.981, 22.72, 0.998, 2.06 ] + }, + { "time": 1.1, "value": 2.21 } + ] + }, + "hair1": { + "rotate": [ + { + "curve": [ 0.047, -0.21, 0.048, 7.86 ] + }, + { + "time": 0.0667, + "value": 13.27, + "curve": [ 0.083, 18.05, 0.135, 24.44 ] + }, + { + "time": 0.2, + "value": 24.02, + "curve": [ 0.225, 24.02, 0.28, 6.32 ] + }, + { + "time": 0.3, + "value": 3.1, + "curve": [ 0.323, -0.58, 0.382, -7.12 ] + }, + { + "time": 0.4667, + "value": -7.45, + "curve": [ 0.512, -7.66, 0.538, 12.13 ] + }, + { + "time": 0.5667, + "value": 16.46, + "curve": [ 0.609, 22.72, 0.672, 27.4 ] + }, + { + "time": 0.7333, + "value": 27.55, + "curve": [ 0.827, 27.4, 0.933, 23.23 ] + }, + { + "time": 0.9667, + "value": 19.11, + "curve": [ 0.998, 15.27, 1.092, -2.53 ] + }, + { + "time": 1.1333, + "value": -2.53, + "curve": [ 1.158, -2.53, 1.208, 0 ] + }, + { "time": 1.2333, "curve": "stepped" }, + { + "time": 2, + "curve": [ 2.075, 0, 2.248, 0.35 ] + }, + { + "time": 2.3333, + "value": 0.78, + "curve": [ 2.585, 2.06, 2.805, 3.46 ] + }, + { + "time": 3.2, + "value": 3.5, + "curve": [ 3.593, 3.54, 3.979, 2.36 ] + }, + { + "time": 4.1667, + "value": 1.55, + "curve": [ 4.391, 0.59, 4.447, 0.04 ] + }, + { + "time": 4.6, + "value": 0.04, + "curve": [ 4.642, 0.04, 4.742, 0 ] + }, + { "time": 4.9333 } + ] + }, + "head-control": { + "translate": [ + { + "curve": [ 0.025, 0, 0.09, 1.43, 0.025, 0, 0.075, -34.76 ] + }, + { + "time": 0.1, + "x": 1.59, + "y": -34.76, + "curve": [ 0.214, 3.33, 0.375, 5.34, 0.192, -34.76, 0.441, -21.17 ] + }, + { + "time": 0.4667, + "x": 5.34, + "y": -12.57, + "curve": [ 0.492, 5.34, 0.55, 5.24, 0.482, -7.36, 0.504, 4.03 ] + }, + { + "time": 0.5667, + "x": 5.11, + "y": 4.01, + "curve": [ 0.658, 4.45, 0.679, 3.19, 0.649, 3.98, 0.642, -16.84 ] + }, + { + "time": 0.7, + "x": 2.8, + "y": -16.74, + "curve": [ 0.787, 1.15, 0.881, -1.29, 0.772, -16.62, 0.82, 8.95 ] + }, + { + "time": 0.9, + "x": -1.72, + "y": 8.91, + "curve": [ 0.961, -3.06, 1.025, -3.58, 0.975, 8.87, 0.951, -1.37 ] + }, + { + "time": 1.1, + "x": -3.58, + "y": -1.45, + "curve": [ 1.292, -3.58, 2.002, -2.4, 1.292, -1.56, 1.975, -1.45 ] + }, + { + "time": 2.1667, + "x": -1.39, + "y": -1.45, + "curve": [ 2.25, -0.88, 2.503, 1.38, 2.283, -1.45, 2.603, -12.44 ] + }, + { + "time": 2.6667, + "x": 2.13, + "y": -14.45, + "curve": [ 2.766, 2.59, 2.999, 2.81, 2.835, -19.73, 3.003, -25.2 ] + }, + { + "time": 3.1333, + "x": 2.91, + "y": -26.08, + "curve": [ 3.392, 3.1, 4.199, 4.05, 3.483, -28.44, 4.129, -27.23 ] + }, + { + "time": 4.3667, + "x": 4.81, + "y": -19.59, + "curve": [ 4.429, 5.1, 4.594, 8.54, 4.538, -14.08, 4.583, -7.88 ] + }, + { + "time": 4.6667, + "x": 8.65, + "y": -4.56, + "curve": [ 4.794, 8.86, 4.806, 5.93, 4.691, -3.59, 4.8, -1.61 ] + }, + { "time": 4.9333, "x": 5.8, "y": -1.99 } + ] + } + }, + "ik": { + "front-foot-ik": [ + { "mix": 0 } + ], + "front-leg-ik": [ + { "mix": 0, "bendPositive": false } + ], + "rear-foot-ik": [ + { "mix": 0.005 } + ], + "rear-leg-ik": [ + { "mix": 0.005, "bendPositive": false } + ] + } + }, + "hoverboard": { + "slots": { + "exhaust1": { + "attachment": [ + { "name": "hoverglow-small" } + ] + }, + "exhaust2": { + "attachment": [ + { "name": "hoverglow-small" } + ] + }, + "exhaust3": { + "attachment": [ + { "name": "hoverglow-small" } + ] + }, + "front-fist": { + "attachment": [ + { "name": "front-fist-open" } + ] + }, + "hoverboard-board": { + "attachment": [ + { "name": "hoverboard-board" } + ] + }, + "hoverboard-thruster-front": { + "attachment": [ + { "name": "hoverboard-thruster" } + ] + }, + "hoverboard-thruster-rear": { + "attachment": [ + { "name": "hoverboard-thruster" } + ] + }, + "hoverglow-front": { + "attachment": [ + { "name": "hoverglow-small" } + ] + }, + "hoverglow-rear": { + "attachment": [ + { "name": "hoverglow-small" } + ] + }, + "side-glow1": { + "attachment": [ + { "name": "hoverglow-small" }, + { "time": 0.9667 } + ] + }, + "side-glow2": { + "attachment": [ + { "time": 0.0667, "name": "hoverglow-small" }, + { "time": 1 } + ] + }, + "side-glow3": { + "attachment": [ + { "name": "hoverglow-small" }, + { "time": 0.9667 } + ] + } + }, + "bones": { + "hoverboard-controller": { + "translate": [ + { + "x": 319.55, + "y": -1.59, + "curve": [ 0.064, 319.55, 0.2, 347.85, 0.058, -1.2, 0.2, 23.11 ] + }, + { + "time": 0.2667, + "x": 347.66, + "y": 39.62, + "curve": [ 0.35, 347.41, 0.476, 341.47, 0.323, 53.58, 0.44, 85.82 ] + }, + { + "time": 0.5333, + "x": 338.47, + "y": 85.72, + "curve": [ 0.603, 334.83, 0.913, 319.65, 0.621, 85.62, 0.88, -1.53 ] + }, + { "time": 1, "x": 319.55, "y": -1.59 } + ] + }, + "hip": { + "translate": [ + { + "x": -53.49, + "y": 32.14, + "curve": [ 0.061, -53.77, 0.093, -51.81, 0.044, 16.34, 0.063, 9.67 ] + }, + { + "time": 0.1333, + "x": -49.31, + "y": 7.01, + "curve": [ 0.3, -35.27, 0.461, -20.06, 0.314, 9.52, 0.408, 121.09 ] + }, + { + "time": 0.5667, + "x": -20.06, + "y": 122.72, + "curve": [ 0.716, -20.09, 0.912, -53.29, 0.753, 121.8, 0.946, 51.85 ] + }, + { "time": 1, "x": -53.49, "y": 32.14 } + ] + }, + "exhaust1": { + "scale": [ + { + "x": 1.593, + "y": 0.964, + "curve": [ 0.033, 1.593, 0.1, 1, 0.033, 0.964, 0.1, 0.713 ] + }, + { + "time": 0.1333, + "y": 0.713, + "curve": [ 0.15, 1, 0.183, 1.774, 0.15, 0.713, 0.183, 0.883 ] + }, + { + "time": 0.2, + "x": 1.774, + "y": 0.883, + "curve": [ 0.242, 1.774, 0.325, 1.181, 0.242, 0.883, 0.325, 0.649 ] + }, + { + "time": 0.3667, + "x": 1.181, + "y": 0.649, + "curve": [ 0.408, 1.181, 0.492, 1.893, 0.408, 0.649, 0.492, 0.819 ] + }, + { + "time": 0.5333, + "x": 1.893, + "y": 0.819, + "curve": [ 0.558, 1.893, 0.608, 1.18, 0.558, 0.819, 0.608, 0.686 ] + }, + { + "time": 0.6333, + "x": 1.18, + "y": 0.686, + "curve": [ 0.658, 1.18, 0.708, 1.903, 0.658, 0.686, 0.708, 0.855 ] + }, + { + "time": 0.7333, + "x": 1.903, + "y": 0.855, + "curve": [ 0.767, 1.903, 0.833, 1.311, 0.767, 0.855, 0.833, 0.622 ] + }, + { + "time": 0.8667, + "x": 1.311, + "y": 0.622, + "curve": [ 0.9, 1.311, 0.967, 1.593, 0.9, 0.622, 0.967, 0.964 ] + }, + { "time": 1, "x": 1.593, "y": 0.964 } + ] + }, + "exhaust2": { + "scale": [ + { + "x": 1.88, + "y": 0.832, + "curve": [ 0.025, 1.88, 0.075, 1.311, 0.025, 0.832, 0.075, 0.686 ] + }, + { + "time": 0.1, + "x": 1.311, + "y": 0.686, + "curve": [ 0.133, 1.311, 0.2, 2.01, 0.133, 0.686, 0.208, 0.736 ] + }, + { + "time": 0.2333, + "x": 2.01, + "y": 0.769, + "curve": [ 0.267, 2.01, 0.333, 1, 0.282, 0.831, 0.333, 0.91 ] + }, + { + "time": 0.3667, + "y": 0.91, + "curve": [ 0.4, 1, 0.467, 1.699, 0.4, 0.91, 0.474, 0.891 ] + }, + { + "time": 0.5, + "x": 1.699, + "y": 0.86, + "curve": [ 0.517, 1.699, 0.55, 1.181, 0.54, 0.813, 0.55, 0.713 ] + }, + { + "time": 0.5667, + "x": 1.181, + "y": 0.713, + "curve": [ 0.617, 1.181, 0.717, 1.881, 0.617, 0.713, 0.717, 0.796 ] + }, + { + "time": 0.7667, + "x": 1.881, + "y": 0.796, + "curve": [ 0.8, 1.881, 0.867, 1.3, 0.8, 0.796, 0.867, 0.649 ] + }, + { + "time": 0.9, + "x": 1.3, + "y": 0.649, + "curve": [ 0.925, 1.3, 0.975, 1.88, 0.925, 0.649, 0.975, 0.832 ] + }, + { "time": 1, "x": 1.88, "y": 0.832 } + ] + }, + "hoverboard-thruster-front": { + "rotate": [ + { + "curve": [ 0.125, 0, 0.375, 24.06 ] + }, + { + "time": 0.5, + "value": 24.06, + "curve": [ 0.625, 24.06, 0.875, 0 ] + }, + { "time": 1 } + ] + }, + "hoverglow-front": { + "scale": [ + { + "x": 0.849, + "y": 1.764, + "curve": [ 0.017, 0.849, 0.05, 0.835, 0.017, 1.764, 0.05, 2.033 ] + }, + { + "time": 0.0667, + "x": 0.835, + "y": 2.033, + "curve": [ 0.092, 0.835, 0.142, 0.752, 0.092, 2.033, 0.142, 1.584 ] + }, + { + "time": 0.1667, + "x": 0.752, + "y": 1.584, + "curve": [ 0.183, 0.752, 0.217, 0.809, 0.183, 1.584, 0.217, 1.71 ] + }, + { + "time": 0.2333, + "x": 0.809, + "y": 1.71, + "curve": [ 0.25, 0.809, 0.283, 0.717, 0.25, 1.71, 0.283, 1.45 ] + }, + { + "time": 0.3, + "x": 0.717, + "y": 1.45, + "curve": [ 0.317, 0.717, 0.35, 0.777, 0.317, 1.45, 0.35, 1.698 ] + }, + { + "time": 0.3667, + "x": 0.777, + "y": 1.698, + "curve": [ 0.4, 0.781, 0.45, 0.685, 0.375, 1.698, 0.45, 1.173 ] + }, + { + "time": 0.4667, + "x": 0.685, + "y": 1.173, + "curve": [ 0.492, 0.685, 0.542, 0.825, 0.492, 1.173, 0.542, 1.572 ] + }, + { + "time": 0.5667, + "x": 0.825, + "y": 1.572, + "curve": [ 0.611, 0.816, 0.63, 0.727, 0.611, 1.577, 0.606, 1.255 ] + }, + { + "time": 0.6667, + "x": 0.725, + "y": 1.241, + "curve": [ 0.692, 0.725, 0.742, 0.895, 0.692, 1.241, 0.749, 1.799 ] + }, + { + "time": 0.7667, + "x": 0.895, + "y": 1.857, + "curve": [ 0.783, 0.895, 0.796, 0.892, 0.796, 1.955, 0.817, 1.962 ] + }, + { + "time": 0.8333, + "x": 0.845, + "y": 1.962, + "curve": [ 0.845, 0.831, 0.883, 0.802, 0.85, 1.962, 0.872, 1.704 ] + }, + { + "time": 0.9, + "x": 0.802, + "y": 1.491, + "curve": [ 0.917, 0.802, 0.95, 0.845, 0.907, 1.441, 0.936, 1.508 ] + }, + { + "time": 0.9667, + "x": 0.845, + "y": 1.627, + "curve": [ 0.975, 0.845, 0.992, 0.849, 0.973, 1.652, 0.992, 1.764 ] + }, + { "time": 1, "x": 0.849, "y": 1.764 } + ] + }, + "hoverboard-thruster-rear": { + "rotate": [ + { + "curve": [ 0.125, 0, 0.375, 24.06 ] + }, + { + "time": 0.5, + "value": 24.06, + "curve": [ 0.625, 24.06, 0.875, 0 ] + }, + { "time": 1 } + ] + }, + "hoverglow-rear": { + "scale": [ + { + "x": 0.845, + "y": 1.31, + "curve": [ 0.017, 0.845, 0.117, 0.899, 0.017, 1.31, 0.117, 2.033 ] + }, + { + "time": 0.1333, + "x": 0.899, + "y": 2.033, + "curve": [ 0.15, 0.899, 0.183, 0.752, 0.15, 2.033, 0.183, 1.574 ] + }, + { + "time": 0.2, + "x": 0.752, + "y": 1.574, + "curve": [ 0.225, 0.752, 0.275, 0.809, 0.225, 1.574, 0.275, 1.71 ] + }, + { + "time": 0.3, + "x": 0.809, + "y": 1.71, + "curve": [ 0.317, 0.809, 0.35, 0.717, 0.317, 1.71, 0.35, 1.397 ] + }, + { + "time": 0.3667, + "x": 0.717, + "y": 1.397, + "curve": [ 0.383, 0.717, 0.417, 0.777, 0.383, 1.397, 0.417, 1.45 ] + }, + { + "time": 0.4333, + "x": 0.777, + "y": 1.45, + "curve": [ 0.45, 0.777, 0.496, 0.689, 0.45, 1.45, 0.481, 1.168 ] + }, + { + "time": 0.5333, + "x": 0.685, + "y": 1.173, + "curve": [ 0.565, 0.682, 0.617, 0.758, 0.575, 1.177, 0.617, 1.297 ] + }, + { + "time": 0.6333, + "x": 0.758, + "y": 1.297, + "curve": [ 0.658, 0.758, 0.708, 0.725, 0.658, 1.297, 0.708, 1.241 ] + }, + { + "time": 0.7333, + "x": 0.725, + "y": 1.241, + "curve": [ 0.772, 0.732, 0.796, 0.893, 0.782, 1.238, 0.778, 1.854 ] + }, + { + "time": 0.8333, + "x": 0.895, + "y": 1.857, + "curve": [ 0.878, 0.9, 0.992, 0.845, 0.88, 1.86, 0.992, 1.31 ] + }, + { "time": 1, "x": 0.845, "y": 1.31 } + ] + }, + "front-upper-arm": { + "rotate": [ + { + "value": -85.92, + "curve": [ 0.08, -85.59, 0.284, -62.7 ] + }, + { + "time": 0.3667, + "value": -55.14, + "curve": [ 0.438, -48.65, 0.551, -43.21 ] + }, + { + "time": 0.6333, + "value": -43.21, + "curve": [ 0.716, -43.22, 0.908, -85.92 ] + }, + { "time": 1, "value": -85.92 } + ], + "translate": [ + { + "x": -0.59, + "y": -2.94, + "curve": [ 0.1, -1.21, 0.275, -1.74, 0.092, -2.94, 0.275, -6.39 ] + }, + { + "time": 0.3667, + "x": -1.74, + "y": -6.39, + "curve": [ 0.433, -1.74, 0.567, 0.72, 0.433, -6.39, 0.587, -4.48 ] + }, + { + "time": 0.6333, + "x": 0.72, + "y": -4.21, + "curve": [ 0.725, 0.72, 0.908, -0.08, 0.743, -3.57, 0.908, -2.94 ] + }, + { "time": 1, "x": -0.59, "y": -2.94 } + ] + }, + "front-fist": { + "rotate": [ + { + "value": 7.61, + "curve": [ 0.143, 7.62, 0.247, -23.17 ] + }, + { + "time": 0.2667, + "value": -26.56, + "curve": [ 0.281, -29.08, 0.351, -37.36 ] + }, + { + "time": 0.4333, + "value": -37.2, + "curve": [ 0.513, -37.05, 0.562, -29.88 ] + }, + { + "time": 0.6, + "value": -25.18, + "curve": [ 0.621, -22.58, 0.694, -3.98 ] + }, + { + "time": 0.8, + "value": 3.63, + "curve": [ 0.861, 8.03, 0.946, 7.57 ] + }, + { "time": 1, "value": 7.61 } + ], + "translate": [ + { + "curve": [ 0.117, 0, 0.35, 0.52, 0.117, 0, 0.35, -3.27 ] + }, + { + "time": 0.4667, + "x": 0.52, + "y": -3.27, + "curve": [ 0.6, 0.52, 0.867, 0, 0.6, -3.27, 0.867, 0 ] + }, + { "time": 1 } + ], + "shear": [ + { + "y": 19.83, + "curve": [ 0.117, 0, 0.35, 15.28, 0.117, 19.83, 0.35, 28.31 ] + }, + { + "time": 0.4667, + "x": 15.28, + "y": 28.31, + "curve": [ 0.6, 15.28, 0.867, 0, 0.6, 28.31, 0.867, 19.83 ] + }, + { "time": 1, "y": 19.83 } + ] + }, + "board-ik": { + "translate": [ + { + "x": 393.62, + "curve": [ 0.083, 393.62, 0.25, 393.48, 0.083, 0, 0.25, 117.69 ] + }, + { + "time": 0.3333, + "x": 393.48, + "y": 117.69, + "curve": [ 0.375, 393.48, 0.458, 393.62, 0.375, 117.69, 0.458, 83.82 ] + }, + { "time": 0.5, "x": 393.62, "y": 83.82 }, + { "time": 0.6667, "x": 393.62, "y": 30.15 }, + { "time": 1, "x": 393.62 } + ] + }, + "front-thigh": { + "translate": [ + { "x": -7.49, "y": 8.51 } + ] + }, + "front-leg-target": { + "translate": [ + { + "time": 0.3667, + "curve": [ 0.428, 10.83, 0.567, 12.78, 0.414, 7.29, 0.567, 8.79 ] + }, + { + "time": 0.6, + "x": 12.78, + "y": 8.79, + "curve": [ 0.692, 12.78, 0.772, 11.27, 0.692, 8.79, 0.766, 8.62 ] + }, + { "time": 0.8667 } + ] + }, + "rear-leg-target": { + "translate": [ + { + "time": 0.4667, + "curve": [ 0.492, 0, 0.534, 4.47, 0.492, 0, 0.542, 1.63 ] + }, + { + "time": 0.5667, + "x": 4.53, + "y": 1.77, + "curve": [ 0.622, 4.64, 0.717, 3.31, 0.615, 2.06, 0.71, 2.1 ] + }, + { "time": 0.8 } + ] + }, + "exhaust3": { + "scale": [ + { + "x": 1.882, + "y": 0.81, + "curve": [ 0.017, 1.882, 0.167, 1.3, 0.017, 0.81, 0.167, 0.649 ] + }, + { + "time": 0.2, + "x": 1.3, + "y": 0.649, + "curve": [ 0.225, 1.3, 0.275, 2.051, 0.225, 0.649, 0.275, 0.984 ] + }, + { + "time": 0.3, + "x": 2.051, + "y": 0.984, + "curve": [ 0.325, 2.051, 0.375, 1.311, 0.325, 0.984, 0.384, 0.715 ] + }, + { + "time": 0.4, + "x": 1.311, + "y": 0.686, + "curve": [ 0.433, 1.311, 0.5, 1.86, 0.426, 0.638, 0.5, 0.537 ] + }, + { + "time": 0.5333, + "x": 1.86, + "y": 0.537, + "curve": [ 0.567, 1.86, 0.633, 1.187, 0.567, 0.537, 0.604, 0.854 ] + }, + { + "time": 0.6667, + "x": 1.187, + "y": 0.854, + "curve": [ 0.7, 1.187, 0.767, 1.549, 0.707, 0.854, 0.774, 0.775 ] + }, + { + "time": 0.8, + "x": 1.549, + "y": 0.746, + "curve": [ 0.817, 1.549, 0.85, 1.181, 0.815, 0.729, 0.85, 0.713 ] + }, + { + "time": 0.8667, + "x": 1.181, + "y": 0.713, + "curve": [ 0.9, 1.181, 0.967, 1.882, 0.9, 0.713, 0.967, 0.81 ] + }, + { "time": 1, "x": 1.882, "y": 0.81 } + ] + }, + "side-glow1": { + "rotate": [ + { "value": 51.12, "curve": "stepped" }, + { "time": 0.0667, "value": 43.82, "curve": "stepped" }, + { "time": 0.1, "value": 40.95, "curve": "stepped" }, + { "time": 0.1667, "value": 27.78, "curve": "stepped" }, + { "time": 0.2, "value": 10.24, "curve": "stepped" }, + { "time": 0.2667, "curve": "stepped" }, + { "time": 0.8, "value": -25.81 } + ], + "translate": [ + { "x": 338.28, "y": 40.22, "curve": "stepped" }, + { "time": 0.0667, "x": 331.2, "y": 30.39, "curve": "stepped" }, + { "time": 0.1, "x": 318.63, "y": 20.59, "curve": "stepped" }, + { "time": 0.1667, "x": 302.45, "y": 9.64, "curve": "stepped" }, + { "time": 0.2, "x": 276.87, "y": 1.13, "curve": "stepped" }, + { "time": 0.2667, "x": 248.16, "curve": "stepped" }, + { "time": 0.3, "x": 221.36, "curve": "stepped" }, + { "time": 0.3667, "x": 195.69, "curve": "stepped" }, + { "time": 0.4, "x": 171.08, "curve": "stepped" }, + { "time": 0.4667, "x": 144.84, "curve": "stepped" }, + { "time": 0.5, "x": 121.22, "curve": "stepped" }, + { "time": 0.5667, "x": 91.98, "curve": "stepped" }, + { "time": 0.6, "x": 62.63, "curve": "stepped" }, + { "time": 0.6667, "x": 30.78, "curve": "stepped" }, + { "time": 0.7, "curve": "stepped" }, + { "time": 0.7667, "x": -28.45, "curve": "stepped" }, + { "time": 0.8, "x": -67.49, "y": 16.82, "curve": "stepped" }, + { "time": 0.8667, "x": -83.07, "y": 24.36, "curve": "stepped" }, + { "time": 0.9, "x": -93.81, "y": 29.55 } + ], + "scale": [ + { "x": 0.535, "curve": "stepped" }, + { "time": 0.0667, "x": 0.594, "curve": "stepped" }, + { "time": 0.1, "x": 0.844, "curve": "stepped" }, + { "time": 0.1667, "curve": "stepped" }, + { "time": 0.8, "x": 0.534, "curve": "stepped" }, + { "time": 0.8667, "x": 0.428, "y": 0.801, "curve": "stepped" }, + { "time": 0.9, "x": 0.349, "y": 0.654 } + ] + }, + "side-glow2": { + "rotate": [ + { "time": 0.0667, "value": 51.12, "curve": "stepped" }, + { "time": 0.1, "value": 43.82, "curve": "stepped" }, + { "time": 0.1667, "value": 40.95, "curve": "stepped" }, + { "time": 0.2, "value": 27.78, "curve": "stepped" }, + { "time": 0.2667, "value": 10.24, "curve": "stepped" }, + { "time": 0.3, "curve": "stepped" }, + { "time": 0.8667, "value": -25.81 } + ], + "translate": [ + { "time": 0.0667, "x": 338.28, "y": 40.22, "curve": "stepped" }, + { "time": 0.1, "x": 331.2, "y": 30.39, "curve": "stepped" }, + { "time": 0.1667, "x": 318.63, "y": 20.59, "curve": "stepped" }, + { "time": 0.2, "x": 302.45, "y": 9.64, "curve": "stepped" }, + { "time": 0.2667, "x": 276.87, "y": 1.13, "curve": "stepped" }, + { "time": 0.3, "x": 248.16, "curve": "stepped" }, + { "time": 0.3667, "x": 221.36, "curve": "stepped" }, + { "time": 0.4, "x": 195.69, "curve": "stepped" }, + { "time": 0.4667, "x": 171.08, "curve": "stepped" }, + { "time": 0.5, "x": 144.84, "curve": "stepped" }, + { "time": 0.5667, "x": 121.22, "curve": "stepped" }, + { "time": 0.6, "x": 91.98, "curve": "stepped" }, + { "time": 0.6667, "x": 62.63, "curve": "stepped" }, + { "time": 0.7, "x": 30.78, "curve": "stepped" }, + { "time": 0.7667, "curve": "stepped" }, + { "time": 0.8, "x": -28.45, "curve": "stepped" }, + { "time": 0.8667, "x": -67.49, "y": 16.82, "curve": "stepped" }, + { "time": 0.9, "x": -83.07, "y": 24.36, "curve": "stepped" }, + { "time": 0.9667, "x": -93.81, "y": 29.55 } + ], + "scale": [ + { "time": 0.0667, "x": 0.535, "curve": "stepped" }, + { "time": 0.1, "x": 0.594, "curve": "stepped" }, + { "time": 0.1667, "x": 0.844, "curve": "stepped" }, + { "time": 0.2, "curve": "stepped" }, + { "time": 0.8667, "x": 0.534, "curve": "stepped" }, + { "time": 0.9, "x": 0.428, "y": 0.801, "curve": "stepped" }, + { "time": 0.9667, "x": 0.349, "y": 0.654 } + ] + }, + "torso": { + "rotate": [ + { + "value": -34.73, + "curve": [ 0.034, -36.31, 0.162, -39.33 ] + }, + { + "time": 0.2667, + "value": -39.37, + "curve": [ 0.384, -39.37, 0.491, -29.52 ] + }, + { + "time": 0.5, + "value": -28.86, + "curve": [ 0.525, -26.95, 0.571, -21.01 ] + }, + { + "time": 0.6333, + "value": -21.01, + "curve": [ 0.725, -21.01, 0.969, -33.35 ] + }, + { "time": 1, "value": -34.73 } + ] + }, + "neck": { + "rotate": [ + { + "value": 10.2, + "curve": [ 0.07, 12.09, 0.189, 16.03 ] + }, + { + "time": 0.2667, + "value": 16.14, + "curve": [ 0.333, 16.14, 0.449, 8.03 ] + }, + { + "time": 0.5, + "value": 5.83, + "curve": [ 0.542, 4.02, 0.6, 2.68 ] + }, + { + "time": 0.6333, + "value": 2.68, + "curve": [ 0.725, 2.68, 0.943, 8.57 ] + }, + { "time": 1, "value": 10.2 } + ] + }, + "head": { + "rotate": [ + { + "value": 10.2, + "curve": [ 0.044, 11.52, 0.2, 16.12 ] + }, + { + "time": 0.2667, + "value": 16.14, + "curve": [ 0.375, 16.17, 0.492, 2.65 ] + }, + { + "time": 0.6333, + "value": 2.68, + "curve": [ 0.725, 2.7, 0.963, 9.26 ] + }, + { "time": 1, "value": 10.2 } + ], + "translate": [ + { + "curve": [ 0.03, -0.24, 0.2, -4.22, 0.051, -1.06, 0.2, -3.62 ] + }, + { + "time": 0.2667, + "x": -4.22, + "y": -3.62, + "curve": [ 0.358, -4.22, 0.542, 0.84, 0.358, -3.62, 0.542, 6.01 ] + }, + { + "time": 0.6333, + "x": 0.84, + "y": 6.01, + "curve": [ 0.725, 0.84, 0.939, 0.32, 0.725, 6.01, 0.945, 1.14 ] + }, + { "time": 1 } + ] + }, + "front-bracer": { + "rotate": [ + { + "value": -11.18, + "curve": [ 0.064, -14.82, 0.25, -20.01 ] + }, + { + "time": 0.3333, + "value": -20.01, + "curve": [ 0.429, -20.12, 0.58, 5.12 ] + }, + { + "time": 0.6, + "value": 8.67, + "curve": [ 0.617, 11.72, 0.687, 20.52 ] + }, + { + "time": 0.7667, + "value": 20.55, + "curve": [ 0.848, 20.7, 0.963, -9.43 ] + }, + { "time": 1, "value": -11.18 } + ] + }, + "hair3": { + "rotate": [ + { + "value": 9.61, + "curve": [ 0.014, 8.51, 0.075, 2.63 ] + }, + { + "time": 0.1, + "value": 2.63, + "curve": [ 0.15, 2.63, 0.25, 13.52 ] + }, + { + "time": 0.3, + "value": 13.52, + "curve": [ 0.35, 13.52, 0.45, 11.28 ] + }, + { + "time": 0.5, + "value": 11.28, + "curve": [ 0.575, 11.28, 0.725, 18.13 ] + }, + { + "time": 0.8, + "value": 18.13, + "curve": [ 0.85, 18.13, 0.978, 11.07 ] + }, + { "time": 1, "value": 9.61 } + ] + }, + "hair4": { + "rotate": [ + { + "value": -17.7, + "curve": [ 0.008, -17.7, 0.025, -23.73 ] + }, + { + "time": 0.0333, + "value": -23.73, + "curve": [ 0.067, -23.73, 0.154, -4.4 ] + }, + { + "time": 0.1667, + "value": -1.92, + "curve": [ 0.197, 4.09, 0.236, 12.91 ] + }, + { + "time": 0.2667, + "value": 17.56, + "curve": [ 0.301, 22.68, 0.342, 27.97 ] + }, + { + "time": 0.3667, + "value": 27.97, + "curve": [ 0.4, 27.97, 0.467, -1.45 ] + }, + { + "time": 0.5, + "value": -1.45, + "curve": [ 0.517, -1.45, 0.55, 3.16 ] + }, + { + "time": 0.5667, + "value": 3.16, + "curve": [ 0.583, 3.16, 0.617, -8.9 ] + }, + { + "time": 0.6333, + "value": -8.9, + "curve": [ 0.642, -8.9, 0.658, -5.4 ] + }, + { + "time": 0.6667, + "value": -5.4, + "curve": [ 0.683, -5.4, 0.717, -15.32 ] + }, + { + "time": 0.7333, + "value": -15.32, + "curve": [ 0.75, -15.32, 0.783, -9.19 ] + }, + { + "time": 0.8, + "value": -9.19, + "curve": [ 0.817, -9.19, 0.85, -23.6 ] + }, + { + "time": 0.8667, + "value": -23.6, + "curve": [ 0.883, -23.6, 0.917, -17.38 ] + }, + { + "time": 0.9333, + "value": -17.38, + "curve": [ 0.942, -17.38, 0.958, -20.46 ] + }, + { + "time": 0.9667, + "value": -20.46, + "curve": [ 0.975, -20.46, 0.992, -17.7 ] + }, + { "time": 1, "value": -17.7 } + ] + }, + "hair1": { + "rotate": [ + { + "value": 9.61, + "curve": [ 0.06, 9.04, 0.25, 8.9 ] + }, + { + "time": 0.3333, + "value": 8.9, + "curve": [ 0.392, 8.9, 0.508, 14.58 ] + }, + { + "time": 0.5667, + "value": 14.58, + "curve": [ 0.675, 14.58, 0.956, 10.28 ] + }, + { "time": 1, "value": 9.61 } + ] + }, + "hair2": { + "rotate": [ + { + "value": -3.82, + "curve": [ 0.017, -3.82, 0.064, -9.16 ] + }, + { + "time": 0.1333, + "value": -9.09, + "curve": [ 0.178, -9.04, 0.234, 1.29 ] + }, + { + "time": 0.2667, + "value": 5.98, + "curve": [ 0.276, 7.27, 0.336, 17.1 ] + }, + { + "time": 0.3667, + "value": 17.1, + "curve": [ 0.413, 17.1, 0.467, 1.59 ] + }, + { + "time": 0.5, + "value": 1.59, + "curve": [ 0.533, 1.59, 0.567, 13.63 ] + }, + { + "time": 0.6, + "value": 13.63, + "curve": [ 0.617, 13.63, 0.683, 0.78 ] + }, + { + "time": 0.7, + "value": 0.78, + "curve": [ 0.717, 0.78, 0.75, 12.01 ] + }, + { + "time": 0.7667, + "value": 11.9, + "curve": [ 0.792, 11.73, 0.817, -0.85 ] + }, + { + "time": 0.8333, + "value": -0.85, + "curve": [ 0.85, -0.85, 0.88, 1.99 ] + }, + { + "time": 0.9, + "value": 1.82, + "curve": [ 0.916, 1.68, 0.95, -6.9 ] + }, + { + "time": 0.9667, + "value": -6.9, + "curve": [ 0.975, -6.9, 0.992, -3.82 ] + }, + { "time": 1, "value": -3.82 } + ] + }, + "rear-upper-arm": { + "rotate": [ + { + "value": 31.65, + "curve": [ 0.108, 31.65, 0.325, 13.01 ] + }, + { + "time": 0.4333, + "value": 13.01, + "curve": [ 0.71, 13.01, 0.917, 31.65 ] + }, + { "time": 1, "value": 31.65 } + ] + }, + "rear-bracer": { + "rotate": [ + { + "value": 31, + "curve": [ 0.108, 31, 0.325, 12.76 ] + }, + { + "time": 0.4333, + "value": 12.79, + "curve": [ 0.587, 12.82, 0.917, 31 ] + }, + { "time": 1, "value": 31 } + ] + }, + "gun": { + "rotate": [ + { + "value": 1.95, + "curve": [ 0.083, 1.95, 0.245, 36.73 ] + }, + { + "time": 0.3333, + "value": 36.71, + "curve": [ 0.439, 36.69, 0.589, 10.68 ] + }, + { + "time": 0.6333, + "value": 8.75, + "curve": [ 0.701, 5.81, 0.917, 1.95 ] + }, + { "time": 1, "value": 1.95 } + ] + }, + "torso2": { + "rotate": [ + { + "curve": [ 0.033, 0, 0.1, 2.35 ] + }, + { + "time": 0.1333, + "value": 2.35, + "curve": [ 0.225, 2.35, 0.408, -2.4 ] + }, + { + "time": 0.5, + "value": -2.4, + "curve": [ 0.567, -2.4, 0.7, 1.44 ] + }, + { + "time": 0.7667, + "value": 1.44, + "curve": [ 0.825, 1.44, 0.942, 0 ] + }, + { "time": 1 } + ] + }, + "torso3": { + "rotate": [ + { + "curve": [ 0.063, 0.77, 0.106, 1.42 ] + }, + { + "time": 0.1667, + "value": 1.42, + "curve": [ 0.259, 1.42, 0.344, -1.25 ] + }, + { + "time": 0.4667, + "value": -1.26, + "curve": [ 0.656, -1.26, 0.917, -0.78 ] + }, + { "time": 1 } + ] + }, + "head-control": { + "translate": [ + { + "x": 0.37, + "y": -11.17, + "curve": [ 0.133, 0.37, 0.335, -10.23, 0.133, -11.17, 0.335, 3.15 ] + }, + { + "time": 0.5333, + "x": -10.23, + "y": 3.15, + "curve": [ 0.71, -10.23, 0.883, 0.37, 0.71, 3.15, 0.883, -11.17 ] + }, + { "time": 1, "x": 0.37, "y": -11.17 } + ] + }, + "front-shoulder": { + "translate": [ + { + "x": 1.46, + "y": 10.15, + "curve": [ 0.103, 1.46, 0.249, 1.36, 0.103, 10.15, 0.249, -4.39 ] + }, + { + "time": 0.4, + "x": 1.36, + "y": -4.39, + "curve": [ 0.621, 1.36, 0.85, 1.46, 0.621, -4.39, 0.85, 10.15 ] + }, + { "time": 1, "x": 1.46, "y": 10.15 } + ] + }, + "back-shoulder": { + "translate": [ + { + "x": 1.4, + "y": 0.44, + "curve": [ 0.088, 1.4, 0.208, -2.47, 0.088, 0.44, 0.208, 8.61 ] + }, + { + "time": 0.3333, + "x": -2.47, + "y": 8.61, + "curve": [ 0.572, -2.47, 0.833, 1.4, 0.572, 8.61, 0.833, 0.44 ] + }, + { "time": 1, "x": 1.4, "y": 0.44 } + ] + } + }, + "transform": { + "front-foot-board-transform": [ + { "mixRotate": 0.997 } + ], + "rear-foot-board-transform": [ + {} + ], + "toes-board": [ + { "mixX": 0, "mixScaleX": 0, "mixShearY": 0 } + ] + }, + "attachments": { + "default": { + "front-foot": { + "front-foot": { + "deform": [ + { + "offset": 26, + "vertices": [ -0.02832, -5.37024, -0.02832, -5.37024, 3.8188, -3.7757, -0.02832, -5.37024, -3.82159, 3.77847 ] + } + ] + } + }, + "front-shin": { + "front-shin": { + "deform": [ + { + "offset": 14, + "vertices": [ 0.5298, -1.12677, -0.85507, -4.20587, -11.35158, -10.19225, -10.79865, -8.43765, -6.06447, -6.89757, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.54892, -3.06021, 1.48463, -2.29663, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -4.80437, -7.01817 ] + }, + { + "time": 0.3667, + "offset": 14, + "vertices": [ 0.5298, -1.12677, -11.66571, -9.07211, -25.65866, -17.53735, -25.53217, -16.50978, -11.78232, -11.26097, 0, 0, 0.60487, -1.63589, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.60487, -1.63589, 0, 0, -2.64522, -7.35739, 1.48463, -2.29663, 0, 0, 0, 0, 0, 0, 0.60487, -1.63589, 0.60487, -1.63589, 0.60487, -1.63589, 0.60487, -1.63589, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.60487, -1.63589, 0, 0, -10.06873, -12.0999 ] + }, + { + "time": 0.5333, + "offset": 14, + "vertices": [ 0.5298, -1.12677, -0.85507, -4.20587, -7.00775, -8.24771, -6.45482, -6.49312, -6.06447, -6.89757, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.54892, -3.06021, 1.48463, -2.29663, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -4.80437, -7.01817 ] + }, + { + "time": 1, + "offset": 14, + "vertices": [ 0.5298, -1.12677, -0.85507, -4.20587, -11.35158, -10.19225, -10.79865, -8.43765, -6.06447, -6.89757, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.54892, -3.06021, 1.48463, -2.29663, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -4.80437, -7.01817 ] + } + ] + } + }, + "hoverboard-board": { + "hoverboard-board": { + "deform": [ + { + "curve": [ 0.067, 0, 0.2, 1 ] + }, + { + "time": 0.2667, + "offset": 1, + "vertices": [ 2.45856, 0, 0, 0, 0, 0, 0, 0, 0, 3.55673, -3.0E-4, 3.55673, -3.0E-4, 0, 0, 0, 0, 0, 0, -7.6E-4, -9.84158, -7.6E-4, -9.84158, -7.6E-4, -9.84158, -7.6E-4, -9.84158, -7.6E-4, -9.84158, -7.6E-4, -9.84158, -7.6E-4, -9.84158, -7.6E-4, -9.84158, -7.6E-4, -9.84158, -7.6E-4, -9.84158, -7.6E-4, -9.84158, -7.6E-4, -9.84158, 0, 0, 0, 0, 0, 0, 0, 0, -4.90558, 0.11214, -9.40706, 6.2E-4, -6.34871, 4.3E-4, -6.34925, -6.57018, -6.34925, -6.57018, -6.34871, 4.3E-4, -2.3308, 1.7E-4, -2.33133, -6.57045, -2.33133, -6.57045, -2.3308, 1.7E-4, 0, 0, 1.2E-4, 2.45856, 1.2E-4, 2.45856, 1.2E-4, 2.45856, 1.2E-4, 2.45856, 3.3297, 4.44005, 3.3297, 4.44005, 3.3297, 4.44005, 1.2E-4, 2.45856, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2.46227, 1.7E-4, -2.46227, 1.7E-4, -2.52316, 1.1313, -2.52316, 1.1313, -2.52316, 1.1313, 1.2E-4, 2.45856, 1.2E-4, 2.45856, -9.40694, 2.45918, 1.88063, 0.44197, -2.9E-4, -3.54808, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2.52316, 1.1313, -2.52316, 1.1313, -2.52316, 1.1313, -2.46227, 1.7E-4, -2.46227, 1.7E-4, -2.46227, 1.7E-4, 0, 0, 0, 0, 1.2E-4, 2.45856 ], + "curve": [ 0.45, 0, 0.817, 1 ] + }, + { "time": 1 } + ] + } + }, + "rear-foot": { + "rear-foot": { + "deform": [ + { + "offset": 28, + "vertices": [ -1.93078, 1.34782, -0.31417, 2.33363, 3.05122, 0.33946, 2.31472, -2.01678, 2.17583, -2.05795, -0.04277, -2.99459, 1.15429, 0.26328, 0.97501, -0.67169 ] + } + ] + } + } + } + } + }, + "idle": { + "slots": { + "front-fist": { + "attachment": [ + { "name": "front-fist-open" } + ] + } + }, + "bones": { + "front-foot-target": { + "translate": [ + { "x": -69.06 } + ] + }, + "hip": { + "rotate": [ + { + "curve": [ 0.073, 0.35, 0.303, 1.27 ] + }, + { + "time": 0.4, + "value": 1.28, + "curve": [ 0.615, 1.3, 0.847, -1.41 ] + }, + { + "time": 1.2, + "value": -1.38, + "curve": [ 1.344, -1.37, 1.602, -0.28 ] + }, + { "time": 1.6667 } + ], + "translate": [ + { + "x": -11.97, + "y": -23.15, + "curve": [ 0.059, -12.96, 0.258, -15.19, 0.142, -23.15, 0.341, -24.89 ] + }, + { + "time": 0.4667, + "x": -15.14, + "y": -26.74, + "curve": [ 0.62, -15.1, 0.788, -13.28, 0.597, -28.66, 0.75, -30.01 ] + }, + { + "time": 0.9, + "x": -12.02, + "y": -30.01, + "curve": [ 0.978, -11.13, 1.175, -9.05, 1.036, -29.94, 1.234, -28.08 ] + }, + { + "time": 1.3333, + "x": -9.06, + "y": -26.64, + "curve": [ 1.501, -9.06, 1.614, -10.95, 1.454, -24.89, 1.609, -23.15 ] + }, + { "time": 1.6667, "x": -11.97, "y": -23.15 } + ] + }, + "rear-foot-target": { + "translate": [ + { "x": 48.87 } + ] + }, + "front-upper-arm": { + "rotate": [ + { + "value": -60.87, + "curve": [ 0.154, -60.85, 0.452, -68.65 ] + }, + { + "time": 0.8333, + "value": -68.65, + "curve": [ 1.221, -68.65, 1.542, -60.87 ] + }, + { "time": 1.6667, "value": -60.87 } + ] + }, + "front-bracer": { + "rotate": [ + { + "value": 42.46, + "curve": [ 0.029, 42.97, 0.134, 45.28 ] + }, + { + "time": 0.3333, + "value": 45.27, + "curve": [ 0.578, 45.26, 0.798, 40.07 ] + }, + { + "time": 0.8333, + "value": 39.74, + "curve": [ 0.878, 39.32, 1.019, 38.23 ] + }, + { + "time": 1.2, + "value": 38.22, + "curve": [ 1.377, 38.22, 1.619, 41.68 ] + }, + { "time": 1.6667, "value": 42.46 } + ] + }, + "rear-upper-arm": { + "rotate": [ + { + "value": 39.2, + "curve": [ 0.185, 39.22, 0.5, 29.37 ] + }, + { + "time": 0.6667, + "value": 29.37, + "curve": [ 0.917, 29.37, 1.417, 39.2 ] + }, + { "time": 1.6667, "value": 39.2 } + ] + }, + "head": { + "rotate": [ + { + "value": -6.75, + "curve": [ 0.176, -7.88, 0.349, -8.95 ] + }, + { + "time": 0.4667, + "value": -8.95, + "curve": [ 0.55, -8.95, 0.697, -6.77 ] + }, + { + "time": 0.8333, + "value": -5.44, + "curve": [ 0.88, -4.98, 1.05, -4.12 ] + }, + { + "time": 1.1333, + "value": -4.12, + "curve": [ 1.266, -4.12, 1.469, -5.48 ] + }, + { "time": 1.6667, "value": -6.75 } + ] + }, + "front-fist": { + "rotate": [ + { + "curve": [ 0.086, 0, 0.233, 2.48 ] + }, + { + "time": 0.3333, + "value": 4.13, + "curve": [ 0.429, 5.7, 0.711, 10.06 ] + }, + { + "time": 0.8333, + "value": 10.06, + "curve": [ 0.926, 10.06, 1.092, 4.21 ] + }, + { + "time": 1.2, + "value": 2.78, + "curve": [ 1.349, 0.8, 1.551, 0 ] + }, + { "time": 1.6667 } + ] + }, + "rear-bracer": { + "rotate": [ + { + "curve": [ 0.063, 0.54, 0.367, 3.39 ] + }, + { + "time": 0.5333, + "value": 3.39, + "curve": [ 0.696, 3.39, 0.939, -1.63 ] + }, + { + "time": 1.2, + "value": -1.61, + "curve": [ 1.42, -1.59, 1.574, -0.67 ] + }, + { "time": 1.6667 } + ] + }, + "gun": { + "rotate": [ + { + "curve": [ 0.099, 0.27, 0.367, 1.23 ] + }, + { + "time": 0.5333, + "value": 1.23, + "curve": [ 0.665, 1.23, 0.937, -0.56 ] + }, + { + "time": 1.1333, + "value": -0.55, + "curve": [ 1.316, -0.55, 1.582, -0.21 ] + }, + { "time": 1.6667 } + ] + }, + "torso": { + "rotate": [ + { + "value": -22.88, + "curve": [ 0.099, -23.45, 0.363, -24.74 ] + }, + { + "time": 0.5333, + "value": -24.74, + "curve": [ 0.706, -24.74, 0.961, -20.97 ] + }, + { + "time": 1.1333, + "value": -20.97, + "curve": [ 1.355, -20.97, 1.567, -22.28 ] + }, + { "time": 1.6667, "value": -22.88 } + ] + }, + "neck": { + "rotate": [ + { + "value": 3.78, + "curve": [ 0.167, 3.78, 0.5, 5.45 ] + }, + { + "time": 0.6667, + "value": 5.45, + "curve": [ 0.917, 5.45, 1.417, 3.78 ] + }, + { "time": 1.6667, "value": 3.78 } + ] + }, + "torso2": { + "rotate": [ + { + "curve": [ 0.067, 0.33, 0.341, 2.54 ] + }, + { + "time": 0.5333, + "value": 2.54, + "curve": [ 0.734, 2.55, 0.982, -0.94 ] + }, + { + "time": 1.1333, + "value": -0.93, + "curve": [ 1.365, -0.91, 1.549, -0.56 ] + }, + { "time": 1.6667 } + ] + }, + "torso3": { + "rotate": [ + { + "value": -2.15, + "curve": [ 0.052, -1.9, 0.384, -0.15 ] + }, + { + "time": 0.5333, + "value": -0.14, + "curve": [ 0.762, -0.13, 0.895, -3.1 ] + }, + { + "time": 1.1333, + "value": -3.1, + "curve": [ 1.348, -3.1, 1.592, -2.46 ] + }, + { "time": 1.6667, "value": -2.15 } + ] + }, + "hair1": { + "rotate": [ + { + "curve": [ 0.067, 0, 0.213, 2.86 ] + }, + { + "time": 0.2667, + "value": 3.65, + "curve": [ 0.358, 4.99, 0.535, 7.92 ] + }, + { + "time": 0.6667, + "value": 7.92, + "curve": [ 0.809, 7.92, 1.067, 5.49 ] + }, + { + "time": 1.1333, + "value": 4.7, + "curve": [ 1.245, 3.34, 1.525, 0 ] + }, + { "time": 1.6667 } + ] + }, + "hair2": { + "rotate": [ + { + "curve": [ 0.067, 0, 0.225, -7.97 ] + }, + { + "time": 0.2667, + "value": -9.75, + "curve": [ 0.316, -11.84, 0.519, -16.66 ] + }, + { + "time": 0.6667, + "value": -16.66, + "curve": [ 0.817, -16.66, 1.029, -11.43 ] + }, + { + "time": 1.1333, + "value": -9.14, + "curve": [ 1.25, -6.56, 1.525, 0 ] + }, + { "time": 1.6667 } + ] + }, + "hair3": { + "rotate": [ + { + "curve": [ 0.1, 0, 0.3, 1.32 ] + }, + { + "time": 0.4, + "value": 1.32, + "curve": [ 0.55, 1.32, 0.866, 0.93 ] + }, + { + "time": 1, + "value": 0.73, + "curve": [ 1.189, 0.46, 1.5, 0 ] + }, + { "time": 1.6667 } + ] + }, + "hair4": { + "rotate": [ + { + "curve": [ 0.118, -0.44, 0.3, -8.52 ] + }, + { + "time": 0.4, + "value": -8.52, + "curve": [ 0.55, -8.52, 0.85, 1.96 ] + }, + { + "time": 1, + "value": 1.96, + "curve": [ 1.167, 1.96, 1.577, 0.38 ] + }, + { "time": 1.6667 } + ] + }, + "head-control": { + "translate": [ + { + "curve": [ 0.098, 1.46, 0.3, 4.49, 0.17, 0.13, 0.316, -3.28 ] + }, + { + "time": 0.4, + "x": 4.55, + "y": -5.95, + "curve": [ 0.53, 4.64, 0.776, 2.59, 0.492, -8.89, 0.668, -14.21 ] + }, + { + "time": 0.8667, + "x": 1.42, + "y": -14.26, + "curve": [ 0.966, 0.15, 1.109, -2.91, 0.994, -14.26, 1.144, -10.58 ] + }, + { + "time": 1.2333, + "x": -3.02, + "y": -8.26, + "curve": [ 1.342, -3.02, 1.568, -1.48, 1.317, -6.1, 1.558, 0 ] + }, + { "time": 1.6667 } + ] + }, + "front-shoulder": { + "translate": [ + { + "curve": [ 0.21, 0, 0.525, -1.72, 0.21, 0, 0.525, 4.08 ] + }, + { + "time": 0.8333, + "x": -1.72, + "y": 4.08, + "curve": [ 1.15, -1.72, 1.46, 0, 1.15, 4.08, 1.46, 0 ] + }, + { "time": 1.6667 } + ] + } + } + }, + "idle-turn": { + "slots": { + "front-fist": { + "attachment": [ + { "name": "front-fist-open" } + ] + } + }, + "bones": { + "front-upper-arm": { + "rotate": [ + { + "value": -302.77, + "curve": [ 0, -406.9, 0.125, -420.87 ] + }, + { "time": 0.2667, "value": -420.87 } + ], + "translate": [ + { + "x": 2.24, + "y": -4.98, + "curve": [ 0.067, 2.24, 0.111, 0, 0.067, -4.98, 0.111, 0 ] + }, + { "time": 0.2667 } + ] + }, + "rear-upper-arm": { + "rotate": [ + { + "value": 248.56, + "curve": [ 0, 371.28, 0.062, 399.2 ] + }, + { "time": 0.1333, "value": 399.2 } + ], + "translate": [ + { + "x": -2.84, + "y": 37.28, + "curve": [ 0.033, -2.84, 0.069, 0, 0.033, 37.28, 0.069, 0 ] + }, + { "time": 0.1333 } + ] + }, + "gun": { + "rotate": [ + { + "value": -3.95, + "curve": [ 0, -10.4, 0.019, -20.43 ] + }, + { + "time": 0.0333, + "value": -20.45, + "curve": [ 0.044, -20.47, 0.125, 0 ] + }, + { "time": 0.2 } + ] + }, + "neck": { + "rotate": [ + { + "value": 17.2, + "curve": [ 0, 6.27, 0.125, 3.78 ] + }, + { "time": 0.2667, "value": 3.78 } + ] + }, + "hip": { + "translate": [ + { + "x": -2.69, + "y": -6.79, + "curve": [ 0.067, -2.69, 0.2, -11.97, 0.067, -6.79, 0.2, -23.15 ] + }, + { "time": 0.2667, "x": -11.97, "y": -23.15 } + ] + }, + "front-fist": { + "rotate": [ + { + "value": -15.54, + "curve": [ 0, -3.08, 0.034, 18.44 ] + }, + { + "time": 0.0667, + "value": 19.02, + "curve": [ 0.108, 19.75, 0.169, 0 ] + }, + { "time": 0.2667 } + ], + "scale": [ + { + "x": 0.94, + "curve": [ 0, 0.962, 0.024, 1.237, 0, 1, 0.026, 0.947 ] + }, + { + "time": 0.0667, + "x": 1.236, + "y": 0.947, + "curve": [ 0.117, 1.235, 0.189, 1, 0.117, 0.947, 0.189, 1 ] + }, + { "time": 0.2667 } + ] + }, + "rear-bracer": { + "rotate": [ + { + "value": 11.75, + "curve": [ 0, -7.97, 0.017, -33.4 ] + }, + { + "time": 0.0333, + "value": -33.39, + "curve": [ 0.049, -33.37, 0.131, 0 ] + }, + { "time": 0.2 } + ] + }, + "torso": { + "rotate": [ + { + "value": -18.25, + "curve": [ 0, -10.59, 0.125, -22.88 ] + }, + { "time": 0.2667, "value": -22.88 } + ], + "scale": [ + { + "y": 1.03, + "curve": [ 0.067, 1, 0.132, 1, 0.067, 1.03, 0.132, 1 ] + }, + { "time": 0.2667 } + ] + }, + "head": { + "rotate": [ + { + "value": 5.12, + "curve": [ 0, -6.34, 0.125, -6.75 ] + }, + { "time": 0.2667, "value": -6.75 } + ], + "scale": [ + { + "y": 1.03, + "curve": [ 0.067, 1, 0.107, 1, 0.067, 1.03, 0.107, 1 ] + }, + { "time": 0.2667 } + ] + }, + "rear-foot-target": { + "translate": [ + { + "x": -58.39, + "y": 30.48, + "curve": [ 0, -7.15, 0.047, 16.62, 0, 12.71, 0.039, 0.22 ] + }, + { + "time": 0.1, + "x": 34.14, + "y": -0.19, + "curve": [ 0.136, 45.79, 0.163, 48.87, 0.133, -0.41, 0.163, 0 ] + }, + { "time": 0.2, "x": 48.87 } + ] + }, + "front-bracer": { + "rotate": [ + { + "value": 6.69, + "curve": [ 0, 19.76, 0.039, 56.53 ] + }, + { + "time": 0.0667, + "value": 56.63, + "curve": [ 0.114, 56.79, 0.189, 42.46 ] + }, + { "time": 0.2667, "value": 42.46 } + ] + }, + "front-foot-target": { + "rotate": [ + { + "value": -1.85, + "curve": [ 0.014, -8.91, 0.047, -28.4 ] + }, + { + "time": 0.1, + "value": -28.89, + "curve": [ 0.144, -29.29, 0.262, -21.77 ] + }, + { "time": 0.2667 } + ], + "translate": [ + { + "x": 9.97, + "y": 0.82, + "curve": [ 0, -54.41, 0.078, -69.06, 0, 0.15, 0.078, 0 ] + }, + { "time": 0.1667, "x": -69.06 } + ] + }, + "hair3": { + "rotate": [ + { + "value": -9.01, + "curve": [ 0.044, -9.01, 0.072, 7.41 ] + }, + { + "time": 0.1333, + "value": 10.08, + "curve": [ 0.166, 11.47, 0.208, 0 ] + }, + { "time": 0.2667 } + ] + }, + "hair4": { + "rotate": [ + { + "value": -16.49, + "curve": [ 0.044, -16.49, 0.101, -5.98 ] + }, + { + "time": 0.1333, + "value": -2.95, + "curve": [ 0.162, -0.34, 0.208, 0 ] + }, + { "time": 0.2667 } + ] + }, + "hair1": { + "rotate": [ + { + "value": -3.85, + "curve": [ 0.044, -3.85, 0.072, 6.91 ] + }, + { + "time": 0.1333, + "value": 8.05, + "curve": [ 0.166, 8.65, 0.208, 0 ] + }, + { "time": 0.2667 } + ] + }, + "hair2": { + "rotate": [ + { + "value": 1.25, + "curve": [ 0.044, 1.25, 0.072, 8.97 ] + }, + { + "time": 0.1333, + "value": 8.6, + "curve": [ 0.166, 8.4, 0.208, 0 ] + }, + { "time": 0.2667 } + ] + }, + "front-thigh": { + "translate": [ + { + "x": 12.21, + "y": 1.89, + "curve": [ 0.033, 12.21, 0.1, 0, 0.033, 1.89, 0.1, 0 ] + }, + { "time": 0.1333 } + ] + }, + "rear-thigh": { + "translate": [ + { + "x": -16.11, + "y": -1.38, + "curve": [ 0.033, -16.11, 0.1, 0, 0.033, -1.38, 0.1, 0 ] + }, + { "time": 0.1333 } + ] + }, + "torso3": { + "rotate": [ + { "time": 0.2667, "value": -2.15 } + ] + }, + "head-control": { + "translate": [ + { + "x": -13.72, + "y": -34.7, + "curve": [ 0.067, -13.72, 0.2, 0, 0.067, -34.7, 0.2, 0 ] + }, + { "time": 0.2667 } + ] + }, + "front-shoulder": { + "translate": [ + { + "x": 1.13, + "y": -14.31, + "curve": [ 0.067, 1.13, 0.2, 0, 0.067, -14.31, 0.2, 0 ] + }, + { "time": 0.2667 } + ] + } + } + }, + "jump": { + "slots": { + "front-fist": { + "attachment": [ + { "name": "front-fist-open" }, + { "time": 0.1, "name": "front-fist-closed" }, + { "time": 0.8333, "name": "front-fist-open" } + ] + } + }, + "bones": { + "front-thigh": { + "rotate": [ + { + "value": 55.08, + "curve": [ 0.007, 46.66, 0.043, 26.3 ] + }, + { + "time": 0.0667, + "value": 22.84, + "curve": [ 0.1, 17.99, 0.165, 15.78 ] + }, + { + "time": 0.2333, + "value": 15.71, + "curve": [ 0.309, 15.63, 0.408, 46.67 ] + }, + { + "time": 0.5, + "value": 63.6, + "curve": [ 0.56, 74.72, 0.762, 91.48 ] + }, + { + "time": 0.9667, + "value": 91.81, + "curve": [ 1.068, 92.01, 1.096, 22.05 ] + }, + { + "time": 1.1667, + "value": 22.25, + "curve": [ 1.18, 22.29, 1.176, 56.17 ] + }, + { + "time": 1.2, + "value": 56.16, + "curve": [ 1.246, 56.15, 1.263, 54.94 ] + }, + { "time": 1.3333, "value": 55.08 } + ], + "translate": [ + { "x": -5.13, "y": 11.55 } + ] + }, + "torso": { + "rotate": [ + { + "value": -45.57, + "curve": [ 0.022, -44.61, 0.03, -39.06 ] + }, + { + "time": 0.0667, + "value": -35.29, + "curve": [ 0.12, -29.77, 0.28, -19.95 ] + }, + { + "time": 0.4333, + "value": -19.95, + "curve": [ 0.673, -19.95, 0.871, -22.38 ] + }, + { + "time": 0.9667, + "value": -27.08, + "curve": [ 1.094, -33.33, 1.176, -44.93 ] + }, + { "time": 1.3333, "value": -45.57 } + ], + "translate": [ + { "x": -3.79, "y": -0.77 } + ] + }, + "rear-thigh": { + "rotate": [ + { + "value": 12.81, + "curve": [ 0.067, 12.81, 0.242, 67.88 ] + }, + { + "time": 0.2667, + "value": 74.11, + "curve": [ 0.314, 86.02, 0.454, 92.23 ] + }, + { + "time": 0.5667, + "value": 92.24, + "curve": [ 0.753, 92.26, 0.966, 67.94 ] + }, + { + "time": 1, + "value": 61.32, + "curve": [ 1.039, 53.75, 1.218, 12.68 ] + }, + { "time": 1.3333, "value": 12.81 } + ] + }, + "rear-shin": { + "rotate": [ + { + "value": -115.64, + "curve": [ 0.067, -117.17, 0.125, -117.15 ] + }, + { + "time": 0.1667, + "value": -117.15, + "curve": [ 0.225, -117.15, 0.332, -108.76 ] + }, + { + "time": 0.4, + "value": -107.15, + "curve": [ 0.48, -105.26, 0.685, -103.49 ] + }, + { + "time": 0.7667, + "value": -101.97, + "curve": [ 0.826, -100.87, 0.919, -92.3 ] + }, + { + "time": 1, + "value": -92.28, + "curve": [ 1.113, -92.26, 1.297, -114.22 ] + }, + { "time": 1.3333, "value": -115.64 } + ] + }, + "front-upper-arm": { + "rotate": [ + { + "value": -40.21, + "curve": [ 0.054, -35.46, 0.15, -31.12 ] + }, + { + "time": 0.2, + "value": -31.12, + "curve": [ 0.308, -31.12, 0.547, -80.12 ] + }, + { + "time": 0.6333, + "value": -96.56, + "curve": [ 0.697, -108.56, 0.797, -112.54 ] + }, + { + "time": 0.8667, + "value": -112.6, + "curve": [ 1.137, -112.84, 1.274, -49.19 ] + }, + { "time": 1.3333, "value": -40.21 } + ] + }, + "front-bracer": { + "rotate": [ + { + "value": 20.54, + "curve": [ 0.054, 32.23, 0.192, 55.84 ] + }, + { + "time": 0.2333, + "value": 62.58, + "curve": [ 0.29, 71.87, 0.375, 79.28 ] + }, + { + "time": 0.4333, + "value": 79.18, + "curve": [ 0.555, 78.98, 0.684, 27.54 ] + }, + { + "time": 0.7333, + "value": 13.28, + "curve": [ 0.786, -1.85, 0.874, -24.76 ] + }, + { + "time": 1, + "value": -25.45, + "curve": [ 1.165, -26.36, 1.303, 9.1 ] + }, + { "time": 1.3333, "value": 20.54 } + ] + }, + "front-fist": { + "rotate": [ + { + "value": -36.16, + "curve": [ 0.114, -39.59, 0.3, -45.61 ] + }, + { + "time": 0.4, + "value": -45.61, + "curve": [ 0.442, -45.61, 0.537, -21.54 ] + }, + { + "time": 0.5667, + "value": -15.4, + "curve": [ 0.592, -10.23, 0.692, 11.89 ] + }, + { + "time": 0.7333, + "value": 11.73, + "curve": [ 0.783, 11.54, 0.831, 1.8 ] + }, + { + "time": 0.8667, + "value": -5.78, + "curve": [ 0.897, -12.22, 0.901, -14.22 ] + }, + { + "time": 0.9333, + "value": -14.51, + "curve": [ 0.974, -14.89, 0.976, 10.38 ] + }, + { + "time": 1, + "value": 10.55, + "curve": [ 1.027, 10.74, 1.023, -8.44 ] + }, + { + "time": 1.0333, + "value": -8.42, + "curve": [ 1.059, -8.36, 1.074, 10.12 ] + }, + { + "time": 1.1, + "value": 10.22, + "curve": [ 1.168, 10.48, 1.27, -36.07 ] + }, + { "time": 1.3333, "value": -36.16 } + ] + }, + "rear-upper-arm": { + "rotate": [ + { + "value": 40.5, + "curve": [ 0.048, 36.1, 0.168, 20.45 ] + }, + { + "time": 0.3, + "value": 20.45, + "curve": [ 0.476, 20.45, 0.571, 33.76 ] + }, + { + "time": 0.6, + "value": 38.67, + "curve": [ 0.642, 45.8, 0.681, 57.44 ] + }, + { + "time": 0.7333, + "value": 62.91, + "curve": [ 0.829, 72.8, 0.996, 77.61 ] + }, + { + "time": 1.0333, + "value": 80.37, + "curve": [ 1.082, 83.94, 1.148, 90.6 ] + }, + { + "time": 1.2, + "value": 90.6, + "curve": [ 1.248, 90.46, 1.317, 53.07 ] + }, + { "time": 1.3333, "value": 49.06 } + ] + }, + "rear-bracer": { + "rotate": [ + { + "value": 28.28, + "curve": [ 0.022, 25.12, 0.187, -0.89 ] + }, + { + "time": 0.2, + "value": -2.52, + "curve": [ 0.257, -9.92, 0.372, -17.38 ] + }, + { + "time": 0.4333, + "value": -17.41, + "curve": [ 0.54, -17.47, 0.659, -16.91 ] + }, + { + "time": 0.7667, + "value": -12.1, + "curve": [ 0.907, -5.79, 1.025, 14.58 ] + }, + { + "time": 1.1, + "value": 20.58, + "curve": [ 1.191, 27.85, 1.283, 29.67 ] + }, + { "time": 1.3333, "value": 29.67 } + ] + }, + "neck": { + "rotate": [ + { + "value": 11.88, + "curve": [ 0.104, 11.82, 0.179, 11.15 ] + }, + { + "time": 0.2, + "value": 10.08, + "curve": [ 0.255, 7.29, 0.405, -8.15 ] + }, + { + "time": 0.4333, + "value": -9.35, + "curve": [ 0.508, -12.48, 0.595, -13.14 ] + }, + { + "time": 0.6667, + "value": -12.61, + "curve": [ 0.714, -12.26, 0.815, -5.57 ] + }, + { + "time": 0.8333, + "value": -4.08, + "curve": [ 0.883, -0.07, 1.045, 12.77 ] + }, + { + "time": 1.1, + "value": 15.06, + "curve": [ 1.208, 19.6, 1.279, 20.64 ] + }, + { "time": 1.3333, "value": 20.73 } + ] + }, + "head": { + "rotate": [ + { + "value": 13.14, + "curve": [ 0.008, 12.19, 0.197, -23.53 ] + }, + { + "time": 0.3333, + "value": -23.95, + "curve": [ 0.509, -23.95, 0.667, -2.66 ] + }, + { + "time": 0.7333, + "value": -2.66, + "curve": [ 0.792, -2.66, 0.908, -13.32 ] + }, + { + "time": 0.9667, + "value": -13.32, + "curve": [ 1.158, -13.11, 1.241, -1.58 ] + }, + { "time": 1.3333, "value": -1.58 } + ], + "scale": [ + { + "curve": [ 0.041, 1, 0.052, 0.962, 0.041, 1, 0.052, 1.137 ] + }, + { + "time": 0.1, + "x": 0.954, + "y": 1.137, + "curve": [ 0.202, 0.962, 0.318, 1, 0.202, 1.137, 0.252, 1.002 ] + }, + { "time": 0.4667 }, + { + "time": 1.0667, + "x": 1.002, + "curve": [ 1.092, 1.002, 1.126, 1.143, 1.092, 1, 1.128, 0.975 ] + }, + { + "time": 1.1667, + "x": 1.144, + "y": 0.973, + "curve": [ 1.204, 1.145, 1.233, 0.959, 1.206, 0.972, 1.227, 1.062 ] + }, + { + "time": 1.2667, + "x": 0.958, + "y": 1.063, + "curve": [ 1.284, 0.958, 1.292, 1.001, 1.288, 1.063, 1.288, 1.001 ] + }, + { "time": 1.3333 } + ] + }, + "hip": { + "translate": [ + { + "y": -45.46, + "curve": [ 0.042, -0.09, 0.15, 15.22, 0.031, 44.98, 0.123, 289.73 ] + }, + { + "time": 0.2, + "x": 15.22, + "y": 415.85, + "curve": [ 0.332, 15.22, 0.539, -34.52, 0.271, 532.93, 0.483, 720.5 ] + }, + { + "time": 0.7667, + "x": -34.52, + "y": 721.6, + "curve": [ 0.888, -34.52, 1.057, -21.95, 1.049, 721.17, 1.098, 379.84 ] + }, + { + "time": 1.1333, + "x": -15.67, + "y": 266.77, + "curve": [ 1.144, -14.77, 1.188, -10.53, 1.15, 213.72, 1.172, -61.32 ] + }, + { + "time": 1.2333, + "x": -6.53, + "y": -61.34, + "curve": [ 1.272, -3.22, 1.311, 0.05, 1.291, -61.36, 1.296, -44.8 ] + }, + { "time": 1.3333, "y": -45.46 } + ] + }, + "front-shin": { + "rotate": [ + { + "value": -74.19, + "curve": [ 0, -51.14, 0.042, -12.54 ] + }, + { + "time": 0.1667, + "value": -12.28, + "curve": [ 0.285, -12.32, 0.37, -74.44 ] + }, + { + "time": 0.4333, + "value": -92.92, + "curve": [ 0.498, -111.86, 0.617, -140.28 ] + }, + { + "time": 0.9, + "value": -140.84, + "curve": [ 1.004, -141.04, 1.09, -47.87 ] + }, + { + "time": 1.1, + "value": -37.44, + "curve": [ 1.108, -29.83, 1.14, -21.18 ] + }, + { + "time": 1.1667, + "value": -21.08, + "curve": [ 1.18, -21.03, 1.191, -50.65 ] + }, + { + "time": 1.2, + "value": -53.17, + "curve": [ 1.22, -58.53, 1.271, -73.38 ] + }, + { "time": 1.3333, "value": -74.19 } + ] + }, + "front-foot": { + "rotate": [ + { + "value": 7.35, + "curve": [ 0, 4.8, 0.05, -26.64 ] + }, + { + "time": 0.0667, + "value": -26.64, + "curve": [ 0.192, -26.64, 0.442, -11.77 ] + }, + { + "time": 0.5667, + "value": -11.77, + "curve": [ 0.692, -11.77, 0.942, -19.36 ] + }, + { + "time": 1.0667, + "value": -19.36, + "curve": [ 1.133, -19.36, 1.32, 3.82 ] + }, + { "time": 1.3333, "value": 7.35 } + ] + }, + "rear-foot": { + "rotate": [ + { "value": -7.14 } + ] + }, + "gun": { + "rotate": [ + { + "value": 12.36, + "curve": [ 0.022, 16.28, 0.15, 30.81 ] + }, + { + "time": 0.2, + "value": 30.81, + "curve": [ 0.258, 30.81, 0.375, 13.26 ] + }, + { + "time": 0.4333, + "value": 13.26, + "curve": [ 0.508, 13.26, 0.658, 15.05 ] + }, + { + "time": 0.7333, + "value": 14.98, + "curve": [ 0.789, 14.94, 0.828, 13.62 ] + }, + { + "time": 0.8667, + "value": 12.72, + "curve": [ 0.887, 12.25, 0.984, 9.83 ] + }, + { + "time": 1.0333, + "value": 8.6, + "curve": [ 1.045, 8.31, 1.083, 7.55 ] + }, + { + "time": 1.1333, + "value": 7.13, + "curve": [ 1.175, 6.78, 1.283, 6.18 ] + }, + { "time": 1.3333, "value": 6.18 } + ] + }, + "front-leg-target": { + "translate": [ + { "x": -13.95, "y": -30.34 } + ] + }, + "rear-leg-target": { + "rotate": [ + { "value": -38.43 } + ], + "translate": [ + { "x": 85, "y": -33.59 } + ] + }, + "front-foot-target": { + "rotate": [ + { "value": -62.54 } + ], + "translate": [ + { "x": 16.34, "y": 0.18 } + ] + }, + "rear-foot-target": { + "rotate": [ + { "value": 18.55 } + ], + "translate": [ + { "x": -176.39, "y": 134.12 } + ] + }, + "back-foot-tip": { + "rotate": [ + { + "value": -143.73, + "curve": [ 0.083, -144.24, 0.167, -74.26 ] + }, + { + "time": 0.2667, + "value": -52.76, + "curve": [ 0.342, -36.57, 0.513, -36.57 ] + }, + { + "time": 0.6333, + "value": -30.97, + "curve": [ 0.724, -26.78, 0.848, -17.06 ] + }, + { + "time": 0.9667, + "value": -16.74, + "curve": [ 1.167, -16.2, 1.272, -144.17 ] + }, + { "time": 1.3333, "value": -143.73 } + ] + }, + "front-foot-tip": { + "rotate": [ + { + "value": -1.57, + "curve": [ 0, -24.71, 0.162, -60.88 ] + }, + { + "time": 0.2667, + "value": -60.83, + "curve": [ 0.342, -60.8, 0.582, -43.5 ] + }, + { + "time": 0.7, + "value": -39.45, + "curve": [ 0.773, -36.94, 0.832, -36.78 ] + }, + { + "time": 0.9667, + "value": -36.6, + "curve": [ 1.054, -36.49, 1.092, -37.37 ] + }, + { + "time": 1.1667, + "value": -33.26, + "curve": [ 1.237, -29.37, 1.147, -1.41 ] + }, + { "time": 1.2, "value": -1.57 } + ] + }, + "hair3": { + "rotate": [ + { + "value": -6.81, + "curve": [ 0, 13.59, 0.117, 18.21 ] + }, + { + "time": 0.1333, + "value": 18.21, + "curve": [ 0.167, 18.21, 0.26, 12.95 ] + }, + { + "time": 0.3, + "value": 11.56, + "curve": [ 0.382, 8.7, 0.55, 9.43 ] + }, + { + "time": 0.6667, + "value": 9.32, + "curve": [ 0.843, 9.15, 0.918, -7.34 ] + }, + { "time": 1.3333, "value": -6.81 } + ], + "translate": [ + { + "time": 0.6667, + "curve": [ 0.781, 0, 0.972, 16.03, 0.781, 0, 0.972, 0.92 ] + }, + { + "time": 1.1333, + "x": 16.03, + "y": 0.92, + "curve": [ 1.211, 16.03, 1.281, 0, 1.211, 0.92, 1.281, 0 ] + }, + { "time": 1.3333 } + ] + }, + "hair4": { + "rotate": [ + { + "value": -6.81, + "curve": [ 0.001, -3.88, 0.063, 16.18 ] + }, + { + "time": 0.1667, + "value": 16.14, + "curve": [ 0.242, 16.1, 0.249, 16.07 ] + }, + { + "time": 0.3333, + "value": 13.46, + "curve": [ 0.442, 10.09, 0.573, -2.2 ] + }, + { + "time": 0.6, + "value": -6.04, + "curve": [ 0.614, -8.05, 0.717, -33.44 ] + }, + { + "time": 0.7667, + "value": -33.44, + "curve": [ 0.809, -33.44, 0.835, -31.32 ] + }, + { + "time": 0.8667, + "value": -27.36, + "curve": [ 0.874, -26.47, 0.903, -14.28 ] + }, + { + "time": 0.9333, + "value": -14.47, + "curve": [ 0.956, -14.62, 0.944, -25.91 ] + }, + { + "time": 1, + "value": -25.96, + "curve": [ 1.062, -26.02, 1.051, -1.87 ] + }, + { + "time": 1.0667, + "value": -1.87, + "curve": [ 1.096, -1.87, 1.096, -16.09 ] + }, + { + "time": 1.1333, + "value": -16.08, + "curve": [ 1.169, -16.08, 1.153, -3.38 ] + }, + { + "time": 1.2, + "value": -3.38, + "curve": [ 1.234, -3.38, 1.271, -6.07 ] + }, + { "time": 1.3333, "value": -6.07 } + ] + }, + "hair2": { + "rotate": [ + { + "value": -6.81, + "curve": [ 0, -3.17, 0.042, 16.33 ] + }, + { + "time": 0.0667, + "value": 16.33, + "curve": [ 0.21, 15.74, 0.208, -12.06 ] + }, + { + "time": 0.3333, + "value": -12.21, + "curve": [ 0.417, -12.3, 0.552, -3.98 ] + }, + { + "time": 0.6667, + "value": 1.52, + "curve": [ 0.726, 4.35, 0.817, 4.99 ] + }, + { + "time": 0.8667, + "value": 4.99, + "curve": [ 0.901, 4.99, 0.912, -29.05 ] + }, + { + "time": 0.9667, + "value": -27.45, + "curve": [ 0.987, -26.83, 1.018, -5.42 ] + }, + { + "time": 1.0667, + "value": -5.46, + "curve": [ 1.107, -5.22, 1.095, -33.51 ] + }, + { + "time": 1.1333, + "value": -33.28, + "curve": [ 1.162, -33.57, 1.192, 8.04 ] + }, + { + "time": 1.2667, + "value": 7.86, + "curve": [ 1.302, 7.77, 1.313, 2.7 ] + }, + { "time": 1.3333, "value": 2.7 } + ] + }, + "hair1": { + "rotate": [ + { + "value": -6.81, + "curve": [ 0.001, -3.12, 0.074, 14.66 ] + }, + { + "time": 0.1333, + "value": 14.66, + "curve": [ 0.188, 14.8, 0.293, 9.56 ] + }, + { + "time": 0.3333, + "value": 5.99, + "curve": [ 0.381, 1.72, 0.55, -11.11 ] + }, + { + "time": 0.6667, + "value": -11.11, + "curve": [ 0.833, -11.11, 0.933, 22.54 ] + }, + { + "time": 1.1, + "value": 22.54, + "curve": [ 1.158, 22.54, 1.275, -6.81 ] + }, + { "time": 1.3333, "value": -6.81 } + ] + }, + "torso2": { + "rotate": [ + { + "value": 4.52, + "curve": [ 0.013, 2.33, 0.092, -9.75 ] + }, + { + "time": 0.1333, + "value": -9.75, + "curve": [ 0.175, -9.75, 0.291, -1.26 ] + }, + { + "time": 0.3333, + "value": 0.96, + "curve": [ 0.359, 2.3, 0.543, 4.25 ] + }, + { + "time": 0.6, + "value": 4.68, + "curve": [ 0.683, 5.3, 0.771, 5.92 ] + }, + { + "time": 0.8333, + "value": 6.48, + "curve": [ 0.871, 6.82, 1.083, 11.37 ] + }, + { + "time": 1.1667, + "value": 11.37, + "curve": [ 1.208, 11.37, 1.317, 6.18 ] + }, + { "time": 1.3333, "value": 4.52 } + ], + "translate": [ + { + "curve": [ 0, 0, 0.082, -2.24, 0, 0, 0.082, -0.42 ] + }, + { + "time": 0.1667, + "x": -2.98, + "y": -0.56, + "curve": [ 0.232, -2.24, 0.298, 0, 0.232, -0.42, 0.298, 0 ] + }, + { "time": 0.3333, "curve": "stepped" }, + { + "time": 0.8667, + "curve": [ 0.889, 0, 0.912, 0.26, 0.889, 0, 0.912, 0.06 ] + }, + { + "time": 0.9333, + "x": 0.68, + "y": 0.23, + "curve": [ 1.016, 2.22, 1.095, 5.9, 1.023, 0.97, 1.095, 1.99 ] + }, + { + "time": 1.1667, + "x": 6.47, + "y": 2.18, + "curve": [ 1.23, 5.75, 1.286, 0, 1.23, 1.94, 1.286, 0 ] + }, + { "time": 1.3333 } + ] + }, + "torso3": { + "rotate": [ + { + "value": 4.52, + "curve": [ 0.025, 4.52, 0.075, -6.17 ] + }, + { + "time": 0.1, + "value": -6.17, + "curve": [ 0.175, -6.17, 0.381, -0.71 ] + }, + { + "time": 0.4, + "value": -0.25, + "curve": [ 0.447, 0.87, 0.775, 4.84 ] + }, + { + "time": 0.9, + "value": 4.84, + "curve": [ 1.008, 4.84, 1.225, 4.52 ] + }, + { "time": 1.3333, "value": 4.52 } + ] + }, + "head-control": { + "translate": [ + { + "curve": [ 0.138, -2.4, 0.227, -10.44, 0.123, 1.05, 0.227, 2.7 ] + }, + { + "time": 0.3667, + "x": -10.44, + "y": 2.7, + "curve": [ 0.484, -10.44, 0.585, -5.63, 0.484, 2.7, 0.629, -23.62 ] + }, + { + "time": 0.7333, + "x": -2.29, + "y": -26.61, + "curve": [ 0.818, -0.39, 0.962, 1.21, 0.858, -30.17, 0.972, -28.75 ] + }, + { + "time": 1.1, + "x": 1.25, + "y": -28.75, + "curve": [ 1.192, 1.28, 1.234, 0.98, 1.224, -28.75, 1.235, -2.15 ] + }, + { "time": 1.3333 } + ] + }, + "front-shoulder": { + "translate": [ + { + "curve": [ 0.031, -2.22, 0.065, -3.73, 0.02, -3.25, 0.065, -14.74 ] + }, + { + "time": 0.1, + "x": -3.73, + "y": -14.74, + "curve": [ 0.216, -3.73, 0.384, -0.17, 0.216, -14.74, 0.402, -12.51 ] + }, + { + "time": 0.5, + "x": 1.63, + "y": -9.51, + "curve": [ 0.632, 3.69, 0.935, 7.41, 0.585, -6.91, 0.909, 10.86 ] + }, + { + "time": 1.1, + "x": 7.45, + "y": 10.99, + "curve": [ 1.18, 7.46, 1.265, 2.86, 1.193, 11.05, 1.294, 3.38 ] + }, + { "time": 1.3333 } + ] + } + }, + "ik": { + "front-foot-ik": [ + { + "mix": 0, + "curve": [ 0.3, 0, 0.9, 1, 0.3, 0, 0.9, 0 ] + }, + { "time": 1.2 } + ], + "front-leg-ik": [ + { + "mix": 0, + "bendPositive": false, + "curve": [ 0.3, 0, 0.9, 1, 0.3, 0, 0.9, 0 ] + }, + { "time": 1.2, "bendPositive": false } + ], + "rear-foot-ik": [ + { "mix": 0 } + ], + "rear-leg-ik": [ + { "mix": 0, "bendPositive": false } + ] + }, + "events": [ + { "time": 1.2, "name": "footstep" } + ] + }, + "portal": { + "slots": { + "clipping": { + "attachment": [ + { "name": "clipping" } + ] + }, + "front-fist": { + "attachment": [ + { "name": "front-fist-open" } + ] + }, + "mouth": { + "attachment": [ + { "time": 0.9, "name": "mouth-grind" }, + { "time": 2.2667, "name": "mouth-smile" } + ] + }, + "portal-bg": { + "attachment": [ + { "name": "portal-bg" }, + { "time": 3 } + ] + }, + "portal-flare1": { + "attachment": [ + { "time": 1.1, "name": "portal-flare1" }, + { "time": 1.1333, "name": "portal-flare2" }, + { "time": 1.1667, "name": "portal-flare3" }, + { "time": 1.2, "name": "portal-flare1" }, + { "time": 1.2333, "name": "portal-flare2" }, + { "time": 1.2667, "name": "portal-flare1" }, + { "time": 1.3333 } + ] + }, + "portal-flare2": { + "attachment": [ + { "time": 1.1, "name": "portal-flare2" }, + { "time": 1.1333, "name": "portal-flare3" }, + { "time": 1.1667, "name": "portal-flare1" }, + { "time": 1.2, "name": "portal-flare2" }, + { "time": 1.2333, "name": "portal-flare3" }, + { "time": 1.2667 } + ] + }, + "portal-flare3": { + "attachment": [ + { "time": 1.2, "name": "portal-flare3" }, + { "time": 1.2333, "name": "portal-flare2" }, + { "time": 1.2667 } + ] + }, + "portal-flare4": { + "attachment": [ + { "time": 1.2, "name": "portal-flare2" }, + { "time": 1.2333, "name": "portal-flare1" }, + { "time": 1.2667, "name": "portal-flare2" }, + { "time": 1.3333 } + ] + }, + "portal-flare5": { + "attachment": [ + { "time": 1.2333, "name": "portal-flare3" }, + { "time": 1.2667, "name": "portal-flare1" }, + { "time": 1.3333 } + ] + }, + "portal-flare6": { + "attachment": [ + { "time": 1.2667, "name": "portal-flare3" }, + { "time": 1.3333 } + ] + }, + "portal-flare7": { + "attachment": [ + { "time": 1.1333, "name": "portal-flare2" }, + { "time": 1.1667 } + ] + }, + "portal-flare8": { + "attachment": [ + { "time": 1.2, "name": "portal-flare3" }, + { "time": 1.2333, "name": "portal-flare2" }, + { "time": 1.2667 } + ] + }, + "portal-flare9": { + "attachment": [ + { "time": 1.2, "name": "portal-flare2" }, + { "time": 1.2333, "name": "portal-flare3" }, + { "time": 1.2667, "name": "portal-flare1" }, + { "time": 1.3 } + ] + }, + "portal-flare10": { + "attachment": [ + { "time": 1.2, "name": "portal-flare2" }, + { "time": 1.2333, "name": "portal-flare1" }, + { "time": 1.2667, "name": "portal-flare3" }, + { "time": 1.3 } + ] + }, + "portal-shade": { + "attachment": [ + { "name": "portal-shade" }, + { "time": 3 } + ] + }, + "portal-streaks1": { + "attachment": [ + { "name": "portal-streaks1" }, + { "time": 3 } + ] + }, + "portal-streaks2": { + "attachment": [ + { "name": "portal-streaks2" }, + { "time": 3 } + ] + } + }, + "bones": { + "portal-root": { + "translate": [ + { + "x": -458.35, + "y": 105.19, + "curve": [ 0.333, -458.22, 0.669, -457.86, 0.934, 105.19, 0.671, 105.19 ] + }, + { + "time": 1, + "x": -456.02, + "y": 105.19, + "curve": [ 1.339, -454.14, 2.208, -447.28, 1.35, 105.19, 2.05, 105.19 ] + }, + { + "time": 2.4, + "x": -439.12, + "y": 105.19, + "curve": [ 2.463, -436.44, 2.502, -432.92, 2.487, 105.19, 2.512, 105.09 ] + }, + { + "time": 2.6, + "x": -432.58, + "y": 105.09, + "curve": [ 2.784, -431.94, 2.978, -446.6, 2.772, 105.09, 2.933, 105.19 ] + }, + { "time": 3.0333, "x": -457.42, "y": 105.19 } + ], + "scale": [ + { + "x": 0.003, + "y": 0.006, + "curve": [ 0.329, 0.044, 0.347, 0.117, 0.329, 0.097, 0.37, 0.249 ] + }, + { + "time": 0.4, + "x": 0.175, + "y": 0.387, + "curve": [ 0.63, 0.619, 0.663, 0.723, 0.609, 1.338, 0.645, 1.524 ] + }, + { + "time": 0.7333, + "x": 0.724, + "y": 1.52, + "curve": [ 0.798, 0.725, 0.907, 0.647, 0.797, 1.517, 0.895, 1.424 ] + }, + { + "time": 1, + "x": 0.645, + "y": 1.426, + "curve": [ 1.095, 0.643, 1.139, 0.688, 1.089, 1.428, 1.115, 1.513 ] + }, + { + "time": 1.2333, + "x": 0.685, + "y": 1.516, + "curve": [ 1.325, 0.683, 1.508, 0.636, 1.343, 1.518, 1.467, 1.4 ] + }, + { + "time": 1.6, + "x": 0.634, + "y": 1.401, + "curve": [ 1.728, 0.631, 1.946, 0.687, 1.722, 1.402, 1.924, 1.522 ] + }, + { + "time": 2.0667, + "x": 0.688, + "y": 1.522, + "curve": [ 2.189, 0.69, 2.289, 0.649, 2.142, 1.522, 2.265, 1.417 ] + }, + { + "time": 2.4, + "x": 0.65, + "y": 1.426, + "curve": [ 2.494, 0.651, 2.504, 0.766, 2.508, 1.434, 2.543, 1.566 ] + }, + { + "time": 2.6, + "x": 0.766, + "y": 1.568, + "curve": [ 2.73, 0.765, 3.006, 0.098, 2.767, 1.564, 2.997, 0.1 ] + }, + { "time": 3.0333, "x": 0.007, "y": 0.015 } + ] + }, + "portal-streaks1": { + "rotate": [ + {}, + { "time": 3.1667, "value": 1200 } + ], + "translate": [ + { + "x": 15.15, + "curve": [ 0.162, 15.15, 0.432, 12.6, 0.162, 0, 0.432, -3.86 ] + }, + { + "time": 0.6667, + "x": 10.9, + "y": -6.44, + "curve": [ 0.794, 9.93, 0.912, 9.21, 0.794, -7.71, 0.912, -8.66 ] + }, + { + "time": 1, + "x": 9.21, + "y": -8.66, + "curve": [ 1.083, 9.21, 1.25, 21.53, 1.083, -8.66, 1.265, -4.9 ] + }, + { + "time": 1.3333, + "x": 21.53, + "y": -3.19, + "curve": [ 1.5, 21.53, 1.939, 12.3, 1.446, -0.37, 1.9, 6.26 ] + }, + { + "time": 2.0667, + "x": 11.26, + "y": 6.26, + "curve": [ 2.239, 9.85, 2.389, 9.68, 2.208, 6.26, 2.523, 0.51 ] + }, + { + "time": 2.5667, + "x": 9.39, + "y": -0.8, + "curve": [ 2.657, 9.24, 2.842, 9.21, 2.646, -3.2, 2.842, -8.91 ] + }, + { "time": 2.9333, "x": 9.21, "y": -8.91 } + ], + "scale": [ + { + "curve": [ 0.167, 1, 0.5, 1.053, 0.167, 1, 0.5, 1.053 ] + }, + { + "time": 0.6667, + "x": 1.053, + "y": 1.053, + "curve": [ 0.833, 1.053, 1.167, 0.986, 0.833, 1.053, 1.167, 0.986 ] + }, + { + "time": 1.3333, + "x": 0.986, + "y": 0.986, + "curve": [ 1.5, 0.986, 1.833, 1.053, 1.5, 0.986, 1.833, 1.053 ] + }, + { "time": 2, "x": 1.053, "y": 1.053 } + ] + }, + "portal-streaks2": { + "rotate": [ + {}, + { "time": 3.1667, "value": 600 } + ], + "translate": [ + { "x": -2.11 }, + { "time": 1, "x": -2.11, "y": 6.63 }, + { "time": 1.9333, "x": -2.11 } + ], + "scale": [ + { + "x": 1.014, + "y": 1.014, + "curve": [ 0.229, 0.909, 0.501, 0.755, 0.242, 0.892, 0.502, 0.768 ] + }, + { + "time": 0.8667, + "x": 0.745, + "y": 0.745, + "curve": [ 1.282, 0.733, 2.021, 0.699, 1.27, 0.719, 2.071, 0.709 ] + }, + { + "time": 2.2, + "x": 0.7, + "y": 0.704, + "curve": [ 2.315, 0.7, 2.421, 0.794, 2.311, 0.701, 2.485, 0.797 ] + }, + { + "time": 2.5667, + "x": 0.794, + "y": 0.794, + "curve": [ 2.734, 0.794, 2.99, 0.323, 2.714, 0.789, 3.019, 0.341 ] + }, + { "time": 3.1667, "x": 0, "y": 0 } + ] + }, + "portal-shade": { + "translate": [ + { "x": -29.68 } + ], + "scale": [ + { "x": 0.714, "y": 0.714 } + ] + }, + "portal": { + "rotate": [ + {}, + { "time": 3.1667, "value": 600 } + ] + }, + "clipping": { + "translate": [ + { "x": -476.55, "y": 2.27 } + ], + "scale": [ + { "x": 0.983, "y": 1.197 } + ] + }, + "hip": { + "rotate": [ + { + "time": 1.0667, + "value": 22.74, + "curve": [ 1.163, 18.84, 1.77, 8.77 ] + }, + { + "time": 1.9, + "value": 7.82, + "curve": [ 2.271, 5.1, 2.89, 0 ] + }, + { "time": 3.1667 } + ], + "translate": [ + { "x": -899.41, "y": 4.47, "curve": "stepped" }, + { + "time": 1.0667, + "x": -694.16, + "y": 183.28, + "curve": [ 1.091, -602.08, 1.138, -427.59, 1.115, 185.6, 1.171, 133.18 ] + }, + { + "time": 1.2333, + "x": -316.97, + "y": 55.29, + "curve": [ 1.317, -220.27, 1.512, -123.21, 1.271, 8.68, 1.461, -83.18 ] + }, + { + "time": 1.6, + "x": -95.53, + "y": -112.23, + "curve": [ 1.718, -58.25, 2.037, -22.54, 1.858, -166.17, 2.109, -31.4 ] + }, + { + "time": 2.1667, + "x": -14.82, + "y": -31.12, + "curve": [ 2.294, -7.28, 2.442, -7.2, 2.274, -30.6, 2.393, -36.76 ] + }, + { + "time": 2.6, + "x": -7.2, + "y": -36.96, + "curve": [ 2.854, -7.2, 3.071, -11.87, 2.786, -36.27, 3.082, -22.98 ] + }, + { "time": 3.1667, "x": -11.97, "y": -23.15 } + ] + }, + "rear-foot-target": { + "rotate": [ + { "time": 1.0667, "value": 41.6, "curve": "stepped" }, + { + "time": 1.2333, + "value": 41.6, + "curve": [ 1.258, 41.6, 1.379, 35.46 ] + }, + { + "time": 1.4, + "value": 30.09, + "curve": [ 1.412, 27.04, 1.433, 10.65 ] + }, + { "time": 1.4333, "value": -0.28 }, + { "time": 1.6, "value": 2.44 } + ], + "translate": [ + { "x": -899.41, "y": 4.47, "curve": "stepped" }, + { + "time": 1.0667, + "x": -591.13, + "y": 438.46, + "curve": [ 1.076, -539.77, 1.206, -268.1, 1.117, 418.44, 1.21, 333.18 ] + }, + { + "time": 1.2333, + "x": -225.28, + "y": 304.53, + "curve": [ 1.265, -175.22, 1.393, -74.21, 1.296, 226.52, 1.401, 49.61 ] + }, + { + "time": 1.4333, + "x": -52.32, + "y": 0.2, + "curve": [ 1.454, -40.85, 1.616, 40.87, 1.466, 0.17, 1.614, 0.04 ] + }, + { "time": 1.6667, "x": 45.87, "y": 0.01 }, + { "time": 1.9333, "x": 48.87 } + ] + }, + "front-foot-target": { + "rotate": [ + { + "time": 1.0667, + "value": 32.08, + "curve": [ 1.108, 32.08, 1.192, 35.16 ] + }, + { + "time": 1.2333, + "value": 35.16, + "curve": [ 1.258, 35.16, 1.317, 2.23 ] + }, + { + "time": 1.3333, + "value": -4.74, + "curve": [ 1.351, -12.14, 1.429, -34.96 ] + }, + { + "time": 1.6, + "value": -34.77, + "curve": [ 1.765, -34.58, 1.897, -17.25 ] + }, + { "time": 1.9333 } + ], + "translate": [ + { "x": -899.41, "y": 4.47, "curve": "stepped" }, + { + "time": 1.0667, + "x": -533.93, + "y": 363.75, + "curve": [ 1.074, -480.85, 1.18, -261.31, 1.094, 362.3, 1.195, 267.77 ] + }, + { + "time": 1.2333, + "x": -201.23, + "y": 199.93, + "curve": [ 1.269, -161.38, 1.294, -140.32, 1.274, 126.67, 1.308, 77.12 ] + }, + { + "time": 1.3333, + "x": -124.08, + "y": 0.2, + "curve": [ 1.426, -85.6, 1.633, -69.06, 1.45, 0.48, 1.633, 0 ] + }, + { "time": 1.7333, "x": -69.06 } + ] + }, + "torso": { + "rotate": [ + { + "time": 1.0667, + "value": 27.02, + "curve": [ 1.187, 26.86, 1.291, 7.81 ] + }, + { + "time": 1.3333, + "value": -2.62, + "curve": [ 1.402, -19.72, 1.429, -48.64 ] + }, + { + "time": 1.4667, + "value": -56.31, + "curve": [ 1.509, -64.87, 1.62, -77.14 ] + }, + { + "time": 1.7333, + "value": -77.34, + "curve": [ 1.837, -76.89, 1.895, -71.32 ] + }, + { + "time": 2, + "value": -57.52, + "curve": [ 2.104, -43.83, 2.189, -28.59 ] + }, + { + "time": 2.3, + "value": -29.03, + "curve": [ 2.413, -29.48, 2.513, -36.79 ] + }, + { + "time": 2.6667, + "value": -36.79, + "curve": [ 2.814, -36.95, 2.947, -22.88 ] + }, + { "time": 3.1667, "value": -22.88 } + ] + }, + "neck": { + "rotate": [ + { + "time": 1.0667, + "value": -3.57, + "curve": [ 1.146, -3.66, 1.15, -13.5 ] + }, + { + "time": 1.2333, + "value": -13.5, + "curve": [ 1.428, -13.5, 1.443, 11.58 ] + }, + { + "time": 1.5667, + "value": 11.42, + "curve": [ 1.658, 11.3, 1.775, 3.78 ] + }, + { + "time": 1.8667, + "value": 3.78, + "curve": [ 1.92, 3.78, 2.036, 8.01 ] + }, + { + "time": 2.1, + "value": 7.93, + "curve": [ 2.266, 7.72, 2.42, 3.86 ] + }, + { + "time": 2.5333, + "value": 3.86, + "curve": [ 2.783, 3.86, 3.004, 3.78 ] + }, + { "time": 3.1667, "value": 3.78 } + ] + }, + "head": { + "rotate": [ + { + "time": 1.0667, + "value": 16.4, + "curve": [ 1.133, 9.9, 1.207, 1.87 ] + }, + { + "time": 1.3333, + "value": 1.67, + "curve": [ 1.46, 1.56, 1.547, 47.54 ] + }, + { + "time": 1.7333, + "value": 47.55, + "curve": [ 1.897, 47.56, 2.042, 5.68 ] + }, + { + "time": 2.0667, + "value": 0.86, + "curve": [ 2.074, -0.61, 2.086, -2.81 ] + }, + { + "time": 2.1, + "value": -5.31, + "curve": [ 2.145, -13.07, 2.216, -23.65 ] + }, + { + "time": 2.2667, + "value": -23.71, + "curve": [ 2.334, -23.79, 2.426, -13.43 ] + }, + { + "time": 2.4667, + "value": -9.18, + "curve": [ 2.498, -5.91, 2.604, 2.53 ] + }, + { + "time": 2.6667, + "value": 2.52, + "curve": [ 2.738, 2.24, 2.85, -8.76 ] + }, + { + "time": 2.9333, + "value": -8.67, + "curve": [ 3.036, -8.55, 3.09, -7.09 ] + }, + { "time": 3.1667, "value": -6.75 } + ], + "scale": [ + { + "time": 1.3333, + "curve": [ 1.392, 1, 1.526, 1, 1.392, 1, 1.508, 1.043 ] + }, + { + "time": 1.5667, + "x": 0.992, + "y": 1.043, + "curve": [ 1.598, 0.985, 1.676, 0.955, 1.584, 1.043, 1.672, 1.04 ] + }, + { + "time": 1.7333, + "x": 0.954, + "y": 1.029, + "curve": [ 1.843, 0.954, 1.933, 1, 1.825, 1.013, 1.933, 1 ] + }, + { "time": 2 } + ] + }, + "rear-upper-arm": { + "rotate": [ + { + "time": 0.9, + "value": 39.24, + "curve": [ 0.968, 39.93, 1.267, 85.31 ] + }, + { + "time": 1.4667, + "value": 112.27, + "curve": [ 1.555, 124.24, 1.576, 126.44 ] + }, + { + "time": 1.6333, + "value": 126.44, + "curve": [ 1.782, 126.44, 1.992, 94.55 ] + }, + { + "time": 2.1, + "value": 79.96, + "curve": [ 2.216, 64.26, 2.407, 34.36 ] + }, + { + "time": 2.5667, + "value": 33.38, + "curve": [ 2.815, 31.87, 3.1, 39.2 ] + }, + { "time": 3.1667, "value": 39.2 } + ] + }, + "back-foot-tip": { + "rotate": [ + { + "time": 1.0667, + "value": 56.07, + "curve": [ 1.138, 59.21, 1.192, 59.65 ] + }, + { + "time": 1.2333, + "value": 59.46, + "curve": [ 1.295, 59.17, 1.45, 22.54 ] + }, + { "time": 1.4667, "value": -0.84 } + ] + }, + "front-upper-arm": { + "rotate": [ + { + "time": 1.0667, + "value": 118.03, + "curve": [ 1.075, 93.64, 1.358, -34.03 ] + }, + { + "time": 1.6667, + "value": -33.94, + "curve": [ 1.808, -33.89, 1.879, -25 ] + }, + { + "time": 1.9667, + "value": -25.19, + "curve": [ 2.09, -25.46, 2.312, -34.58 ] + }, + { + "time": 2.3667, + "value": -38.36, + "curve": [ 2.465, -45.18, 2.557, -60.1 ] + }, + { + "time": 2.8333, + "value": -61.1, + "curve": [ 2.843, -61.06, 3.16, -60.87 ] + }, + { "time": 3.1667, "value": -60.87 } + ] + }, + "front-bracer": { + "rotate": [ + { + "time": 1.0667, + "value": 0.66, + "curve": [ 1.108, 0.66, 1.221, 44.95 ] + }, + { + "time": 1.2333, + "value": 49.25, + "curve": [ 1.263, 59.42, 1.342, 68.06 ] + }, + { + "time": 1.3667, + "value": 68.34, + "curve": [ 1.409, 68.8, 1.476, 4.9 ] + }, + { + "time": 1.5, + "value": -2.05, + "curve": [ 1.529, -10.3, 1.695, -15.95 ] + }, + { + "time": 1.7333, + "value": -17.38, + "curve": [ 1.807, -20.1, 1.878, -21.19 ] + }, + { + "time": 1.9333, + "value": -21.08, + "curve": [ 2.073, -20.8, 2.146, -7.63 ] + }, + { + "time": 2.1667, + "value": -3.64, + "curve": [ 2.186, 0.12, 2.275, 15.28 ] + }, + { + "time": 2.3333, + "value": 21.78, + "curve": [ 2.392, 28.31, 2.575, 37.66 ] + }, + { + "time": 2.7, + "value": 39.43, + "curve": [ 2.947, 42.93, 3.02, 42.46 ] + }, + { "time": 3.1667, "value": 42.46 } + ] + }, + "front-thigh": { + "translate": [ + { "time": 1.1, "x": -6.41, "y": 18.23, "curve": "stepped" }, + { "time": 1.1333, "x": -6.41, "y": 18.23 }, + { "time": 1.2, "x": 1.61, "y": 3.66 }, + { "time": 1.2333, "x": 4.5, "y": -3.15 }, + { "time": 1.3667, "x": -3.79, "y": 2.94 }, + { "time": 1.4, "x": -8.37, "y": 8.72 }, + { "time": 1.4333, "x": -11.26, "y": 16.99 }, + { "time": 1.4667, "x": -9.89, "y": 24.73, "curve": "stepped" }, + { "time": 1.8667, "x": -9.89, "y": 24.73 }, + { "time": 2.1 } + ] + }, + "front-foot-tip": { + "rotate": [ + { "time": 1.0667, "value": 42.55, "curve": "stepped" }, + { "time": 1.1333, "value": 42.55 }, + { "time": 1.2333, "value": 17.71 }, + { "time": 1.3667, "value": 3.63 }, + { "time": 1.4333 } + ] + }, + "rear-bracer": { + "rotate": [ + { + "time": 1.0667, + "value": 108.71, + "curve": [ 1.082, 108.29, 1.437, 50.73 ] + }, + { + "time": 1.5667, + "value": 24.87, + "curve": [ 1.62, 14.2, 1.66, -11.74 ] + }, + { + "time": 1.7333, + "value": -11.74, + "curve": [ 1.961, -11.73, 2.172, 1.66 ] + }, + { + "time": 2.2667, + "value": 7.88, + "curve": [ 2.331, 12.13, 2.439, 18.65 ] + }, + { + "time": 2.5333, + "value": 18.72, + "curve": [ 2.788, 18.91, 3.145, -0.3 ] + }, + { "time": 3.1667 } + ] + }, + "front-fist": { + "rotate": [ + { + "time": 1.1, + "value": 6.32, + "curve": [ 1.11, 3.31, 1.153, -5.07 ] + }, + { + "time": 1.2333, + "value": -5.13, + "curve": [ 1.311, -5.19, 1.364, 34.65 ] + }, + { + "time": 1.4667, + "value": 34.53, + "curve": [ 1.574, 34.41, 1.547, -55.78 ] + }, + { + "time": 1.8667, + "value": -54.7, + "curve": [ 1.947, -54.7, 2.03, -53.94 ] + }, + { + "time": 2.1333, + "value": -42.44, + "curve": [ 2.215, -33.42, 2.358, -4.43 ] + }, + { + "time": 2.4, + "value": 0.03, + "curve": [ 2.444, 4.66, 2.536, 8.2 ] + }, + { + "time": 2.6333, + "value": 8.2, + "curve": [ 2.733, 8.19, 2.804, -0.67 ] + }, + { + "time": 2.9, + "value": -0.82, + "curve": [ 3.127, -1.16, 3.093, 0 ] + }, + { "time": 3.1667 } + ] + }, + "gun": { + "rotate": [ + { + "time": 1.2667, + "curve": [ 1.35, 0, 1.549, 7.49 ] + }, + { + "time": 1.6, + "value": 9.5, + "curve": [ 1.663, 12.02, 1.846, 19.58 ] + }, + { + "time": 1.9333, + "value": 19.43, + "curve": [ 1.985, 19.4, 2.057, 2.98 ] + }, + { + "time": 2.2, + "value": 2.95, + "curve": [ 2.304, 3.55, 2.458, 10.8 ] + }, + { + "time": 2.5, + "value": 10.8, + "curve": [ 2.642, 10.8, 2.873, -2.54 ] + }, + { + "time": 2.9333, + "value": -2.55, + "curve": [ 3.09, -2.57, 3.08, 0 ] + }, + { "time": 3.1667 } + ] + }, + "hair2": { + "rotate": [ + { + "time": 1.0667, + "value": 26.19, + "curve": [ 1.158, 26.19, 1.368, 26 ] + }, + { + "time": 1.4333, + "value": 24.43, + "curve": [ 1.534, 22.03, 2, -29.14 ] + }, + { + "time": 2.2, + "value": -29.14, + "curve": [ 2.292, -29.14, 2.475, 6.71 ] + }, + { + "time": 2.5667, + "value": 6.71, + "curve": [ 2.675, 6.71, 2.814, -5.06 ] + }, + { + "time": 2.9, + "value": -5.06, + "curve": [ 2.973, -5.06, 3.123, 0 ] + }, + { "time": 3.1667 } + ] + }, + "hair4": { + "rotate": [ + { + "time": 1.0667, + "value": 5.21, + "curve": [ 1.108, 5.21, 1.192, 26.19 ] + }, + { + "time": 1.2333, + "value": 26.19, + "curve": [ 1.317, 26.19, 1.483, 10.63 ] + }, + { + "time": 1.5667, + "value": 10.63, + "curve": [ 1.627, 10.63, 1.642, 17.91 ] + }, + { + "time": 1.7, + "value": 17.94, + "curve": [ 1.761, 17.97, 1.774, 8.22 ] + }, + { + "time": 1.8, + "value": 3.33, + "curve": [ 1.839, -4.21, 1.95, -22.67 ] + }, + { + "time": 2, + "value": -22.67, + "curve": [ 2.025, -22.67, 2.123, -21.86 ] + }, + { + "time": 2.1667, + "value": -18.71, + "curve": [ 2.228, -14.31, 2.294, -0.3 ] + }, + { + "time": 2.3667, + "value": 6.36, + "curve": [ 2.433, 12.45, 2.494, 19.21 ] + }, + { + "time": 2.6, + "value": 19.21, + "curve": [ 2.729, 19.21, 2.854, 6.75 ] + }, + { + "time": 2.9333, + "value": 4.62, + "curve": [ 3.09, 0.45, 3.062, 0 ] + }, + { "time": 3.1667 } + ] + }, + "hair3": { + "rotate": [ + { + "time": 1.4333, + "curve": [ 1.45, 0, 1.452, 11.29 ] + }, + { + "time": 1.5, + "value": 11.21, + "curve": [ 1.596, 11.06, 1.573, -14.17 ] + }, + { + "time": 1.7333, + "value": -20.4, + "curve": [ 1.851, -24.98, 1.943, -28.45 ] + }, + { + "time": 2.2, + "value": -28.75, + "curve": [ 2.317, -28.75, 2.55, 7.04 ] + }, + { + "time": 2.6667, + "value": 7.04, + "curve": [ 2.792, 7.04, 2.885, -5.19 ] + }, + { + "time": 2.9667, + "value": -5.19, + "curve": [ 3.037, -5.19, 3.096, 0 ] + }, + { "time": 3.1667 } + ] + }, + "hair1": { + "rotate": [ + { + "time": 1.2333, + "curve": [ 1.283, 0, 1.349, 3.99 ] + }, + { + "time": 1.4333, + "value": 6.58, + "curve": [ 1.497, 8.54, 1.683, 9.35 ] + }, + { + "time": 1.7667, + "value": 9.35, + "curve": [ 1.825, 9.35, 1.945, -8.71 ] + }, + { + "time": 2, + "value": -11.15, + "curve": [ 2.058, -13.71, 2.2, -14.97 ] + }, + { + "time": 2.2667, + "value": -14.97, + "curve": [ 2.367, -14.97, 2.567, 18.77 ] + }, + { + "time": 2.6667, + "value": 18.77, + "curve": [ 2.733, 18.77, 2.817, 8.29 ] + }, + { + "time": 2.8667, + "value": 6.51, + "curve": [ 2.988, 2.17, 3.058, 0 ] + }, + { "time": 3.1667 } + ] + }, + "flare1": { + "rotate": [ + { "time": 1.1, "value": 8.2 } + ], + "translate": [ + { "time": 1.1, "x": -19.97, "y": 149.68 }, + { "time": 1.2, "x": 3.85, "y": 152.43 }, + { "time": 1.2333, "x": -15.42, "y": 152.29 } + ], + "scale": [ + { + "time": 1.1, + "x": 0.805, + "y": 0.805, + "curve": [ 1.119, 0.763, 1.16, 1.162, 1.117, 0.805, 1.15, 0.605 ] + }, + { + "time": 1.1667, + "x": 1.279, + "y": 0.605, + "curve": [ 1.177, 1.47, 1.192, 2.151, 1.175, 0.605, 1.192, 0.911 ] + }, + { + "time": 1.2, + "x": 2.151, + "y": 0.911, + "curve": [ 1.208, 2.151, 1.231, 1.668, 1.208, 0.911, 1.227, 0.844 ] + }, + { + "time": 1.2333, + "x": 1.608, + "y": 0.805, + "curve": [ 1.249, 1.205, 1.283, 0.547, 1.254, 0.685, 1.283, 0.416 ] + }, + { "time": 1.3, "x": 0.547, "y": 0.416 } + ], + "shear": [ + { "time": 1.1, "y": 4.63 }, + { "time": 1.2333, "x": -5.74, "y": 4.63 } + ] + }, + "flare2": { + "rotate": [ + { "time": 1.1, "value": 12.29 } + ], + "translate": [ + { "time": 1.1, "x": -8.63, "y": 132.96 }, + { "time": 1.2, "x": 4.35, "y": 132.93 } + ], + "scale": [ + { "time": 1.1, "x": 0.864, "y": 0.864 }, + { "time": 1.1667, "x": 0.945, "y": 0.945 }, + { "time": 1.2, "x": 1.511, "y": 1.081 } + ], + "shear": [ + { "time": 1.1, "y": 24.03 } + ] + }, + "flare3": { + "rotate": [ + { "time": 1.1667, "value": 2.88 } + ], + "translate": [ + { "time": 1.1667, "x": 3.24, "y": 114.81 } + ], + "scale": [ + { "time": 1.1667, "x": 0.668, "y": 0.668 } + ], + "shear": [ + { "time": 1.1667, "y": 38.59 } + ] + }, + "flare4": { + "rotate": [ + { "time": 1.1667, "value": -8.64 } + ], + "translate": [ + { "time": 1.1667, "x": -3.82, "y": 194.06 }, + { "time": 1.2667, "x": -1.82, "y": 198.47, "curve": "stepped" }, + { "time": 1.3, "x": -1.94, "y": 187.81 } + ], + "scale": [ + { "time": 1.1667, "x": 0.545, "y": 0.545 }, + { "time": 1.2667, "x": 0.757, "y": 0.757 } + ], + "shear": [ + { "time": 1.1667, "x": 7.42, "y": -22.04 } + ] + }, + "flare5": { + "translate": [ + { "time": 1.2, "x": -11.17, "y": 176.42 }, + { "time": 1.2333, "x": -8.56, "y": 179.04, "curve": "stepped" }, + { "time": 1.3, "x": -14.57, "y": 168.69 } + ], + "scale": [ + { "time": 1.2333, "x": 1.146 }, + { "time": 1.3, "x": 0.703, "y": 0.61 } + ], + "shear": [ + { "time": 1.2, "x": 6.9 } + ] + }, + "flare6": { + "rotate": [ + { "time": 1.2333, "value": -5.36 }, + { "time": 1.2667, "value": -0.54 } + ], + "translate": [ + { "time": 1.2333, "x": 14.52, "y": 204.67 }, + { "time": 1.2667, "x": 19.16, "y": 212.9, "curve": "stepped" }, + { "time": 1.3, "x": 9.23, "y": 202.85 } + ], + "scale": [ + { "time": 1.2333, "x": 0.777, "y": 0.49 }, + { "time": 1.2667, "x": 0.777, "y": 0.657 }, + { "time": 1.3, "x": 0.475, "y": 0.401 } + ] + }, + "flare7": { + "rotate": [ + { "time": 1.1, "value": 5.98 }, + { "time": 1.1333, "value": 32.82 } + ], + "translate": [ + { "time": 1.1, "x": -6.34, "y": 112.98 }, + { "time": 1.1333, "x": 2.66, "y": 111.6 } + ], + "scale": [ + { "time": 1.1, "x": 0.588, "y": 0.588 } + ], + "shear": [ + { "time": 1.1333, "x": -19.93 } + ] + }, + "flare8": { + "rotate": [ + { "time": 1.2333, "value": -6.85 } + ], + "translate": [ + { "time": 1.1667, "x": 66.67, "y": 125.52, "curve": "stepped" }, + { "time": 1.2, "x": 58.24, "y": 113.53, "curve": "stepped" }, + { "time": 1.2333, "x": 40.15, "y": 114.69 } + ], + "scale": [ + { "time": 1.1667, "x": 1.313, "y": 1.203 }, + { "time": 1.2333, "x": 1.038, "y": 0.95 } + ], + "shear": [ + { "time": 1.2, "y": -13.01 } + ] + }, + "flare9": { + "rotate": [ + { "time": 1.1667, "value": 2.9 } + ], + "translate": [ + { "time": 1.1667, "x": 28.45, "y": 151.35, "curve": "stepped" }, + { "time": 1.2, "x": 48.8, "y": 191.09, "curve": "stepped" }, + { "time": 1.2333, "x": 52, "y": 182.52, "curve": "stepped" }, + { "time": 1.2667, "x": 77.01, "y": 195.96 } + ], + "scale": [ + { "time": 1.1667, "x": 0.871, "y": 1.073 }, + { "time": 1.2, "x": 0.927, "y": 0.944 }, + { "time": 1.2333, "x": 1.165, "y": 1.336 } + ], + "shear": [ + { "time": 1.1667, "x": 7.95, "y": 25.48 } + ] + }, + "flare10": { + "rotate": [ + { "time": 1.1667, "value": 2.18 } + ], + "translate": [ + { "time": 1.1667, "x": 55.64, "y": 137.64, "curve": "stepped" }, + { "time": 1.2, "x": 90.49, "y": 151.07, "curve": "stepped" }, + { "time": 1.2333, "x": 114.06, "y": 153.05, "curve": "stepped" }, + { "time": 1.2667, "x": 90.44, "y": 164.61 } + ], + "scale": [ + { "time": 1.1667, "x": 2.657, "y": 0.891 }, + { "time": 1.2, "x": 3.314, "y": 1.425 }, + { "time": 1.2333, "x": 2.871, "y": 0.924 }, + { "time": 1.2667, "x": 2.317, "y": 0.775 } + ], + "shear": [ + { "time": 1.1667, "x": -1.35 } + ] + }, + "torso2": { + "rotate": [ + { + "time": 1, + "curve": [ 1.117, 0, 1.255, 24.94 ] + }, + { + "time": 1.4, + "value": 24.94, + "curve": [ 1.477, 24.94, 1.59, -17.62 ] + }, + { + "time": 1.6333, + "value": -19.48, + "curve": [ 1.717, -23.1, 1.784, -26.12 ] + }, + { + "time": 1.9333, + "value": -26.14, + "curve": [ 2.067, -26.15, 2.158, 4.3 ] + }, + { + "time": 2.3, + "value": 4.22, + "curve": [ 2.45, 4.13, 2.579, -1.76 ] + }, + { + "time": 2.7333, + "value": -1.8, + "curve": [ 2.816, -1.82, 2.857, -2.94 ] + }, + { + "time": 2.9333, + "value": -2.99, + "curve": [ 3.056, -3.08, 3.09, 0 ] + }, + { "time": 3.1667 } + ] + }, + "torso3": { + "rotate": [ + { + "time": 1.3, + "curve": [ 1.352, 0, 1.408, 6.47 ] + }, + { + "time": 1.4667, + "value": 6.43, + "curve": [ 1.55, 6.39, 1.723, -5.05 ] + }, + { + "time": 1.7333, + "value": -5.53, + "curve": [ 1.782, -7.72, 1.843, -16.94 ] + }, + { + "time": 1.9667, + "value": -16.86, + "curve": [ 2.111, -16.78, 2.259, -3.97 ] + }, + { + "time": 2.4, + "value": -2.43, + "curve": [ 2.525, -1.12, 2.639, -0.5 ] + }, + { + "time": 2.7333, + "value": -0.49, + "curve": [ 2.931, -0.47, 2.999, -2.15 ] + }, + { "time": 3.1667, "value": -2.15 } + ] + }, + "head-control": { + "translate": [ + { + "time": 1.2333, + "curve": [ 1.25, 0, 1.474, 6.89, 1.25, 0, 1.496, 0.98 ] + }, + { + "time": 1.6667, + "x": 11.99, + "y": -6.42, + "curve": [ 1.743, 14.01, 1.86, 14.33, 1.785, -11.55, 1.86, -27.1 ] + }, + { + "time": 1.9667, + "x": 13.91, + "y": -26.88, + "curve": [ 2.074, 13.49, 2.244, 8.13, 2.074, -26.65, 2.215, -21.78 ] + }, + { + "time": 2.3, + "x": 6.07, + "y": -16.64, + "curve": [ 2.416, 1.84, 2.497, -1.41, 2.417, -9.57, 2.526, -1.72 ] + }, + { + "time": 2.5667, + "x": -3.78, + "y": -1.71, + "curve": [ 2.661, -6.98, 2.76, -8.76, 2.692, -1.68, 2.821, -15.75 ] + }, + { + "time": 2.9, + "x": -8.32, + "y": -16.7, + "curve": [ 2.962, -8.12, 3.082, -0.04, 2.958, -17.39, 3.089, 0 ] + }, + { "time": 3.1667 } + ] + }, + "front-shoulder": { + "translate": [ + { + "time": 1.3333, + "curve": [ 1.488, 0, 1.717, 0.21, 1.488, 0, 1.688, -30.29 ] + }, + { + "time": 1.9, + "x": 0.83, + "y": -30.29, + "curve": [ 2.078, 1.43, 2.274, 2.88, 2.071, -30.29, 2.289, 4.48 ] + }, + { + "time": 2.4333, + "x": 2.89, + "y": 4.59, + "curve": [ 2.604, 2.89, 2.677, -0.68, 2.57, 4.7, 2.694, -2.43 ] + }, + { + "time": 2.7667, + "x": -0.67, + "y": -2.47, + "curve": [ 2.866, -0.67, 2.986, -0.07, 2.882, -2.47, 3.036, -0.06 ] + }, + { "time": 3.1667 } + ] + } + }, + "ik": { + "rear-leg-ik": [ + { "time": 3.1667, "softness": 10, "bendPositive": false } + ] + } + }, + "run": { + "slots": { + "mouth": { + "attachment": [ + { "name": "mouth-grind" } + ] + } + }, + "bones": { + "front-thigh": { + "translate": [ + { + "x": -5.14, + "y": 11.13, + "curve": [ 0.033, -7.77, 0.112, -9.03, 0.034, 11.13, 0.108, 9.74 ] + }, + { + "time": 0.1667, + "x": -9.03, + "y": 7.99, + "curve": [ 0.23, -9.05, 0.314, -1.34, 0.236, 5.93, 0.28, 3.22 ] + }, + { + "time": 0.3333, + "x": 0.41, + "y": 3.19, + "curve": [ 0.352, 2.09, 0.449, 11.16, 0.384, 3.16, 0.449, 4.98 ] + }, + { + "time": 0.5, + "x": 11.17, + "y": 6.76, + "curve": [ 0.571, 10.79, 0.621, -1.83, 0.542, 8.21, 0.625, 11.13 ] + }, + { "time": 0.6667, "x": -5.14, "y": 11.13 } + ] + }, + "torso": { + "rotate": [ + { + "value": -37.66, + "curve": [ 0.034, -37.14, 0.107, -36.21 ] + }, + { + "time": 0.1333, + "value": -36.21, + "curve": [ 0.158, -36.21, 0.209, -38.8 ] + }, + { + "time": 0.2333, + "value": -38.79, + "curve": [ 0.259, -38.78, 0.313, -38.03 ] + }, + { + "time": 0.3333, + "value": -37.66, + "curve": [ 0.357, -37.21, 0.4, -36.21 ] + }, + { + "time": 0.4333, + "value": -36.21, + "curve": [ 0.458, -36.21, 0.539, -38.8 ] + }, + { + "time": 0.5667, + "value": -38.8, + "curve": [ 0.592, -38.8, 0.645, -38 ] + }, + { "time": 0.6667, "value": -37.66 } + ] + }, + "rear-thigh": { + "translate": [ + { + "x": -16.41, + "y": 1.55, + "curve": [ 0.013, -15.67, 0.183, -8.55, 0.03, 2.39, 0.183, 6.17 ] + }, + { + "time": 0.2333, + "x": -8.55, + "y": 6.17, + "curve": [ 0.308, -8.55, 0.492, -19.75, 0.308, 6.17, 0.492, 0.61 ] + }, + { + "time": 0.5667, + "x": -19.75, + "y": 0.61, + "curve": [ 0.592, -19.75, 0.641, -18.06, 0.592, 0.61, 0.632, 0.78 ] + }, + { "time": 0.6667, "x": -16.41, "y": 1.55 } + ] + }, + "front-upper-arm": { + "rotate": [ + { + "value": -39.03, + "curve": [ 0.051, -0.1, 0.145, 88.36 ] + }, + { + "time": 0.2333, + "value": 88.36, + "curve": [ 0.28, 88.76, 0.324, 59.52 ] + }, + { + "time": 0.3333, + "value": 51.13, + "curve": [ 0.358, 30.2, 0.445, -74.91 ] + }, + { + "time": 0.5667, + "value": -75.82, + "curve": [ 0.599, -76.06, 0.642, -55.72 ] + }, + { "time": 0.6667, "value": -39.03 } + ] + }, + "front-bracer": { + "rotate": [ + { + "value": 20.54, + "curve": [ 0.052, 11.42, 0.089, 0.13 ] + }, + { + "time": 0.1333, + "value": 0.15, + "curve": [ 0.186, 0.17, 0.221, 26.29 ] + }, + { + "time": 0.2333, + "value": 32.37, + "curve": [ 0.247, 39.19, 0.286, 61.45 ] + }, + { + "time": 0.3333, + "value": 61.58, + "curve": [ 0.371, 61.69, 0.42, 55.79 ] + }, + { "time": 0.4667, "value": 49.68 }, + { "time": 0.6667, "value": 20.54 } + ] + }, + "front-fist": { + "rotate": [ + { + "value": -36.16, + "curve": [ 0.014, -38.8, 0.036, -43.27 ] + }, + { + "time": 0.0667, + "value": -43.37, + "curve": [ 0.102, -43.49, 0.182, -28.46 ] + }, + { + "time": 0.2, + "value": -23.04, + "curve": [ 0.23, -13.87, 0.264, 3.86 ] + }, + { + "time": 0.3333, + "value": 3.7, + "curve": [ 0.38, 3.64, 0.535, -16.22 ] + }, + { "time": 0.5667, "value": -21.29 }, + { "time": 0.6667, "value": -36.16 } + ] + }, + "rear-upper-arm": { + "rotate": [ + { + "value": 40.5, + "curve": [ 0.028, 23.74, 0.128, -79.86 ] + }, + { + "time": 0.2333, + "value": -79.87, + "curve": [ 0.38, -79.88, 0.403, 63.25 ] + }, + { + "time": 0.5667, + "value": 64.13, + "curve": [ 0.607, 64.35, 0.644, 53.1 ] + }, + { "time": 0.6667, "value": 40.5 } + ], + "translate": [ + { + "x": -3.79, + "y": -0.77, + "curve": [ 0.044, -4.58, 0.169, -5.48, 0.044, 0.93, 0.169, 2.85 ] + }, + { + "time": 0.2333, + "x": -5.48, + "y": 2.85, + "curve": [ 0.346, -5.48, 0.475, -2.68, 0.346, 2.85, 0.475, -3.13 ] + }, + { + "time": 0.5667, + "x": -2.68, + "y": -3.13, + "curve": [ 0.611, -2.68, 0.642, -3.34, 0.611, -3.13, 0.642, -1.73 ] + }, + { "time": 0.6667, "x": -3.79, "y": -0.77 } + ] + }, + "rear-bracer": { + "rotate": [ + { "value": 28.28 }, + { + "time": 0.2333, + "value": -11.12, + "curve": [ 0.252, -14.12, 0.297, -19.37 ] + }, + { + "time": 0.3333, + "value": -19.38, + "curve": [ 0.435, -19.41, 0.522, 38.96 ] + }, + { + "time": 0.5667, + "value": 38.87, + "curve": [ 0.619, 38.76, 0.644, 32.01 ] + }, + { "time": 0.6667, "value": 28.28 } + ] + }, + "neck": { + "rotate": [ + { + "value": 11.88, + "curve": [ 0.024, 11.4, 0.075, 9.74 ] + }, + { + "time": 0.1, + "value": 9.74, + "curve": [ 0.125, 9.74, 0.208, 13.36 ] + }, + { + "time": 0.2333, + "value": 13.36, + "curve": [ 0.258, 13.36, 0.321, 12.2 ] + }, + { + "time": 0.3333, + "value": 11.88, + "curve": [ 0.365, 11.06, 0.408, 9.72 ] + }, + { + "time": 0.4333, + "value": 9.72, + "curve": [ 0.458, 9.72, 0.542, 13.36 ] + }, + { + "time": 0.5667, + "value": 13.36, + "curve": [ 0.592, 13.36, 0.636, 12.48 ] + }, + { "time": 0.6667, "value": 11.88 } + ] + }, + "head": { + "rotate": [ + { + "value": 13.14, + "curve": [ 0.02, 11.99, 0.039, 8.94 ] + }, + { + "time": 0.0667, + "value": 8.93, + "curve": [ 0.122, 8.9, 0.232, 15.8 ] + }, + { + "time": 0.2667, + "value": 15.81, + "curve": [ 0.325, 15.82, 0.357, 8.95 ] + }, + { + "time": 0.4, + "value": 8.93, + "curve": [ 0.444, 8.91, 0.568, 15.8 ] + }, + { + "time": 0.6, + "value": 15.77, + "curve": [ 0.632, 15.74, 0.649, 14.05 ] + }, + { "time": 0.6667, "value": 13.14 } + ], + "scale": [ + { + "curve": [ 0.014, 0.996, 0.068, 0.991, 0.027, 1.005, 0.083, 1.012 ] + }, + { + "time": 0.1, + "x": 0.991, + "y": 1.012, + "curve": [ 0.128, 0.991, 0.205, 1.018, 0.128, 1.012, 0.197, 0.988 ] + }, + { + "time": 0.2333, + "x": 1.018, + "y": 0.988, + "curve": [ 0.272, 1.018, 0.305, 1.008, 0.262, 0.988, 0.311, 0.995 ] + }, + { + "time": 0.3333, + "curve": [ 0.351, 0.995, 0.417, 0.987, 0.359, 1.006, 0.417, 1.013 ] + }, + { + "time": 0.4333, + "x": 0.987, + "y": 1.013, + "curve": [ 0.467, 0.987, 0.533, 1.02, 0.467, 1.013, 0.533, 0.989 ] + }, + { + "time": 0.5667, + "x": 1.02, + "y": 0.989, + "curve": [ 0.592, 1.02, 0.652, 1.004, 0.592, 0.989, 0.644, 0.996 ] + }, + { "time": 0.6667 } + ] + }, + "gun": { + "rotate": [ + { + "value": 12.36, + "curve": [ 0.022, 16.28, 0.087, 20.25 ] + }, + { + "time": 0.1333, + "value": 20.19, + "curve": [ 0.168, 20.32, 0.254, -8.82 ] + }, + { + "time": 0.2667, + "value": -11.88, + "curve": [ 0.291, -17.91, 0.344, -24.11 ] + }, + { + "time": 0.4, + "value": -23.88, + "curve": [ 0.448, -23.69, 0.533, -15.47 ] + }, + { "time": 0.5667, "value": -8.69 }, + { "time": 0.6667, "value": 12.36 } + ] + }, + "hip": { + "rotate": [ + { "value": -8.24 } + ], + "translate": [ + { + "x": -3.6, + "y": -34.1, + "curve": [ 0.042, -3.84, 0.118, 7.62, 0.042, -33.74, 0.112, 20.55 ] + }, + { + "time": 0.1667, + "x": 7.61, + "y": 20.36, + "curve": [ 0.194, 7.6, 0.21, 5.06, 0.204, 20.65, 0.217, -8.69 ] + }, + { + "time": 0.2333, + "x": 1.68, + "y": -18.48, + "curve": [ 0.279, -4.99, 0.297, -5.64, 0.254, -31.08, 0.292, -34.55 ] + }, + { + "time": 0.3333, + "x": -5.76, + "y": -35, + "curve": [ 0.379, -5.9, 0.451, 6.8, 0.384, -35.56, 0.428, 17.6 ] + }, + { + "time": 0.5, + "x": 6.61, + "y": 17.01, + "curve": [ 0.536, 6.47, 0.545, 3.56, 0.533, 16.75, 0.548, -8.71 ] + }, + { + "time": 0.5667, + "x": 0.35, + "y": -18.81, + "curve": [ 0.597, -4.07, 0.642, -3.45, 0.584, -28.58, 0.642, -34.32 ] + }, + { "time": 0.6667, "x": -3.6, "y": -34.1 } + ] + }, + "front-foot-target": { + "rotate": [ + { + "value": -62.54, + "curve": [ 0.015, -74.19, 0.056, -103.19 ] + }, + { + "time": 0.0667, + "value": -111.08, + "curve": [ 0.092, -129.44, 0.189, -146.55 ] + }, + { + "time": 0.2333, + "value": -146.32, + "curve": [ 0.285, -146.06, 0.32, -125.1 ] + }, + { "time": 0.3333, "value": -117.24 }, + { + "time": 0.5, + "value": -35.07, + "curve": [ 0.522, -28.64, 0.546, -24.84 ] + }, + { + "time": 0.5667, + "value": -24.9, + "curve": [ 0.595, -25, 0.623, -40.82 ] + }, + { "time": 0.6667, "value": -62.54 } + ], + "translate": [ + { "x": 16.34, "y": 0.18 }, + { + "time": 0.0667, + "x": -101.43, + "y": 8.04, + "curve": [ 0.085, -131.35, 0.129, -207.69, 0.08, 14.9, 0.124, 113.28 ] + }, + { + "time": 0.1667, + "x": -207.92, + "y": 145.81, + "curve": [ 0.196, -208.13, 0.21, -202.91, 0.186, 160.26, 0.206, 163.48 ] + }, + { + "time": 0.2333, + "x": -189.94, + "y": 163.85, + "curve": [ 0.27, -169.94, 0.31, -126.19, 0.269, 164.35, 0.316, 85.97 ] + }, + { + "time": 0.3333, + "x": -90.56, + "y": 78.57, + "curve": [ 0.355, -57.99, 0.376, -29.14, 0.35, 71.55, 0.376, 66.4 ] + }, + { + "time": 0.4, + "x": 2.87, + "y": 66.38, + "curve": [ 0.412, 19.24, 0.469, 90.73, 0.429, 66.37, 0.469, 70.66 ] + }, + { + "time": 0.5, + "x": 117.18, + "y": 70.46, + "curve": [ 0.522, 136.24, 0.542, 151.33, 0.539, 70.2, 0.555, 38.25 ] + }, + { + "time": 0.5667, + "x": 151.49, + "y": 25.29, + "curve": [ 0.578, 146.76, 0.586, 133.13, 0.572, 19.7, 0.582, 12.23 ] + }, + { "time": 0.6, "x": 115.02, "y": 0.1 }, + { "time": 0.6667, "x": 16.34, "y": 0.18 } + ] + }, + "front-leg-target": { + "translate": [ + { "x": -13.95, "y": -30.34 } + ] + }, + "rear-foot-target": { + "rotate": [ + { "value": 18.55 }, + { + "time": 0.2333, + "value": 167.84, + "curve": [ 0.246, 153.66, 0.256, 129.74 ] + }, + { + "time": 0.2667, + "value": 124.32, + "curve": [ 0.296, 124.43, 0.313, 129.93 ] + }, + { + "time": 0.3667, + "value": 129.87, + "curve": [ 0.421, 128.32, 0.519, 0.98 ] + }, + { + "time": 0.5667, + "curve": [ 0.6, 0.27, 0.642, 4.73 ] + }, + { "time": 0.6667, "value": 18.55 } + ], + "translate": [ + { + "x": -176.39, + "y": 134.12, + "curve": [ 0.018, -142.26, 0.054, -94.41, 0.01, 120.96, 0.044, 84.08 ] + }, + { + "time": 0.0667, + "x": -73.56, + "y": 76.68, + "curve": [ 0.086, -42.82, 0.194, 101.2, 0.098, 66.73, 0.198, 60.88 ] + }, + { "time": 0.2333, "x": 98.32, "y": 32.17 }, + { "time": 0.2667, "x": 49.13, "y": -0.63 }, + { + "time": 0.4, + "x": -147.9, + "y": 0.32, + "curve": [ 0.414, -168.78, 0.478, -284.76, 0.43, 30.09, 0.478, 129.14 ] + }, + { + "time": 0.5, + "x": -283.37, + "y": 167.12, + "curve": [ 0.526, -285.66, 0.548, -280.54, 0.516, 194.84, 0.55, 216.53 ] + }, + { + "time": 0.5667, + "x": -266.98, + "y": 216.12, + "curve": [ 0.581, -256.27, 0.643, -206.54, 0.61, 214.82, 0.65, 145.33 ] + }, + { "time": 0.6667, "x": -176.39, "y": 134.12 } + ] + }, + "rear-leg-target": { + "translate": [ + { "x": 85, "y": -33.59 } + ] + }, + "back-foot-tip": { + "rotate": [ + { + "value": -147.04, + "curve": [ 0.033, -113.4, 0.161, 44.34 ] + }, + { + "time": 0.2333, + "value": 43.48, + "curve": [ 0.24, 43.41, 0.282, 35.72 ] + }, + { + "time": 0.3, + "value": 0.29, + "curve": [ 0.347, 0.28, 0.396, 4.27 ] + }, + { + "time": 0.4, + "curve": [ 0.424, -23.8, 0.525, -181.39 ] + }, + { + "time": 0.5667, + "value": -181.39, + "curve": [ 0.592, -181.39, 0.642, -169.09 ] + }, + { "time": 0.6667, "value": -147.04 } + ] + }, + "front-foot-tip": { + "rotate": [ + { + "value": -0.25, + "curve": [ 0.008, -0.25, 0.056, 1.73 ] + }, + { + "time": 0.0667, + "value": -7.68, + "curve": [ 0.075, -43.13, 0.15, -130.44 ] + }, + { + "time": 0.2, + "value": -130.08, + "curve": [ 0.239, -129.79, 0.272, -126.8 ] + }, + { + "time": 0.3, + "value": -116.24, + "curve": [ 0.333, -103.91, 0.348, -86.1 ] + }, + { + "time": 0.3667, + "value": -71.08, + "curve": [ 0.386, -55.25, 0.415, -32.44 ] + }, + { + "time": 0.4333, + "value": -21.63, + "curve": [ 0.47, -0.01, 0.542, 33.42 ] + }, + { + "time": 0.5667, + "value": 33.2, + "curve": [ 0.622, 32.7, 0.569, 0.64 ] + }, + { "time": 0.6667, "value": -0.25 } + ] + }, + "hair1": { + "rotate": [ + { + "value": -6.81, + "curve": [ 0.087, -6.81, 0.143, -5.75 ] + }, + { + "time": 0.1667, + "value": -4.3, + "curve": [ 0.183, -3.28, 0.209, 2.79 ] + }, + { + "time": 0.2333, + "value": 2.78, + "curve": [ 0.262, 2.77, 0.305, -6.63 ] + }, + { + "time": 0.3333, + "value": -6.64, + "curve": [ 0.419, -6.68, 0.49, -4.84 ] + }, + { + "time": 0.5, + "value": -4.38, + "curve": [ 0.518, -3.56, 0.574, 2.32 ] + }, + { + "time": 0.6, + "value": 2.33, + "curve": [ 0.643, 2.35, 0.633, -6.81 ] + }, + { "time": 0.6667, "value": -6.81 } + ] + }, + "hair2": { + "rotate": [ + { + "value": -6.81, + "curve": [ 0.014, -3.17, 0.109, 43.93 ] + }, + { + "time": 0.1333, + "value": 43.95, + "curve": [ 0.177, 43.97, 0.192, -13.76 ] + }, + { + "time": 0.2667, + "value": -13.83, + "curve": [ 0.302, -13.72, 0.322, -8.86 ] + }, + { + "time": 0.3333, + "value": -6.6, + "curve": [ 0.349, -3.5, 0.436, 41.1 ] + }, + { + "time": 0.4667, + "value": 41.05, + "curve": [ 0.51, 40.99, 0.549, -14.06 ] + }, + { + "time": 0.6, + "value": -14.18, + "curve": [ 0.63, -14.26, 0.656, -9.04 ] + }, + { "time": 0.6667, "value": -6.81 } + ] + }, + "hair3": { + "rotate": [ + { + "value": -6.81, + "curve": [ 0.079, -6.83, 0.108, 0.3 ] + }, + { + "time": 0.1333, + "value": 1.96, + "curve": [ 0.177, 4.89, 0.208, 6.28 ] + }, + { + "time": 0.2333, + "value": 6.29, + "curve": [ 0.313, 6.31, 0.383, 3.49 ] + }, + { + "time": 0.4, + "value": 2.58, + "curve": [ 0.442, 0.28, 0.523, -6.81 ] + }, + { "time": 0.6, "value": -6.81 } + ] + }, + "hair4": { + "rotate": [ + { + "value": -6.81, + "curve": [ 0.011, -4.06, 0.108, 24.92 ] + }, + { + "time": 0.1333, + "value": 24.92, + "curve": [ 0.158, 24.92, 0.208, -10.62 ] + }, + { + "time": 0.2333, + "value": -10.62, + "curve": [ 0.254, -10.62, 0.312, -9.73 ] + }, + { + "time": 0.3333, + "value": -6.4, + "curve": [ 0.356, -2.95, 0.438, 24.93 ] + }, + { + "time": 0.4667, + "value": 24.93, + "curve": [ 0.492, 24.93, 0.575, -9.78 ] + }, + { + "time": 0.6, + "value": -9.78, + "curve": [ 0.617, -9.78, 0.655, -8.63 ] + }, + { "time": 0.6667, "value": -6.81 } + ] + }, + "torso2": { + "rotate": [ + { + "value": 3.5, + "curve": [ 0.07, 3.51, 0.075, 8.69 ] + }, + { + "time": 0.1, + "value": 8.69, + "curve": [ 0.139, 8.69, 0.214, 6.9 ] + }, + { + "time": 0.2333, + "value": 6.33, + "curve": [ 0.266, 5.34, 0.285, 3.48 ] + }, + { + "time": 0.3333, + "value": 3.48, + "curve": [ 0.398, 3.48, 0.408, 8.68 ] + }, + { + "time": 0.4333, + "value": 8.68, + "curve": [ 0.458, 8.68, 0.551, 6.8 ] + }, + { + "time": 0.5667, + "value": 6.26, + "curve": [ 0.598, 5.17, 0.642, 3.49 ] + }, + { "time": 0.6667, "value": 3.5 } + ] + }, + "torso3": { + "rotate": [ + { + "value": 4.52, + "curve": [ 0.067, 4.54, 0.075, -7.27 ] + }, + { + "time": 0.1, + "value": -7.27, + "curve": [ 0.125, -7.27, 0.227, 0.84 ] + }, + { + "time": 0.2333, + "value": 1.24, + "curve": [ 0.254, 2.5, 0.301, 4.51 ] + }, + { + "time": 0.3333, + "value": 4.52, + "curve": [ 0.386, 4.54, 0.408, -7.35 ] + }, + { + "time": 0.4333, + "value": -7.35, + "curve": [ 0.458, -7.35, 0.549, -0.14 ] + }, + { + "time": 0.5667, + "value": 0.95, + "curve": [ 0.586, 2.18, 0.632, 4.54 ] + }, + { "time": 0.6667, "value": 4.52 } + ] + }, + "aim-constraint-target": { + "rotate": [ + { "value": 30.57 } + ] + }, + "rear-foot": { + "rotate": [ + { "value": -6.5 } + ] + }, + "front-foot": { + "rotate": [ + { "value": 4.5 } + ] + }, + "head-control": { + "translate": [ + { + "y": -9.94, + "curve": [ 0.058, 0, 0.175, -15.32, 0.044, -4.19, 0.175, 5 ] + }, + { + "time": 0.2333, + "x": -15.32, + "y": 5, + "curve": [ 0.317, -15.32, 0.429, -9.74, 0.317, 5, 0.382, -31.71 ] + }, + { + "time": 0.4667, + "x": -7.81, + "y": -31.59, + "curve": [ 0.507, -5.76, 0.617, 0, 0.549, -31.47, 0.628, -13.33 ] + }, + { "time": 0.6667, "y": -9.94 } + ] + }, + "front-shoulder": { + "translate": [ + { + "x": -0.74, + "y": 11.22, + "curve": [ 0.061, -0.74, 0.144, 1.17, 0.061, 11.22, 0.143, -17.93 ] + }, + { + "time": 0.2333, + "x": 1.19, + "y": -17.9, + "curve": [ 0.54, 1.25, 0.558, -0.74, 0.545, -17.8, 0.558, 11.22 ] + }, + { "time": 0.6667, "x": -0.74, "y": 11.22 } + ] + }, + "back-shoulder": { + "translate": [ + { + "curve": [ 0.083, 0, 0.25, 0, 0.083, 0, 0.25, 8.93 ] + }, + { + "time": 0.3333, + "y": 8.93, + "curve": [ 0.417, 0, 0.583, 0, 0.417, 8.93, 0.583, 0 ] + }, + { "time": 0.6667 } + ] + } + }, + "ik": { + "front-leg-ik": [ + { "softness": 10, "bendPositive": false }, + { "time": 0.5667, "softness": 14.8, "bendPositive": false }, + { "time": 0.6, "softness": 48.2, "bendPositive": false }, + { "time": 0.6667, "softness": 10, "bendPositive": false } + ], + "rear-leg-ik": [ + { "bendPositive": false }, + { "time": 0.1667, "softness": 22.5, "bendPositive": false }, + { "time": 0.3, "softness": 61.4, "bendPositive": false }, + { "time": 0.6667, "bendPositive": false } + ] + }, + "events": [ + { "time": 0.2333, "name": "footstep" }, + { "time": 0.5667, "name": "footstep" } + ] + }, + "run-to-idle": { + "slots": { + "front-fist": { + "attachment": [ + { "name": "front-fist-open" } + ] + } + }, + "bones": { + "front-foot-target": { + "translate": [ + { + "x": -16.5, + "y": 3.41, + "curve": [ 0.033, -16.5, 0.1, -69.06, 0.033, 3.41, 0.1, 0 ] + }, + { "time": 0.1333, "x": -69.06 } + ] + }, + "hip": { + "translate": [ + { + "x": -28.78, + "y": -72.96, + "curve": [ 0.036, -28.63, 0.2, -10.85, 0.135, -62.35, 0.2, -23.15 ] + }, + { "time": 0.2667, "x": -11.97, "y": -23.15 } + ] + }, + "rear-foot-target": { + "translate": [ + { + "x": 33.15, + "y": 31.61, + "curve": [ 0.017, 33.15, 0.05, 24.41, 0.017, 31.61, 0.041, 20.73 ] + }, + { + "time": 0.0667, + "x": 24.41, + "y": 0.19, + "curve": [ 0.117, 24.41, 0.217, 48.87, 0.117, 0.19, 0.217, 0 ] + }, + { "time": 0.2667, "x": 48.87 } + ] + }, + "front-upper-arm": { + "rotate": [ + { + "value": -80.61, + "curve": [ 0.067, -80.61, 0.2, -60.87 ] + }, + { "time": 0.2667, "value": -60.87 } + ] + }, + "front-bracer": { + "rotate": [ + { + "value": 8.79, + "curve": [ 0.041, 8.79, 0.115, 6.3 ] + }, + { + "time": 0.1667, + "value": 6.41, + "curve": [ 0.201, 6.48, 0.241, 42.46 ] + }, + { "time": 0.2667, "value": 42.46 } + ] + }, + "rear-upper-arm": { + "rotate": [ + { + "value": 55.3, + "curve": [ 0.067, 55.3, 0.2, 39.2 ] + }, + { "time": 0.2667, "value": 39.2 } + ] + }, + "head": { + "rotate": [ + { + "curve": [ 0.05, 0, 0.083, 2.67 ] + }, + { + "time": 0.1333, + "value": 2.67, + "curve": [ 0.15, 2.67, 0.25, -6.75 ] + }, + { "time": 0.2667, "value": -6.75 } + ] + }, + "front-fist": { + "rotate": [ + { + "value": 38.26, + "curve": [ 0.041, 38.26, 0.127, -2.19 ] + }, + { + "time": 0.1667, + "value": -3, + "curve": [ 0.209, -3.84, 0.241, 0 ] + }, + { "time": 0.2667 } + ], + "scale": [ + { + "x": 0.844, + "curve": [ 0.067, 0.844, 0.2, 1, 0.067, 1, 0.2, 1 ] + }, + { "time": 0.2667 } + ] + }, + "rear-bracer": { + "rotate": [ + { + "value": 57.24, + "curve": [ 0.067, 57.24, 0.2, 0 ] + }, + { "time": 0.2667 } + ] + }, + "gun": { + "rotate": [ + { + "value": 2.28, + "curve": [ 0.041, 2.28, 0.105, 15.34 ] + }, + { + "time": 0.1667, + "value": 15.32, + "curve": [ 0.205, 15.31, 0.241, 0 ] + }, + { "time": 0.2667 } + ] + }, + "torso": { + "rotate": [ + { + "value": -12.98, + "curve": [ 0.033, -12.98, 0.103, -14.81 ] + }, + { + "time": 0.1333, + "value": -16.63, + "curve": [ 0.168, -18.69, 0.233, -22.88 ] + }, + { "time": 0.2667, "value": -22.88 } + ], + "scale": [ + { + "x": 0.963, + "y": 1.074, + "curve": [ 0.067, 0.963, 0.132, 1, 0.067, 1.074, 0.132, 1 ] + }, + { "time": 0.2667 } + ] + }, + "neck": { + "rotate": [ + {}, + { "time": 0.2667, "value": 3.78 } + ] + }, + "hair3": { + "rotate": [ + { + "curve": [ 0.033, 0, 0.1, 0.88 ] + }, + { + "time": 0.1333, + "value": 0.88, + "curve": [ 0.167, 0.88, 0.233, 0 ] + }, + { "time": 0.2667 } + ] + }, + "hair4": { + "rotate": [ + { + "curve": [ 0.033, 0, 0.1, 15.97 ] + }, + { + "time": 0.1333, + "value": 15.97, + "curve": [ 0.167, 15.97, 0.233, 0 ] + }, + { "time": 0.2667 } + ] + }, + "hair1": { + "rotate": [ + { + "curve": [ 0.033, 0, 0.1, 10.76 ] + }, + { + "time": 0.1333, + "value": 10.76, + "curve": [ 0.167, 10.76, 0.233, 0 ] + }, + { "time": 0.2667 } + ] + }, + "hair2": { + "rotate": [ + { + "curve": [ 0.014, -2.28, 0.042, -7.84 ] + }, + { + "time": 0.0667, + "value": -7.82, + "curve": [ 0.108, -7.79, 0.166, 6.57 ] + }, + { + "time": 0.2, + "value": 6.67, + "curve": [ 0.222, 6.73, 0.255, 1.98 ] + }, + { "time": 0.2667 } + ] + }, + "torso2": { + "rotate": [ + { + "curve": [ 0.041, 0, 0.107, 3.03 ] + }, + { + "time": 0.1667, + "value": 3.03, + "curve": [ 0.205, 3.03, 0.241, 0 ] + }, + { "time": 0.2667 } + ] + }, + "torso3": { + "rotate": [ + { + "curve": [ 0.049, 0, 0.166, 0.66 ] + }, + { + "time": 0.2, + "value": 0.66, + "curve": [ 0.232, 0.65, 0.249, -2.15 ] + }, + { "time": 0.2667, "value": -2.15 } + ] + }, + "head-control": { + "translate": [ + { "x": -10.12, "y": 8.71 }, + { "time": 0.2667 } + ] + }, + "front-shoulder": { + "translate": [ + { "x": 4.91, "y": 11.54 }, + { "time": 0.2667 } + ] + } + } + }, + "shoot": { + "slots": { + "muzzle": { + "rgba": [ + { "time": 0.1333, "color": "ffffffff" }, + { "time": 0.2, "color": "ffffff62" } + ], + "attachment": [ + { "time": 0.0333, "name": "muzzle01" }, + { "time": 0.0667, "name": "muzzle02" }, + { "time": 0.1, "name": "muzzle03" }, + { "time": 0.1333, "name": "muzzle04" }, + { "time": 0.1667, "name": "muzzle05" }, + { "time": 0.2 } + ] + }, + "muzzle-glow": { + "rgba": [ + { "color": "ff0c0c00" }, + { + "time": 0.0333, + "color": "ffc9adff", + "curve": [ 0.255, 1, 0.273, 1, 0.255, 0.76, 0.273, 0.4, 0.255, 0.65, 0.273, 0.22, 0.255, 1, 0.273, 1 ] + }, + { "time": 0.3, "color": "ff400cff" }, + { "time": 0.6333, "color": "ff0c0c00" } + ], + "attachment": [ + { "name": "muzzle-glow" } + ] + }, + "muzzle-ring": { + "rgba": [ + { + "time": 0.0333, + "color": "d8baffff", + "curve": [ 0.202, 0.85, 0.214, 0.84, 0.202, 0.73, 0.214, 0.73, 0.202, 1, 0.214, 1, 0.202, 1, 0.214, 0.21 ] + }, + { "time": 0.2333, "color": "d7baff00" } + ], + "attachment": [ + { "time": 0.0333, "name": "muzzle-ring" }, + { "time": 0.2333 } + ] + }, + "muzzle-ring2": { + "rgba": [ + { + "time": 0.0333, + "color": "d8baffff", + "curve": [ 0.174, 0.85, 0.184, 0.84, 0.174, 0.73, 0.184, 0.73, 0.174, 1, 0.184, 1, 0.174, 1, 0.184, 0.21 ] + }, + { "time": 0.2, "color": "d7baff00" } + ], + "attachment": [ + { "time": 0.0333, "name": "muzzle-ring" }, + { "time": 0.2 } + ] + }, + "muzzle-ring3": { + "rgba": [ + { + "time": 0.0333, + "color": "d8baffff", + "curve": [ 0.174, 0.85, 0.184, 0.84, 0.174, 0.73, 0.184, 0.73, 0.174, 1, 0.184, 1, 0.174, 1, 0.184, 0.21 ] + }, + { "time": 0.2, "color": "d7baff00" } + ], + "attachment": [ + { "time": 0.0333, "name": "muzzle-ring" }, + { "time": 0.2 } + ] + }, + "muzzle-ring4": { + "rgba": [ + { + "time": 0.0333, + "color": "d8baffff", + "curve": [ 0.174, 0.85, 0.184, 0.84, 0.174, 0.73, 0.184, 0.73, 0.174, 1, 0.184, 1, 0.174, 1, 0.184, 0.21 ] + }, + { "time": 0.2, "color": "d7baff00" } + ], + "attachment": [ + { "time": 0.0333, "name": "muzzle-ring" }, + { "time": 0.2 } + ] + } + }, + "bones": { + "gun": { + "rotate": [ + { + "time": 0.0667, + "curve": [ 0.094, 25.89, 0.112, 45.27 ] + }, + { + "time": 0.1333, + "value": 45.35, + "curve": [ 0.192, 45.28, 0.18, -0.09 ] + }, + { "time": 0.6333 } + ] + }, + "muzzle": { + "translate": [ + { "x": -11.02, "y": 25.16 } + ] + }, + "rear-upper-arm": { + "translate": [ + { + "time": 0.0333, + "curve": [ 0.045, 0.91, 0.083, 3.46, 0.044, 0.86, 0.083, 3.32 ] + }, + { + "time": 0.1, + "x": 3.46, + "y": 3.32, + "curve": [ 0.133, 3.46, 0.176, -0.1, 0.133, 3.32, 0.169, 0 ] + }, + { "time": 0.2333 } + ] + }, + "rear-bracer": { + "translate": [ + { + "time": 0.0333, + "curve": [ 0.075, -3.78, 0.083, -4.36, 0.08, -2.7, 0.083, -2.88 ] + }, + { + "time": 0.1, + "x": -4.36, + "y": -2.88, + "curve": [ 0.133, -4.36, 0.168, 0.18, 0.133, -2.88, 0.167, 0 ] + }, + { "time": 0.2333 } + ] + }, + "gun-tip": { + "translate": [ + {}, + { "time": 0.3, "x": 3.15, "y": 0.39 } + ], + "scale": [ + { "x": 0.366, "y": 0.366 }, + { "time": 0.0333, "x": 1.453, "y": 1.453 }, + { "time": 0.3, "x": 0.366, "y": 0.366 } + ] + }, + "muzzle-ring": { + "translate": [ + { "time": 0.0333 }, + { "time": 0.2333, "x": 64.47 } + ], + "scale": [ + { "time": 0.0333 }, + { "time": 0.2333, "x": 5.951, "y": 5.951 } + ] + }, + "muzzle-ring2": { + "translate": [ + { "time": 0.0333 }, + { "time": 0.2, "x": 172.57 } + ], + "scale": [ + { "time": 0.0333 }, + { "time": 0.2, "x": 4, "y": 4 } + ] + }, + "muzzle-ring3": { + "translate": [ + { "time": 0.0333 }, + { "time": 0.2, "x": 277.17 } + ], + "scale": [ + { "time": 0.0333 }, + { "time": 0.2, "x": 2, "y": 2 } + ] + }, + "muzzle-ring4": { + "translate": [ + { "time": 0.0333 }, + { "time": 0.2, "x": 392.06 } + ] + } + } + }, + "walk": { + "bones": { + "rear-foot-target": { + "rotate": [ + { + "value": -32.82, + "curve": [ 0.035, -42.69, 0.057, -70.49 ] + }, + { + "time": 0.1, + "value": -70.59, + "curve": [ 0.236, -70.78, 0.335, -9.87 ] + }, + { + "time": 0.3667, + "value": -1.56, + "curve": [ 0.393, 5.5, 0.477, 13.96 ] + }, + { + "time": 0.5, + "value": 13.96, + "curve": [ 0.519, 13.96, 0.508, 0.13 ] + }, + { "time": 0.5667, "value": -0.28 }, + { + "time": 0.7333, + "value": -0.28, + "curve": [ 0.827, -0.06, 0.958, -21.07 ] + }, + { "time": 1, "value": -32.82 } + ], + "translate": [ + { + "x": -167.32, + "y": 0.58, + "curve": [ 0.022, -180.55, 0.075, -235.51, 0.045, 0.58, 0.075, 30.12 ] + }, + { + "time": 0.1, + "x": -235.51, + "y": 39.92, + "curve": [ 0.142, -235.51, 0.208, -201.73, 0.138, 54.94, 0.18, 60.78 ] + }, + { + "time": 0.2333, + "x": -176.33, + "y": 61.48, + "curve": [ 0.272, -136.61, 0.321, -45.18, 0.275, 62.02, 0.321, 56.6 ] + }, + { + "time": 0.3667, + "x": 8.44, + "y": 49.67, + "curve": [ 0.403, 51.03, 0.486, 66.86, 0.401, 44.37, 0.48, 23.11 ] + }, + { "time": 0.5, "x": 66.57, "y": 14.22 }, + { "time": 0.5333, "x": 52.58, "y": 0.6 }, + { "time": 1, "x": -167.32, "y": 0.58 } + ] + }, + "front-foot-target": { + "rotate": [ + { + "value": 18.19, + "curve": [ 0.01, 11.17, 0.043, 1.37 ] + }, + { "time": 0.1, "value": 0.47 }, + { + "time": 0.2333, + "value": 0.55, + "curve": [ 0.364, 0.3, 0.515, -80.48 ] + }, + { + "time": 0.7333, + "value": -80.78, + "curve": [ 0.788, -80.38, 0.921, 17.42 ] + }, + { "time": 1, "value": 18.19 } + ], + "translate": [ + { + "x": 139.21, + "y": 22.94, + "curve": [ 0.025, 139.21, 0.069, 111.46, 0.031, 3.25, 0.075, 0.06 ] + }, + { "time": 0.1, "x": 96.69, "y": 0.06 }, + { + "time": 0.5, + "x": -94.87, + "y": -0.03, + "curve": [ 0.518, -106.82, 0.575, -152.56, 0.534, 5.42, 0.557, 38.46 ] + }, + { + "time": 0.6, + "x": -152.56, + "y": 57.05, + "curve": [ 0.633, -152.56, 0.688, -128.05, 0.643, 75.61, 0.7, 84.14 ] + }, + { + "time": 0.7333, + "x": -109.42, + "y": 84.14, + "curve": [ 0.771, -93.91, 0.832, -30.64, 0.787, 84.14, 0.799, 89.65 ] + }, + { + "time": 0.8667, + "x": 17, + "y": 75.25, + "curve": [ 0.903, 66.18, 0.967, 139.21, 0.932, 61.53, 0.967, 44.02 ] + }, + { "time": 1, "x": 139.21, "y": 22.94 } + ] + }, + "hip": { + "rotate": [ + { "value": -4.35 } + ], + "translate": [ + { + "x": -2.86, + "y": -13.86, + "curve": [ 0.025, -2.84, 0.067, -2.82, 0.028, -19.14, 0.054, -24.02 ] + }, + { + "time": 0.1, + "x": -2.61, + "y": -24.19, + "curve": [ 0.143, -2.34, 0.202, -1.79, 0.152, -23.98, 0.213, -14.81 ] + }, + { + "time": 0.2667, + "x": -1.21, + "y": -7.12, + "curve": [ 0.308, -0.86, 0.345, -0.51, 0.306, -1.63, 0.341, 3.15 ] + }, + { + "time": 0.3667, + "x": -0.33, + "y": 3.15, + "curve": [ 0.41, 0.02, 0.458, 0.26, 0.427, 3.3, 0.481, -6.75 ] + }, + { + "time": 0.5, + "x": 0.26, + "y": -10.59, + "curve": [ 0.553, 0.26, 0.559, 0.2, 0.519, -14.41, 0.548, -23.88 ] + }, + { + "time": 0.6, + "x": -0.17, + "y": -23.71, + "curve": [ 0.663, -0.72, 0.798, -2.09, 0.702, -23.36, 0.802, 3.53 ] + }, + { + "time": 0.8667, + "x": -2.46, + "y": 3.48, + "curve": [ 0.901, -2.63, 0.967, -2.87, 0.913, 3.45, 0.967, -7.64 ] + }, + { "time": 1, "x": -2.86, "y": -13.86 } + ] + }, + "front-foot-tip": { + "rotate": [ + { + "value": 28.96, + "curve": [ 0.056, 28.74, 0.049, 19.6 ] + }, + { "time": 0.0667, "value": 1.68 }, + { + "time": 0.5, + "value": -10, + "curve": [ 0.525, -10, 0.592, -54.69 ] + }, + { + "time": 0.6, + "value": -59.66, + "curve": [ 0.623, -74.54, 0.674, -101.78 ] + }, + { + "time": 0.7333, + "value": -101.78, + "curve": [ 0.812, -101.78, 0.855, -84.67 ] + }, + { + "time": 0.8667, + "value": -63.53, + "curve": [ 0.869, -58.38, 0.975, 28.96 ] + }, + { "time": 1, "value": 28.96 } + ] + }, + "torso": { + "rotate": [ + { + "value": -20.72, + "curve": [ 0.025, -20.57, 0.071, -20.04 ] + }, + { + "time": 0.1333, + "value": -20.04, + "curve": [ 0.187, -20.04, 0.285, -21.16 ] + }, + { + "time": 0.3667, + "value": -21.16, + "curve": [ 0.405, -21.16, 0.47, -20.9 ] + }, + { + "time": 0.5, + "value": -20.71, + "curve": [ 0.518, -20.6, 0.582, -20.03 ] + }, + { + "time": 0.6333, + "value": -20.04, + "curve": [ 0.709, -20.05, 0.815, -21.18 ] + }, + { + "time": 0.8667, + "value": -21.18, + "curve": [ 0.908, -21.18, 0.971, -20.93 ] + }, + { "time": 1, "value": -20.72 } + ] + }, + "neck": { + "rotate": [ + { + "value": 17.78, + "curve": [ 0.025, 17.93, 0.071, 18.46 ] + }, + { + "time": 0.1333, + "value": 18.46, + "curve": [ 0.187, 18.46, 0.285, 17.34 ] + }, + { + "time": 0.3667, + "value": 17.34, + "curve": [ 0.405, 17.34, 0.47, 17.6 ] + }, + { + "time": 0.5, + "value": 17.79, + "curve": [ 0.518, 17.9, 0.582, 18.47 ] + }, + { + "time": 0.6333, + "value": 18.46, + "curve": [ 0.709, 18.45, 0.815, 17.32 ] + }, + { + "time": 0.8667, + "value": 17.32, + "curve": [ 0.908, 17.32, 0.971, 17.57 ] + }, + { "time": 1, "value": 17.78 } + ] + }, + "head": { + "rotate": [ + { + "value": -12.23, + "curve": [ 0.061, -12.23, 0.191, -7.45 ] + }, + { + "time": 0.2667, + "value": -7.43, + "curve": [ 0.341, -7.42, 0.421, -12.23 ] + }, + { + "time": 0.5, + "value": -12.23, + "curve": [ 0.567, -12.26, 0.694, -7.46 ] + }, + { + "time": 0.7667, + "value": -7.47, + "curve": [ 0.853, -7.49, 0.943, -12.23 ] + }, + { "time": 1, "value": -12.23 } + ], + "scale": [ + { + "curve": [ 0.039, 1, 0.084, 0.991, 0.039, 1, 0.084, 1.019 ] + }, + { + "time": 0.1333, + "x": 0.991, + "y": 1.019, + "curve": [ 0.205, 0.991, 0.318, 1.019, 0.205, 1.019, 0.337, 0.992 ] + }, + { + "time": 0.4, + "x": 1.019, + "y": 0.992, + "curve": [ 0.456, 1.019, 0.494, 1.001, 0.483, 0.991, 0.493, 0.999 ] + }, + { + "time": 0.5, + "curve": [ 0.508, 0.998, 0.584, 0.991, 0.51, 1.002, 0.584, 1.019 ] + }, + { + "time": 0.6333, + "x": 0.991, + "y": 1.019, + "curve": [ 0.705, 0.991, 0.818, 1.019, 0.705, 1.019, 0.837, 0.992 ] + }, + { + "time": 0.9, + "x": 1.019, + "y": 0.992, + "curve": [ 0.956, 1.019, 0.955, 1, 0.983, 0.991, 0.955, 1 ] + }, + { "time": 1 } + ] + }, + "back-foot-tip": { + "rotate": [ + { "value": 4.09 }, + { "time": 0.0333, "value": 3.05 }, + { + "time": 0.1, + "value": -59.01, + "curve": [ 0.124, -72.97, 0.169, -100.05 ] + }, + { + "time": 0.2333, + "value": -99.71, + "curve": [ 0.326, -99.21, 0.349, -37.4 ] + }, + { + "time": 0.3667, + "value": -17.85, + "curve": [ 0.388, 4.74, 0.451, 32.35 ] + }, + { + "time": 0.5, + "value": 32.4, + "curve": [ 0.537, 32.44, 0.566, 6.43 ] + }, + { "time": 0.5667, "value": 2 }, + { "time": 1, "value": 4.09 } + ] + }, + "front-thigh": { + "translate": [ + { + "x": 17.15, + "y": -0.09, + "curve": [ 0.178, 17.14, 0.295, -4.26, 0.009, -0.09, 0.475, 0.02 ] + }, + { + "time": 0.5, + "x": -4.26, + "y": 0.02, + "curve": [ 0.705, -4.27, 0.848, 17.15, 0.525, 0.02, 0.975, -0.09 ] + }, + { "time": 1, "x": 17.15, "y": -0.09 } + ] + }, + "rear-thigh": { + "translate": [ + { + "x": -17.71, + "y": -4.63, + "curve": [ 0.036, -19.81, 0.043, -20.86, 0.036, -4.63, 0.05, -7.03 ] + }, + { + "time": 0.1, + "x": -20.95, + "y": -7.06, + "curve": [ 0.162, -21.05, 0.4, 7.79, 0.2, -7.13, 0.4, -1.9 ] + }, + { + "time": 0.5, + "x": 7.79, + "y": -1.94, + "curve": [ 0.612, 7.69, 0.875, -10.49, 0.592, -1.97, 0.917, -3.25 ] + }, + { "time": 1, "x": -17.71, "y": -4.63 } + ] + }, + "torso2": { + "rotate": [ + { + "value": 1, + "curve": [ 0.006, 1.2, 0.084, 2.88 ] + }, + { + "time": 0.1333, + "value": 2.88, + "curve": [ 0.205, 2.88, 0.284, -1.17 ] + }, + { + "time": 0.3667, + "value": -1.17, + "curve": [ 0.411, -1.17, 0.481, 0.57 ] + }, + { + "time": 0.5, + "value": 1, + "curve": [ 0.515, 1.33, 0.59, 2.83 ] + }, + { + "time": 0.6333, + "value": 2.85, + "curve": [ 0.683, 2.86, 0.796, -1.2 ] + }, + { + "time": 0.8667, + "value": -1.2, + "curve": [ 0.916, -1.2, 0.984, 0.62 ] + }, + { "time": 1, "value": 1 } + ] + }, + "torso3": { + "rotate": [ + { "value": -1.81 } + ] + }, + "front-upper-arm": { + "rotate": [ + { + "value": -9.51, + "curve": [ 0.021, -13.32, 0.058, -19.4 ] + }, + { + "time": 0.1, + "value": -19.4, + "curve": [ 0.238, -19.69, 0.337, 7.78 ] + }, + { + "time": 0.3667, + "value": 16.2, + "curve": [ 0.399, 25.42, 0.497, 60.19 ] + }, + { + "time": 0.6, + "value": 60.26, + "curve": [ 0.719, 60.13, 0.845, 27.61 ] + }, + { + "time": 0.8667, + "value": 22.45, + "curve": [ 0.892, 16.38, 0.979, -3.27 ] + }, + { "time": 1, "value": -9.51 } + ] + }, + "front-bracer": { + "rotate": [ + { + "value": 13.57, + "curve": [ 0.022, 9.71, 0.147, -3.78 ] + }, + { + "time": 0.3667, + "value": -3.69, + "curve": [ 0.457, -3.66, 0.479, 0.83 ] + }, + { + "time": 0.5, + "value": 4.05, + "curve": [ 0.513, 6.08, 0.635, 30.8 ] + }, + { + "time": 0.8, + "value": 30.92, + "curve": [ 0.974, 31, 0.98, 18.35 ] + }, + { "time": 1, "value": 13.57 } + ] + }, + "front-fist": { + "rotate": [ + { + "value": -28.72, + "curve": [ 0.024, -31.74, 0.176, -43.4 ] + }, + { + "time": 0.3667, + "value": -43.6, + "curve": [ 0.403, -43.65, 0.47, -40.15 ] + }, + { + "time": 0.5, + "value": -35.63, + "curve": [ 0.547, -28.59, 0.624, -4.57 ] + }, + { + "time": 0.7333, + "value": -4.59, + "curve": [ 0.891, -4.62, 0.954, -24.28 ] + }, + { "time": 1, "value": -28.48 } + ] + }, + "rear-upper-arm": { + "rotate": [ + { + "value": 28.28, + "curve": [ 0.034, 30.94, 0.068, 32.05 ] + }, + { + "time": 0.1, + "value": 31.88, + "curve": [ 0.194, 31.01, 0.336, -0.11 ] + }, + { + "time": 0.3667, + "value": -7.11, + "curve": [ 0.421, -19.73, 0.53, -46.21 ] + }, + { + "time": 0.6, + "value": -45.75, + "curve": [ 0.708, -45.03, 0.844, -13.56 ] + }, + { + "time": 0.8667, + "value": -6.48, + "curve": [ 0.909, 6.59, 0.958, 24.21 ] + }, + { "time": 1, "value": 28.28 } + ] + }, + "hair2": { + "rotate": [ + { + "value": -2.79, + "curve": [ 0.074, -2.84, 0.121, 25.08 ] + }, + { + "time": 0.2333, + "value": 24.99, + "curve": [ 0.35, 24.89, 0.427, -2.86 ] + }, + { + "time": 0.5, + "value": -2.8, + "curve": [ 0.575, -2.73, 0.652, 24.5 ] + }, + { + "time": 0.7333, + "value": 24.55, + "curve": [ 0.828, 24.6, 0.932, -2.69 ] + }, + { "time": 1, "value": -2.79 } + ] + }, + "hair4": { + "rotate": [ + { + "value": -6.01, + "curve": [ 0.106, -5.97, 0.151, 18.62 ] + }, + { + "time": 0.2333, + "value": 18.72, + "curve": [ 0.336, 18.7, 0.405, -11.37 ] + }, + { + "time": 0.5, + "value": -11.45, + "curve": [ 0.626, -11.46, 0.629, 18.94 ] + }, + { + "time": 0.7333, + "value": 18.92, + "curve": [ 0.833, 18.92, 0.913, -6.06 ] + }, + { "time": 1, "value": -6.01 } + ], + "translate": [ + { "x": 0.03, "y": 1.35 } + ] + }, + "rear-bracer": { + "rotate": [ + { + "value": 10.06, + "curve": [ 0.044, 11.16, 0.063, 11.49 ] + }, + { + "time": 0.1, + "value": 11.49, + "curve": [ 0.215, 11.49, 0.336, 2.92 ] + }, + { + "time": 0.3667, + "value": 0.84, + "curve": [ 0.416, -2.52, 0.498, -10.84 ] + }, + { + "time": 0.6, + "value": -10.83, + "curve": [ 0.762, -10.71, 0.845, -3.05 ] + }, + { + "time": 0.8667, + "value": -1.34, + "curve": [ 0.917, 2.54, 0.977, 8.81 ] + }, + { "time": 1, "value": 10.06 } + ] + }, + "gun": { + "rotate": [ + { + "value": -14.67, + "curve": [ 0.086, -14.67, 0.202, 8.31 ] + }, + { + "time": 0.2333, + "value": 12.14, + "curve": [ 0.279, 17.71, 0.391, 25.79 ] + }, + { + "time": 0.5, + "value": 25.77, + "curve": [ 0.631, 25.74, 0.694, 4.53 ] + }, + { + "time": 0.7333, + "value": -0.65, + "curve": [ 0.768, -5.21, 0.902, -14.4 ] + }, + { "time": 1, "value": -14.67 } + ] + }, + "front-leg-target": { + "translate": [ + { + "x": -2.83, + "y": -8.48, + "curve": [ 0.008, -2.83, 0.058, 0.09, 0.001, 4.97, 0.058, 6.68 ] + }, + { + "time": 0.0667, + "x": 0.09, + "y": 6.68, + "curve": [ 0.3, 0.09, 0.767, -2.83, 0.3, 6.68, 0.767, -8.48 ] + }, + { "time": 1, "x": -2.83, "y": -8.48 } + ] + }, + "hair1": { + "rotate": [ + { + "curve": [ 0.028, 1.24, 0.016, 3.46 ] + }, + { + "time": 0.1, + "value": 3.45, + "curve": [ 0.159, 3.45, 0.189, 0.23 ] + }, + { + "time": 0.2333, + "value": -2.29, + "curve": [ 0.265, -4.32, 0.305, -5.92 ] + }, + { + "time": 0.3667, + "value": -5.94, + "curve": [ 0.446, -5.96, 0.52, 3.41 ] + }, + { + "time": 0.6, + "value": 3.42, + "curve": [ 0.717, 3.42, 0.772, -5.93 ] + }, + { + "time": 0.8667, + "value": -5.97, + "curve": [ 0.933, -5.99, 0.982, -0.94 ] + }, + { "time": 1 } + ] + }, + "hair3": { + "rotate": [ + { + "curve": [ 0.067, 0, 0.159, -10.48 ] + }, + { + "time": 0.2333, + "value": -10.49, + "curve": [ 0.334, -10.5, 0.439, -0.09 ] + }, + { + "time": 0.5, + "value": -0.09, + "curve": [ 0.569, -0.09, 0.658, -10.75 ] + }, + { + "time": 0.7333, + "value": -10.7, + "curve": [ 0.833, -10.63, 0.947, 0 ] + }, + { "time": 1 } + ] + }, + "gun-tip": { + "rotate": [ + { "time": 0.2333, "value": 0.11 } + ] + }, + "muzzle-ring": { + "rotate": [ + { "time": 0.2333, "value": 0.11 } + ] + }, + "muzzle-ring2": { + "rotate": [ + { "time": 0.2667, "value": 0.11 } + ] + }, + "muzzle-ring3": { + "rotate": [ + { "time": 0.2667, "value": 0.11 } + ] + }, + "muzzle-ring4": { + "rotate": [ + { "time": 0.2667, "value": 0.11 } + ] + }, + "back-shoulder": { + "translate": [ + { + "x": -0.18, + "y": -4.49, + "curve": [ 0.133, -0.18, 0.333, 7.69, 0.133, -4.49, 0.333, 2.77 ] + }, + { + "time": 0.4667, + "x": 7.69, + "y": 2.77, + "curve": [ 0.6, 7.69, 0.858, -0.18, 0.6, 2.77, 0.858, -4.49 ] + }, + { "time": 1, "x": -0.18, "y": -4.49 } + ] + }, + "front-shoulder": { + "translate": [ + { + "x": 1.46, + "y": 9.37, + "curve": [ 0.162, 1.41, 0.333, -1.66, 0.162, 9.37, 0.301, -7.23 ] + }, + { + "time": 0.5, + "x": -1.6, + "y": -7.27, + "curve": [ 0.735, -1.5, 0.847, 1.46, 0.723, -7.31, 0.838, 9.32 ] + }, + { "time": 1, "x": 1.46, "y": 9.37 } + ] + }, + "head-control": { + "translate": [ + { + "x": -6.46, + "y": -8.4, + "curve": [ 0.053, -5.31, 0.167, -3.64, 0.093, -8.4, 0.196, -3.81 ] + }, + { + "time": 0.2333, + "x": -3.64, + "y": -1.32, + "curve": [ 0.309, -3.64, 0.436, -5.84, 0.275, 1.43, 0.38, 10.3 ] + }, + { + "time": 0.5, + "x": -7.03, + "y": 10.29, + "curve": [ 0.538, -7.75, 0.66, -10.54, 0.598, 10.27, 0.694, 1.56 ] + }, + { + "time": 0.7333, + "x": -10.54, + "y": -1.26, + "curve": [ 0.797, -10.54, 0.933, -7.91, 0.768, -3.79, 0.875, -8.4 ] + }, + { "time": 1, "x": -6.46, "y": -8.4 } + ] + } + }, + "ik": { + "front-leg-ik": [ + { + "softness": 25.7, + "bendPositive": false, + "curve": [ 0.008, 1, 0.025, 1, 0.008, 25.7, 0.025, 9.9 ] + }, + { + "time": 0.0333, + "softness": 9.9, + "bendPositive": false, + "curve": [ 0.15, 1, 0.383, 1, 0.15, 9.9, 0.383, 43.2 ] + }, + { + "time": 0.5, + "softness": 43.2, + "bendPositive": false, + "curve": [ 0.625, 1, 0.875, 1, 0.625, 43.2, 0.846, 45.57 ] + }, + { "time": 1, "softness": 25.7, "bendPositive": false } + ], + "rear-leg-ik": [ + { "softness": 5, "bendPositive": false }, + { "time": 0.4333, "softness": 4.9, "bendPositive": false }, + { "time": 0.5, "softness": 28.81, "bendPositive": false }, + { "time": 0.6, "softness": 43.8, "bendPositive": false }, + { "time": 1, "softness": 5, "bendPositive": false } + ] + }, + "events": [ + { "name": "footstep" }, + { "time": 0.5, "name": "footstep" } + ] + } + } + } \ No newline at end of file diff --git a/e2e/.dev/public/spineboy.png b/e2e/.dev/public/spineboy.png new file mode 100644 index 0000000000000000000000000000000000000000..0ea9737f30707915e0bf495cdf987c6511dc0500 GIT binary patch literal 245321 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 z

  • sZ@kf@gL7X)y;k|NZYN7CI4}AlT7Z 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

    $ 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%0DxD_{VYG+f0?szS)DJk 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 kPx# literal 0 HcmV?d00001 diff --git a/e2e/.dev/public/tank-pro.atlas b/e2e/.dev/public/tank-pro.atlas new file mode 100644 index 0000000000..0c1a46580e --- /dev/null +++ b/e2e/.dev/public/tank-pro.atlas @@ -0,0 +1,65 @@ +tank-pro.png +size:870,638 +filter:Linear,Linear +scale:0.5 +antenna +bounds:647,376,11,152 +cannon +bounds:2,359,466,29 +cannon-connector +bounds:812,448,56,68 +ground +bounds:2,16,512,177 +guntower +bounds:641,4,365,145 +rotate:90 +machinegun +bounds:470,359,166,29 +machinegun-mount +bounds:823,518,36,48 +rock +bounds:690,371,265,61 +offsets:7,1,290,64 +rotate:90 +smoke-glow +bounds:817,371,50,50 +smoke-puff01-bg +bounds:753,371,92,62 +rotate:90 +smoke-puff01-fg +bounds:753,465,86,57 +offsets:1,1,88,59 +rotate:90 +smoke-puff02-fg +bounds:788,127,85,53 +offsets:4,6,92,62 +rotate:90 +smoke-puff03-fg +bounds:788,214,85,53 +offsets:4,6,92,62 +rotate:90 +smoke-puff04-fg +bounds:788,47,78,48 +rotate:90 +tank-bottom +bounds:2,390,643,138 +tank-bottom-shadow +bounds:2,195,638,162 +offsets:1,8,646,171 +tank-top +bounds:2,530,687,106 +offsets:13,5,704,111 +tread +bounds:817,423,48,15 +tread-inside +bounds:611,81,13,14 +wheel-big +bounds:516,97,96,96 +wheel-big-overlay +bounds:516,2,93,93 +wheel-mid +bounds:753,553,68,68 +wheel-mid-overlay +bounds:788,301,68,68 +wheel-small +bounds:788,9,36,36 diff --git a/e2e/.dev/public/tank-pro.json b/e2e/.dev/public/tank-pro.json new file mode 100644 index 0000000000..324d313149 --- /dev/null +++ b/e2e/.dev/public/tank-pro.json @@ -0,0 +1 @@ +{"skeleton":{"hash":"nW5DL2uoEfA","spine":"4.2.40","x":-5852.65,"y":-348.5,"width":7202.61,"height":1298.88,"images":"./images/","audio":""},"bones":[{"name":"root"},{"name":"tank-root","parent":"root","y":146.79},{"name":"tank-treads","parent":"tank-root","y":48.35},{"name":"tank-body","parent":"tank-treads","y":10},{"name":"guntower","parent":"tank-body","x":-21.72,"y":245.48},{"name":"antenna-root","parent":"guntower","x":164.61,"y":202.53},{"name":"antenna1","parent":"antenna-root","length":40,"rotation":90,"y":40,"color":"ffee00ff"},{"name":"antenna2","parent":"antenna1","length":42,"x":42,"color":"ffee00ff"},{"name":"antenna3","parent":"antenna2","length":42,"x":42,"color":"ffee00ff"},{"name":"antenna4","parent":"antenna3","length":42,"x":42,"color":"ffee00ff"},{"name":"antenna5","parent":"antenna4","length":42,"x":42,"color":"ffee00ff"},{"name":"antenna6","parent":"antenna5","length":42,"x":42,"color":"ffee00ff"},{"name":"cannon-connector","parent":"guntower","x":-235.05,"y":96.07},{"name":"cannon-target","parent":"tank-root","x":-2276.67,"y":400.17,"color":"0096ffff","icon":"arrows"},{"name":"cannon","parent":"cannon-connector","length":946.68,"rotation":180,"color":"ff4000ff"},{"name":"machinegun-mount","parent":"guntower","length":90.98,"rotation":90,"x":-123.73,"y":218.33,"color":"15ff00ff"},{"name":"machinegun-target","parent":"tank-root","x":-2272.76,"y":607.77,"color":"0096ffff","icon":"ik"},{"name":"machinegun","parent":"machinegun-mount","length":208.95,"rotation":90,"x":91.52,"y":-1.03,"color":"15ff00ff"},{"name":"machinegun-tip","parent":"machinegun","x":210.43,"y":-12.21},{"name":"rock","parent":"root","x":-1925.2,"y":33.17},{"name":"smoke-root","parent":"tank-root","x":-1200.38,"y":405.76,"scaleX":-6.5,"scaleY":6.5,"color":"ff4000ff","icon":"particles"},{"name":"smoke-glow","parent":"smoke-root","x":62.92,"y":-0.71,"color":"ff4000ff","icon":"particles"},{"name":"smoke1","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke10","parent":"smoke-root","rotation":-103.52,"x":25.45,"y":2.48,"scaleX":3.9011,"scaleY":2.8523,"color":"ff4000ff","icon":"particles"},{"name":"smoke11","parent":"smoke-root","rotation":-103.52,"x":25.45,"y":2.48,"scaleX":3.9011,"scaleY":2.8523,"color":"ff4000ff","icon":"particles"},{"name":"smoke12","parent":"smoke-root","rotation":-103.52,"x":25.45,"y":2.48,"scaleX":3.9011,"scaleY":2.8523,"color":"ff4000ff","icon":"particles"},{"name":"smoke13","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke14","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke15","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke16","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke17","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke18","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke2","parent":"smoke-root","rotation":-84.14,"x":45.06,"y":29.7,"scaleX":3.3345,"scaleY":3.3345,"color":"ff4000ff","icon":"particles"},{"name":"smoke20","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke21","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke22","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke23","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke24","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke25","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke26","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke27","parent":"smoke-root","rotation":-179.99,"x":14.75,"y":-1.55,"scaleX":1.6484,"scaleY":1.6484,"color":"ff4000ff","icon":"particles"},{"name":"smoke3","parent":"smoke-root","rotation":-87.91,"x":55.15,"y":-17.5,"scaleX":3.0415,"scaleY":4.157,"color":"ff4000ff","icon":"particles"},{"name":"smoke4","parent":"smoke-root","rotation":-87.91,"x":69.25,"y":8.01,"scaleX":2.1808,"scaleY":2.9807,"color":"ff4000ff","icon":"particles"},{"name":"smoke5","parent":"smoke-root","rotation":-87.91,"x":80.63,"y":59.88,"scaleX":4.5119,"scaleY":2.9725,"color":"ff4000ff","icon":"particles"},{"name":"smoke6","parent":"smoke-root","rotation":-87.91,"x":96.19,"y":25.65,"scaleX":3.7912,"scaleY":3.0552,"color":"ff4000ff","icon":"particles"},{"name":"smoke7","parent":"smoke-root","rotation":153.68,"x":85.65,"y":-50.47,"scaleX":4.8523,"scaleY":3.6528,"color":"ff4000ff","icon":"particles"},{"name":"smoke8","parent":"smoke-root","rotation":67.58,"x":47.85,"y":-42.55,"scaleX":4.0006,"scaleY":3.4796,"color":"ff4000ff","icon":"particles"},{"name":"smoke9","parent":"smoke-root","rotation":150.05,"x":104.02,"y":-8.73,"scaleX":4.2074,"scaleY":3.0762,"color":"ff4000ff","icon":"particles"},{"name":"tank-glow","parent":"tank-root","x":-247.72,"y":404.37,"scaleX":1.0582,"scaleY":0.6785},{"name":"tread","parent":"tank-root","length":82,"rotation":180,"x":-22.9,"y":213.86,"scaleX":0.9933,"color":"e64344ff"},{"name":"wheel-mid-center","parent":"tank-root","y":-66.21},{"name":"tread-collider1","parent":"wheel-mid-center","x":-329.58,"y":-85.44,"color":"ff00fbff"},{"name":"tread-collider2","parent":"wheel-mid-center","x":-165.95,"y":-85.44,"color":"ff00fbff"},{"name":"tread-collider3","parent":"wheel-mid-center","y":-85.44,"color":"ff00fbff"},{"name":"tread-collider4","parent":"wheel-mid-center","x":163.56,"y":-85.44,"color":"ff00fbff"},{"name":"tread-collider5","parent":"wheel-mid-center","x":329.12,"y":-85.44,"color":"ff00fbff"},{"name":"tread-gravity1","parent":"tank-root","rotation":180,"x":-175.35,"y":149.31,"color":"ff00fbff"},{"name":"tread-gravity2","parent":"tank-root","rotation":180,"x":177.89,"y":144.78,"color":"ff00fbff"},{"name":"tread10","parent":"tread","length":82,"rotation":48.85,"x":662.9,"y":-120.35,"color":"e64344ff"},{"name":"tread11","parent":"tread","length":82,"rotation":97.99,"x":651.5,"y":-39.69,"color":"e64344ff"},{"name":"tread12","parent":"tread","length":82,"rotation":113.79,"x":618.43,"y":34.83,"color":"e64344ff"},{"name":"tread13","parent":"tread","length":82,"rotation":122.96,"x":573.82,"y":103.18,"color":"e64344ff"},{"name":"tread14","parent":"tread","length":82,"rotation":142.01,"x":509.19,"y":153.3,"color":"e64344ff"},{"name":"tread15","parent":"tread","length":82,"rotation":157.84,"x":433.25,"y":184.02,"color":"e64344ff"},{"name":"tread16","parent":"tread","length":82,"rotation":157.37,"x":357.56,"y":215.37,"color":"e64344ff"},{"name":"tread17","parent":"tread","length":82,"rotation":157.29,"x":281.92,"y":246.8,"color":"e64344ff"},{"name":"tread18","parent":"tread","length":82,"rotation":157.19,"x":206.33,"y":278.38,"color":"e64344ff"},{"name":"tread19","parent":"tread","length":82,"rotation":157.14,"x":130.77,"y":310.02,"color":"e64344ff"},{"name":"tread2","parent":"tread","length":82,"x":82,"color":"e64344ff"},{"name":"tread20","parent":"tread","length":82,"rotation":157.34,"x":55.1,"y":341.41,"color":"e64344ff"},{"name":"tread21","parent":"tread","length":82,"rotation":158.11,"x":-20.99,"y":371.77,"color":"e64344ff"},{"name":"tread22","parent":"tread","length":82,"rotation":157.99,"x":-97.02,"y":402.28,"color":"e64344ff"},{"name":"tread23","parent":"tread","length":82,"rotation":157.59,"x":-172.83,"y":433.33,"color":"e64344ff"},{"name":"tread24","parent":"tread","length":82,"rotation":156.86,"x":-248.23,"y":465.34,"color":"e64344ff"},{"name":"tread25","parent":"tread","length":82,"rotation":177.94,"x":-330.17,"y":468.27,"color":"e64344ff"},{"name":"tread26","parent":"tread","length":82,"rotation":-169.55,"x":-410.81,"y":453.5,"color":"e64344ff"},{"name":"tread27","parent":"tread","length":82,"rotation":-163.86,"x":-489.58,"y":430.86,"color":"e64344ff"},{"name":"tread28","parent":"tread","length":82,"rotation":-139.13,"x":-551.59,"y":377.57,"color":"e64344ff"},{"name":"tread29","parent":"tread","length":82,"rotation":-89.04,"x":-550.21,"y":296.14,"color":"e64344ff"},{"name":"tread3","parent":"tread","length":82,"rotation":-8.91,"x":163.01,"y":-12.61,"color":"e64344ff"},{"name":"tread30","parent":"tread","length":82,"rotation":-38.99,"x":-486.48,"y":244.89,"color":"e64344ff"},{"name":"tread31","parent":"tread","length":82,"rotation":-20.04,"x":-409.45,"y":216.98,"color":"e64344ff"},{"name":"tread32","parent":"tread","length":82,"rotation":-46.24,"x":-352.74,"y":158.15,"color":"e64344ff"},{"name":"tread33","parent":"tread","length":82,"rotation":-27.95,"x":-280.3,"y":119.98,"color":"e64344ff"},{"name":"tread34","parent":"tread","length":82,"rotation":10.46,"x":-199.66,"y":134.77,"color":"e64344ff"},{"name":"tread35","parent":"tread","length":82,"rotation":-17.9,"x":-121.63,"y":109.73,"color":"e64344ff"},{"name":"tread36","parent":"tread","length":82,"rotation":-36.82,"x":-55.99,"y":60.92,"color":"fbff00ff"},{"name":"tread4","parent":"tread","length":82,"rotation":-29.27,"x":234.55,"y":-52.43,"color":"e64344ff"},{"name":"tread5","parent":"tread","length":82,"rotation":-45.26,"x":292.26,"y":-110.28,"color":"e64344ff"},{"name":"tread6","parent":"tread","length":82,"rotation":-15.29,"x":371.36,"y":-131.76,"color":"e64344ff"},{"name":"tread7","parent":"tread","length":82,"rotation":-5.49,"x":452.98,"y":-139.55,"color":"e64344ff"},{"name":"tread8","parent":"tread","length":82,"rotation":-24.99,"x":527.31,"y":-173.95,"color":"e64344ff"},{"name":"tread9","parent":"tread","length":82,"rotation":-5.44,"x":608.94,"y":-181.68,"color":"e64344ff"},{"name":"wheel-big-root1","parent":"tank-treads","x":-549.6,"y":14.4,"color":"abe323ff"},{"name":"wheel-big-root2","parent":"tank-treads","x":547.34,"y":14.4},{"name":"wheel-big1","parent":"wheel-big-root1","x":-0.02,"color":"abe323ff"},{"name":"wheel-big2","parent":"wheel-big-root2"},{"name":"wheel-mid-root1","parent":"wheel-mid-center","x":-410.57,"color":"abe323ff"},{"name":"wheel-mid-root2","parent":"wheel-mid-center","x":-246.95},{"name":"wheel-mid-root3","parent":"wheel-mid-center","x":-82.73},{"name":"wheel-mid-root4","parent":"wheel-mid-center","x":80.89},{"name":"wheel-mid-root5","parent":"wheel-mid-center","x":244.51},{"name":"wheel-mid-root6","parent":"wheel-mid-center","x":408.74},{"name":"wheel-mid1","parent":"wheel-mid-root1","color":"abe323ff"},{"name":"wheel-mid2","parent":"wheel-mid-root2"},{"name":"wheel-mid3","parent":"wheel-mid-root3"},{"name":"wheel-mid4","parent":"wheel-mid-root4"},{"name":"wheel-mid5","parent":"wheel-mid-root5"},{"name":"wheel-mid6","parent":"wheel-mid-root6"},{"name":"wheel-small-root1","parent":"tank-treads","x":-337.39,"y":109.43},{"name":"wheel-small-root2","parent":"tank-treads","x":0.09,"y":109.43},{"name":"wheel-small-root3","parent":"tank-treads","x":334.69,"y":109.43},{"name":"wheel-small1","parent":"wheel-small-root1","color":"abe323ff"},{"name":"wheel-small2","parent":"wheel-small-root2"},{"name":"wheel-small3","parent":"wheel-small-root3"}],"slots":[{"name":"rock","bone":"rock","attachment":"rock"},{"name":"ground","bone":"root","attachment":"ground"},{"name":"ground2","bone":"root","attachment":"ground"},{"name":"ground3","bone":"root","attachment":"ground"},{"name":"ground4","bone":"root","attachment":"ground"},{"name":"ground5","bone":"root","attachment":"ground"},{"name":"ground6","bone":"root","attachment":"ground"},{"name":"ground7","bone":"root","attachment":"ground"},{"name":"tank-body-shadow","bone":"tank-body","color":"ffffffb9","attachment":"tank-bottom-shadow"},{"name":"bottom","bone":"tank-body","attachment":"tank-bottom"},{"name":"tread-inside1","bone":"tread","attachment":"tread-inside"},{"name":"tread-inside53","bone":"tread27","attachment":"tread-inside"},{"name":"tread-inside27","bone":"tread14","attachment":"tread-inside"},{"name":"tread-inside3","bone":"tread2","attachment":"tread-inside"},{"name":"tread-inside55","bone":"tread28","attachment":"tread-inside"},{"name":"tread-inside29","bone":"tread15","attachment":"tread-inside"},{"name":"tread-inside5","bone":"tread3","attachment":"tread-inside"},{"name":"tread-inside57","bone":"tread29","attachment":"tread-inside"},{"name":"tread-inside31","bone":"tread16","attachment":"tread-inside"},{"name":"tread-inside7","bone":"tread4","attachment":"tread-inside"},{"name":"tread-inside59","bone":"tread30","attachment":"tread-inside"},{"name":"tread-inside33","bone":"tread17","attachment":"tread-inside"},{"name":"tread-inside9","bone":"tread5","attachment":"tread-inside"},{"name":"tread-inside61","bone":"tread31","attachment":"tread-inside"},{"name":"tread-inside35","bone":"tread18","attachment":"tread-inside"},{"name":"tread-inside11","bone":"tread6","attachment":"tread-inside"},{"name":"tread-inside63","bone":"tread32","attachment":"tread-inside"},{"name":"tread-inside37","bone":"tread19","attachment":"tread-inside"},{"name":"tread-inside13","bone":"tread7","attachment":"tread-inside"},{"name":"tread-inside65","bone":"tread33","attachment":"tread-inside"},{"name":"tread-inside39","bone":"tread20","attachment":"tread-inside"},{"name":"tread-inside15","bone":"tread8","attachment":"tread-inside"},{"name":"tread-inside67","bone":"tread34","attachment":"tread-inside"},{"name":"tread-inside69","bone":"tread35","attachment":"tread-inside"},{"name":"tread-inside71","bone":"tread36","attachment":"tread-inside"},{"name":"tread-inside41","bone":"tread21","attachment":"tread-inside"},{"name":"tread-inside17","bone":"tread9","attachment":"tread-inside"},{"name":"tread-inside43","bone":"tread22","attachment":"tread-inside"},{"name":"tread-inside19","bone":"tread10","attachment":"tread-inside"},{"name":"tread-inside45","bone":"tread23","attachment":"tread-inside"},{"name":"tread-inside21","bone":"tread11","attachment":"tread-inside"},{"name":"tread-inside47","bone":"tread24","attachment":"tread-inside"},{"name":"tread-inside23","bone":"tread12","attachment":"tread-inside"},{"name":"tread-inside49","bone":"tread25","attachment":"tread-inside"},{"name":"tread-inside25","bone":"tread13","attachment":"tread-inside"},{"name":"tread-inside51","bone":"tread26","attachment":"tread-inside"},{"name":"tread-inside2","bone":"tread","attachment":"tread-inside"},{"name":"tread-inside54","bone":"tread27","attachment":"tread-inside"},{"name":"tread-inside28","bone":"tread14","attachment":"tread-inside"},{"name":"tread-inside4","bone":"tread2","attachment":"tread-inside"},{"name":"tread-inside56","bone":"tread28","attachment":"tread-inside"},{"name":"tread-inside30","bone":"tread15","attachment":"tread-inside"},{"name":"tread-inside6","bone":"tread3","attachment":"tread-inside"},{"name":"tread-inside58","bone":"tread29","attachment":"tread-inside"},{"name":"tread-inside32","bone":"tread16","attachment":"tread-inside"},{"name":"tread-inside8","bone":"tread4","attachment":"tread-inside"},{"name":"tread-inside60","bone":"tread30","attachment":"tread-inside"},{"name":"tread-inside34","bone":"tread17","attachment":"tread-inside"},{"name":"tread-inside10","bone":"tread5","attachment":"tread-inside"},{"name":"tread-inside62","bone":"tread31","attachment":"tread-inside"},{"name":"tread-inside36","bone":"tread18","attachment":"tread-inside"},{"name":"tread-inside12","bone":"tread6","attachment":"tread-inside"},{"name":"tread-inside64","bone":"tread32","attachment":"tread-inside"},{"name":"tread-inside38","bone":"tread19","attachment":"tread-inside"},{"name":"tread-inside14","bone":"tread7","attachment":"tread-inside"},{"name":"tread-inside66","bone":"tread33","attachment":"tread-inside"},{"name":"tread-inside40","bone":"tread20","attachment":"tread-inside"},{"name":"tread-inside16","bone":"tread8","attachment":"tread-inside"},{"name":"tread-inside68","bone":"tread34","attachment":"tread-inside"},{"name":"tread-inside70","bone":"tread35","attachment":"tread-inside"},{"name":"tread-inside72","bone":"tread36","attachment":"tread-inside"},{"name":"tread-inside42","bone":"tread21","attachment":"tread-inside"},{"name":"tread-inside18","bone":"tread9","attachment":"tread-inside"},{"name":"tread-inside44","bone":"tread22","attachment":"tread-inside"},{"name":"tread-inside20","bone":"tread10","attachment":"tread-inside"},{"name":"tread-inside46","bone":"tread23","attachment":"tread-inside"},{"name":"tread-inside22","bone":"tread11","attachment":"tread-inside"},{"name":"tread-inside48","bone":"tread24","attachment":"tread-inside"},{"name":"tread-inside24","bone":"tread12","attachment":"tread-inside"},{"name":"tread-inside50","bone":"tread25","attachment":"tread-inside"},{"name":"tread-inside26","bone":"tread13","attachment":"tread-inside"},{"name":"tread-inside52","bone":"tread26","attachment":"tread-inside"},{"name":"wheel-big","bone":"wheel-big1","color":"dbdbdbff","attachment":"wheel-big"},{"name":"wheel-big2","bone":"wheel-big2","color":"dbdbdbff","attachment":"wheel-big"},{"name":"wheel-mid","bone":"wheel-mid1","attachment":"wheel-mid"},{"name":"wheel-mid2","bone":"wheel-mid2","attachment":"wheel-mid"},{"name":"wheel-mid3","bone":"wheel-mid3","attachment":"wheel-mid"},{"name":"wheel-mid4","bone":"wheel-mid4","attachment":"wheel-mid"},{"name":"wheel-mid5","bone":"wheel-mid5","attachment":"wheel-mid"},{"name":"wheel-mid6","bone":"wheel-mid6","attachment":"wheel-mid"},{"name":"wheel-small","bone":"wheel-small1","attachment":"wheel-small"},{"name":"wheel-small2","bone":"wheel-small2","attachment":"wheel-small"},{"name":"wheel-small3","bone":"wheel-small3","attachment":"wheel-small"},{"name":"wheel-mid-overlay","bone":"wheel-mid-root1","color":"ffffffec","attachment":"wheel-mid-overlay","blend":"multiply"},{"name":"wheel-mid-overlay2","bone":"wheel-mid-root2","color":"ffffffec","attachment":"wheel-mid-overlay","blend":"multiply"},{"name":"wheel-mid-overlay3","bone":"wheel-mid-root3","color":"ffffffec","attachment":"wheel-mid-overlay","blend":"multiply"},{"name":"wheel-mid-overlay4","bone":"wheel-mid-root4","color":"ffffffec","attachment":"wheel-mid-overlay","blend":"multiply"},{"name":"wheel-mid-overlay5","bone":"wheel-mid-root5","color":"ffffffec","attachment":"wheel-mid-overlay","blend":"multiply"},{"name":"wheel-mid-overlay6","bone":"wheel-mid-root6","color":"ffffffec","attachment":"wheel-mid-overlay","blend":"multiply"},{"name":"wheel-big-overlay1","bone":"wheel-big-root1","color":"ffffffe9","attachment":"wheel-big-overlay","blend":"multiply"},{"name":"wheel-big-overlay2","bone":"wheel-big-root2","color":"ffffffe9","attachment":"wheel-big-overlay","blend":"multiply"},{"name":"treads-path","bone":"tank-root","attachment":"treads-path"},{"name":"tread","bone":"tread","attachment":"tread"},{"name":"tread27","bone":"tread27","color":"adc9b8ff","attachment":"tread"},{"name":"tread14","bone":"tread14","attachment":"tread"},{"name":"tread2","bone":"tread2","attachment":"tread"},{"name":"tread28","bone":"tread28","attachment":"tread"},{"name":"tread15","bone":"tread15","color":"adc9b8ff","attachment":"tread"},{"name":"tread3","bone":"tread3","color":"adc9b8ff","attachment":"tread"},{"name":"tread29","bone":"tread29","color":"adc9b8ff","attachment":"tread"},{"name":"tread16","bone":"tread16","attachment":"tread"},{"name":"tread4","bone":"tread4","attachment":"tread"},{"name":"tread30","bone":"tread30","attachment":"tread"},{"name":"tread17","bone":"tread17","color":"adc9b8ff","attachment":"tread"},{"name":"tread5","bone":"tread5","color":"adc9b8ff","attachment":"tread"},{"name":"tread31","bone":"tread31","color":"adc9b8ff","attachment":"tread"},{"name":"tread18","bone":"tread18","attachment":"tread"},{"name":"tread6","bone":"tread6","attachment":"tread"},{"name":"tread32","bone":"tread32","attachment":"tread"},{"name":"tread19","bone":"tread19","color":"adc9b8ff","attachment":"tread"},{"name":"tread7","bone":"tread7","color":"adc9b8ff","attachment":"tread"},{"name":"tread33","bone":"tread33","color":"adc9b8ff","attachment":"tread"},{"name":"tread20","bone":"tread20","attachment":"tread"},{"name":"tread8","bone":"tread8","attachment":"tread"},{"name":"tread34","bone":"tread34","attachment":"tread"},{"name":"tread35","bone":"tread35","color":"adc9b8ff","attachment":"tread"},{"name":"tread36","bone":"tread36","color":"adc9b8ff","attachment":"tread"},{"name":"tread21","bone":"tread21","color":"adc9b8ff","attachment":"tread"},{"name":"tread9","bone":"tread9","color":"adc9b8ff","attachment":"tread"},{"name":"tread22","bone":"tread22","attachment":"tread"},{"name":"tread10","bone":"tread10","attachment":"tread"},{"name":"tread23","bone":"tread23","color":"adc9b8ff","attachment":"tread"},{"name":"tread11","bone":"tread11","color":"adc9b8ff","attachment":"tread"},{"name":"tread24","bone":"tread24","attachment":"tread"},{"name":"tread12","bone":"tread12","attachment":"tread"},{"name":"tread25","bone":"tread25","color":"adc9b8ff","attachment":"tread"},{"name":"tread13","bone":"tread13","color":"adc9b8ff","attachment":"tread"},{"name":"tread26","bone":"tread26","attachment":"tread"},{"name":"machinegun","bone":"machinegun","attachment":"machinegun"},{"name":"machinegun-mount","bone":"machinegun-mount","attachment":"machinegun-mount"},{"name":"tank-top","bone":"tank-body","attachment":"tank-top"},{"name":"guntower","bone":"guntower","attachment":"guntower"},{"name":"cannon","bone":"cannon","attachment":"cannon"},{"name":"cannon-connector","bone":"cannon-connector","attachment":"cannon-connector"},{"name":"antenna","bone":"antenna-root","attachment":"antenna"},{"name":"smoke-puff1-bg","bone":"smoke1","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg13","bone":"smoke13","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg14","bone":"smoke14","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg15","bone":"smoke15","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg16","bone":"smoke16","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg17","bone":"smoke17","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg18","bone":"smoke18","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg20","bone":"smoke20","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg21","bone":"smoke21","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg22","bone":"smoke22","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg23","bone":"smoke23","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg24","bone":"smoke24","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg25","bone":"smoke25","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg26","bone":"smoke26","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg27","bone":"smoke27","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg2","bone":"smoke2","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg5","bone":"smoke5","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg6","bone":"smoke6","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg7","bone":"smoke7","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg10","bone":"smoke10","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg11","bone":"smoke11","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg12","bone":"smoke12","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg8","bone":"smoke8","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg9","bone":"smoke9","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg4","bone":"smoke4","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-bg3","bone":"smoke3","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg","bone":"smoke1","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg13","bone":"smoke13","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg14","bone":"smoke14","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg15","bone":"smoke15","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg16","bone":"smoke16","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg17","bone":"smoke17","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg18","bone":"smoke18","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg20","bone":"smoke20","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg21","bone":"smoke21","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg22","bone":"smoke22","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg23","bone":"smoke23","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg24","bone":"smoke24","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg25","bone":"smoke25","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg26","bone":"smoke26","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg27","bone":"smoke27","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg2","bone":"smoke2","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg5","bone":"smoke5","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg6","bone":"smoke6","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg7","bone":"smoke7","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg10","bone":"smoke10","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg11","bone":"smoke11","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg12","bone":"smoke12","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg8","bone":"smoke8","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg9","bone":"smoke9","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg4","bone":"smoke4","color":"ecececff","dark":"000000"},{"name":"smoke-puff1-fg3","bone":"smoke3","color":"ecececff","dark":"000000"},{"name":"smoke-glow","bone":"smoke-glow","blend":"additive"},{"name":"clipping","bone":"tank-body","attachment":"clipping"},{"name":"tank-glow","bone":"tank-glow","color":"fcdc6da7","blend":"additive"}],"ik":[{"name":"cannon-ik","bones":["cannon"],"target":"cannon-target"},{"name":"machinegun-ik","order":1,"bones":["machinegun"],"target":"machinegun-target","mix":0}],"transform":[{"name":"wheel-big-transform","order":8,"bones":["wheel-big2"],"target":"wheel-big1","rotation":65.6,"mixX":0,"mixScaleX":0,"mixShearY":0},{"name":"wheel-mid1-transform","order":3,"bones":["wheel-mid2","wheel-mid4"],"target":"wheel-mid1","rotation":93,"mixX":0,"mixScaleX":0,"mixShearY":0},{"name":"wheel-mid2-transform","order":4,"bones":["wheel-mid3","wheel-mid5"],"target":"wheel-mid1","rotation":-89,"mixX":0,"mixScaleX":0,"mixShearY":0},{"name":"wheel-mid3-transform","order":5,"bones":["wheel-mid6"],"target":"wheel-mid1","rotation":-152.6,"mixX":0,"mixScaleX":0,"mixShearY":0},{"name":"wheel-small1-transform","order":6,"bones":["wheel-small2"],"target":"wheel-small1","rotation":87,"mixX":0,"mixScaleX":0,"mixShearY":0},{"name":"wheel-small2-transform","order":7,"bones":["wheel-small3"],"target":"wheel-small1","rotation":54.9,"mixX":0,"mixScaleX":0,"mixShearY":0}],"path":[{"name":"treads-path","order":2,"bones":["tread","tread2","tread3","tread4","tread5","tread6","tread7","tread8","tread9","tread10","tread11","tread12","tread13","tread14","tread15","tread16","tread17","tread18","tread19","tread20","tread21","tread22","tread23","tread24","tread25","tread26","tread27","tread28","tread29","tread30","tread31","tread32","tread33","tread34","tread35","tread36"],"target":"treads-path","rotateMode":"chain"}],"skins":[{"name":"default","attachments":{"antenna":{"antenna":{"type":"mesh","uvs":[0.64286,0.07876,0.65354,0.1535,0.66325,0.22138,0.67367,0.29433,0.68383,0.36543,0.6936,0.43374,0.70311,0.5003,0.71311,0.57031,0.72327,0.64139,0.73406,0.71689,0.74441,0.7893,0.75614,0.87141,0.76905,0.94311,1,0.94311,1,1,0,1,0,0.94311,0.20106,0.94311,0.20106,0.87094,0.21461,0.78847,0.22651,0.71607,0.23886,0.64099,0.25036,0.57105,0.26206,0.49983,0.27306,0.43291,0.2843,0.36454,0.29593,0.29382,0.308,0.22038,0.319,0.15345,0.33142,0.07796,0.34423,0,0.63161,0],"triangles":[30,31,0,29,30,0,29,0,1,28,29,1,28,1,2,27,28,2,27,2,3,26,3,4,25,26,4,25,4,5,26,27,3,24,5,6,23,24,6,7,23,6,24,25,5,22,7,8,21,22,8,21,8,9,7,22,23,20,9,10,19,20,10,20,21,9,19,10,11,18,19,11,17,18,11,17,11,12,15,16,17,12,13,14,15,17,12,14,15,12],"vertices":[2,10,65.38,-3.14,0.3125,11,23.38,-3.14,0.6875,2,10,42.73,-3.38,0.66667,11,0.73,-3.38,0.33333,2,9,64.17,-3.59,0.33333,10,22.17,-3.59,0.66667,2,9,42.06,-3.82,0.66667,10,0.06,-3.82,0.33333,2,8,62.52,-4.04,0.33333,9,20.52,-4.04,0.66667,2,8,41.82,-4.26,0.66667,9,-0.18,-4.26,0.33333,2,7,63.65,-4.47,0.33333,8,21.65,-4.47,0.66667,2,7,42.44,-4.69,0.66667,8,0.44,-4.69,0.33333,2,6,62.9,-4.91,0.33333,7,20.9,-4.91,0.66667,2,6,40.03,-5.15,0.66667,7,-1.97,-5.15,0.33333,2,5,5.38,58.09,0.4,6,18.09,-5.38,0.6,1,5,5.64,33.21,1,1,5,5.92,11.48,1,1,5,11,11.48,1,1,5,11,-5.76,1,1,5,-11,-5.76,1,1,5,-11,11.48,1,1,5,-6.58,11.48,1,1,5,-6.58,33.35,1,2,5,-6.28,58.34,0.4,6,18.34,6.28,0.6,2,6,40.27,6.02,0.66667,7,-1.73,6.02,0.33333,2,6,63.03,5.75,0.33333,7,21.03,5.75,0.66667,2,7,42.22,5.49,0.66667,8,0.22,5.49,0.33333,2,7,63.8,5.23,0.33333,8,21.8,5.23,0.66667,2,8,42.07,4.99,0.66667,9,0.07,4.99,0.33333,2,8,62.79,4.75,0.33333,9,20.79,4.75,0.66667,2,9,42.22,4.49,0.66667,10,0.22,4.49,0.33333,2,9,64.47,4.22,0.33333,10,22.47,4.22,0.66667,2,10,42.75,3.98,0.66667,11,0.75,3.98,0.33333,2,10,65.62,3.71,0.3125,11,23.62,3.71,0.6875,1,11,47.24,3.43,1,1,11,47.24,-2.9,1],"hull":32,"edges":[28,30,28,26,30,32,26,24,24,22,32,34,34,24,34,36,36,22,60,62,38,36,20,22,38,20,40,38,18,20,40,18,42,40,16,18,42,16,44,42,14,16,44,14,46,44,12,14,46,12,48,46,10,12,48,10,50,48,8,10,50,8,52,50,6,8,52,6,54,52,4,6,54,4,56,54,2,4,56,2,60,58,58,56,62,0,0,2,58,0],"width":22,"height":303}},"bottom":{"tank-bottom":{"x":-16.67,"y":9.89,"width":1285,"height":276}},"cannon":{"cannon":{"x":481.95,"y":-0.03,"rotation":180,"width":931,"height":58}},"cannon-connector":{"cannon-connector":{"type":"mesh","uvs":[1,0.03237,1,0.10603,0.90988,0.32859,0.81975,0.55116,0.72963,0.77373,0.6395,0.9963,0.42157,0.9963,0.20364,0.9963,0,0.85434,0,0.69902,0.02268,0.52884,0,0.31444,0.21602,0.12998,0.43368,0,0.63547,0.0037,0.48408,0.77059,0.31496,0.52497,0.64133,0.19648,0.21516,0.76766,0.58346,0.56471,0.68444,0.40146,0.46758,0.36649,0.28935,0.34604],"triangles":[7,18,6,6,18,15,7,8,18,8,9,18,18,16,15,15,16,19,9,10,18,18,10,16,16,21,19,19,21,20,10,22,16,10,11,22,16,22,21,21,17,20,21,12,13,17,13,14,17,21,13,11,12,22,21,22,12,6,15,5,5,15,4,15,19,4,4,19,3,19,20,3,3,20,2,20,17,2,2,17,1,17,14,1,14,0,1],"vertices":[1,12,35.91,69.08,1,1,12,35.91,59.14,1,1,12,25.82,29.09,1,1,12,15.72,-0.95,1,1,12,5.63,-31,1,1,12,-4.46,-61.05,1,2,12,-28.87,-61.05,0.33333,14,28.87,61.03,0.66667,1,14,53.28,61.02,1,1,14,76.09,41.84,1,1,14,71.17,21.63,1,1,14,72.83,-1.62,1,1,14,70.38,-29.12,1,1,14,50.67,-56.14,1,2,12,-28.43,74.38,0.41,14,28.43,-74.4,0.59,2,12,-4.92,72.95,0.52,14,4.92,-72.95,0.48,2,12,-21.87,-30.58,0.49,14,21.87,30.57,0.51,1,14,40.81,-2.6,1,2,12,-4.26,46.93,0.49,14,4.26,-46.93,0.51,1,14,51.99,30.15,1,2,12,-10.74,-2.78,0.49,14,10.74,2.78,0.51,2,12,0.57,19.25,0.49,14,-0.57,-19.25,0.51,1,14,23.72,-23.99,1,1,14,43.68,-26.76,1],"hull":15,"edges":[0,2,2,4,4,6,6,8,8,10,10,12,12,14,14,16,16,18,18,20,20,22,22,24,24,26,26,28,28,0],"width":112,"height":135}},"clipping":{"clipping":{"type":"clipping","end":"tank-glow","vertexCount":32,"vertices":[1,3,165.84,455.67,1,1,3,114.21,493.01,1,1,3,-38.53,492.23,1,1,3,-193.4,464.18,1,2,3,-280.85,415.48,0.752,14,24.09,-73.93,0.248,1,14,70.34,-27.32,1,1,14,412.56,-22.02,1,1,14,412.82,-29.21,1,1,14,539.26,-29.34,1,1,14,539.52,-17.09,1,1,14,894.02,-16.8,1,1,14,902.99,-28.89,1,1,14,942.06,-28.58,1,1,14,948.14,-16.64,1,1,14,947.9,14.29,1,1,14,539.3,14.55,1,1,14,539,29.22,1,1,14,412.51,29.88,1,1,14,412.51,21.73,1,1,14,74.24,27.28,1,1,3,-296.64,281.2,1,1,3,-316.06,225.71,1,1,3,-521.69,190.74,1,1,3,-610.03,141.02,1,1,3,-671.84,87.13,1,1,3,-652.23,-11.24,1,1,3,-618.53,-71.36,1,1,3,-478.77,-114.21,1,1,3,-274.11,-116.26,1,1,3,1.38,-45.75,1,1,3,189.67,148.78,1,1,3,215.75,276.59,1],"color":"ce3a3aff"}},"ground":{"ground":{"x":837.96,"y":-172,"width":1024,"height":353}},"ground2":{"ground":{"x":-179.89,"y":-172,"width":1024,"height":353}},"ground3":{"ground":{"x":-1213.48,"y":-172,"scaleX":1.035,"width":1024,"height":353}},"ground4":{"ground":{"x":-2268.51,"y":-172,"scaleX":1.04,"width":1024,"height":353}},"ground5":{"ground":{"x":-3306.54,"y":-172,"width":1024,"height":353}},"ground6":{"ground":{"x":-4322.71,"y":-172,"width":1024,"height":353}},"ground7":{"ground":{"x":-5340.65,"y":-172,"width":1024,"height":353}},"guntower":{"guntower":{"x":77.22,"y":122.59,"width":730,"height":289}},"machinegun":{"machinegun":{"x":44.85,"y":-5.72,"rotation":-180,"width":331,"height":57}},"machinegun-mount":{"machinegun-mount":{"x":47.42,"y":-1.54,"rotation":-90,"width":72,"height":96}},"rock":{"rock":{"x":25.24,"y":27.35,"width":580,"height":127}},"smoke-glow":{"smoke-glow":{"type":"mesh","uvs":[1,0.24906,1,0.51991,1,0.73165,0.70776,1,0.49012,1,0.24373,1,0,0.71158,0,0.50308,0,0.26235,0.28107,0,0.47435,0,0.73345,0,0.48858,0.51759],"triangles":[12,7,8,12,10,11,12,11,0,9,10,12,12,8,9,12,0,1,6,7,12,12,1,2,5,6,12,3,4,12,5,12,4,2,3,12],"vertices":[49.99,25.1,50,-1.98,50.01,-23.15,20.79,-50,-0.98,-50,-25.62,-50.01,-50,-21.17,-50,-0.32,-50.01,23.75,-21.9,50,-2.58,50,23.33,50.01,-1.14,-1.76],"hull":12,"edges":[2,24,24,14,20,24,24,8,2,0,20,22,0,22,18,20,14,16,18,16,12,14,8,10,12,10,6,8,2,4,6,4],"width":100,"height":100}},"smoke-puff1-bg":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg2":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg3":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg4":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg5":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg6":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg7":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg8":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg9":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg10":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg11":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg12":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg13":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg14":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg15":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg16":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg17":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg18":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg20":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg21":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg22":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg23":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg24":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg25":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg26":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-bg27":{"smoke-puff01-bg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123}},"smoke-puff1-fg":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg2":{"smoke-puff01-fg":{"x":-1.01,"y":-0.07,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.82,"y":-0.39,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.82,"y":-0.39,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.82,"y":-0.39,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg3":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.02,"y":-0.25,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1145,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.03,"y":-0.43,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg4":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.63,"y":-0.09,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.63,"y":-0.09,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.63,"y":-0.09,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg5":{"smoke-puff01-fg":{"x":-1.21,"y":-0.08,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.89,"y":-0.04,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.89,"y":-0.04,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.89,"y":-0.04,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg6":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.48,"y":-0.07,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.48,"y":-0.07,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.48,"y":-0.07,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg7":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-0.7,"y":-0.36,"scaleX":0.1216,"scaleY":0.1214,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-0.56,"y":-0.15,"scaleX":0.1224,"scaleY":0.1224,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-0.56,"y":-0.15,"scaleX":0.1224,"scaleY":0.1224,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg8":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-0.65,"y":0.01,"scaleX":0.1226,"scaleY":0.1226,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-0.75,"y":-0.15,"scaleX":0.1211,"scaleY":0.1211,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-0.75,"y":-0.15,"scaleX":0.1211,"scaleY":0.1211,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg9":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.99,"y":-0.09,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.99,"y":-0.09,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-0.95,"y":-0.48,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg10":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.27,"y":-0.37,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.27,"y":-0.37,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.27,"y":-0.37,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg11":{"smoke-puff04-fg":{"x":-1.27,"y":-0.37,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg12":{"smoke-puff04-fg":{"x":-1.27,"y":-0.37,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg13":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg14":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg15":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg16":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg17":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg18":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg20":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg21":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg22":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg23":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg24":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg25":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg26":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"smoke-puff1-fg27":{"smoke-puff01-fg":{"x":-0.5,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":175,"height":118},"smoke-puff02-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff03-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":184,"height":123},"smoke-puff04-fg":{"x":-1.38,"y":-0.12,"scaleX":0.1106,"scaleY":0.1106,"rotation":88.58,"width":155,"height":96}},"tank-body-shadow":{"tank-bottom-shadow":{"x":-11.44,"y":-42.89,"width":1291,"height":341}},"tank-glow":{"smoke-glow":{"type":"mesh","uvs":[1,1,0,1,1,0],"triangles":[1,2,0],"vertices":[469.64,-738.08,-1660.32,-738.08,469.64,1391.88],"hull":3,"edges":[0,2,0,4,2,4],"width":100,"height":100}},"tank-top":{"tank-top":{"x":6.8,"y":168.71,"width":1407,"height":222}},"tread":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread-inside1":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside2":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside3":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside4":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside5":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside6":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside7":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside8":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside9":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside10":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside11":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside12":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside13":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside14":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside15":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside16":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside17":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside18":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside19":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside20":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside21":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside22":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside23":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside24":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside25":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside26":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside27":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside28":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside29":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside30":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside31":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside32":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside33":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside34":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside35":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside36":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside37":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside38":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside39":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside40":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside41":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside42":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside43":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside44":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside45":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside46":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside47":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside48":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside49":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside50":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside51":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside52":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside53":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside54":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside55":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside56":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside57":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside58":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside59":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside60":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside61":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside62":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside63":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside64":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside65":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside66":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside67":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside68":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside69":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside70":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside71":{"tread-inside":{"x":60.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread-inside72":{"tread-inside":{"x":20.1,"y":12.56,"rotation":-180,"width":25,"height":28}},"tread2":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread3":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread4":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread5":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread6":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread7":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread8":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread9":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread10":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread11":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread12":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread13":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread14":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread15":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread16":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread17":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread18":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread19":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread20":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread21":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread22":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread23":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread24":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread25":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread26":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread27":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread28":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread29":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread30":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread31":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread32":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread33":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread34":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread35":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"tread36":{"tread":{"x":45.47,"y":-8.28,"rotation":-180,"width":96,"height":30}},"treads-path":{"treads-path":{"type":"path","closed":true,"lengths":[185.21,354.53,478.3,608.52,786,1058.49,1138.97,1223.96,1303.87,1388.23,1471.11,1551.64,1633.55,1713.27,1799.89,1882.28,2164.2,2326.85,2444.07,2584.91,2754.15,2940.62],"vertexCount":66,"vertices":[1,110,11.23,41.87,1,1,110,0.79,41.95,1,1,110,-34.72,42.24,1,1,56,-104.22,0.41,1,1,56,0.07,0.55,1,1,56,68.8,0.65,1,1,109,20.5,43.47,1,1,109,1.14,40.82,1,1,109,-27.38,36.85,1,1,93,147.07,105.01,1,1,93,96.21,96.63,1,1,93,43.87,87.72,1,1,93,16.18,103.35,1,1,93,-33.67,94.21,1,1,93,-99.36,81.25,1,1,93,-122.05,-1.7,1,1,93,-83.58,-47.93,1,1,93,-33.53,-109.37,1,1,97,-83.57,-66.1,1,1,97,-2.17,-67.9,1,2,97,56.68,-41.49,0.68,51,-24.31,-41.49,0.32,1,51,-26.59,16.7,1,1,51,-2.69,16.7,1,1,51,13.52,16.7,1,2,98,-52.42,-46.51,0.744,51,30.21,-46.52,0.256,1,98,-0.32,-68.92,1,2,98,52.09,-44.73,0.712,52,-28.91,-44.73,0.288,1,52,-22.81,16.24,1,1,52,-1.42,16.24,1,1,52,20.48,16.24,1,2,99,-47.21,-47.46,0.744,52,36.01,-47.46,0.256,1,99,-0.29,-69.66,1,2,99,45.24,-47.26,0.736,53,-37.49,-47.26,0.264,1,53,-23.76,15.28,1,1,53,-0.14,15.28,1,1,53,24.45,15.28,1,2,100,-47.37,-48.7,0.744,53,33.53,-48.7,0.256,1,100,-0.5,-70.4,1,2,100,49.09,-48.34,0.744,54,-33.58,-48.34,0.256,1,54,-20.89,15.84,1,1,54,-1.26,15.84,1,1,54,15.78,15.84,1,2,101,-52.5,-48.21,0.76,54,28.45,-48.22,0.24,1,101,-2.5,-68.92,1,2,101,55.72,-47.82,0.752,55,-28.88,-47.83,0.248,1,55,-21.64,16.7,1,1,55,-0.48,16.7,1,1,55,20.74,16.7,1,2,102,-53.65,-48.9,0.76,55,25.97,-48.9,0.24,1,102,2.28,-69.66,1,1,102,44.95,-69.74,1,1,94,76.03,-85.61,1,1,94,93.58,-42.24,1,1,94,118.67,19.75,1,1,94,78.59,76.62,1,1,94,37.27,95.07,1,1,94,31.45,97.67,1,1,94,-15.16,87.48,1,1,94,-79.8,92.52,1,1,94,-119.06,95.58,1,1,111,47.07,42.29,1,1,111,0.25,42.75,1,1,111,-29.64,43.29,1,1,57,-86.65,1.35,1,1,57,0.49,0.26,1,1,57,92.42,-0.9,1],"color":"ff8819ff"}},"wheel-big":{"wheel-big":{"width":191,"height":191}},"wheel-big-overlay1":{"wheel-big-overlay":{"width":186,"height":186}},"wheel-big-overlay2":{"wheel-big-overlay":{"width":186,"height":186}},"wheel-big2":{"wheel-big":{"width":191,"height":191}},"wheel-mid":{"wheel-mid":{"width":136,"height":136}},"wheel-mid-overlay":{"wheel-mid-overlay":{"width":136,"height":136}},"wheel-mid-overlay2":{"wheel-mid-overlay":{"width":136,"height":136}},"wheel-mid-overlay3":{"wheel-mid-overlay":{"width":136,"height":136}},"wheel-mid-overlay4":{"wheel-mid-overlay":{"width":136,"height":136}},"wheel-mid-overlay5":{"wheel-mid-overlay":{"width":136,"height":136}},"wheel-mid-overlay6":{"wheel-mid-overlay":{"width":136,"height":136}},"wheel-mid2":{"wheel-mid":{"width":136,"height":136}},"wheel-mid3":{"wheel-mid":{"width":136,"height":136}},"wheel-mid4":{"wheel-mid":{"width":136,"height":136}},"wheel-mid5":{"wheel-mid":{"width":136,"height":136}},"wheel-mid6":{"wheel-mid":{"width":136,"height":136}},"wheel-small":{"wheel-small":{"width":71,"height":71}},"wheel-small2":{"wheel-small":{"width":71,"height":71}},"wheel-small3":{"wheel-small":{"width":71,"height":71}}}}],"animations":{"drive":{"bones":{"tank-root":{"rotate":[{"time":2},{"time":2.0667,"value":1.99},{"time":2.5,"value":-15.63},{"time":2.6667,"value":-10.37,"curve":[2.718,-10.37,2.78,-8.34]},{"time":2.8333,"value":-6.13,"curve":[2.909,-2.8,2.974,0.8]},{"time":3,"value":1.84},{"time":3.0667,"value":5.32},{"time":3.1667,"value":10.99},{"time":3.2333,"value":9.73},{"time":3.4333,"value":-4.52,"curve":[3.474,-3.99,3.608,0.01]},{"time":3.6667,"value":0.01}],"translate":[{"curve":[1.019,0,1.608,-582.83,1.019,0,1.608,0]},{"time":2,"x":-1209.75},{"time":2.3333,"x":-1652.84,"y":26.05},{"time":2.5,"x":-1877.69,"y":71.5},{"time":2.6667,"x":-2053.37,"y":100.44},{"time":2.8333,"x":-2183.86,"y":97.42},{"time":3,"x":-2312.32,"y":74.12},{"time":3.0667,"x":-2340.68,"y":45.94},{"time":3.1333,"x":-2403.04,"y":17.04},{"time":3.1667,"x":-2439.84,"y":5.45},{"time":3.2333,"x":-2523.34,"y":-3.31},{"time":3.4333,"x":-2728.27,"y":-12.73},{"time":3.5,"x":-2795.65,"y":-6.14,"curve":[3.538,-2829.89,3.583,-2878.59,3.538,-4.93,3.583,-3.21]},{"time":3.6333,"x":-2938.53,"y":-1.09,"curve":[3.89,-3218.84,4.404,-3972.02,3.89,-0.79,4.404,0]},{"time":4.8333,"x":-3972.02},{"time":5,"x":-3991.31},{"time":5.3667,"x":-3973.94}]},"tread-collider1":{"translate":[{"time":2},{"time":2.0667,"y":9.99},{"time":2.1667,"y":37.69},{"time":2.3333,"y":53.45},{"time":2.5,"y":30.97},{"time":2.6667,"y":-2.89},{"time":2.8333,"y":-0.71},{"time":3.0667,"y":-13.64},{"time":3.1667,"y":59.3},{"time":3.2333,"y":48.2},{"time":3.4333,"y":-11.27},{"time":3.6333,"y":4.15}]},"tread-collider2":{"translate":[{"time":2},{"time":2.0667,"y":-2.83},{"time":2.1667,"y":-17.44},{"time":2.3333,"y":46.07},{"time":2.5,"y":19.45},{"time":2.6667,"y":13.46},{"time":2.8333,"y":-1.92,"curve":"stepped"},{"time":2.9667,"y":-1.92},{"time":3,"y":-13.17},{"time":3.0667,"y":-23.25},{"time":3.1667,"y":28.13},{"time":3.2333,"y":25.63},{"time":3.4333,"y":-1.52},{"time":3.6333,"y":1.15}]},"tread-collider3":{"translate":[{"time":2},{"time":2.0667,"y":-7.76},{"time":2.1667,"y":-16.61},{"time":2.5,"y":29.05},{"time":2.6667,"y":30.12},{"time":2.8333,"y":5.3},{"time":3,"y":-0.38},{"time":3.1667,"y":2.6},{"time":3.4333,"y":15.41},{"time":3.6333,"y":1.44}]},"tread-collider4":{"translate":[{"time":2},{"time":2.1667,"y":-6.72},{"time":2.3333,"y":-0.92},{"time":2.5,"y":18.37},{"time":2.6667,"y":38.77},{"time":2.8333,"y":30.6},{"time":3.1667,"y":12.61},{"time":3.2333,"y":-16},{"time":3.4333,"y":25.62},{"time":3.6333,"y":-0.68}]},"tread-collider5":{"translate":[{"time":2},{"time":2.1667,"y":3.35},{"time":2.3333,"y":22.17},{"time":2.6667,"y":13.35},{"time":2.8333,"y":39},{"time":3,"y":39.88},{"time":3.1667,"y":26.57},{"time":3.2333,"y":-10.15},{"time":3.4333,"y":35.98},{"time":3.6333,"y":-1.36}]},"wheel-mid-root6":{"translate":[{"time":2},{"time":2.1667,"y":5.61},{"time":2.3333,"y":27.21},{"time":2.5,"y":30.28},{"time":2.6667,"y":-2.81},{"time":2.8333,"y":19.59},{"time":3,"y":29.11},{"time":3.1667,"y":32.55},{"time":3.2333,"y":3.55},{"time":3.4333,"y":40.54},{"time":3.6333}]},"wheel-mid-root5":{"translate":[{"time":2},{"time":2.1667,"y":-7.46},{"time":2.3333,"y":9.53},{"time":2.6667,"y":36.78},{"time":2.8333,"y":46.11},{"time":3.1667,"y":7.55},{"time":3.2333,"y":-16.28},{"time":3.4333,"y":26.21},{"time":3.6333}]},"wheel-mid-root4":{"translate":[{"time":2},{"time":2.1667,"y":-13.98},{"time":2.3333,"y":-8.26},{"time":2.5,"y":24.27},{"time":2.6667,"y":34.42},{"time":2.8333,"y":8.88},{"time":3.1667,"y":10.32},{"time":3.2333,"y":-7.63},{"time":3.4333,"y":19.69},{"time":3.6333}]},"wheel-mid-root3":{"translate":[{"time":2},{"time":2.1667,"y":-21.14},{"time":2.3333,"y":22.83},{"time":2.5,"y":23.34},{"time":2.6667,"y":18.07},{"time":2.8333,"y":1.2},{"time":3.0667,"y":-13.36},{"time":3.1667,"y":15.48},{"time":3.2333,"y":13.34},{"time":3.4333,"y":6.4},{"time":3.6333}]},"wheel-mid-root2":{"translate":[{"time":2},{"time":2.0667,"y":-4.39},{"time":2.1667,"y":3.13},{"time":2.3333,"y":53.56},{"time":2.5,"y":16.65},{"time":2.6667,"y":8.39},{"time":3.0667,"y":-19.16},{"time":3.1667,"y":43.25},{"time":3.2333,"y":39.04},{"time":3.4333,"y":-8.61},{"time":3.6333}]},"wheel-mid-root1":{"translate":[{"time":2},{"time":2.0333,"y":22.64},{"time":2.0667,"y":53.65},{"time":2.1667,"y":71.18},{"time":2.5,"y":46.83},{"time":2.6667,"y":8.38},{"time":3.0667,"y":-10.03},{"time":3.1667,"y":72.71},{"time":3.2333,"y":64.74},{"time":3.4333,"y":-17.65},{"time":3.6333}]},"tank-body":{"rotate":[{"curve":[0.208,0,0.625,-4.39]},{"time":0.8333,"value":-4.39},{"time":2},{"time":2.1667,"value":-1.34},{"time":2.3333,"value":-6.23},{"time":2.5,"value":-5.45},{"time":2.9667,"value":-5.07},{"time":3.0667,"value":-2.39},{"time":3.1667,"value":-0.98},{"time":3.2333,"value":-1.1},{"time":3.4,"value":0.43,"curve":[3.433,0.43,3.483,-1.56]},{"time":3.5333,"value":-3.55,"curve":[3.675,-3.47,3.754,1.49]},{"time":3.8333,"value":1.93},{"time":4,"value":0.48},{"time":4.3333,"curve":[4.476,0.59,4.833,3.8]},{"time":5,"value":3.8,"curve":[5.286,3.8,5.35,-2.17]},{"time":5.4667,"value":-2.17},{"time":5.6,"value":-0.61}]},"wheel-big-root1":{"translate":[{"time":2},{"time":2.0667,"y":20.07},{"time":2.3333,"y":67.24},{"time":2.6667,"y":21.04},{"time":3,"y":10.28},{"time":3.1,"y":11.28},{"time":3.1667,"y":29.43},{"time":3.2333,"y":35.31},{"time":3.4333,"y":18.38},{"time":3.5}]},"tank-treads":{"rotate":[{},{"time":0.8333,"value":-2.4},{"time":2},{"time":2.0667,"value":1.72},{"time":2.4333,"value":-0.37},{"time":2.8},{"time":3,"value":-1.41},{"time":3.1667,"value":0.54},{"time":3.2667,"value":2.22,"curve":[3.348,2.22,3.392,-1.31]},{"time":3.4333,"value":-1.31},{"time":3.7333,"value":-1.14},{"time":4.3333,"curve":[4.476,0.35,4.833,2.24]},{"time":5,"value":2.24,"curve":[5.286,2.24,5.35,0]},{"time":5.4667}]},"cannon-target":{"translate":[{},{"time":0.8333,"y":121.95},{"time":2,"y":45.73}]},"wheel-big-root2":{"translate":[{"time":3.4333,"y":13.01}]},"wheel-big1":{"rotate":[{"curve":[0.51,0,0.804,57.81]},{"time":1,"value":120},{"time":1.2667,"value":240},{"time":1.5333,"value":360},{"time":1.7667,"value":480},{"time":2.0333,"value":600},{"time":2.2,"value":720},{"time":2.4,"value":840},{"time":2.5667,"value":960},{"time":2.7333,"value":1080},{"time":2.9333,"value":1200},{"time":3.1333,"value":1320},{"time":3.3333,"value":1440},{"time":3.5,"value":1560},{"time":3.6667,"value":1680},{"time":3.8667,"value":1800},{"time":4.0667,"value":1920},{"time":4.2667,"value":2040},{"time":4.5,"value":2160,"curve":[4.563,2194.34,4.695,2225.3]},{"time":4.8333,"value":2247.67}]},"wheel-mid1":{"rotate":[{"curve":[0.459,0,0.724,57.81]},{"time":0.9,"value":120},{"time":1.1667,"value":240},{"time":1.4333,"value":360},{"time":1.6333,"value":480},{"time":1.8333,"value":600},{"time":2,"value":720},{"time":2.1333,"value":840},{"time":2.2667,"value":960},{"time":2.4,"value":1080},{"time":2.5333,"value":1200},{"time":2.6667,"value":1320},{"time":2.8333,"value":1440},{"time":2.9667,"value":1560},{"time":3.1,"value":1680},{"time":3.2333,"value":1800},{"time":3.3667,"value":1920},{"time":3.5,"value":2040},{"time":3.6333,"value":2160},{"time":3.7667,"value":2280},{"time":3.9,"value":2400},{"time":4.0333,"value":2520},{"time":4.1667,"value":2640},{"time":4.3,"value":2760},{"time":4.4667,"value":2880,"curve":[4.538,2949.2,4.742,3000]},{"time":4.8333,"value":3000}]},"wheel-small1":{"rotate":[{"curve":[0.34,0,0.536,57.81]},{"time":0.6667,"value":120},{"time":0.8667,"value":240},{"time":1.0333,"value":360},{"time":1.1667,"value":480},{"time":1.3,"value":600},{"time":1.4333,"value":720},{"time":1.5333,"value":840},{"time":1.6333,"value":960},{"time":1.7333,"value":1080},{"time":1.8333,"value":1200},{"time":1.9333,"value":1320},{"time":2.0333,"value":1440},{"time":2.1333,"value":1560},{"time":2.2333,"value":1680},{"time":2.3333,"value":1800},{"time":2.4333,"value":1920},{"time":2.5333,"value":2040},{"time":2.6333,"value":2160},{"time":2.7333,"value":2280},{"time":2.8333,"value":2400},{"time":2.9333,"value":2520},{"time":3.0333,"value":2640},{"time":3.1333,"value":2760},{"time":3.2333,"value":2880},{"time":3.3333,"value":3000},{"time":3.4333,"value":3120},{"time":3.5333,"value":3240},{"time":3.6333,"value":3360},{"time":3.7333,"value":3480},{"time":3.8333,"value":3600},{"time":3.9333,"value":3720},{"time":4.0333,"value":3840},{"time":4.1333,"value":3960},{"time":4.2333,"value":4080},{"time":4.3333,"value":4200},{"time":4.4333,"value":4320},{"time":4.6667,"value":4440},{"time":4.9,"value":4490}]},"wheel-small-root1":{"translate":[{"time":2},{"time":2.1333,"y":12.37},{"time":2.4667,"y":32.37},{"time":2.7333,"y":-5.27},{"time":2.9667,"y":14.31},{"time":3.1667,"y":19.54},{"time":3.4667,"y":7.5},{"time":4.3667,"y":-2.4}]},"wheel-small-root2":{"translate":[{"time":2},{"time":2.9,"y":5.26},{"time":3.1667,"y":10.67},{"time":3.4667,"y":-4.71}]},"wheel-small-root3":{"translate":[{"time":2},{"time":2.4667,"y":-10.56},{"time":2.9,"y":-16.08},{"time":3.1667,"y":10.12},{"time":3.4667,"y":4.1},{"time":4.3667,"y":-0.03}]},"antenna1":{"rotate":[{},{"time":0.3333,"value":1.05,"curve":[0.731,1.05,1.008,-4.55]},{"time":1.2333,"value":-4.55,"curve":[1.35,-4.55,1.583,0.53]},{"time":1.7,"value":0.53},{"time":2,"value":-2.13},{"time":2.2,"value":4.71},{"time":2.3667,"value":-7.26},{"time":2.5667,"value":2.26},{"time":2.7333,"value":-3.39},{"time":3.0667,"value":-5.53},{"time":3.2333,"value":7.78},{"time":3.4667,"value":-5.99},{"time":3.7,"value":3.11},{"time":3.9,"value":-3.05},{"time":4.1,"value":0.31},{"time":4.3,"value":-3.06},{"time":4.5333,"value":0.36},{"time":4.8667,"value":4.94,"curve":[4.925,4.94,5.042,-2.38]},{"time":5.1,"value":-2.38},{"time":5.2667,"value":3.65},{"time":5.4,"value":-3.04},{"time":5.5,"value":1.49},{"time":5.6,"value":-1.86},{"time":5.7,"value":0.42}]},"antenna2":{"rotate":[{},{"time":0.3333,"value":1.05,"curve":[0.731,1.05,1.008,-4.55]},{"time":1.2333,"value":-4.55,"curve":[1.35,-4.55,1.583,0.53]},{"time":1.7,"value":0.53},{"time":2,"value":-2.13},{"time":2.2,"value":4.71},{"time":2.3667,"value":-7.26},{"time":2.5667,"value":2.26},{"time":2.7333,"value":-3.39},{"time":3.0667,"value":-5.53},{"time":3.2333,"value":7.78},{"time":3.4667,"value":-5.99},{"time":3.7,"value":3.11},{"time":3.9,"value":-3.05},{"time":4.1,"value":0.31},{"time":4.3,"value":-3.06},{"time":4.5333,"value":0.36},{"time":4.8667,"value":4.94,"curve":[4.925,4.94,5.042,-2.38]},{"time":5.1,"value":-2.38},{"time":5.2667,"value":3.65},{"time":5.4,"value":-3.04},{"time":5.5,"value":1.49},{"time":5.6,"value":-1.86},{"time":5.7,"value":0.42}]},"antenna3":{"rotate":[{},{"time":0.3333,"value":1.05,"curve":[0.731,1.05,1.008,-4.55]},{"time":1.2333,"value":-4.55,"curve":[1.35,-4.55,1.583,0.53]},{"time":1.7,"value":0.53},{"time":2,"value":-2.13},{"time":2.2,"value":4.71},{"time":2.3667,"value":-7.26},{"time":2.5667,"value":2.26},{"time":2.7333,"value":-3.39},{"time":3.0667,"value":-5.53},{"time":3.2333,"value":7.78},{"time":3.4667,"value":-5.99},{"time":3.7,"value":3.11},{"time":3.9,"value":-3.05},{"time":4.1,"value":0.31},{"time":4.3,"value":-3.06},{"time":4.5333,"value":0.36},{"time":4.8667,"value":4.94,"curve":[4.925,4.94,5.042,-2.38]},{"time":5.1,"value":-2.38},{"time":5.2667,"value":3.65},{"time":5.4,"value":-3.04},{"time":5.5,"value":1.49},{"time":5.6,"value":-1.86},{"time":5.7,"value":0.42}]},"antenna4":{"rotate":[{},{"time":0.3333,"value":1.05,"curve":[0.731,1.05,1.008,-4.55]},{"time":1.2333,"value":-4.55,"curve":[1.35,-4.55,1.583,0.53]},{"time":1.7,"value":0.53},{"time":2,"value":-2.13},{"time":2.2,"value":4.71},{"time":2.3667,"value":-7.26},{"time":2.5667,"value":2.26},{"time":2.7333,"value":-3.39},{"time":3.0667,"value":-5.53},{"time":3.2333,"value":7.78},{"time":3.4667,"value":-5.99},{"time":3.7,"value":3.11},{"time":3.9,"value":-3.05},{"time":4.1,"value":0.31},{"time":4.3,"value":-3.06},{"time":4.5333,"value":0.36},{"time":4.8667,"value":4.94,"curve":[4.925,4.94,5.042,-2.38]},{"time":5.1,"value":-2.38},{"time":5.2667,"value":3.65},{"time":5.4,"value":-3.04},{"time":5.5,"value":1.49},{"time":5.6,"value":-1.86},{"time":5.7,"value":0.42}]},"antenna5":{"rotate":[{},{"time":0.3333,"value":1.05,"curve":[0.731,1.05,1.008,-4.55]},{"time":1.2333,"value":-4.55,"curve":[1.35,-4.55,1.583,0.53]},{"time":1.7,"value":0.53},{"time":2,"value":-2.13},{"time":2.2,"value":4.71},{"time":2.3667,"value":-7.26},{"time":2.5667,"value":2.26},{"time":2.7333,"value":-3.39},{"time":3.0667,"value":-5.53},{"time":3.2333,"value":7.78},{"time":3.4667,"value":-5.99},{"time":3.7,"value":3.11},{"time":3.9,"value":-3.05},{"time":4.1,"value":0.31},{"time":4.3,"value":-3.06},{"time":4.5333,"value":0.36},{"time":4.8667,"value":4.94,"curve":[4.925,4.94,5.042,-2.38]},{"time":5.1,"value":-2.38},{"time":5.2667,"value":3.65},{"time":5.4,"value":-3.04},{"time":5.5,"value":1.49},{"time":5.6,"value":-1.86},{"time":5.7,"value":0.42}]},"antenna6":{"rotate":[{},{"time":0.3333,"value":1.05,"curve":[0.731,1.05,1.008,-4.55]},{"time":1.2333,"value":-4.55,"curve":[1.35,-4.55,1.583,0.53]},{"time":1.7,"value":0.53},{"time":2,"value":-2.13},{"time":2.2,"value":4.71},{"time":2.3667,"value":-7.26},{"time":2.5667,"value":2.26},{"time":2.7333,"value":-3.39},{"time":3.0667,"value":-5.53},{"time":3.2333,"value":7.78},{"time":3.4667,"value":-5.99},{"time":3.7,"value":3.11},{"time":3.9,"value":-3.05},{"time":4.1,"value":0.31},{"time":4.3,"value":-3.06},{"time":4.5333,"value":0.36},{"time":4.8667,"value":4.94,"curve":[4.925,4.94,5.042,-2.38]},{"time":5.1,"value":-2.38},{"time":5.2667,"value":3.65},{"time":5.4,"value":-3.04},{"time":5.5,"value":1.49},{"time":5.6,"value":-1.86},{"time":5.7,"value":0.42}]},"machinegun":{"rotate":[{"value":8.07,"curve":"stepped"},{"time":2.0667,"value":8.07},{"time":2.1667,"value":3.11},{"time":2.5667,"value":-10.99,"curve":"stepped"},{"time":3.1333,"value":-10.99},{"time":3.2667,"value":18.18},{"time":3.4333,"value":2.75,"curve":"stepped"},{"time":4.7,"value":2.75},{"time":4.9,"value":8.07}]}},"path":{"treads-path":{"position":[{"curve":[0.984,0,1.588,0.1788]},{"time":2,"value":0.385,"curve":[2.023,0.3916,2.045,0.3983]},{"time":2.0667,"value":0.405},{"time":2.3333,"value":0.555},{"time":2.5,"value":0.605},{"time":2.6667,"value":0.685},{"time":2.8333,"value":0.745},{"time":3,"value":0.785},{"time":3.0667,"value":0.8},{"time":3.1333,"value":0.825},{"time":3.1667,"value":0.835},{"time":3.2333,"value":0.87},{"time":3.5,"value":0.98,"curve":[3.726,1.0474,4.335,1.4]},{"time":4.8333,"value":1.4}]}}},"shoot":{"slots":{"rock":{"attachment":[{}]},"smoke-glow":{"rgba":[{"time":0.1333,"color":"ffffffff"},{"time":0.1667,"color":"ffbc8af4"},{"time":0.2,"color":"fc8e8e90"},{"time":0.2667,"color":"fa3e3e1e"}],"attachment":[{"time":0.0667,"name":"smoke-glow"},{"time":0.3}]},"smoke-puff1-bg":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.6,"light":"ffd50cff","dark":"604b3f"},{"time":1.0333,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.0667,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg2":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":0.8333,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg3":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":0.8333,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg4":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.6,"light":"ffd50cff","dark":"604b3f"},{"time":0.9,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg5":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.6,"light":"ffd50cff","dark":"604b3f"},{"time":0.9,"light":"ffd50c00","dark":"604a3f"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg6":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":0.7,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg7":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3333,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":0.8333,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg8":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4333,"light":"ffd50cff","dark":"604b3f"},{"time":0.9333,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg9":{"rgba2":[{"time":0.1333,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.5333,"light":"ffd50cff","dark":"604b3f"},{"time":0.8333,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg10":{"rgba2":[{"time":0.1333,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.5333,"light":"ffd50cff","dark":"604b3f"},{"time":0.8333,"light":"ffd50c00","dark":"604a3f"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg11":{"rgba2":[{"time":0.1333,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":0.7667,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.3333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg12":{"rgba2":[{"time":0.3333,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.6,"light":"ffd50cff","dark":"604b3f"},{"time":0.8667,"light":"ffd50c00","dark":"604a3f"}],"attachment":[{"time":0.3667,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg13":{"rgba2":[{"time":0.3667,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":1,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.3667,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg14":{"rgba2":[{"time":0.4333,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.6,"light":"ffd50cff","dark":"604b3f"},{"time":1.0667,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.4333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg15":{"rgba2":[{"time":0.4,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":0.8333,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.4,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg16":{"rgba2":[{"time":0.4,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":0.7,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.4,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg17":{"rgba2":[{"time":0.2333,"light":"ffd50cff","dark":"534035"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4,"light":"ffd50cff","dark":"604b3f"},{"time":0.6667,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.2333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg18":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.5,"light":"ffd50cff","dark":"604b3f"},{"time":0.8,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.2333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg20":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":0.8,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.3333,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg21":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":0.7,"light":"ffd50c00","dark":"604b3f"}]},"smoke-puff1-bg22":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":0.7,"light":"ffd50c00","dark":"604b3f"}]},"smoke-puff1-bg23":{"rgba2":[{"time":0.0667,"light":"ffd50cff","dark":"3b2c23"},{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.5,"light":"ffd50cff","dark":"604b3f"},{"time":0.7667,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.3,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg24":{"rgba2":[{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":0.7,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.3,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg25":{"rgba2":[{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":1,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.3667,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg26":{"rgba2":[{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.6,"light":"ffd50cff","dark":"604b3f"},{"time":0.9333,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.3667,"name":"smoke-puff01-bg"}]},"smoke-puff1-bg27":{"rgba2":[{"time":0.3,"light":"ffd50cff","dark":"604b3f","curve":"stepped"},{"time":0.4667,"light":"ffd50cff","dark":"604b3f"},{"time":0.7333,"light":"ffd50c00","dark":"604b3f"}],"attachment":[{"time":0.3667,"name":"smoke-puff01-bg"}]},"smoke-puff1-fg":{"rgba2":[{"time":0.0667,"light":"ffdf31ff","dark":"ff0000"},{"time":0.1333,"light":"fde252ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.6,"light":"ab764cff","dark":"ac8d75"},{"time":1.0333,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.0667,"name":"smoke-puff01-fg"},{"time":0.1667,"name":"smoke-puff02-fg"},{"time":0.2,"name":"smoke-puff03-fg"},{"time":0.2333,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg2":{"rgba2":[{"time":0.1333,"light":"ffdf31ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":0.8333,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-fg"},{"time":0.1667,"name":"smoke-puff02-fg"},{"time":0.2,"name":"smoke-puff03-fg"},{"time":0.2333,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg3":{"rgba2":[{"time":0.1333,"light":"ffe457ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":0.8333,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-fg"},{"time":0.1667,"name":"smoke-puff02-fg"},{"time":0.2,"name":"smoke-puff03-fg"},{"time":0.2667,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg4":{"rgba2":[{"time":0.1333,"light":"fae781ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.6,"light":"ab764cff","dark":"ac8d75"},{"time":0.9,"light":"ac8c7500","dark":"604a3f"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-fg"},{"time":0.1667,"name":"smoke-puff02-fg"},{"time":0.2,"name":"smoke-puff03-fg"},{"time":0.2667,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg5":{"rgba2":[{"time":0.1333,"light":"ffdf31ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.6,"light":"ab764cff","dark":"ac8d75"},{"time":0.9,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-fg"},{"time":0.1667,"name":"smoke-puff02-fg"},{"time":0.2,"name":"smoke-puff03-fg"},{"time":0.2667,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg6":{"rgba2":[{"time":0.1333,"light":"ffdf31ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":0.7,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-fg"},{"time":0.1667,"name":"smoke-puff02-fg"},{"time":0.2,"name":"smoke-puff03-fg"},{"time":0.2667,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg7":{"rgba2":[{"time":0.1333,"light":"ffdf31ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3333,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":0.8333,"light":"ac8c7500","dark":"604a3f"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-fg"},{"time":0.1667,"name":"smoke-puff02-fg"},{"time":0.2,"name":"smoke-puff03-fg"},{"time":0.2333,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg8":{"rgba2":[{"time":0.1333,"light":"ffdf31ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4333,"light":"ab764cff","dark":"ac8d75"},{"time":0.9333,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-fg"},{"time":0.1667,"name":"smoke-puff02-fg"},{"time":0.2,"name":"smoke-puff03-fg"},{"time":0.2333,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg9":{"rgba2":[{"time":0.1333,"light":"ffdf31ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.5333,"light":"ab764cff","dark":"ac8d75"},{"time":0.8333,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-fg"},{"time":0.1667,"name":"smoke-puff02-fg"},{"time":0.2,"name":"smoke-puff03-fg"},{"time":0.2333,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg10":{"rgba2":[{"time":0.1333,"light":"fce35dff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.5333,"light":"ab764cff","dark":"ac8d75"},{"time":0.8333,"light":"ac8c7500","dark":"604a3f"}],"attachment":[{"time":0.1333,"name":"smoke-puff01-fg"},{"time":0.1667,"name":"smoke-puff02-fg"},{"time":0.2,"name":"smoke-puff03-fg"},{"time":0.2667,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg11":{"rgba2":[{"time":0.3333,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":0.7667,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.3333,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg12":{"rgba2":[{"time":0.3667,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.6,"light":"ab764cff","dark":"ac8d75"},{"time":0.8667,"light":"ac8c7500","dark":"604a3f"}],"attachment":[{"time":0.3667,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg13":{"rgba2":[{"time":0.3667,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":1,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.3667,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg14":{"rgba2":[{"time":0.4333,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.6,"light":"ab764cff","dark":"ac8d75"},{"time":1.0667,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.4333,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg15":{"rgba2":[{"time":0.4,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":0.8333,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.4,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg16":{"rgba2":[{"time":0.4,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":0.7,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.4,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg17":{"rgba2":[{"time":0.2333,"light":"e3c05eff","dark":"ab7e59"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4,"light":"ab764cff","dark":"ac8d75"},{"time":0.6667,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.2333,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg18":{"rgba2":[{"time":0.1333,"light":"ffdf31ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.5,"light":"ab764cff","dark":"ac8d75"},{"time":0.7667,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.2333,"name":"smoke-puff03-fg"},{"time":0.2667,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg20":{"rgba2":[{"time":0.1333,"light":"ffdf31ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":0.8,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.3333,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg21":{"rgba2":[{"time":0.1333,"light":"ffdf31ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":0.7,"light":"ab764c00","dark":"ac8d75"}]},"smoke-puff1-fg22":{"rgba2":[{"time":0.1333,"light":"ffdf31ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":0.7,"light":"ab764c00","dark":"ac8d75"}]},"smoke-puff1-fg23":{"rgba2":[{"time":0.1333,"light":"ffdf31ff","dark":"ff0000"},{"time":0.1667,"light":"ffe568ff","dark":"e26425"},{"time":0.2,"light":"ffe568ff","dark":"ab774c"},{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.5,"light":"ab764cff","dark":"ac8d75"},{"time":0.7667,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.3,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg24":{"rgba2":[{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":0.7,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.3,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg25":{"rgba2":[{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":1,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.3667,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg26":{"rgba2":[{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.6,"light":"ab764cff","dark":"ac8d75"},{"time":0.9333,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.3667,"name":"smoke-puff04-fg"}]},"smoke-puff1-fg27":{"rgba2":[{"time":0.3,"light":"ab764cff","dark":"ac8d75","curve":"stepped"},{"time":0.4667,"light":"ab764cff","dark":"ac8d75"},{"time":0.7333,"light":"ab764c00","dark":"ac8d75"}],"attachment":[{"time":0.3667,"name":"smoke-puff04-fg"}]},"tank-glow":{"rgba":[{"time":0.0667,"color":"fc994d84"},{"time":0.1333,"color":"f5b16bc8","curve":[0.221,0.96,0.252,0.98,0.221,0.69,0.252,0.62,0.221,0.42,0.252,0.33,0.221,0.78,0.252,0.32]},{"time":0.2667,"color":"fc994c30"}],"attachment":[{"time":0.0667,"name":"smoke-glow"},{"time":0.2667}]}},"bones":{"cannon":{"translate":[{"time":0.0667},{"time":0.1667,"x":34.77,"y":0.9},{"time":0.2667,"x":1.3}]},"tank-body":{"rotate":[{"time":0.0667},{"time":0.1667,"value":-4.29,"curve":[0.2,-4.29,0.267,2.37]},{"time":0.3,"value":2.37,"curve":[0.333,2.37,0.4,0]},{"time":0.4333}],"translate":[{"time":0.0667},{"time":0.1667,"x":31.04,"y":1.67,"curve":[0.2,31.04,0.267,-12.05,0.2,1.67,0.267,-0.23]},{"time":0.3,"x":-12.05,"y":-0.23},{"time":0.3667}]},"tank-treads":{"rotate":[{"time":0.0667},{"time":0.1667,"value":-3.08},{"time":0.3,"value":-0.42}]},"smoke1":{"rotate":[{"time":0.0667},{"time":0.1333,"value":2.88},{"time":0.1667,"value":2.34},{"time":0.2,"value":124.36},{"time":0.2667,"value":142.26},{"time":0.3333,"value":86.78},{"time":0.4667,"value":128.79},{"time":0.6333,"value":146.22},{"time":1.0333,"value":210.7}],"translate":[{"time":0.0667,"x":-9.69,"y":1.05},{"time":0.1333,"x":7.53,"y":1.21},{"time":0.1667,"x":3.26,"y":4.07},{"time":0.2,"x":29.64,"y":-17.46},{"time":0.2667,"x":86.97,"y":17.83},{"time":0.3333,"x":193.74,"y":-38.98},{"time":0.4,"x":341.67,"y":-39.52},{"time":0.6333,"x":393.24,"y":-4.01},{"time":1.0333,"x":410.76,"y":6.35}],"scale":[{"time":0.0667},{"time":0.1333,"x":3.171,"y":0.756},{"time":0.1667,"x":3.488,"y":1.279},{"time":0.2,"x":5.151,"y":2.369},{"time":0.2667,"x":4.735,"y":3.622},{"time":0.3,"x":4.735,"y":4.019},{"time":0.3333,"x":4.613,"y":3.339},{"time":0.3667,"x":4.918,"y":3.561},{"time":0.4,"x":4.6,"y":4.263},{"time":0.6333,"x":4.449,"y":2.62},{"time":1.0333,"x":3.09,"y":1.447}]},"smoke2":{"rotate":[{"time":0.1667,"value":31.55},{"time":0.3,"value":-22.63},{"time":0.4667,"value":142.89},{"time":0.6,"value":253.78},{"time":0.8333,"value":299.28}],"translate":[{"time":0.1667,"x":17.26,"y":4.86},{"time":0.2333,"x":141.22,"y":27.27},{"time":0.3,"x":178.86,"y":56.63},{"time":0.3667,"x":200.46,"y":71.05},{"time":0.4333,"x":213.12,"y":78.39},{"time":0.6333,"x":221.44,"y":73.1},{"time":0.8333,"x":223.32,"y":73.74}],"scale":[{"time":0.1667,"x":1.34,"y":1.34},{"time":0.2333,"x":2.81,"y":1.317},{"time":0.3,"x":2.932,"y":1.374},{"time":0.4667,"x":1.247,"y":0.639},{"time":0.8333,"x":0.778,"y":0.515}]},"smoke3":{"rotate":[{"time":0.1667,"value":-5.54},{"time":0.2333,"value":0.2},{"time":0.3333,"value":20.27},{"time":0.4,"value":31.36},{"time":0.4667,"value":68.52},{"time":0.5333,"value":99.74},{"time":0.6333,"value":145.8},{"time":0.8333,"value":193.28}],"translate":[{"time":0.1333,"x":1.17,"y":8.53},{"time":0.1667,"x":37.53,"y":4.84},{"time":0.2,"x":67.99,"y":9.85},{"time":0.2333,"x":134.14,"y":-13.5},{"time":0.2667,"x":181.31,"y":-19.93},{"time":0.3,"x":238.28,"y":-8.82},{"time":0.3333,"x":268.51,"y":-25.75},{"time":0.3667,"x":359.06,"y":-28.49},{"time":0.4,"x":432.96,"y":-24.11},{"time":0.4667,"x":452.16,"y":-16.73},{"time":0.6333,"x":456.28,"y":-0.41},{"time":0.8333,"x":454.14,"y":16.41}],"scale":[{"time":0.1333,"x":2.258,"y":1.366},{"time":0.1667,"x":2.656,"y":1.47},{"time":0.2,"x":3.202,"y":1.772},{"time":0.2333,"x":3.202,"y":1.93},{"time":0.2667,"x":3.124,"y":1.896},{"time":0.3,"x":3.593,"y":1.896},{"time":0.3333,"x":2.363,"y":1.247},{"time":0.3667,"x":1.845,"y":0.973},{"time":0.4,"x":1.754,"y":0.926},{"time":0.4333,"x":1.448,"y":0.695},{"time":0.4667,"x":1.441,"y":0.688},{"time":0.5333,"x":0.865,"y":0.456},{"time":0.7,"x":0.86,"y":0.454},{"time":0.8333,"x":0.211,"y":0.111}]},"smoke4":{"rotate":[{"time":0.1667,"value":-20.35},{"time":0.2333,"value":18.5},{"time":0.3,"value":57.77},{"time":0.4,"value":105.85},{"time":0.6,"value":161.28},{"time":0.9,"value":208.43}],"translate":[{"time":0.1667,"x":35.95,"y":25.54},{"time":0.2333,"x":34.17,"y":1.87},{"time":0.3,"x":136.7,"y":21.5},{"time":0.4,"x":138.61,"y":34.8},{"time":0.6,"x":160.38,"y":37.13},{"time":0.9,"x":196.41,"y":30.36}],"scale":[{"time":0.1667,"x":2.751,"y":1.754},{"time":0.2333,"x":3.486,"y":2.224},{"time":0.2667,"x":3.486,"y":2.586},{"time":0.3,"x":3.847,"y":2.109},{"time":0.4,"x":1.96,"y":1.074},{"time":0.9,"x":0.825,"y":0.452}]},"smoke5":{"rotate":[{"time":0.2,"value":23.09},{"time":0.2667,"value":12.24},{"time":0.3333,"value":36.92},{"time":0.4333,"value":-37.33},{"time":0.5333,"value":-0.66},{"time":0.9,"value":64.02}],"translate":[{"time":0.1333},{"time":0.2333,"x":123.76,"y":19.44},{"time":0.3,"x":239.08,"y":-49.72},{"time":0.3667,"x":280.23,"y":-51.46},{"time":0.7,"x":340.62,"y":-20.09},{"time":0.9,"x":349.18,"y":-5.25}],"scale":[{"time":0.1333},{"time":0.1667,"x":1.718,"y":1.718},{"time":0.2,"x":2.109,"y":2.109},{"time":0.2333,"x":1.781,"y":2.183},{"time":0.2667,"x":2.148,"y":2.633},{"time":0.3333,"x":2.234,"y":2.738},{"time":0.3667,"x":1.366,"y":2.148},{"time":0.4,"x":0.97,"y":1.524},{"time":0.4333,"x":1.078,"y":1.157},{"time":0.4667,"x":1.126,"y":1.005},{"time":0.7,"x":1.241,"y":1.301},{"time":0.9,"x":0.709,"y":0.893}]},"smoke6":{"rotate":[{"time":0.1667,"value":-37.43},{"time":0.2333,"value":-18.36},{"time":0.3333,"value":28.58},{"time":0.4,"value":150.54},{"time":0.7,"value":301.59}],"translate":[{"time":0.1333},{"time":0.2,"x":68.04,"y":16.15},{"time":0.2667,"x":214.52,"y":13.25},{"time":0.3333,"x":285.4,"y":17.95},{"time":0.4,"x":202.91,"y":101.43},{"time":0.4667,"x":189.25,"y":116.39},{"time":0.7,"x":182.77,"y":137.4}],"scale":[{"time":0.1333},{"time":0.1667,"x":1.152,"y":1.288},{"time":0.2,"x":1.939,"y":2.168},{"time":0.2333,"x":2.278,"y":2.223},{"time":0.2667,"x":2.023,"y":1.974},{"time":0.3,"x":2.644,"y":1.974},{"time":0.4,"x":1.539,"y":1.425},{"time":0.4667,"x":1.14,"y":0.939},{"time":0.7,"x":0.215,"y":0.161}]},"smoke7":{"rotate":[{"time":0.1667,"value":-243.11},{"time":0.4,"value":-182.02},{"time":0.8333,"value":-83.02}],"translate":[{"time":0.1333,"x":3.19,"y":-6.53},{"time":0.1667,"x":44.54,"y":1.12},{"time":0.2,"x":65.84,"y":6.02},{"time":0.2333,"x":173.84,"y":97.51},{"time":0.4,"x":167.39,"y":74.58},{"time":0.8333,"x":227.77,"y":84.64}],"scale":[{"time":0.1333,"x":0.878,"y":0.878},{"time":0.1667,"x":1.235,"y":1.235},{"time":0.2,"x":1.461,"y":1.461},{"time":0.2333,"x":1.114,"y":1.114},{"time":0.3333,"x":1.067,"y":1.067},{"time":0.4667,"x":0.81,"y":0.753},{"time":0.8333,"x":0.52,"y":0.484}]},"smoke8":{"rotate":[{"time":0.1667,"value":-156.52},{"time":0.2667,"value":-154.05},{"time":0.3333,"value":-108.35},{"time":0.6,"value":-93.14},{"time":0.9333,"value":-70.89}],"translate":[{"time":0.1667,"x":20.72,"y":0.25},{"time":0.2333,"x":46.1,"y":-10.06},{"time":0.3,"x":149.77,"y":0.92},{"time":0.3667,"x":241.21,"y":49.01},{"time":0.5333,"x":276,"y":58.76},{"time":0.7,"x":292.02,"y":65.91},{"time":0.9333,"x":308.7,"y":69.51}],"scale":[{"time":0.1333,"y":1.174},{"time":0.1667,"x":1.813,"y":1.438},{"time":0.2,"x":1.813,"y":1.878},{"time":0.2333,"x":1.211,"y":1.878},{"time":0.2667,"x":1.584,"y":1.596},{"time":0.3,"x":1.958,"y":1.878},{"time":0.4667,"x":1.139,"y":0.958},{"time":0.9333,"x":0.839,"y":0.591}]},"smoke9":{"rotate":[{"time":0.1333,"value":-44.34},{"time":0.1667,"value":14.73},{"time":0.2333,"value":116.07},{"time":0.2667,"value":118.29},{"time":0.3333,"value":148.13},{"time":0.3667,"value":172.74},{"time":0.4,"value":235.69},{"time":0.4333,"value":283.36},{"time":0.7667,"value":358.76}],"translate":[{"time":0.1333,"x":-3.49,"y":0.04},{"time":0.2,"x":87.4,"y":-7.97},{"time":0.2667,"x":233.69,"y":-33.86},{"time":0.3333,"x":296.44,"y":-30.87},{"time":0.4,"x":390.8,"y":4},{"time":0.4667,"x":391.42,"y":13.17},{"time":0.6333,"x":413.3,"y":36.13},{"time":0.7667,"x":408.59,"y":40.75}],"scale":[{"time":0.1333,"x":1.289,"y":1.501},{"time":0.2,"x":1.751,"y":2.039},{"time":0.2667,"x":2.064,"y":2.347},{"time":0.3333,"x":1.822,"y":2.072},{"time":0.4,"x":1.296,"y":1.045},{"time":0.4667,"x":1.872,"y":1.526},{"time":0.6333,"x":1.181,"y":1.037},{"time":0.7667,"x":0.716,"y":0.615}]},"smoke10":{"rotate":[{"time":0.1333,"value":12.16},{"time":0.2,"value":49.19},{"time":0.2667,"value":33.17},{"time":0.3333,"value":42.23},{"time":0.4,"value":11.69},{"time":0.4667,"value":41.83},{"time":0.5333,"value":54.86},{"time":0.6333,"value":75.25},{"time":0.8333,"value":126.4}],"translate":[{"time":0.1333,"x":7.74,"y":10.25},{"time":0.2,"x":42.9,"y":72.89},{"time":0.2667,"x":221.58,"y":82.27},{"time":0.3333,"x":297.31,"y":85.39},{"time":0.4,"x":322.91,"y":81.04},{"time":0.4667,"x":346.62,"y":76.68},{"time":0.6667,"x":377.46,"y":81.85},{"time":0.8333,"x":402.18,"y":101.03}],"scale":[{"time":0.1333,"x":0.537,"y":1.062},{"time":0.1667,"x":1.042,"y":0.841},{"time":0.2,"x":1.937,"y":1.563},{"time":0.2333,"x":1.937,"y":2.176},{"time":0.2667,"x":2.254,"y":2.532},{"time":0.3,"x":2.24,"y":2.516},{"time":0.5333,"x":1.731,"y":1.882},{"time":0.8333,"x":0.855,"y":0.867}]},"smoke-glow":{"translate":[{"time":0.0667,"x":-57.08,"y":0.01},{"time":0.1,"x":-49.68,"y":-1.46},{"time":0.1333,"x":6.3,"y":-2.92},{"time":0.1667,"x":31.57,"y":0.44},{"time":0.2,"x":34.04,"y":0.27},{"time":0.2333,"x":109.29,"y":1.02},{"time":0.4,"x":119.89,"y":1.01},{"time":0.4333,"x":135.2,"y":1.03},{"time":0.4667,"x":152.86,"y":1.06},{"time":0.5333,"x":164.64,"y":1.07},{"time":0.6,"x":179.94,"y":1.09},{"time":0.6333,"x":190.54,"y":1.1}],"scale":[{"time":0.0667,"x":0.233,"y":0.233},{"time":0.1,"x":0.42,"y":0.288},{"time":0.1333,"x":1.669,"y":1.072},{"time":0.1667,"x":1.669,"y":1.785,"curve":"stepped"},{"time":0.2,"x":1.669,"y":1.785},{"time":0.2333,"x":2.544,"y":1.785},{"time":0.4333,"x":3.48,"y":2.22},{"time":0.4667,"x":4.337,"y":2.655}]},"smoke11":{"rotate":[{"time":0.4,"value":47.07},{"time":0.4333,"value":109.71},{"time":0.4667,"value":164.62},{"time":0.8333,"value":276.93}],"translate":[{"time":0.3333,"x":280.31,"y":126.85},{"time":0.4,"x":296.27,"y":125.62},{"time":0.4667,"x":312.45,"y":131.57},{"time":0.6667,"x":310.5,"y":149.67},{"time":0.8333,"x":307.08,"y":153.94}],"scale":[{"time":0.3333,"x":1.491,"y":1.491},{"time":0.4667,"x":1.144,"y":0.948},{"time":0.5667,"x":0.491,"y":0.491},{"time":0.8333,"x":0.985,"y":0.91}]},"smoke12":{"rotate":[{"time":0.3667,"value":-37.96},{"time":0.4333,"value":28.55},{"time":0.5333,"value":108.53},{"time":0.8667,"value":191.85}],"translate":[{"time":0.3667,"x":390.22,"y":-1.06},{"time":0.4333,"x":411.78,"y":26.39},{"time":0.5333,"x":428.12,"y":56.28},{"time":0.8667,"x":444.34,"y":68.06}],"scale":[{"time":0.3667,"x":2.006,"y":1.821},{"time":0.5333,"x":1.719,"y":1.293},{"time":0.7333,"x":1.562,"y":1.304},{"time":0.8667,"x":0.727,"y":0.637}]},"smoke13":{"rotate":[{"time":0.3667,"value":305.8},{"time":0.4,"value":478.49},{"time":0.4333,"value":537.45},{"time":0.4667,"value":573.84},{"time":0.5333,"value":596.4},{"time":0.7,"value":622.3},{"time":1,"value":657.95}],"translate":[{"time":0.3667,"x":331.84,"y":-25.82},{"time":0.4,"x":417.88,"y":-42.62},{"time":0.4667,"x":451.61,"y":-42.21},{"time":0.5333,"x":453.81,"y":-37.03},{"time":0.6,"x":451.86,"y":-31.89},{"time":0.7,"x":453.37,"y":-27.28},{"time":1,"x":454.04,"y":-17.89}],"scale":[{"time":0.3667,"x":4.509,"y":3.114},{"time":0.4,"x":3.673,"y":2.537},{"time":0.4333,"x":4.201,"y":2.638},{"time":0.4667,"x":4.27,"y":2.399},{"time":0.6,"x":2.798,"y":1.932},{"time":0.8333,"x":2.316,"y":1.599},{"time":1,"x":1.081,"y":0.746}]},"smoke14":{"rotate":[{"time":0.4333,"value":271.03},{"time":0.7,"value":299.97},{"time":1.0667,"value":331.16}],"translate":[{"time":0.4333,"x":371.68,"y":-29.8},{"time":0.7667,"x":400.59,"y":-44.36},{"time":1.0667,"x":432.26,"y":-44.79}],"scale":[{"time":0.4333,"x":4.011,"y":3.366},{"time":0.7667,"x":2.071,"y":1.624},{"time":1.0667,"x":1.798,"y":1.111}]},"smoke15":{"rotate":[{"time":0.4,"value":111.75},{"time":0.4667,"value":171.93},{"time":0.6,"value":256.95},{"time":0.8333,"value":299.15}],"translate":[{"time":0.4,"x":266.71,"y":-53.04},{"time":0.4333,"x":290.84,"y":-51.43},{"time":0.5333,"x":305.65,"y":-44.32},{"time":0.6667,"x":318.96,"y":-38.95},{"time":0.8333,"x":342.65,"y":-27.33}],"scale":[{"time":0.4,"x":2.749,"y":2.095},{"time":0.4333,"x":3.302,"y":2.289},{"time":0.4667,"x":2.591,"y":1.895},{"time":0.5333,"x":1.777,"y":1.354},{"time":0.7,"x":1.932,"y":1.267},{"time":0.8333,"x":1.002,"y":1.546}]},"smoke16":{"rotate":[{"time":0.4,"value":89.78},{"time":0.4667,"value":137.83},{"time":0.5333,"value":193.49},{"time":0.6,"value":235.26},{"time":0.6333,"value":286.8}],"translate":[{"time":0.4,"x":217.23,"y":-21.45},{"time":0.4667,"x":249.95,"y":-13.73},{"time":0.5333,"x":264.96,"y":-9.87},{"time":0.6,"x":278.95,"y":6.37},{"time":0.6333,"x":245.65,"y":11.77}],"scale":[{"time":0.4,"x":2.265,"y":1.859},{"time":0.4333,"x":2.621,"y":1.955},{"time":0.4667,"x":1.953,"y":1.538},{"time":0.6,"x":1.005,"y":0.825},{"time":0.6333,"x":0.387,"y":0.318}]},"smoke17":{"rotate":[{"time":0.2333,"value":99.02},{"time":0.3,"value":58.06},{"time":0.3333,"value":34.05},{"time":0.3667,"value":-17.34},{"time":0.6667,"value":-62.36}],"translate":[{"time":0.2333,"x":18.91,"y":-62.91},{"time":0.3,"x":2.43,"y":-61.54},{"time":0.3333,"x":1.89,"y":-36.55},{"time":0.3667,"x":6.97,"y":-29.52},{"time":0.4333,"x":10.78,"y":-20.55},{"time":0.6667,"x":18.65,"y":-13.19}],"scale":[{"time":0.2333,"x":1.915,"y":1.915},{"time":0.3,"x":1.509,"y":1.509},{"time":0.3333,"x":1.01,"y":1.01},{"time":0.3667,"x":0.715,"y":0.715},{"time":0.4333,"x":0.949,"y":0.721},{"time":0.5667,"x":0.785,"y":0.74}]},"smoke18":{"rotate":[{"time":0.2333,"value":141.75},{"time":0.2667,"value":134.51},{"time":0.3333,"value":249.12},{"time":0.5,"value":363.82},{"time":0.7333,"value":450.54}],"translate":[{"time":0.2333,"x":60.81,"y":56.17},{"time":0.2667,"x":68.74,"y":69.4},{"time":0.3333,"x":76.85,"y":69.07},{"time":0.5,"x":101.49,"y":89.87},{"time":0.7333,"x":118.58,"y":101.16}],"scale":[{"time":0.2333,"x":2.288,"y":2.288},{"time":0.2667,"x":2.288,"y":1.628},{"time":0.3,"x":1.524,"y":1.308},{"time":0.5,"x":1.757,"y":1.385},{"time":0.5333,"x":2.08,"y":1.51},{"time":0.7333,"x":1.405,"y":0.896}]},"smoke20":{"rotate":[{"time":0.3333,"value":95.16},{"time":0.3667,"value":130.42},{"time":0.4,"value":170.7},{"time":0.4333,"value":266.75},{"time":0.4667,"value":299.82},{"time":0.5333,"value":326.88},{"time":0.6,"value":350.8},{"time":0.9,"value":403.14}],"translate":[{"time":0.3333,"x":124.61,"y":-46.55},{"time":0.5333,"x":173.8,"y":-36.62},{"time":0.7,"x":186.5,"y":-35.41},{"time":0.9,"x":188.56,"y":-37.75}],"scale":[{"time":0.3333,"x":3.346,"y":2.654},{"time":0.3667,"x":2.661,"y":2.111},{"time":0.4333,"x":2.751,"y":1.984},{"time":0.4667,"x":3.059,"y":2.21},{"time":0.5333,"x":2.159,"y":1.712},{"time":0.7,"x":1.601,"y":1.27},{"time":0.9,"x":1.679,"y":0.856}]},"smoke23":{"rotate":[{"time":0.3,"value":115.12},{"time":0.3667,"value":79.01},{"time":0.7667,"value":6.96}],"translate":[{"time":0.3,"x":75.15,"y":-50.92},{"time":0.3667,"x":59.33,"y":-53.52},{"time":0.7667,"x":39.68,"y":-48.64}],"scale":[{"time":0.3,"x":3.331,"y":2.096},{"time":0.4333,"x":2.4,"y":2.006},{"time":0.5,"x":2.555,"y":2.094},{"time":0.7667,"x":1.35,"y":1.241}]},"antenna1":{"rotate":[{"time":0.0667},{"time":0.2,"value":11.78},{"time":0.3,"value":-9.52},{"time":0.4,"value":8.07},{"time":0.5,"value":-4.45},{"time":0.6,"value":1.54},{"time":0.7,"value":-0.34}]},"antenna2":{"rotate":[{"time":0.0667},{"time":0.2,"value":11.78},{"time":0.3,"value":-9.52},{"time":0.4,"value":8.07},{"time":0.5,"value":-4.45},{"time":0.6,"value":1.54},{"time":0.7,"value":-0.34}]},"antenna3":{"rotate":[{"time":0.0667},{"time":0.2,"value":11.78},{"time":0.3,"value":-9.52},{"time":0.4,"value":8.07},{"time":0.5,"value":-4.45},{"time":0.6,"value":1.54},{"time":0.7,"value":-0.34}]},"antenna4":{"rotate":[{"time":0.0667},{"time":0.2,"value":11.78},{"time":0.3,"value":-9.52},{"time":0.4,"value":8.07},{"time":0.5,"value":-4.45},{"time":0.6,"value":1.54},{"time":0.7,"value":-0.34}]},"antenna5":{"rotate":[{"time":0.0667},{"time":0.2,"value":11.78},{"time":0.3,"value":-9.52},{"time":0.4,"value":8.07},{"time":0.5,"value":-4.45},{"time":0.6,"value":1.54},{"time":0.7,"value":-0.34}]},"antenna6":{"rotate":[{"time":0.0667},{"time":0.2,"value":11.78},{"time":0.3,"value":-9.52},{"time":0.4,"value":8.07},{"time":0.5,"value":-4.45},{"time":0.6,"value":1.54},{"time":0.7,"value":-0.34}]},"smoke24":{"rotate":[{"time":0.3,"value":71.32},{"time":0.3667,"value":112.39},{"time":0.4667,"value":159.56},{"time":0.7,"value":224.21}],"translate":[{"time":0.3,"x":90.72,"y":-18.79},{"time":0.3667,"x":149.69,"y":-7.78},{"time":0.4667,"x":176.26,"y":12.31},{"time":0.7,"x":184.07,"y":31.75}],"scale":[{"time":0.3,"x":2.906,"y":2.311},{"time":0.4333,"x":3.567,"y":2.58},{"time":0.4667,"x":3.157,"y":2.41},{"time":0.7,"x":1.705,"y":1.356}]},"smoke25":{"rotate":[{"time":0.3667,"value":91.25},{"time":0.4333,"value":117.56},{"time":0.6333,"value":150.9},{"time":1,"value":189.47}],"translate":[{"time":0.3667,"x":187.21,"y":-51.18},{"time":0.5333,"x":245.48,"y":-46.28},{"time":0.6667,"x":277.36,"y":-43.12},{"time":1,"x":313.27,"y":-38.14}],"scale":[{"time":0.3667,"x":3.606,"y":2.657},{"time":0.4333,"x":4.166,"y":2.792},{"time":0.5333,"x":3.09,"y":2.091},{"time":1,"x":3.062,"y":1.801}]},"smoke26":{"rotate":[{"time":0.3667,"value":10.64},{"time":0.4,"value":60.85},{"time":0.4667,"value":89.45},{"time":0.7,"value":125.01},{"time":0.9333,"value":155.24}],"translate":[{"time":0.3667,"x":442.07,"y":-13.19},{"time":0.4,"x":453.7,"y":0.81},{"time":0.4667,"x":443.57,"y":-6.95},{"time":0.7,"x":460.97,"y":15.79},{"time":0.9333,"x":465.22,"y":20.92}],"scale":[{"time":0.3667,"x":2.726,"y":2.726},{"time":0.4333,"x":3.729,"y":2.822},{"time":0.4667,"x":3.398,"y":2.441},{"time":0.7,"x":4.324,"y":3.159},{"time":0.9,"x":1.977,"y":1.48}]},"smoke27":{"rotate":[{"time":0.3667,"value":24.75},{"time":0.4333,"value":-5.43},{"time":0.5333,"value":-39.76},{"time":0.8333,"value":-56.25}],"translate":[{"time":0.3667,"x":92.98,"y":-49.06},{"time":0.5333,"x":129.81,"y":-33.09},{"time":0.8333,"x":143.68,"y":-25.27}],"scale":[{"time":0.3667,"x":3.633,"y":2.223},{"time":0.4333,"x":2.745,"y":2.283},{"time":0.4667,"x":2.962,"y":2.122},{"time":0.5333,"x":2.007,"y":1.266}]},"cannon-target":{"translate":[{"time":0.1333},{"time":0.2,"y":128.38,"curve":[0.4,0,0.8,0,0.4,128.38,0.8,0]},{"time":1}],"scale":[{"time":0.4333,"x":0.632,"y":1.244},{"time":0.4667,"x":0.477,"y":1.487}]},"machinegun-target":{"scale":[{"time":0.4333,"x":0.632,"y":1.244},{"time":0.4667,"x":0.477,"y":1.487}]},"machinegun":{"rotate":[{"value":8.07,"curve":"stepped"},{"time":0.0667,"value":8.07},{"time":0.2333,"value":-18.67,"curve":[0.894,-18.44,0.832,7.5]},{"time":0.9,"value":8.07}]},"tank-root":{"translate":[{"time":0.0667},{"time":0.1667,"x":46.59,"curve":[0.192,46.59,0.242,0,0.192,0,0.242,0]},{"time":0.2667}]},"tank-glow":{"translate":[{"time":0.1333,"x":198.14,"curve":[0.199,190.76,0.222,-255.89,0.199,0,0.222,0]},{"time":0.2333,"x":-390}],"scale":[{"time":0.0667},{"time":0.1333,"x":1.185,"y":0.945,"curve":[0.199,1.182,0.222,1.048,0.199,0.939,0.222,0.579]},{"time":0.2333,"x":1.008,"y":0.471}]}},"attachments":{"default":{"clipping":{"clipping":{"deform":[{"time":0.0667,"offset":54,"vertices":[4.59198,-4.59192]},{"time":0.1333,"offset":8,"vertices":[-8.97369,-1.88211,9.11177,1.02258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-14.73321,-45.16878,-30.31448,-84.4631,-32.24969,-108.78421,70.26825,-36.90201]},{"time":0.1667,"offset":8,"vertices":[-11.32373,-1.65065,11.42179,0.53259,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-15.36503,-69.18713,-4.45626,-121.90839,5.46554,-115.23274,71.78526,-33.85687]},{"time":0.2,"offset":8,"vertices":[-8.70522,1.02196,8.65102,-1.4101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4.59198,-4.59192]},{"time":0.2333,"offset":8,"vertices":[-5.23146,0.85796,5.23882,-0.81519]},{"time":0.2667,"offset":54,"vertices":[4.59198,-4.59192]}]}},"smoke-glow":{"smoke-glow":{"deform":[{"time":0.1333,"vertices":[-14.17073,19.14352,0,0,-10.97961,-15.09065,-5.79558,-24.82121,0.68117,-17.78759,-1.1179,-5.4463,0,0,0,0,17.52957,6.89397,-0.33841,-2.21582,5.51004,18.88118,-6.80153,20.91101]},{"time":0.1667,"vertices":[-4.34264,39.78125,5.6649,-2.42686,-8.39346,-22.52338,-2.66431,5.08595,-19.28093,3.98568,-11.21397,10.2879,4.56749,4.1329,-19.50706,-2.28786,11.35747,4.55941,9.04341,-11.72194,2.15381,5.14344,-12.82158,16.08209,-23.19814,1.81836]},{"time":0.2,"vertices":[-3.95581,36.12203,37.20779,-0.87419,21.29579,-15.76854,-2.02438,6.16526,-5.92201,4.19709,-1.39027,9.92793,7.70584,-0.7169,-6.69733,-2.62048,17.91826,7.77333,-12.2858,3.25454,-12.75876,3.71516,9.67891,15.48546]},{"time":0.2333,"vertices":[-11.9371,26.01078,2.91821,-0.27533,7.69899,-17.45375,-2.02438,6.16526,-5.92201,4.19709,-1.39027,9.92793,7.70584,-0.7169,-6.69733,-2.62048,17.91826,7.77333,-4.30551,-6.01406,-12.75876,3.71516,-5.10017,17.59191]},{"time":0.2667,"vertices":[0.5959,23.58176,20.74303,0.93943,7.69899,-17.45375,-2.02438,6.16526,-5.92201,4.19709,-1.39027,9.92793,20.51733,2.52203,13.35544,2.64274,24.32408,-1.94308,8.50604,-20.99353,13.14276,5.73959,6.31876,19.2114,16.98909,0.80981]}]}}}},"drawOrder":[{"time":0.3,"offsets":[{"slot":"smoke-puff1-bg2","offset":24},{"slot":"smoke-puff1-bg8","offset":19},{"slot":"smoke-puff1-bg9","offset":22},{"slot":"smoke-puff1-bg3","offset":17},{"slot":"smoke-puff1-fg17","offset":13},{"slot":"smoke-puff1-fg2","offset":2},{"slot":"smoke-puff1-fg5","offset":8},{"slot":"smoke-puff1-fg6","offset":4},{"slot":"smoke-puff1-fg7","offset":-4},{"slot":"smoke-puff1-fg4","offset":-4}]},{"time":0.3333,"offsets":[{"slot":"smoke-puff1-bg2","offset":8},{"slot":"smoke-puff1-bg8","offset":5},{"slot":"smoke-puff1-bg9","offset":3},{"slot":"smoke-puff1-fg","offset":24},{"slot":"smoke-puff1-fg5","offset":-14},{"slot":"smoke-puff1-fg6","offset":-21},{"slot":"smoke-puff1-fg7","offset":-19},{"slot":"smoke-puff1-fg10","offset":-21}]},{"time":0.3667,"offsets":[{"slot":"smoke-puff1-bg2","offset":7},{"slot":"smoke-puff1-bg9","offset":4},{"slot":"smoke-puff1-fg","offset":24},{"slot":"smoke-puff1-fg2","offset":5},{"slot":"smoke-puff1-fg6","offset":-22},{"slot":"smoke-puff1-fg7","offset":-18},{"slot":"smoke-puff1-fg10","offset":-20}]},{"time":0.4,"offsets":[{"slot":"smoke-puff1-bg2","offset":5},{"slot":"smoke-puff1-bg4","offset":0},{"slot":"smoke-puff1-fg","offset":24},{"slot":"smoke-puff1-fg2","offset":5},{"slot":"smoke-puff1-fg6","offset":-21},{"slot":"smoke-puff1-fg7","offset":-18},{"slot":"smoke-puff1-fg10","offset":-22}]},{"time":0.4333,"offsets":[{"slot":"smoke-puff1-bg2","offset":4},{"slot":"smoke-puff1-bg9","offset":4},{"slot":"smoke-puff1-fg","offset":24},{"slot":"smoke-puff1-fg2","offset":5},{"slot":"smoke-puff1-fg6","offset":-17},{"slot":"smoke-puff1-fg7","offset":-19},{"slot":"smoke-puff1-fg10","offset":-23}]},{"time":0.5333,"offsets":[{"slot":"smoke-puff1-bg2","offset":9},{"slot":"smoke-puff1-bg12","offset":0},{"slot":"smoke-puff1-fg","offset":24},{"slot":"smoke-puff1-fg2","offset":6},{"slot":"smoke-puff1-fg6","offset":-20},{"slot":"smoke-puff1-fg7","offset":-19},{"slot":"smoke-puff1-fg10","offset":-23},{"slot":"smoke-puff1-fg4","offset":-5}]}]}}} \ No newline at end of file diff --git a/e2e/.dev/public/tank-pro.png b/e2e/.dev/public/tank-pro.png new file mode 100644 index 0000000000000000000000000000000000000000..badc613ca27b7fdd5739135163fdcd3c638b358c GIT binary patch literal 533353 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

    duM z0n{p07_iJXLlywEQ0{nDwOF;Br_>K(&zv#yH2hbxGlswn2TBOcnCEr?v+WqOZAqhp zv8f%^fmx^1{)cO8D_@r^_M5qoqW{P=<^VAjjTk#FcGs%*mPpJmSpQ%M4&fJR6J| zpp5;~u8DN{X6Phc78j2pOLEx-sgiBarkF1PwqY;}F3g!Ox_Izd3|OcPfT;m6VQJWr zIi-vd0l6)ee-79pc5ttFCc>)gIz%# zdrcr?u}zC}0JmU4$Acdly2iL+xH)5GY1Q){BemFp?J2q!+JLC<-{u?LA`6>#w$BVm z599_U*mX_pmc7V+VB+*Mz!0%J^aX}bgZK;>nF@wX;CT($NDM(Ussa{(Kza;O+xXY+ zg-iJ?+hET7#bW9b9<)u{%*D|fF4FHK*M63^!vo|RYloNW_SM zNl?S8oSu?K9w6rij2j-Xt5afo<0aU4$1@@!BQOuVzAl%YLGZ*cc%U7qOzytU7*RG+ z<#-k=sP`T=hBy-b!sZ}-Le2!JHD<$%BL}7CA%n5V!vcCN4F)P2bQ}v9dY1h#TJ~xRQ&o2@!9mj0g%d-kA3qWSWa$=v_#3t!+ z=E**2d)vV{dZHG~PFSRGeFJb~HngoFYrH;-bhc#xpL4=K@SNcQ!7~O+MtoweN;XMY zO=o^R0Y^brdU-jv*_U&N26A%$z@(+2l#r}>Jo^Ab`Wge34bkmc%CcWwkZ45jN4-dr z7ZS$AjD@p|fXM8!9t#vo;Q$%PVA&6gz1@)X@_KQoplsBVB?UM=H6)NCk}+|no{?7G z84*}SlF3+XQJT3o8!$-<0%5|^$R%C&=z!9JVz`xwZC*`#&8YzU>T@-*Nw9Im##QnP zQWcd`5C65z3j71BkOYR+|s#Z*2!g5%Y_Uv6yFSG}VAV&pgYP)e{!fK&m^k*!z(- z8n}r$LxrT*wqCGt#65MTN4WX7Ih;zw%96bi1%i%494ZR(M!mAk_1G}mK_5w6$V|ID zSC@kuJ-KoJtbV6vg`Q!SBUtW%ZwZqb}CBs__k3IyQFvxwsql>jU#%pL5S&T$IH7v?ImKBwbO zLG++bY+c#rX~FuDA%+_9c<&`J3 zl6^5u^znI^vTqhp7FMK`zxu1c3OM@-aakkTB7aakl|K8Nu?h9pY*_1W&uabk@%OLg z^1!3#Z@uNh!I$p7?}S`Z0QUIP7v$M@+$T>yb3q<@^qc{+2MwT|GGMl_*tr7CEVj&; zFP$3|c-7R_XppHo0Ap4SsOkSzz*aVRKVZtJuxZAu<(ze+2C-@(Yj)NCW+5=c8M!0Q z%z>Fp8bhY+KTiHJ&XjF~GyUw9R;&55D=SN}@jkj%ir$wO0b{Ub05f8~*!;QMZCU0- zQriR0kRW;jkG>`JICWyfQsZB`=SI1pzau?9Kn)2app1kcuw`Uq++3eC)@@Y*-qPZX zZD2j4pzqM3MFV62vm0($W{kZ4*~y8$d(3OgB7#R8uz_AZF&U(zPQj7|h7ub-$4D5F zGxh-B0;|SG(Rko&kCiOP2&8`r_l{3Zu`%%;H3afznDH6E-F^La!lu?pD*#g-98OY- zX<#{P}mDyfJmXO(~VO%Y{^r?^x zSh|eMPi&KAj1RoL{EwtASm*!*7U+qAWCpG;zyPN(E)4(&LuiRLraVg;6;GyT9fa+2 z`5=Zd?;&$^(BpAG#xh{Mz{+9hTn11%G_g$ zkrbdo7B>B`zY>U7fWxsRF;E5gVIF!mnscJMK{D-tUFI3*&E_L!0xblC0CnW0m;^*1 zn1G0u1MDmAhmyF$wCKD$Fjs#f7^+5h@B`4nq~fl~c_bKBpEH1-GXB)JxqOl!n~Xd2 zjB8-DuRVJiOIRBhGY9<9#0j2`+30<4qKw@?q5X@_m2R3_)1~s_GbrVC6G%X^gyPZF> zWBVVs_CL&7y$JB*yvXr#W}5x3mX^QNkqzDDY^Y$Mk#XP-=#3kbvu4PaA1!~v6BiwA zEn(qkXP7%&(lJw0>Xcii%Xtxr?=0_I$6R3B**6S_x(%3(Rx%nJmJNktmkosZHs1hB z01h>EZ*U12K{Dr;2ET({nXuRb@HQL^8(qFh?IlSEWWH@pI2U@ecmOtu3wri-;{4a- zMlx93Q+`@8=|hU z%}sfHupTgv0X*J9LhA$oJ&E0ay&4*4?Hm+yNgaEc@7Q}W&0vjWt^#nWIRl``yr$mk zW7T9FpaJUz5YXD^ttj|Twlm-C^>H*T#@t7345Fk_Y+!@RpemBdanIoT(Xl5HRO9Uv zm^(6&Va_DDPq-XXY^uE;J9MgtCM=VHj3=lV7>uabUD}Oo#LRPS06rvENLD4BWfY*a zHOqTLx@#noQ3jwMSHvz%Yr0zPn$3sVm8`7n%f-epXVij;ikq<@Unlv8ascTu`|D1_ zByJ)lg_2U3j2y1|GWM5yBXzejO5#1$?#@_m`%c5$CK8JY`oDQQ2iEg;1Btx`uqwHX zA197cHP->_s=GdN%t;IYz^H31ccep~{SS$>oUO|_`eIt}i41wpa8Y%PZ8mokbuQ6o z`?>E=jRy=o4BGgyjcHT=_H-V%-FBNKjE_G0XzB6CAKz%8>l#m0AS{Hu+({{NOQ z{;5&!EM4)lpZyF8<41x^8b24j&lydqOX0sEY2|dvhhx_-3>(%i5FlH&WJFAvyD|3a{eU_ojM(G% z_0q?zB(Qhnk;4LljFFMf$82tlPu;%f+yj>=Y1nO%roea>Ma8ugCA;M}7Q5U~k`aoHtXSrL3pGgxeyM z0VW`xcGzF*rbz!G6;judb*|R`0VDub1OW6$g8|9e1&E_y&H|*~LbGaU`e4U0UL)W@ z#sQkMWuAFY)#5Pc1=tkftH~~zlt!-sBLIkRk`z$WNEp^dfqoRo!0zE9$3WKO@)i$5 z6lfAWk@Y3;6=Q|dmOGnqd}81h*B^Mz9E=)rC*O7%eQ<1HLiFs8T^tWFW-KHGLv}3V zV4m;rz$NKHJPS~QeSvM7DVH}%_y95~XAmVLgB7W!u`@;I)FsdxYKE8|q(NhStun;u zqa=*)_b-ZG!om;uP3{oscBUa$NmeBc9UzI2z0lJt~uXvogUk)T1h*1 zE!1fykucWTvBMdTW8sZ`#gsJDJE}GUf6kOD0I!1q@a#Q|0PGG70yQ{G$ID@?w=Hy9 zkdLW@yB2yamJDm`_!aVQ2~RB==QloarU6uN;v|^NaT9KGDV$r-J7|{K2r}hb4_L9b2aqfp?+o(>+rzT?-7*y7 zd;%;;D8L|ecjCs_F6k-Fgh?cY3oy?7)8lYZBXO6Vs2Mhy|6Q^ zTv!RSdotQKF{<`C+TVzQ;j5Q8wksttoQWwX( zV+~Fwu&#HOiq95!N&)Jq6Rz^iF>{Capb|(>GwBQ;wTzM!Hu(ArFd^Z^nj;A`_Ypuf z*D>49HJ8MM!~rGOq-593X+T,SJ6*an%MM8J!|cRA>-_O&@mBJm#j%?%3lW^~N? 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%0DGcXOZ3Y50=zYk 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

    ~TMaV=bn+wVmNV)iL-v;m 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 z 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

    Rxb0k$D4R;#f&poRLNgZhO|;ch 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�Ik_l3Qkw%^K?_+lcmEiibq0z0#8z>B*q|b4&`s) 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�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 { + engine.canvas.resizeByClientSize(); + const scene = engine.sceneManager.activeScene; + const root = scene.createRootEntity(); + + const cameraEntity = root.createChild("camera"); + const camera = cameraEntity.addComponent(Camera); + cameraEntity.transform.setPosition(0, 0, 20); + cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); + + engine.resourceManager + .load({ + urls: ["/spineboy.json", "/spineboy.atlas", "/spineboy.png"], + type: "Spine" + }) + .then((resource: any) => { + const spineEntity = resource.instantiate(); + spineEntity.transform.setPosition(-0.5, -3.2, 0); + root.addChild(spineEntity); + spineEntity.getComponent(SpineAnimationRenderer).state.setAnimation(0, "idle", true); + + updateForE2E(engine); + initScreenshot(engine, camera); + }); +}); diff --git a/e2e/case/spine-tint-black.ts b/e2e/case/spine-tint-black.ts new file mode 100644 index 0000000000..b2c6c91504 --- /dev/null +++ b/e2e/case/spine-tint-black.ts @@ -0,0 +1,41 @@ +/** + * @title Spine TintBlack + * @category Spine + */ +import { Camera, Logger, Vector3, WebGLEngine } from "@galacean/engine"; +import { SpineAnimationRenderer } from "@galacean/engine-spine"; +import "@galacean/engine-spine-core-4.2"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +Logger.enable(); + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + const scene = engine.sceneManager.activeScene; + const root = scene.createRootEntity(); + + const cameraEntity = root.createChild("camera"); + const camera = cameraEntity.addComponent(Camera); + cameraEntity.transform.setPosition(0, 0, 60); + cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); + + engine.resourceManager + .load({ + urls: ["/tank-pro.json", "/tank-pro.atlas", "/tank-pro.png"], + type: "Spine" + }) + .then((resource: any) => { + const spineEntity = resource.instantiate(); + const spine = spineEntity.getComponent(SpineAnimationRenderer); + // tank-pro ships with per-slot darkColor; tintBlack renders the two-color tint. + spine.tintBlack = true; + spineEntity.transform.setPosition(3, -5, 0); + root.addChild(spineEntity); + spine.state.setAnimation(0, "shoot", true); + + // Sample ~0.4s into "shoot": the muzzle-smoke darkColor makes the two-color tint + // clearly visible (tintBlack on/off differs ~7% here, vs ~0.01% on a static frame). + updateForE2E(engine, 40, 10); + initScreenshot(engine, camera); + }); +}); diff --git a/e2e/config.ts b/e2e/config.ts new file mode 100644 index 0000000000..d0142dcc29 --- /dev/null +++ b/e2e/config.ts @@ -0,0 +1,532 @@ +export const E2E_CONFIG = { + Spine: { + spineboy: { + category: "Spine", + caseFileName: "spine-spineboy", + threshold: 0, + diffPercentage: 0.05 + }, + tintBlack: { + category: "Spine", + caseFileName: "spine-tint-black", + threshold: 0, + diffPercentage: 0.05 + } + }, + Animator: { + additive: { + category: "Animator", + caseFileName: "animator-additive", + threshold: 0, + diffPercentage: 0 + }, + blendShape: { + category: "Animator", + caseFileName: "animator-blendShape", + threshold: 0, + diffPercentage: 0.01 + }, + blendShapeQuantization: { + category: "Animator", + caseFileName: "animator-blendShape-quantization", + threshold: 0, + diffPercentage: 0.05 + }, + crossfade: { + category: "Animator", + caseFileName: "animator-crossfade", + threshold: 0, + diffPercentage: 0 + }, + customAnimationClip: { + category: "Animator", + caseFileName: "animator-customAnimationClip", + threshold: 0, + diffPercentage: 0 + }, + customBlendShape: { + category: "Animator", + caseFileName: "animator-customBlendShape", + threshold: 0, + diffPercentage: 0 + }, + multiSubMeshBlendShape: { + category: "Animator", + caseFileName: "animator-multiSubMeshBlendShape", + threshold: 0, + diffPercentage: 0 + }, + event: { + category: "Animator", + caseFileName: "animator-event", + threshold: 0, + diffPercentage: 0.0036 + }, + play: { + category: "Animator", + caseFileName: "animator-play", + threshold: 0, + diffPercentage: 0 + }, + playBackWards: { + category: "Animator", + caseFileName: "animator-play-backwards", + threshold: 0, + diffPercentage: 0 + }, + playBeforeActive: { + category: "Animator", + caseFileName: "animator-play-beforeActive", + threshold: 0, + diffPercentage: 0 + }, + reuse: { + category: "Animator", + caseFileName: "animator-reuse", + threshold: 0, + diffPercentage: 0 + }, + stateMachineScript: { + category: "Animator", + caseFileName: "animator-stateMachineScript", + threshold: 0, + diffPercentage: 0.0036 + }, + stateMachine: { + category: "Animator", + caseFileName: "animator-stateMachine", + threshold: 0, + diffPercentage: 0 + } + }, + GLTF: { + meshopt: { + category: "GLTF", + caseFileName: "gltf-meshopt", + threshold: 0, + diffPercentage: 0.059 + }, + blendShape: { + category: "GLTF", + caseFileName: "gltf-blendshape", + threshold: 0, + diffPercentage: 0.05 + } + }, + + Material: { + blendMode: { + category: "Material", + caseFileName: "material-blendMode", + threshold: 0, + diffPercentage: 0.02 + }, + "blinn-phong": { + category: "Material", + caseFileName: "material-blinn-phong", + threshold: 0, + diffPercentage: 0.36 + }, + "pbr-clearcoat": { + category: "Material", + caseFileName: "material-pbr-clearcoat", + threshold: 0, + diffPercentage: 0.12 + }, + "white-furnace": { + category: "Material", + caseFileName: "material-white-furnace", + threshold: 0, + diffPercentage: 0.0 + }, + "pbr-specular": { + category: "Material", + caseFileName: "material-pbr-specular", + threshold: 0, + diffPercentage: 0.055 + }, + pbr: { + category: "Material", + caseFileName: "material-pbr", + threshold: 0, + diffPercentage: 0.0044 + }, + shaderLab: { + category: "Material", + caseFileName: "material-shaderLab", + threshold: 0, + diffPercentage: 0 + }, + shaderLabMRT: { + category: "Material", + caseFileName: "shaderLab-mrt", + threshold: 0, + diffPercentage: 0 + }, + shaderReplacement: { + category: "Material", + caseFileName: "material-shaderReplacement", + threshold: 0, + diffPercentage: 0.049 + }, + unlit: { + category: "Material", + caseFileName: "material-unlit", + threshold: 0, + diffPercentage: 0.033 + }, + "shaderLab-renderState": { + category: "Material", + caseFileName: "shaderLab-renderState", + threshold: 0, + diffPercentage: 0 + }, + LUT: { + category: "Material", + caseFileName: "material-LUT", + threshold: 0, + diffPercentage: 0 + } + }, + Texture: { + sRGB: { + category: "Texture", + caseFileName: "texture-sRGB-KTX2", + threshold: 0, + diffPercentage: 0.072 + }, + R8G8: { + category: "Texture", + caseFileName: "texture-R8G8", + threshold: 0, + diffPercentage: 0 + }, + KTX2HDR: { + category: "Texture", + caseFileName: "texture-hdr-ktx2", + threshold: 0, + diffPercentage: 0.015 + }, + HDR: { + category: "Texture", + caseFileName: "texture-hdr", + threshold: 0, + diffPercentage: 0.0512 + } + }, + Shadow: { + basic: { + category: "Shadow", + caseFileName: "shadow-basic", + threshold: 0, + diffPercentage: 0.008 + }, + transparent: { + category: "Shadow", + caseFileName: "shadow-transparent", + threshold: 0, + diffPercentage: 0.0552 + } + }, + Primitive: { + capsule: { + category: "Primitive", + caseFileName: "primitive-capsule", + threshold: 0, + diffPercentage: 0.0016 + }, + cone: { + category: "Primitive", + caseFileName: "primitive-cone", + threshold: 0, + diffPercentage: 0.0054 + }, + cuboid: { + category: "Primitive", + caseFileName: "primitive-cuboid", + threshold: 0, + diffPercentage: 0.0016 + }, + cylinder: { + category: "Primitive", + caseFileName: "primitive-cylinder", + threshold: 0, + diffPercentage: 0.0036 + }, + plane: { + category: "Primitive", + caseFileName: "primitive-plane", + threshold: 0, + diffPercentage: 0.0016 + }, + sphere: { + category: "Primitive", + caseFileName: "primitive-sphere", + threshold: 0, + diffPercentage: 0.0058 + }, + torus: { + category: "Primitive", + caseFileName: "primitive-torus", + threshold: 0, + diffPercentage: 0 + } + }, + Camera: { + opaqueTexture: { + category: "Camera", + caseFileName: "camera-opaque-texture", + threshold: 0, + diffPercentage: 0 + }, + fxaa: { + category: "Camera", + caseFileName: "camera-fxaa", + threshold: 0, + diffPercentage: 0.161 + }, + ssao: { + category: "Camera", + caseFileName: "camera-ssao", + threshold: 0, + diffPercentage: 0.12 + } + }, + Physics: { + "physx-collision": { + category: "Physics", + caseFileName: "physx-collision", + threshold: 0, + diffPercentage: 0 + }, + "LitePhysics Collision Group": { + category: "Physics", + caseFileName: "litePhysics-collision-group", + threshold: 0, + diffPercentage: 0 + }, + "PhysXPhysics Collision Group": { + category: "Physics", + caseFileName: "physx-collision-group", + threshold: 0, + diffPercentage: 0 + }, + "PhysXPhysics Custom Url": { + category: "Physics", + caseFileName: "physx-customUrl", + threshold: 0, + diffPercentage: 0 + }, + "PhysX Mesh Collider": { + category: "Physics", + caseFileName: "physx-mesh-collider", + threshold: 0, + diffPercentage: 0.12094 + }, + "PhysX Mesh Collider Data": { + category: "Physics", + caseFileName: "physx-mesh-collider-data", + threshold: 0, + diffPercentage: 0.02 + }, + "PhysX Deferred Contact": { + category: "Physics", + caseFileName: "physx-deferred-contact", + threshold: 0, + diffPercentage: 0.0 + } + }, + Particle: { + particleDream: { + category: "Particle", + caseFileName: "particleRenderer-dream", + threshold: 0.005, + diffPercentage: 0.015 + }, + particleFire: { + category: "Particle", + caseFileName: "particleRenderer-fire", + threshold: 0, + diffPercentage: 0.0707 + }, + forceOverLifetime: { + category: "Particle", + caseFileName: "particleRenderer-force", + threshold: 0, + diffPercentage: 0.1630209 + }, + limitVelocityOverLifetime: { + category: "Particle", + caseFileName: "particleRenderer-limitVelocity", + threshold: 0, + diffPercentage: 0.0364 + }, + textureSheetAnimation: { + category: "Particle", + caseFileName: "particleRenderer-textureSheetAnimation", + threshold: 0, + diffPercentage: 0 + }, + particleShapeMesh: { + category: "Particle", + caseFileName: "particleRenderer-shape-mesh", + threshold: 0, + diffPercentage: 0.01698 + }, + particleEmissive: { + category: "Particle", + caseFileName: "particleRenderer-emissive", + threshold: 0, + diffPercentage: 0 + }, + particleEmitMeshNoShape: { + category: "Particle", + caseFileName: "particleRenderer-emit-mesh-no-shape", + threshold: 0, + diffPercentage: 0.00136 + }, + particleEmitMeshCone: { + category: "Particle", + caseFileName: "particleRenderer-emit-mesh-cone", + threshold: 0, + diffPercentage: 0.00219 + }, + particleEmitMeshConeScaleRotation: { + category: "Particle", + caseFileName: "particleRenderer-emit-mesh-cone-scale-rotation", + threshold: 0, + diffPercentage: 0.0031 + }, + particleEmitMeshConeScaleRotationWorld: { + category: "Particle", + caseFileName: "particleRenderer-emit-mesh-cone-scale-rotation-world", + threshold: 0, + diffPercentage: 0.00928 + }, + particleEmitMeshNoShapeWorld: { + category: "Particle", + caseFileName: "particleRenderer-emit-mesh-no-shape-world", + threshold: 0, + diffPercentage: 0.00146 + }, + particleEmitMeshConeScale3DRotation: { + category: "Particle", + caseFileName: "particleRenderer-emit-mesh-cone-scale-3d-rotation", + threshold: 0, + diffPercentage: 0.0184 + }, + particleEmitMeshConeScaleRotationLife: { + category: "Particle", + caseFileName: "particleRenderer-emit-mesh-cone-scale-rotation-life", + threshold: 0, + diffPercentage: 0.036459 + }, + particleEmitMeshConeScaleRotationLifeSeperate: { + category: "Particle", + caseFileName: "particleRenderer-emit-mesh-cone-scale-rotation-life-seperate", + threshold: 0, + diffPercentage: 0.0068 + }, + particleEmitMeshConeScale3DRotationLifeSeperate: { + category: "Particle", + caseFileName: "particleRenderer-emit-mesh-cone-scale-3d-rotation-life-seperate", + threshold: 0, + diffPercentage: 0.00782 + }, + particleEmitBillboardStretched: { + category: "Particle", + caseFileName: "particleRenderer-emit-billboard-stretched", + threshold: 0, + diffPercentage: 0.0 + }, + particleHorizontalBillboard: { + category: "Particle", + caseFileName: "particleRenderer-horizontal-billboard", + threshold: 0, + diffPercentage: 0.2162 + } + }, + PostProcess: { + HDRBloomACES: { + category: "PostProcess", + caseFileName: "postProcess-HDR-bloom-ACES", + threshold: 0, + diffPercentage: 0.148 + }, + HDRBloomNeutral: { + category: "PostProcess", + caseFileName: "postProcess-HDR-bloom-neutral", + threshold: 0, + diffPercentage: 0.072 + }, + LDRBloomNeutral: { + category: "PostProcess", + caseFileName: "postProcess-LDR-bloom-neutral", + threshold: 0, + diffPercentage: 0.097 + }, + customPass: { + category: "PostProcess", + caseFileName: "postProcess-customPass", + threshold: 0, + diffPercentage: 0.03 + } + }, + SpriteMask: { + CustomStencil: { + category: "SpriteMask", + caseFileName: "spriteMask-customStencil", + threshold: 0, + diffPercentage: 0.0024 + } + }, + Text: { + TypedText: { + category: "Text", + caseFileName: "text-typed", + threshold: 0.016, + diffPercentage: 0.00136 + }, + CharacterSpacing: { + category: "Text", + caseFileName: "text-character-spacing", + threshold: 0.0, + diffPercentage: 0.0 + } + }, + Trail: { + basic: { + category: "Trail", + caseFileName: "trailRenderer-basic", + threshold: 0, + diffPercentage: 0 + } + }, + Other: { + MultiSceneClear: { + category: "Advance", + caseFileName: "multi-scene-clear", + threshold: 0, + diffPercentage: 0 + }, + MultiSceneNoClear: { + category: "Advance", + caseFileName: "multi-scene-no-clear", + threshold: 0, + diffPercentage: 0 + }, + MultiCameraNoClear: { + category: "Advance", + caseFileName: "multi-camera-no-clear", + threshold: 0, + diffPercentage: 0 + }, + + CanvasTransparency: { + category: "Advance", + caseFileName: "canvas-transparency", + threshold: 0, + diffPercentage: 0.044 + } + } +}; diff --git a/e2e/fixtures/originImage/Spine_spine-spineboy.jpg b/e2e/fixtures/originImage/Spine_spine-spineboy.jpg new file mode 100644 index 0000000000..76065431eb --- /dev/null +++ b/e2e/fixtures/originImage/Spine_spine-spineboy.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58b5f1a156b82707ead037eb64d0b130534834c146d907b45be9b0cb16dbed49 +size 74591 diff --git a/e2e/fixtures/originImage/Spine_spine-tint-black.jpg b/e2e/fixtures/originImage/Spine_spine-tint-black.jpg new file mode 100644 index 0000000000..410a970ccf --- /dev/null +++ b/e2e/fixtures/originImage/Spine_spine-tint-black.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51fc0948e2ce8b819093526b5665b10e9975a810d4c1975fa661c091f2842e81 +size 122845 diff --git a/e2e/package.json b/e2e/package.json index 6558caf6a3..e646c5dd12 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -20,6 +20,8 @@ "@galacean/engine-rhi-webgl": "workspace:*", "@galacean/engine-physics-lite": "workspace:*", "@galacean/engine-physics-physx": "workspace:*", + "@galacean/engine-spine": "workspace:*", + "@galacean/engine-spine-core-4.2": "workspace:*", "dat.gui": "^0.7.9", "vite": "3.1.6", "sass": "^1.55.0" diff --git a/examples/package.json b/examples/package.json index 444aef9f77..59376745de 100644 --- a/examples/package.json +++ b/examples/package.json @@ -21,6 +21,9 @@ "@galacean/engine-rhi-webgl": "workspace:*", "@galacean/engine-shader": "workspace:*", "@galacean/engine-shaderlab": "workspace:*", + "@galacean/engine-spine": "workspace:*", + "@galacean/engine-spine-core-3.8": "workspace:*", + "@galacean/engine-spine-core-4.2": "workspace:*", "@galacean/engine-toolkit": "latest", "@galacean/engine-toolkit-stats": "latest", "@galacean/engine-ui": "workspace:*" @@ -29,4 +32,4 @@ "dat.gui": "^0.7.9", "vite": "^4.4.4" } -} \ No newline at end of file +} diff --git a/examples/src/spine-keli-4.2.ts b/examples/src/spine-keli-4.2.ts new file mode 100644 index 0000000000..8ad862a4a0 --- /dev/null +++ b/examples/src/spine-keli-4.2.ts @@ -0,0 +1,60 @@ +/** + * @title Spine Keli (4.2) + * @category Spine + */ +import { Camera, Entity, Logger, Vector3, WebGLEngine } from "@galacean/engine"; +import { SpineAnimationRenderer, SpineResource } from "@galacean/engine-spine"; +import "@galacean/engine-spine-core-4.2"; +import * as dat from "dat.gui"; + +Logger.enable(); + +const gui = new dat.GUI(); +const state = { animation: "" }; + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + const scene = engine.sceneManager.activeScene; + const root = scene.createRootEntity(); + + const cameraEntity = root.createChild("camera"); + const camera = cameraEntity.addComponent(Camera); + cameraEntity.transform.setPosition(0, 2, 10); + cameraEntity.transform.lookAt(new Vector3(0, 2, 0)); + + engine.run(); + + engine.resourceManager + .load({ + urls: [ + "/spine/keli/keli.json", + "/spine/keli/keli.atlas", + "/spine/keli/images/arm_l.png", + "/spine/keli/images/arm_r.png", + "/spine/keli/images/head.png", + "/spine/keli/images/legs.png", + "/spine/keli/images/torso.png" + ], + type: "Spine" + }) + .then((resource) => { + const spineEntity: Entity = resource.instantiate(); + root.addChild(spineEntity); + + const animator = spineEntity.getComponent(SpineAnimationRenderer); + const animationNames = resource.skeletonData.animations.map((animation) => animation.name); + + state.animation = animationNames[0]; + animator.state.setAnimation(0, state.animation, true); + + gui + .add(state, "animation", animationNames) + .name("Animation") + .onChange((name: string) => { + animator.state.setAnimation(0, name, true); + }); + }) + .catch((error) => { + console.error("Failed to load keli (4.2):", error); + }); +}); diff --git a/examples/src/spine-otakugirl-3.8.ts b/examples/src/spine-otakugirl-3.8.ts new file mode 100644 index 0000000000..410db286dd --- /dev/null +++ b/examples/src/spine-otakugirl-3.8.ts @@ -0,0 +1,55 @@ +/** + * @title Spine Otakugirl (3.8) + * @category Spine + * @remarks + * A genuine spine 3.8.99 export (binary .skel), unlike the keli asset used in the 4.2 example — + * this verifies the 3.8 backend against data it's actually meant to parse. + */ +import { Camera, Entity, Logger, Vector3, WebGLEngine } from "@galacean/engine"; +import { SpineAnimationRenderer, SpineResource } from "@galacean/engine-spine"; +import "@galacean/engine-spine-core-3.8"; +import * as dat from "dat.gui"; + +Logger.enable(); + +const gui = new dat.GUI(); +const state = { animation: "" }; + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + const scene = engine.sceneManager.activeScene; + const root = scene.createRootEntity(); + + const cameraEntity = root.createChild("camera"); + const camera = cameraEntity.addComponent(Camera); + cameraEntity.transform.setPosition(0, 2, 10); + cameraEntity.transform.lookAt(new Vector3(0, 2, 0)); + + engine.run(); + + engine.resourceManager + .load({ + urls: ["/spine/otakugirl/otakugirl.skel", "/spine/otakugirl/otakugirl.atlas", "/spine/otakugirl/otakugirl.png"], + type: "Spine" + }) + .then((resource) => { + const spineEntity: Entity = resource.instantiate(); + root.addChild(spineEntity); + + const animator = spineEntity.getComponent(SpineAnimationRenderer); + const animationNames = resource.skeletonData.animations.map((animation) => animation.name); + + state.animation = animationNames[0]; + animator.state.setAnimation(0, state.animation, true); + + gui + .add(state, "animation", animationNames) + .name("Animation") + .onChange((name: string) => { + animator.state.setAnimation(0, name, true); + }); + }) + .catch((error) => { + console.error("Failed to load otakugirl (3.8):", error); + }); +}); diff --git a/packages/core/src/shader/ShaderPool.ts b/packages/core/src/shader/ShaderPool.ts index e677d9e22f..231f1121fe 100644 --- a/packages/core/src/shader/ShaderPool.ts +++ b/packages/core/src/shader/ShaderPool.ts @@ -24,6 +24,8 @@ import spriteMaskFs from "../shaderlib/extra/sprite-mask.fs.glsl"; import spriteMaskVs from "../shaderlib/extra/sprite-mask.vs.glsl"; import spriteFs from "../shaderlib/extra/sprite.fs.glsl"; import spriteVs from "../shaderlib/extra/sprite.vs.glsl"; +import spineFs from "../shaderlib/extra/spine.fs.glsl"; +import spineVs from "../shaderlib/extra/spine.vs.glsl"; import textFs from "../shaderlib/extra/text.fs.glsl"; import textVs from "../shaderlib/extra/text.vs.glsl"; import trailFs from "../shaderlib/extra/trail.fs.glsl"; @@ -33,7 +35,10 @@ import unlitVs from "../shaderlib/extra/unlit.vs.glsl"; import { TransformFeedbackShader } from "../graphic/TransformFeedbackShader"; import { Shader } from "./Shader"; import { ShaderPass } from "./ShaderPass"; +import { ShaderProperty } from "./ShaderProperty"; +import { CullMode } from "./enums/CullMode"; import { RenderStateElementKey } from "./enums/RenderStateElementKey"; +import { RenderQueueType } from "./enums/RenderQueueType"; import { RenderState } from "./state"; /** @@ -87,8 +92,32 @@ export class ShaderPool { Shader.create("SpriteMask", [new ShaderPass("Forward", spriteMaskVs, spriteMaskFs, forwardPassTags)]); Shader.create("Sprite", [new ShaderPass("Forward", spriteVs, spriteFs, forwardPassTags)]); Shader.create("Text", [new ShaderPass("Forward", textVs, textFs, forwardPassTags)]); + Shader.create("2D/Spine", [ShaderPool._createSpinePass(forwardPassTags)]); Shader.create("background-texture", [ new ShaderPass("Forward", backgroundTextureVs, backgroundTextureFs, forwardPassTags) ]); } + + private static _createSpinePass(tags: Record): ShaderPass { + const pass = new ShaderPass("Forward", spineVs, spineFs, tags); + const renderState = (pass._renderState = new RenderState()); + const blendState = renderState.blendState.targetBlendState; + + blendState.enabled = true; + renderState.depthState.writeEnabled = false; + renderState.rasterState.cullMode = CullMode.Off; + renderState.renderQueueType = RenderQueueType.Transparent; + + const renderStateDataMap = pass._renderStateDataMap; + renderStateDataMap[RenderStateElementKey.BlendStateSourceColorBlendFactor0] = + ShaderProperty.getByName("sourceColorBlendFactor"); + renderStateDataMap[RenderStateElementKey.BlendStateDestinationColorBlendFactor0] = + ShaderProperty.getByName("destinationColorBlendFactor"); + renderStateDataMap[RenderStateElementKey.BlendStateSourceAlphaBlendFactor0] = + ShaderProperty.getByName("sourceAlphaBlendFactor"); + renderStateDataMap[RenderStateElementKey.BlendStateDestinationAlphaBlendFactor0] = + ShaderProperty.getByName("destinationAlphaBlendFactor"); + + return pass; + } } diff --git a/packages/core/src/shaderlib/extra/spine.fs.glsl b/packages/core/src/shaderlib/extra/spine.fs.glsl new file mode 100644 index 0000000000..4137749806 --- /dev/null +++ b/packages/core/src/shaderlib/extra/spine.fs.glsl @@ -0,0 +1,29 @@ +#include + +uniform sampler2D material_SpineTexture; +uniform float renderer_PremultipliedAlpha; + +varying vec2 v_uv; +varying vec4 v_lightColor; + +#ifdef RENDERER_TINT_BLACK + varying vec3 v_darkColor; +#endif + +void main() +{ + vec4 texColor = texture2D(material_SpineTexture, v_uv); + vec4 lightColor = sRGBToLinear(v_lightColor); + + #ifdef RENDERER_TINT_BLACK + vec4 darkColor = sRGBToLinear(vec4(v_darkColor, 1.0)); + vec3 dark_premult = (texColor.a - texColor.rgb) * darkColor.rgb; + vec3 dark_nonpremult = (1.0 - texColor.rgb) * darkColor.rgb; + vec3 dark = mix(dark_nonpremult, dark_premult, renderer_PremultipliedAlpha); + vec3 light = texColor.rgb * lightColor.rgb; + gl_FragColor.rgb = dark + light; + gl_FragColor.a = texColor.a * lightColor.a; + #else + gl_FragColor = texColor * lightColor; + #endif +} diff --git a/packages/core/src/shaderlib/extra/spine.vs.glsl b/packages/core/src/shaderlib/extra/spine.vs.glsl new file mode 100644 index 0000000000..ccac4ff172 --- /dev/null +++ b/packages/core/src/shaderlib/extra/spine.vs.glsl @@ -0,0 +1,28 @@ +uniform mat4 renderer_MVPMat; + +attribute vec3 POSITION; +attribute vec2 TEXCOORD_0; +attribute vec4 LIGHT_COLOR; + +#ifdef RENDERER_TINT_BLACK + attribute vec3 DARK_COLOR; +#endif + +varying vec2 v_uv; +varying vec4 v_lightColor; + +#ifdef RENDERER_TINT_BLACK + varying vec3 v_darkColor; +#endif + +void main() +{ + gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); + + v_uv = TEXCOORD_0; + v_lightColor = LIGHT_COLOR; + + #ifdef RENDERER_TINT_BLACK + v_darkColor = DARK_COLOR; + #endif +} diff --git a/packages/spine-core-3.8/package.json b/packages/spine-core-3.8/package.json new file mode 100644 index 0000000000..6ecce6e2e0 --- /dev/null +++ b/packages/spine-core-3.8/package.json @@ -0,0 +1,40 @@ +{ + "name": "@galacean/engine-spine-core-3.8", + "version": "0.0.0-experimental-2.0-game.18", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "repository": { + "url": "https://github.com/galacean/engine.git" + }, + "license": "MIT", + "main": "dist/main.js", + "module": "dist/module.js", + "debug": "src/index.ts", + "browser": "dist/browser.js", + "types": "types/index.d.ts", + "scripts": { + "b:types": "tsc" + }, + "umd": { + "name": "Galacean.SpineCore38", + "globals": { + "@galacean/engine": "Galacean", + "@galacean/engine-spine": "Galacean.Spine" + } + }, + "files": [ + "dist/**/*", + "types/**/*" + ], + "dependencies": { + "@galacean/engine-spine": "workspace:*" + }, + "devDependencies": { + "@galacean/engine": "workspace:*" + }, + "peerDependencies": { + "@galacean/engine": "workspace:*" + } +} diff --git a/packages/spine-core-3.8/src/Spine38Runtime.ts b/packages/spine-core-3.8/src/Spine38Runtime.ts new file mode 100644 index 0000000000..f2d8a9c7e9 --- /dev/null +++ b/packages/spine-core-3.8/src/Spine38Runtime.ts @@ -0,0 +1,94 @@ +import { + AnimationState, + AnimationStateData, + AtlasAttachmentLoader, + Skeleton, + SkeletonBinary, + SkeletonData, + SkeletonJson, + TextureAtlas +} from "./spine-core"; +import type { Texture2D } from "@galacean/engine"; +import type { ISpineRenderTarget } from "@galacean/engine-spine"; +import { SpineGenerator } from "./SpineGenerator"; +import { SpineTexture } from "./SpineTexture"; + +/** + * Spine 3.8 runtime backend. + * + * @remarks + * Owns everything specific to the spine 3.8 runtime line: parsing (`SkeletonJson`/`SkeletonBinary`/ + * `TextureAtlas`), object-model construction, world-transform stepping (3.8 has no `Physics` argument), + * and the attachment-reading mesh generator. Registered automatically when this package is imported. + * + * Deliberately does not `implements ISpineRuntime`: that interface types its methods against the + * 4.2 npm package as the "canonical contract", but 4.2 and 3.8 have actually diverged enough + * (4.2 added `TextureAtlas.setTextures`; 4.2 dropped `Skeleton`/`SkeletonData`'s `updateCacheReset`/ + * `findBoneIndex`/`findSlotIndex`/`findPathConstraintIndex`, all present in 3.8) that TS's structural + * check fails even though every member `ISpineRuntime` actually calls lines up. Registered via a cast + * in `index.ts` instead of fighting the type checker method-by-method. + */ +export class Spine38Runtime { + private _generator = new SpineGenerator(); + + parseAtlasPageNames(atlasText: string): string[] { + // Unlike 4.2, spine 3.8's `TextureAtlas` constructor requires a synchronous texture loader and + // calls `setFilters`/`setWraps` on whatever it returns immediately during parsing, so a bare + // `null` loader would throw; hand back a no-op stub instead (its return type is `any`). + const pageNames: string[] = []; + new TextureAtlas(atlasText, (path) => { + pageNames.push(path); + return { setFilters() {}, setWraps() {} }; + }); + return pageNames; + } + + createTextureAtlas(atlasText: string, textures: Texture2D[]): TextureAtlas { + let index = 0; + return new TextureAtlas(atlasText, (path) => { + const engineTexture = textures.find((item) => item.name === path) || textures[index]; + index++; + return new SpineTexture(engineTexture); + }); + } + + createSkeletonData( + skeletonRawData: string | ArrayBuffer, + textureAtlas: TextureAtlas, + skeletonDataScale: number + ): SkeletonData { + const atlasLoader = new AtlasAttachmentLoader(textureAtlas); + if (typeof skeletonRawData === "string") { + const skeletonJson = new SkeletonJson(atlasLoader); + skeletonJson.scale = skeletonDataScale; + return skeletonJson.readSkeletonData(skeletonRawData); + } else { + const skeletonBinary = new SkeletonBinary(atlasLoader); + skeletonBinary.scale = skeletonDataScale; + return skeletonBinary.readSkeletonData(new Uint8Array(skeletonRawData as ArrayBuffer)); + } + } + + createAnimationStateData(skeletonData: SkeletonData): AnimationStateData { + return new AnimationStateData(skeletonData); + } + + createSkeleton(skeletonData: SkeletonData): Skeleton { + return new Skeleton(skeletonData); + } + + createAnimationState(stateData: AnimationStateData): AnimationState { + return new AnimationState(stateData); + } + + updateState(skeleton: Skeleton, state: AnimationState, delta: number): void { + state.update(delta); + state.apply(skeleton); + skeleton.update(delta); + skeleton.updateWorldTransform(); + } + + buildPrimitive(skeleton: Skeleton, target: ISpineRenderTarget): void { + this._generator.buildPrimitive(skeleton, target); + } +} diff --git a/packages/spine-core-3.8/src/SpineGenerator.ts b/packages/spine-core-3.8/src/SpineGenerator.ts new file mode 100644 index 0000000000..5ba048f320 --- /dev/null +++ b/packages/spine-core-3.8/src/SpineGenerator.ts @@ -0,0 +1,358 @@ +import { + ArrayLike, + BlendMode, + ClippingAttachment, + Color, + MeshAttachment, + RegionAttachment, + Skeleton, + SkeletonClipping, + TextureAtlasRegion +} from "./spine-core"; +import { BoundingBox, SubPrimitive } from "@galacean/engine"; +import { SpineTexture } from "./SpineTexture"; +import { ClearablePool } from "./util/ClearablePool"; +import { ReturnablePool } from "./util/ReturnablePool"; +import type { ISpineRenderTarget } from "@galacean/engine-spine"; +import { SpineBlendMode, SpineVertexStride } from "@galacean/engine-spine"; + +class SubRenderItem { + subPrimitive: SubPrimitive; + blendMode: BlendMode; + texture: any; +} + +/** + * @internal + */ +export class SpineGenerator { + static tempDark = new Color(); + static tempColor = new Color(); + static tempVerts = new Array(8); + static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0]; + static subPrimitivePool = new ReturnablePool(SubPrimitive); + static subRenderItemPool = new ClearablePool(SubRenderItem); + + private _subRenderItems: SubRenderItem[] = []; + private _clipper: SkeletonClipping = new SkeletonClipping(); + + buildPrimitive(skeleton: Skeleton, renderer: ISpineRenderTarget) { + const { _indices, _vertices, _localBounds, _vertexCount, _subPrimitives, zSpacing, premultipliedAlpha, tintBlack } = + renderer; + + _localBounds.min.set(Infinity, Infinity, Infinity); + _localBounds.max.set(-Infinity, -Infinity, -Infinity); + + const { _clipper, _subRenderItems } = this; + + const { tempVerts, subRenderItemPool, subPrimitivePool } = SpineGenerator; + const { withTint: vertexStrideWithTint, withoutTint: vertexStrideWithoutTint } = SpineVertexStride; + + _subRenderItems.length = 0; + subRenderItemPool.clear(); + + let triangles: Array; + let uvs: ArrayLike; + + let verticesLength = 0; + let indicesLength = 0; + let start = 0; + let count = 0; + + let blend = BlendMode.Normal; + let texture: SpineTexture = null; + let tempBlendMode: BlendMode | null = null; + let tempTexture: SpineTexture | null = null; + + let primitiveIndex = 0; + + const drawOrder = skeleton.drawOrder; + for (let slotIndex = 0, n = drawOrder.length; slotIndex < n; ++slotIndex) { + const slot = drawOrder[slotIndex]; + if (!slot.bone.active) { + _clipper.clipEndWithSlot(slot); + continue; + } + + const attachment = slot.getAttachment(); + if (!attachment) { + _clipper.clipEndWithSlot(slot); + continue; + } + + const z = zSpacing * slotIndex; + const isClipping = _clipper.isClipping(); + let numFloats = 0; + let attachmentColor: Color = null; + + // This vertexSize will be passed to spine-core's computeWorldVertices function. + // + // Expected format by computeWorldVertices: + // - Without tintBlack: [x, y, u, v, r, g, b, a] = 8 components + // - With tintBlack: [x, y, u, v, r, g, b, a, dr, dg, db, da] = 12 components + // + // Our actual vertex buffer format: + // - vertexStrideWithoutTint: [x, y, z, u, v, r, g, b, a] = 9 components + // - vertexStrideWithTint: [x, y, z, u, v, r, g, b, a, dr, dg, db] = 12 components + // (Note: we optimized 'da' as uniform instead of buffer attribute) + // + // Calculation: + // - Without tintBlack: 9 - 1 (remove z) = 8 ✓ + // - With tintBlack: 12 - 1 (remove z) + 1 (add back da) = 12 ✓ + const vertexSize = tintBlack ? vertexStrideWithTint : vertexStrideWithoutTint - 1; + const clippedVertexSize = isClipping ? 2 : vertexSize; + + switch (attachment.constructor) { + case RegionAttachment: + const regionAttachment = attachment; + attachmentColor = regionAttachment.color; + numFloats = clippedVertexSize << 2; + // spine 3.8's RegionAttachment.computeWorldVertices takes the Bone, not the Slot (4.2 takes Slot). + regionAttachment.computeWorldVertices(slot.bone, tempVerts, 0, clippedVertexSize); + triangles = SpineGenerator.QUAD_TRIANGLES; + uvs = regionAttachment.uvs; + // `region` is statically typed as the base TextureRegion (no `texture` field); it's + // always a TextureAtlasRegion in practice, since AtlasAttachmentLoader is the only + // source of regions. Its `.texture` is in turn always a SpineTexture, since + // createTextureAtlas is the only place that constructs one. + texture = (regionAttachment.region as TextureAtlasRegion).texture as unknown as SpineTexture; + break; + case MeshAttachment: + const meshAttachment = attachment; + attachmentColor = meshAttachment.color; + numFloats = (meshAttachment.worldVerticesLength >> 1) * clippedVertexSize; + if (numFloats > _vertices.length) { + SpineGenerator.tempVerts = new Array(numFloats); + } + meshAttachment.computeWorldVertices( + slot, + 0, + meshAttachment.worldVerticesLength, + tempVerts, + 0, + clippedVertexSize + ); + triangles = meshAttachment.triangles; + uvs = meshAttachment.uvs; + texture = (meshAttachment.region as TextureAtlasRegion).texture as unknown as SpineTexture; + break; + case ClippingAttachment: + const clip = attachment; + _clipper.clipStart(slot, clip); + continue; + default: + _clipper.clipEndWithSlot(slot); + continue; + } + + if (texture != null) { + let finalVertices: ArrayLike; + let finalVerticesLength: number; + let finalIndices: ArrayLike; + let finalIndicesLength: number; + + const skeleton = slot.bone.skeleton; + const skeletonColor = skeleton.color; + const slotColor = slot.color; + const finalColor = SpineGenerator.tempColor; + const finalAlpha = skeletonColor.a * slotColor.a * attachmentColor.a; + + finalColor.r = skeletonColor.r * slotColor.r * attachmentColor.r; + finalColor.g = skeletonColor.g * slotColor.g * attachmentColor.g; + finalColor.b = skeletonColor.b * slotColor.b * attachmentColor.b; + finalColor.a = finalAlpha; + + if (premultipliedAlpha) { + finalColor.r *= finalAlpha; + finalColor.g *= finalAlpha; + finalColor.b *= finalAlpha; + } + + const darkColor = SpineGenerator.tempDark; + const slotDarkColor = slot.darkColor; + if (!slotDarkColor) { + darkColor.set(0, 0, 0, 1); + } else { + if (premultipliedAlpha) { + darkColor.r = slotDarkColor.r * finalAlpha; + darkColor.g = slotDarkColor.g * finalAlpha; + darkColor.b = slotDarkColor.b * finalAlpha; + } else { + darkColor.setFromColor(slotDarkColor); + } + } + + if (isClipping) { + // spine 3.8's SkeletonClipping.clipTriangles takes an extra (unused) verticesLength param + // right after `vertices` that 4.2 dropped. + _clipper.clipTriangles( + tempVerts, + numFloats, + triangles, + triangles.length, + uvs, + finalColor, + darkColor, + tintBlack + ); + finalVertices = _clipper.clippedVertices; + finalVerticesLength = finalVertices.length; + finalIndices = _clipper.clippedTriangles; + finalIndicesLength = finalIndices.length; + } else { + const { r, g, b, a } = finalColor; + for (let v = 2, u = 0, n = numFloats; v < n; v += vertexSize, u += 2) { + tempVerts[v] = r; + tempVerts[v + 1] = g; + tempVerts[v + 2] = b; + tempVerts[v + 3] = a; + tempVerts[v + 4] = uvs[u]; + tempVerts[v + 5] = uvs[u + 1]; + if (tintBlack) { + tempVerts[v + 6] = darkColor.r; + tempVerts[v + 7] = darkColor.g; + tempVerts[v + 8] = darkColor.b; + tempVerts[v + 9] = darkColor.a; + } + } + finalVertices = tempVerts; + finalVerticesLength = numFloats; + finalIndices = triangles; + finalIndicesLength = triangles.length; + } + + if (finalVerticesLength == 0 || finalIndicesLength == 0) { + _clipper.clipEndWithSlot(slot); + continue; + } + + const stride = tintBlack ? vertexStrideWithTint : vertexStrideWithoutTint; + const indexStart = verticesLength / stride; + let i = verticesLength; + let j = 0; + for (; j < finalVerticesLength; ) { + const x = finalVertices[j++]; + const y = finalVertices[j++]; + _vertices[i++] = x; + _vertices[i++] = y; + _vertices[i++] = z; + _vertices[i++] = finalVertices[j++]; // u + _vertices[i++] = finalVertices[j++]; // v + _vertices[i++] = finalVertices[j++]; // r + _vertices[i++] = finalVertices[j++]; // g + _vertices[i++] = finalVertices[j++]; // b + _vertices[i++] = finalVertices[j++]; // a + if (tintBlack) { + _vertices[i++] = finalVertices[j++]; // darkR + _vertices[i++] = finalVertices[j++]; // darkG + _vertices[i++] = finalVertices[j++]; // darkB + j++; // darkA + } + this._expandBounds(x, y, z, _localBounds); + } + verticesLength = i; + + for (i = indicesLength, j = 0; j < finalIndicesLength; i++, j++) { + _indices[i] = finalIndices[j] + indexStart; + } + indicesLength += finalIndicesLength; + + const textureChanged = tempTexture !== null && tempTexture !== texture; + blend = slot.data.blendMode; + const blendModeChanged = tempBlendMode !== null && tempBlendMode !== blend; + + if (blendModeChanged || textureChanged) { + // Finish accumulated count first + if (count > 0) { + primitiveIndex = this._createRenderItem( + _subPrimitives, + primitiveIndex, + start, + count, + tempTexture, + tempBlendMode + ); + start += count; + count = 0; + } + } + count += finalIndicesLength; + tempTexture = texture; + tempBlendMode = blend; + } + + _clipper.clipEndWithSlot(slot); + } // slot traverse end + + // add reset sub primitive + if (count > 0) { + primitiveIndex = this._createRenderItem(_subPrimitives, primitiveIndex, start, count, texture, blend); + count = 0; + } + + _clipper.clipEnd(); + + const lastLen = _subPrimitives.length; + const curLen = _subRenderItems.length; + for (let i = curLen; i < lastLen; i++) { + const item = _subPrimitives[i]; + subPrimitivePool.return(item); + } + + renderer._clearSubPrimitives(); + for (let i = 0, l = curLen; i < l; ++i) { + const item = _subRenderItems[i]; + const { blendMode, texture } = item; + renderer._addSubPrimitive(item.subPrimitive); + const material = renderer._getMaterial(texture.getImage(), blendMode as unknown as SpineBlendMode); + renderer.setMaterial(i, material); + } + + if (indicesLength > _vertexCount || renderer._needResizeBuffer) { + renderer._createAndBindBuffer(indicesLength); + renderer._needResizeBuffer = false; + this.buildPrimitive(skeleton, renderer); + return; + } + + if (renderer._vertexBuffer) { + renderer._vertexBuffer.setData(_vertices); + renderer._indexBuffer.setData(_indices); + } + } + + private _createRenderItem( + subPrimitives: SubPrimitive[], + primitiveIndex: number, + start: number, + count: number, + texture: SpineTexture, + blend: BlendMode + ): number { + const { subPrimitivePool, subRenderItemPool } = SpineGenerator; + const origin = subPrimitives[primitiveIndex]; + + if (origin) { + primitiveIndex++; + } + + const subPrimitive = origin || subPrimitivePool.get(); + subPrimitive.start = start; + subPrimitive.count = count; + + const renderItem = subRenderItemPool.get(); + renderItem.blendMode = blend; + renderItem.subPrimitive = subPrimitive; + renderItem.texture = texture; + + this._subRenderItems.push(renderItem); + + return primitiveIndex; + } + + private _expandBounds(x: number, y: number, z: number, localBounds: BoundingBox) { + const { min, max } = localBounds; + min.set(Math.min(min.x, x), Math.min(min.y, y), Math.min(min.z, z)); + max.set(Math.max(max.x, x), Math.max(max.y, y), Math.max(max.z, z)); + } +} diff --git a/packages/spine-core-3.8/src/SpineTexture.ts b/packages/spine-core-3.8/src/SpineTexture.ts new file mode 100644 index 0000000000..cd604451ab --- /dev/null +++ b/packages/spine-core-3.8/src/SpineTexture.ts @@ -0,0 +1,50 @@ +import { Texture, TextureFilter, TextureWrap } from "./spine-core/Texture"; +import { Texture2D, TextureFilterMode, TextureWrapMode } from "@galacean/engine"; + +/** + * @internal + */ +export class SpineTexture extends Texture { + constructor(image: Texture2D) { + super(image); + image.generateMipmaps(); + } + + // The vendored 3.8 `Texture` base class exposes the Texture2D via a `texture` getter instead of + // spine-core 4.2's `getImage()`; this alias keeps SpineGenerator's call sites identical across backends. + getImage(): Texture2D { + return this._texture; + } + + setFilters(minFilter: TextureFilter, magFilter: TextureFilter) { + if (minFilter === TextureFilter.Nearest) { + this._texture.filterMode = TextureFilterMode.Point; + } else if (minFilter === TextureFilter.Linear) { + this._texture.filterMode = TextureFilterMode.Bilinear; + } else { + // The remaining min filters are the MipMap* family (mag filters are never MipMap*); + // mipmaps are generated in the constructor. + this._texture.filterMode = TextureFilterMode.Trilinear; + } + } + + setWraps(uWrap: TextureWrap, vWrap: TextureWrap) { + this._texture.wrapModeU = this._convertWrapMode(uWrap); + this._texture.wrapModeV = this._convertWrapMode(vWrap); + } + + dispose() {} + + private _convertWrapMode(wrap: TextureWrap): TextureWrapMode { + switch (wrap) { + case TextureWrap.ClampToEdge: + return TextureWrapMode.Clamp; + case TextureWrap.Repeat: + return TextureWrapMode.Repeat; + case TextureWrap.MirroredRepeat: + return TextureWrapMode.Mirror; + default: + throw new Error("Unsupported texture wrap mode."); + } + } +} diff --git a/packages/spine-core-3.8/src/index.ts b/packages/spine-core-3.8/src/index.ts new file mode 100644 index 0000000000..c9e0710bc4 --- /dev/null +++ b/packages/spine-core-3.8/src/index.ts @@ -0,0 +1,17 @@ +import type { ISpineRuntime } from "@galacean/engine-spine"; +import { registerSpineRuntime } from "@galacean/engine-spine"; +import { Spine38Runtime } from "./Spine38Runtime"; + +// Self-register on import: `import "@galacean/engine-spine-core-3.8"` wires the 3.8 backend +// into the shared `@galacean/engine-spine` package. Cast needed because Spine38Runtime doesn't +// `implements ISpineRuntime` at the type level — see the class doc comment for why. +registerSpineRuntime(new Spine38Runtime() as unknown as ISpineRuntime); + +export { Spine38Runtime }; + +// Re-export the vendored spine 3.8 runtime API. Power users that construct spine-core objects +// directly (custom attachments, manual skeleton parsing) import them from this core package. +export * from "./spine-core"; + +export const version = `__buildVersion`; +console.log(`Galacean spine-core-3.8 version: ${version}`); diff --git a/packages/spine-core-3.8/src/spine-core/Animation.ts b/packages/spine-core-3.8/src/spine-core/Animation.ts new file mode 100644 index 0000000000..e15edbdc9d --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/Animation.ts @@ -0,0 +1,1828 @@ +import { Skeleton } from "./Skeleton"; +import { Utils, MathUtils, ArrayLike } from "./Utils"; +import { Slot } from "./Slot"; +import { VertexAttachment, Attachment } from "./attachments/Attachment"; +import { IkConstraint } from "./IkConstraint"; +import { TransformConstraint } from "./TransformConstraint"; +import { PathConstraint } from "./PathConstraint"; +import { Event } from "./Event"; + +/** A simple container for a list of timelines and a name. */ +export class Animation { + /** The animation's name, which is unique across all animations in the skeleton. */ + name: string; + timelines: Array; + timelineIds: Array; + + /** The duration of the animation in seconds, which is the highest time of all keys in the timeline. */ + duration: number; + + constructor(name: string, timelines: Array, duration: number) { + if (name == null) throw new Error("name cannot be null."); + if (timelines == null) throw new Error("timelines cannot be null."); + this.name = name; + this.timelines = timelines; + this.timelineIds = []; + for (let i = 0; i < timelines.length; i++) this.timelineIds[timelines[i].getPropertyId()] = true; + this.duration = duration; + } + + hasTimeline(id: number) { + return this.timelineIds[id] == true; + } + + /** Applies all the animation's timelines to the specified skeleton. + * + * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. + * @param loop If true, the animation repeats after {@link #getDuration()}. + * @param events May be null to ignore fired events. */ + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + loop: boolean, + events: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + if (skeleton == null) throw new Error("skeleton cannot be null."); + + if (loop && this.duration != 0) { + time %= this.duration; + if (lastTime > 0) lastTime %= this.duration; + } + + const timelines = this.timelines; + for (let i = 0, n = timelines.length; i < n; i++) + timelines[i].apply(skeleton, lastTime, time, events, alpha, blend, direction); + } + + /** @param target After the first and before the last value. + * @returns index of first value greater than the target. */ + static binarySearch(values: ArrayLike, target: number, step: number = 1) { + let low = 0; + let high = values.length / step - 2; + if (high == 0) return step; + let current = high >>> 1; + while (true) { + if (values[(current + 1) * step] <= target) low = current + 1; + else high = current; + if (low == high) return (low + 1) * step; + current = (low + high) >>> 1; + } + } + + static linearSearch(values: ArrayLike, target: number, step: number) { + for (let i = 0, last = values.length - step; i <= last; i += step) if (values[i] > target) return i; + return -1; + } +} + +/** The interface for all timelines. */ +export interface Timeline { + /** Applies this timeline to the skeleton. + * @param skeleton The skeleton the timeline is being applied to. This provides access to the bones, slots, and other + * skeleton components the timeline may change. + * @param lastTime The time this timeline was last applied. Timelines such as {@link EventTimeline}} trigger only at specific + * times rather than every frame. In that case, the timeline triggers everything between `lastTime` + * (exclusive) and `time` (inclusive). + * @param time The time within the animation. Most timelines find the key before and the key after this time so they can + * interpolate between the keys. + * @param events If any events are fired, they are added to this list. Can be null to ignore fired events or if the timeline + * does not fire events. + * @param alpha 0 applies the current or setup value (depending on `blend`). 1 applies the timeline value. + * Between 0 and 1 applies a value between the current or setup value and the timeline value. By adjusting + * `alpha` over time, an animation can be mixed in or out. `alpha` can also be useful to + * apply animations on top of each other (layering). + * @param blend Controls how mixing is applied when `alpha` < 1. + * @param direction Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, + * such as {@link DrawOrderTimeline} or {@link AttachmentTimeline}. */ + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + events: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ): void; + + /** Uniquely encodes both the type of this timeline and the skeleton property that it affects. */ + getPropertyId(): number; +} + +/** Controls how a timeline value is mixed with the setup pose value or current pose value when a timeline's `alpha` + * < 1. + * + * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */ +export enum MixBlend { + /** Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup + * value is set. */ + setup, + /** Transitions from the current value to the timeline value. Before the first key, transitions from the current value to + * the setup value. Timelines which perform instant transitions, such as {@link DrawOrderTimeline} or + * {@link AttachmentTimeline}, use the setup value before the first key. + * + * `first` is intended for the first animations applied, not for animations layered on top of those. */ + first, + /** Transitions from the current value to the timeline value. No change is made before the first key (the current value is + * kept until the first key). + * + * `replace` is intended for animations layered on top of others, not for the first animations applied. */ + replace, + /** Transitions from the current value to the current value plus the timeline value. No change is made before the first key + * (the current value is kept until the first key). + * + * `add` is intended for animations layered on top of others, not for the first animations applied. Properties + * keyed by additive animations must be set manually or by another animation before applying the additive animations, else + * the property values will increase continually. */ + add +} + +/** Indicates whether a timeline's `alpha` is mixing out over time toward 0 (the setup or current pose value) or + * mixing in toward 1 (the timeline's value). + * + * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */ +export enum MixDirection { + mixIn, + mixOut +} + +export enum TimelineType { + rotate, + translate, + scale, + shear, + attachment, + color, + deform, + event, + drawOrder, + ikConstraint, + transformConstraint, + pathConstraintPosition, + pathConstraintSpacing, + pathConstraintMix, + twoColor +} + +/** The base class for timelines that use interpolation between key frame values. */ +export abstract class CurveTimeline implements Timeline { + static LINEAR = 0; + static STEPPED = 1; + static BEZIER = 2; + static BEZIER_SIZE = 10 * 2 - 1; + + private curves: ArrayLike; // type, x, y, ... + + abstract getPropertyId(): number; + + constructor(frameCount: number) { + if (frameCount <= 0) throw new Error("frameCount must be > 0: " + frameCount); + this.curves = Utils.newFloatArray((frameCount - 1) * CurveTimeline.BEZIER_SIZE); + } + + /** The number of key frames for this timeline. */ + getFrameCount() { + return this.curves.length / CurveTimeline.BEZIER_SIZE + 1; + } + + /** Sets the specified key frame to linear interpolation. */ + setLinear(frameIndex: number) { + this.curves[frameIndex * CurveTimeline.BEZIER_SIZE] = CurveTimeline.LINEAR; + } + + /** Sets the specified key frame to stepped interpolation. */ + setStepped(frameIndex: number) { + this.curves[frameIndex * CurveTimeline.BEZIER_SIZE] = CurveTimeline.STEPPED; + } + + /** Returns the interpolation type for the specified key frame. + * @returns Linear is 0, stepped is 1, Bezier is 2. */ + getCurveType(frameIndex: number): number { + const index = frameIndex * CurveTimeline.BEZIER_SIZE; + if (index == this.curves.length) return CurveTimeline.LINEAR; + const type = this.curves[index]; + if (type == CurveTimeline.LINEAR) return CurveTimeline.LINEAR; + if (type == CurveTimeline.STEPPED) return CurveTimeline.STEPPED; + return CurveTimeline.BEZIER; + } + + /** Sets the specified key frame to Bezier interpolation. `cx1` and `cx2` are from 0 to 1, + * representing the percent of time between the two key frames. `cy1` and `cy2` are the percent of the + * difference between the key frame's values. */ + setCurve(frameIndex: number, cx1: number, cy1: number, cx2: number, cy2: number) { + const tmpx = (-cx1 * 2 + cx2) * 0.03, + tmpy = (-cy1 * 2 + cy2) * 0.03; + const dddfx = ((cx1 - cx2) * 3 + 1) * 0.006, + dddfy = ((cy1 - cy2) * 3 + 1) * 0.006; + let ddfx = tmpx * 2 + dddfx, + ddfy = tmpy * 2 + dddfy; + let dfx = cx1 * 0.3 + tmpx + dddfx * 0.16666667, + dfy = cy1 * 0.3 + tmpy + dddfy * 0.16666667; + + let i = frameIndex * CurveTimeline.BEZIER_SIZE; + const curves = this.curves; + curves[i++] = CurveTimeline.BEZIER; + + let x = dfx, + y = dfy; + for (let n = i + CurveTimeline.BEZIER_SIZE - 1; i < n; i += 2) { + curves[i] = x; + curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + } + + /** Returns the interpolated percentage for the specified key frame and linear percentage. */ + getCurvePercent(frameIndex: number, percent: number) { + percent = MathUtils.clamp(percent, 0, 1); + const curves = this.curves; + let i = frameIndex * CurveTimeline.BEZIER_SIZE; + const type = curves[i]; + if (type == CurveTimeline.LINEAR) return percent; + if (type == CurveTimeline.STEPPED) return 0; + i++; + let x = 0; + for (let start = i, n = i + CurveTimeline.BEZIER_SIZE - 1; i < n; i += 2) { + x = curves[i]; + if (x >= percent) { + let prevX: number, prevY: number; + if (i == start) { + prevX = 0; + prevY = 0; + } else { + prevX = curves[i - 2]; + prevY = curves[i - 1]; + } + return prevY + ((curves[i + 1] - prevY) * (percent - prevX)) / (x - prevX); + } + } + const y = curves[i - 1]; + return y + ((1 - y) * (percent - x)) / (1 - x); // Last point is 1,1. + } + + abstract apply( + skeleton: Skeleton, + lastTime: number, + time: number, + events: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ): void; +} + +/** Changes a bone's local {@link Bone#rotation}. */ +export class RotateTimeline extends CurveTimeline { + static ENTRIES = 2; + static PREV_TIME = -2; + static PREV_ROTATION = -1; + static ROTATION = 1; + + /** The index of the bone in {@link Skeleton#bones} that will be changed. */ + boneIndex: number; + + /** The time in seconds and rotation in degrees for each key frame. */ + frames: ArrayLike; // time, degrees, ... + + constructor(frameCount: number) { + super(frameCount); + this.frames = Utils.newFloatArray(frameCount << 1); + } + + getPropertyId() { + return (TimelineType.rotate << 24) + this.boneIndex; + } + + /** Sets the time and angle of the specified keyframe. */ + setFrame(frameIndex: number, time: number, degrees: number) { + frameIndex <<= 1; + this.frames[frameIndex] = time; + this.frames[frameIndex + RotateTimeline.ROTATION] = degrees; + } + + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + events: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const frames = this.frames; + + const bone = skeleton.bones[this.boneIndex]; + if (!bone.active) return; + if (time < frames[0]) { + switch (blend) { + case MixBlend.setup: + bone.rotation = bone.data.rotation; + return; + case MixBlend.first: + const r = bone.data.rotation - bone.rotation; + bone.rotation += (r - (16384 - ((16384.499999999996 - r / 360) | 0)) * 360) * alpha; + } + return; + } + + if (time >= frames[frames.length - RotateTimeline.ENTRIES]) { + // Time is after last frame. + let r = frames[frames.length + RotateTimeline.PREV_ROTATION]; + switch (blend) { + case MixBlend.setup: + bone.rotation = bone.data.rotation + r * alpha; + break; + case MixBlend.first: + case MixBlend.replace: + r += bone.data.rotation - bone.rotation; + r -= (16384 - ((16384.499999999996 - r / 360) | 0)) * 360; // Wrap within -180 and 180. + case MixBlend.add: + bone.rotation += r * alpha; + } + return; + } + + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time, RotateTimeline.ENTRIES); + const prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; + const frameTime = frames[frame]; + const percent = this.getCurvePercent( + (frame >> 1) - 1, + 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime) + ); + + let r = frames[frame + RotateTimeline.ROTATION] - prevRotation; + r = prevRotation + (r - (16384 - ((16384.499999999996 - r / 360) | 0)) * 360) * percent; + switch (blend) { + case MixBlend.setup: + bone.rotation = bone.data.rotation + (r - (16384 - ((16384.499999999996 - r / 360) | 0)) * 360) * alpha; + break; + case MixBlend.first: + case MixBlend.replace: + r += bone.data.rotation - bone.rotation; + case MixBlend.add: + bone.rotation += (r - (16384 - ((16384.499999999996 - r / 360) | 0)) * 360) * alpha; + } + } +} + +/** Changes a bone's local {@link Bone#x} and {@link Bone#y}. */ +export class TranslateTimeline extends CurveTimeline { + static ENTRIES = 3; + static PREV_TIME = -3; + static PREV_X = -2; + static PREV_Y = -1; + static X = 1; + static Y = 2; + + /** The index of the bone in {@link Skeleton#bones} that will be changed. */ + boneIndex: number; + + /** The time in seconds, x, and y values for each key frame. */ + frames: ArrayLike; // time, x, y, ... + + constructor(frameCount: number) { + super(frameCount); + this.frames = Utils.newFloatArray(frameCount * TranslateTimeline.ENTRIES); + } + + getPropertyId() { + return (TimelineType.translate << 24) + this.boneIndex; + } + + /** Sets the time in seconds, x, and y values for the specified key frame. */ + setFrame(frameIndex: number, time: number, x: number, y: number) { + frameIndex *= TranslateTimeline.ENTRIES; + this.frames[frameIndex] = time; + this.frames[frameIndex + TranslateTimeline.X] = x; + this.frames[frameIndex + TranslateTimeline.Y] = y; + } + + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + events: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const frames = this.frames; + + const bone = skeleton.bones[this.boneIndex]; + if (!bone.active) return; + if (time < frames[0]) { + switch (blend) { + case MixBlend.setup: + bone.x = bone.data.x; + bone.y = bone.data.y; + return; + case MixBlend.first: + bone.x += (bone.data.x - bone.x) * alpha; + bone.y += (bone.data.y - bone.y) * alpha; + } + return; + } + + let x = 0, + y = 0; + if (time >= frames[frames.length - TranslateTimeline.ENTRIES]) { + // Time is after last frame. + x = frames[frames.length + TranslateTimeline.PREV_X]; + y = frames[frames.length + TranslateTimeline.PREV_Y]; + } else { + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time, TranslateTimeline.ENTRIES); + x = frames[frame + TranslateTimeline.PREV_X]; + y = frames[frame + TranslateTimeline.PREV_Y]; + const frameTime = frames[frame]; + const percent = this.getCurvePercent( + frame / TranslateTimeline.ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + TranslateTimeline.PREV_TIME] - frameTime) + ); + + x += (frames[frame + TranslateTimeline.X] - x) * percent; + y += (frames[frame + TranslateTimeline.Y] - y) * percent; + } + switch (blend) { + case MixBlend.setup: + bone.x = bone.data.x + x * alpha; + bone.y = bone.data.y + y * alpha; + break; + case MixBlend.first: + case MixBlend.replace: + bone.x += (bone.data.x + x - bone.x) * alpha; + bone.y += (bone.data.y + y - bone.y) * alpha; + break; + case MixBlend.add: + bone.x += x * alpha; + bone.y += y * alpha; + } + } +} + +/** Changes a bone's local {@link Bone#scaleX)} and {@link Bone#scaleY}. */ +export class ScaleTimeline extends TranslateTimeline { + constructor(frameCount: number) { + super(frameCount); + } + + override getPropertyId() { + return (TimelineType.scale << 24) + this.boneIndex; + } + + override apply( + skeleton: Skeleton, + lastTime: number, + time: number, + events: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const frames = this.frames; + + const bone = skeleton.bones[this.boneIndex]; + if (!bone.active) return; + if (time < frames[0]) { + switch (blend) { + case MixBlend.setup: + bone.scaleX = bone.data.scaleX; + bone.scaleY = bone.data.scaleY; + return; + case MixBlend.first: + bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; + } + return; + } + + let x = 0, + y = 0; + if (time >= frames[frames.length - ScaleTimeline.ENTRIES]) { + // Time is after last frame. + x = frames[frames.length + ScaleTimeline.PREV_X] * bone.data.scaleX; + y = frames[frames.length + ScaleTimeline.PREV_Y] * bone.data.scaleY; + } else { + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time, ScaleTimeline.ENTRIES); + x = frames[frame + ScaleTimeline.PREV_X]; + y = frames[frame + ScaleTimeline.PREV_Y]; + const frameTime = frames[frame]; + const percent = this.getCurvePercent( + frame / ScaleTimeline.ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + ScaleTimeline.PREV_TIME] - frameTime) + ); + + x = (x + (frames[frame + ScaleTimeline.X] - x) * percent) * bone.data.scaleX; + y = (y + (frames[frame + ScaleTimeline.Y] - y) * percent) * bone.data.scaleY; + } + if (alpha == 1) { + if (blend == MixBlend.add) { + bone.scaleX += x - bone.data.scaleX; + bone.scaleY += y - bone.data.scaleY; + } else { + bone.scaleX = x; + bone.scaleY = y; + } + } else { + let bx = 0, + by = 0; + if (direction == MixDirection.mixOut) { + switch (blend) { + case MixBlend.setup: + bx = bone.data.scaleX; + by = bone.data.scaleY; + bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha; + bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha; + break; + case MixBlend.first: + case MixBlend.replace: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bx) * alpha; + bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - by) * alpha; + break; + case MixBlend.add: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.abs(x) * MathUtils.signum(bx) - bone.data.scaleX) * alpha; + bone.scaleY = by + (Math.abs(y) * MathUtils.signum(by) - bone.data.scaleY) * alpha; + } + } else { + switch (blend) { + case MixBlend.setup: + bx = Math.abs(bone.data.scaleX) * MathUtils.signum(x); + by = Math.abs(bone.data.scaleY) * MathUtils.signum(y); + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.first: + case MixBlend.replace: + bx = Math.abs(bone.scaleX) * MathUtils.signum(x); + by = Math.abs(bone.scaleY) * MathUtils.signum(y); + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.add: + bx = MathUtils.signum(x); + by = MathUtils.signum(y); + bone.scaleX = Math.abs(bone.scaleX) * bx + (x - Math.abs(bone.data.scaleX) * bx) * alpha; + bone.scaleY = Math.abs(bone.scaleY) * by + (y - Math.abs(bone.data.scaleY) * by) * alpha; + } + } + } + } +} + +/** Changes a bone's local {@link Bone#shearX} and {@link Bone#shearY}. */ +export class ShearTimeline extends TranslateTimeline { + constructor(frameCount: number) { + super(frameCount); + } + + override getPropertyId() { + return (TimelineType.shear << 24) + this.boneIndex; + } + + override apply( + skeleton: Skeleton, + lastTime: number, + time: number, + events: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const frames = this.frames; + + const bone = skeleton.bones[this.boneIndex]; + if (!bone.active) return; + if (time < frames[0]) { + switch (blend) { + case MixBlend.setup: + bone.shearX = bone.data.shearX; + bone.shearY = bone.data.shearY; + return; + case MixBlend.first: + bone.shearX += (bone.data.shearX - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY - bone.shearY) * alpha; + } + return; + } + + let x = 0, + y = 0; + if (time >= frames[frames.length - ShearTimeline.ENTRIES]) { + // Time is after last frame. + x = frames[frames.length + ShearTimeline.PREV_X]; + y = frames[frames.length + ShearTimeline.PREV_Y]; + } else { + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time, ShearTimeline.ENTRIES); + x = frames[frame + ShearTimeline.PREV_X]; + y = frames[frame + ShearTimeline.PREV_Y]; + const frameTime = frames[frame]; + const percent = this.getCurvePercent( + frame / ShearTimeline.ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + ShearTimeline.PREV_TIME] - frameTime) + ); + + x = x + (frames[frame + ShearTimeline.X] - x) * percent; + y = y + (frames[frame + ShearTimeline.Y] - y) * percent; + } + switch (blend) { + case MixBlend.setup: + bone.shearX = bone.data.shearX + x * alpha; + bone.shearY = bone.data.shearY + y * alpha; + break; + case MixBlend.first: + case MixBlend.replace: + bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; + break; + case MixBlend.add: + bone.shearX += x * alpha; + bone.shearY += y * alpha; + } + } +} + +/** Changes a slot's {@link Slot#color}. */ +export class ColorTimeline extends CurveTimeline { + static ENTRIES = 5; + static PREV_TIME = -5; + static PREV_R = -4; + static PREV_G = -3; + static PREV_B = -2; + static PREV_A = -1; + static R = 1; + static G = 2; + static B = 3; + static A = 4; + + /** The index of the slot in {@link Skeleton#slots} that will be changed. */ + slotIndex: number; + + /** The time in seconds, red, green, blue, and alpha values for each key frame. */ + frames: ArrayLike; // time, r, g, b, a, ... + + constructor(frameCount: number) { + super(frameCount); + this.frames = Utils.newFloatArray(frameCount * ColorTimeline.ENTRIES); + } + + getPropertyId() { + return (TimelineType.color << 24) + this.slotIndex; + } + + /** Sets the time in seconds, red, green, blue, and alpha for the specified key frame. */ + setFrame(frameIndex: number, time: number, r: number, g: number, b: number, a: number) { + frameIndex *= ColorTimeline.ENTRIES; + this.frames[frameIndex] = time; + this.frames[frameIndex + ColorTimeline.R] = r; + this.frames[frameIndex + ColorTimeline.G] = g; + this.frames[frameIndex + ColorTimeline.B] = b; + this.frames[frameIndex + ColorTimeline.A] = a; + } + + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + events: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const slot = skeleton.slots[this.slotIndex]; + if (!slot.bone.active) return; + const frames = this.frames; + if (time < frames[0]) { + switch (blend) { + case MixBlend.setup: + slot.color.setFromColor(slot.data.color); + return; + case MixBlend.first: + const color = slot.color, + setup = slot.data.color; + color.add( + (setup.r - color.r) * alpha, + (setup.g - color.g) * alpha, + (setup.b - color.b) * alpha, + (setup.a - color.a) * alpha + ); + } + return; + } + + let r = 0, + g = 0, + b = 0, + a = 0; + if (time >= frames[frames.length - ColorTimeline.ENTRIES]) { + // Time is after last frame. + const i = frames.length; + r = frames[i + ColorTimeline.PREV_R]; + g = frames[i + ColorTimeline.PREV_G]; + b = frames[i + ColorTimeline.PREV_B]; + a = frames[i + ColorTimeline.PREV_A]; + } else { + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time, ColorTimeline.ENTRIES); + r = frames[frame + ColorTimeline.PREV_R]; + g = frames[frame + ColorTimeline.PREV_G]; + b = frames[frame + ColorTimeline.PREV_B]; + a = frames[frame + ColorTimeline.PREV_A]; + const frameTime = frames[frame]; + const percent = this.getCurvePercent( + frame / ColorTimeline.ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + ColorTimeline.PREV_TIME] - frameTime) + ); + + r += (frames[frame + ColorTimeline.R] - r) * percent; + g += (frames[frame + ColorTimeline.G] - g) * percent; + b += (frames[frame + ColorTimeline.B] - b) * percent; + a += (frames[frame + ColorTimeline.A] - a) * percent; + } + if (alpha == 1) slot.color.set(r, g, b, a); + else { + const color = slot.color; + if (blend == MixBlend.setup) color.setFromColor(slot.data.color); + color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha); + } + } +} + +/** Changes a slot's {@link Slot#color} and {@link Slot#darkColor} for two color tinting. */ +export class TwoColorTimeline extends CurveTimeline { + static ENTRIES = 8; + static PREV_TIME = -8; + static PREV_R = -7; + static PREV_G = -6; + static PREV_B = -5; + static PREV_A = -4; + static PREV_R2 = -3; + static PREV_G2 = -2; + static PREV_B2 = -1; + static R = 1; + static G = 2; + static B = 3; + static A = 4; + static R2 = 5; + static G2 = 6; + static B2 = 7; + + /** The index of the slot in {@link Skeleton#slots()} that will be changed. The {@link Slot#darkColor()} must not be + * null. */ + slotIndex: number; + + /** The time in seconds, red, green, blue, and alpha values of the color, red, green, blue of the dark color, for each key frame. */ + frames: ArrayLike; // time, r, g, b, a, r2, g2, b2, ... + + constructor(frameCount: number) { + super(frameCount); + this.frames = Utils.newFloatArray(frameCount * TwoColorTimeline.ENTRIES); + } + + getPropertyId() { + return (TimelineType.twoColor << 24) + this.slotIndex; + } + + /** Sets the time in seconds, light, and dark colors for the specified key frame. */ + setFrame( + frameIndex: number, + time: number, + r: number, + g: number, + b: number, + a: number, + r2: number, + g2: number, + b2: number + ) { + frameIndex *= TwoColorTimeline.ENTRIES; + this.frames[frameIndex] = time; + this.frames[frameIndex + TwoColorTimeline.R] = r; + this.frames[frameIndex + TwoColorTimeline.G] = g; + this.frames[frameIndex + TwoColorTimeline.B] = b; + this.frames[frameIndex + TwoColorTimeline.A] = a; + this.frames[frameIndex + TwoColorTimeline.R2] = r2; + this.frames[frameIndex + TwoColorTimeline.G2] = g2; + this.frames[frameIndex + TwoColorTimeline.B2] = b2; + } + + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + events: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const slot = skeleton.slots[this.slotIndex]; + if (!slot.bone.active) return; + const frames = this.frames; + if (time < frames[0]) { + switch (blend) { + case MixBlend.setup: + slot.color.setFromColor(slot.data.color); + slot.darkColor.setFromColor(slot.data.darkColor); + return; + case MixBlend.first: + const light = slot.color, + dark = slot.darkColor, + setupLight = slot.data.color, + setupDark = slot.data.darkColor; + light.add( + (setupLight.r - light.r) * alpha, + (setupLight.g - light.g) * alpha, + (setupLight.b - light.b) * alpha, + (setupLight.a - light.a) * alpha + ); + dark.add((setupDark.r - dark.r) * alpha, (setupDark.g - dark.g) * alpha, (setupDark.b - dark.b) * alpha, 0); + } + return; + } + + let r = 0, + g = 0, + b = 0, + a = 0, + r2 = 0, + g2 = 0, + b2 = 0; + if (time >= frames[frames.length - TwoColorTimeline.ENTRIES]) { + // Time is after last frame. + const i = frames.length; + r = frames[i + TwoColorTimeline.PREV_R]; + g = frames[i + TwoColorTimeline.PREV_G]; + b = frames[i + TwoColorTimeline.PREV_B]; + a = frames[i + TwoColorTimeline.PREV_A]; + r2 = frames[i + TwoColorTimeline.PREV_R2]; + g2 = frames[i + TwoColorTimeline.PREV_G2]; + b2 = frames[i + TwoColorTimeline.PREV_B2]; + } else { + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time, TwoColorTimeline.ENTRIES); + r = frames[frame + TwoColorTimeline.PREV_R]; + g = frames[frame + TwoColorTimeline.PREV_G]; + b = frames[frame + TwoColorTimeline.PREV_B]; + a = frames[frame + TwoColorTimeline.PREV_A]; + r2 = frames[frame + TwoColorTimeline.PREV_R2]; + g2 = frames[frame + TwoColorTimeline.PREV_G2]; + b2 = frames[frame + TwoColorTimeline.PREV_B2]; + const frameTime = frames[frame]; + const percent = this.getCurvePercent( + frame / TwoColorTimeline.ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + TwoColorTimeline.PREV_TIME] - frameTime) + ); + + r += (frames[frame + TwoColorTimeline.R] - r) * percent; + g += (frames[frame + TwoColorTimeline.G] - g) * percent; + b += (frames[frame + TwoColorTimeline.B] - b) * percent; + a += (frames[frame + TwoColorTimeline.A] - a) * percent; + r2 += (frames[frame + TwoColorTimeline.R2] - r2) * percent; + g2 += (frames[frame + TwoColorTimeline.G2] - g2) * percent; + b2 += (frames[frame + TwoColorTimeline.B2] - b2) * percent; + } + if (alpha == 1) { + slot.color.set(r, g, b, a); + slot.darkColor.set(r2, g2, b2, 1); + } else { + const light = slot.color, + dark = slot.darkColor; + if (blend == MixBlend.setup) { + light.setFromColor(slot.data.color); + dark.setFromColor(slot.data.darkColor); + } + light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha); + dark.add((r2 - dark.r) * alpha, (g2 - dark.g) * alpha, (b2 - dark.b) * alpha, 0); + } + } +} + +/** Changes a slot's {@link Slot#attachment}. */ +export class AttachmentTimeline implements Timeline { + /** The index of the slot in {@link Skeleton#slots} that will be changed. */ + slotIndex: number; + + /** The time in seconds for each key frame. */ + frames: ArrayLike; // time, ... + + /** The attachment name for each key frame. May contain null values to clear the attachment. */ + attachmentNames: Array; + + constructor(frameCount: number) { + this.frames = Utils.newFloatArray(frameCount); + this.attachmentNames = new Array(frameCount); + } + + getPropertyId() { + return (TimelineType.attachment << 24) + this.slotIndex; + } + + /** The number of key frames for this timeline. */ + getFrameCount() { + return this.frames.length; + } + + /** Sets the time in seconds and the attachment name for the specified key frame. */ + setFrame(frameIndex: number, time: number, attachmentName: string) { + this.frames[frameIndex] = time; + this.attachmentNames[frameIndex] = attachmentName; + } + + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + events: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const slot = skeleton.slots[this.slotIndex]; + if (!slot.bone.active) return; + if (direction == MixDirection.mixOut) { + if (blend == MixBlend.setup) this.setAttachment(skeleton, slot, slot.data.attachmentName); + return; + } + + const frames = this.frames; + if (time < frames[0]) { + if (blend == MixBlend.setup || blend == MixBlend.first) + this.setAttachment(skeleton, slot, slot.data.attachmentName); + return; + } + + let frameIndex = 0; + if (time >= frames[frames.length - 1]) + // Time is after last frame. + frameIndex = frames.length - 1; + else frameIndex = Animation.binarySearch(frames, time, 1) - 1; + + const attachmentName = this.attachmentNames[frameIndex]; + skeleton.slots[this.slotIndex].setAttachment( + attachmentName == null ? null : skeleton.getAttachment(this.slotIndex, attachmentName) + ); + } + + setAttachment(skeleton: Skeleton, slot: Slot, attachmentName: string) { + slot.attachment = attachmentName == null ? null : skeleton.getAttachment(this.slotIndex, attachmentName); + } +} + +let zeros: ArrayLike = null; + +/** Changes a slot's {@link Slot#deform} to deform a {@link VertexAttachment}. */ +export class DeformTimeline extends CurveTimeline { + /** The index of the slot in {@link Skeleton#getSlots()} that will be changed. */ + slotIndex: number; + + /** The attachment that will be deformed. */ + attachment: VertexAttachment; + + /** The time in seconds for each key frame. */ + frames: ArrayLike; // time, ... + + /** The vertices for each key frame. */ + frameVertices: Array>; + + constructor(frameCount: number) { + super(frameCount); + this.frames = Utils.newFloatArray(frameCount); + this.frameVertices = new Array>(frameCount); + if (zeros == null) zeros = Utils.newFloatArray(64); + } + + getPropertyId() { + return (TimelineType.deform << 27) + +this.attachment.id + this.slotIndex; + } + + /** Sets the time in seconds and the vertices for the specified key frame. + * @param vertices Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. */ + setFrame(frameIndex: number, time: number, vertices: ArrayLike) { + this.frames[frameIndex] = time; + this.frameVertices[frameIndex] = vertices; + } + + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + firedEvents: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const slot: Slot = skeleton.slots[this.slotIndex]; + if (!slot.bone.active) return; + const slotAttachment: Attachment = slot.getAttachment(); + if ( + !(slotAttachment instanceof VertexAttachment) || + !((slotAttachment).deformAttachment == this.attachment) + ) + return; + + const deformArray: Array = slot.deform; + if (deformArray.length == 0) blend = MixBlend.setup; + + const frameVertices = this.frameVertices; + const vertexCount = frameVertices[0].length; + + const frames = this.frames; + if (time < frames[0]) { + const vertexAttachment = slotAttachment; + switch (blend) { + case MixBlend.setup: + deformArray.length = 0; + return; + case MixBlend.first: + if (alpha == 1) { + deformArray.length = 0; + break; + } + const deform: Array = Utils.setArraySize(deformArray, vertexCount); + if (vertexAttachment.bones == null) { + // Unweighted vertex positions. + const setupVertices = vertexAttachment.vertices; + for (var i = 0; i < vertexCount; i++) deform[i] += (setupVertices[i] - deform[i]) * alpha; + } else { + // Weighted deform offsets. + alpha = 1 - alpha; + for (var i = 0; i < vertexCount; i++) deform[i] *= alpha; + } + } + return; + } + + const deform: Array = Utils.setArraySize(deformArray, vertexCount); + if (time >= frames[frames.length - 1]) { + // Time is after last frame. + const lastVertices = frameVertices[frames.length - 1]; + if (alpha == 1) { + if (blend == MixBlend.add) { + const vertexAttachment = slotAttachment as VertexAttachment; + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, with alpha. + const setupVertices = vertexAttachment.vertices; + for (let i = 0; i < vertexCount; i++) { + deform[i] += lastVertices[i] - setupVertices[i]; + } + } else { + // Weighted deform offsets, with alpha. + for (let i = 0; i < vertexCount; i++) deform[i] += lastVertices[i]; + } + } else { + Utils.arrayCopy(lastVertices, 0, deform, 0, vertexCount); + } + } else { + switch (blend) { + case MixBlend.setup: { + const vertexAttachment = slotAttachment as VertexAttachment; + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, with alpha. + const setupVertices = vertexAttachment.vertices; + for (let i = 0; i < vertexCount; i++) { + const setup = setupVertices[i]; + deform[i] = setup + (lastVertices[i] - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (let i = 0; i < vertexCount; i++) deform[i] = lastVertices[i] * alpha; + } + break; + } + case MixBlend.first: + case MixBlend.replace: + for (let i = 0; i < vertexCount; i++) deform[i] += (lastVertices[i] - deform[i]) * alpha; + break; + case MixBlend.add: + const vertexAttachment = slotAttachment as VertexAttachment; + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, with alpha. + const setupVertices = vertexAttachment.vertices; + for (let i = 0; i < vertexCount; i++) { + deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (let i = 0; i < vertexCount; i++) deform[i] += lastVertices[i] * alpha; + } + } + } + return; + } + + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time); + const prevVertices = frameVertices[frame - 1]; + const nextVertices = frameVertices[frame]; + const frameTime = frames[frame]; + const percent = this.getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); + + if (alpha == 1) { + if (blend == MixBlend.add) { + const vertexAttachment = slotAttachment as VertexAttachment; + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, with alpha. + const setupVertices = vertexAttachment.vertices; + for (let i = 0; i < vertexCount; i++) { + const prev = prevVertices[i]; + deform[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i]; + } + } else { + // Weighted deform offsets, with alpha. + for (let i = 0; i < vertexCount; i++) { + const prev = prevVertices[i]; + deform[i] += prev + (nextVertices[i] - prev) * percent; + } + } + } else { + for (let i = 0; i < vertexCount; i++) { + const prev = prevVertices[i]; + deform[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } else { + switch (blend) { + case MixBlend.setup: { + const vertexAttachment = slotAttachment as VertexAttachment; + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, with alpha. + const setupVertices = vertexAttachment.vertices; + for (let i = 0; i < vertexCount; i++) { + const prev = prevVertices[i], + setup = setupVertices[i]; + deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (let i = 0; i < vertexCount; i++) { + const prev = prevVertices[i]; + deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + case MixBlend.first: + case MixBlend.replace: + for (let i = 0; i < vertexCount; i++) { + const prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; + } + break; + case MixBlend.add: + const vertexAttachment = slotAttachment as VertexAttachment; + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, with alpha. + const setupVertices = vertexAttachment.vertices; + for (let i = 0; i < vertexCount; i++) { + const prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (let i = 0; i < vertexCount; i++) { + const prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + } + } + } +} + +/** Fires an {@link Event} when specific animation times are reached. */ +export class EventTimeline implements Timeline { + /** The time in seconds for each key frame. */ + frames: ArrayLike; // time, ... + + /** The event for each key frame. */ + events: Array; + + constructor(frameCount: number) { + this.frames = Utils.newFloatArray(frameCount); + this.events = new Array(frameCount); + } + + getPropertyId() { + return TimelineType.event << 24; + } + + /** The number of key frames for this timeline. */ + getFrameCount() { + return this.frames.length; + } + + /** Sets the time in seconds and the event for the specified key frame. */ + setFrame(frameIndex: number, event: Event) { + this.frames[frameIndex] = event.time; + this.events[frameIndex] = event; + } + + /** Fires events for frames > `lastTime` and <= `time`. */ + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + firedEvents: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + if (firedEvents == null) return; + const frames = this.frames; + const frameCount = this.frames.length; + + if (lastTime > time) { + // Fire events after last time for looped animations. + this.apply(skeleton, lastTime, Number.MAX_VALUE, firedEvents, alpha, blend, direction); + lastTime = -1; + } else if (lastTime >= frames[frameCount - 1]) + // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + let frame = 0; + if (lastTime < frames[0]) frame = 0; + else { + frame = Animation.binarySearch(frames, lastTime); + const frameTime = frames[frame]; + while (frame > 0) { + // Fire multiple events with the same frame. + if (frames[frame - 1] != frameTime) break; + frame--; + } + } + for (; frame < frameCount && time >= frames[frame]; frame++) firedEvents.push(this.events[frame]); + } +} + +/** Changes a skeleton's {@link Skeleton#drawOrder}. */ +export class DrawOrderTimeline implements Timeline { + /** The time in seconds for each key frame. */ + frames: ArrayLike; // time, ... + + /** The draw order for each key frame. See {@link #setFrame(int, float, int[])}. */ + drawOrders: Array>; + + constructor(frameCount: number) { + this.frames = Utils.newFloatArray(frameCount); + this.drawOrders = new Array>(frameCount); + } + + getPropertyId() { + return TimelineType.drawOrder << 24; + } + + /** The number of key frames for this timeline. */ + getFrameCount() { + return this.frames.length; + } + + /** Sets the time in seconds and the draw order for the specified key frame. + * @param drawOrder For each slot in {@link Skeleton#slots}, the index of the new draw order. May be null to use setup pose + * draw order. */ + setFrame(frameIndex: number, time: number, drawOrder: Array) { + this.frames[frameIndex] = time; + this.drawOrders[frameIndex] = drawOrder; + } + + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + firedEvents: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const drawOrder: Array = skeleton.drawOrder; + const slots: Array = skeleton.slots; + if (direction == MixDirection.mixOut) { + if (blend == MixBlend.setup) Utils.arrayCopy(skeleton.slots, 0, skeleton.drawOrder, 0, skeleton.slots.length); + return; + } + + const frames = this.frames; + if (time < frames[0]) { + if (blend == MixBlend.setup || blend == MixBlend.first) + Utils.arrayCopy(skeleton.slots, 0, skeleton.drawOrder, 0, skeleton.slots.length); + return; + } + + let frame = 0; + if (time >= frames[frames.length - 1]) + // Time is after last frame. + frame = frames.length - 1; + else frame = Animation.binarySearch(frames, time) - 1; + + const drawOrderToSetupIndex = this.drawOrders[frame]; + if (drawOrderToSetupIndex == null) Utils.arrayCopy(slots, 0, drawOrder, 0, slots.length); + else { + for (let i = 0, n = drawOrderToSetupIndex.length; i < n; i++) drawOrder[i] = slots[drawOrderToSetupIndex[i]]; + } + } +} + +/** Changes an IK constraint's {@link IkConstraint#mix}, {@link IkConstraint#softness}, + * {@link IkConstraint#bendDirection}, {@link IkConstraint#stretch}, and {@link IkConstraint#compress}. */ +export class IkConstraintTimeline extends CurveTimeline { + static ENTRIES = 6; + static PREV_TIME = -6; + static PREV_MIX = -5; + static PREV_SOFTNESS = -4; + static PREV_BEND_DIRECTION = -3; + static PREV_COMPRESS = -2; + static PREV_STRETCH = -1; + static MIX = 1; + static SOFTNESS = 2; + static BEND_DIRECTION = 3; + static COMPRESS = 4; + static STRETCH = 5; + + /** The index of the IK constraint slot in {@link Skeleton#ikConstraints} that will be changed. */ + ikConstraintIndex: number; + + /** The time in seconds, mix, softness, bend direction, compress, and stretch for each key frame. */ + frames: ArrayLike; // time, mix, softness, bendDirection, compress, stretch, ... + + constructor(frameCount: number) { + super(frameCount); + this.frames = Utils.newFloatArray(frameCount * IkConstraintTimeline.ENTRIES); + } + + getPropertyId() { + return (TimelineType.ikConstraint << 24) + this.ikConstraintIndex; + } + + /** Sets the time in seconds, mix, softness, bend direction, compress, and stretch for the specified key frame. */ + setFrame( + frameIndex: number, + time: number, + mix: number, + softness: number, + bendDirection: number, + compress: boolean, + stretch: boolean + ) { + frameIndex *= IkConstraintTimeline.ENTRIES; + this.frames[frameIndex] = time; + this.frames[frameIndex + IkConstraintTimeline.MIX] = mix; + this.frames[frameIndex + IkConstraintTimeline.SOFTNESS] = softness; + this.frames[frameIndex + IkConstraintTimeline.BEND_DIRECTION] = bendDirection; + this.frames[frameIndex + IkConstraintTimeline.COMPRESS] = compress ? 1 : 0; + this.frames[frameIndex + IkConstraintTimeline.STRETCH] = stretch ? 1 : 0; + } + + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + firedEvents: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const frames = this.frames; + const constraint: IkConstraint = skeleton.ikConstraints[this.ikConstraintIndex]; + if (!constraint.active) return; + if (time < frames[0]) { + switch (blend) { + case MixBlend.setup: + constraint.mix = constraint.data.mix; + constraint.softness = constraint.data.softness; + constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; + constraint.stretch = constraint.data.stretch; + return; + case MixBlend.first: + constraint.mix += (constraint.data.mix - constraint.mix) * alpha; + constraint.softness += (constraint.data.softness - constraint.softness) * alpha; + constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; + constraint.stretch = constraint.data.stretch; + } + return; + } + + if (time >= frames[frames.length - IkConstraintTimeline.ENTRIES]) { + // Time is after last frame. + if (blend == MixBlend.setup) { + constraint.mix = + constraint.data.mix + (frames[frames.length + IkConstraintTimeline.PREV_MIX] - constraint.data.mix) * alpha; + constraint.softness = + constraint.data.softness + + (frames[frames.length + IkConstraintTimeline.PREV_SOFTNESS] - constraint.data.softness) * alpha; + if (direction == MixDirection.mixOut) { + constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; + constraint.stretch = constraint.data.stretch; + } else { + constraint.bendDirection = frames[frames.length + IkConstraintTimeline.PREV_BEND_DIRECTION]; + constraint.compress = frames[frames.length + IkConstraintTimeline.PREV_COMPRESS] != 0; + constraint.stretch = frames[frames.length + IkConstraintTimeline.PREV_STRETCH] != 0; + } + } else { + constraint.mix += (frames[frames.length + IkConstraintTimeline.PREV_MIX] - constraint.mix) * alpha; + constraint.softness += + (frames[frames.length + IkConstraintTimeline.PREV_SOFTNESS] - constraint.softness) * alpha; + if (direction == MixDirection.mixIn) { + constraint.bendDirection = frames[frames.length + IkConstraintTimeline.PREV_BEND_DIRECTION]; + constraint.compress = frames[frames.length + IkConstraintTimeline.PREV_COMPRESS] != 0; + constraint.stretch = frames[frames.length + IkConstraintTimeline.PREV_STRETCH] != 0; + } + } + return; + } + + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time, IkConstraintTimeline.ENTRIES); + const mix = frames[frame + IkConstraintTimeline.PREV_MIX]; + const softness = frames[frame + IkConstraintTimeline.PREV_SOFTNESS]; + const frameTime = frames[frame]; + const percent = this.getCurvePercent( + frame / IkConstraintTimeline.ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + IkConstraintTimeline.PREV_TIME] - frameTime) + ); + + if (blend == MixBlend.setup) { + constraint.mix = + constraint.data.mix + + (mix + (frames[frame + IkConstraintTimeline.MIX] - mix) * percent - constraint.data.mix) * alpha; + constraint.softness = + constraint.data.softness + + (softness + (frames[frame + IkConstraintTimeline.SOFTNESS] - softness) * percent - constraint.data.softness) * + alpha; + if (direction == MixDirection.mixOut) { + constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; + constraint.stretch = constraint.data.stretch; + } else { + constraint.bendDirection = frames[frame + IkConstraintTimeline.PREV_BEND_DIRECTION]; + constraint.compress = frames[frame + IkConstraintTimeline.PREV_COMPRESS] != 0; + constraint.stretch = frames[frame + IkConstraintTimeline.PREV_STRETCH] != 0; + } + } else { + constraint.mix += (mix + (frames[frame + IkConstraintTimeline.MIX] - mix) * percent - constraint.mix) * alpha; + constraint.softness += + (softness + (frames[frame + IkConstraintTimeline.SOFTNESS] - softness) * percent - constraint.softness) * alpha; + if (direction == MixDirection.mixIn) { + constraint.bendDirection = frames[frame + IkConstraintTimeline.PREV_BEND_DIRECTION]; + constraint.compress = frames[frame + IkConstraintTimeline.PREV_COMPRESS] != 0; + constraint.stretch = frames[frame + IkConstraintTimeline.PREV_STRETCH] != 0; + } + } + } +} + +/** Changes a transform constraint's {@link TransformConstraint#rotateMix}, {@link TransformConstraint#translateMix}, + * {@link TransformConstraint#scaleMix}, and {@link TransformConstraint#shearMix}. */ +export class TransformConstraintTimeline extends CurveTimeline { + static ENTRIES = 5; + static PREV_TIME = -5; + static PREV_ROTATE = -4; + static PREV_TRANSLATE = -3; + static PREV_SCALE = -2; + static PREV_SHEAR = -1; + static ROTATE = 1; + static TRANSLATE = 2; + static SCALE = 3; + static SHEAR = 4; + + /** The index of the transform constraint slot in {@link Skeleton#transformConstraints} that will be changed. */ + transformConstraintIndex: number; + + /** The time in seconds, rotate mix, translate mix, scale mix, and shear mix for each key frame. */ + frames: ArrayLike; // time, rotate mix, translate mix, scale mix, shear mix, ... + + constructor(frameCount: number) { + super(frameCount); + this.frames = Utils.newFloatArray(frameCount * TransformConstraintTimeline.ENTRIES); + } + + getPropertyId() { + return (TimelineType.transformConstraint << 24) + this.transformConstraintIndex; + } + + /** The time in seconds, rotate mix, translate mix, scale mix, and shear mix for the specified key frame. */ + setFrame( + frameIndex: number, + time: number, + rotateMix: number, + translateMix: number, + scaleMix: number, + shearMix: number + ) { + frameIndex *= TransformConstraintTimeline.ENTRIES; + this.frames[frameIndex] = time; + this.frames[frameIndex + TransformConstraintTimeline.ROTATE] = rotateMix; + this.frames[frameIndex + TransformConstraintTimeline.TRANSLATE] = translateMix; + this.frames[frameIndex + TransformConstraintTimeline.SCALE] = scaleMix; + this.frames[frameIndex + TransformConstraintTimeline.SHEAR] = shearMix; + } + + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + firedEvents: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const frames = this.frames; + + const constraint: TransformConstraint = skeleton.transformConstraints[this.transformConstraintIndex]; + if (!constraint.active) return; + if (time < frames[0]) { + const data = constraint.data; + switch (blend) { + case MixBlend.setup: + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; + constraint.shearMix = data.shearMix; + return; + case MixBlend.first: + constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; + constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; + constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha; + } + return; + } + + let rotate = 0, + translate = 0, + scale = 0, + shear = 0; + if (time >= frames[frames.length - TransformConstraintTimeline.ENTRIES]) { + // Time is after last frame. + const i = frames.length; + rotate = frames[i + TransformConstraintTimeline.PREV_ROTATE]; + translate = frames[i + TransformConstraintTimeline.PREV_TRANSLATE]; + scale = frames[i + TransformConstraintTimeline.PREV_SCALE]; + shear = frames[i + TransformConstraintTimeline.PREV_SHEAR]; + } else { + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time, TransformConstraintTimeline.ENTRIES); + rotate = frames[frame + TransformConstraintTimeline.PREV_ROTATE]; + translate = frames[frame + TransformConstraintTimeline.PREV_TRANSLATE]; + scale = frames[frame + TransformConstraintTimeline.PREV_SCALE]; + shear = frames[frame + TransformConstraintTimeline.PREV_SHEAR]; + const frameTime = frames[frame]; + const percent = this.getCurvePercent( + frame / TransformConstraintTimeline.ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + TransformConstraintTimeline.PREV_TIME] - frameTime) + ); + + rotate += (frames[frame + TransformConstraintTimeline.ROTATE] - rotate) * percent; + translate += (frames[frame + TransformConstraintTimeline.TRANSLATE] - translate) * percent; + scale += (frames[frame + TransformConstraintTimeline.SCALE] - scale) * percent; + shear += (frames[frame + TransformConstraintTimeline.SHEAR] - shear) * percent; + } + if (blend == MixBlend.setup) { + const data = constraint.data; + constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; + constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; + constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; + constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; + } else { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + constraint.scaleMix += (scale - constraint.scaleMix) * alpha; + constraint.shearMix += (shear - constraint.shearMix) * alpha; + } + } +} + +/** Changes a path constraint's {@link PathConstraint#position}. */ +export class PathConstraintPositionTimeline extends CurveTimeline { + static ENTRIES = 2; + static PREV_TIME = -2; + static PREV_VALUE = -1; + static VALUE = 1; + + /** The index of the path constraint slot in {@link Skeleton#pathConstraints} that will be changed. */ + pathConstraintIndex: number; + + /** The time in seconds and path constraint position for each key frame. */ + frames: ArrayLike; // time, position, ... + + constructor(frameCount: number) { + super(frameCount); + this.frames = Utils.newFloatArray(frameCount * PathConstraintPositionTimeline.ENTRIES); + } + + getPropertyId() { + return (TimelineType.pathConstraintPosition << 24) + this.pathConstraintIndex; + } + + /** Sets the time in seconds and path constraint position for the specified key frame. */ + setFrame(frameIndex: number, time: number, value: number) { + frameIndex *= PathConstraintPositionTimeline.ENTRIES; + this.frames[frameIndex] = time; + this.frames[frameIndex + PathConstraintPositionTimeline.VALUE] = value; + } + + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + firedEvents: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const frames = this.frames; + const constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex]; + if (!constraint.active) return; + if (time < frames[0]) { + switch (blend) { + case MixBlend.setup: + constraint.position = constraint.data.position; + return; + case MixBlend.first: + constraint.position += (constraint.data.position - constraint.position) * alpha; + } + return; + } + + let position = 0; + if (time >= frames[frames.length - PathConstraintPositionTimeline.ENTRIES]) + // Time is after last frame. + position = frames[frames.length + PathConstraintPositionTimeline.PREV_VALUE]; + else { + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time, PathConstraintPositionTimeline.ENTRIES); + position = frames[frame + PathConstraintPositionTimeline.PREV_VALUE]; + const frameTime = frames[frame]; + const percent = this.getCurvePercent( + frame / PathConstraintPositionTimeline.ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PathConstraintPositionTimeline.PREV_TIME] - frameTime) + ); + + position += (frames[frame + PathConstraintPositionTimeline.VALUE] - position) * percent; + } + if (blend == MixBlend.setup) + constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; + else constraint.position += (position - constraint.position) * alpha; + } +} + +/** Changes a path constraint's {@link PathConstraint#spacing}. */ +export class PathConstraintSpacingTimeline extends PathConstraintPositionTimeline { + constructor(frameCount: number) { + super(frameCount); + } + + override getPropertyId() { + return (TimelineType.pathConstraintSpacing << 24) + this.pathConstraintIndex; + } + + override apply( + skeleton: Skeleton, + lastTime: number, + time: number, + firedEvents: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const frames = this.frames; + const constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex]; + if (!constraint.active) return; + if (time < frames[0]) { + switch (blend) { + case MixBlend.setup: + constraint.spacing = constraint.data.spacing; + return; + case MixBlend.first: + constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; + } + return; + } + + let spacing = 0; + if (time >= frames[frames.length - PathConstraintSpacingTimeline.ENTRIES]) + // Time is after last frame. + spacing = frames[frames.length + PathConstraintSpacingTimeline.PREV_VALUE]; + else { + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time, PathConstraintSpacingTimeline.ENTRIES); + spacing = frames[frame + PathConstraintSpacingTimeline.PREV_VALUE]; + const frameTime = frames[frame]; + const percent = this.getCurvePercent( + frame / PathConstraintSpacingTimeline.ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PathConstraintSpacingTimeline.PREV_TIME] - frameTime) + ); + + spacing += (frames[frame + PathConstraintSpacingTimeline.VALUE] - spacing) * percent; + } + + if (blend == MixBlend.setup) + constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; + else constraint.spacing += (spacing - constraint.spacing) * alpha; + } +} + +/** Changes a transform constraint's {@link PathConstraint#rotateMix} and + * {@link TransformConstraint#translateMix}. */ +export class PathConstraintMixTimeline extends CurveTimeline { + static ENTRIES = 3; + static PREV_TIME = -3; + static PREV_ROTATE = -2; + static PREV_TRANSLATE = -1; + static ROTATE = 1; + static TRANSLATE = 2; + + /** The index of the path constraint slot in {@link Skeleton#getPathConstraints()} that will be changed. */ + pathConstraintIndex: number; + + /** The time in seconds, rotate mix, and translate mix for each key frame. */ + frames: ArrayLike; // time, rotate mix, translate mix, ... + + constructor(frameCount: number) { + super(frameCount); + this.frames = Utils.newFloatArray(frameCount * PathConstraintMixTimeline.ENTRIES); + } + + getPropertyId() { + return (TimelineType.pathConstraintMix << 24) + this.pathConstraintIndex; + } + + /** The time in seconds, rotate mix, and translate mix for the specified key frame. */ + setFrame(frameIndex: number, time: number, rotateMix: number, translateMix: number) { + frameIndex *= PathConstraintMixTimeline.ENTRIES; + this.frames[frameIndex] = time; + this.frames[frameIndex + PathConstraintMixTimeline.ROTATE] = rotateMix; + this.frames[frameIndex + PathConstraintMixTimeline.TRANSLATE] = translateMix; + } + + apply( + skeleton: Skeleton, + lastTime: number, + time: number, + firedEvents: Array, + alpha: number, + blend: MixBlend, + direction: MixDirection + ) { + const frames = this.frames; + const constraint: PathConstraint = skeleton.pathConstraints[this.pathConstraintIndex]; + if (!constraint.active) return; + if (time < frames[0]) { + switch (blend) { + case MixBlend.setup: + constraint.rotateMix = constraint.data.rotateMix; + constraint.translateMix = constraint.data.translateMix; + return; + case MixBlend.first: + constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; + } + return; + } + + let rotate = 0, + translate = 0; + if (time >= frames[frames.length - PathConstraintMixTimeline.ENTRIES]) { + // Time is after last frame. + rotate = frames[frames.length + PathConstraintMixTimeline.PREV_ROTATE]; + translate = frames[frames.length + PathConstraintMixTimeline.PREV_TRANSLATE]; + } else { + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time, PathConstraintMixTimeline.ENTRIES); + rotate = frames[frame + PathConstraintMixTimeline.PREV_ROTATE]; + translate = frames[frame + PathConstraintMixTimeline.PREV_TRANSLATE]; + const frameTime = frames[frame]; + const percent = this.getCurvePercent( + frame / PathConstraintMixTimeline.ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PathConstraintMixTimeline.PREV_TIME] - frameTime) + ); + + rotate += (frames[frame + PathConstraintMixTimeline.ROTATE] - rotate) * percent; + translate += (frames[frame + PathConstraintMixTimeline.TRANSLATE] - translate) * percent; + } + + if (blend == MixBlend.setup) { + constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; + constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; + } else { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + } + } +} diff --git a/packages/spine-core-3.8/src/spine-core/AnimationState.ts b/packages/spine-core-3.8/src/spine-core/AnimationState.ts new file mode 100644 index 0000000000..7bb6b64177 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/AnimationState.ts @@ -0,0 +1,1193 @@ +import { AnimationStateData } from "./AnimationStateData"; +import { IntSet, Pool, Utils, MathUtils } from "./Utils"; +import { Skeleton } from "./Skeleton"; +import { + MixBlend, + AttachmentTimeline, + MixDirection, + RotateTimeline, + DrawOrderTimeline, + Timeline, + EventTimeline +} from "./Animation"; +import { Slot } from "./Slot"; +import { Animation } from "./Animation"; +import { Event } from "./Event"; + +/** Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies + * multiple animations on top of each other (layering). + * + * See [Applying Animations](http://esotericsoftware.com/spine-applying-animations/) in the Spine Runtimes Guide. */ +export class AnimationState { + static emptyAnimation = new Animation("", [], 0); + + /** 1. A previously applied timeline has set this property. + * + * Result: Mix from the current pose to the timeline pose. */ + static SUBSEQUENT = 0; + /** 1. This is the first timeline to set this property. + * 2. The next track entry applied after this one does not have a timeline to set this property. + * + * Result: Mix from the setup pose to the timeline pose. */ + static FIRST = 1; + /** 1) A previously applied timeline has set this property.
    + * 2) The next track entry to be applied does have a timeline to set this property.
    + * 3) The next track entry after that one does not have a timeline to set this property.
    + * Result: Mix from the current pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading + * animations that key the same property. A subsequent timeline will set this property using a mix. */ + static HOLD_SUBSEQUENT = 2; + /** 1) This is the first timeline to set this property.
    + * 2) The next track entry to be applied does have a timeline to set this property.
    + * 3) The next track entry after that one does not have a timeline to set this property.
    + * Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations + * that key the same property. A subsequent timeline will set this property using a mix. */ + static HOLD_FIRST = 3; + /** 1. This is the first timeline to set this property. + * 2. The next track entry to be applied does have a timeline to set this property. + * 3. The next track entry after that one does have a timeline to set this property. + * 4. timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property. + * + * Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than + * 2 track entries in a row have a timeline that sets the same property. + * + * Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid + * "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A + * (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap into + * place. */ + static HOLD_MIX = 4; + + static SETUP = 1; + static CURRENT = 2; + + /** The AnimationStateData to look up mix durations. */ + data: AnimationStateData; + + /** The list of tracks that currently have animations, which may contain null entries. */ + tracks = new Array(); + + /** Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower + * or faster. Defaults to 1. + * + * See TrackEntry {@link TrackEntry#timeScale} for affecting a single animation. */ + timeScale = 1; + unkeyedState = 0; + + events = new Array(); + listeners = new Array(); + queue = new EventQueue(this); + propertyIDs = new IntSet(); + animationsChanged = false; + + trackEntryPool = new Pool(() => new TrackEntry()); + + constructor(data: AnimationStateData) { + this.data = data; + } + + /** Increments each track entry {@link TrackEntry#trackTime()}, setting queued animations as current if needed. */ + update(delta: number) { + delta *= this.timeScale; + const tracks = this.tracks; + for (let i = 0, n = tracks.length; i < n; i++) { + const current = tracks[i]; + if (current == null) continue; + + current.animationLast = current.nextAnimationLast; + current.trackLast = current.nextTrackLast; + + let currentDelta = delta * current.timeScale; + + if (current.delay > 0) { + current.delay -= currentDelta; + if (current.delay > 0) continue; + currentDelta = -current.delay; + current.delay = 0; + } + + let next = current.next; + if (next != null) { + // When the next entry's delay is passed, change to the next entry, preserving leftover time. + const nextTime = current.trackLast - next.delay; + if (nextTime >= 0) { + next.delay = 0; + next.trackTime += current.timeScale == 0 ? 0 : (nextTime / current.timeScale + delta) * next.timeScale; + current.trackTime += currentDelta; + this.setCurrent(i, next, true); + while (next.mixingFrom != null) { + next.mixTime += delta; + next = next.mixingFrom; + } + continue; + } + } else if (current.trackLast >= current.trackEnd && current.mixingFrom == null) { + tracks[i] = null; + this.queue.end(current); + this.disposeNext(current); + continue; + } + if (current.mixingFrom != null && this.updateMixingFrom(current, delta)) { + // End mixing from entries once all have completed. + let from = current.mixingFrom; + current.mixingFrom = null; + if (from != null) from.mixingTo = null; + while (from != null) { + this.queue.end(from); + from = from.mixingFrom; + } + } + + current.trackTime += currentDelta; + } + + this.queue.drain(); + } + + /** Returns true when all mixing from entries are complete. */ + updateMixingFrom(to: TrackEntry, delta: number): boolean { + const from = to.mixingFrom; + if (from == null) return true; + + const finished = this.updateMixingFrom(from, delta); + + from.animationLast = from.nextAnimationLast; + from.trackLast = from.nextTrackLast; + + // Require mixTime > 0 to ensure the mixing from entry was applied at least once. + if (to.mixTime > 0 && to.mixTime >= to.mixDuration) { + // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). + if (from.totalAlpha == 0 || to.mixDuration == 0) { + to.mixingFrom = from.mixingFrom; + if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; + to.interruptAlpha = from.interruptAlpha; + this.queue.end(from); + } + return finished; + } + + from.trackTime += delta * from.timeScale; + to.mixTime += delta; + return false; + } + + /** Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the + * animation state can be applied to multiple skeletons to pose them identically. + * @returns True if any animations were applied. */ + apply(skeleton: Skeleton): boolean { + if (skeleton == null) throw new Error("skeleton cannot be null."); + if (this.animationsChanged) this._animationsChanged(); + + const events = this.events; + const tracks = this.tracks; + let applied = false; + + for (let i = 0, n = tracks.length; i < n; i++) { + const current = tracks[i]; + if (current == null || current.delay > 0) continue; + applied = true; + const blend: MixBlend = i == 0 ? MixBlend.first : current.mixBlend; + + // Apply mixing from entries first. + let mix = current.alpha; + if (current.mixingFrom != null) mix *= this.applyMixingFrom(current, skeleton, blend); + else if (current.trackTime >= current.trackEnd && current.next == null) mix = 0; + + // Apply current entry. + const animationLast = current.animationLast, + animationTime = current.getAnimationTime(); + const timelineCount = current.animation.timelines.length; + const timelines = current.animation.timelines; + if ((i == 0 && mix == 1) || blend == MixBlend.add) { + for (let ii = 0; ii < timelineCount; ii++) { + // Fixes issue #302 on IOS9 where mix, blend sometimes became undefined and caused assets + // to sometimes stop rendering when using color correction, as their RGBA values become NaN. + // (https://github.com/pixijs/pixi-spine/issues/302) + Utils.webkit602BugfixHelper(mix, blend); + const timeline = timelines[ii]; + if (timeline instanceof AttachmentTimeline) + this.applyAttachmentTimeline(timeline, skeleton, animationTime, blend, true); + else timeline.apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.mixIn); + } + } else { + const timelineMode = current.timelineMode; + + const firstFrame = current.timelinesRotation.length == 0; + if (firstFrame) Utils.setArraySize(current.timelinesRotation, timelineCount << 1, null); + const timelinesRotation = current.timelinesRotation; + + for (let ii = 0; ii < timelineCount; ii++) { + const timeline = timelines[ii]; + const timelineBlend = timelineMode[ii] == AnimationState.SUBSEQUENT ? blend : MixBlend.setup; + if (timeline instanceof RotateTimeline) { + this.applyRotateTimeline( + timeline, + skeleton, + animationTime, + mix, + timelineBlend, + timelinesRotation, + ii << 1, + firstFrame + ); + } else if (timeline instanceof AttachmentTimeline) { + this.applyAttachmentTimeline(timeline, skeleton, animationTime, blend, true); + } else { + // This fixes the WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109 + Utils.webkit602BugfixHelper(mix, blend); + timeline.apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.mixIn); + } + } + } + this.queueEvents(current, animationTime); + events.length = 0; + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + // Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so + // subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or + // the time is before the first key). + const setupState = this.unkeyedState + AnimationState.SETUP; + const slots = skeleton.slots; + for (let i = 0, n = skeleton.slots.length; i < n; i++) { + const slot = slots[i]; + if (slot.attachmentState == setupState) { + const attachmentName = slot.data.attachmentName; + slot.attachment = attachmentName == null ? null : skeleton.getAttachment(slot.data.index, attachmentName); + } + } + this.unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot. + + this.queue.drain(); + return applied; + } + + applyMixingFrom(to: TrackEntry, skeleton: Skeleton, blend: MixBlend) { + const from = to.mixingFrom; + if (from.mixingFrom != null) this.applyMixingFrom(from, skeleton, blend); + + let mix = 0; + if (to.mixDuration == 0) { + // Single frame mix to undo mixingFrom changes. + mix = 1; + if (blend == MixBlend.first) blend = MixBlend.setup; + } else { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + if (blend != MixBlend.first) blend = from.mixBlend; + } + + const events = mix < from.eventThreshold ? this.events : null; + const attachments = mix < from.attachmentThreshold, + drawOrder = mix < from.drawOrderThreshold; + const animationLast = from.animationLast, + animationTime = from.getAnimationTime(); + const timelineCount = from.animation.timelines.length; + const timelines = from.animation.timelines; + const alphaHold = from.alpha * to.interruptAlpha, + alphaMix = alphaHold * (1 - mix); + if (blend == MixBlend.add) { + for (let i = 0; i < timelineCount; i++) + timelines[i].apply(skeleton, animationLast, animationTime, events, alphaMix, blend, MixDirection.mixOut); + } else { + const timelineMode = from.timelineMode; + const timelineHoldMix = from.timelineHoldMix; + + const firstFrame = from.timelinesRotation.length == 0; + if (firstFrame) Utils.setArraySize(from.timelinesRotation, timelineCount << 1, null); + const timelinesRotation = from.timelinesRotation; + + from.totalAlpha = 0; + for (let i = 0; i < timelineCount; i++) { + const timeline = timelines[i]; + let direction = MixDirection.mixOut; + let timelineBlend: MixBlend; + let alpha = 0; + switch (timelineMode[i]) { + case AnimationState.SUBSEQUENT: + if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; + timelineBlend = blend; + alpha = alphaMix; + break; + case AnimationState.FIRST: + timelineBlend = MixBlend.setup; + alpha = alphaMix; + break; + case AnimationState.HOLD_SUBSEQUENT: + timelineBlend = blend; + alpha = alphaHold; + break; + case AnimationState.HOLD_FIRST: + timelineBlend = MixBlend.setup; + alpha = alphaHold; + break; + default: + timelineBlend = MixBlend.setup; + const holdMix = timelineHoldMix[i]; + alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration); + break; + } + from.totalAlpha += alpha; + + if (timeline instanceof RotateTimeline) + this.applyRotateTimeline( + timeline, + skeleton, + animationTime, + alpha, + timelineBlend, + timelinesRotation, + i << 1, + firstFrame + ); + else if (timeline instanceof AttachmentTimeline) + this.applyAttachmentTimeline(timeline, skeleton, animationTime, timelineBlend, attachments); + else { + // This fixes the WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109 + Utils.webkit602BugfixHelper(alpha, blend); + if (drawOrder && timeline instanceof DrawOrderTimeline && timelineBlend == MixBlend.setup) + direction = MixDirection.mixIn; + timeline.apply(skeleton, animationLast, animationTime, events, alpha, timelineBlend, direction); + } + } + } + + if (to.mixDuration > 0) this.queueEvents(from, animationTime); + this.events.length = 0; + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + applyAttachmentTimeline( + timeline: AttachmentTimeline, + skeleton: Skeleton, + time: number, + blend: MixBlend, + attachments: boolean + ) { + const slot = skeleton.slots[timeline.slotIndex]; + if (!slot.bone.active) return; + + const frames = timeline.frames; + if (time < frames[0]) { + // Time is before first frame. + if (blend == MixBlend.setup || blend == MixBlend.first) + this.setAttachment(skeleton, slot, slot.data.attachmentName, attachments); + } else { + let frameIndex; + if (time >= frames[frames.length - 1]) + // Time is after last frame. + frameIndex = frames.length - 1; + else frameIndex = Animation.binarySearch(frames, time) - 1; + this.setAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments); + } + + // If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later. + if (slot.attachmentState <= this.unkeyedState) slot.attachmentState = this.unkeyedState + AnimationState.SETUP; + } + + setAttachment(skeleton: Skeleton, slot: Slot, attachmentName: string, attachments: boolean) { + slot.attachment = attachmentName == null ? null : skeleton.getAttachment(slot.data.index, attachmentName); + if (attachments) slot.attachmentState = this.unkeyedState + AnimationState.CURRENT; + } + + applyRotateTimeline( + timeline: Timeline, + skeleton: Skeleton, + time: number, + alpha: number, + blend: MixBlend, + timelinesRotation: Array, + i: number, + firstFrame: boolean + ) { + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) { + timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.mixIn); + return; + } + + const rotateTimeline = timeline as RotateTimeline; + const frames = rotateTimeline.frames; + const bone = skeleton.bones[rotateTimeline.boneIndex]; + if (!bone.active) return; + let r1 = 0, + r2 = 0; + if (time < frames[0]) { + switch (blend) { + case MixBlend.setup: + bone.rotation = bone.data.rotation; + default: + return; + case MixBlend.first: + r1 = bone.rotation; + r2 = bone.data.rotation; + } + } else { + r1 = blend == MixBlend.setup ? bone.data.rotation : bone.rotation; + if (time >= frames[frames.length - RotateTimeline.ENTRIES]) + // Time is after last frame. + r2 = bone.data.rotation + frames[frames.length + RotateTimeline.PREV_ROTATION]; + else { + // Interpolate between the previous frame and the current frame. + const frame = Animation.binarySearch(frames, time, RotateTimeline.ENTRIES); + const prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; + const frameTime = frames[frame]; + const percent = rotateTimeline.getCurvePercent( + (frame >> 1) - 1, + 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime) + ); + + r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; + r2 -= (16384 - ((16384.499999999996 - r2 / 360) | 0)) * 360; + r2 = prevRotation + r2 * percent + bone.data.rotation; + r2 -= (16384 - ((16384.499999999996 - r2 / 360) | 0)) * 360; + } + } + + // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. + let total = 0, + diff = r2 - r1; + diff -= (16384 - ((16384.499999999996 - diff / 360) | 0)) * 360; + if (diff == 0) { + total = timelinesRotation[i]; + } else { + let lastTotal = 0, + lastDiff = 0; + if (firstFrame) { + lastTotal = 0; + lastDiff = diff; + } else { + lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. + lastDiff = timelinesRotation[i + 1]; // Difference between bones. + } + let current = diff > 0, + dir = lastTotal >= 0; + // Detect cross at 0 (not 180). + if (MathUtils.signum(lastDiff) != MathUtils.signum(diff) && Math.abs(lastDiff) <= 90) { + // A cross after a 360 rotation is a loop. + if (Math.abs(lastTotal) > 180) lastTotal += 360 * MathUtils.signum(lastTotal); + dir = current; + } + total = diff + lastTotal - (lastTotal % 360); // Store loops as part of lastTotal. + if (dir != current) total += 360 * MathUtils.signum(lastTotal); + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + r1 += total * alpha; + bone.rotation = r1 - (16384 - ((16384.499999999996 - r1 / 360) | 0)) * 360; + } + + queueEvents(entry: TrackEntry, animationTime: number) { + const animationStart = entry.animationStart, + animationEnd = entry.animationEnd; + const duration = animationEnd - animationStart; + const trackLastWrapped = entry.trackLast % duration; + + // Queue events before complete. + const events = this.events; + let i = 0, + n = events.length; + for (; i < n; i++) { + const event = events[i]; + if (event.time < trackLastWrapped) break; + if (event.time > animationEnd) continue; // Discard events outside animation start/end. + this.queue.event(entry, event); + } + + // Queue complete if completed a loop iteration or the animation. + let complete = false; + if (entry.loop) complete = duration == 0 || trackLastWrapped > entry.trackTime % duration; + else complete = animationTime >= animationEnd && entry.animationLast < animationEnd; + if (complete) this.queue.complete(entry); + + // Queue events after complete. + for (; i < n; i++) { + const event = events[i]; + if (event.time < animationStart) continue; // Discard events outside animation start/end. + this.queue.event(entry, events[i]); + } + } + + /** Removes all animations from all tracks, leaving skeletons in their current pose. + * + * It may be desired to use {@link AnimationState#setEmptyAnimation()} to mix the skeletons back to the setup pose, + * rather than leaving them in their current pose. */ + clearTracks() { + const oldDrainDisabled = this.queue.drainDisabled; + this.queue.drainDisabled = true; + for (let i = 0, n = this.tracks.length; i < n; i++) this.clearTrack(i); + this.tracks.length = 0; + this.queue.drainDisabled = oldDrainDisabled; + this.queue.drain(); + } + + /** Removes all animations from the track, leaving skeletons in their current pose. + * + * It may be desired to use {@link AnimationState#setEmptyAnimation()} to mix the skeletons back to the setup pose, + * rather than leaving them in their current pose. */ + clearTrack(trackIndex: number) { + if (trackIndex >= this.tracks.length) return; + const current = this.tracks[trackIndex]; + if (current == null) return; + + this.queue.end(current); + + this.disposeNext(current); + + let entry = current; + while (true) { + const from = entry.mixingFrom; + if (from == null) break; + this.queue.end(from); + entry.mixingFrom = null; + entry.mixingTo = null; + entry = from; + } + + this.tracks[current.trackIndex] = null; + + this.queue.drain(); + } + + setCurrent(index: number, current: TrackEntry, interrupt: boolean) { + const from = this.expandToIndex(index); + this.tracks[index] = current; + + if (from != null) { + if (interrupt) this.queue.interrupt(from); + current.mixingFrom = from; + from.mixingTo = current; + current.mixTime = 0; + + // Store the interrupted mix percentage. + if (from.mixingFrom != null && from.mixDuration > 0) + current.interruptAlpha *= Math.min(1, from.mixTime / from.mixDuration); + + from.timelinesRotation.length = 0; // Reset rotation for mixing out, in case entry was mixed in. + } + + this.queue.start(current); + } + + /** Sets an animation by name. + * + * {@link #setAnimationWith(}. */ + setAnimation(trackIndex: number, animationName: string, loop: boolean) { + const animation = this.data.skeletonData.findAnimation(animationName); + if (animation == null) throw new Error("Animation not found: " + animationName); + return this.setAnimationWith(trackIndex, animation, loop); + } + + /** Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never + * applied to a skeleton, it is replaced (not mixed from). + * @param loop If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its + * duration. In either case {@link TrackEntry#trackEnd} determines when the track is cleared. + * @returns A track entry to allow further customization of animation playback. References to the track entry must not be kept + * after the {@link AnimationStateListener#dispose()} event occurs. */ + setAnimationWith(trackIndex: number, animation: Animation, loop: boolean) { + if (animation == null) throw new Error("animation cannot be null."); + let interrupt = true; + let current = this.expandToIndex(trackIndex); + if (current != null) { + if (current.nextTrackLast == -1) { + // Don't mix from an entry that was never applied. + this.tracks[trackIndex] = current.mixingFrom; + this.queue.interrupt(current); + this.queue.end(current); + this.disposeNext(current); + current = current.mixingFrom; + interrupt = false; + } else this.disposeNext(current); + } + const entry = this.trackEntry(trackIndex, animation, loop, current); + this.setCurrent(trackIndex, entry, interrupt); + this.queue.drain(); + return entry; + } + + /** Queues an animation by name. + * + * See {@link #addAnimationWith()}. */ + addAnimation(trackIndex: number, animationName: string, loop: boolean, delay: number) { + const animation = this.data.skeletonData.findAnimation(animationName); + if (animation == null) throw new Error("Animation not found: " + animationName); + return this.addAnimationWith(trackIndex, animation, loop, delay); + } + + /** Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is + * equivalent to calling {@link #setAnimationWith()}. + * @param delay If > 0, sets {@link TrackEntry#delay}. If <= 0, the delay set is the duration of the previous track entry + * minus any mix duration (from the {@link AnimationStateData}) plus the specified `delay` (ie the mix + * ends at (`delay` = 0) or before (`delay` < 0) the previous track entry duration). If the + * previous entry is looping, its next loop completion is used instead of its duration. + * @returns A track entry to allow further customization of animation playback. References to the track entry must not be kept + * after the {@link AnimationStateListener#dispose()} event occurs. */ + addAnimationWith(trackIndex: number, animation: Animation, loop: boolean, delay: number) { + if (animation == null) throw new Error("animation cannot be null."); + + let last = this.expandToIndex(trackIndex); + if (last != null) { + while (last.next != null) last = last.next; + } + + const entry = this.trackEntry(trackIndex, animation, loop, last); + + if (last == null) { + this.setCurrent(trackIndex, entry, true); + this.queue.drain(); + } else { + last.next = entry; + if (delay <= 0) { + const duration = last.animationEnd - last.animationStart; + if (duration != 0) { + if (last.loop) delay += duration * (1 + ((last.trackTime / duration) | 0)); + else delay += Math.max(duration, last.trackTime); + delay -= this.data.getMix(last.animation, animation); + } else delay = last.trackTime; + } + } + + entry.delay = delay; + return entry; + } + + /** Sets an empty animation for a track, discarding any queued animations, and sets the track entry's + * {@link TrackEntry#mixduration}. An empty animation has no timelines and serves as a placeholder for mixing in or out. + * + * Mixing out is done by setting an empty animation with a mix duration using either {@link #setEmptyAnimation()}, + * {@link #setEmptyAnimations()}, or {@link #addEmptyAnimation()}. Mixing to an empty animation causes + * the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation + * transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of + * 0 still mixes out over one frame. + * + * Mixing in is done by first setting an empty animation, then adding an animation using + * {@link #addAnimation()} and on the returned track entry, set the + * {@link TrackEntry#setMixDuration()}. Mixing from an empty animation causes the new animation to be applied more and + * more over the mix duration. Properties keyed in the new animation transition from the value from lower tracks or from the + * setup pose value if no lower tracks key the property to the value keyed in the new animation. */ + setEmptyAnimation(trackIndex: number, mixDuration: number) { + const entry = this.setAnimationWith(trackIndex, AnimationState.emptyAnimation, false); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /** Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's + * {@link TrackEntry#mixDuration}. If the track is empty, it is equivalent to calling + * {@link #setEmptyAnimation()}. + * + * See {@link #setEmptyAnimation()}. + * @param delay If > 0, sets {@link TrackEntry#delay}. If <= 0, the delay set is the duration of the previous track entry + * minus any mix duration plus the specified `delay` (ie the mix ends at (`delay` = 0) or + * before (`delay` < 0) the previous track entry duration). If the previous entry is looping, its next + * loop completion is used instead of its duration. + * @return A track entry to allow further customization of animation playback. References to the track entry must not be kept + * after the {@link AnimationStateListener#dispose()} event occurs. */ + addEmptyAnimation(trackIndex: number, mixDuration: number, delay: number) { + if (delay <= 0) delay -= mixDuration; + const entry = this.addAnimationWith(trackIndex, AnimationState.emptyAnimation, false, delay); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /** Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix + * duration. */ + setEmptyAnimations(mixDuration: number) { + const oldDrainDisabled = this.queue.drainDisabled; + this.queue.drainDisabled = true; + for (let i = 0, n = this.tracks.length; i < n; i++) { + const current = this.tracks[i]; + if (current != null) this.setEmptyAnimation(current.trackIndex, mixDuration); + } + this.queue.drainDisabled = oldDrainDisabled; + this.queue.drain(); + } + + expandToIndex(index: number) { + if (index < this.tracks.length) return this.tracks[index]; + Utils.ensureArrayCapacity(this.tracks, index + 1, null); + this.tracks.length = index + 1; + return null; + } + + /** @param last May be null. */ + trackEntry(trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry) { + const entry = this.trackEntryPool.obtain(); + entry.trackIndex = trackIndex; + entry.animation = animation; + entry.loop = loop; + entry.holdPrevious = false; + + entry.eventThreshold = 0; + entry.attachmentThreshold = 0; + entry.drawOrderThreshold = 0; + + entry.animationStart = 0; + entry.animationEnd = animation.duration; + entry.animationLast = -1; + entry.nextAnimationLast = -1; + + entry.delay = 0; + entry.trackTime = 0; + entry.trackLast = -1; + entry.nextTrackLast = -1; + entry.trackEnd = Number.MAX_VALUE; + entry.timeScale = 1; + + entry.alpha = 1; + entry.interruptAlpha = 1; + entry.mixTime = 0; + entry.mixDuration = last == null ? 0 : this.data.getMix(last.animation, animation); + entry.mixBlend = MixBlend.replace; + return entry; + } + + disposeNext(entry: TrackEntry) { + let next = entry.next; + while (next != null) { + this.queue.dispose(next); + next = next.next; + } + entry.next = null; + } + + _animationsChanged() { + this.animationsChanged = false; + + this.propertyIDs.clear(); + + for (let i = 0, n = this.tracks.length; i < n; i++) { + let entry = this.tracks[i]; + if (entry == null) continue; + while (entry.mixingFrom != null) entry = entry.mixingFrom; + + do { + if (entry.mixingFrom == null || entry.mixBlend != MixBlend.add) this.computeHold(entry); + entry = entry.mixingTo; + } while (entry != null); + } + } + + computeHold(entry: TrackEntry) { + const to = entry.mixingTo; + const timelines = entry.animation.timelines; + const timelinesCount = entry.animation.timelines.length; + const timelineMode = Utils.setArraySize(entry.timelineMode, timelinesCount); + entry.timelineHoldMix.length = 0; + const timelineDipMix = Utils.setArraySize(entry.timelineHoldMix, timelinesCount); + const propertyIDs = this.propertyIDs; + + if (to != null && to.holdPrevious) { + for (let i = 0; i < timelinesCount; i++) { + timelineMode[i] = propertyIDs.add(timelines[i].getPropertyId()) + ? AnimationState.HOLD_FIRST + : AnimationState.HOLD_SUBSEQUENT; + } + return; + } + + outer: for (let i = 0; i < timelinesCount; i++) { + const timeline = timelines[i]; + const id = timeline.getPropertyId(); + if (!propertyIDs.add(id)) timelineMode[i] = AnimationState.SUBSEQUENT; + else if ( + to == null || + timeline instanceof AttachmentTimeline || + timeline instanceof DrawOrderTimeline || + timeline instanceof EventTimeline || + !to.animation.hasTimeline(id) + ) { + timelineMode[i] = AnimationState.FIRST; + } else { + for (let next = to.mixingTo; next != null; next = next.mixingTo) { + if (next.animation.hasTimeline(id)) continue; + if (entry.mixDuration > 0) { + timelineMode[i] = AnimationState.HOLD_MIX; + timelineDipMix[i] = next; + continue outer; + } + break; + } + timelineMode[i] = AnimationState.HOLD_FIRST; + } + } + } + + /** Returns the track entry for the animation currently playing on the track, or null if no animation is currently playing. */ + getCurrent(trackIndex: number) { + if (trackIndex >= this.tracks.length) return null; + return this.tracks[trackIndex]; + } + + /** Adds a listener to receive events for all track entries. */ + addListener(listener: AnimationStateListener) { + if (listener == null) throw new Error("listener cannot be null."); + this.listeners.push(listener); + } + + /** Removes the listener added with {@link #addListener()}. */ + removeListener(listener: AnimationStateListener) { + const index = this.listeners.indexOf(listener); + if (index >= 0) this.listeners.splice(index, 1); + } + + /** Removes all listeners added with {@link #addListener()}. */ + clearListeners() { + this.listeners.length = 0; + } + + /** Discards all listener notifications that have not yet been delivered. This can be useful to call from an + * {@link AnimationStateListener} when it is known that further notifications that may have been already queued for delivery + * are not wanted because new animations are being set. */ + clearListenerNotifications() { + this.queue.clear(); + } +} + +/** Stores settings and other state for the playback of an animation on an {@link AnimationState} track. + * + * References to a track entry must not be kept after the {@link AnimationStateListener#dispose()} event occurs. */ +export class TrackEntry { + /** The animation to apply for this track entry. */ + animation: Animation; + + /** The animation queued to start after this animation, or null. `next` makes up a linked list. */ + next: TrackEntry; + + /** The track entry for the previous animation when mixing from the previous animation to this animation, or null if no + * mixing is currently occuring. When mixing from multiple animations, `mixingFrom` makes up a linked list. */ + mixingFrom: TrackEntry; + + /** The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is + * currently occuring. When mixing to multiple animations, `mixingTo` makes up a linked list. */ + mixingTo: TrackEntry; + + /** The listener for events generated by this track entry, or null. + * + * A track entry returned from {@link AnimationState#setAnimation()} is already the current animation + * for the track, so the track entry listener {@link AnimationStateListener#start()} will not be called. */ + listener: AnimationStateListener; + + /** The index of the track where this track entry is either current or queued. + * + * See {@link AnimationState#getCurrent()}. */ + trackIndex: number; + + /** If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its + * duration. */ + loop: boolean; + + /** If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead + * of being mixed out. + * + * When mixing between animations that key the same property, if a lower track also keys that property then the value will + * briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0% + * while the second animation mixes from 0% to 100%. Setting `holdPrevious` to true applies the first animation + * at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which + * keys the property, only when a higher track also keys the property. + * + * Snapping will occur if `holdPrevious` is true and this animation does not key all the same properties as the + * previous animation. */ + holdPrevious: boolean; + + /** When the mix percentage ({@link #mixTime} / {@link #mixDuration}) is less than the + * `eventThreshold`, event timelines are applied while this animation is being mixed out. Defaults to 0, so event + * timelines are not applied while this animation is being mixed out. */ + eventThreshold: number; + + /** When the mix percentage ({@link #mixtime} / {@link #mixDuration}) is less than the + * `attachmentThreshold`, attachment timelines are applied while this animation is being mixed out. Defaults to + * 0, so attachment timelines are not applied while this animation is being mixed out. */ + attachmentThreshold: number; + + /** When the mix percentage ({@link #mixTime} / {@link #mixDuration}) is less than the + * `drawOrderThreshold`, draw order timelines are applied while this animation is being mixed out. Defaults to 0, + * so draw order timelines are not applied while this animation is being mixed out. */ + drawOrderThreshold: number; + + /** Seconds when this animation starts, both initially and after looping. Defaults to 0. + * + * When changing the `animationStart` time, it often makes sense to set {@link #animationLast} to the same + * value to prevent timeline keys before the start time from triggering. */ + animationStart: number; + + /** Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + * loop back to {@link #animationStart} at this time. Defaults to the animation {@link Animation#duration}. */ + animationEnd: number; + + /** The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this + * animation is applied, event timelines will fire all events between the `animationLast` time (exclusive) and + * `animationTime` (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation + * is applied. */ + animationLast: number; + + nextAnimationLast: number; + + /** Seconds to postpone playing the animation. When this track entry is the current track entry, `delay` + * postpones incrementing the {@link #trackTime}. When this track entry is queued, `delay` is the time from + * the start of the previous animation to when this track entry will become the current track entry (ie when the previous + * track entry {@link TrackEntry#trackTime} >= this track entry's `delay`). + * + * {@link #timeScale} affects the delay. */ + delay: number; + + /** Current time in seconds this track entry has been the current track entry. The track time determines + * {@link #animationTime}. The track time can be set to start the animation at a time other than 0, without affecting + * looping. */ + trackTime: number; + + trackLast: number; + nextTrackLast: number; + + /** The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float + * value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time + * is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the + * properties keyed by the animation are set to the setup pose and the track is cleared. + * + * It may be desired to use {@link AnimationState#addEmptyAnimation()} rather than have the animation + * abruptly cease being applied. */ + trackEnd: number; + + /** Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or + * faster. Defaults to 1. + * + * {@link #mixTime} is not affected by track entry time scale, so {@link #mixDuration} may need to be adjusted to + * match the animation speed. + * + * When using {@link AnimationState#addAnimation()} with a `delay` <= 0, note the + * {@link #delay} is set using the mix duration from the {@link AnimationStateData}, assuming time scale to be 1. If + * the time scale is not 1, the delay may need to be adjusted. + * + * See AnimationState {@link AnimationState#timeScale} for affecting all animations. */ + timeScale: number; + + /** Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults + * to 1, which overwrites the skeleton's current pose with this animation. + * + * Typically track 0 is used to completely pose the skeleton, then alpha is used on higher tracks. It doesn't make sense to + * use alpha on track 0 if the skeleton pose is from the last frame render. */ + alpha: number; + + /** Seconds from 0 to the {@link #getMixDuration()} when mixing from the previous animation to this animation. May be + * slightly more than `mixDuration` when the mix is complete. */ + mixTime: number; + + /** Seconds for mixing from the previous animation to this animation. Defaults to the value provided by AnimationStateData + * {@link AnimationStateData#getMix()} based on the animation before this animation (if any). + * + * A mix duration of 0 still mixes out over one frame to provide the track entry being mixed out a chance to revert the + * properties it was animating. + * + * The `mixDuration` can be set manually rather than use the value from + * {@link AnimationStateData#getMix()}. In that case, the `mixDuration` can be set for a new + * track entry only before {@link AnimationState#update(float)} is first called. + * + * When using {@link AnimationState#addAnimation()} with a `delay` <= 0, note the + * {@link #delay} is set using the mix duration from the {@link AnimationStateData}, not a mix duration set + * afterward. */ + mixDuration: number; + interruptAlpha: number; + totalAlpha: number; + + /** Controls how properties keyed in the animation are mixed with lower tracks. Defaults to {@link MixBlend#replace}, which + * replaces the values from the lower tracks with the animation values. {@link MixBlend#add} adds the animation values to + * the values from the lower tracks. + * + * The `mixBlend` can be set for a new track entry only before {@link AnimationState#apply()} is first + * called. */ + mixBlend = MixBlend.replace; + timelineMode = new Array(); + timelineHoldMix = new Array(); + timelinesRotation = new Array(); + + reset() { + this.next = null; + this.mixingFrom = null; + this.mixingTo = null; + this.animation = null; + this.listener = null; + this.timelineMode.length = 0; + this.timelineHoldMix.length = 0; + this.timelinesRotation.length = 0; + } + + /** Uses {@link #trackTime} to compute the `animationTime`, which is between {@link #animationStart} + * and {@link #animationEnd}. When the `trackTime` is 0, the `animationTime` is equal to the + * `animationStart` time. */ + getAnimationTime() { + if (this.loop) { + const duration = this.animationEnd - this.animationStart; + if (duration == 0) return this.animationStart; + return (this.trackTime % duration) + this.animationStart; + } + return Math.min(this.trackTime + this.animationStart, this.animationEnd); + } + + setAnimationLast(animationLast: number) { + this.animationLast = animationLast; + this.nextAnimationLast = animationLast; + } + + /** Returns true if at least one loop has been completed. + * + * See {@link AnimationStateListener#complete()}. */ + isComplete() { + return this.trackTime >= this.animationEnd - this.animationStart; + } + + /** Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the + * long way around when using {@link #alpha} and starting animations on other tracks. + * + * Mixing with {@link MixBlend#replace} involves finding a rotation between two others, which has two possible solutions: + * the short way or the long way around. The two rotations likely change over time, so which direction is the short or long + * way also changes. If the short way was always chosen, bones would flip to the other side when that direction became the + * long way. TrackEntry chooses the short way the first time it is applied and remembers that direction. */ + resetRotationDirections() { + this.timelinesRotation.length = 0; + } +} + +export class EventQueue { + objects: Array = []; + drainDisabled = false; + animState: AnimationState; + + constructor(animState: AnimationState) { + this.animState = animState; + } + + start(entry: TrackEntry) { + this.objects.push(EventType.start); + this.objects.push(entry); + this.animState.animationsChanged = true; + } + + interrupt(entry: TrackEntry) { + this.objects.push(EventType.interrupt); + this.objects.push(entry); + } + + end(entry: TrackEntry) { + this.objects.push(EventType.end); + this.objects.push(entry); + this.animState.animationsChanged = true; + } + + dispose(entry: TrackEntry) { + this.objects.push(EventType.dispose); + this.objects.push(entry); + } + + complete(entry: TrackEntry) { + this.objects.push(EventType.complete); + this.objects.push(entry); + } + + event(entry: TrackEntry, event: Event) { + this.objects.push(EventType.event); + this.objects.push(entry); + this.objects.push(event); + } + + drain() { + if (this.drainDisabled) return; + this.drainDisabled = true; + + const objects = this.objects; + const listeners = this.animState.listeners; + + for (let i = 0; i < objects.length; i += 2) { + const type = objects[i] as EventType; + const entry = objects[i + 1] as TrackEntry; + switch (type) { + case EventType.start: + if (entry.listener != null && entry.listener.start) entry.listener.start(entry); + for (let ii = 0; ii < listeners.length; ii++) if (listeners[ii].start) listeners[ii].start(entry); + break; + case EventType.interrupt: + if (entry.listener != null && entry.listener.interrupt) entry.listener.interrupt(entry); + for (let ii = 0; ii < listeners.length; ii++) if (listeners[ii].interrupt) listeners[ii].interrupt(entry); + break; + case EventType.end: + if (entry.listener != null && entry.listener.end) entry.listener.end(entry); + for (let ii = 0; ii < listeners.length; ii++) if (listeners[ii].end) listeners[ii].end(entry); + // Fall through. + case EventType.dispose: + if (entry.listener != null && entry.listener.dispose) entry.listener.dispose(entry); + for (let ii = 0; ii < listeners.length; ii++) if (listeners[ii].dispose) listeners[ii].dispose(entry); + this.animState.trackEntryPool.free(entry); + break; + case EventType.complete: + if (entry.listener != null && entry.listener.complete) entry.listener.complete(entry); + for (let ii = 0; ii < listeners.length; ii++) if (listeners[ii].complete) listeners[ii].complete(entry); + break; + case EventType.event: + const event = objects[i++ + 2] as Event; + if (entry.listener != null && entry.listener.event) entry.listener.event(entry, event); + for (let ii = 0; ii < listeners.length; ii++) if (listeners[ii].event) listeners[ii].event(entry, event); + break; + } + } + this.clear(); + + this.drainDisabled = false; + } + + clear() { + this.objects.length = 0; + } +} + +export enum EventType { + start, + interrupt, + end, + dispose, + complete, + event +} + +/** The interface to implement for receiving TrackEntry events. It is always safe to call AnimationState methods when receiving + * events. + * + * See TrackEntry {@link TrackEntry#listener} and AnimationState + * {@link AnimationState#addListener()}. */ +export interface AnimationStateListener { + /** Invoked when this entry has been set as the current entry. */ + start(entry: TrackEntry): void; + + /** Invoked when another entry has replaced this entry as the current entry. This entry may continue being applied for + * mixing. */ + interrupt(entry: TrackEntry): void; + + /** Invoked when this entry is no longer the current entry and will never be applied again. */ + end(entry: TrackEntry): void; + + /** Invoked when this entry will be disposed. This may occur without the entry ever being set as the current entry. + * References to the entry should not be kept after dispose is called, as it may be destroyed or reused. */ + dispose(entry: TrackEntry): void; + + /** Invoked every time this entry's animation completes a loop. */ + complete(entry: TrackEntry): void; + + /** Invoked when this entry's animation triggers an event. */ + event(entry: TrackEntry, event: Event): void; +} + +export abstract class AnimationStateAdapter implements AnimationStateListener { + start(entry: TrackEntry) {} + + interrupt(entry: TrackEntry) {} + + end(entry: TrackEntry) {} + + dispose(entry: TrackEntry) {} + + complete(entry: TrackEntry) {} + + event(entry: TrackEntry, event: Event) {} +} diff --git a/packages/spine-core-3.8/src/spine-core/AnimationStateData.ts b/packages/spine-core-3.8/src/spine-core/AnimationStateData.ts new file mode 100644 index 0000000000..009fcfad95 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/AnimationStateData.ts @@ -0,0 +1,48 @@ +import { SkeletonData } from "./SkeletonData"; +import { Animation } from "./Animation"; +import { Map } from "./Utils"; + +/** Stores mix (crossfade) durations to be applied when {@link AnimationState} animations are changed. */ +export class AnimationStateData { + /** The SkeletonData to look up animations when they are specified by name. */ + skeletonData: SkeletonData; + + animationToMixTime: Map = {}; + + /** The mix duration to use when no mix duration has been defined between two animations. */ + defaultMix = 0; + + constructor(skeletonData: SkeletonData) { + if (skeletonData == null) throw new Error("skeletonData cannot be null."); + this.skeletonData = skeletonData; + } + + /** Sets a mix duration by animation name. + * + * See {@link #setMixWith()}. */ + setMix(fromName: string, toName: string, duration: number) { + const from = this.skeletonData.findAnimation(fromName); + if (from == null) throw new Error("Animation not found: " + fromName); + const to = this.skeletonData.findAnimation(toName); + if (to == null) throw new Error("Animation not found: " + toName); + this.setMixWith(from, to, duration); + } + + /** Sets the mix duration when changing from the specified animation to the other. + * + * See {@link TrackEntry#mixDuration}. */ + setMixWith(from: Animation, to: Animation, duration: number) { + if (from == null) throw new Error("from cannot be null."); + if (to == null) throw new Error("to cannot be null."); + const key = from.name + "." + to.name; + this.animationToMixTime[key] = duration; + } + + /** Returns the mix duration to use when changing from the specified animation to the other, or the {@link #defaultMix} if + * no mix duration has been set. */ + getMix(from: Animation, to: Animation) { + const key = from.name + "." + to.name; + const value = this.animationToMixTime[key]; + return value === undefined ? this.defaultMix : value; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/AtlasAttachmentLoader.ts b/packages/spine-core-3.8/src/spine-core/AtlasAttachmentLoader.ts new file mode 100644 index 0000000000..eca31cbf2e --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/AtlasAttachmentLoader.ts @@ -0,0 +1,55 @@ +import { AttachmentLoader } from "./attachments/AttachmentLoader"; +import { TextureAtlas } from "./TextureAtlas"; +import { Skin } from "./Skin"; +import { RegionAttachment } from "./attachments/RegionAttachment"; +import { MeshAttachment } from "./attachments/MeshAttachment"; +import { BoundingBoxAttachment } from "./attachments/BoundingBoxAttachment"; +import { PathAttachment } from "./attachments/PathAttachment"; +import { PointAttachment } from "./attachments/PointAttachment"; +import { ClippingAttachment } from "./attachments/ClippingAttachment"; + +/** An {@link AttachmentLoader} that configures attachments using texture regions from an {@link TextureAtlas}. + * + * See [Loading skeleton data](http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data) in the + * Spine Runtimes Guide. */ +export class AtlasAttachmentLoader implements AttachmentLoader { + atlas: TextureAtlas; + + constructor(atlas: TextureAtlas) { + this.atlas = atlas; + } + + newRegionAttachment(skin: Skin, name: string, path: string): RegionAttachment { + const region = this.atlas.findRegion(path); + if (region == null) throw new Error("Region not found in atlas: " + path + " (region attachment: " + name + ")"); + region.renderObject = region; + const attachment = new RegionAttachment(name); + attachment.setRegion(region); + return attachment; + } + + newMeshAttachment(skin: Skin, name: string, path: string): MeshAttachment { + const region = this.atlas.findRegion(path); + if (region == null) throw new Error("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); + region.renderObject = region; + const attachment = new MeshAttachment(name); + attachment.region = region; + return attachment; + } + + newBoundingBoxAttachment(skin: Skin, name: string): BoundingBoxAttachment { + return new BoundingBoxAttachment(name); + } + + newPathAttachment(skin: Skin, name: string): PathAttachment { + return new PathAttachment(name); + } + + newPointAttachment(skin: Skin, name: string): PointAttachment { + return new PointAttachment(name); + } + + newClippingAttachment(skin: Skin, name: string): ClippingAttachment { + return new ClippingAttachment(name); + } +} diff --git a/packages/spine-core-3.8/src/spine-core/BlendMode.ts b/packages/spine-core-3.8/src/spine-core/BlendMode.ts new file mode 100644 index 0000000000..2f004954b4 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/BlendMode.ts @@ -0,0 +1,7 @@ +/** Determines how images are blended with existing pixels when drawn. */ +export enum BlendMode { + Normal, + Additive, + Multiply, + Screen +} diff --git a/packages/spine-core-3.8/src/spine-core/Bone.ts b/packages/spine-core-3.8/src/spine-core/Bone.ts new file mode 100644 index 0000000000..1d4c9b7ee3 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/Bone.ts @@ -0,0 +1,391 @@ +import { Updatable } from "./Updatable"; +import { BoneData, TransformMode } from "./BoneData"; +import { Skeleton } from "./Skeleton"; +import { MathUtils, Vector2 } from "./Utils"; + +/** Stores a bone's current pose. + * + * A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a + * local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a + * constraint or application code modifies the world transform after it was computed from the local transform. */ +export class Bone implements Updatable { + /** The bone's setup pose data. */ + data: BoneData; + + /** The skeleton this bone belongs to. */ + skeleton: Skeleton; + + /** The parent bone, or null if this is the root bone. */ + parent: Bone; + + /** The immediate children of this bone. */ + children = new Array(); + + /** The local x translation. */ + x = 0; + + /** The local y translation. */ + y = 0; + + /** The local rotation in degrees, counter clockwise. */ + rotation = 0; + + /** The local scaleX. */ + scaleX = 0; + + /** The local scaleY. */ + scaleY = 0; + + /** The local shearX. */ + shearX = 0; + + /** The local shearY. */ + shearY = 0; + + /** The applied local x translation. */ + ax = 0; + + /** The applied local y translation. */ + ay = 0; + + /** The applied local rotation in degrees, counter clockwise. */ + arotation = 0; + + /** The applied local scaleX. */ + ascaleX = 0; + + /** The applied local scaleY. */ + ascaleY = 0; + + /** The applied local shearX. */ + ashearX = 0; + + /** The applied local shearY. */ + ashearY = 0; + + /** If true, the applied transform matches the world transform. If false, the world transform has been modified since it was + * computed and {@link #updateAppliedTransform()} must be called before accessing the applied transform. */ + appliedValid = false; + + /** Part of the world transform matrix for the X axis. If changed, {@link #appliedValid} should be set to false. */ + a = 0; + + /** Part of the world transform matrix for the Y axis. If changed, {@link #appliedValid} should be set to false. */ + b = 0; + + /** Part of the world transform matrix for the X axis. If changed, {@link #appliedValid} should be set to false. */ + c = 0; + + /** Part of the world transform matrix for the Y axis. If changed, {@link #appliedValid} should be set to false. */ + d = 0; + + /** The world X position. If changed, {@link #appliedValid} should be set to false. */ + worldY = 0; + + /** The world Y position. If changed, {@link #appliedValid} should be set to false. */ + worldX = 0; + + sorted = false; + active = false; + + /** @param parent May be null. */ + constructor(data: BoneData, skeleton: Skeleton, parent: Bone) { + if (data == null) throw new Error("data cannot be null."); + if (skeleton == null) throw new Error("skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + this.setToSetupPose(); + } + + /** Returns false when the bone has not been computed because {@link BoneData#skinRequired} is true and the + * {@link Skeleton#skin active skin} does not {@link Skin#bones contain} this bone. */ + isActive() { + return this.active; + } + + /** Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}. */ + update() { + this.updateWorldTransformWith(this.x, this.y, this.rotation, this.scaleX, this.scaleY, this.shearX, this.shearY); + } + + /** Computes the world transform using the parent bone and this bone's local transform. + * + * See {@link #updateWorldTransformWith()}. */ + updateWorldTransform() { + this.updateWorldTransformWith(this.x, this.y, this.rotation, this.scaleX, this.scaleY, this.shearX, this.shearY); + } + + /** Computes the world transform using the parent bone and the specified local transform. Child bones are not updated. + * + * See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine + * Runtimes Guide. */ + updateWorldTransformWith( + x: number, + y: number, + rotation: number, + scaleX: number, + scaleY: number, + shearX: number, + shearY: number + ) { + this.ax = x; + this.ay = y; + this.arotation = rotation; + this.ascaleX = scaleX; + this.ascaleY = scaleY; + this.ashearX = shearX; + this.ashearY = shearY; + this.appliedValid = true; + + const parent = this.parent; + if (parent == null) { + // Root bone. + const skeleton = this.skeleton; + const rotationY = rotation + 90 + shearY; + const sx = skeleton.scaleX; + const sy = skeleton.scaleY; + this.a = MathUtils.cosDeg(rotation + shearX) * scaleX * sx; + this.b = MathUtils.cosDeg(rotationY) * scaleY * sx; + this.c = MathUtils.sinDeg(rotation + shearX) * scaleX * sy; + this.d = MathUtils.sinDeg(rotationY) * scaleY * sy; + this.worldX = x * sx + skeleton.x; + this.worldY = y * sy + skeleton.y; + return; + } + + let pa = parent.a, + pb = parent.b, + pc = parent.c, + pd = parent.d; + this.worldX = pa * x + pb * y + parent.worldX; + this.worldY = pc * x + pd * y + parent.worldY; + + switch (this.data.transformMode) { + case TransformMode.Normal: { + const rotationY = rotation + 90 + shearY; + const la = MathUtils.cosDeg(rotation + shearX) * scaleX; + const lb = MathUtils.cosDeg(rotationY) * scaleY; + const lc = MathUtils.sinDeg(rotation + shearX) * scaleX; + const ld = MathUtils.sinDeg(rotationY) * scaleY; + this.a = pa * la + pb * lc; + this.b = pa * lb + pb * ld; + this.c = pc * la + pd * lc; + this.d = pc * lb + pd * ld; + return; + } + case TransformMode.OnlyTranslation: { + const rotationY = rotation + 90 + shearY; + this.a = MathUtils.cosDeg(rotation + shearX) * scaleX; + this.b = MathUtils.cosDeg(rotationY) * scaleY; + this.c = MathUtils.sinDeg(rotation + shearX) * scaleX; + this.d = MathUtils.sinDeg(rotationY) * scaleY; + break; + } + case TransformMode.NoRotationOrReflection: { + let s = pa * pa + pc * pc; + let prx = 0; + if (s > 0.0001) { + s = Math.abs(pa * pd - pb * pc) / s; + pa /= this.skeleton.scaleX; + pc /= this.skeleton.scaleY; + pb = pc * s; + pd = pa * s; + prx = Math.atan2(pc, pa) * MathUtils.radDeg; + } else { + pa = 0; + pc = 0; + prx = 90 - Math.atan2(pd, pb) * MathUtils.radDeg; + } + const rx = rotation + shearX - prx; + const ry = rotation + shearY - prx + 90; + const la = MathUtils.cosDeg(rx) * scaleX; + const lb = MathUtils.cosDeg(ry) * scaleY; + const lc = MathUtils.sinDeg(rx) * scaleX; + const ld = MathUtils.sinDeg(ry) * scaleY; + this.a = pa * la - pb * lc; + this.b = pa * lb - pb * ld; + this.c = pc * la + pd * lc; + this.d = pc * lb + pd * ld; + break; + } + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: { + const cos = MathUtils.cosDeg(rotation); + const sin = MathUtils.sinDeg(rotation); + let za = (pa * cos + pb * sin) / this.skeleton.scaleX; + let zc = (pc * cos + pd * sin) / this.skeleton.scaleY; + let s = Math.sqrt(za * za + zc * zc); + if (s > 0.00001) s = 1 / s; + za *= s; + zc *= s; + s = Math.sqrt(za * za + zc * zc); + if ( + this.data.transformMode == TransformMode.NoScale && + pa * pd - pb * pc < 0 != (this.skeleton.scaleX < 0 != this.skeleton.scaleY < 0) + ) + s = -s; + const r = Math.PI / 2 + Math.atan2(zc, za); + const zb = Math.cos(r) * s; + const zd = Math.sin(r) * s; + const la = MathUtils.cosDeg(shearX) * scaleX; + const lb = MathUtils.cosDeg(90 + shearY) * scaleY; + const lc = MathUtils.sinDeg(shearX) * scaleX; + const ld = MathUtils.sinDeg(90 + shearY) * scaleY; + this.a = za * la + zb * lc; + this.b = za * lb + zb * ld; + this.c = zc * la + zd * lc; + this.d = zc * lb + zd * ld; + break; + } + } + this.a *= this.skeleton.scaleX; + this.b *= this.skeleton.scaleX; + this.c *= this.skeleton.scaleY; + this.d *= this.skeleton.scaleY; + } + + /** Sets this bone's local transform to the setup pose. */ + setToSetupPose() { + const data = this.data; + this.x = data.x; + this.y = data.y; + this.rotation = data.rotation; + this.scaleX = data.scaleX; + this.scaleY = data.scaleY; + this.shearX = data.shearX; + this.shearY = data.shearY; + } + + /** The world rotation for the X axis, calculated using {@link #a} and {@link #c}. */ + getWorldRotationX() { + return Math.atan2(this.c, this.a) * MathUtils.radDeg; + } + + /** The world rotation for the Y axis, calculated using {@link #b} and {@link #d}. */ + getWorldRotationY() { + return Math.atan2(this.d, this.b) * MathUtils.radDeg; + } + + /** The magnitude (always positive) of the world scale X, calculated using {@link #a} and {@link #c}. */ + getWorldScaleX() { + return Math.sqrt(this.a * this.a + this.c * this.c); + } + + /** The magnitude (always positive) of the world scale Y, calculated using {@link #b} and {@link #d}. */ + getWorldScaleY() { + return Math.sqrt(this.b * this.b + this.d * this.d); + } + + /** Computes the applied transform values from the world transform. This allows the applied transform to be accessed after the + * world transform has been modified (by a constraint, {@link #rotateWorld()}, etc). + * + * If {@link #updateWorldTransform()} has been called for a bone and {@link #appliedValid} is false, then + * {@link #updateAppliedTransform()} must be called before accessing the applied transform. + * + * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after + * calling this method is equivalent to the local tranform used to compute the world transform, but may not be identical. */ + updateAppliedTransform() { + this.appliedValid = true; + const parent = this.parent; + if (parent == null) { + this.ax = this.worldX; + this.ay = this.worldY; + this.arotation = Math.atan2(this.c, this.a) * MathUtils.radDeg; + this.ascaleX = Math.sqrt(this.a * this.a + this.c * this.c); + this.ascaleY = Math.sqrt(this.b * this.b + this.d * this.d); + this.ashearX = 0; + this.ashearY = + Math.atan2(this.a * this.b + this.c * this.d, this.a * this.d - this.b * this.c) * MathUtils.radDeg; + return; + } + const pa = parent.a, + pb = parent.b, + pc = parent.c, + pd = parent.d; + const pid = 1 / (pa * pd - pb * pc); + const dx = this.worldX - parent.worldX, + dy = this.worldY - parent.worldY; + this.ax = dx * pd * pid - dy * pb * pid; + this.ay = dy * pa * pid - dx * pc * pid; + const ia = pid * pd; + const id = pid * pa; + const ib = pid * pb; + const ic = pid * pc; + const ra = ia * this.a - ib * this.c; + const rb = ia * this.b - ib * this.d; + const rc = id * this.c - ic * this.a; + const rd = id * this.d - ic * this.b; + this.ashearX = 0; + this.ascaleX = Math.sqrt(ra * ra + rc * rc); + if (this.ascaleX > 0.0001) { + const det = ra * rd - rb * rc; + this.ascaleY = det / this.ascaleX; + this.ashearY = Math.atan2(ra * rb + rc * rd, det) * MathUtils.radDeg; + this.arotation = Math.atan2(rc, ra) * MathUtils.radDeg; + } else { + this.ascaleX = 0; + this.ascaleY = Math.sqrt(rb * rb + rd * rd); + this.ashearY = 0; + this.arotation = 90 - Math.atan2(rd, rb) * MathUtils.radDeg; + } + } + + /** Transforms a point from world coordinates to the bone's local coordinates. */ + worldToLocal(world: Vector2) { + const a = this.a, + b = this.b, + c = this.c, + d = this.d; + const invDet = 1 / (a * d - b * c); + const x = world.x - this.worldX, + y = world.y - this.worldY; + world.x = x * d * invDet - y * b * invDet; + world.y = y * a * invDet - x * c * invDet; + return world; + } + + /** Transforms a point from the bone's local coordinates to world coordinates. */ + localToWorld(local: Vector2) { + const x = local.x, + y = local.y; + local.x = x * this.a + y * this.b + this.worldX; + local.y = x * this.c + y * this.d + this.worldY; + return local; + } + + /** Transforms a world rotation to a local rotation. */ + worldToLocalRotation(worldRotation: number) { + const sin = MathUtils.sinDeg(worldRotation), + cos = MathUtils.cosDeg(worldRotation); + return ( + Math.atan2(this.a * sin - this.c * cos, this.d * cos - this.b * sin) * MathUtils.radDeg + + this.rotation - + this.shearX + ); + } + + /** Transforms a local rotation to a world rotation. */ + localToWorldRotation(localRotation: number) { + localRotation -= this.rotation - this.shearX; + const sin = MathUtils.sinDeg(localRotation), + cos = MathUtils.cosDeg(localRotation); + return Math.atan2(cos * this.c + sin * this.d, cos * this.a + sin * this.b) * MathUtils.radDeg; + } + + /** Rotates the world transform the specified amount and sets {@link #appliedValid} to false. + * {@link #updateWorldTransform()} will need to be called on any child bones, recursively, and any constraints reapplied. */ + rotateWorld(degrees: number) { + const a = this.a, + b = this.b, + c = this.c, + d = this.d; + const cos = MathUtils.cosDeg(degrees), + sin = MathUtils.sinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + this.appliedValid = false; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/BoneData.ts b/packages/spine-core-3.8/src/spine-core/BoneData.ts new file mode 100644 index 0000000000..a51d1b16cf --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/BoneData.ts @@ -0,0 +1,66 @@ +import { Color } from "./Utils"; + +/** Stores the setup pose for a {@link Bone}. */ +export class BoneData { + /** The index of the bone in {@link Skeleton#getBones()}. */ + index: number; + + /** The name of the bone, which is unique across all bones in the skeleton. */ + name: string; + + /** @returns May be null. */ + parent: BoneData; + + /** The bone's length. */ + length: number; + + /** The local x translation. */ + x = 0; + + /** The local y translation. */ + y = 0; + + /** The local rotation. */ + rotation = 0; + + /** The local scaleX. */ + scaleX = 1; + + /** The local scaleY. */ + scaleY = 1; + + /** The local shearX. */ + shearX = 0; + + /** The local shearX. */ + shearY = 0; + + /** The transform mode for how parent world transforms affect this bone. */ + transformMode = TransformMode.Normal; + + /** When true, {@link Skeleton#updateWorldTransform()} only updates this bone if the {@link Skeleton#skin} contains this + * bone. + * @see Skin#bones */ + skinRequired = false; + + /** The color of the bone as it was in Spine. Available only when nonessential data was exported. Bones are not usually + * rendered at runtime. */ + color = new Color(); + + constructor(index: number, name: string, parent: BoneData) { + if (index < 0) throw new Error("index must be >= 0."); + if (name == null) throw new Error("name cannot be null."); + this.index = index; + this.name = name; + this.parent = parent; + } +} + +/** Determines how a bone inherits world transforms from parent bones. */ +export enum TransformMode { + Normal, + OnlyTranslation, + NoRotationOrReflection, + NoScale, + NoScaleOrReflection +} diff --git a/packages/spine-core-3.8/src/spine-core/ConstraintData.ts b/packages/spine-core-3.8/src/spine-core/ConstraintData.ts new file mode 100644 index 0000000000..5d928d2169 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/ConstraintData.ts @@ -0,0 +1,8 @@ +/** The base class for all constraint datas. */ +export abstract class ConstraintData { + constructor( + public name: string, + public order: number, + public skinRequired: boolean + ) {} +} diff --git a/packages/spine-core-3.8/src/spine-core/Event.ts b/packages/spine-core-3.8/src/spine-core/Event.ts new file mode 100644 index 0000000000..a6318c90a9 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/Event.ts @@ -0,0 +1,22 @@ +import { EventData } from "./EventData"; + +/** Stores the current pose values for an {@link Event}. + * + * See Timeline {@link Timeline#apply()}, + * AnimationStateListener {@link AnimationStateListener#event()}, and + * [Events](http://esotericsoftware.com/spine-events) in the Spine User Guide. */ +export class Event { + data: EventData; + intValue: number; + floatValue: number; + stringValue: string; + time: number; + volume: number; + balance: number; + + constructor(time: number, data: EventData) { + if (data == null) throw new Error("data cannot be null."); + this.time = time; + this.data = data; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/EventData.ts b/packages/spine-core-3.8/src/spine-core/EventData.ts new file mode 100644 index 0000000000..46aa71702e --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/EventData.ts @@ -0,0 +1,16 @@ +/** Stores the setup pose values for an {@link Event}. + * + * See [Events](http://esotericsoftware.com/spine-events) in the Spine User Guide. */ +export class EventData { + name: string; + intValue: number; + floatValue: number; + stringValue: string; + audioPath: string; + volume: number; + balance: number; + + constructor(name: string) { + this.name = name; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/IkConstraint.ts b/packages/spine-core-3.8/src/spine-core/IkConstraint.ts new file mode 100644 index 0000000000..7f05ce8e04 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/IkConstraint.ts @@ -0,0 +1,346 @@ +import { Updatable } from "./Updatable"; +import { IkConstraintData } from "./IkConstraintData"; +import { Bone } from "./Bone"; +import { Skeleton } from "./Skeleton"; +import { TransformMode } from "./BoneData"; +import { MathUtils } from "./Utils"; + +/** Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of + * the last bone is as close to the target bone as possible. + * + * See [IK constraints](http://esotericsoftware.com/spine-ik-constraints) in the Spine User Guide. */ +export class IkConstraint implements Updatable { + /** The IK constraint's setup pose data. */ + data: IkConstraintData; + + /** The bones that will be modified by this IK constraint. */ + bones: Array; + + /** The bone that is the IK target. */ + target: Bone; + + /** Controls the bend direction of the IK bones, either 1 or -1. */ + bendDirection = 0; + + /** When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. */ + compress = false; + + /** When true, if the target is out of range, the parent bone is scaled to reach it. If more than one bone is being constrained + * and the parent bone has local nonuniform scale, stretch is not applied. */ + stretch = false; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */ + mix = 1; + + /** For two bone IK, the distance from the maximum reach of the bones that rotation will slow. */ + softness = 0; + active = false; + + constructor(data: IkConstraintData, skeleton: Skeleton) { + if (data == null) throw new Error("data cannot be null."); + if (skeleton == null) throw new Error("skeleton cannot be null."); + this.data = data; + this.mix = data.mix; + this.softness = data.softness; + this.bendDirection = data.bendDirection; + this.compress = data.compress; + this.stretch = data.stretch; + + this.bones = new Array(); + for (let i = 0; i < data.bones.length; i++) this.bones.push(skeleton.findBone(data.bones[i].name)); + this.target = skeleton.findBone(data.target.name); + } + + isActive() { + return this.active; + } + + /** Applies the constraint to the constrained bones. */ + apply() { + this.update(); + } + + update() { + const target = this.target; + const bones = this.bones; + switch (bones.length) { + case 1: + this.apply1(bones[0], target.worldX, target.worldY, this.compress, this.stretch, this.data.uniform, this.mix); + break; + case 2: + this.apply2( + bones[0], + bones[1], + target.worldX, + target.worldY, + this.bendDirection, + this.stretch, + this.softness, + this.mix + ); + break; + } + } + + /** Applies 1 bone IK. The target is specified in the world coordinate system. */ + apply1( + bone: Bone, + targetX: number, + targetY: number, + compress: boolean, + stretch: boolean, + uniform: boolean, + alpha: number + ) { + if (!bone.appliedValid) bone.updateAppliedTransform(); + const p = bone.parent; + + let pa = p.a, + pb = p.b, + pc = p.c, + pd = p.d; + let rotationIK = -bone.ashearX - bone.arotation, + tx = 0, + ty = 0; + + switch (bone.data.transformMode) { + case TransformMode.OnlyTranslation: + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; + break; + case TransformMode.NoRotationOrReflection: + const s = Math.abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + const sa = pa / bone.skeleton.scaleX; + const sc = pc / bone.skeleton.scaleY; + pb = -sc * s * bone.skeleton.scaleX; + pd = sa * s * bone.skeleton.scaleY; + rotationIK += Math.atan2(sc, sa) * MathUtils.radDeg; + // Fall through + default: + const x = targetX - p.worldX, + y = targetY - p.worldY; + const d = pa * pd - pb * pc; + tx = (x * pd - y * pb) / d - bone.ax; + ty = (y * pa - x * pc) / d - bone.ay; + } + rotationIK += Math.atan2(ty, tx) * MathUtils.radDeg; + if (bone.ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) rotationIK -= 360; + else if (rotationIK < -180) rotationIK += 360; + let sx = bone.ascaleX, + sy = bone.ascaleY; + if (compress || stretch) { + switch (bone.data.transformMode) { + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; + } + const b = bone.data.length * sx, + dd = Math.sqrt(tx * tx + ty * ty); + if ((compress && dd < b) || (stretch && dd > b && b > 0.0001)) { + const s = (dd / b - 1) * alpha + 1; + sx *= s; + if (uniform) sy *= s; + } + } + bone.updateWorldTransformWith( + bone.ax, + bone.ay, + bone.arotation + rotationIK * alpha, + sx, + sy, + bone.ashearX, + bone.ashearY + ); + } + + /** Applies 2 bone IK. The target is specified in the world coordinate system. + * @param child A direct descendant of the parent bone. */ + apply2( + parent: Bone, + child: Bone, + targetX: number, + targetY: number, + bendDir: number, + stretch: boolean, + softness: number, + alpha: number + ) { + if (alpha == 0) { + child.updateWorldTransform(); + return; + } + if (!parent.appliedValid) parent.updateAppliedTransform(); + if (!child.appliedValid) child.updateAppliedTransform(); + let px = parent.ax, + py = parent.ay, + psx = parent.ascaleX, + sx = psx, + psy = parent.ascaleY, + csx = child.ascaleX; + let os1 = 0, + os2 = 0, + s2 = 0; + if (psx < 0) { + psx = -psx; + os1 = 180; + s2 = -1; + } else { + os1 = 0; + s2 = 1; + } + if (psy < 0) { + psy = -psy; + s2 = -s2; + } + if (csx < 0) { + csx = -csx; + os2 = 180; + } else os2 = 0; + let cx = child.ax, + cy = 0, + cwx = 0, + cwy = 0, + a = parent.a, + b = parent.b, + c = parent.c, + d = parent.d; + const u = Math.abs(psx - psy) <= 0.0001; + if (!u) { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } else { + cy = child.ay; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } + const pp = parent.parent; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + let id = 1 / (a * d - b * c), + x = cwx - pp.worldX, + y = cwy - pp.worldY; + const dx = (x * d - y * b) * id - px, + dy = (y * a - x * c) * id - py; + let l1 = Math.sqrt(dx * dx + dy * dy), + l2 = child.data.length * csx, + a1, + a2; + if (l1 < 0.0001) { + this.apply1(parent, targetX, targetY, false, stretch, false, alpha); + child.updateWorldTransformWith(cx, cy, 0, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + return; + } + x = targetX - pp.worldX; + y = targetY - pp.worldY; + let tx = (x * d - y * b) * id - px, + ty = (y * a - x * c) * id - py; + let dd = tx * tx + ty * ty; + if (softness != 0) { + softness *= (psx * (csx + 1)) / 2; + const td = Math.sqrt(dd), + sd = td - l1 - l2 * psx + softness; + if (sd > 0) { + let p = Math.min(1, sd / (softness * 2)) - 1; + p = (sd - softness * (1 - p * p)) / td; + tx -= p * tx; + ty -= p * ty; + dd = tx * tx + ty * ty; + } + } + outer: if (u) { + l2 *= psx; + let cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) cos = -1; + else if (cos > 1) { + cos = 1; + if (stretch) sx *= (Math.sqrt(dd) / (l1 + l2) - 1) * alpha + 1; + } + a2 = Math.acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * Math.sin(a2); + a1 = Math.atan2(ty * a - tx * b, tx * a + ty * b); + } else { + a = psx * l2; + b = psy * l2; + const aa = a * a, + bb = b * b, + ta = Math.atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + const c1 = -2 * bb * l1, + c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) { + let q = Math.sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) / 2; + const r0 = q / c2, + r1 = c / q; + const r = Math.abs(r0) < Math.abs(r1) ? r0 : r1; + if (r * r <= dd) { + y = Math.sqrt(dd - r * r) * bendDir; + a1 = ta - Math.atan2(y, r); + a2 = Math.atan2(y / psy, (r - l1) / psx); + break outer; + } + } + let minAngle = MathUtils.PI, + minX = l1 - a, + minDist = minX * minX, + minY = 0; + let maxAngle = 0, + maxX = l1 + a, + maxDist = maxX * maxX, + maxY = 0; + c = (-a * l1) / (aa - bb); + if (c >= -1 && c <= 1) { + c = Math.acos(c); + x = a * Math.cos(c) + l1; + y = b * Math.sin(c); + d = x * x + y * y; + if (d < minDist) { + minAngle = c; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) { + maxAngle = c; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) / 2) { + a1 = ta - Math.atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } else { + a1 = ta - Math.atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + const os = Math.atan2(cy, cx) * s2; + let rotation = parent.arotation; + a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation; + if (a1 > 180) a1 -= 360; + else if (a1 < -180) a1 += 360; + parent.updateWorldTransformWith(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0); + rotation = child.arotation; + a2 = ((a2 + os) * MathUtils.radDeg - child.ashearX) * s2 + os2 - rotation; + if (a2 > 180) a2 -= 360; + else if (a2 < -180) a2 += 360; + child.updateWorldTransformWith( + cx, + cy, + rotation + a2 * alpha, + child.ascaleX, + child.ascaleY, + child.ashearX, + child.ashearY + ); + } +} diff --git a/packages/spine-core-3.8/src/spine-core/IkConstraintData.ts b/packages/spine-core-3.8/src/spine-core/IkConstraintData.ts new file mode 100644 index 0000000000..200085b990 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/IkConstraintData.ts @@ -0,0 +1,37 @@ +import { ConstraintData } from "./ConstraintData"; +import { BoneData } from "./BoneData"; + +/** Stores the setup pose for an {@link IkConstraint}. + *

    + * See [IK constraints](http://esotericsoftware.com/spine-ik-constraints) in the Spine User Guide. */ +export class IkConstraintData extends ConstraintData { + /** The bones that are constrained by this IK constraint. */ + bones = new Array(); + + /** The bone that is the IK target. */ + target: BoneData; + + /** Controls the bend direction of the IK bones, either 1 or -1. */ + bendDirection = 1; + + /** When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. */ + compress = false; + + /** When true, if the target is out of range, the parent bone is scaled to reach it. If more than one bone is being constrained + * and the parent bone has local nonuniform scale, stretch is not applied. */ + stretch = false; + + /** When true, only a single bone is being constrained, and {@link #getCompress()} or {@link #getStretch()} is used, the bone + * is scaled on both the X and Y axes. */ + uniform = false; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */ + mix = 1; + + /** For two bone IK, the distance from the maximum reach of the bones that rotation will slow. */ + softness = 0; + + constructor(name: string) { + super(name, 0, false); + } +} diff --git a/packages/spine-core-3.8/src/spine-core/PathConstraint.ts b/packages/spine-core-3.8/src/spine-core/PathConstraint.ts new file mode 100644 index 0000000000..057ebd1f20 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/PathConstraint.ts @@ -0,0 +1,500 @@ +import { Updatable } from "./Updatable"; +import { PathConstraintData, SpacingMode, RotateMode, PositionMode } from "./PathConstraintData"; +import { Bone } from "./Bone"; +import { Slot } from "./Slot"; +import { Skeleton } from "./Skeleton"; +import { PathAttachment } from "./attachments/PathAttachment"; +import { Utils, MathUtils } from "./Utils"; + +/** Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the + * constrained bones so they follow a {@link PathAttachment}. + * + * See [Path constraints](http://esotericsoftware.com/spine-path-constraints) in the Spine User Guide. */ +export class PathConstraint implements Updatable { + static NONE = -1; + static BEFORE = -2; + static AFTER = -3; + static epsilon = 0.00001; + + /** The path constraint's setup pose data. */ + data: PathConstraintData; + + /** The bones that will be modified by this path constraint. */ + bones: Array; + + /** The slot whose path attachment will be used to constrained the bones. */ + target: Slot; + + /** The position along the path. */ + position = 0; + + /** The spacing between bones. */ + spacing = 0; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */ + rotateMix = 0; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained translations. */ + translateMix = 0; + + spaces = new Array(); + positions = new Array(); + world = new Array(); + curves = new Array(); + lengths = new Array(); + segments = new Array(); + + active = false; + + constructor(data: PathConstraintData, skeleton: Skeleton) { + if (data == null) throw new Error("data cannot be null."); + if (skeleton == null) throw new Error("skeleton cannot be null."); + this.data = data; + this.bones = new Array(); + for (let i = 0, n = data.bones.length; i < n; i++) this.bones.push(skeleton.findBone(data.bones[i].name)); + this.target = skeleton.findSlot(data.target.name); + this.position = data.position; + this.spacing = data.spacing; + this.rotateMix = data.rotateMix; + this.translateMix = data.translateMix; + } + + isActive() { + return this.active; + } + + /** Applies the constraint to the constrained bones. */ + apply() { + this.update(); + } + + update() { + const attachment = this.target.getAttachment(); + if (!(attachment instanceof PathAttachment)) return; + + const rotateMix = this.rotateMix, + translateMix = this.translateMix; + const translate = translateMix > 0, + rotate = rotateMix > 0; + if (!translate && !rotate) return; + + const data = this.data; + const percentSpacing = data.spacingMode == SpacingMode.Percent; + const rotateMode = data.rotateMode; + const tangents = rotateMode == RotateMode.Tangent, + scale = rotateMode == RotateMode.ChainScale; + const boneCount = this.bones.length, + spacesCount = tangents ? boneCount : boneCount + 1; + const bones = this.bones; + let spaces = Utils.setArraySize(this.spaces, spacesCount), + lengths: Array = null; + const spacing = this.spacing; + if (scale || !percentSpacing) { + if (scale) lengths = Utils.setArraySize(this.lengths, boneCount); + const lengthSpacing = data.spacingMode == SpacingMode.Length; + for (let i = 0, n = spacesCount - 1; i < n; ) { + const bone = bones[i]; + const setupLength = bone.data.length; + if (setupLength < PathConstraint.epsilon) { + if (scale) lengths[i] = 0; + spaces[++i] = 0; + } else if (percentSpacing) { + if (scale) { + const x = setupLength * bone.a, + y = setupLength * bone.c; + const length = Math.sqrt(x * x + y * y); + lengths[i] = length; + } + spaces[++i] = spacing; + } else { + const x = setupLength * bone.a, + y = setupLength * bone.c; + const length = Math.sqrt(x * x + y * y); + if (scale) lengths[i] = length; + spaces[++i] = ((lengthSpacing ? setupLength + spacing : spacing) * length) / setupLength; + } + } + } else { + for (let i = 1; i < spacesCount; i++) spaces[i] = spacing; + } + + const positions = this.computeWorldPositions( + attachment, + spacesCount, + tangents, + data.positionMode == PositionMode.Percent, + percentSpacing + ); + let boneX = positions[0], + boneY = positions[1], + offsetRotation = data.offsetRotation; + let tip = false; + if (offsetRotation == 0) tip = rotateMode == RotateMode.Chain; + else { + tip = false; + const p = this.target.bone; + offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.degRad : -MathUtils.degRad; + } + for (let i = 0, p = 3; i < boneCount; i++, p += 3) { + const bone = bones[i]; + bone.worldX += (boneX - bone.worldX) * translateMix; + bone.worldY += (boneY - bone.worldY) * translateMix; + const x = positions[p], + y = positions[p + 1], + dx = x - boneX, + dy = y - boneY; + if (scale) { + const length = lengths[i]; + if (length != 0) { + const s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; + bone.a *= s; + bone.c *= s; + } + } + boneX = x; + boneY = y; + if (rotate) { + let a = bone.a, + b = bone.b, + c = bone.c, + d = bone.d, + r = 0, + cos = 0, + sin = 0; + if (tangents) r = positions[p - 1]; + else if (spaces[i + 1] == 0) r = positions[p + 2]; + else r = Math.atan2(dy, dx); + r -= Math.atan2(c, a); + if (tip) { + cos = Math.cos(r); + sin = Math.sin(r); + const length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * rotateMix; + boneY += (length * (sin * a + cos * c) - dy) * rotateMix; + } else { + r += offsetRotation; + } + if (r > MathUtils.PI) r -= MathUtils.PI2; + else if (r < -MathUtils.PI) + // + r += MathUtils.PI2; + r *= rotateMix; + cos = Math.cos(r); + sin = Math.sin(r); + bone.a = cos * a - sin * c; + bone.b = cos * b - sin * d; + bone.c = sin * a + cos * c; + bone.d = sin * b + cos * d; + } + bone.appliedValid = false; + } + } + + computeWorldPositions( + path: PathAttachment, + spacesCount: number, + tangents: boolean, + percentPosition: boolean, + percentSpacing: boolean + ) { + const target = this.target; + let position = this.position; + let spaces = this.spaces, + out = Utils.setArraySize(this.positions, spacesCount * 3 + 2), + world: Array = null; + const closed = path.closed; + let verticesLength = path.worldVerticesLength, + curveCount = verticesLength / 6, + prevCurve = PathConstraint.NONE; + + if (!path.constantSpeed) { + const lengths = path.lengths; + curveCount -= closed ? 1 : 2; + const pathLength = lengths[curveCount]; + if (percentPosition) position *= pathLength; + if (percentSpacing) { + for (let i = 1; i < spacesCount; i++) spaces[i] *= pathLength; + } + world = Utils.setArraySize(this.world, 8); + for (let i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { + const space = spaces[i]; + position += space; + let p = position; + + if (closed) { + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } else if (p < 0) { + if (prevCurve != PathConstraint.BEFORE) { + prevCurve = PathConstraint.BEFORE; + path.computeWorldVertices(target, 2, 4, world, 0, 2); + } + this.addBeforePosition(p, world, 0, out, o); + continue; + } else if (p > pathLength) { + if (prevCurve != PathConstraint.AFTER) { + prevCurve = PathConstraint.AFTER; + path.computeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); + } + this.addAfterPosition(p - pathLength, world, 0, out, o); + continue; + } + + // Determine curve containing position. + for (; ; curve++) { + const length = lengths[curve]; + if (p > length) continue; + if (curve == 0) p /= length; + else { + const prev = lengths[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + if (curve != prevCurve) { + prevCurve = curve; + if (closed && curve == curveCount) { + path.computeWorldVertices(target, verticesLength - 4, 4, world, 0, 2); + path.computeWorldVertices(target, 0, 4, world, 4, 2); + } else path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); + } + this.addCurvePosition( + p, + world[0], + world[1], + world[2], + world[3], + world[4], + world[5], + world[6], + world[7], + out, + o, + tangents || (i > 0 && space == 0) + ); + } + return out; + } + + // World vertices. + if (closed) { + verticesLength += 2; + world = Utils.setArraySize(this.world, verticesLength); + path.computeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); + path.computeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } else { + curveCount--; + verticesLength -= 4; + world = Utils.setArraySize(this.world, verticesLength); + path.computeWorldVertices(target, 2, verticesLength, world, 0, 2); + } + + // Curve lengths. + const curves = Utils.setArraySize(this.curves, curveCount); + let pathLength = 0; + let x1 = world[0], + y1 = world[1], + cx1 = 0, + cy1 = 0, + cx2 = 0, + cy2 = 0, + x2 = 0, + y2 = 0; + let tmpx = 0, + tmpy = 0, + dddfx = 0, + dddfy = 0, + ddfx = 0, + ddfy = 0, + dfx = 0, + dfy = 0; + for (let i = 0, w = 2; i < curveCount; i++, w += 6) { + cx1 = world[w]; + cy1 = world[w + 1]; + cx2 = world[w + 2]; + cy2 = world[w + 3]; + x2 = world[w + 4]; + y2 = world[w + 5]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.1875; + tmpy = (y1 - cy1 * 2 + cy2) * 0.1875; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.75 + tmpx + dddfx * 0.16666667; + dfy = (cy1 - y1) * 0.75 + tmpy + dddfy * 0.16666667; + pathLength += Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += Math.sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += Math.sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + if (percentPosition) position *= pathLength; + else position *= pathLength / path.lengths[curveCount - 1]; + if (percentSpacing) { + for (let i = 1; i < spacesCount; i++) spaces[i] *= pathLength; + } + + const segments = this.segments; + let curveLength = 0; + for (let i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { + const space = spaces[i]; + position += space; + let p = position; + + if (closed) { + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } else if (p < 0) { + this.addBeforePosition(p, world, 0, out, o); + continue; + } else if (p > pathLength) { + this.addAfterPosition(p - pathLength, world, verticesLength - 4, out, o); + continue; + } + + // Determine curve containing position. + for (; ; curve++) { + const length = curves[curve]; + if (p > length) continue; + if (curve == 0) p /= length; + else { + const prev = curves[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + + // Curve segment lengths. + if (curve != prevCurve) { + prevCurve = curve; + let ii = curve * 6; + x1 = world[ii]; + y1 = world[ii + 1]; + cx1 = world[ii + 2]; + cy1 = world[ii + 3]; + cx2 = world[ii + 4]; + cy2 = world[ii + 5]; + x2 = world[ii + 6]; + y2 = world[ii + 7]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.03; + tmpy = (y1 - cy1 * 2 + cy2) * 0.03; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.3 + tmpx + dddfx * 0.16666667; + dfy = (cy1 - y1) * 0.3 + tmpy + dddfy * 0.16666667; + curveLength = Math.sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += Math.sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += Math.sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += Math.sqrt(dfx * dfx + dfy * dfy); + segments[9] = curveLength; + segment = 0; + } + + // Weight by segment length. + p *= curveLength; + for (; ; segment++) { + const length = segments[segment]; + if (p > length) continue; + if (segment == 0) p /= length; + else { + const prev = segments[segment - 1]; + p = segment + (p - prev) / (length - prev); + } + break; + } + this.addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0)); + } + return out; + } + + addBeforePosition(p: number, temp: Array, i: number, out: Array, o: number) { + const x1 = temp[i], + y1 = temp[i + 1], + dx = temp[i + 2] - x1, + dy = temp[i + 3] - y1, + r = Math.atan2(dy, dx); + out[o] = x1 + p * Math.cos(r); + out[o + 1] = y1 + p * Math.sin(r); + out[o + 2] = r; + } + + addAfterPosition(p: number, temp: Array, i: number, out: Array, o: number) { + const x1 = temp[i + 2], + y1 = temp[i + 3], + dx = x1 - temp[i], + dy = y1 - temp[i + 1], + r = Math.atan2(dy, dx); + out[o] = x1 + p * Math.cos(r); + out[o + 1] = y1 + p * Math.sin(r); + out[o + 2] = r; + } + + addCurvePosition( + p: number, + x1: number, + y1: number, + cx1: number, + cy1: number, + cx2: number, + cy2: number, + x2: number, + y2: number, + out: Array, + o: number, + tangents: boolean + ) { + if (p == 0 || isNaN(p)) { + out[o] = x1; + out[o + 1] = y1; + out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1); + return; + } + const tt = p * p, + ttt = tt * p, + u = 1 - p, + uu = u * u, + uuu = uu * u; + const ut = u * p, + ut3 = ut * 3, + uut3 = u * ut3, + utt3 = ut3 * p; + const x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, + y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; + out[o] = x; + out[o + 1] = y; + if (tangents) { + if (p < 0.001) out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1); + else out[o + 2] = Math.atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } +} diff --git a/packages/spine-core-3.8/src/spine-core/PathConstraintData.ts b/packages/spine-core-3.8/src/spine-core/PathConstraintData.ts new file mode 100644 index 0000000000..32f5bafe50 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/PathConstraintData.ts @@ -0,0 +1,68 @@ +import { ConstraintData } from "./ConstraintData"; +import { BoneData } from "./BoneData"; +import { SlotData } from "./SlotData"; + +/** Stores the setup pose for a {@link PathConstraint}. + * + * See [Path constraints](http://esotericsoftware.com/spine-path-constraints) in the Spine User Guide. */ +export class PathConstraintData extends ConstraintData { + /** The bones that will be modified by this path constraint. */ + bones = new Array(); + + /** The slot whose path attachment will be used to constrained the bones. */ + target: SlotData; + + /** The mode for positioning the first bone on the path. */ + positionMode: PositionMode; + + /** The mode for positioning the bones after the first bone on the path. */ + spacingMode: SpacingMode; + + /** The mode for adjusting the rotation of the bones. */ + rotateMode: RotateMode; + + /** An offset added to the constrained bone rotation. */ + offsetRotation: number; + + /** The position along the path. */ + position: number; + + /** The spacing between bones. */ + spacing: number; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */ + rotateMix: number; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained translations. */ + translateMix: number; + + constructor(name: string) { + super(name, 0, false); + } +} + +/** Controls how the first bone is positioned along the path. + * + * See [Position mode](http://esotericsoftware.com/spine-path-constraints#Position-mode) in the Spine User Guide. */ +export enum PositionMode { + Fixed, + Percent +} + +/** Controls how bones after the first bone are positioned along the path. + * + * [Spacing mode](http://esotericsoftware.com/spine-path-constraints#Spacing-mode) in the Spine User Guide. */ +export enum SpacingMode { + Length, + Fixed, + Percent +} + +/** Controls how bones are rotated, translated, and scaled to match the path. + * + * [Rotate mode](http://esotericsoftware.com/spine-path-constraints#Rotate-mod) in the Spine User Guide. */ +export enum RotateMode { + Tangent, + Chain, + ChainScale +} diff --git a/packages/spine-core-3.8/src/spine-core/Skeleton.ts b/packages/spine-core-3.8/src/spine-core/Skeleton.ts new file mode 100644 index 0000000000..2573e25dd9 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/Skeleton.ts @@ -0,0 +1,592 @@ +import { SkeletonData } from "./SkeletonData"; +import { Bone } from "./Bone"; +import { Slot } from "./Slot"; +import { IkConstraint } from "./IkConstraint"; +import { TransformConstraint } from "./TransformConstraint"; +import { PathConstraint } from "./PathConstraint"; +import { Updatable } from "./Updatable"; +import { Skin } from "./Skin"; +import { Color, Utils, Vector2 } from "./Utils"; +import { PathAttachment } from "./attachments/PathAttachment"; +import { Attachment } from "./attachments/Attachment"; +import { RegionAttachment } from "./attachments/RegionAttachment"; +import { MeshAttachment } from "./attachments/MeshAttachment"; + +/** Stores the current pose for a skeleton. + * + * See [Instance objects](http://esotericsoftware.com/spine-runtime-architecture#Instance-objects) in the Spine Runtimes Guide. */ +export class Skeleton { + /** The skeleton's setup pose data. */ + data: SkeletonData; + + /** The skeleton's bones, sorted parent first. The root bone is always the first bone. */ + bones: Array; + + /** The skeleton's slots. */ + slots: Array; + + /** The skeleton's slots in the order they should be drawn. The returned array may be modified to change the draw order. */ + drawOrder: Array; + + /** The skeleton's IK constraints. */ + ikConstraints: Array; + + /** The skeleton's transform constraints. */ + transformConstraints: Array; + + /** The skeleton's path constraints. */ + pathConstraints: Array; + + /** The list of bones and constraints, sorted in the order they should be updated, as computed by {@link #updateCache()}. */ + _updateCache = new Array(); + updateCacheReset = new Array(); + + /** The skeleton's current skin. May be null. */ + skin: Skin; + + /** The color to tint all the skeleton's attachments. */ + color: Color; + + /** Returns the skeleton's time. This can be used for tracking, such as with Slot {@link Slot#attachmentTime}. + *

    + * See {@link #update()}. */ + time = 0; + + /** Scales the entire skeleton on the X axis. This affects all bones, even if the bone's transform mode disallows scale + * inheritance. */ + scaleX = 1; + + /** Scales the entire skeleton on the Y axis. This affects all bones, even if the bone's transform mode disallows scale + * inheritance. */ + scaleY = 1; + + /** Sets the skeleton X position, which is added to the root bone worldX position. */ + x = 0; + + /** Sets the skeleton Y position, which is added to the root bone worldY position. */ + y = 0; + + constructor(data: SkeletonData) { + if (data == null) throw new Error("data cannot be null."); + this.data = data; + + this.bones = new Array(); + for (let i = 0; i < data.bones.length; i++) { + const boneData = data.bones[i]; + let bone: Bone; + if (boneData.parent == null) bone = new Bone(boneData, this, null); + else { + const parent = this.bones[boneData.parent.index]; + bone = new Bone(boneData, this, parent); + parent.children.push(bone); + } + this.bones.push(bone); + } + + this.slots = new Array(); + this.drawOrder = new Array(); + for (let i = 0; i < data.slots.length; i++) { + const slotData = data.slots[i]; + const bone = this.bones[slotData.boneData.index]; + const slot = new Slot(slotData, bone); + this.slots.push(slot); + this.drawOrder.push(slot); + } + + this.ikConstraints = new Array(); + for (let i = 0; i < data.ikConstraints.length; i++) { + const ikConstraintData = data.ikConstraints[i]; + this.ikConstraints.push(new IkConstraint(ikConstraintData, this)); + } + + this.transformConstraints = new Array(); + for (let i = 0; i < data.transformConstraints.length; i++) { + const transformConstraintData = data.transformConstraints[i]; + this.transformConstraints.push(new TransformConstraint(transformConstraintData, this)); + } + + this.pathConstraints = new Array(); + for (let i = 0; i < data.pathConstraints.length; i++) { + const pathConstraintData = data.pathConstraints[i]; + this.pathConstraints.push(new PathConstraint(pathConstraintData, this)); + } + + this.color = new Color(1, 1, 1, 1); + this.updateCache(); + } + + /** Caches information about bones and constraints. Must be called if the {@link #getSkin()} is modified or if bones, + * constraints, or weighted path attachments are added or removed. */ + updateCache() { + const updateCache = this._updateCache; + updateCache.length = 0; + this.updateCacheReset.length = 0; + + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) { + const bone = bones[i]; + bone.sorted = bone.data.skinRequired; + bone.active = !bone.sorted; + } + + if (this.skin != null) { + const skinBones = this.skin.bones; + for (let i = 0, n = this.skin.bones.length; i < n; i++) { + let bone = this.bones[skinBones[i].index]; + do { + bone.sorted = false; + bone.active = true; + bone = bone.parent; + } while (bone != null); + } + } + + // IK first, lowest hierarchy depth first. + const ikConstraints = this.ikConstraints; + const transformConstraints = this.transformConstraints; + const pathConstraints = this.pathConstraints; + const ikCount = ikConstraints.length, + transformCount = transformConstraints.length, + pathCount = pathConstraints.length; + const constraintCount = ikCount + transformCount + pathCount; + + outer: for (let i = 0; i < constraintCount; i++) { + for (let ii = 0; ii < ikCount; ii++) { + const constraint = ikConstraints[ii]; + if (constraint.data.order == i) { + this.sortIkConstraint(constraint); + continue outer; + } + } + for (let ii = 0; ii < transformCount; ii++) { + const constraint = transformConstraints[ii]; + if (constraint.data.order == i) { + this.sortTransformConstraint(constraint); + continue outer; + } + } + for (let ii = 0; ii < pathCount; ii++) { + const constraint = pathConstraints[ii]; + if (constraint.data.order == i) { + this.sortPathConstraint(constraint); + continue outer; + } + } + } + + for (let i = 0, n = bones.length; i < n; i++) this.sortBone(bones[i]); + } + + sortIkConstraint(constraint: IkConstraint) { + constraint.active = + constraint.target.isActive() && + (!constraint.data.skinRequired || + (this.skin != null && Utils.contains(this.skin.constraints, constraint.data, true))); + if (!constraint.active) return; + + const target = constraint.target; + this.sortBone(target); + + const constrained = constraint.bones; + const parent = constrained[0]; + this.sortBone(parent); + + if (constrained.length > 1) { + const child = constrained[constrained.length - 1]; + if (!(this._updateCache.indexOf(child) > -1)) this.updateCacheReset.push(child); + } + + this._updateCache.push(constraint); + + this.sortReset(parent.children); + constrained[constrained.length - 1].sorted = true; + } + + sortPathConstraint(constraint: PathConstraint) { + constraint.active = + constraint.target.bone.isActive() && + (!constraint.data.skinRequired || + (this.skin != null && Utils.contains(this.skin.constraints, constraint.data, true))); + if (!constraint.active) return; + + const slot = constraint.target; + const slotIndex = slot.data.index; + const slotBone = slot.bone; + if (this.skin != null) this.sortPathConstraintAttachment(this.skin, slotIndex, slotBone); + if (this.data.defaultSkin != null && this.data.defaultSkin != this.skin) + this.sortPathConstraintAttachment(this.data.defaultSkin, slotIndex, slotBone); + for (let i = 0, n = this.data.skins.length; i < n; i++) + this.sortPathConstraintAttachment(this.data.skins[i], slotIndex, slotBone); + + const attachment = slot.getAttachment(); + if (attachment instanceof PathAttachment) this.sortPathConstraintAttachmentWith(attachment, slotBone); + + const constrained = constraint.bones; + const boneCount = constrained.length; + for (let i = 0; i < boneCount; i++) this.sortBone(constrained[i]); + + this._updateCache.push(constraint); + + for (let i = 0; i < boneCount; i++) this.sortReset(constrained[i].children); + for (let i = 0; i < boneCount; i++) constrained[i].sorted = true; + } + + sortTransformConstraint(constraint: TransformConstraint) { + constraint.active = + constraint.target.isActive() && + (!constraint.data.skinRequired || + (this.skin != null && Utils.contains(this.skin.constraints, constraint.data, true))); + if (!constraint.active) return; + + this.sortBone(constraint.target); + + const constrained = constraint.bones; + const boneCount = constrained.length; + if (constraint.data.local) { + for (let i = 0; i < boneCount; i++) { + const child = constrained[i]; + this.sortBone(child.parent); + if (!(this._updateCache.indexOf(child) > -1)) this.updateCacheReset.push(child); + } + } else { + for (let i = 0; i < boneCount; i++) { + this.sortBone(constrained[i]); + } + } + + this._updateCache.push(constraint); + + for (let ii = 0; ii < boneCount; ii++) this.sortReset(constrained[ii].children); + for (let ii = 0; ii < boneCount; ii++) constrained[ii].sorted = true; + } + + sortPathConstraintAttachment(skin: Skin, slotIndex: number, slotBone: Bone) { + const attachments = skin.attachments[slotIndex]; + if (!attachments) return; + for (const key in attachments) { + this.sortPathConstraintAttachmentWith(attachments[key], slotBone); + } + } + + sortPathConstraintAttachmentWith(attachment: Attachment, slotBone: Bone) { + if (!(attachment instanceof PathAttachment)) return; + const pathBones = (attachment).bones; + if (pathBones == null) this.sortBone(slotBone); + else { + const bones = this.bones; + let i = 0; + while (i < pathBones.length) { + const boneCount = pathBones[i++]; + for (let n = i + boneCount; i < n; i++) { + const boneIndex = pathBones[i]; + this.sortBone(bones[boneIndex]); + } + } + } + } + + sortBone(bone: Bone) { + if (bone.sorted) return; + const parent = bone.parent; + if (parent != null) this.sortBone(parent); + bone.sorted = true; + this._updateCache.push(bone); + } + + sortReset(bones: Array) { + for (let i = 0, n = bones.length; i < n; i++) { + const bone = bones[i]; + if (!bone.active) continue; + if (bone.sorted) this.sortReset(bone.children); + bone.sorted = false; + } + } + + /** Updates the world transform for each bone and applies all constraints. + * + * See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine + * Runtimes Guide. */ + updateWorldTransform() { + const updateCacheReset = this.updateCacheReset; + for (let i = 0, n = updateCacheReset.length; i < n; i++) { + const bone = updateCacheReset[i] as Bone; + bone.ax = bone.x; + bone.ay = bone.y; + bone.arotation = bone.rotation; + bone.ascaleX = bone.scaleX; + bone.ascaleY = bone.scaleY; + bone.ashearX = bone.shearX; + bone.ashearY = bone.shearY; + bone.appliedValid = true; + } + const updateCache = this._updateCache; + for (let i = 0, n = updateCache.length; i < n; i++) updateCache[i].update(); + } + + /** Sets the bones, constraints, and slots to their setup pose values. */ + setToSetupPose() { + this.setBonesToSetupPose(); + this.setSlotsToSetupPose(); + } + + /** Sets the bones and constraints to their setup pose values. */ + setBonesToSetupPose() { + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) bones[i].setToSetupPose(); + + const ikConstraints = this.ikConstraints; + for (let i = 0, n = ikConstraints.length; i < n; i++) { + const constraint = ikConstraints[i]; + constraint.mix = constraint.data.mix; + constraint.softness = constraint.data.softness; + constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; + constraint.stretch = constraint.data.stretch; + } + + const transformConstraints = this.transformConstraints; + for (let i = 0, n = transformConstraints.length; i < n; i++) { + const constraint = transformConstraints[i]; + const data = constraint.data; + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; + constraint.shearMix = data.shearMix; + } + + const pathConstraints = this.pathConstraints; + for (let i = 0, n = pathConstraints.length; i < n; i++) { + const constraint = pathConstraints[i]; + const data = constraint.data; + constraint.position = data.position; + constraint.spacing = data.spacing; + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + } + } + + /** Sets the slots and draw order to their setup pose values. */ + setSlotsToSetupPose() { + const slots = this.slots; + Utils.arrayCopy(slots, 0, this.drawOrder, 0, slots.length); + for (let i = 0, n = slots.length; i < n; i++) slots[i].setToSetupPose(); + } + + /** @returns May return null. */ + getRootBone() { + if (this.bones.length == 0) return null; + return this.bones[0]; + } + + /** @returns May be null. */ + findBone(boneName: string) { + if (boneName == null) throw new Error("boneName cannot be null."); + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) { + const bone = bones[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } + + /** @returns -1 if the bone was not found. */ + findBoneIndex(boneName: string) { + if (boneName == null) throw new Error("boneName cannot be null."); + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) if (bones[i].data.name == boneName) return i; + return -1; + } + + /** Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it + * repeatedly. + * @returns May be null. */ + findSlot(slotName: string) { + if (slotName == null) throw new Error("slotName cannot be null."); + const slots = this.slots; + for (let i = 0, n = slots.length; i < n; i++) { + const slot = slots[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } + + /** @returns -1 if the bone was not found. */ + findSlotIndex(slotName: string) { + if (slotName == null) throw new Error("slotName cannot be null."); + const slots = this.slots; + for (let i = 0, n = slots.length; i < n; i++) if (slots[i].data.name == slotName) return i; + return -1; + } + + /** Sets a skin by name. + * + * See {@link #setSkin()}. */ + setSkinByName(skinName: string) { + const skin = this.data.findSkin(skinName); + if (skin == null) throw new Error("Skin not found: " + skinName); + this.setSkin(skin); + } + + /** Sets the skin used to look up attachments before looking in the {@link SkeletonData#defaultSkin default skin}. If the + * skin is changed, {@link #updateCache()} is called. + * + * Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. If there was no + * old skin, each slot's setup mode attachment is attached from the new skin. + * + * After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling + * {@link #setSlotsToSetupPose()}. Also, often {@link AnimationState#apply()} is called before the next time the + * skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin. + * @param newSkin May be null. */ + setSkin(newSkin: Skin) { + if (newSkin == this.skin) return; + if (newSkin != null) { + if (this.skin != null) newSkin.attachAll(this, this.skin); + else { + const slots = this.slots; + for (let i = 0, n = slots.length; i < n; i++) { + const slot = slots[i]; + const name = slot.data.attachmentName; + if (name != null) { + const attachment: Attachment = newSkin.getAttachment(i, name); + if (attachment != null) slot.setAttachment(attachment); + } + } + } + } + this.skin = newSkin; + this.updateCache(); + } + + /** Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment + * name. + * + * See {@link #getAttachment()}. + * @returns May be null. */ + getAttachmentByName(slotName: string, attachmentName: string): Attachment { + return this.getAttachment(this.data.findSlotIndex(slotName), attachmentName); + } + + /** Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot index and + * attachment name. First the skin is checked and if the attachment was not found, the default skin is checked. + * + * See [Runtime skins](http://esotericsoftware.com/spine-runtime-skins) in the Spine Runtimes Guide. + * @returns May be null. */ + getAttachment(slotIndex: number, attachmentName: string): Attachment { + if (attachmentName == null) throw new Error("attachmentName cannot be null."); + if (this.skin != null) { + const attachment: Attachment = this.skin.getAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + if (this.data.defaultSkin != null) return this.data.defaultSkin.getAttachment(slotIndex, attachmentName); + return null; + } + + /** A convenience method to set an attachment by finding the slot with {@link #findSlot()}, finding the attachment with + * {@link #getAttachment()}, then setting the slot's {@link Slot#attachment}. + * @param attachmentName May be null to clear the slot's attachment. */ + setAttachment(slotName: string, attachmentName: string) { + if (slotName == null) throw new Error("slotName cannot be null."); + const slots = this.slots; + for (let i = 0, n = slots.length; i < n; i++) { + const slot = slots[i]; + if (slot.data.name == slotName) { + let attachment: Attachment = null; + if (attachmentName != null) { + attachment = this.getAttachment(i, attachmentName); + if (attachment == null) + throw new Error("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.setAttachment(attachment); + return; + } + } + throw new Error("Slot not found: " + slotName); + } + + /** Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method + * than to call it repeatedly. + * @return May be null. */ + findIkConstraint(constraintName: string) { + if (constraintName == null) throw new Error("constraintName cannot be null."); + const ikConstraints = this.ikConstraints; + for (let i = 0, n = ikConstraints.length; i < n; i++) { + const ikConstraint = ikConstraints[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } + + /** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of + * this method than to call it repeatedly. + * @return May be null. */ + findTransformConstraint(constraintName: string) { + if (constraintName == null) throw new Error("constraintName cannot be null."); + const transformConstraints = this.transformConstraints; + for (let i = 0, n = transformConstraints.length; i < n; i++) { + const constraint = transformConstraints[i]; + if (constraint.data.name == constraintName) return constraint; + } + return null; + } + + /** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method + * than to call it repeatedly. + * @return May be null. */ + findPathConstraint(constraintName: string) { + if (constraintName == null) throw new Error("constraintName cannot be null."); + const pathConstraints = this.pathConstraints; + for (let i = 0, n = pathConstraints.length; i < n; i++) { + const constraint = pathConstraints[i]; + if (constraint.data.name == constraintName) return constraint; + } + return null; + } + + /** Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + * @param offset An output value, the distance from the skeleton origin to the bottom left corner of the AABB. + * @param size An output value, the width and height of the AABB. + * @param temp Working memory to temporarily store attachments' computed world vertices. */ + getBounds(offset: Vector2, size: Vector2, temp: Array = new Array(2)) { + if (offset == null) throw new Error("offset cannot be null."); + if (size == null) throw new Error("size cannot be null."); + const drawOrder = this.drawOrder; + let minX = Number.POSITIVE_INFINITY, + minY = Number.POSITIVE_INFINITY, + maxX = Number.NEGATIVE_INFINITY, + maxY = Number.NEGATIVE_INFINITY; + for (let i = 0, n = drawOrder.length; i < n; i++) { + const slot = drawOrder[i]; + if (!slot.bone.active) continue; + let verticesLength = 0; + let vertices: ArrayLike = null; + const attachment = slot.getAttachment(); + if (attachment instanceof RegionAttachment) { + verticesLength = 8; + vertices = Utils.setArraySize(temp, verticesLength, 0); + (attachment).computeWorldVertices(slot.bone, vertices, 0, 2); + } else if (attachment instanceof MeshAttachment) { + const mesh = attachment; + verticesLength = mesh.worldVerticesLength; + vertices = Utils.setArraySize(temp, verticesLength, 0); + mesh.computeWorldVertices(slot, 0, verticesLength, vertices, 0, 2); + } + if (vertices != null) { + for (let ii = 0, nn = vertices.length; ii < nn; ii += 2) { + const x = vertices[ii], + y = vertices[ii + 1]; + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + } + } + } + offset.set(minX, minY); + size.set(maxX - minX, maxY - minY); + } + + /** Increments the skeleton's {@link #time}. */ + update(delta: number) { + this.time += delta; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/SkeletonBinary.ts b/packages/spine-core-3.8/src/spine-core/SkeletonBinary.ts new file mode 100644 index 0000000000..86ec2f722c --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/SkeletonBinary.ts @@ -0,0 +1,942 @@ +import { TransformMode, BoneData } from "./BoneData"; +import { PositionMode, SpacingMode, RotateMode, PathConstraintData } from "./PathConstraintData"; +import { BlendMode } from "./BlendMode"; +import { AttachmentLoader } from "./attachments/AttachmentLoader"; +import { SkeletonData } from "./SkeletonData"; +import { Color, Utils } from "./Utils"; +import { SlotData } from "./SlotData"; +import { IkConstraintData } from "./IkConstraintData"; +import { TransformConstraintData } from "./TransformConstraintData"; +import { VertexAttachment, Attachment } from "./attachments/Attachment"; +import { MeshAttachment } from "./attachments/MeshAttachment"; +import { EventData } from "./EventData"; +import { Skin } from "./Skin"; +import { AttachmentType } from "./attachments/AttachmentType"; +import { + Timeline, + AttachmentTimeline, + ColorTimeline, + TwoColorTimeline, + RotateTimeline, + ScaleTimeline, + ShearTimeline, + TranslateTimeline, + IkConstraintTimeline, + TransformConstraintTimeline, + PathConstraintSpacingTimeline, + PathConstraintPositionTimeline, + PathConstraintMixTimeline, + DeformTimeline, + DrawOrderTimeline, + EventTimeline, + CurveTimeline +} from "./Animation"; +import { Animation } from "./Animation"; +import { Event } from "./Event"; + +/** Loads skeleton data in the Spine binary format. + * + * See [Spine binary format](http://esotericsoftware.com/spine-binary-format) and + * [JSON and binary data](http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data) in the Spine + * Runtimes Guide. */ +export class SkeletonBinary { + static AttachmentTypeValues = [ + 0 /*AttachmentType.Region*/, 1 /*AttachmentType.BoundingBox*/, 2 /*AttachmentType.Mesh*/, + 3 /*AttachmentType.LinkedMesh*/, 4 /*AttachmentType.Path*/, 5 /*AttachmentType.Point*/, + 6 /*AttachmentType.Clipping*/ + ]; + static TransformModeValues = [ + TransformMode.Normal, + TransformMode.OnlyTranslation, + TransformMode.NoRotationOrReflection, + TransformMode.NoScale, + TransformMode.NoScaleOrReflection + ]; + static PositionModeValues = [PositionMode.Fixed, PositionMode.Percent]; + static SpacingModeValues = [SpacingMode.Length, SpacingMode.Fixed, SpacingMode.Percent]; + static RotateModeValues = [RotateMode.Tangent, RotateMode.Chain, RotateMode.ChainScale]; + static BlendModeValues = [BlendMode.Normal, BlendMode.Additive, BlendMode.Multiply, BlendMode.Screen]; + + static BONE_ROTATE = 0; + static BONE_TRANSLATE = 1; + static BONE_SCALE = 2; + static BONE_SHEAR = 3; + + static SLOT_ATTACHMENT = 0; + static SLOT_COLOR = 1; + static SLOT_TWO_COLOR = 2; + + static PATH_POSITION = 0; + static PATH_SPACING = 1; + static PATH_MIX = 2; + + static CURVE_LINEAR = 0; + static CURVE_STEPPED = 1; + static CURVE_BEZIER = 2; + + /** Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at + * runtime than were used in Spine. + * + * See [Scaling](http://esotericsoftware.com/spine-loading-skeleton-data#Scaling) in the Spine Runtimes Guide. */ + scale = 1; + + attachmentLoader: AttachmentLoader; + private linkedMeshes = new Array(); + + constructor(attachmentLoader: AttachmentLoader) { + this.attachmentLoader = attachmentLoader; + } + + readSkeletonData(binary: Uint8Array): SkeletonData { + const scale = this.scale; + + const skeletonData = new SkeletonData(); + skeletonData.name = ""; // BOZO + + const input = new BinaryInput(binary); + + skeletonData.hash = input.readString(); + skeletonData.version = input.readString(); + if ("3.8.75" == skeletonData.version) + throw new Error("Unsupported skeleton data, please export with a newer version of Spine."); + skeletonData.x = input.readFloat(); + skeletonData.y = input.readFloat(); + skeletonData.width = input.readFloat(); + skeletonData.height = input.readFloat(); + + const nonessential = input.readBoolean(); + if (nonessential) { + skeletonData.fps = input.readFloat(); + + skeletonData.imagesPath = input.readString(); + skeletonData.audioPath = input.readString(); + } + + let n = 0; + // Strings. + n = input.readInt(true); + for (let i = 0; i < n; i++) input.strings.push(input.readString()); + + // Bones. + n = input.readInt(true); + for (let i = 0; i < n; i++) { + const name = input.readString(); + const parent = i == 0 ? null : skeletonData.bones[input.readInt(true)]; + const data = new BoneData(i, name, parent); + data.rotation = input.readFloat(); + data.x = input.readFloat() * scale; + data.y = input.readFloat() * scale; + data.scaleX = input.readFloat(); + data.scaleY = input.readFloat(); + data.shearX = input.readFloat(); + data.shearY = input.readFloat(); + data.length = input.readFloat() * scale; + data.transformMode = SkeletonBinary.TransformModeValues[input.readInt(true)]; + data.skinRequired = input.readBoolean(); + if (nonessential) Color.rgba8888ToColor(data.color, input.readInt32()); + skeletonData.bones.push(data); + } + + // Slots. + n = input.readInt(true); + for (let i = 0; i < n; i++) { + const slotName = input.readString(); + const boneData = skeletonData.bones[input.readInt(true)]; + const data = new SlotData(i, slotName, boneData); + Color.rgba8888ToColor(data.color, input.readInt32()); + + const darkColor = input.readInt32(); + if (darkColor != -1) Color.rgb888ToColor((data.darkColor = new Color()), darkColor); + + data.attachmentName = input.readStringRef(); + data.blendMode = SkeletonBinary.BlendModeValues[input.readInt(true)]; + skeletonData.slots.push(data); + } + + // IK constraints. + n = input.readInt(true); + for (let i = 0, nn; i < n; i++) { + const data = new IkConstraintData(input.readString()); + data.order = input.readInt(true); + data.skinRequired = input.readBoolean(); + nn = input.readInt(true); + for (let ii = 0; ii < nn; ii++) data.bones.push(skeletonData.bones[input.readInt(true)]); + data.target = skeletonData.bones[input.readInt(true)]; + data.mix = input.readFloat(); + data.softness = input.readFloat() * scale; + data.bendDirection = input.readByte(); + data.compress = input.readBoolean(); + data.stretch = input.readBoolean(); + data.uniform = input.readBoolean(); + skeletonData.ikConstraints.push(data); + } + + // Transform constraints. + n = input.readInt(true); + for (let i = 0, nn; i < n; i++) { + const data = new TransformConstraintData(input.readString()); + data.order = input.readInt(true); + data.skinRequired = input.readBoolean(); + nn = input.readInt(true); + for (let ii = 0; ii < nn; ii++) data.bones.push(skeletonData.bones[input.readInt(true)]); + data.target = skeletonData.bones[input.readInt(true)]; + data.local = input.readBoolean(); + data.relative = input.readBoolean(); + data.offsetRotation = input.readFloat(); + data.offsetX = input.readFloat() * scale; + data.offsetY = input.readFloat() * scale; + data.offsetScaleX = input.readFloat(); + data.offsetScaleY = input.readFloat(); + data.offsetShearY = input.readFloat(); + data.rotateMix = input.readFloat(); + data.translateMix = input.readFloat(); + data.scaleMix = input.readFloat(); + data.shearMix = input.readFloat(); + skeletonData.transformConstraints.push(data); + } + + // Path constraints. + n = input.readInt(true); + for (let i = 0, nn; i < n; i++) { + const data = new PathConstraintData(input.readString()); + data.order = input.readInt(true); + data.skinRequired = input.readBoolean(); + nn = input.readInt(true); + for (let ii = 0; ii < nn; ii++) data.bones.push(skeletonData.bones[input.readInt(true)]); + data.target = skeletonData.slots[input.readInt(true)]; + data.positionMode = SkeletonBinary.PositionModeValues[input.readInt(true)]; + data.spacingMode = SkeletonBinary.SpacingModeValues[input.readInt(true)]; + data.rotateMode = SkeletonBinary.RotateModeValues[input.readInt(true)]; + data.offsetRotation = input.readFloat(); + data.position = input.readFloat(); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = input.readFloat(); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.rotateMix = input.readFloat(); + data.translateMix = input.readFloat(); + skeletonData.pathConstraints.push(data); + } + + // Default skin. + const defaultSkin = this.readSkin(input, skeletonData, true, nonessential); + if (defaultSkin != null) { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.push(defaultSkin); + } + + // Skins. + { + let i = skeletonData.skins.length; + Utils.setArraySize(skeletonData.skins, (n = i + input.readInt(true))); + for (; i < n; i++) skeletonData.skins[i] = this.readSkin(input, skeletonData, false, nonessential); + } + + // Linked meshes. + n = this.linkedMeshes.length; + for (let i = 0; i < n; i++) { + const linkedMesh = this.linkedMeshes[i]; + const skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin); + if (skin == null) throw new Error("Skin not found: " + linkedMesh.skin); + const parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Error("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.deformAttachment = linkedMesh.inheritDeform ? (parent as VertexAttachment) : linkedMesh.mesh; + linkedMesh.mesh.setParentMesh(parent as MeshAttachment); + linkedMesh.mesh.updateUVs(); + } + this.linkedMeshes.length = 0; + + // Events. + n = input.readInt(true); + for (let i = 0; i < n; i++) { + const data = new EventData(input.readStringRef()); + data.intValue = input.readInt(false); + data.floatValue = input.readFloat(); + data.stringValue = input.readString(); + data.audioPath = input.readString(); + if (data.audioPath != null) { + data.volume = input.readFloat(); + data.balance = input.readFloat(); + } + skeletonData.events.push(data); + } + + // Animations. + n = input.readInt(true); + for (let i = 0; i < n; i++) + skeletonData.animations.push(this.readAnimation(input, input.readString(), skeletonData)); + return skeletonData; + } + + private readSkin(input: BinaryInput, skeletonData: SkeletonData, defaultSkin: boolean, nonessential: boolean): Skin { + let skin = null; + let slotCount = 0; + + if (defaultSkin) { + slotCount = input.readInt(true); + if (slotCount == 0) return null; + skin = new Skin("default"); + } else { + skin = new Skin(input.readStringRef()); + skin.bones.length = input.readInt(true); + for (let i = 0, n = skin.bones.length; i < n; i++) skin.bones[i] = skeletonData.bones[input.readInt(true)]; + + for (let i = 0, n = input.readInt(true); i < n; i++) + skin.constraints.push(skeletonData.ikConstraints[input.readInt(true)]); + for (let i = 0, n = input.readInt(true); i < n; i++) + skin.constraints.push(skeletonData.transformConstraints[input.readInt(true)]); + for (let i = 0, n = input.readInt(true); i < n; i++) + skin.constraints.push(skeletonData.pathConstraints[input.readInt(true)]); + + slotCount = input.readInt(true); + } + + for (let i = 0; i < slotCount; i++) { + const slotIndex = input.readInt(true); + for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { + const name = input.readStringRef(); + const attachment = this.readAttachment(input, skeletonData, skin, slotIndex, name, nonessential); + if (attachment != null) skin.setAttachment(slotIndex, name, attachment); + } + } + return skin; + } + + private readAttachment( + input: BinaryInput, + skeletonData: SkeletonData, + skin: Skin, + slotIndex: number, + attachmentName: string, + nonessential: boolean + ): Attachment { + const scale = this.scale; + + let name = input.readStringRef(); + if (name == null) name = attachmentName; + + const typeIndex = input.readByte(); + const type = SkeletonBinary.AttachmentTypeValues[typeIndex]; + switch (type) { + case AttachmentType.Region: { + let path = input.readStringRef(); + const rotation = input.readFloat(); + const x = input.readFloat(); + const y = input.readFloat(); + const scaleX = input.readFloat(); + const scaleY = input.readFloat(); + const width = input.readFloat(); + const height = input.readFloat(); + const color = input.readInt32(); + + if (path == null) path = name; + const region = this.attachmentLoader.newRegionAttachment(skin, name, path); + if (region == null) return null; + region.path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + Color.rgba8888ToColor(region.color, color); + region.updateOffset(); + return region; + } + case AttachmentType.BoundingBox: { + const vertexCount = input.readInt(true); + const vertices = this.readVertices(input, vertexCount); + const color = nonessential ? input.readInt32() : 0; + + const box = this.attachmentLoader.newBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.worldVerticesLength = vertexCount << 1; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + if (nonessential) Color.rgba8888ToColor(box.color, color); + return box; + } + case AttachmentType.Mesh: { + let path = input.readStringRef(); + const color = input.readInt32(); + const vertexCount = input.readInt(true); + const uvs = this.readFloatArray(input, vertexCount << 1, 1); + const triangles = this.readShortArray(input); + const vertices = this.readVertices(input, vertexCount); + const hullLength = input.readInt(true); + let edges = null; + let width = 0, + height = 0; + if (nonessential) { + edges = this.readShortArray(input); + width = input.readFloat(); + height = input.readFloat(); + } + + if (path == null) path = name; + const mesh = this.attachmentLoader.newMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.path = path; + Color.rgba8888ToColor(mesh.color, color); + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.worldVerticesLength = vertexCount << 1; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.updateUVs(); + mesh.hullLength = hullLength << 1; + if (nonessential) { + mesh.edges = edges; + mesh.width = width * scale; + mesh.height = height * scale; + } + return mesh; + } + case AttachmentType.LinkedMesh: { + let path = input.readStringRef(); + const color = input.readInt32(); + const skinName = input.readStringRef(); + const parent = input.readStringRef(); + const inheritDeform = input.readBoolean(); + let width = 0, + height = 0; + if (nonessential) { + width = input.readFloat(); + height = input.readFloat(); + } + + if (path == null) path = name; + const mesh = this.attachmentLoader.newMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.path = path; + Color.rgba8888ToColor(mesh.color, color); + if (nonessential) { + mesh.width = width * scale; + mesh.height = height * scale; + } + this.linkedMeshes.push(new LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform)); + return mesh; + } + case AttachmentType.Path: { + const closed = input.readBoolean(); + const constantSpeed = input.readBoolean(); + const vertexCount = input.readInt(true); + const vertices = this.readVertices(input, vertexCount); + const lengths = Utils.newArray(vertexCount / 3, 0); + for (let i = 0, n = lengths.length; i < n; i++) lengths[i] = input.readFloat() * scale; + const color = nonessential ? input.readInt32() : 0; + + const path = this.attachmentLoader.newPathAttachment(skin, name); + if (path == null) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertexCount << 1; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + if (nonessential) Color.rgba8888ToColor(path.color, color); + return path; + } + case AttachmentType.Point: { + const rotation = input.readFloat(); + const x = input.readFloat(); + const y = input.readFloat(); + const color = nonessential ? input.readInt32() : 0; + + const point = this.attachmentLoader.newPointAttachment(skin, name); + if (point == null) return null; + point.x = x * scale; + point.y = y * scale; + point.rotation = rotation; + if (nonessential) Color.rgba8888ToColor(point.color, color); + return point; + } + case AttachmentType.Clipping: { + const endSlotIndex = input.readInt(true); + const vertexCount = input.readInt(true); + const vertices = this.readVertices(input, vertexCount); + const color = nonessential ? input.readInt32() : 0; + + const clip = this.attachmentLoader.newClippingAttachment(skin, name); + if (clip == null) return null; + clip.endSlot = skeletonData.slots[endSlotIndex]; + clip.worldVerticesLength = vertexCount << 1; + clip.vertices = vertices.vertices; + clip.bones = vertices.bones; + if (nonessential) Color.rgba8888ToColor(clip.color, color); + return clip; + } + } + return null; + } + + private readVertices(input: BinaryInput, vertexCount: number): Vertices { + const verticesLength = vertexCount << 1; + const vertices = new Vertices(); + const scale = this.scale; + if (!input.readBoolean()) { + vertices.vertices = this.readFloatArray(input, verticesLength, scale); + return vertices; + } + const weights = new Array(); + const bonesArray = new Array(); + for (let i = 0; i < vertexCount; i++) { + const boneCount = input.readInt(true); + bonesArray.push(boneCount); + for (let ii = 0; ii < boneCount; ii++) { + bonesArray.push(input.readInt(true)); + weights.push(input.readFloat() * scale); + weights.push(input.readFloat() * scale); + weights.push(input.readFloat()); + } + } + vertices.vertices = Utils.toFloatArray(weights); + vertices.bones = bonesArray; + return vertices; + } + + private readFloatArray(input: BinaryInput, n: number, scale: number): number[] { + const array = new Array(n); + if (scale == 1) { + for (let i = 0; i < n; i++) array[i] = input.readFloat(); + } else { + for (let i = 0; i < n; i++) array[i] = input.readFloat() * scale; + } + return array; + } + + private readShortArray(input: BinaryInput): number[] { + const n = input.readInt(true); + const array = new Array(n); + for (let i = 0; i < n; i++) array[i] = input.readShort(); + return array; + } + + private readAnimation(input: BinaryInput, name: string, skeletonData: SkeletonData): Animation { + const timelines = new Array(); + const scale = this.scale; + let duration = 0; + const tempColor1 = new Color(); + const tempColor2 = new Color(); + + // Slot timelines. + for (let i = 0, n = input.readInt(true); i < n; i++) { + const slotIndex = input.readInt(true); + for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { + const timelineType = input.readByte(); + const frameCount = input.readInt(true); + switch (timelineType) { + case SkeletonBinary.SLOT_ATTACHMENT: { + const timeline = new AttachmentTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.setFrame(frameIndex, input.readFloat(), input.readStringRef()); + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[frameCount - 1]); + break; + } + case SkeletonBinary.SLOT_COLOR: { + const timeline = new ColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) { + const time = input.readFloat(); + Color.rgba8888ToColor(tempColor1, input.readInt32()); + timeline.setFrame(frameIndex, time, tempColor1.r, tempColor1.g, tempColor1.b, tempColor1.a); + if (frameIndex < frameCount - 1) this.readCurve(input, frameIndex, timeline); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(frameCount - 1) * ColorTimeline.ENTRIES]); + break; + } + case SkeletonBinary.SLOT_TWO_COLOR: { + const timeline = new TwoColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) { + const time = input.readFloat(); + Color.rgba8888ToColor(tempColor1, input.readInt32()); + Color.rgb888ToColor(tempColor2, input.readInt32()); + timeline.setFrame( + frameIndex, + time, + tempColor1.r, + tempColor1.g, + tempColor1.b, + tempColor1.a, + tempColor2.r, + tempColor2.g, + tempColor2.b + ); + if (frameIndex < frameCount - 1) this.readCurve(input, frameIndex, timeline); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(frameCount - 1) * TwoColorTimeline.ENTRIES]); + break; + } + } + } + } + + // Bone timelines. + for (let i = 0, n = input.readInt(true); i < n; i++) { + const boneIndex = input.readInt(true); + for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { + const timelineType = input.readByte(); + const frameCount = input.readInt(true); + switch (timelineType) { + case SkeletonBinary.BONE_ROTATE: { + const timeline = new RotateTimeline(frameCount); + timeline.boneIndex = boneIndex; + for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.setFrame(frameIndex, input.readFloat(), input.readFloat()); + if (frameIndex < frameCount - 1) this.readCurve(input, frameIndex, timeline); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); + break; + } + case SkeletonBinary.BONE_TRANSLATE: + case SkeletonBinary.BONE_SCALE: + case SkeletonBinary.BONE_SHEAR: { + let timeline; + let timelineScale = 1; + if (timelineType == SkeletonBinary.BONE_SCALE) timeline = new ScaleTimeline(frameCount); + else if (timelineType == SkeletonBinary.BONE_SHEAR) timeline = new ShearTimeline(frameCount); + else { + timeline = new TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.setFrame( + frameIndex, + input.readFloat(), + input.readFloat() * timelineScale, + input.readFloat() * timelineScale + ); + if (frameIndex < frameCount - 1) this.readCurve(input, frameIndex, timeline); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); + break; + } + } + } + } + + // IK constraint timelines. + for (let i = 0, n = input.readInt(true); i < n; i++) { + const index = input.readInt(true); + const frameCount = input.readInt(true); + const timeline = new IkConstraintTimeline(frameCount); + timeline.ikConstraintIndex = index; + for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.setFrame( + frameIndex, + input.readFloat(), + input.readFloat(), + input.readFloat() * scale, + input.readByte(), + input.readBoolean(), + input.readBoolean() + ); + if (frameIndex < frameCount - 1) this.readCurve(input, frameIndex, timeline); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + + // Transform constraint timelines. + for (let i = 0, n = input.readInt(true); i < n; i++) { + const index = input.readInt(true); + const frameCount = input.readInt(true); + const timeline = new TransformConstraintTimeline(frameCount); + timeline.transformConstraintIndex = index; + for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.setFrame( + frameIndex, + input.readFloat(), + input.readFloat(), + input.readFloat(), + input.readFloat(), + input.readFloat() + ); + if (frameIndex < frameCount - 1) this.readCurve(input, frameIndex, timeline); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + + // Path constraint timelines. + for (let i = 0, n = input.readInt(true); i < n; i++) { + const index = input.readInt(true); + const data = skeletonData.pathConstraints[index]; + for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { + const timelineType = input.readByte(); + const frameCount = input.readInt(true); + switch (timelineType) { + case SkeletonBinary.PATH_POSITION: + case SkeletonBinary.PATH_SPACING: { + let timeline; + let timelineScale = 1; + if (timelineType == SkeletonBinary.PATH_SPACING) { + timeline = new PathConstraintSpacingTimeline(frameCount); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) + timelineScale = scale; + } else { + timeline = new PathConstraintPositionTimeline(frameCount); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.setFrame(frameIndex, input.readFloat(), input.readFloat() * timelineScale); + if (frameIndex < frameCount - 1) this.readCurve(input, frameIndex, timeline); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + break; + } + case SkeletonBinary.PATH_MIX: { + const timeline = new PathConstraintMixTimeline(frameCount); + timeline.pathConstraintIndex = index; + for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) { + timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat()); + if (frameIndex < frameCount - 1) this.readCurve(input, frameIndex, timeline); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + break; + } + } + } + } + + // Deform timelines. + for (let i = 0, n = input.readInt(true); i < n; i++) { + const skin = skeletonData.skins[input.readInt(true)]; + for (let ii = 0, nn = input.readInt(true); ii < nn; ii++) { + const slotIndex = input.readInt(true); + for (let iii = 0, nnn = input.readInt(true); iii < nnn; iii++) { + const attachment = skin.getAttachment(slotIndex, input.readStringRef()) as VertexAttachment; + const weighted = attachment.bones != null; + const vertices = attachment.vertices; + const deformLength = weighted ? (vertices.length / 3) * 2 : vertices.length; + + const frameCount = input.readInt(true); + const timeline = new DeformTimeline(frameCount); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) { + const time = input.readFloat(); + let deform; + let end = input.readInt(true); + if (end == 0) deform = weighted ? Utils.newFloatArray(deformLength) : vertices; + else { + deform = Utils.newFloatArray(deformLength); + const start = input.readInt(true); + end += start; + if (scale == 1) { + for (let v = start; v < end; v++) deform[v] = input.readFloat(); + } else { + for (let v = start; v < end; v++) deform[v] = input.readFloat() * scale; + } + if (!weighted) { + for (let v = 0, vn = deform.length; v < vn; v++) deform[v] += vertices[v]; + } + } + + timeline.setFrame(frameIndex, time, deform); + if (frameIndex < frameCount - 1) this.readCurve(input, frameIndex, timeline); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + const drawOrderCount = input.readInt(true); + if (drawOrderCount > 0) { + const timeline = new DrawOrderTimeline(drawOrderCount); + const slotCount = skeletonData.slots.length; + for (let i = 0; i < drawOrderCount; i++) { + const time = input.readFloat(); + const offsetCount = input.readInt(true); + const drawOrder = Utils.newArray(slotCount, 0); + for (let ii = slotCount - 1; ii >= 0; ii--) drawOrder[ii] = -1; + const unchanged = Utils.newArray(slotCount - offsetCount, 0); + let originalIndex = 0, + unchangedIndex = 0; + for (let ii = 0; ii < offsetCount; ii++) { + const slotIndex = input.readInt(true); + // Collect unchanged items. + while (originalIndex != slotIndex) unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + input.readInt(true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (let ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline.setFrame(i, time, drawOrder); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[drawOrderCount - 1]); + } + + // Event timeline. + const eventCount = input.readInt(true); + if (eventCount > 0) { + const timeline = new EventTimeline(eventCount); + for (let i = 0; i < eventCount; i++) { + const time = input.readFloat(); + const eventData = skeletonData.events[input.readInt(true)]; + const event = new Event(time, eventData); + event.intValue = input.readInt(false); + event.floatValue = input.readFloat(); + event.stringValue = input.readBoolean() ? input.readString() : eventData.stringValue; + if (event.data.audioPath != null) { + event.volume = input.readFloat(); + event.balance = input.readFloat(); + } + timeline.setFrame(i, event); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[eventCount - 1]); + } + + return new Animation(name, timelines, duration); + } + + private readCurve(input: BinaryInput, frameIndex: number, timeline: CurveTimeline) { + switch (input.readByte()) { + case SkeletonBinary.CURVE_STEPPED: + timeline.setStepped(frameIndex); + break; + case SkeletonBinary.CURVE_BEZIER: + this.setCurve(timeline, frameIndex, input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat()); + break; + } + } + + setCurve(timeline: CurveTimeline, frameIndex: number, cx1: number, cy1: number, cx2: number, cy2: number) { + timeline.setCurve(frameIndex, cx1, cy1, cx2, cy2); + } +} + +class BinaryInput { + constructor( + data: Uint8Array, + public strings = new Array(), + private index: number = 0, + private buffer = new DataView(data.buffer) + ) {} + + readByte(): number { + return this.buffer.getInt8(this.index++); + } + + readShort(): number { + const value = this.buffer.getInt16(this.index); + this.index += 2; + return value; + } + + readInt32(): number { + const value = this.buffer.getInt32(this.index); + this.index += 4; + return value; + } + + readInt(optimizePositive: boolean) { + let b = this.readByte(); + let result = b & 0x7f; + if ((b & 0x80) != 0) { + b = this.readByte(); + result |= (b & 0x7f) << 7; + if ((b & 0x80) != 0) { + b = this.readByte(); + result |= (b & 0x7f) << 14; + if ((b & 0x80) != 0) { + b = this.readByte(); + result |= (b & 0x7f) << 21; + if ((b & 0x80) != 0) { + b = this.readByte(); + result |= (b & 0x7f) << 28; + } + } + } + } + return optimizePositive ? result : (result >>> 1) ^ -(result & 1); + } + + readStringRef(): string { + const index = this.readInt(true); + return index == 0 ? null : this.strings[index - 1]; + } + + readString(): string { + let byteCount = this.readInt(true); + switch (byteCount) { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + let chars = ""; + const charCount = 0; + for (let i = 0; i < byteCount; ) { + const b = this.readByte(); + switch (b >> 4) { + case 12: + case 13: + chars += String.fromCharCode(((b & 0x1f) << 6) | (this.readByte() & 0x3f)); + i += 2; + break; + case 14: + chars += String.fromCharCode(((b & 0x0f) << 12) | ((this.readByte() & 0x3f) << 6) | (this.readByte() & 0x3f)); + i += 3; + break; + default: + chars += String.fromCharCode(b); + i++; + } + } + return chars; + } + + readFloat(): number { + const value = this.buffer.getFloat32(this.index); + this.index += 4; + return value; + } + + readBoolean(): boolean { + return this.readByte() != 0; + } +} + +class LinkedMesh { + parent: string; + skin: string; + slotIndex: number; + mesh: MeshAttachment; + inheritDeform: boolean; + + constructor(mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + this.inheritDeform = inheritDeform; + } +} + +class Vertices { + constructor( + public bones: Array = null, + public vertices: Array | Float32Array = null + ) {} +} diff --git a/packages/spine-core-3.8/src/spine-core/SkeletonBounds.ts b/packages/spine-core-3.8/src/spine-core/SkeletonBounds.ts new file mode 100644 index 0000000000..6c7323ba77 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/SkeletonBounds.ts @@ -0,0 +1,215 @@ +import { BoundingBoxAttachment } from "./attachments/BoundingBoxAttachment"; +import { Pool, Utils } from "./Utils"; +import { Skeleton } from "./Skeleton"; + +/** Collects each visible {@link BoundingBoxAttachment} and computes the world vertices for its polygon. The polygon vertices are + * provided along with convenience methods for doing hit detection. */ +export class SkeletonBounds { + /** The left edge of the axis aligned bounding box. */ + minX = 0; + + /** The bottom edge of the axis aligned bounding box. */ + minY = 0; + + /** The right edge of the axis aligned bounding box. */ + maxX = 0; + + /** The top edge of the axis aligned bounding box. */ + maxY = 0; + + /** The visible bounding boxes. */ + boundingBoxes = new Array(); + + /** The world vertices for the bounding box polygons. */ + polygons = new Array>(); + + private polygonPool = new Pool>(() => { + return Utils.newFloatArray(16); + }); + + /** Clears any previous polygons, finds all visible bounding box attachments, and computes the world vertices for each bounding + * box's polygon. + * @param updateAabb If true, the axis aligned bounding box containing all the polygons is computed. If false, the + * SkeletonBounds AABB methods will always return true. */ + update(skeleton: Skeleton, updateAabb: boolean) { + if (skeleton == null) throw new Error("skeleton cannot be null."); + const boundingBoxes = this.boundingBoxes; + const polygons = this.polygons; + const polygonPool = this.polygonPool; + const slots = skeleton.slots; + const slotCount = slots.length; + + boundingBoxes.length = 0; + polygonPool.freeAll(polygons); + polygons.length = 0; + + for (let i = 0; i < slotCount; i++) { + const slot = slots[i]; + if (!slot.bone.active) continue; + const attachment = slot.getAttachment(); + if (attachment instanceof BoundingBoxAttachment) { + const boundingBox = attachment as BoundingBoxAttachment; + boundingBoxes.push(boundingBox); + + let polygon = polygonPool.obtain(); + if (polygon.length != boundingBox.worldVerticesLength) { + polygon = Utils.newFloatArray(boundingBox.worldVerticesLength); + } + polygons.push(polygon); + boundingBox.computeWorldVertices(slot, 0, boundingBox.worldVerticesLength, polygon, 0, 2); + } + } + + if (updateAabb) { + this.aabbCompute(); + } else { + this.minX = Number.POSITIVE_INFINITY; + this.minY = Number.POSITIVE_INFINITY; + this.maxX = Number.NEGATIVE_INFINITY; + this.maxY = Number.NEGATIVE_INFINITY; + } + } + + aabbCompute() { + let minX = Number.POSITIVE_INFINITY, + minY = Number.POSITIVE_INFINITY, + maxX = Number.NEGATIVE_INFINITY, + maxY = Number.NEGATIVE_INFINITY; + const polygons = this.polygons; + for (let i = 0, n = polygons.length; i < n; i++) { + const polygon = polygons[i]; + const vertices = polygon; + for (let ii = 0, nn = polygon.length; ii < nn; ii += 2) { + const x = vertices[ii]; + const y = vertices[ii + 1]; + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** Returns true if the axis aligned bounding box contains the point. */ + aabbContainsPoint(x: number, y: number) { + return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY; + } + + /** Returns true if the axis aligned bounding box intersects the line segment. */ + aabbIntersectsSegment(x1: number, y1: number, x2: number, y2: number) { + const minX = this.minX; + const minY = this.minY; + const maxX = this.maxX; + const maxY = this.maxY; + if ( + (x1 <= minX && x2 <= minX) || + (y1 <= minY && y2 <= minY) || + (x1 >= maxX && x2 >= maxX) || + (y1 >= maxY && y2 >= maxY) + ) + return false; + const m = (y2 - y1) / (x2 - x1); + let y = m * (minX - x1) + y1; + if (y > minY && y < maxY) return true; + y = m * (maxX - x1) + y1; + if (y > minY && y < maxY) return true; + let x = (minY - y1) / m + x1; + if (x > minX && x < maxX) return true; + x = (maxY - y1) / m + x1; + if (x > minX && x < maxX) return true; + return false; + } + + /** Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. */ + aabbIntersectsSkeleton(bounds: SkeletonBounds) { + return this.minX < bounds.maxX && this.maxX > bounds.minX && this.minY < bounds.maxY && this.maxY > bounds.minY; + } + + /** Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more + * efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true. */ + containsPoint(x: number, y: number): BoundingBoxAttachment { + const polygons = this.polygons; + for (let i = 0, n = polygons.length; i < n; i++) + if (this.containsPointPolygon(polygons[i], x, y)) return this.boundingBoxes[i]; + return null; + } + + /** Returns true if the polygon contains the point. */ + containsPointPolygon(polygon: ArrayLike, x: number, y: number) { + const vertices = polygon; + const nn = polygon.length; + + let prevIndex = nn - 2; + let inside = false; + for (let ii = 0; ii < nn; ii += 2) { + const vertexY = vertices[ii + 1]; + const prevY = vertices[prevIndex + 1]; + if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) { + const vertexX = vertices[ii]; + if (vertexX + ((y - vertexY) / (prevY - vertexY)) * (vertices[prevIndex] - vertexX) < x) inside = !inside; + } + prevIndex = ii; + } + return inside; + } + + /** Returns the first bounding box attachment that contains any part of the line segment, or null. When doing many checks, it + * is usually more efficient to only call this method if {@link #aabbIntersectsSegment()} returns + * true. */ + intersectsSegment(x1: number, y1: number, x2: number, y2: number) { + const polygons = this.polygons; + for (let i = 0, n = polygons.length; i < n; i++) + if (this.intersectsSegmentPolygon(polygons[i], x1, y1, x2, y2)) return this.boundingBoxes[i]; + return null; + } + + /** Returns true if the polygon contains any part of the line segment. */ + intersectsSegmentPolygon(polygon: ArrayLike, x1: number, y1: number, x2: number, y2: number) { + const vertices = polygon; + const nn = polygon.length; + + const width12 = x1 - x2, + height12 = y1 - y2; + const det1 = x1 * y2 - y1 * x2; + let x3 = vertices[nn - 2], + y3 = vertices[nn - 1]; + for (let ii = 0; ii < nn; ii += 2) { + const x4 = vertices[ii], + y4 = vertices[ii + 1]; + const det2 = x3 * y4 - y3 * x4; + const width34 = x3 - x4, + height34 = y3 - y4; + const det3 = width12 * height34 - height12 * width34; + const x = (det1 * width34 - width12 * det2) / det3; + if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) { + const y = (det1 * height34 - height12 * det2) / det3; + if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) + return true; + } + x3 = x4; + y3 = y4; + } + return false; + } + + /** Returns the polygon for the specified bounding box, or null. */ + getPolygon(boundingBox: BoundingBoxAttachment) { + if (boundingBox == null) throw new Error("boundingBox cannot be null."); + const index = this.boundingBoxes.indexOf(boundingBox); + return index == -1 ? null : this.polygons[index]; + } + + /** The width of the axis aligned bounding box. */ + getWidth() { + return this.maxX - this.minX; + } + + /** The height of the axis aligned bounding box. */ + getHeight() { + return this.maxY - this.minY; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/SkeletonClipping.ts b/packages/spine-core-3.8/src/spine-core/SkeletonClipping.ts new file mode 100644 index 0000000000..563a93c1f6 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/SkeletonClipping.ts @@ -0,0 +1,363 @@ +import { Triangulator } from "./Triangulator"; +import { ClippingAttachment } from "./attachments/ClippingAttachment"; +import { Slot } from "./Slot"; +import { Utils, Color, ArrayLike } from "./Utils"; + +export class SkeletonClipping { + private triangulator = new Triangulator(); + private clippingPolygon = new Array(); + private clipOutput = new Array(); + clippedVertices = new Array(); + clippedTriangles = new Array(); + private scratch = new Array(); + + private clipAttachment: ClippingAttachment; + private clippingPolygons: Array>; + + clipStart(slot: Slot, clip: ClippingAttachment): number { + if (this.clipAttachment != null) return 0; + this.clipAttachment = clip; + + const n = clip.worldVerticesLength; + const vertices = Utils.setArraySize(this.clippingPolygon, n); + clip.computeWorldVertices(slot, 0, n, vertices, 0, 2); + const clippingPolygon = this.clippingPolygon; + SkeletonClipping.makeClockwise(clippingPolygon); + const clippingPolygons = (this.clippingPolygons = this.triangulator.decompose( + clippingPolygon, + this.triangulator.triangulate(clippingPolygon) + )); + for (let i = 0, n = clippingPolygons.length; i < n; i++) { + const polygon = clippingPolygons[i]; + SkeletonClipping.makeClockwise(polygon); + polygon.push(polygon[0]); + polygon.push(polygon[1]); + } + + return clippingPolygons.length; + } + + clipEndWithSlot(slot: Slot) { + if (this.clipAttachment != null && this.clipAttachment.endSlot == slot.data) this.clipEnd(); + } + + clipEnd() { + if (this.clipAttachment == null) return; + this.clipAttachment = null; + this.clippingPolygons = null; + this.clippedVertices.length = 0; + this.clippedTriangles.length = 0; + this.clippingPolygon.length = 0; + } + + isClipping(): boolean { + return this.clipAttachment != null; + } + + clipTriangles( + vertices: ArrayLike, + verticesLength: number, + triangles: ArrayLike, + trianglesLength: number, + uvs: ArrayLike, + light: Color, + dark: Color, + twoColor: boolean + ) { + const clipOutput = this.clipOutput, + clippedVertices = this.clippedVertices; + const clippedTriangles = this.clippedTriangles; + const polygons = this.clippingPolygons; + const polygonsCount = this.clippingPolygons.length; + const vertexSize = twoColor ? 12 : 8; + + let index = 0; + clippedVertices.length = 0; + clippedTriangles.length = 0; + outer: for (let i = 0; i < trianglesLength; i += 3) { + let vertexOffset = triangles[i] << 1; + const x1 = vertices[vertexOffset], + y1 = vertices[vertexOffset + 1]; + const u1 = uvs[vertexOffset], + v1 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] << 1; + const x2 = vertices[vertexOffset], + y2 = vertices[vertexOffset + 1]; + const u2 = uvs[vertexOffset], + v2 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] << 1; + const x3 = vertices[vertexOffset], + y3 = vertices[vertexOffset + 1]; + const u3 = uvs[vertexOffset], + v3 = uvs[vertexOffset + 1]; + + for (let p = 0; p < polygonsCount; p++) { + let s = clippedVertices.length; + if (this.clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { + const clipOutputLength = clipOutput.length; + if (clipOutputLength == 0) continue; + const d0 = y2 - y3, + d1 = x3 - x2, + d2 = x1 - x3, + d4 = y3 - y1; + const d = 1 / (d0 * d2 + d1 * (y1 - y3)); + + let clipOutputCount = clipOutputLength >> 1; + const clipOutputItems = this.clipOutput; + const clippedVerticesItems = Utils.setArraySize(clippedVertices, s + clipOutputCount * vertexSize); + for (let ii = 0; ii < clipOutputLength; ii += 2) { + const x = clipOutputItems[ii], + y = clipOutputItems[ii + 1]; + clippedVerticesItems[s] = x; + clippedVerticesItems[s + 1] = y; + clippedVerticesItems[s + 2] = light.r; + clippedVerticesItems[s + 3] = light.g; + clippedVerticesItems[s + 4] = light.b; + clippedVerticesItems[s + 5] = light.a; + const c0 = x - x3, + c1 = y - y3; + const a = (d0 * c0 + d1 * c1) * d; + const b = (d4 * c0 + d2 * c1) * d; + const c = 1 - a - b; + clippedVerticesItems[s + 6] = u1 * a + u2 * b + u3 * c; + clippedVerticesItems[s + 7] = v1 * a + v2 * b + v3 * c; + if (twoColor) { + clippedVerticesItems[s + 8] = dark.r; + clippedVerticesItems[s + 9] = dark.g; + clippedVerticesItems[s + 10] = dark.b; + clippedVerticesItems[s + 11] = dark.a; + } + s += vertexSize; + } + + s = clippedTriangles.length; + const clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3 * (clipOutputCount - 2)); + clipOutputCount--; + for (let ii = 1; ii < clipOutputCount; ii++) { + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + ii; + clippedTrianglesItems[s + 2] = index + ii + 1; + s += 3; + } + index += clipOutputCount + 1; + } else { + const clippedVerticesItems = Utils.setArraySize(clippedVertices, s + 3 * vertexSize); + clippedVerticesItems[s] = x1; + clippedVerticesItems[s + 1] = y1; + clippedVerticesItems[s + 2] = light.r; + clippedVerticesItems[s + 3] = light.g; + clippedVerticesItems[s + 4] = light.b; + clippedVerticesItems[s + 5] = light.a; + if (!twoColor) { + clippedVerticesItems[s + 6] = u1; + clippedVerticesItems[s + 7] = v1; + + clippedVerticesItems[s + 8] = x2; + clippedVerticesItems[s + 9] = y2; + clippedVerticesItems[s + 10] = light.r; + clippedVerticesItems[s + 11] = light.g; + clippedVerticesItems[s + 12] = light.b; + clippedVerticesItems[s + 13] = light.a; + clippedVerticesItems[s + 14] = u2; + clippedVerticesItems[s + 15] = v2; + + clippedVerticesItems[s + 16] = x3; + clippedVerticesItems[s + 17] = y3; + clippedVerticesItems[s + 18] = light.r; + clippedVerticesItems[s + 19] = light.g; + clippedVerticesItems[s + 20] = light.b; + clippedVerticesItems[s + 21] = light.a; + clippedVerticesItems[s + 22] = u3; + clippedVerticesItems[s + 23] = v3; + } else { + clippedVerticesItems[s + 6] = u1; + clippedVerticesItems[s + 7] = v1; + clippedVerticesItems[s + 8] = dark.r; + clippedVerticesItems[s + 9] = dark.g; + clippedVerticesItems[s + 10] = dark.b; + clippedVerticesItems[s + 11] = dark.a; + + clippedVerticesItems[s + 12] = x2; + clippedVerticesItems[s + 13] = y2; + clippedVerticesItems[s + 14] = light.r; + clippedVerticesItems[s + 15] = light.g; + clippedVerticesItems[s + 16] = light.b; + clippedVerticesItems[s + 17] = light.a; + clippedVerticesItems[s + 18] = u2; + clippedVerticesItems[s + 19] = v2; + clippedVerticesItems[s + 20] = dark.r; + clippedVerticesItems[s + 21] = dark.g; + clippedVerticesItems[s + 22] = dark.b; + clippedVerticesItems[s + 23] = dark.a; + + clippedVerticesItems[s + 24] = x3; + clippedVerticesItems[s + 25] = y3; + clippedVerticesItems[s + 26] = light.r; + clippedVerticesItems[s + 27] = light.g; + clippedVerticesItems[s + 28] = light.b; + clippedVerticesItems[s + 29] = light.a; + clippedVerticesItems[s + 30] = u3; + clippedVerticesItems[s + 31] = v3; + clippedVerticesItems[s + 32] = dark.r; + clippedVerticesItems[s + 33] = dark.g; + clippedVerticesItems[s + 34] = dark.b; + clippedVerticesItems[s + 35] = dark.a; + } + + s = clippedTriangles.length; + const clippedTrianglesItems = Utils.setArraySize(clippedTriangles, s + 3); + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + 1; + clippedTrianglesItems[s + 2] = index + 2; + index += 3; + continue outer; + } + } + } + } + + /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping + * area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list. */ + clip( + x1: number, + y1: number, + x2: number, + y2: number, + x3: number, + y3: number, + clippingArea: Array, + output: Array + ) { + const originalOutput = output; + let clipped = false; + + // Avoid copy at the end. + let input: Array = null; + if (clippingArea.length % 4 >= 2) { + input = output; + output = this.scratch; + } else input = this.scratch; + + input.length = 0; + input.push(x1); + input.push(y1); + input.push(x2); + input.push(y2); + input.push(x3); + input.push(y3); + input.push(x1); + input.push(y1); + output.length = 0; + + const clippingVertices = clippingArea; + const clippingVerticesLast = clippingArea.length - 4; + for (let i = 0; ; i += 2) { + const edgeX = clippingVertices[i], + edgeY = clippingVertices[i + 1]; + const edgeX2 = clippingVertices[i + 2], + edgeY2 = clippingVertices[i + 3]; + const deltaX = edgeX - edgeX2, + deltaY = edgeY - edgeY2; + + const inputVertices = input; + const inputVerticesLength = input.length - 2, + outputStart = output.length; + for (let ii = 0; ii < inputVerticesLength; ii += 2) { + const inputX = inputVertices[ii], + inputY = inputVertices[ii + 1]; + const inputX2 = inputVertices[ii + 2], + inputY2 = inputVertices[ii + 3]; + const side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; + if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) { + if (side2) { + // v1 inside, v2 inside + output.push(inputX2); + output.push(inputY2); + continue; + } + // v1 inside, v2 outside + const c0 = inputY2 - inputY, + c2 = inputX2 - inputX; + const s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (Math.abs(s) > 0.000001) { + const ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output.push(edgeX + (edgeX2 - edgeX) * ua); + output.push(edgeY + (edgeY2 - edgeY) * ua); + } else { + output.push(edgeX); + output.push(edgeY); + } + } else if (side2) { + // v1 outside, v2 inside + const c0 = inputY2 - inputY, + c2 = inputX2 - inputX; + const s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (Math.abs(s) > 0.000001) { + const ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output.push(edgeX + (edgeX2 - edgeX) * ua); + output.push(edgeY + (edgeY2 - edgeY) * ua); + } else { + output.push(edgeX); + output.push(edgeY); + } + output.push(inputX2); + output.push(inputY2); + } + clipped = true; + } + + if (outputStart == output.length) { + // All edges outside. + originalOutput.length = 0; + return true; + } + + output.push(output[0]); + output.push(output[1]); + + if (i == clippingVerticesLast) break; + const temp = output; + output = input; + output.length = 0; + input = temp; + } + + if (originalOutput != output) { + originalOutput.length = 0; + for (let i = 0, n = output.length - 2; i < n; i++) originalOutput[i] = output[i]; + } else originalOutput.length = originalOutput.length - 2; + + return clipped; + } + + public static makeClockwise(polygon: ArrayLike) { + const vertices = polygon; + const verticeslength = polygon.length; + + let area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], + p1x = 0, + p1y = 0, + p2x = 0, + p2y = 0; + for (let i = 0, n = verticeslength - 3; i < n; i += 2) { + p1x = vertices[i]; + p1y = vertices[i + 1]; + p2x = vertices[i + 2]; + p2y = vertices[i + 3]; + area += p1x * p2y - p2x * p1y; + } + if (area < 0) return; + + for (let i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { + const x = vertices[i], + y = vertices[i + 1]; + const other = lastX - i; + vertices[i] = vertices[other]; + vertices[i + 1] = vertices[other + 1]; + vertices[other] = x; + vertices[other + 1] = y; + } + } +} diff --git a/packages/spine-core-3.8/src/spine-core/SkeletonData.ts b/packages/spine-core-3.8/src/spine-core/SkeletonData.ts new file mode 100644 index 0000000000..67c4f657bb --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/SkeletonData.ts @@ -0,0 +1,198 @@ +import { BoneData } from "./BoneData"; +import { SlotData } from "./SlotData"; +import { Skin } from "./Skin"; +import { EventData } from "./EventData"; +import { IkConstraintData } from "./IkConstraintData"; +import { TransformConstraintData } from "./TransformConstraintData"; +import { PathConstraintData } from "./PathConstraintData"; +import { Animation } from "./Animation"; + +/** Stores the setup pose and all of the stateless data for a skeleton. + * + * See [Data objects](http://esotericsoftware.com/spine-runtime-architecture#Data-objects) in the Spine Runtimes + * Guide. */ +export class SkeletonData { + /** The skeleton's name, which by default is the name of the skeleton data file, if possible. May be null. */ + name: string; + + /** The skeleton's bones, sorted parent first. The root bone is always the first bone. */ + bones = new Array(); // Ordered parents first. + + /** The skeleton's slots. */ + slots = new Array(); // Setup pose draw order. + skins = new Array(); + + /** The skeleton's default skin. By default this skin contains all attachments that were not in a skin in Spine. + * + * See {@link Skeleton#getAttachmentByName()}. + * May be null. */ + defaultSkin: Skin; + + /** The skeleton's events. */ + events = new Array(); + + /** The skeleton's animations. */ + animations = new Array(); + + /** The skeleton's IK constraints. */ + ikConstraints = new Array(); + + /** The skeleton's transform constraints. */ + transformConstraints = new Array(); + + /** The skeleton's path constraints. */ + pathConstraints = new Array(); + + /** The X coordinate of the skeleton's axis aligned bounding box in the setup pose. */ + x: number; + + /** The Y coordinate of the skeleton's axis aligned bounding box in the setup pose. */ + y: number; + + /** The width of the skeleton's axis aligned bounding box in the setup pose. */ + width: number; + + /** The height of the skeleton's axis aligned bounding box in the setup pose. */ + height: number; + + /** The Spine version used to export the skeleton data, or null. */ + version: string; + + /** The skeleton data hash. This value will change if any of the skeleton data has changed. May be null. */ + hash: string; + + // Nonessential + /** The dopesheet FPS in Spine. Available only when nonessential data was exported. */ + fps = 0; + + /** The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null. */ + imagesPath: string; + + /** The path to the audio directory as defined in Spine. Available only when nonessential data was exported. May be null. */ + audioPath: string; + + /** Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it + * multiple times. + * @returns May be null. */ + findBone(boneName: string) { + if (boneName == null) throw new Error("boneName cannot be null."); + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) { + const bone = bones[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + findBoneIndex(boneName: string) { + if (boneName == null) throw new Error("boneName cannot be null."); + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) if (bones[i].name == boneName) return i; + return -1; + } + + /** Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it + * multiple times. + * @returns May be null. */ + findSlot(slotName: string) { + if (slotName == null) throw new Error("slotName cannot be null."); + const slots = this.slots; + for (let i = 0, n = slots.length; i < n; i++) { + const slot = slots[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + findSlotIndex(slotName: string) { + if (slotName == null) throw new Error("slotName cannot be null."); + const slots = this.slots; + for (let i = 0, n = slots.length; i < n; i++) if (slots[i].name == slotName) return i; + return -1; + } + + /** Finds a skin by comparing each skin's name. It is more efficient to cache the results of this method than to call it + * multiple times. + * @returns May be null. */ + findSkin(skinName: string) { + if (skinName == null) throw new Error("skinName cannot be null."); + const skins = this.skins; + for (let i = 0, n = skins.length; i < n; i++) { + const skin = skins[i]; + if (skin.name == skinName) return skin; + } + return null; + } + + /** Finds an event by comparing each events's name. It is more efficient to cache the results of this method than to call it + * multiple times. + * @returns May be null. */ + findEvent(eventDataName: string) { + if (eventDataName == null) throw new Error("eventDataName cannot be null."); + const events = this.events; + for (let i = 0, n = events.length; i < n; i++) { + const event = events[i]; + if (event.name == eventDataName) return event; + } + return null; + } + + /** Finds an animation by comparing each animation's name. It is more efficient to cache the results of this method than to + * call it multiple times. + * @returns May be null. */ + findAnimation(animationName: string) { + if (animationName == null) throw new Error("animationName cannot be null."); + const animations = this.animations; + for (let i = 0, n = animations.length; i < n; i++) { + const animation = animations[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + /** Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method + * than to call it multiple times. + * @return May be null. */ + findIkConstraint(constraintName: string) { + if (constraintName == null) throw new Error("constraintName cannot be null."); + const ikConstraints = this.ikConstraints; + for (let i = 0, n = ikConstraints.length; i < n; i++) { + const constraint = ikConstraints[i]; + if (constraint.name == constraintName) return constraint; + } + return null; + } + + /** Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of + * this method than to call it multiple times. + * @return May be null. */ + findTransformConstraint(constraintName: string) { + if (constraintName == null) throw new Error("constraintName cannot be null."); + const transformConstraints = this.transformConstraints; + for (let i = 0, n = transformConstraints.length; i < n; i++) { + const constraint = transformConstraints[i]; + if (constraint.name == constraintName) return constraint; + } + return null; + } + + /** Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method + * than to call it multiple times. + * @return May be null. */ + findPathConstraint(constraintName: string) { + if (constraintName == null) throw new Error("constraintName cannot be null."); + const pathConstraints = this.pathConstraints; + for (let i = 0, n = pathConstraints.length; i < n; i++) { + const constraint = pathConstraints[i]; + if (constraint.name == constraintName) return constraint; + } + return null; + } + + findPathConstraintIndex(pathConstraintName: string) { + if (pathConstraintName == null) throw new Error("pathConstraintName cannot be null."); + const pathConstraints = this.pathConstraints; + for (let i = 0, n = pathConstraints.length; i < n; i++) if (pathConstraints[i].name == pathConstraintName) return i; + return -1; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/SkeletonJson.ts b/packages/spine-core-3.8/src/spine-core/SkeletonJson.ts new file mode 100644 index 0000000000..9c55054752 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/SkeletonJson.ts @@ -0,0 +1,906 @@ +import { AttachmentLoader } from "./attachments/AttachmentLoader"; +import { SkeletonData } from "./SkeletonData"; +import { BoneData, TransformMode } from "./BoneData"; +import { SlotData } from "./SlotData"; +import { Color, Utils, ArrayLike } from "./Utils"; +import { IkConstraintData } from "./IkConstraintData"; +import { TransformConstraintData } from "./TransformConstraintData"; +import { PathConstraintData, PositionMode, SpacingMode, RotateMode } from "./PathConstraintData"; +import { Skin } from "./Skin"; +import { VertexAttachment, Attachment } from "./attachments/Attachment"; +import { MeshAttachment } from "./attachments/MeshAttachment"; +import { EventData } from "./EventData"; +import { + Timeline, + AttachmentTimeline, + ColorTimeline, + TwoColorTimeline, + RotateTimeline, + TranslateTimeline, + ScaleTimeline, + ShearTimeline, + IkConstraintTimeline, + TransformConstraintTimeline, + PathConstraintPositionTimeline, + PathConstraintSpacingTimeline, + PathConstraintMixTimeline, + DeformTimeline, + DrawOrderTimeline, + EventTimeline, + CurveTimeline +} from "./Animation"; +import { BlendMode } from "./BlendMode"; +import { Event } from "./Event"; +import { Animation } from "./Animation"; + +/** Loads skeleton data in the Spine JSON format. + * + * See [Spine JSON format](http://esotericsoftware.com/spine-json-format) and + * [JSON and binary data](http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data) in the Spine + * Runtimes Guide. */ +export class SkeletonJson { + attachmentLoader: AttachmentLoader; + + /** Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at + * runtime than were used in Spine. + * + * See [Scaling](http://esotericsoftware.com/spine-loading-skeleton-data#Scaling) in the Spine Runtimes Guide. */ + scale = 1; + private linkedMeshes = new Array(); + + constructor(attachmentLoader: AttachmentLoader) { + this.attachmentLoader = attachmentLoader; + } + + readSkeletonData(json: string | any): SkeletonData { + const scale = this.scale; + const skeletonData = new SkeletonData(); + const root = typeof json === "string" ? JSON.parse(json) : json; + + // Skeleton + const skeletonMap = root.skeleton; + if (skeletonMap != null) { + skeletonData.hash = skeletonMap.hash; + skeletonData.version = skeletonMap.spine; + if ("3.8.75" == skeletonData.version) + throw new Error("Unsupported skeleton data, please export with a newer version of Spine."); + skeletonData.x = skeletonMap.x; + skeletonData.y = skeletonMap.y; + skeletonData.width = skeletonMap.width; + skeletonData.height = skeletonMap.height; + skeletonData.fps = skeletonMap.fps; + skeletonData.imagesPath = skeletonMap.images; + } + + // Bones + if (root.bones) { + for (let i = 0; i < root.bones.length; i++) { + const boneMap = root.bones[i]; + + let parent: BoneData = null; + const parentName: string = this.getValue(boneMap, "parent", null); + if (parentName != null) { + parent = skeletonData.findBone(parentName); + if (parent == null) throw new Error("Parent bone not found: " + parentName); + } + const data = new BoneData(skeletonData.bones.length, boneMap.name, parent); + data.length = this.getValue(boneMap, "length", 0) * scale; + data.x = this.getValue(boneMap, "x", 0) * scale; + data.y = this.getValue(boneMap, "y", 0) * scale; + data.rotation = this.getValue(boneMap, "rotation", 0); + data.scaleX = this.getValue(boneMap, "scaleX", 1); + data.scaleY = this.getValue(boneMap, "scaleY", 1); + data.shearX = this.getValue(boneMap, "shearX", 0); + data.shearY = this.getValue(boneMap, "shearY", 0); + data.transformMode = SkeletonJson.transformModeFromString(this.getValue(boneMap, "transform", "normal")); + data.skinRequired = this.getValue(boneMap, "skin", false); + + skeletonData.bones.push(data); + } + } + + // Slots. + if (root.slots) { + for (let i = 0; i < root.slots.length; i++) { + const slotMap = root.slots[i]; + const slotName: string = slotMap.name; + const boneName: string = slotMap.bone; + const boneData = skeletonData.findBone(boneName); + if (boneData == null) throw new Error("Slot bone not found: " + boneName); + const data = new SlotData(skeletonData.slots.length, slotName, boneData); + + const color: string = this.getValue(slotMap, "color", null); + if (color != null) data.color.setFromString(color); + + const dark: string = this.getValue(slotMap, "dark", null); + if (dark != null) { + data.darkColor = new Color(1, 1, 1, 1); + data.darkColor.setFromString(dark); + } + + data.attachmentName = this.getValue(slotMap, "attachment", null); + data.blendMode = SkeletonJson.blendModeFromString(this.getValue(slotMap, "blend", "normal")); + skeletonData.slots.push(data); + } + } + + // IK constraints + if (root.ik) { + for (let i = 0; i < root.ik.length; i++) { + const constraintMap = root.ik[i]; + const data = new IkConstraintData(constraintMap.name); + data.order = this.getValue(constraintMap, "order", 0); + data.skinRequired = this.getValue(constraintMap, "skin", false); + + for (let j = 0; j < constraintMap.bones.length; j++) { + const boneName = constraintMap.bones[j]; + const bone = skeletonData.findBone(boneName); + if (bone == null) throw new Error("IK bone not found: " + boneName); + data.bones.push(bone); + } + + const targetName: string = constraintMap.target; + data.target = skeletonData.findBone(targetName); + if (data.target == null) throw new Error("IK target bone not found: " + targetName); + + data.mix = this.getValue(constraintMap, "mix", 1); + data.softness = this.getValue(constraintMap, "softness", 0) * scale; + data.bendDirection = this.getValue(constraintMap, "bendPositive", true) ? 1 : -1; + data.compress = this.getValue(constraintMap, "compress", false); + data.stretch = this.getValue(constraintMap, "stretch", false); + data.uniform = this.getValue(constraintMap, "uniform", false); + + skeletonData.ikConstraints.push(data); + } + } + + // Transform constraints. + if (root.transform) { + for (let i = 0; i < root.transform.length; i++) { + const constraintMap = root.transform[i]; + const data = new TransformConstraintData(constraintMap.name); + data.order = this.getValue(constraintMap, "order", 0); + data.skinRequired = this.getValue(constraintMap, "skin", false); + + for (let j = 0; j < constraintMap.bones.length; j++) { + const boneName = constraintMap.bones[j]; + const bone = skeletonData.findBone(boneName); + if (bone == null) throw new Error("Transform constraint bone not found: " + boneName); + data.bones.push(bone); + } + + const targetName: string = constraintMap.target; + data.target = skeletonData.findBone(targetName); + if (data.target == null) throw new Error("Transform constraint target bone not found: " + targetName); + + data.local = this.getValue(constraintMap, "local", false); + data.relative = this.getValue(constraintMap, "relative", false); + data.offsetRotation = this.getValue(constraintMap, "rotation", 0); + data.offsetX = this.getValue(constraintMap, "x", 0) * scale; + data.offsetY = this.getValue(constraintMap, "y", 0) * scale; + data.offsetScaleX = this.getValue(constraintMap, "scaleX", 0); + data.offsetScaleY = this.getValue(constraintMap, "scaleY", 0); + data.offsetShearY = this.getValue(constraintMap, "shearY", 0); + + data.rotateMix = this.getValue(constraintMap, "rotateMix", 1); + data.translateMix = this.getValue(constraintMap, "translateMix", 1); + data.scaleMix = this.getValue(constraintMap, "scaleMix", 1); + data.shearMix = this.getValue(constraintMap, "shearMix", 1); + + skeletonData.transformConstraints.push(data); + } + } + + // Path constraints. + if (root.path) { + for (let i = 0; i < root.path.length; i++) { + const constraintMap = root.path[i]; + const data = new PathConstraintData(constraintMap.name); + data.order = this.getValue(constraintMap, "order", 0); + data.skinRequired = this.getValue(constraintMap, "skin", false); + + for (let j = 0; j < constraintMap.bones.length; j++) { + const boneName = constraintMap.bones[j]; + const bone = skeletonData.findBone(boneName); + if (bone == null) throw new Error("Transform constraint bone not found: " + boneName); + data.bones.push(bone); + } + + const targetName: string = constraintMap.target; + data.target = skeletonData.findSlot(targetName); + if (data.target == null) throw new Error("Path target slot not found: " + targetName); + + data.positionMode = SkeletonJson.positionModeFromString( + this.getValue(constraintMap, "positionMode", "percent") + ); + data.spacingMode = SkeletonJson.spacingModeFromString(this.getValue(constraintMap, "spacingMode", "length")); + data.rotateMode = SkeletonJson.rotateModeFromString(this.getValue(constraintMap, "rotateMode", "tangent")); + data.offsetRotation = this.getValue(constraintMap, "rotation", 0); + data.position = this.getValue(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = this.getValue(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.rotateMix = this.getValue(constraintMap, "rotateMix", 1); + data.translateMix = this.getValue(constraintMap, "translateMix", 1); + + skeletonData.pathConstraints.push(data); + } + } + + // Skins. + if (root.skins) { + for (let i = 0; i < root.skins.length; i++) { + const skinMap = root.skins[i]; + const skin = new Skin(skinMap.name); + + if (skinMap.bones) { + for (let ii = 0; ii < skinMap.bones.length; ii++) { + const bone = skeletonData.findBone(skinMap.bones[ii]); + if (bone == null) throw new Error("Skin bone not found: " + skinMap.bones[i]); + skin.bones.push(bone); + } + } + + if (skinMap.ik) { + for (let ii = 0; ii < skinMap.ik.length; ii++) { + const constraint = skeletonData.findIkConstraint(skinMap.ik[ii]); + if (constraint == null) throw new Error("Skin IK constraint not found: " + skinMap.ik[i]); + skin.constraints.push(constraint); + } + } + + if (skinMap.transform) { + for (let ii = 0; ii < skinMap.transform.length; ii++) { + const constraint = skeletonData.findTransformConstraint(skinMap.transform[ii]); + if (constraint == null) throw new Error("Skin transform constraint not found: " + skinMap.transform[i]); + skin.constraints.push(constraint); + } + } + + if (skinMap.path) { + for (let ii = 0; ii < skinMap.path.length; ii++) { + const constraint = skeletonData.findPathConstraint(skinMap.path[ii]); + if (constraint == null) throw new Error("Skin path constraint not found: " + skinMap.path[i]); + skin.constraints.push(constraint); + } + } + + for (const slotName in skinMap.attachments) { + const slot = skeletonData.findSlot(slotName); + if (slot == null) throw new Error("Slot not found: " + slotName); + const slotMap = skinMap.attachments[slotName]; + for (const entryName in slotMap) { + const attachment = this.readAttachment(slotMap[entryName], skin, slot.index, entryName, skeletonData); + if (attachment != null) skin.setAttachment(slot.index, entryName, attachment); + } + } + skeletonData.skins.push(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (let i = 0, n = this.linkedMeshes.length; i < n; i++) { + const linkedMesh = this.linkedMeshes[i]; + const skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.findSkin(linkedMesh.skin); + if (skin == null) throw new Error("Skin not found: " + linkedMesh.skin); + const parent = skin.getAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Error("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.deformAttachment = linkedMesh.inheritDeform + ? parent + : linkedMesh.mesh; + linkedMesh.mesh.setParentMesh(parent); + linkedMesh.mesh.updateUVs(); + } + this.linkedMeshes.length = 0; + + // Events. + if (root.events) { + for (const eventName in root.events) { + const eventMap = root.events[eventName]; + const data = new EventData(eventName); + data.intValue = this.getValue(eventMap, "int", 0); + data.floatValue = this.getValue(eventMap, "float", 0); + data.stringValue = this.getValue(eventMap, "string", ""); + data.audioPath = this.getValue(eventMap, "audio", null); + if (data.audioPath != null) { + data.volume = this.getValue(eventMap, "volume", 1); + data.balance = this.getValue(eventMap, "balance", 0); + } + skeletonData.events.push(data); + } + } + + // Animations. + if (root.animations) { + for (const animationName in root.animations) { + const animationMap = root.animations[animationName]; + this.readAnimation(animationMap, animationName, skeletonData); + } + } + + return skeletonData; + } + + readAttachment(map: any, skin: Skin, slotIndex: number, name: string, skeletonData: SkeletonData): Attachment { + const scale = this.scale; + name = this.getValue(map, "name", name); + + const type = this.getValue(map, "type", "region"); + + switch (type) { + case "region": { + const path = this.getValue(map, "path", name); + const region = this.attachmentLoader.newRegionAttachment(skin, name, path); + if (region == null) return null; + region.path = path; + region.x = this.getValue(map, "x", 0) * scale; + region.y = this.getValue(map, "y", 0) * scale; + region.scaleX = this.getValue(map, "scaleX", 1); + region.scaleY = this.getValue(map, "scaleY", 1); + region.rotation = this.getValue(map, "rotation", 0); + region.width = map.width * scale; + region.height = map.height * scale; + + const color: string = this.getValue(map, "color", null); + if (color != null) region.color.setFromString(color); + + region.updateOffset(); + return region; + } + case "boundingbox": { + const box = this.attachmentLoader.newBoundingBoxAttachment(skin, name); + if (box == null) return null; + this.readVertices(map, box, map.vertexCount << 1); + const color: string = this.getValue(map, "color", null); + if (color != null) box.color.setFromString(color); + return box; + } + case "mesh": + case "linkedmesh": { + const path = this.getValue(map, "path", name); + const mesh = this.attachmentLoader.newMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.path = path; + + const color = this.getValue(map, "color", null); + if (color != null) mesh.color.setFromString(color); + + mesh.width = this.getValue(map, "width", 0) * scale; + mesh.height = this.getValue(map, "height", 0) * scale; + + const parent: string = this.getValue(map, "parent", null); + if (parent != null) { + this.linkedMeshes.push( + new LinkedMesh( + mesh, + this.getValue(map, "skin", null), + slotIndex, + parent, + this.getValue(map, "deform", true) + ) + ); + return mesh; + } + + const uvs: Array = map.uvs; + this.readVertices(map, mesh, uvs.length); + mesh.triangles = map.triangles; + mesh.regionUVs = uvs; + mesh.updateUVs(); + + mesh.edges = this.getValue(map, "edges", null); + mesh.hullLength = this.getValue(map, "hull", 0) * 2; + return mesh; + } + case "path": { + const path = this.attachmentLoader.newPathAttachment(skin, name); + if (path == null) return null; + path.closed = this.getValue(map, "closed", false); + path.constantSpeed = this.getValue(map, "constantSpeed", true); + + const vertexCount = map.vertexCount; + this.readVertices(map, path, vertexCount << 1); + + const lengths: Array = Utils.newArray(vertexCount / 3, 0); + for (let i = 0; i < map.lengths.length; i++) lengths[i] = map.lengths[i] * scale; + path.lengths = lengths; + + const color: string = this.getValue(map, "color", null); + if (color != null) path.color.setFromString(color); + return path; + } + case "point": { + const point = this.attachmentLoader.newPointAttachment(skin, name); + if (point == null) return null; + point.x = this.getValue(map, "x", 0) * scale; + point.y = this.getValue(map, "y", 0) * scale; + point.rotation = this.getValue(map, "rotation", 0); + + const color = this.getValue(map, "color", null); + if (color != null) point.color.setFromString(color); + return point; + } + case "clipping": { + const clip = this.attachmentLoader.newClippingAttachment(skin, name); + if (clip == null) return null; + + const end = this.getValue(map, "end", null); + if (end != null) { + const slot = skeletonData.findSlot(end); + if (slot == null) throw new Error("Clipping end slot not found: " + end); + clip.endSlot = slot; + } + + const vertexCount = map.vertexCount; + this.readVertices(map, clip, vertexCount << 1); + + const color: string = this.getValue(map, "color", null); + if (color != null) clip.color.setFromString(color); + return clip; + } + } + return null; + } + + readVertices(map: any, attachment: VertexAttachment, verticesLength: number) { + const scale = this.scale; + attachment.worldVerticesLength = verticesLength; + const vertices: Array = map.vertices; + if (verticesLength == vertices.length) { + const scaledVertices = Utils.toFloatArray(vertices); + if (scale != 1) { + for (let i = 0, n = vertices.length; i < n; i++) scaledVertices[i] *= scale; + } + attachment.vertices = scaledVertices; + return; + } + const weights = new Array(); + const bones = new Array(); + for (let i = 0, n = vertices.length; i < n; ) { + const boneCount = vertices[i++]; + bones.push(boneCount); + for (let nn = i + boneCount * 4; i < nn; i += 4) { + bones.push(vertices[i]); + weights.push(vertices[i + 1] * scale); + weights.push(vertices[i + 2] * scale); + weights.push(vertices[i + 3]); + } + } + attachment.bones = bones; + attachment.vertices = Utils.toFloatArray(weights); + } + + readAnimation(map: any, name: string, skeletonData: SkeletonData) { + const scale = this.scale; + const timelines = new Array(); + let duration = 0; + + // Slot timelines. + if (map.slots) { + for (const slotName in map.slots) { + const slotMap = map.slots[slotName]; + const slotIndex = skeletonData.findSlotIndex(slotName); + if (slotIndex == -1) throw new Error("Slot not found: " + slotName); + for (const timelineName in slotMap) { + const timelineMap = slotMap[timelineName]; + if (timelineName == "attachment") { + const timeline = new AttachmentTimeline(timelineMap.length); + timeline.slotIndex = slotIndex; + + let frameIndex = 0; + for (let i = 0; i < timelineMap.length; i++) { + const valueMap = timelineMap[i]; + timeline.setFrame(frameIndex++, this.getValue(valueMap, "time", 0), valueMap.name); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); + } else if (timelineName == "color") { + const timeline = new ColorTimeline(timelineMap.length); + timeline.slotIndex = slotIndex; + + let frameIndex = 0; + for (let i = 0; i < timelineMap.length; i++) { + const valueMap = timelineMap[i]; + const color = new Color(); + color.setFromString(valueMap.color); + timeline.setFrame(frameIndex, this.getValue(valueMap, "time", 0), color.r, color.g, color.b, color.a); + this.readCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(timeline.getFrameCount() - 1) * ColorTimeline.ENTRIES]); + } else if (timelineName == "twoColor") { + const timeline = new TwoColorTimeline(timelineMap.length); + timeline.slotIndex = slotIndex; + + let frameIndex = 0; + for (let i = 0; i < timelineMap.length; i++) { + const valueMap = timelineMap[i]; + const light = new Color(); + const dark = new Color(); + light.setFromString(valueMap.light); + dark.setFromString(valueMap.dark); + timeline.setFrame( + frameIndex, + this.getValue(valueMap, "time", 0), + light.r, + light.g, + light.b, + light.a, + dark.r, + dark.g, + dark.b + ); + this.readCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(timeline.getFrameCount() - 1) * TwoColorTimeline.ENTRIES]); + } else throw new Error("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + // Bone timelines. + if (map.bones) { + for (const boneName in map.bones) { + const boneMap = map.bones[boneName]; + const boneIndex = skeletonData.findBoneIndex(boneName); + if (boneIndex == -1) throw new Error("Bone not found: " + boneName); + for (const timelineName in boneMap) { + const timelineMap = boneMap[timelineName]; + if (timelineName === "rotate") { + const timeline = new RotateTimeline(timelineMap.length); + timeline.boneIndex = boneIndex; + + let frameIndex = 0; + for (let i = 0; i < timelineMap.length; i++) { + const valueMap = timelineMap[i]; + timeline.setFrame(frameIndex, this.getValue(valueMap, "time", 0), this.getValue(valueMap, "angle", 0)); + this.readCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(timeline.getFrameCount() - 1) * RotateTimeline.ENTRIES]); + } else if (timelineName === "translate" || timelineName === "scale" || timelineName === "shear") { + let timeline: TranslateTimeline = null; + let timelineScale = 1, + defaultValue = 0; + if (timelineName === "scale") { + timeline = new ScaleTimeline(timelineMap.length); + defaultValue = 1; + } else if (timelineName === "shear") timeline = new ShearTimeline(timelineMap.length); + else { + timeline = new TranslateTimeline(timelineMap.length); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + + let frameIndex = 0; + for (let i = 0; i < timelineMap.length; i++) { + const valueMap = timelineMap[i]; + const x = this.getValue(valueMap, "x", defaultValue), + y = this.getValue(valueMap, "y", defaultValue); + timeline.setFrame(frameIndex, this.getValue(valueMap, "time", 0), x * timelineScale, y * timelineScale); + this.readCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(timeline.getFrameCount() - 1) * TranslateTimeline.ENTRIES]); + } else throw new Error("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK constraint timelines. + if (map.ik) { + for (const constraintName in map.ik) { + const constraintMap = map.ik[constraintName]; + const constraint = skeletonData.findIkConstraint(constraintName); + const timeline = new IkConstraintTimeline(constraintMap.length); + timeline.ikConstraintIndex = skeletonData.ikConstraints.indexOf(constraint); + let frameIndex = 0; + for (let i = 0; i < constraintMap.length; i++) { + const valueMap = constraintMap[i]; + timeline.setFrame( + frameIndex, + this.getValue(valueMap, "time", 0), + this.getValue(valueMap, "mix", 1), + this.getValue(valueMap, "softness", 0) * scale, + this.getValue(valueMap, "bendPositive", true) ? 1 : -1, + this.getValue(valueMap, "compress", false), + this.getValue(valueMap, "stretch", false) + ); + this.readCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[(timeline.getFrameCount() - 1) * IkConstraintTimeline.ENTRIES]); + } + } + + // Transform constraint timelines. + if (map.transform) { + for (const constraintName in map.transform) { + const constraintMap = map.transform[constraintName]; + const constraint = skeletonData.findTransformConstraint(constraintName); + const timeline = new TransformConstraintTimeline(constraintMap.length); + timeline.transformConstraintIndex = skeletonData.transformConstraints.indexOf(constraint); + let frameIndex = 0; + for (let i = 0; i < constraintMap.length; i++) { + const valueMap = constraintMap[i]; + timeline.setFrame( + frameIndex, + this.getValue(valueMap, "time", 0), + this.getValue(valueMap, "rotateMix", 1), + this.getValue(valueMap, "translateMix", 1), + this.getValue(valueMap, "scaleMix", 1), + this.getValue(valueMap, "shearMix", 1) + ); + this.readCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.push(timeline); + duration = Math.max( + duration, + timeline.frames[(timeline.getFrameCount() - 1) * TransformConstraintTimeline.ENTRIES] + ); + } + } + + // Path constraint timelines. + if (map.path) { + for (const constraintName in map.path) { + const constraintMap = map.path[constraintName]; + const index = skeletonData.findPathConstraintIndex(constraintName); + if (index == -1) throw new Error("Path constraint not found: " + constraintName); + const data = skeletonData.pathConstraints[index]; + for (const timelineName in constraintMap) { + const timelineMap = constraintMap[timelineName]; + if (timelineName === "position" || timelineName === "spacing") { + let timeline: PathConstraintPositionTimeline = null; + let timelineScale = 1; + if (timelineName === "spacing") { + timeline = new PathConstraintSpacingTimeline(timelineMap.length); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) + timelineScale = scale; + } else { + timeline = new PathConstraintPositionTimeline(timelineMap.length); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + let frameIndex = 0; + for (let i = 0; i < timelineMap.length; i++) { + const valueMap = timelineMap[i]; + timeline.setFrame( + frameIndex, + this.getValue(valueMap, "time", 0), + this.getValue(valueMap, timelineName, 0) * timelineScale + ); + this.readCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.push(timeline); + duration = Math.max( + duration, + timeline.frames[(timeline.getFrameCount() - 1) * PathConstraintPositionTimeline.ENTRIES] + ); + } else if (timelineName === "mix") { + const timeline = new PathConstraintMixTimeline(timelineMap.length); + timeline.pathConstraintIndex = index; + let frameIndex = 0; + for (let i = 0; i < timelineMap.length; i++) { + const valueMap = timelineMap[i]; + timeline.setFrame( + frameIndex, + this.getValue(valueMap, "time", 0), + this.getValue(valueMap, "rotateMix", 1), + this.getValue(valueMap, "translateMix", 1) + ); + this.readCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.push(timeline); + duration = Math.max( + duration, + timeline.frames[(timeline.getFrameCount() - 1) * PathConstraintMixTimeline.ENTRIES] + ); + } + } + } + } + + // Deform timelines. + if (map.deform) { + for (const deformName in map.deform) { + const deformMap = map.deform[deformName]; + const skin = skeletonData.findSkin(deformName); + if (skin == null) throw new Error("Skin not found: " + deformName); + for (const slotName in deformMap) { + const slotMap = deformMap[slotName]; + const slotIndex = skeletonData.findSlotIndex(slotName); + if (slotIndex == -1) throw new Error("Slot not found: " + slotMap.name); + for (const timelineName in slotMap) { + const timelineMap = slotMap[timelineName]; + const attachment = skin.getAttachment(slotIndex, timelineName); + if (attachment == null) throw new Error("Deform attachment not found: " + timelineMap.name); + const weighted = attachment.bones != null; + const vertices = attachment.vertices; + const deformLength = weighted ? (vertices.length / 3) * 2 : vertices.length; + + const timeline = new DeformTimeline(timelineMap.length); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + let frameIndex = 0; + for (let j = 0; j < timelineMap.length; j++) { + const valueMap = timelineMap[j]; + let deform: ArrayLike; + const verticesValue: Array = this.getValue(valueMap, "vertices", null); + if (verticesValue == null) deform = weighted ? Utils.newFloatArray(deformLength) : vertices; + else { + deform = Utils.newFloatArray(deformLength); + const start = this.getValue(valueMap, "offset", 0); + Utils.arrayCopy(verticesValue, 0, deform, start, verticesValue.length); + if (scale != 1) { + for (let i = start, n = i + verticesValue.length; i < n; i++) deform[i] *= scale; + } + if (!weighted) { + for (let i = 0; i < deformLength; i++) deform[i] += vertices[i]; + } + } + + timeline.setFrame(frameIndex, this.getValue(valueMap, "time", 0), deform); + this.readCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); + } + } + } + } + + // Draw order timeline. + let drawOrderNode = map.drawOrder; + if (drawOrderNode == null) drawOrderNode = map.draworder; + if (drawOrderNode != null) { + const timeline = new DrawOrderTimeline(drawOrderNode.length); + const slotCount = skeletonData.slots.length; + let frameIndex = 0; + for (let j = 0; j < drawOrderNode.length; j++) { + const drawOrderMap = drawOrderNode[j]; + let drawOrder: Array = null; + const offsets = this.getValue(drawOrderMap, "offsets", null); + if (offsets != null) { + drawOrder = Utils.newArray(slotCount, -1); + const unchanged = Utils.newArray(slotCount - offsets.length, 0); + let originalIndex = 0, + unchangedIndex = 0; + for (let i = 0; i < offsets.length; i++) { + const offsetMap = offsets[i]; + const slotIndex = skeletonData.findSlotIndex(offsetMap.slot); + if (slotIndex == -1) throw new Error("Slot not found: " + offsetMap.slot); + // Collect unchanged items. + while (originalIndex != slotIndex) unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + offsetMap.offset] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (let i = slotCount - 1; i >= 0; i--) if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.setFrame(frameIndex++, this.getValue(drawOrderMap, "time", 0), drawOrder); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); + } + + // Event timeline. + if (map.events) { + const timeline = new EventTimeline(map.events.length); + let frameIndex = 0; + for (let i = 0; i < map.events.length; i++) { + const eventMap = map.events[i]; + const eventData = skeletonData.findEvent(eventMap.name); + if (eventData == null) throw new Error("Event not found: " + eventMap.name); + const event = new Event(Utils.toSinglePrecision(this.getValue(eventMap, "time", 0)), eventData); + event.intValue = this.getValue(eventMap, "int", eventData.intValue); + event.floatValue = this.getValue(eventMap, "float", eventData.floatValue); + event.stringValue = this.getValue(eventMap, "string", eventData.stringValue); + if (event.data.audioPath != null) { + event.volume = this.getValue(eventMap, "volume", 1); + event.balance = this.getValue(eventMap, "balance", 0); + } + timeline.setFrame(frameIndex++, event); + } + timelines.push(timeline); + duration = Math.max(duration, timeline.frames[timeline.getFrameCount() - 1]); + } + + if (isNaN(duration)) { + throw new Error("Error while parsing animation, duration is NaN"); + } + + skeletonData.animations.push(new Animation(name, timelines, duration)); + } + + readCurve(map: any, timeline: CurveTimeline, frameIndex: number) { + if (!map.hasOwnProperty("curve")) return; + if (map.curve == "stepped") timeline.setStepped(frameIndex); + else { + const curve: number = map.curve; + timeline.setCurve( + frameIndex, + curve, + this.getValue(map, "c2", 0), + this.getValue(map, "c3", 1), + this.getValue(map, "c4", 1) + ); + } + } + + getValue(map: any, prop: string, defaultValue: any) { + return map[prop] !== undefined ? map[prop] : defaultValue; + } + + static blendModeFromString(str: string) { + str = str.toLowerCase(); + if (str == "normal") return BlendMode.Normal; + if (str == "additive") return BlendMode.Additive; + if (str == "multiply") return BlendMode.Multiply; + if (str == "screen") return BlendMode.Screen; + throw new Error(`Unknown blend mode: ${str}`); + } + + static positionModeFromString(str: string) { + str = str.toLowerCase(); + if (str == "fixed") return PositionMode.Fixed; + if (str == "percent") return PositionMode.Percent; + throw new Error(`Unknown position mode: ${str}`); + } + + static spacingModeFromString(str: string) { + str = str.toLowerCase(); + if (str == "length") return SpacingMode.Length; + if (str == "fixed") return SpacingMode.Fixed; + if (str == "percent") return SpacingMode.Percent; + throw new Error(`Unknown position mode: ${str}`); + } + + static rotateModeFromString(str: string) { + str = str.toLowerCase(); + if (str == "tangent") return RotateMode.Tangent; + if (str == "chain") return RotateMode.Chain; + if (str == "chainscale") return RotateMode.ChainScale; + throw new Error(`Unknown rotate mode: ${str}`); + } + + static transformModeFromString(str: string) { + str = str.toLowerCase(); + if (str == "normal") return TransformMode.Normal; + if (str == "onlytranslation") return TransformMode.OnlyTranslation; + if (str == "norotationorreflection") return TransformMode.NoRotationOrReflection; + if (str == "noscale") return TransformMode.NoScale; + if (str == "noscaleorreflection") return TransformMode.NoScaleOrReflection; + throw new Error(`Unknown transform mode: ${str}`); + } +} + +class LinkedMesh { + parent: string; + skin: string; + slotIndex: number; + mesh: MeshAttachment; + inheritDeform: boolean; + + constructor(mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + this.inheritDeform = inheritDeform; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/Skin.ts b/packages/spine-core-3.8/src/spine-core/Skin.ts new file mode 100644 index 0000000000..3081742665 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/Skin.ts @@ -0,0 +1,182 @@ +import { Attachment } from "./attachments/Attachment"; +import { BoneData } from "./BoneData"; +import { ConstraintData } from "./ConstraintData"; +import { Skeleton } from "./Skeleton"; +import { MeshAttachment } from "./attachments/MeshAttachment"; +import { Map } from "./Utils"; + +/** Stores an entry in the skin consisting of the slot index, name, and attachment **/ +export class SkinEntry { + constructor( + public slotIndex: number, + public name: string, + public attachment: Attachment + ) {} +} + +/** Stores attachments by slot index and attachment name. + * + * See SkeletonData {@link SkeletonData#defaultSkin}, Skeleton {@link Skeleton#skin}, and + * [Runtime skins](http://esotericsoftware.com/spine-runtime-skins) in the Spine Runtimes Guide. */ +export class Skin { + /** The skin's name, which is unique across all skins in the skeleton. */ + name: string; + + attachments = new Array>(); + bones = Array(); + constraints = new Array(); + + constructor(name: string) { + if (name == null) throw new Error("name cannot be null."); + this.name = name; + } + + /** Adds an attachment to the skin for the specified slot index and name. */ + setAttachment(slotIndex: number, name: string, attachment: Attachment) { + if (attachment == null) throw new Error("attachment cannot be null."); + const attachments = this.attachments; + if (slotIndex >= attachments.length) attachments.length = slotIndex + 1; + if (!attachments[slotIndex]) attachments[slotIndex] = {}; + attachments[slotIndex][name] = attachment; + } + + /** Adds all attachments, bones, and constraints from the specified skin to this skin. */ + addSkin(skin: Skin) { + for (let i = 0; i < skin.bones.length; i++) { + const bone = skin.bones[i]; + let contained = false; + for (let j = 0; j < this.bones.length; j++) { + if (this.bones[j] == bone) { + contained = true; + break; + } + } + if (!contained) this.bones.push(bone); + } + + for (let i = 0; i < skin.constraints.length; i++) { + const constraint = skin.constraints[i]; + let contained = false; + for (let j = 0; j < this.constraints.length; j++) { + if (this.constraints[j] == constraint) { + contained = true; + break; + } + } + if (!contained) this.constraints.push(constraint); + } + + const attachments = skin.getAttachments(); + for (let i = 0; i < attachments.length; i++) { + const attachment = attachments[i]; + this.setAttachment(attachment.slotIndex, attachment.name, attachment.attachment); + } + } + + /** Adds all bones and constraints and copies of all attachments from the specified skin to this skin. Mesh attachments are not + * copied, instead a new linked mesh is created. The attachment copies can be modified without affecting the originals. */ + copySkin(skin: Skin) { + for (let i = 0; i < skin.bones.length; i++) { + const bone = skin.bones[i]; + let contained = false; + for (let j = 0; j < this.bones.length; j++) { + if (this.bones[j] == bone) { + contained = true; + break; + } + } + if (!contained) this.bones.push(bone); + } + + for (let i = 0; i < skin.constraints.length; i++) { + const constraint = skin.constraints[i]; + let contained = false; + for (let j = 0; j < this.constraints.length; j++) { + if (this.constraints[j] == constraint) { + contained = true; + break; + } + } + if (!contained) this.constraints.push(constraint); + } + + const attachments = skin.getAttachments(); + for (let i = 0; i < attachments.length; i++) { + const attachment = attachments[i]; + if (attachment.attachment == null) continue; + if (attachment.attachment instanceof MeshAttachment) { + attachment.attachment = attachment.attachment.newLinkedMesh(); + this.setAttachment(attachment.slotIndex, attachment.name, attachment.attachment); + } else { + attachment.attachment = attachment.attachment.copy(); + this.setAttachment(attachment.slotIndex, attachment.name, attachment.attachment); + } + } + } + + /** Returns the attachment for the specified slot index and name, or null. */ + getAttachment(slotIndex: number, name: string): Attachment { + const dictionary = this.attachments[slotIndex]; + return dictionary ? dictionary[name] : null; + } + + /** Removes the attachment in the skin for the specified slot index and name, if any. */ + removeAttachment(slotIndex: number, name: string) { + const dictionary = this.attachments[slotIndex]; + if (dictionary) dictionary[name] = null; + } + + /** Returns all attachments in this skin. */ + getAttachments(): Array { + const entries = new Array(); + for (let i = 0; i < this.attachments.length; i++) { + const slotAttachments = this.attachments[i]; + if (slotAttachments) { + for (const name in slotAttachments) { + const attachment = slotAttachments[name]; + if (attachment) entries.push(new SkinEntry(i, name, attachment)); + } + } + } + return entries; + } + + /** Returns all attachments in this skin for the specified slot index. */ + getAttachmentsForSlot(slotIndex: number, attachments: Array) { + const slotAttachments = this.attachments[slotIndex]; + if (slotAttachments) { + for (const name in slotAttachments) { + const attachment = slotAttachments[name]; + if (attachment) attachments.push(new SkinEntry(slotIndex, name, attachment)); + } + } + } + + /** Clears all attachments, bones, and constraints. */ + clear() { + this.attachments.length = 0; + this.bones.length = 0; + this.constraints.length = 0; + } + + /** Attach each attachment in this skin if the corresponding attachment in the old skin is currently attached. */ + attachAll(skeleton: Skeleton, oldSkin: Skin) { + let slotIndex = 0; + for (let i = 0; i < skeleton.slots.length; i++) { + const slot = skeleton.slots[i]; + const slotAttachment = slot.getAttachment(); + if (slotAttachment && slotIndex < oldSkin.attachments.length) { + const dictionary = oldSkin.attachments[slotIndex]; + for (const key in dictionary) { + const skinAttachment: Attachment = dictionary[key]; + if (slotAttachment == skinAttachment) { + const attachment = this.getAttachment(slotIndex, key); + if (attachment != null) slot.setAttachment(attachment); + break; + } + } + } + slotIndex++; + } + } +} diff --git a/packages/spine-core-3.8/src/spine-core/Slot.ts b/packages/spine-core-3.8/src/spine-core/Slot.ts new file mode 100644 index 0000000000..5e4903193e --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/Slot.ts @@ -0,0 +1,86 @@ +import { SlotData } from "./SlotData"; +import { Bone } from "./Bone"; +import { Color } from "./Utils"; +import { Attachment } from "./attachments/Attachment"; +import { Skeleton } from "./Skeleton"; + +/** Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store + * state for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared + * across multiple skeletons. */ +export class Slot { + /** The slot's setup pose data. */ + data: SlotData; + + /** The bone this slot belongs to. */ + bone: Bone; + + /** The color used to tint the slot's attachment. If {@link #getDarkColor()} is set, this is used as the light color for two + * color tinting. */ + color: Color; + + /** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark + * color's alpha is not used. */ + darkColor: Color; + + attachment: Attachment; + + private attachmentTime: number; + + attachmentState: number; + + /** Values to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a + * weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. + * + * See {@link VertexAttachment#computeWorldVertices()} and {@link DeformTimeline}. */ + deform = new Array(); + + constructor(data: SlotData, bone: Bone) { + if (data == null) throw new Error("data cannot be null."); + if (bone == null) throw new Error("bone cannot be null."); + this.data = data; + this.bone = bone; + this.color = new Color(); + this.darkColor = data.darkColor == null ? null : new Color(); + this.setToSetupPose(); + } + + /** The skeleton this slot belongs to. */ + getSkeleton(): Skeleton { + return this.bone.skeleton; + } + + /** The current attachment for the slot, or null if the slot has no attachment. */ + getAttachment(): Attachment { + return this.attachment; + } + + /** Sets the slot's attachment and, if the attachment changed, resets {@link #attachmentTime} and clears {@link #deform}. + * @param attachment May be null. */ + setAttachment(attachment: Attachment) { + if (this.attachment == attachment) return; + this.attachment = attachment; + this.attachmentTime = this.bone.skeleton.time; + this.deform.length = 0; + } + + setAttachmentTime(time: number) { + this.attachmentTime = this.bone.skeleton.time - time; + } + + /** The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton + * {@link Skeleton#time}. */ + getAttachmentTime(): number { + return this.bone.skeleton.time - this.attachmentTime; + } + + /** Sets this slot to the setup pose. */ + setToSetupPose() { + this.color.setFromColor(this.data.color); + if (this.darkColor != null) this.darkColor.setFromColor(this.data.darkColor); + if (this.data.attachmentName == null) this.attachment = null; + else { + this.attachment = null; + this.setAttachment(this.bone.skeleton.getAttachment(this.data.index, this.data.attachmentName)); + } + } +} diff --git a/packages/spine-core-3.8/src/spine-core/SlotData.ts b/packages/spine-core-3.8/src/spine-core/SlotData.ts new file mode 100644 index 0000000000..c5d3705b79 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/SlotData.ts @@ -0,0 +1,38 @@ +import { BoneData } from "./BoneData"; +import { Color } from "./Utils"; +import { BlendMode } from "./BlendMode"; + +/** Stores the setup pose for a {@link Slot}. */ +export class SlotData { + /** The index of the slot in {@link Skeleton#getSlots()}. */ + index: number; + + /** The name of the slot, which is unique across all slots in the skeleton. */ + name: string; + + /** The bone this slot belongs to. */ + boneData: BoneData; + + /** The color used to tint the slot's attachment. If {@link #getDarkColor()} is set, this is used as the light color for two + * color tinting. */ + color = new Color(1, 1, 1, 1); + + /** The dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark + * color's alpha is not used. */ + darkColor: Color; + + /** The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. */ + attachmentName: string; + + /** The blend mode for drawing the slot's attachment. */ + blendMode: BlendMode; + + constructor(index: number, name: string, boneData: BoneData) { + if (index < 0) throw new Error("index must be >= 0."); + if (name == null) throw new Error("name cannot be null."); + if (boneData == null) throw new Error("boneData cannot be null."); + this.index = index; + this.name = name; + this.boneData = boneData; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/Texture.ts b/packages/spine-core-3.8/src/spine-core/Texture.ts new file mode 100644 index 0000000000..f911a5625c --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/Texture.ts @@ -0,0 +1,96 @@ +import { Engine, Texture2D } from "@galacean/engine"; + +export abstract class Texture { + protected _texture: Texture2D; + + constructor(texture: Texture2D) { + this._texture = texture; + } + + get width(): number { + return this._texture.width; + } + + get height(): number { + return this._texture.height; + } + + get texture(): Texture2D { + return this._texture; + } + + abstract setFilters(minFilter: TextureFilter, magFilter: TextureFilter): void; + abstract setWraps(uWrap: TextureWrap, vWrap: TextureWrap): void; + abstract dispose(): void; + + public static filterFromString(text: string): TextureFilter { + switch (text.toLowerCase()) { + case "nearest": + return TextureFilter.Nearest; + case "linear": + return TextureFilter.Linear; + case "mipmap": + return TextureFilter.MipMap; + case "mipmapnearestnearest": + return TextureFilter.MipMapNearestNearest; + case "mipmaplinearnearest": + return TextureFilter.MipMapLinearNearest; + case "mipmapnearestlinear": + return TextureFilter.MipMapNearestLinear; + case "mipmaplinearlinear": + return TextureFilter.MipMapLinearLinear; + default: + throw new Error(`Unknown texture filter ${text}`); + } + } + + public static wrapFromString(text: string): TextureWrap { + switch (text.toLowerCase()) { + case "mirroredtepeat": + return TextureWrap.MirroredRepeat; + case "clamptoedge": + return TextureWrap.ClampToEdge; + case "repeat": + return TextureWrap.Repeat; + default: + throw new Error(`Unknown texture wrap ${text}`); + } + } +} + +export enum TextureFilter { + Nearest = 9728, // WebGLRenderingContext.NEAREST + Linear = 9729, // WebGLRenderingContext.LINEAR + MipMap = 9987, // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR + MipMapNearestNearest = 9984, // WebGLRenderingContext.NEAREST_MIPMAP_NEAREST + MipMapLinearNearest = 9985, // WebGLRenderingContext.LINEAR_MIPMAP_NEAREST + MipMapNearestLinear = 9986, // WebGLRenderingContext.NEAREST_MIPMAP_LINEAR + MipMapLinearLinear = 9987 // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR +} + +export enum TextureWrap { + MirroredRepeat = 33648, // WebGLRenderingContext.MIRRORED_REPEAT + ClampToEdge = 33071, // WebGLRenderingContext.CLAMP_TO_EDGE + Repeat = 10497 // WebGLRenderingContext.REPEAT +} + +export class TextureRegion { + renderObject: any; + u = 0; + v = 0; + u2 = 0; + v2 = 0; + width = 0; + height = 0; + rotate = false; + offsetX = 0; + offsetY = 0; + originalWidth = 0; + originalHeight = 0; +} + +export class FakeTexture extends Texture { + setFilters(minFilter: TextureFilter, magFilter: TextureFilter) {} + setWraps(uWrap: TextureWrap, vWrap: TextureWrap) {} + dispose() {} +} diff --git a/packages/spine-core-3.8/src/spine-core/TextureAtlas.ts b/packages/spine-core-3.8/src/spine-core/TextureAtlas.ts new file mode 100644 index 0000000000..299e9f94be --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/TextureAtlas.ts @@ -0,0 +1,187 @@ +import { Disposable } from "./Utils"; +import { Texture, TextureFilter } from "./Texture"; +import { TextureWrap, TextureRegion } from "./Texture"; + +export class TextureAtlas implements Disposable { + pages = new Array(); + regions = new Array(); + + constructor(atlasText: string, textureLoader: (path: string, width?: number, height?: number) => any) { + this.load(atlasText, textureLoader); + } + + private load(atlasText: string, textureLoader: (path: string, width?: number, height?: number) => any) { + if (textureLoader == null) throw new Error("textureLoader cannot be null."); + + const reader = new TextureAtlasReader(atlasText); + const tuple = new Array(4); + let page: TextureAtlasPage = null; + while (true) { + let line = reader.readLine(); + if (line == null) break; + line = line.trim(); + if (line.length == 0) page = null; + else if (!page) { + page = new TextureAtlasPage(); + page.name = line; + + if (reader.readTuple(tuple) == 2) { + // size is only optional for an atlas packed with an old TexturePacker. + page.width = parseInt(tuple[0]); + page.height = parseInt(tuple[1]); + reader.readTuple(tuple); + } + // page.format = Format[tuple[0]]; we don't need format in WebGL + + reader.readTuple(tuple); + page.minFilter = Texture.filterFromString(tuple[0]); + page.magFilter = Texture.filterFromString(tuple[1]); + + const direction = reader.readValue(); + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + if (direction == "x") page.uWrap = TextureWrap.Repeat; + else if (direction == "y") page.vWrap = TextureWrap.Repeat; + else if (direction == "xy") page.uWrap = page.vWrap = TextureWrap.Repeat; + + page.texture = textureLoader(line); + page.texture.setFilters(page.minFilter, page.magFilter); + page.texture.setWraps(page.uWrap, page.vWrap); + page.width = page.texture.width; + page.height = page.texture.height; + this.pages.push(page); + } else { + const region: TextureAtlasRegion = new TextureAtlasRegion(); + region.name = line; + region.page = page; + + const rotateValue = reader.readValue(); + if (rotateValue.toLocaleLowerCase() == "true") { + region.degrees = 90; + } else if (rotateValue.toLocaleLowerCase() == "false") { + region.degrees = 0; + } else { + region.degrees = parseFloat(rotateValue); + } + region.rotate = region.degrees == 90; + + reader.readTuple(tuple); + const x = parseInt(tuple[0]); + const y = parseInt(tuple[1]); + + reader.readTuple(tuple); + const width = parseInt(tuple[0]); + const height = parseInt(tuple[1]); + + region.u = x / page.width; + region.v = y / page.height; + if (region.rotate) { + region.u2 = (x + height) / page.width; + region.v2 = (y + width) / page.height; + } else { + region.u2 = (x + width) / page.width; + region.v2 = (y + height) / page.height; + } + region.x = x; + region.y = y; + region.width = Math.abs(width); + region.height = Math.abs(height); + + if (reader.readTuple(tuple) == 4) { + // split is optional + // region.splits = new Vector.(parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])); + if (reader.readTuple(tuple) == 4) { + // pad is optional, but only present with splits + //region.pads = Vector.(parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])); + reader.readTuple(tuple); + } + } + + region.originalWidth = parseInt(tuple[0]); + region.originalHeight = parseInt(tuple[1]); + + reader.readTuple(tuple); + region.offsetX = parseInt(tuple[0]); + region.offsetY = parseInt(tuple[1]); + + region.index = parseInt(reader.readValue()); + + region.texture = page.texture; + this.regions.push(region); + } + } + } + + findRegion(name: string): TextureAtlasRegion { + for (let i = 0; i < this.regions.length; i++) { + if (this.regions[i].name == name) { + return this.regions[i]; + } + } + return null; + } + + dispose() { + for (let i = 0; i < this.pages.length; i++) { + this.pages[i].texture.dispose(); + } + } +} + +class TextureAtlasReader { + lines: Array; + index: number = 0; + + constructor(text: string) { + this.lines = text.split(/\r\n|\r|\n/); + } + + readLine(): string { + if (this.index >= this.lines.length) return null; + return this.lines[this.index++]; + } + + readValue(): string { + const line = this.readLine(); + const colon = line.indexOf(":"); + if (colon == -1) throw new Error("Invalid line: " + line); + return line.substring(colon + 1).trim(); + } + + readTuple(tuple: Array): number { + const line = this.readLine(); + const colon = line.indexOf(":"); + if (colon == -1) throw new Error("Invalid line: " + line); + let i = 0, + lastMatch = colon + 1; + for (; i < 3; i++) { + const comma = line.indexOf(",", lastMatch); + if (comma == -1) break; + tuple[i] = line.substr(lastMatch, comma - lastMatch).trim(); + lastMatch = comma + 1; + } + tuple[i] = line.substring(lastMatch).trim(); + return i + 1; + } +} + +export class TextureAtlasPage { + name: string; + minFilter: TextureFilter; + magFilter: TextureFilter; + uWrap: TextureWrap; + vWrap: TextureWrap; + texture: Texture; + width: number; + height: number; +} + +export class TextureAtlasRegion extends TextureRegion { + page: TextureAtlasPage; + name: string; + x: number; + y: number; + index: number; + degrees: number; + texture: Texture; +} diff --git a/packages/spine-core-3.8/src/spine-core/TransformConstraint.ts b/packages/spine-core-3.8/src/spine-core/TransformConstraint.ts new file mode 100644 index 0000000000..ab841eb62e --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/TransformConstraint.ts @@ -0,0 +1,296 @@ +import { Updatable } from "./Updatable"; +import { TransformConstraintData } from "./TransformConstraintData"; +import { Bone } from "./Bone"; +import { Skeleton } from "./Skeleton"; +import { MathUtils, Vector2 } from "./Utils"; + +/** Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained + * bones to match that of the target bone. + * + * See [Transform constraints](http://esotericsoftware.com/spine-transform-constraints) in the Spine User Guide. */ +export class TransformConstraint implements Updatable { + /** The transform constraint's setup pose data. */ + data: TransformConstraintData; + + /** The bones that will be modified by this transform constraint. */ + bones: Array; + + /** The target bone whose world transform will be copied to the constrained bones. */ + target: Bone; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */ + rotateMix = 0; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained translations. */ + translateMix = 0; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained scales. */ + scaleMix = 0; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained scales. */ + shearMix = 0; + + temp = new Vector2(); + active = false; + + constructor(data: TransformConstraintData, skeleton: Skeleton) { + if (data == null) throw new Error("data cannot be null."); + if (skeleton == null) throw new Error("skeleton cannot be null."); + this.data = data; + this.rotateMix = data.rotateMix; + this.translateMix = data.translateMix; + this.scaleMix = data.scaleMix; + this.shearMix = data.shearMix; + this.bones = new Array(); + for (let i = 0; i < data.bones.length; i++) this.bones.push(skeleton.findBone(data.bones[i].name)); + this.target = skeleton.findBone(data.target.name); + } + + isActive() { + return this.active; + } + + /** Applies the constraint to the constrained bones. */ + apply() { + this.update(); + } + + update() { + if (this.data.local) { + if (this.data.relative) this.applyRelativeLocal(); + else this.applyAbsoluteLocal(); + } else { + if (this.data.relative) this.applyRelativeWorld(); + else this.applyAbsoluteWorld(); + } + } + + applyAbsoluteWorld() { + const rotateMix = this.rotateMix, + translateMix = this.translateMix, + scaleMix = this.scaleMix, + shearMix = this.shearMix; + const target = this.target; + const ta = target.a, + tb = target.b, + tc = target.c, + td = target.d; + const degRadReflect = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad; + const offsetRotation = this.data.offsetRotation * degRadReflect; + const offsetShearY = this.data.offsetShearY * degRadReflect; + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) { + const bone = bones[i]; + let modified = false; + + if (rotateMix != 0) { + const a = bone.a, + b = bone.b, + c = bone.c, + d = bone.d; + let r = Math.atan2(tc, ta) - Math.atan2(c, a) + offsetRotation; + if (r > MathUtils.PI) r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + const cos = Math.cos(r), + sin = Math.sin(r); + bone.a = cos * a - sin * c; + bone.b = cos * b - sin * d; + bone.c = sin * a + cos * c; + bone.d = sin * b + cos * d; + modified = true; + } + + if (translateMix != 0) { + const temp = this.temp; + target.localToWorld(temp.set(this.data.offsetX, this.data.offsetY)); + bone.worldX += (temp.x - bone.worldX) * translateMix; + bone.worldY += (temp.y - bone.worldY) * translateMix; + modified = true; + } + + if (scaleMix > 0) { + let s = Math.sqrt(bone.a * bone.a + bone.c * bone.c); + let ts = Math.sqrt(ta * ta + tc * tc); + if (s > 0.00001) s = (s + (ts - s + this.data.offsetScaleX) * scaleMix) / s; + bone.a *= s; + bone.c *= s; + s = Math.sqrt(bone.b * bone.b + bone.d * bone.d); + ts = Math.sqrt(tb * tb + td * td); + if (s > 0.00001) s = (s + (ts - s + this.data.offsetScaleY) * scaleMix) / s; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) { + const b = bone.b, + d = bone.d; + const by = Math.atan2(d, b); + let r = Math.atan2(td, tb) - Math.atan2(tc, ta) - (by - Math.atan2(bone.c, bone.a)); + if (r > MathUtils.PI) r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r = by + (r + offsetShearY) * shearMix; + const s = Math.sqrt(b * b + d * d); + bone.b = Math.cos(r) * s; + bone.d = Math.sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + applyRelativeWorld() { + const rotateMix = this.rotateMix, + translateMix = this.translateMix, + scaleMix = this.scaleMix, + shearMix = this.shearMix; + const target = this.target; + const ta = target.a, + tb = target.b, + tc = target.c, + td = target.d; + const degRadReflect = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad; + const offsetRotation = this.data.offsetRotation * degRadReflect, + offsetShearY = this.data.offsetShearY * degRadReflect; + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) { + const bone = bones[i]; + let modified = false; + + if (rotateMix != 0) { + const a = bone.a, + b = bone.b, + c = bone.c, + d = bone.d; + let r = Math.atan2(tc, ta) + offsetRotation; + if (r > MathUtils.PI) r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + const cos = Math.cos(r), + sin = Math.sin(r); + bone.a = cos * a - sin * c; + bone.b = cos * b - sin * d; + bone.c = sin * a + cos * c; + bone.d = sin * b + cos * d; + modified = true; + } + + if (translateMix != 0) { + const temp = this.temp; + target.localToWorld(temp.set(this.data.offsetX, this.data.offsetY)); + bone.worldX += temp.x * translateMix; + bone.worldY += temp.y * translateMix; + modified = true; + } + + if (scaleMix > 0) { + let s = (Math.sqrt(ta * ta + tc * tc) - 1 + this.data.offsetScaleX) * scaleMix + 1; + bone.a *= s; + bone.c *= s; + s = (Math.sqrt(tb * tb + td * td) - 1 + this.data.offsetScaleY) * scaleMix + 1; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) { + let r = Math.atan2(td, tb) - Math.atan2(tc, ta); + if (r > MathUtils.PI) r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + const b = bone.b, + d = bone.d; + r = Math.atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; + const s = Math.sqrt(b * b + d * d); + bone.b = Math.cos(r) * s; + bone.d = Math.sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + applyAbsoluteLocal() { + const rotateMix = this.rotateMix, + translateMix = this.translateMix, + scaleMix = this.scaleMix, + shearMix = this.shearMix; + const target = this.target; + if (!target.appliedValid) target.updateAppliedTransform(); + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) { + const bone = bones[i]; + if (!bone.appliedValid) bone.updateAppliedTransform(); + + let rotation = bone.arotation; + if (rotateMix != 0) { + let r = target.arotation - rotation + this.data.offsetRotation; + r -= (16384 - ((16384.499999999996 - r / 360) | 0)) * 360; + rotation += r * rotateMix; + } + + let x = bone.ax, + y = bone.ay; + if (translateMix != 0) { + x += (target.ax - x + this.data.offsetX) * translateMix; + y += (target.ay - y + this.data.offsetY) * translateMix; + } + + let scaleX = bone.ascaleX, + scaleY = bone.ascaleY; + if (scaleMix != 0) { + if (scaleX > 0.00001) + scaleX = (scaleX + (target.ascaleX - scaleX + this.data.offsetScaleX) * scaleMix) / scaleX; + if (scaleY > 0.00001) + scaleY = (scaleY + (target.ascaleY - scaleY + this.data.offsetScaleY) * scaleMix) / scaleY; + } + + const shearY = bone.ashearY; + if (shearMix != 0) { + let r = target.ashearY - shearY + this.data.offsetShearY; + r -= (16384 - ((16384.499999999996 - r / 360) | 0)) * 360; + bone.shearY += r * shearMix; + } + + bone.updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + applyRelativeLocal() { + const rotateMix = this.rotateMix, + translateMix = this.translateMix, + scaleMix = this.scaleMix, + shearMix = this.shearMix; + const target = this.target; + if (!target.appliedValid) target.updateAppliedTransform(); + const bones = this.bones; + for (let i = 0, n = bones.length; i < n; i++) { + const bone = bones[i]; + if (!bone.appliedValid) bone.updateAppliedTransform(); + + let rotation = bone.arotation; + if (rotateMix != 0) rotation += (target.arotation + this.data.offsetRotation) * rotateMix; + + let x = bone.ax, + y = bone.ay; + if (translateMix != 0) { + x += (target.ax + this.data.offsetX) * translateMix; + y += (target.ay + this.data.offsetY) * translateMix; + } + + let scaleX = bone.ascaleX, + scaleY = bone.ascaleY; + if (scaleMix != 0) { + if (scaleX > 0.00001) scaleX *= (target.ascaleX - 1 + this.data.offsetScaleX) * scaleMix + 1; + if (scaleY > 0.00001) scaleY *= (target.ascaleY - 1 + this.data.offsetScaleY) * scaleMix + 1; + } + + let shearY = bone.ashearY; + if (shearMix != 0) shearY += (target.ashearY + this.data.offsetShearY) * shearMix; + + bone.updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } +} diff --git a/packages/spine-core-3.8/src/spine-core/TransformConstraintData.ts b/packages/spine-core-3.8/src/spine-core/TransformConstraintData.ts new file mode 100644 index 0000000000..c135697c08 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/TransformConstraintData.ts @@ -0,0 +1,50 @@ +import { ConstraintData } from "./ConstraintData"; +import { BoneData } from "./BoneData"; + +/** Stores the setup pose for a {@link TransformConstraint}. + * + * See [Transform constraints](http://esotericsoftware.com/spine-transform-constraints) in the Spine User Guide. */ +export class TransformConstraintData extends ConstraintData { + /** The bones that will be modified by this transform constraint. */ + bones = new Array(); + + /** The target bone whose world transform will be copied to the constrained bones. */ + target: BoneData; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. */ + rotateMix = 0; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained translations. */ + translateMix = 0; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained scales. */ + scaleMix = 0; + + /** A percentage (0-1) that controls the mix between the constrained and unconstrained shears. */ + shearMix = 0; + + /** An offset added to the constrained bone rotation. */ + offsetRotation = 0; + + /** An offset added to the constrained bone X translation. */ + offsetX = 0; + + /** An offset added to the constrained bone Y translation. */ + offsetY = 0; + + /** An offset added to the constrained bone scaleX. */ + offsetScaleX = 0; + + /** An offset added to the constrained bone scaleY. */ + offsetScaleY = 0; + + /** An offset added to the constrained bone shearY. */ + offsetShearY = 0; + + relative = false; + local = false; + + constructor(name: string) { + super(name, 0, false); + } +} diff --git a/packages/spine-core-3.8/src/spine-core/Triangulator.ts b/packages/spine-core-3.8/src/spine-core/Triangulator.ts new file mode 100644 index 0000000000..bd6efff41d --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/Triangulator.ts @@ -0,0 +1,269 @@ +import { Pool } from "./Utils"; + +export class Triangulator { + private convexPolygons = new Array>(); + private convexPolygonsIndices = new Array>(); + + private indicesArray = new Array(); + private isConcaveArray = new Array(); + private triangles = new Array(); + + private polygonPool = new Pool>(() => { + return new Array(); + }); + + private polygonIndicesPool = new Pool>(() => { + return new Array(); + }); + + public triangulate(verticesArray: ArrayLike): Array { + const vertices = verticesArray; + let vertexCount = verticesArray.length >> 1; + + const indices = this.indicesArray; + indices.length = 0; + for (let i = 0; i < vertexCount; i++) indices[i] = i; + + const isConcave = this.isConcaveArray; + isConcave.length = 0; + for (let i = 0, n = vertexCount; i < n; ++i) + isConcave[i] = Triangulator.isConcave(i, vertexCount, vertices, indices); + + const triangles = this.triangles; + triangles.length = 0; + + while (vertexCount > 3) { + // Find ear tip. + let previous = vertexCount - 1, + i = 0, + next = 1; + while (true) { + outer: if (!isConcave[i]) { + const p1 = indices[previous] << 1, + p2 = indices[i] << 1, + p3 = indices[next] << 1; + const p1x = vertices[p1], + p1y = vertices[p1 + 1]; + const p2x = vertices[p2], + p2y = vertices[p2 + 1]; + const p3x = vertices[p3], + p3y = vertices[p3 + 1]; + for (let ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { + if (!isConcave[ii]) continue; + const v = indices[ii] << 1; + const vx = vertices[v], + vy = vertices[v + 1]; + if (Triangulator.positiveArea(p3x, p3y, p1x, p1y, vx, vy)) { + if (Triangulator.positiveArea(p1x, p1y, p2x, p2y, vx, vy)) { + if (Triangulator.positiveArea(p2x, p2y, p3x, p3y, vx, vy)) break outer; + } + } + } + break; + } + + if (next == 0) { + do { + if (!isConcave[i]) break; + i--; + } while (i > 0); + break; + } + + previous = i; + i = next; + next = (next + 1) % vertexCount; + } + + // Cut ear tip. + triangles.push(indices[(vertexCount + i - 1) % vertexCount]); + triangles.push(indices[i]); + triangles.push(indices[(i + 1) % vertexCount]); + indices.splice(i, 1); + isConcave.splice(i, 1); + vertexCount--; + + const previousIndex = (vertexCount + i - 1) % vertexCount; + const nextIndex = i == vertexCount ? 0 : i; + isConcave[previousIndex] = Triangulator.isConcave(previousIndex, vertexCount, vertices, indices); + isConcave[nextIndex] = Triangulator.isConcave(nextIndex, vertexCount, vertices, indices); + } + + if (vertexCount == 3) { + triangles.push(indices[2]); + triangles.push(indices[0]); + triangles.push(indices[1]); + } + + return triangles; + } + + decompose(verticesArray: Array, triangles: Array): Array> { + const vertices = verticesArray; + const convexPolygons = this.convexPolygons; + this.polygonPool.freeAll(convexPolygons); + convexPolygons.length = 0; + + const convexPolygonsIndices = this.convexPolygonsIndices; + this.polygonIndicesPool.freeAll(convexPolygonsIndices); + convexPolygonsIndices.length = 0; + + let polygonIndices = this.polygonIndicesPool.obtain(); + polygonIndices.length = 0; + + let polygon = this.polygonPool.obtain(); + polygon.length = 0; + + // Merge subsequent triangles if they form a triangle fan. + let fanBaseIndex = -1, + lastWinding = 0; + for (let i = 0, n = triangles.length; i < n; i += 3) { + const t1 = triangles[i] << 1, + t2 = triangles[i + 1] << 1, + t3 = triangles[i + 2] << 1; + const x1 = vertices[t1], + y1 = vertices[t1 + 1]; + const x2 = vertices[t2], + y2 = vertices[t2 + 1]; + const x3 = vertices[t3], + y3 = vertices[t3 + 1]; + + // If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan). + let merged = false; + if (fanBaseIndex == t1) { + const o = polygon.length - 4; + const winding1 = Triangulator.winding(polygon[o], polygon[o + 1], polygon[o + 2], polygon[o + 3], x3, y3); + const winding2 = Triangulator.winding(x3, y3, polygon[0], polygon[1], polygon[2], polygon[3]); + if (winding1 == lastWinding && winding2 == lastWinding) { + polygon.push(x3); + polygon.push(y3); + polygonIndices.push(t3); + merged = true; + } + } + + // Otherwise make this triangle the new base. + if (!merged) { + if (polygon.length > 0) { + convexPolygons.push(polygon); + convexPolygonsIndices.push(polygonIndices); + } else { + this.polygonPool.free(polygon); + this.polygonIndicesPool.free(polygonIndices); + } + polygon = this.polygonPool.obtain(); + polygon.length = 0; + polygon.push(x1); + polygon.push(y1); + polygon.push(x2); + polygon.push(y2); + polygon.push(x3); + polygon.push(y3); + polygonIndices = this.polygonIndicesPool.obtain(); + polygonIndices.length = 0; + polygonIndices.push(t1); + polygonIndices.push(t2); + polygonIndices.push(t3); + lastWinding = Triangulator.winding(x1, y1, x2, y2, x3, y3); + fanBaseIndex = t1; + } + } + + if (polygon.length > 0) { + convexPolygons.push(polygon); + convexPolygonsIndices.push(polygonIndices); + } + + // Go through the list of polygons and try to merge the remaining triangles with the found triangle fans. + for (let i = 0, n = convexPolygons.length; i < n; i++) { + polygonIndices = convexPolygonsIndices[i]; + if (polygonIndices.length == 0) continue; + const firstIndex = polygonIndices[0]; + const lastIndex = polygonIndices[polygonIndices.length - 1]; + + polygon = convexPolygons[i]; + const o = polygon.length - 4; + let prevPrevX = polygon[o], + prevPrevY = polygon[o + 1]; + let prevX = polygon[o + 2], + prevY = polygon[o + 3]; + const firstX = polygon[0], + firstY = polygon[1]; + const secondX = polygon[2], + secondY = polygon[3]; + const winding = Triangulator.winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (let ii = 0; ii < n; ii++) { + if (ii == i) continue; + const otherIndices = convexPolygonsIndices[ii]; + if (otherIndices.length != 3) continue; + const otherFirstIndex = otherIndices[0]; + const otherSecondIndex = otherIndices[1]; + const otherLastIndex = otherIndices[2]; + + const otherPoly = convexPolygons[ii]; + const x3 = otherPoly[otherPoly.length - 2], + y3 = otherPoly[otherPoly.length - 1]; + + if (otherFirstIndex != firstIndex || otherSecondIndex != lastIndex) continue; + const winding1 = Triangulator.winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3); + const winding2 = Triangulator.winding(x3, y3, firstX, firstY, secondX, secondY); + if (winding1 == winding && winding2 == winding) { + otherPoly.length = 0; + otherIndices.length = 0; + polygon.push(x3); + polygon.push(y3); + polygonIndices.push(otherLastIndex); + prevPrevX = prevX; + prevPrevY = prevY; + prevX = x3; + prevY = y3; + ii = 0; + } + } + } + + // Remove empty polygons that resulted from the merge step above. + for (let i = convexPolygons.length - 1; i >= 0; i--) { + polygon = convexPolygons[i]; + if (polygon.length == 0) { + convexPolygons.splice(i, 1); + this.polygonPool.free(polygon); + polygonIndices = convexPolygonsIndices[i]; + convexPolygonsIndices.splice(i, 1); + this.polygonIndicesPool.free(polygonIndices); + } + } + + return convexPolygons; + } + + private static isConcave( + index: number, + vertexCount: number, + vertices: ArrayLike, + indices: ArrayLike + ): boolean { + const previous = indices[(vertexCount + index - 1) % vertexCount] << 1; + const current = indices[index] << 1; + const next = indices[(index + 1) % vertexCount] << 1; + return !this.positiveArea( + vertices[previous], + vertices[previous + 1], + vertices[current], + vertices[current + 1], + vertices[next], + vertices[next + 1] + ); + } + + private static positiveArea(p1x: number, p1y: number, p2x: number, p2y: number, p3x: number, p3y: number): boolean { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; + } + + private static winding(p1x: number, p1y: number, p2x: number, p2y: number, p3x: number, p3y: number): number { + const px = p2x - p1x, + py = p2y - p1y; + return p3x * py - p3y * px + px * p1y - p1x * py >= 0 ? 1 : -1; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/Updatable.ts b/packages/spine-core-3.8/src/spine-core/Updatable.ts new file mode 100644 index 0000000000..64e1965de1 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/Updatable.ts @@ -0,0 +1,10 @@ +/** The interface for items updated by {@link Skeleton#updateWorldTransform()}. */ +export interface Updatable { + update(): void; + + /** Returns false when this item has not been updated because a skin is required and the {@link Skeleton#skin active skin} + * does not contain this item. + * @see Skin#getBones() + * @see Skin#getConstraints() */ + isActive(): boolean; +} diff --git a/packages/spine-core-3.8/src/spine-core/Utils.ts b/packages/spine-core-3.8/src/spine-core/Utils.ts new file mode 100644 index 0000000000..510e4cbba4 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/Utils.ts @@ -0,0 +1,417 @@ +import { MixBlend } from "./Animation"; +import { Skeleton } from "./Skeleton"; + +export interface Map { + [key: string]: T; +} + +export class IntSet { + array = new Array(); + + add(value: number): boolean { + const contains = this.contains(value); + this.array[value | 0] = value | 0; + return !contains; + } + + contains(value: number) { + return this.array[value | 0] != undefined; + } + + remove(value: number) { + this.array[value | 0] = undefined; + } + + clear() { + this.array.length = 0; + } +} + +export interface Disposable { + dispose(): void; +} + +export interface Restorable { + restore(): void; +} + +export class Color { + public static WHITE = new Color(1, 1, 1, 1); + public static RED = new Color(1, 0, 0, 1); + public static GREEN = new Color(0, 1, 0, 1); + public static BLUE = new Color(0, 0, 1, 1); + public static MAGENTA = new Color(1, 0, 1, 1); + + constructor( + public r: number = 0, + public g: number = 0, + public b: number = 0, + public a: number = 0 + ) {} + + set(r: number, g: number, b: number, a: number) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + this.clamp(); + return this; + } + + setFromColor(c: Color) { + this.r = c.r; + this.g = c.g; + this.b = c.b; + this.a = c.a; + return this; + } + + setFromString(hex: string) { + hex = hex.charAt(0) == "#" ? hex.substr(1) : hex; + this.r = parseInt(hex.substr(0, 2), 16) / 255.0; + this.g = parseInt(hex.substr(2, 2), 16) / 255.0; + this.b = parseInt(hex.substr(4, 2), 16) / 255.0; + this.a = (hex.length != 8 ? 255 : parseInt(hex.substr(6, 2), 16)) / 255.0; + return this; + } + + add(r: number, g: number, b: number, a: number) { + this.r += r; + this.g += g; + this.b += b; + this.a += a; + this.clamp(); + return this; + } + + clamp() { + if (this.r < 0) this.r = 0; + else if (this.r > 1) this.r = 1; + + if (this.g < 0) this.g = 0; + else if (this.g > 1) this.g = 1; + + if (this.b < 0) this.b = 0; + else if (this.b > 1) this.b = 1; + + if (this.a < 0) this.a = 0; + else if (this.a > 1) this.a = 1; + return this; + } + + static rgba8888ToColor(color: Color, value: number) { + color.r = ((value & 0xff000000) >>> 24) / 255; + color.g = ((value & 0x00ff0000) >>> 16) / 255; + color.b = ((value & 0x0000ff00) >>> 8) / 255; + color.a = (value & 0x000000ff) / 255; + } + + static rgb888ToColor(color: Color, value: number) { + color.r = ((value & 0x00ff0000) >>> 16) / 255; + color.g = ((value & 0x0000ff00) >>> 8) / 255; + color.b = (value & 0x000000ff) / 255; + } +} + +export class MathUtils { + static PI = 3.1415927; + static PI2 = MathUtils.PI * 2; + static radiansToDegrees = 180 / MathUtils.PI; + static radDeg = MathUtils.radiansToDegrees; + static degreesToRadians = MathUtils.PI / 180; + static degRad = MathUtils.degreesToRadians; + + static clamp(value: number, min: number, max: number) { + if (value < min) return min; + if (value > max) return max; + return value; + } + + static cosDeg(degrees: number) { + return Math.cos(degrees * MathUtils.degRad); + } + + static sinDeg(degrees: number) { + return Math.sin(degrees * MathUtils.degRad); + } + + static signum(value: number): number { + return value > 0 ? 1 : value < 0 ? -1 : 0; + } + + static toInt(x: number) { + return x > 0 ? Math.floor(x) : Math.ceil(x); + } + + static cbrt(x: number) { + const y = Math.pow(Math.abs(x), 1 / 3); + return x < 0 ? -y : y; + } + + static randomTriangular(min: number, max: number): number { + return MathUtils.randomTriangularWith(min, max, (min + max) * 0.5); + } + + static randomTriangularWith(min: number, max: number, mode: number): number { + const u = Math.random(); + const d = max - min; + if (u <= (mode - min) / d) return min + Math.sqrt(u * d * (mode - min)); + return max - Math.sqrt((1 - u) * d * (max - mode)); + } +} + +export abstract class Interpolation { + protected abstract applyInternal(a: number): number; + apply(start: number, end: number, a: number): number { + return start + (end - start) * this.applyInternal(a); + } +} + +export class Pow extends Interpolation { + protected power = 2; + + constructor(power: number) { + super(); + this.power = power; + } + + applyInternal(a: number): number { + if (a <= 0.5) return Math.pow(a * 2, this.power) / 2; + return Math.pow((a - 1) * 2, this.power) / (this.power % 2 == 0 ? -2 : 2) + 1; + } +} + +export class PowOut extends Pow { + constructor(power: number) { + super(power); + } + + override applyInternal(a: number): number { + return Math.pow(a - 1, this.power) * (this.power % 2 == 0 ? -1 : 1) + 1; + } +} + +export class Utils { + static SUPPORTS_TYPED_ARRAYS = typeof Float32Array !== "undefined"; + + static arrayCopy( + source: ArrayLike, + sourceStart: number, + dest: ArrayLike, + destStart: number, + numElements: number + ) { + for (let i = sourceStart, j = destStart; i < sourceStart + numElements; i++, j++) { + dest[j] = source[i]; + } + } + + static setArraySize(array: Array, size: number, value: any = 0): Array { + const oldSize = array.length; + if (oldSize == size) return array; + array.length = size; + if (oldSize < size) { + for (let i = oldSize; i < size; i++) array[i] = value; + } + return array; + } + + static ensureArrayCapacity(array: Array, size: number, value: any = 0): Array { + if (array.length >= size) return array; + return Utils.setArraySize(array, size, value); + } + + static newArray(size: number, defaultValue: T): Array { + const array = new Array(size); + for (let i = 0; i < size; i++) array[i] = defaultValue; + return array; + } + + static newFloatArray(size: number): ArrayLike { + if (Utils.SUPPORTS_TYPED_ARRAYS) { + return new Float32Array(size); + } else { + const array = new Array(size); + for (let i = 0; i < array.length; i++) array[i] = 0; + return array; + } + } + + static newShortArray(size: number): ArrayLike { + if (Utils.SUPPORTS_TYPED_ARRAYS) { + return new Int16Array(size); + } else { + const array = new Array(size); + for (let i = 0; i < array.length; i++) array[i] = 0; + return array; + } + } + + static toFloatArray(array: Array) { + return Utils.SUPPORTS_TYPED_ARRAYS ? new Float32Array(array) : array; + } + + static toSinglePrecision(value: number) { + return Utils.SUPPORTS_TYPED_ARRAYS ? Math.fround(value) : value; + } + + // This function is used to fix WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109 + static webkit602BugfixHelper(alpha: number, blend: MixBlend) {} + + static contains(array: Array, element: T, identity = true) { + for (let i = 0; i < array.length; i++) { + if (array[i] == element) return true; + } + return false; + } +} + +export class DebugUtils { + static logBones(skeleton: Skeleton) { + for (let i = 0; i < skeleton.bones.length; i++) { + const bone = skeleton.bones[i]; + console.log( + bone.data.name + + ", " + + bone.a + + ", " + + bone.b + + ", " + + bone.c + + ", " + + bone.d + + ", " + + bone.worldX + + ", " + + bone.worldY + ); + } + } +} + +export class Pool { + private items = new Array(); + private instantiator: () => T; + + constructor(instantiator: () => T) { + this.instantiator = instantiator; + } + + obtain() { + return this.items.length > 0 ? this.items.pop() : this.instantiator(); + } + + free(item: T) { + if ((item as any).reset) (item as any).reset(); + this.items.push(item); + } + + freeAll(items: ArrayLike) { + for (let i = 0; i < items.length; i++) { + this.free(items[i]); + } + } + + clear() { + this.items.length = 0; + } +} + +export class Vector2 { + constructor( + public x = 0, + public y = 0 + ) {} + + set(x: number, y: number): Vector2 { + this.x = x; + this.y = y; + return this; + } + + length() { + const x = this.x; + const y = this.y; + return Math.sqrt(x * x + y * y); + } + + normalize() { + const len = this.length(); + if (len != 0) { + this.x /= len; + this.y /= len; + } + return this; + } +} + +export class TimeKeeper { + maxDelta = 0.064; + framesPerSecond = 0; + delta = 0; + totalTime = 0; + + private lastTime = Date.now() / 1000; + private frameCount = 0; + private frameTime = 0; + + update() { + const now = Date.now() / 1000; + this.delta = now - this.lastTime; + this.frameTime += this.delta; + this.totalTime += this.delta; + if (this.delta > this.maxDelta) this.delta = this.maxDelta; + this.lastTime = now; + + this.frameCount++; + if (this.frameTime > 1) { + this.framesPerSecond = this.frameCount / this.frameTime; + this.frameTime = 0; + this.frameCount = 0; + } + } +} + +export interface ArrayLike { + length: number; + [n: number]: T; +} + +export class WindowedMean { + values: Array; + addedValues = 0; + lastValue = 0; + mean = 0; + dirty = true; + + constructor(windowSize: number = 32) { + this.values = new Array(windowSize); + } + + hasEnoughData() { + return this.addedValues >= this.values.length; + } + + addValue(value: number) { + if (this.addedValues < this.values.length) this.addedValues++; + this.values[this.lastValue++] = value; + if (this.lastValue > this.values.length - 1) this.lastValue = 0; + this.dirty = true; + } + + getMean() { + if (this.hasEnoughData()) { + if (this.dirty) { + let mean = 0; + for (let i = 0; i < this.values.length; i++) { + mean += this.values[i]; + } + this.mean = mean / this.values.length; + this.dirty = false; + } + return this.mean; + } else { + return 0; + } + } +} diff --git a/packages/spine-core-3.8/src/spine-core/VertexEffect.ts b/packages/spine-core-3.8/src/spine-core/VertexEffect.ts new file mode 100644 index 0000000000..ae23cd16d2 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/VertexEffect.ts @@ -0,0 +1,8 @@ +import { Skeleton } from "./Skeleton"; +import { Color, Vector2 } from "./Utils"; + +export interface VertexEffect { + begin(skeleton: Skeleton): void; + transform(position: Vector2, uv: Vector2, light: Color, dark: Color): void; + end(): void; +} diff --git a/packages/spine-core-3.8/src/spine-core/attachments/Attachment.ts b/packages/spine-core-3.8/src/spine-core/attachments/Attachment.ts new file mode 100644 index 0000000000..e13cf7b936 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/attachments/Attachment.ts @@ -0,0 +1,147 @@ +import { Slot } from "../Slot"; +import { Utils, ArrayLike } from "../Utils"; + +/** The base class for all attachments. */ +export abstract class Attachment { + name: string; + + constructor(name: string) { + if (name == null) throw new Error("name cannot be null."); + this.name = name; + } + + abstract copy(): Attachment; +} + +/** Base class for an attachment with vertices that are transformed by one or more bones and can be deformed by a slot's + * {@link Slot#deform}. */ +export abstract class VertexAttachment extends Attachment { + private static nextID = 0; + + /** The unique ID for this attachment. */ + id = (VertexAttachment.nextID++ & 65535) << 11; + + /** The bones which affect the {@link #getVertices()}. The array entries are, for each vertex, the number of bones affecting + * the vertex followed by that many bone indices, which is the index of the bone in {@link Skeleton#bones}. Will be null + * if this attachment has no weights. */ + bones: Array; + + /** The vertex positions in the bone's coordinate system. For a non-weighted attachment, the values are `x,y` + * entries for each vertex. For a weighted attachment, the values are `x,y,weight` entries for each bone affecting + * each vertex. */ + vertices: ArrayLike; + + /** The maximum number of world vertex values that can be output by + * {@link #computeWorldVertices()} using the `count` parameter. */ + worldVerticesLength = 0; + + /** Deform keys for the deform attachment are also applied to this attachment. May be null if no deform keys should be applied. */ + deformAttachment: VertexAttachment = this; + + constructor(name: string) { + super(name); + } + + /** Transforms the attachment's local {@link vertices} to world coordinates. If the slot's {@link Slot#deform} is + * not empty, it is used to deform the vertices. + * + * See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine + * Runtimes Guide. + * @param start The index of the first {@link #vertices} value to transform. Each vertex has 2 values, x and y. + * @param count The number of world vertex values to output. Must be <= {@link #worldVerticesLength} - `start`. + * @param worldVertices The output world vertices. Must have a length >= `offset` + `count` * + * `stride` / 2. + * @param offset The `worldVertices` index to begin writing values. + * @param stride The number of `worldVertices` entries between the value pairs written. */ + computeWorldVertices( + slot: Slot, + start: number, + count: number, + worldVertices: ArrayLike, + offset: number, + stride: number + ) { + count = offset + (count >> 1) * stride; + const skeleton = slot.bone.skeleton; + const deformArray = slot.deform; + let vertices = this.vertices; + const bones = this.bones; + if (bones == null) { + if (deformArray.length > 0) vertices = deformArray; + const bone = slot.bone; + const x = bone.worldX; + const y = bone.worldY; + const a = bone.a, + b = bone.b, + c = bone.c, + d = bone.d; + for (let v = start, w = offset; w < count; v += 2, w += stride) { + const vx = vertices[v], + vy = vertices[v + 1]; + worldVertices[w] = vx * a + vy * b + x; + worldVertices[w + 1] = vx * c + vy * d + y; + } + return; + } + let v = 0, + skip = 0; + for (let i = 0; i < start; i += 2) { + const n = bones[v]; + v += n + 1; + skip += n; + } + const skeletonBones = skeleton.bones; + if (deformArray.length == 0) { + for (let w = offset, b = skip * 3; w < count; w += stride) { + let wx = 0, + wy = 0; + let n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) { + const bone = skeletonBones[bones[v]]; + const vx = vertices[b], + vy = vertices[b + 1], + weight = vertices[b + 2]; + wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight; + wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + } + } else { + const deform = deformArray; + for (let w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { + let wx = 0, + wy = 0; + let n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) { + const bone = skeletonBones[bones[v]]; + const vx = vertices[b] + deform[f], + vy = vertices[b + 1] + deform[f + 1], + weight = vertices[b + 2]; + wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight; + wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + } + } + } + + /** Does not copy id (generated) or name (set on construction). **/ + copyTo(attachment: VertexAttachment) { + if (this.bones != null) { + attachment.bones = new Array(this.bones.length); + Utils.arrayCopy(this.bones, 0, attachment.bones, 0, this.bones.length); + } else attachment.bones = null; + + if (this.vertices != null) { + attachment.vertices = Utils.newFloatArray(this.vertices.length); + Utils.arrayCopy(this.vertices, 0, attachment.vertices, 0, this.vertices.length); + } else attachment.vertices = null; + + attachment.worldVerticesLength = this.worldVerticesLength; + attachment.deformAttachment = this.deformAttachment; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/attachments/AttachmentLoader.ts b/packages/spine-core-3.8/src/spine-core/attachments/AttachmentLoader.ts new file mode 100644 index 0000000000..7d14c9f22c --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/attachments/AttachmentLoader.ts @@ -0,0 +1,31 @@ +import { RegionAttachment } from "./RegionAttachment"; +import { Skin } from "../Skin"; +import { BoundingBoxAttachment } from "./BoundingBoxAttachment"; +import { PathAttachment } from "./PathAttachment"; +import { PointAttachment } from "./PointAttachment"; +import { ClippingAttachment } from "./ClippingAttachment"; +import { MeshAttachment } from "./MeshAttachment"; + +/** The interface which can be implemented to customize creating and populating attachments. + * + * See [Loading skeleton data](http://esotericsoftware.com/spine-loading-skeleton-data#AttachmentLoader) in the Spine + * Runtimes Guide. */ +export interface AttachmentLoader { + /** @return May be null to not load an attachment. */ + newRegionAttachment(skin: Skin, name: string, path: string): RegionAttachment; + + /** @return May be null to not load an attachment. */ + newMeshAttachment(skin: Skin, name: string, path: string): MeshAttachment; + + /** @return May be null to not load an attachment. */ + newBoundingBoxAttachment(skin: Skin, name: string): BoundingBoxAttachment; + + /** @return May be null to not load an attachment */ + newPathAttachment(skin: Skin, name: string): PathAttachment; + + /** @return May be null to not load an attachment */ + newPointAttachment(skin: Skin, name: string): PointAttachment; + + /** @return May be null to not load an attachment */ + newClippingAttachment(skin: Skin, name: string): ClippingAttachment; +} diff --git a/packages/spine-core-3.8/src/spine-core/attachments/AttachmentType.ts b/packages/spine-core-3.8/src/spine-core/attachments/AttachmentType.ts new file mode 100644 index 0000000000..193fa62007 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/attachments/AttachmentType.ts @@ -0,0 +1,9 @@ +export enum AttachmentType { + Region, + BoundingBox, + Mesh, + LinkedMesh, + Path, + Point, + Clipping +} diff --git a/packages/spine-core-3.8/src/spine-core/attachments/BoundingBoxAttachment.ts b/packages/spine-core-3.8/src/spine-core/attachments/BoundingBoxAttachment.ts new file mode 100644 index 0000000000..b23cec9733 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/attachments/BoundingBoxAttachment.ts @@ -0,0 +1,22 @@ +import { VertexAttachment, Attachment } from "./Attachment"; +import { Color } from "../Utils"; + +/** An attachment with vertices that make up a polygon. Can be used for hit detection, creating physics bodies, spawning particle + * effects, and more. + * + * See {@link SkeletonBounds} and [Bounding Boxes](http://esotericsoftware.com/spine-bounding-boxes) in the Spine User + * Guide. */ +export class BoundingBoxAttachment extends VertexAttachment { + color = new Color(1, 1, 1, 1); + + constructor(name: string) { + super(name); + } + + copy(): Attachment { + const copy = new BoundingBoxAttachment(this.name); + this.copyTo(copy); + copy.color.setFromColor(this.color); + return copy; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/attachments/ClippingAttachment.ts b/packages/spine-core-3.8/src/spine-core/attachments/ClippingAttachment.ts new file mode 100644 index 0000000000..8ebc184346 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/attachments/ClippingAttachment.ts @@ -0,0 +1,27 @@ +import { VertexAttachment, Attachment } from "./Attachment"; +import { SlotData } from "../SlotData"; +import { Color } from "../Utils"; + +/** An attachment with vertices that make up a polygon used for clipping the rendering of other attachments. */ +export class ClippingAttachment extends VertexAttachment { + /** Clipping is performed between the clipping polygon's slot and the end slot. Returns null if clipping is done until the end of + * the skeleton's rendering. */ + endSlot: SlotData; + + // Nonessential. + /** The color of the clipping polygon as it was in Spine. Available only when nonessential data was exported. Clipping polygons + * are not usually rendered at runtime. */ + color = new Color(0.2275, 0.2275, 0.8078, 1); // ce3a3aff + + constructor(name: string) { + super(name); + } + + copy(): Attachment { + const copy = new ClippingAttachment(this.name); + this.copyTo(copy); + copy.endSlot = this.endSlot; + copy.color.setFromColor(this.color); + return copy; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/attachments/MeshAttachment.ts b/packages/spine-core-3.8/src/spine-core/attachments/MeshAttachment.ts new file mode 100644 index 0000000000..02deaa68ff --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/attachments/MeshAttachment.ts @@ -0,0 +1,175 @@ +import { VertexAttachment, Attachment } from "./Attachment"; +import { TextureRegion } from "../Texture"; +import { Color, Utils, ArrayLike } from "../Utils"; +import { TextureAtlasRegion } from "../TextureAtlas"; + +/** An attachment that displays a textured mesh. A mesh has hull vertices and internal vertices within the hull. Holes are not + * supported. Each vertex has UVs (texture coordinates) and triangles are used to map an image on to the mesh. + * + * See [Mesh attachments](http://esotericsoftware.com/spine-meshes) in the Spine User Guide. */ +export class MeshAttachment extends VertexAttachment { + region: TextureRegion; + + /** The name of the texture region for this attachment. */ + path: string; + + /** The UV pair for each vertex, normalized within the texture region. */ + regionUVs: ArrayLike; + + /** The UV pair for each vertex, normalized within the entire texture. + * + * See {@link #updateUVs}. */ + uvs: ArrayLike; + + /** Triplets of vertex indices which describe the mesh's triangulation. */ + triangles: Array; + + /** The color to tint the mesh. */ + color = new Color(1, 1, 1, 1); + + /** The width of the mesh's image. Available only when nonessential data was exported. */ + width: number; + + /** The height of the mesh's image. Available only when nonessential data was exported. */ + height: number; + + /** The number of entries at the beginning of {@link #vertices} that make up the mesh hull. */ + hullLength: number; + + /** Vertex index pairs describing edges for controling triangulation. Mesh triangles will never cross edges. Only available if + * nonessential data was exported. Triangulation is not performed at runtime. */ + edges: Array; + + private parentMesh: MeshAttachment; + tempColor = new Color(0, 0, 0, 0); + + constructor(name: string) { + super(name); + } + + /** Calculates {@link #uvs} using {@link #regionUVs} and the {@link #region}. Must be called after changing the region UVs or + * region. */ + updateUVs() { + const regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.length != regionUVs.length) this.uvs = Utils.newFloatArray(regionUVs.length); + const uvs = this.uvs; + const n = this.uvs.length; + let u = this.region.u, + v = this.region.v, + width = 0, + height = 0; + if (this.region instanceof TextureAtlasRegion) { + const region = this.region; + const textureWidth = region.texture.width, + textureHeight = region.texture.height; + switch (region.degrees) { + case 90: + u -= (region.originalHeight - region.offsetY - region.height) / textureWidth; + v -= (region.originalWidth - region.offsetX - region.width) / textureHeight; + width = region.originalHeight / textureWidth; + height = region.originalWidth / textureHeight; + for (let i = 0; i < n; i += 2) { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + (1 - regionUVs[i]) * height; + } + return; + case 180: + u -= (region.originalWidth - region.offsetX - region.width) / textureWidth; + v -= region.offsetY / textureHeight; + width = region.originalWidth / textureWidth; + height = region.originalHeight / textureHeight; + for (let i = 0; i < n; i += 2) { + uvs[i] = u + (1 - regionUVs[i]) * width; + uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height; + } + return; + case 270: + u -= region.offsetY / textureWidth; + v -= region.offsetX / textureHeight; + width = region.originalHeight / textureWidth; + height = region.originalWidth / textureHeight; + for (let i = 0; i < n; i += 2) { + uvs[i] = u + (1 - regionUVs[i + 1]) * width; + uvs[i + 1] = v + regionUVs[i] * height; + } + return; + } + u -= region.offsetX / textureWidth; + v -= (region.originalHeight - region.offsetY - region.height) / textureHeight; + width = region.originalWidth / textureWidth; + height = region.originalHeight / textureHeight; + } else if (this.region == null) { + u = v = 0; + width = height = 1; + } else { + width = this.region.u2 - u; + height = this.region.v2 - v; + } + + for (let i = 0; i < n; i += 2) { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + + /** The parent mesh if this is a linked mesh, else null. A linked mesh shares the {@link #bones}, {@link #vertices}, + * {@link #regionUVs}, {@link #triangles}, {@link #hullLength}, {@link #edges}, {@link #width}, and {@link #height} with the + * parent mesh, but may have a different {@link #name} or {@link #path} (and therefore a different texture). */ + getParentMesh() { + return this.parentMesh; + } + + /** @param parentMesh May be null. */ + setParentMesh(parentMesh: MeshAttachment) { + this.parentMesh = parentMesh; + if (parentMesh != null) { + this.bones = parentMesh.bones; + this.vertices = parentMesh.vertices; + this.worldVerticesLength = parentMesh.worldVerticesLength; + this.regionUVs = parentMesh.regionUVs; + this.triangles = parentMesh.triangles; + this.hullLength = parentMesh.hullLength; + this.worldVerticesLength = parentMesh.worldVerticesLength; + } + } + + copy(): Attachment { + if (this.parentMesh != null) return this.newLinkedMesh(); + + const copy = new MeshAttachment(this.name); + copy.region = this.region; + copy.path = this.path; + copy.color.setFromColor(this.color); + + this.copyTo(copy); + copy.regionUVs = new Array(this.regionUVs.length); + Utils.arrayCopy(this.regionUVs, 0, copy.regionUVs, 0, this.regionUVs.length); + copy.uvs = new Array(this.uvs.length); + Utils.arrayCopy(this.uvs, 0, copy.uvs, 0, this.uvs.length); + copy.triangles = new Array(this.triangles.length); + Utils.arrayCopy(this.triangles, 0, copy.triangles, 0, this.triangles.length); + copy.hullLength = this.hullLength; + + // Nonessential. + if (this.edges != null) { + copy.edges = new Array(this.edges.length); + Utils.arrayCopy(this.edges, 0, copy.edges, 0, this.edges.length); + } + copy.width = this.width; + copy.height = this.height; + + return copy; + } + + /** Returns a new mesh with the {@link #parentMesh} set to this mesh's parent mesh, if any, else to this mesh. **/ + newLinkedMesh(): MeshAttachment { + const copy = new MeshAttachment(this.name); + copy.region = this.region; + copy.path = this.path; + copy.color.setFromColor(this.color); + copy.deformAttachment = this.deformAttachment; + copy.setParentMesh(this.parentMesh != null ? this.parentMesh : this); + copy.updateUVs(); + return copy; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/attachments/PathAttachment.ts b/packages/spine-core-3.8/src/spine-core/attachments/PathAttachment.ts new file mode 100644 index 0000000000..53562b7657 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/attachments/PathAttachment.ts @@ -0,0 +1,36 @@ +import { VertexAttachment, Attachment } from "./Attachment"; +import { Color, Utils } from "../Utils"; + +/** An attachment whose vertices make up a composite Bezier curve. + * + * See {@link PathConstraint} and [Paths](http://esotericsoftware.com/spine-paths) in the Spine User Guide. */ +export class PathAttachment extends VertexAttachment { + /** The lengths along the path in the setup pose from the start of the path to the end of each Bezier curve. */ + lengths: Array; + + /** If true, the start and end knots are connected. */ + closed = false; + + /** If true, additional calculations are performed to make calculating positions along the path more accurate. If false, fewer + * calculations are performed but calculating positions along the path is less accurate. */ + constantSpeed = false; + + /** The color of the path as it was in Spine. Available only when nonessential data was exported. Paths are not usually + * rendered at runtime. */ + color = new Color(1, 1, 1, 1); + + constructor(name: string) { + super(name); + } + + copy(): Attachment { + const copy = new PathAttachment(this.name); + this.copyTo(copy); + copy.lengths = new Array(this.lengths.length); + Utils.arrayCopy(this.lengths, 0, copy.lengths, 0, this.lengths.length); + copy.closed = closed; + copy.constantSpeed = this.constantSpeed; + copy.color.setFromColor(this.color); + return copy; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/attachments/PointAttachment.ts b/packages/spine-core-3.8/src/spine-core/attachments/PointAttachment.ts new file mode 100644 index 0000000000..74de51a2ef --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/attachments/PointAttachment.ts @@ -0,0 +1,46 @@ +import { VertexAttachment, Attachment } from "./Attachment"; +import { Color, MathUtils, Vector2 } from "../Utils"; +import { Bone } from "../Bone"; + +/** An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be + * used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a + * skin. + * + * See [Point Attachments](http://esotericsoftware.com/spine-point-attachments) in the Spine User Guide. */ +export class PointAttachment extends VertexAttachment { + x: number; + y: number; + rotation: number; + + /** The color of the point attachment as it was in Spine. Available only when nonessential data was exported. Point attachments + * are not usually rendered at runtime. */ + color = new Color(0.38, 0.94, 0, 1); + + constructor(name: string) { + super(name); + this.name = name; + } + + computeWorldPosition(bone: Bone, point: Vector2) { + point.x = this.x * bone.a + this.y * bone.b + bone.worldX; + point.y = this.x * bone.c + this.y * bone.d + bone.worldY; + return point; + } + + computeWorldRotation(bone: Bone) { + const cos = MathUtils.cosDeg(this.rotation), + sin = MathUtils.sinDeg(this.rotation); + const x = cos * bone.a + sin * bone.b; + const y = cos * bone.c + sin * bone.d; + return Math.atan2(y, x) * MathUtils.radDeg; + } + + copy(): Attachment { + const copy = new PointAttachment(this.name); + copy.x = this.x; + copy.y = this.y; + copy.rotation = this.rotation; + copy.color.setFromColor(this.color); + return copy; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/attachments/RegionAttachment.ts b/packages/spine-core-3.8/src/spine-core/attachments/RegionAttachment.ts new file mode 100644 index 0000000000..1730e3880a --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/attachments/RegionAttachment.ts @@ -0,0 +1,211 @@ +import { Attachment } from "./Attachment"; +import { Color, Utils, ArrayLike } from "../Utils"; +import { TextureRegion } from "../Texture"; +import { Bone } from "../Bone"; + +/** An attachment that displays a textured quadrilateral. + * + * See [Region attachments](http://esotericsoftware.com/spine-regions) in the Spine User Guide. */ +export class RegionAttachment extends Attachment { + static OX1 = 0; + static OY1 = 1; + static OX2 = 2; + static OY2 = 3; + static OX3 = 4; + static OY3 = 5; + static OX4 = 6; + static OY4 = 7; + + static X1 = 0; + static Y1 = 1; + static C1R = 2; + static C1G = 3; + static C1B = 4; + static C1A = 5; + static U1 = 6; + static V1 = 7; + + static X2 = 8; + static Y2 = 9; + static C2R = 10; + static C2G = 11; + static C2B = 12; + static C2A = 13; + static U2 = 14; + static V2 = 15; + + static X3 = 16; + static Y3 = 17; + static C3R = 18; + static C3G = 19; + static C3B = 20; + static C3A = 21; + static U3 = 22; + static V3 = 23; + + static X4 = 24; + static Y4 = 25; + static C4R = 26; + static C4G = 27; + static C4B = 28; + static C4A = 29; + static U4 = 30; + static V4 = 31; + + /** The local x translation. */ + x = 0; + + /** The local y translation. */ + y = 0; + + /** The local scaleX. */ + scaleX = 1; + + /** The local scaleY. */ + scaleY = 1; + + /** The local rotation. */ + rotation = 0; + + /** The width of the region attachment in Spine. */ + width = 0; + + /** The height of the region attachment in Spine. */ + height = 0; + + /** The color to tint the region attachment. */ + color = new Color(1, 1, 1, 1); + + /** The name of the texture region for this attachment. */ + path: string; + + rendererObject: any; + region: TextureRegion; + + /** For each of the 4 vertices, a pair of x,y values that is the local position of the vertex. + * + * See {@link #updateOffset()}. */ + offset = Utils.newFloatArray(8); + + uvs = Utils.newFloatArray(8); + + tempColor = new Color(1, 1, 1, 1); + + constructor(name: string) { + super(name); + } + + /** Calculates the {@link #offset} using the region settings. Must be called after changing region settings. */ + updateOffset(): void { + const regionScaleX = (this.width / this.region.originalWidth) * this.scaleX; + const regionScaleY = (this.height / this.region.originalHeight) * this.scaleY; + const localX = (-this.width / 2) * this.scaleX + this.region.offsetX * regionScaleX; + const localY = (-this.height / 2) * this.scaleY + this.region.offsetY * regionScaleY; + const localX2 = localX + this.region.width * regionScaleX; + const localY2 = localY + this.region.height * regionScaleY; + const radians = (this.rotation * Math.PI) / 180; + const cos = Math.cos(radians); + const sin = Math.sin(radians); + const localXCos = localX * cos + this.x; + const localXSin = localX * sin; + const localYCos = localY * cos + this.y; + const localYSin = localY * sin; + const localX2Cos = localX2 * cos + this.x; + const localX2Sin = localX2 * sin; + const localY2Cos = localY2 * cos + this.y; + const localY2Sin = localY2 * sin; + const offset = this.offset; + offset[RegionAttachment.OX1] = localXCos - localYSin; + offset[RegionAttachment.OY1] = localYCos + localXSin; + offset[RegionAttachment.OX2] = localXCos - localY2Sin; + offset[RegionAttachment.OY2] = localY2Cos + localXSin; + offset[RegionAttachment.OX3] = localX2Cos - localY2Sin; + offset[RegionAttachment.OY3] = localY2Cos + localX2Sin; + offset[RegionAttachment.OX4] = localX2Cos - localYSin; + offset[RegionAttachment.OY4] = localYCos + localX2Sin; + } + + setRegion(region: TextureRegion): void { + this.region = region; + const uvs = this.uvs; + if (region.rotate) { + uvs[2] = region.u; + uvs[3] = region.v2; + uvs[4] = region.u; + uvs[5] = region.v; + uvs[6] = region.u2; + uvs[7] = region.v; + uvs[0] = region.u2; + uvs[1] = region.v2; + } else { + uvs[0] = region.u; + uvs[1] = region.v2; + uvs[2] = region.u; + uvs[3] = region.v; + uvs[4] = region.u2; + uvs[5] = region.v; + uvs[6] = region.u2; + uvs[7] = region.v2; + } + } + + /** Transforms the attachment's four vertices to world coordinates. + * + * See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine + * Runtimes Guide. + * @param worldVertices The output world vertices. Must have a length >= `offset` + 8. + * @param offset The `worldVertices` index to begin writing values. + * @param stride The number of `worldVertices` entries between the value pairs written. */ + computeWorldVertices(bone: Bone, worldVertices: ArrayLike, offset: number, stride: number) { + const vertexOffset = this.offset; + const x = bone.worldX, + y = bone.worldY; + const a = bone.a, + b = bone.b, + c = bone.c, + d = bone.d; + let offsetX = 0, + offsetY = 0; + + offsetX = vertexOffset[RegionAttachment.OX1]; + offsetY = vertexOffset[RegionAttachment.OY1]; + worldVertices[offset] = offsetX * a + offsetY * b + x; // br + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = vertexOffset[RegionAttachment.OX2]; + offsetY = vertexOffset[RegionAttachment.OY2]; + worldVertices[offset] = offsetX * a + offsetY * b + x; // bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = vertexOffset[RegionAttachment.OX3]; + offsetY = vertexOffset[RegionAttachment.OY3]; + worldVertices[offset] = offsetX * a + offsetY * b + x; // ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = vertexOffset[RegionAttachment.OX4]; + offsetY = vertexOffset[RegionAttachment.OY4]; + worldVertices[offset] = offsetX * a + offsetY * b + x; // ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + } + + copy(): Attachment { + const copy = new RegionAttachment(this.name); + copy.region = this.region; + copy.rendererObject = this.rendererObject; + copy.path = this.path; + copy.x = this.x; + copy.y = this.y; + copy.scaleX = this.scaleX; + copy.scaleY = this.scaleY; + copy.rotation = this.rotation; + copy.width = this.width; + copy.height = this.height; + Utils.arrayCopy(this.uvs, 0, copy.uvs, 0, 8); + Utils.arrayCopy(this.offset, 0, copy.offset, 0, 8); + copy.color.setFromColor(this.color); + return copy; + } +} diff --git a/packages/spine-core-3.8/src/spine-core/index.ts b/packages/spine-core-3.8/src/spine-core/index.ts new file mode 100644 index 0000000000..9dfa8aee05 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/index.ts @@ -0,0 +1,50 @@ +// Barrel for the vendored spine 3.8 runtime. There is no `@esotericsoftware/spine-core` npm +// release for the 3.8 line (npm starts at 4.0.1), so this directory vendors the runtime source +// instead of depending on a package; this file is the re-export surface the 4.2 package gets for +// free from `export * from "@esotericsoftware/spine-core"`. +import "./polyfills"; + +export * from "./Animation"; +export * from "./AnimationState"; +export * from "./AnimationStateData"; +export * from "./AtlasAttachmentLoader"; +export * from "./BlendMode"; +export * from "./Bone"; +export * from "./BoneData"; +export * from "./ConstraintData"; +export * from "./Event"; +export * from "./EventData"; +export * from "./IkConstraint"; +export * from "./IkConstraintData"; +export * from "./PathConstraint"; +export * from "./PathConstraintData"; +export * from "./Skeleton"; +export * from "./SkeletonBinary"; +export * from "./SkeletonBounds"; +export * from "./SkeletonClipping"; +export * from "./SkeletonData"; +export * from "./SkeletonJson"; +export * from "./Skin"; +export * from "./Slot"; +export * from "./SlotData"; +export * from "./Texture"; +export * from "./TextureAtlas"; +export * from "./TransformConstraint"; +export * from "./TransformConstraintData"; +export * from "./Triangulator"; +export * from "./Updatable"; +export * from "./Utils"; +export * from "./VertexEffect"; + +export * from "./attachments/Attachment"; +export * from "./attachments/AttachmentLoader"; +export * from "./attachments/AttachmentType"; +export * from "./attachments/BoundingBoxAttachment"; +export * from "./attachments/ClippingAttachment"; +export * from "./attachments/MeshAttachment"; +export * from "./attachments/PathAttachment"; +export * from "./attachments/PointAttachment"; +export * from "./attachments/RegionAttachment"; + +export * from "./vertexeffects/JitterEffect"; +export * from "./vertexeffects/SwirlEffect"; diff --git a/packages/spine-core-3.8/src/spine-core/polyfills.ts b/packages/spine-core-3.8/src/spine-core/polyfills.ts new file mode 100644 index 0000000000..0531725d1c --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/polyfills.ts @@ -0,0 +1,13 @@ +interface Math { + fround(n: number): number; +} + +(() => { + if (!Math.fround) { + Math.fround = (function (array) { + return function (x: number) { + return (array[0] = x), array[0]; + }; + })(new Float32Array(1)); + } +})(); diff --git a/packages/spine-core-3.8/src/spine-core/vertexeffects/JitterEffect.ts b/packages/spine-core-3.8/src/spine-core/vertexeffects/JitterEffect.ts new file mode 100644 index 0000000000..f7060b06ba --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/vertexeffects/JitterEffect.ts @@ -0,0 +1,22 @@ +import { VertexEffect } from "../VertexEffect"; +import { Skeleton } from "../Skeleton"; +import { Color, MathUtils, Vector2 } from "../Utils"; + +export class JitterEffect implements VertexEffect { + jitterX = 0; + jitterY = 0; + + constructor(jitterX: number, jitterY: number) { + this.jitterX = jitterX; + this.jitterY = jitterY; + } + + begin(skeleton: Skeleton): void {} + + transform(position: Vector2, uv: Vector2, light: Color, dark: Color): void { + position.x += MathUtils.randomTriangular(-this.jitterX, this.jitterY); + position.y += MathUtils.randomTriangular(-this.jitterX, this.jitterY); + } + + end(): void {} +} diff --git a/packages/spine-core-3.8/src/spine-core/vertexeffects/SwirlEffect.ts b/packages/spine-core-3.8/src/spine-core/vertexeffects/SwirlEffect.ts new file mode 100644 index 0000000000..52f0169be5 --- /dev/null +++ b/packages/spine-core-3.8/src/spine-core/vertexeffects/SwirlEffect.ts @@ -0,0 +1,38 @@ +import { VertexEffect } from "../VertexEffect"; +import { PowOut, Color, MathUtils, Vector2 } from "../Utils"; +import { Skeleton } from "../Skeleton"; + +export class SwirlEffect implements VertexEffect { + static interpolation = new PowOut(2); + centerX = 0; + centerY = 0; + radius = 0; + angle = 0; + private worldX = 0; + private worldY = 0; + + constructor(radius: number) { + this.radius = radius; + } + + begin(skeleton: Skeleton): void { + this.worldX = skeleton.x + this.centerX; + this.worldY = skeleton.y + this.centerY; + } + + transform(position: Vector2, uv: Vector2, light: Color, dark: Color): void { + const radAngle = this.angle * MathUtils.degreesToRadians; + const x = position.x - this.worldX; + const y = position.y - this.worldY; + const dist = Math.sqrt(x * x + y * y); + if (dist < this.radius) { + const theta = SwirlEffect.interpolation.apply(0, radAngle, (this.radius - dist) / this.radius); + const cos = Math.cos(theta); + const sin = Math.sin(theta); + position.x = cos * x - sin * y + this.worldX; + position.y = sin * x + cos * y + this.worldY; + } + } + + end(): void {} +} diff --git a/packages/spine-core-3.8/src/util/ClearablePool.ts b/packages/spine-core-3.8/src/util/ClearablePool.ts new file mode 100644 index 0000000000..ff8d6ccc6a --- /dev/null +++ b/packages/spine-core-3.8/src/util/ClearablePool.ts @@ -0,0 +1,26 @@ +export class ClearablePool { + private _type: new () => T; + private _elements: T[]; + private _usedElementCount: number = 0; + + constructor(type: new () => T) { + this._type = type; + this._elements = []; + } + + get(): T { + const { _usedElementCount: usedElementCount, _elements: elements } = this; + this._usedElementCount++; + if (elements.length === usedElementCount) { + const element = new this._type(); + elements.push(element); + return element; + } else { + return elements[usedElementCount]; + } + } + + clear(): void { + this._usedElementCount = 0; + } +} diff --git a/packages/spine-core-3.8/src/util/ReturnablePool.ts b/packages/spine-core-3.8/src/util/ReturnablePool.ts new file mode 100644 index 0000000000..77c1bcaa47 --- /dev/null +++ b/packages/spine-core-3.8/src/util/ReturnablePool.ts @@ -0,0 +1,25 @@ +export class ReturnablePool { + private _type: new () => T; + private _elements: T[]; + private _lastElementIndex: number; + + constructor(type: new () => T, initializeCount: number = 1) { + this._type = type; + this._lastElementIndex = initializeCount - 1; + const elements = (this._elements = new Array(initializeCount)); + for (let i = 0; i < initializeCount; ++i) { + elements[i] = new type(); + } + } + + get(): T { + if (this._lastElementIndex < 0) { + return new this._type(); + } + return this._elements[this._lastElementIndex--]; + } + + return(element: T): void { + this._elements[++this._lastElementIndex] = element; + } +} diff --git a/packages/spine-core-3.8/tsconfig.json b/packages/spine-core-3.8/tsconfig.json new file mode 100644 index 0000000000..588b0055c3 --- /dev/null +++ b/packages/spine-core-3.8/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "declaration": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "declarationDir": "types", + "emitDeclarationOnly": true, + "noImplicitOverride": true, + "sourceMap": true, + "incremental": false, + "skipLibCheck": true, + "stripInternal": true + }, + "include": ["src/**/*"] +} diff --git a/packages/spine-core-4.2/package.json b/packages/spine-core-4.2/package.json new file mode 100644 index 0000000000..5f5aa0e11e --- /dev/null +++ b/packages/spine-core-4.2/package.json @@ -0,0 +1,41 @@ +{ + "name": "@galacean/engine-spine-core-4.2", + "version": "0.0.0-experimental-2.0-game.18", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "repository": { + "url": "https://github.com/galacean/engine.git" + }, + "license": "MIT", + "main": "dist/main.js", + "module": "dist/module.js", + "debug": "src/index.ts", + "browser": "dist/browser.js", + "types": "types/index.d.ts", + "scripts": { + "b:types": "tsc" + }, + "umd": { + "name": "Galacean.SpineCore42", + "globals": { + "@galacean/engine": "Galacean", + "@galacean/engine-spine": "Galacean.Spine" + } + }, + "files": [ + "dist/**/*", + "types/**/*" + ], + "dependencies": { + "@galacean/engine-spine": "workspace:*" + }, + "devDependencies": { + "@esotericsoftware/spine-core": "~4.2.66", + "@galacean/engine": "workspace:*" + }, + "peerDependencies": { + "@galacean/engine": "workspace:*" + } +} diff --git a/packages/spine-core-4.2/src/Spine42Runtime.ts b/packages/spine-core-4.2/src/Spine42Runtime.ts new file mode 100644 index 0000000000..03082b3589 --- /dev/null +++ b/packages/spine-core-4.2/src/Spine42Runtime.ts @@ -0,0 +1,81 @@ +import { + AnimationState, + AnimationStateData, + AtlasAttachmentLoader, + Physics, + Skeleton, + SkeletonBinary, + SkeletonData, + SkeletonJson, + TextureAtlas +} from "@esotericsoftware/spine-core"; +import type { Texture2D } from "@galacean/engine"; +import type { ISpineRenderTarget, ISpineRuntime } from "@galacean/engine-spine"; +import { SpineGenerator } from "./SpineGenerator"; +import { SpineTexture } from "./SpineTexture"; + +/** + * Spine 4.2 runtime backend. + * + * @remarks + * Owns everything specific to the spine 4.2 runtime line: parsing (`SkeletonJson`/`SkeletonBinary`/ + * `TextureAtlas`), object-model construction, world-transform stepping (4.2's `Physics.update`), and + * the attachment-reading mesh generator. Registered automatically when this package is imported. + */ +export class Spine42Runtime implements ISpineRuntime { + private _generator = new SpineGenerator(); + + parseAtlasPageNames(atlasText: string): string[] { + return new TextureAtlas(atlasText).pages.map((page) => page.name); + } + + createTextureAtlas(atlasText: string, textures: Texture2D[]): TextureAtlas { + const textureAtlas = new TextureAtlas(atlasText); + textureAtlas.pages.forEach((page, index) => { + const engineTexture = textures.find((item) => item.name === page.name) || textures[index]; + const texture = new SpineTexture(engineTexture); + page.setTexture(texture); + }); + return textureAtlas; + } + + createSkeletonData( + skeletonRawData: string | ArrayBuffer, + textureAtlas: TextureAtlas, + skeletonDataScale: number + ): SkeletonData { + const atlasLoader = new AtlasAttachmentLoader(textureAtlas); + if (typeof skeletonRawData === "string") { + const skeletonJson = new SkeletonJson(atlasLoader); + skeletonJson.scale = skeletonDataScale; + return skeletonJson.readSkeletonData(skeletonRawData); + } else { + const skeletonBinary = new SkeletonBinary(atlasLoader); + skeletonBinary.scale = skeletonDataScale; + return skeletonBinary.readSkeletonData(new Uint8Array(skeletonRawData as ArrayBuffer)); + } + } + + createAnimationStateData(skeletonData: SkeletonData): AnimationStateData { + return new AnimationStateData(skeletonData); + } + + createSkeleton(skeletonData: SkeletonData): Skeleton { + return new Skeleton(skeletonData); + } + + createAnimationState(stateData: AnimationStateData): AnimationState { + return new AnimationState(stateData); + } + + updateState(skeleton: Skeleton, state: AnimationState, delta: number): void { + state.update(delta); + state.apply(skeleton); + skeleton.update(delta); + skeleton.updateWorldTransform(Physics.update); + } + + buildPrimitive(skeleton: Skeleton, target: ISpineRenderTarget): void { + this._generator.buildPrimitive(skeleton, target); + } +} diff --git a/packages/spine-core-4.2/src/SpineGenerator.ts b/packages/spine-core-4.2/src/SpineGenerator.ts new file mode 100644 index 0000000000..e995320a03 --- /dev/null +++ b/packages/spine-core-4.2/src/SpineGenerator.ts @@ -0,0 +1,342 @@ +import { + ArrayLike, + BlendMode, + ClippingAttachment, + Color, + MeshAttachment, + NumberArrayLike, + RegionAttachment, + Skeleton, + SkeletonClipping +} from "@esotericsoftware/spine-core"; +import { BoundingBox, SubPrimitive } from "@galacean/engine"; +import { SpineTexture } from "./SpineTexture"; +import { ClearablePool } from "./util/ClearablePool"; +import { ReturnablePool } from "./util/ReturnablePool"; +import type { ISpineRenderTarget } from "@galacean/engine-spine"; +import { SpineBlendMode, SpineVertexStride } from "@galacean/engine-spine"; + +class SubRenderItem { + subPrimitive: SubPrimitive; + blendMode: BlendMode; + texture: any; +} + +/** + * @internal + */ +export class SpineGenerator { + static tempDark = new Color(); + static tempColor = new Color(); + static tempVerts = new Array(8); + static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0]; + static subPrimitivePool = new ReturnablePool(SubPrimitive); + static subRenderItemPool = new ClearablePool(SubRenderItem); + + private _subRenderItems: SubRenderItem[] = []; + private _clipper: SkeletonClipping = new SkeletonClipping(); + + buildPrimitive(skeleton: Skeleton, renderer: ISpineRenderTarget) { + const { _indices, _vertices, _localBounds, _vertexCount, _subPrimitives, zSpacing, premultipliedAlpha, tintBlack } = + renderer; + + _localBounds.min.set(Infinity, Infinity, Infinity); + _localBounds.max.set(-Infinity, -Infinity, -Infinity); + + const { _clipper, _subRenderItems } = this; + + const { tempVerts, subRenderItemPool, subPrimitivePool } = SpineGenerator; + const { withTint: vertexStrideWithTint, withoutTint: vertexStrideWithoutTint } = SpineVertexStride; + + _subRenderItems.length = 0; + subRenderItemPool.clear(); + + let triangles: Array; + let uvs: NumberArrayLike; + + let verticesLength = 0; + let indicesLength = 0; + let start = 0; + let count = 0; + + let blend = BlendMode.Normal; + let texture: SpineTexture = null; + let tempBlendMode: BlendMode | null = null; + let tempTexture: SpineTexture | null = null; + + let primitiveIndex = 0; + + const drawOrder = skeleton.drawOrder; + for (let slotIndex = 0, n = drawOrder.length; slotIndex < n; ++slotIndex) { + const slot = drawOrder[slotIndex]; + if (!slot.bone.active) { + _clipper.clipEndWithSlot(slot); + continue; + } + + const attachment = slot.getAttachment(); + if (!attachment) { + _clipper.clipEndWithSlot(slot); + continue; + } + + const z = zSpacing * slotIndex; + const isClipping = _clipper.isClipping(); + let numFloats = 0; + let attachmentColor: Color = null; + + // This vertexSize will be passed to spine-core's computeWorldVertices function. + // + // Expected format by computeWorldVertices: + // - Without tintBlack: [x, y, u, v, r, g, b, a] = 8 components + // - With tintBlack: [x, y, u, v, r, g, b, a, dr, dg, db, da] = 12 components + // + // Our actual vertex buffer format: + // - vertexStrideWithoutTint: [x, y, z, u, v, r, g, b, a] = 9 components + // - vertexStrideWithTint: [x, y, z, u, v, r, g, b, a, dr, dg, db] = 12 components + // (Note: we optimized 'da' as uniform instead of buffer attribute) + // + // Calculation: + // - Without tintBlack: 9 - 1 (remove z) = 8 ✓ + // - With tintBlack: 12 - 1 (remove z) + 1 (add back da) = 12 ✓ + let vertexSize = tintBlack ? vertexStrideWithTint : vertexStrideWithoutTint - 1; + let clippedVertexSize = isClipping ? 2 : vertexSize; + + switch (attachment.constructor) { + case RegionAttachment: + const regionAttachment = attachment; + attachmentColor = regionAttachment.color; + numFloats = clippedVertexSize << 2; + regionAttachment.computeWorldVertices(slot, tempVerts, 0, clippedVertexSize); + triangles = SpineGenerator.QUAD_TRIANGLES; + uvs = regionAttachment.uvs; + texture = regionAttachment.region.texture; + break; + case MeshAttachment: + const meshAttachment = attachment; + attachmentColor = meshAttachment.color; + numFloats = (meshAttachment.worldVerticesLength >> 1) * clippedVertexSize; + if (numFloats > _vertices.length) { + SpineGenerator.tempVerts = new Array(numFloats); + } + meshAttachment.computeWorldVertices( + slot, + 0, + meshAttachment.worldVerticesLength, + tempVerts, + 0, + clippedVertexSize + ); + triangles = meshAttachment.triangles; + uvs = meshAttachment.uvs; + texture = meshAttachment.region.texture; + break; + case ClippingAttachment: + let clip = attachment; + _clipper.clipStart(slot, clip); + continue; + default: + _clipper.clipEndWithSlot(slot); + continue; + } + + if (texture != null) { + let finalVertices: ArrayLike; + let finalVerticesLength: number; + let finalIndices: ArrayLike; + let finalIndicesLength: number; + + const skeleton = slot.bone.skeleton; + const skeletonColor = skeleton.color; + const slotColor = slot.color; + const finalColor = SpineGenerator.tempColor; + const finalAlpha = skeletonColor.a * slotColor.a * attachmentColor.a; + + finalColor.r = skeletonColor.r * slotColor.r * attachmentColor.r; + finalColor.g = skeletonColor.g * slotColor.g * attachmentColor.g; + finalColor.b = skeletonColor.b * slotColor.b * attachmentColor.b; + finalColor.a = finalAlpha; + + if (premultipliedAlpha) { + finalColor.r *= finalAlpha; + finalColor.g *= finalAlpha; + finalColor.b *= finalAlpha; + } + + let darkColor = SpineGenerator.tempDark; + const slotDarkColor = slot.darkColor; + if (!slotDarkColor) { + darkColor.set(0, 0, 0, 1); + } else { + if (premultipliedAlpha) { + darkColor.r = slotDarkColor.r * finalAlpha; + darkColor.g = slotDarkColor.g * finalAlpha; + darkColor.b = slotDarkColor.b * finalAlpha; + } else { + darkColor.setFromColor(slotDarkColor); + } + } + + if (isClipping) { + _clipper.clipTriangles(tempVerts, triangles, triangles.length, uvs, finalColor, darkColor, tintBlack); + finalVertices = _clipper.clippedVertices; + finalVerticesLength = finalVertices.length; + finalIndices = _clipper.clippedTriangles; + finalIndicesLength = finalIndices.length; + } else { + const { r, g, b, a } = finalColor; + for (let v = 2, u = 0, n = numFloats; v < n; v += vertexSize, u += 2) { + tempVerts[v] = r; + tempVerts[v + 1] = g; + tempVerts[v + 2] = b; + tempVerts[v + 3] = a; + tempVerts[v + 4] = uvs[u]; + tempVerts[v + 5] = uvs[u + 1]; + if (tintBlack) { + tempVerts[v + 6] = darkColor.r; + tempVerts[v + 7] = darkColor.g; + tempVerts[v + 8] = darkColor.b; + tempVerts[v + 9] = darkColor.a; + } + } + finalVertices = tempVerts; + finalVerticesLength = numFloats; + finalIndices = triangles; + finalIndicesLength = triangles.length; + } + + if (finalVerticesLength == 0 || finalIndicesLength == 0) { + _clipper.clipEndWithSlot(slot); + continue; + } + + const stride = tintBlack ? vertexStrideWithTint : vertexStrideWithoutTint; + let indexStart = verticesLength / stride; + let i = verticesLength; + let j = 0; + for (; j < finalVerticesLength; ) { + let x = finalVertices[j++]; + let y = finalVertices[j++]; + _vertices[i++] = x; + _vertices[i++] = y; + _vertices[i++] = z; + _vertices[i++] = finalVertices[j++]; // u + _vertices[i++] = finalVertices[j++]; // v + _vertices[i++] = finalVertices[j++]; // r + _vertices[i++] = finalVertices[j++]; // g + _vertices[i++] = finalVertices[j++]; // b + _vertices[i++] = finalVertices[j++]; // a + if (tintBlack) { + _vertices[i++] = finalVertices[j++]; // darkR + _vertices[i++] = finalVertices[j++]; // darkG + _vertices[i++] = finalVertices[j++]; // darkB + j++; // darkA + } + this._expandBounds(x, y, z, _localBounds); + } + verticesLength = i; + + for (i = indicesLength, j = 0; j < finalIndicesLength; i++, j++) { + _indices[i] = finalIndices[j] + indexStart; + } + indicesLength += finalIndicesLength; + + const textureChanged = tempTexture !== null && tempTexture !== texture; + blend = slot.data.blendMode; + const blendModeChanged = tempBlendMode !== null && tempBlendMode !== blend; + + if (blendModeChanged || textureChanged) { + // Finish accumulated count first + if (count > 0) { + primitiveIndex = this._createRenderItem( + _subPrimitives, + primitiveIndex, + start, + count, + tempTexture, + tempBlendMode + ); + start += count; + count = 0; + } + } + count += finalIndicesLength; + tempTexture = texture; + tempBlendMode = blend; + } + + _clipper.clipEndWithSlot(slot); + } // slot traverse end + + // add reset sub primitive + if (count > 0) { + primitiveIndex = this._createRenderItem(_subPrimitives, primitiveIndex, start, count, texture, blend); + count = 0; + } + + _clipper.clipEnd(); + + const lastLen = _subPrimitives.length; + const curLen = _subRenderItems.length; + for (let i = curLen; i < lastLen; i++) { + const item = _subPrimitives[i]; + subPrimitivePool.return(item); + } + + renderer._clearSubPrimitives(); + for (let i = 0, l = curLen; i < l; ++i) { + const item = _subRenderItems[i]; + const { blendMode, texture } = item; + renderer._addSubPrimitive(item.subPrimitive); + const material = renderer._getMaterial(texture.getImage(), blendMode as unknown as SpineBlendMode); + renderer.setMaterial(i, material); + } + + if (indicesLength > _vertexCount || renderer._needResizeBuffer) { + renderer._createAndBindBuffer(indicesLength); + renderer._needResizeBuffer = false; + this.buildPrimitive(skeleton, renderer); + return; + } + + if (renderer._vertexBuffer) { + renderer._vertexBuffer.setData(_vertices); + renderer._indexBuffer.setData(_indices); + } + } + + private _createRenderItem( + subPrimitives: SubPrimitive[], + primitiveIndex: number, + start: number, + count: number, + texture: SpineTexture, + blend: BlendMode + ): number { + const { subPrimitivePool, subRenderItemPool } = SpineGenerator; + const origin = subPrimitives[primitiveIndex]; + + if (origin) { + primitiveIndex++; + } + + const subPrimitive = origin || subPrimitivePool.get(); + subPrimitive.start = start; + subPrimitive.count = count; + + const renderItem = subRenderItemPool.get(); + renderItem.blendMode = blend; + renderItem.subPrimitive = subPrimitive; + renderItem.texture = texture; + + this._subRenderItems.push(renderItem); + + return primitiveIndex; + } + + private _expandBounds(x: number, y: number, z: number, localBounds: BoundingBox) { + const { min, max } = localBounds; + min.set(Math.min(min.x, x), Math.min(min.y, y), Math.min(min.z, z)); + max.set(Math.max(max.x, x), Math.max(max.y, y), Math.max(max.z, z)); + } +} diff --git a/packages/spine-core-4.2/src/SpineTexture.ts b/packages/spine-core-4.2/src/SpineTexture.ts new file mode 100644 index 0000000000..5297919e1b --- /dev/null +++ b/packages/spine-core-4.2/src/SpineTexture.ts @@ -0,0 +1,49 @@ +import { Texture, TextureFilter, TextureWrap } from "@esotericsoftware/spine-core"; +import { Texture2D, TextureFilterMode, TextureWrapMode } from "@galacean/engine"; + +/** + * @internal + */ +export class SpineTexture extends Texture { + constructor(image: Texture2D) { + super(image); + image.generateMipmaps(); + } + + // rewrite getImage function, return galacean texture2D, then attachment can get size of texture + override getImage(): Texture2D { + return this._image; + } + + setFilters(minFilter: TextureFilter, magFilter: TextureFilter) { + if (minFilter === TextureFilter.Nearest) { + this._image.filterMode = TextureFilterMode.Point; + } else if (minFilter === TextureFilter.Linear) { + this._image.filterMode = TextureFilterMode.Bilinear; + } else { + // The remaining min filters are the MipMap* family (mag filters are never MipMap*); + // mipmaps are generated in the constructor. + this._image.filterMode = TextureFilterMode.Trilinear; + } + } + + setWraps(uWrap: TextureWrap, vWrap: TextureWrap) { + this._image.wrapModeU = this._convertWrapMode(uWrap); + this._image.wrapModeV = this._convertWrapMode(vWrap); + } + + dispose() {} + + private _convertWrapMode(wrap: TextureWrap): TextureWrapMode { + switch (wrap) { + case TextureWrap.ClampToEdge: + return TextureWrapMode.Clamp; + case TextureWrap.Repeat: + return TextureWrapMode.Repeat; + case TextureWrap.MirroredRepeat: + return TextureWrapMode.Mirror; + default: + throw new Error("Unsupported texture wrap mode."); + } + } +} diff --git a/packages/spine-core-4.2/src/index.ts b/packages/spine-core-4.2/src/index.ts new file mode 100644 index 0000000000..8822f0a362 --- /dev/null +++ b/packages/spine-core-4.2/src/index.ts @@ -0,0 +1,15 @@ +import { registerSpineRuntime } from "@galacean/engine-spine"; +import { Spine42Runtime } from "./Spine42Runtime"; + +// Self-register on import: `import "@galacean/engine-spine-core-4.2"` wires the 4.2 backend +// into the shared `@galacean/engine-spine` package. +registerSpineRuntime(new Spine42Runtime()); + +export { Spine42Runtime }; + +// Re-export the spine 4.2 runtime API. Power users that construct spine-core objects directly +// (custom attachments, manual skeleton parsing) import them from this core package. +export * from "@esotericsoftware/spine-core"; + +export const version = `__buildVersion`; +console.log(`Galacean spine-core-4.2 version: ${version}`); diff --git a/packages/spine-core-4.2/src/util/ClearablePool.ts b/packages/spine-core-4.2/src/util/ClearablePool.ts new file mode 100644 index 0000000000..ff8d6ccc6a --- /dev/null +++ b/packages/spine-core-4.2/src/util/ClearablePool.ts @@ -0,0 +1,26 @@ +export class ClearablePool { + private _type: new () => T; + private _elements: T[]; + private _usedElementCount: number = 0; + + constructor(type: new () => T) { + this._type = type; + this._elements = []; + } + + get(): T { + const { _usedElementCount: usedElementCount, _elements: elements } = this; + this._usedElementCount++; + if (elements.length === usedElementCount) { + const element = new this._type(); + elements.push(element); + return element; + } else { + return elements[usedElementCount]; + } + } + + clear(): void { + this._usedElementCount = 0; + } +} diff --git a/packages/spine-core-4.2/src/util/ReturnablePool.ts b/packages/spine-core-4.2/src/util/ReturnablePool.ts new file mode 100644 index 0000000000..77c1bcaa47 --- /dev/null +++ b/packages/spine-core-4.2/src/util/ReturnablePool.ts @@ -0,0 +1,25 @@ +export class ReturnablePool { + private _type: new () => T; + private _elements: T[]; + private _lastElementIndex: number; + + constructor(type: new () => T, initializeCount: number = 1) { + this._type = type; + this._lastElementIndex = initializeCount - 1; + const elements = (this._elements = new Array(initializeCount)); + for (let i = 0; i < initializeCount; ++i) { + elements[i] = new type(); + } + } + + get(): T { + if (this._lastElementIndex < 0) { + return new this._type(); + } + return this._elements[this._lastElementIndex--]; + } + + return(element: T): void { + this._elements[++this._lastElementIndex] = element; + } +} diff --git a/packages/spine-core-4.2/tsconfig.json b/packages/spine-core-4.2/tsconfig.json new file mode 100644 index 0000000000..588b0055c3 --- /dev/null +++ b/packages/spine-core-4.2/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "declaration": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "declarationDir": "types", + "emitDeclarationOnly": true, + "noImplicitOverride": true, + "sourceMap": true, + "incremental": false, + "skipLibCheck": true, + "stripInternal": true + }, + "include": ["src/**/*"] +} diff --git a/packages/spine/package.json b/packages/spine/package.json new file mode 100644 index 0000000000..2b513502f3 --- /dev/null +++ b/packages/spine/package.json @@ -0,0 +1,37 @@ +{ + "name": "@galacean/engine-spine", + "version": "0.0.0-experimental-2.0-game.18", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "repository": { + "url": "https://github.com/galacean/engine.git" + }, + "license": "MIT", + "main": "dist/main.js", + "module": "dist/module.js", + "debug": "src/index.ts", + "browser": "dist/browser.js", + "types": "types/index.d.ts", + "scripts": { + "b:types": "tsc" + }, + "umd": { + "name": "Galacean.Spine", + "globals": { + "@galacean/engine": "Galacean" + } + }, + "files": [ + "dist/**/*", + "types/**/*" + ], + "devDependencies": { + "@esotericsoftware/spine-core": "~4.2.66", + "@galacean/engine": "workspace:*" + }, + "peerDependencies": { + "@galacean/engine": "workspace:*" + } +} diff --git a/packages/spine/src/SpineConstant.ts b/packages/spine/src/SpineConstant.ts new file mode 100644 index 0000000000..679d01d2e9 --- /dev/null +++ b/packages/spine/src/SpineConstant.ts @@ -0,0 +1,13 @@ +/** + * Vertex stride (float count per vertex) of the spine vertex layout owned by `SpineAnimationRenderer`. + * + * - `withoutTint`: [x, y, z, u, v, r, g, b, a] = 9 floats + * - `withTint`: [x, y, z, u, v, r, g, b, a, dr, dg, db] = 12 floats + * + * Shared between the renderer (which declares the vertex elements and allocates buffers) and the + * version-specific generator in a spine core package (which fills them), so the two never drift. + */ +export const SpineVertexStride = { + withoutTint: 9, + withTint: 12 +} as const; diff --git a/packages/spine/src/enums/SpineBlendMode.ts b/packages/spine/src/enums/SpineBlendMode.ts new file mode 100644 index 0000000000..509a2b60af --- /dev/null +++ b/packages/spine/src/enums/SpineBlendMode.ts @@ -0,0 +1,14 @@ +/** + * Blend mode of a spine slot. + * + * @remarks + * Version-neutral mirror of spine-core's `BlendMode` enum. The values match spine-core across all + * supported runtime versions (3.8 / 4.2), so a core package can pass its `BlendMode` straight through. + * Owning it here keeps the shared package free of any runtime spine-core dependency. + */ +export enum SpineBlendMode { + Normal = 0, + Additive = 1, + Multiply = 2, + Screen = 3 +} diff --git a/packages/spine/src/index.ts b/packages/spine/src/index.ts new file mode 100644 index 0000000000..938f79117d --- /dev/null +++ b/packages/spine/src/index.ts @@ -0,0 +1,21 @@ +import * as LoaderObject from "./loader"; +import * as RendererObject from "./renderer"; +import { Loader } from "@galacean/engine"; + +for (let key in RendererObject) { + Loader.registerClass(key, RendererObject[key]); +} +for (let key in LoaderObject) { + Loader.registerClass(key, LoaderObject[key]); +} + +export * from "./loader/index"; +export * from "./renderer/index"; +export { SpineBlendMode } from "./enums/SpineBlendMode"; +export { SpineVertexStride } from "./SpineConstant"; +export { registerSpineRuntime, getSpineRuntime } from "./runtime/SpineRuntimeRegistry"; +export type { ISpineRuntime } from "./runtime/ISpineRuntime"; +export type { ISpineRenderTarget } from "./runtime/ISpineRenderTarget"; + +export const version = `__buildVersion`; +console.log(`Galacean spine version: ${version}`); diff --git a/packages/spine/src/loader/LoaderUtils.ts b/packages/spine/src/loader/LoaderUtils.ts new file mode 100644 index 0000000000..9ad7721420 --- /dev/null +++ b/packages/spine/src/loader/LoaderUtils.ts @@ -0,0 +1,96 @@ +import type { SkeletonData, TextureAtlas } from "@esotericsoftware/spine-core"; +import { AssetPromise, AssetType, Engine, Texture2D } from "@galacean/engine"; +import { getSpineRuntime } from "../runtime/SpineRuntimeRegistry"; + +/** + * @internal + */ +export class LoaderUtils { + static createSkeletonData( + skeletonRawData: string | ArrayBuffer, + textureAtlas: TextureAtlas, + skeletonDataScale: number + ): SkeletonData { + return getSpineRuntime().createSkeletonData(skeletonRawData, textureAtlas, skeletonDataScale); + } + + static createTextureAtlas(atlasText: string, textures: Texture2D[]): TextureAtlas { + return getSpineRuntime().createTextureAtlas(atlasText, textures); + } + + static loadTexturesByPaths( + imagePaths: string[], + imageExtensions: string[], + engine: Engine, + reject: (reason?: any) => void + ): Promise { + const resourceManager = engine.resourceManager; + // @ts-ignore + const virtualPathResourceMap = resourceManager._virtualPathResourceMap; + const texturePromises: AssetPromise[] = imagePaths.map((imagePath, index) => { + const ext = imageExtensions[index]; + let imageLoaderType = AssetType.Texture2D; + const virtualElement = virtualPathResourceMap[imagePath]; + if (virtualElement) { + imageLoaderType = virtualElement.type; + } else if (ext === "ktx") { + imageLoaderType = AssetType.KTX; + } else if (ext === "ktx2") { + imageLoaderType = AssetType.KTX2; + } + return resourceManager.load({ + url: imagePath, + type: imageLoaderType + }); + }); + return Promise.all(texturePromises).catch((error) => { + reject(error); + return []; + }); + } + + static loadTextureAtlas(atlasPath: string, engine: Engine, reject: (reason?: any) => void): Promise { + const baseUrl = LoaderUtils.getBaseUrl(atlasPath); + const resourceManager = engine.resourceManager; + let atlasText: string; + return ( + resourceManager + // @ts-ignore + ._request(atlasPath, { type: "text" }) + .then((text: string) => { + atlasText = text; + const runtime = getSpineRuntime(); + const pageNames = runtime.parseAtlasPageNames(atlasText); + const loadTexturePromises = pageNames.map((name) => { + const textureUrl = baseUrl + name; + return resourceManager.load({ + url: textureUrl, + type: AssetType.Texture2D + }) as unknown as Promise; + }); + return Promise.all(loadTexturePromises).then((textures) => { + return runtime.createTextureAtlas(atlasText, textures); + }); + }) + .catch((err) => { + reject(new Error(`Spine Atlas: ${atlasPath} load error: ${err}`)); + return null; + }) + ); + } + + static getBaseUrl(url: string): string { + const isLocalPath = !/^(http|https|ftp):\/\/.*/i.test(url); + if (isLocalPath) { + const lastSlashIndex = url.lastIndexOf("/"); + if (lastSlashIndex === -1) { + return ""; + } + return url.substring(0, lastSlashIndex + 1); + } else { + const parsedUrl = new URL(url); + const basePath = parsedUrl.origin + parsedUrl.pathname; + return basePath.endsWith("/") ? basePath : basePath.substring(0, basePath.lastIndexOf("/") + 1); + } + } +} diff --git a/packages/spine/src/loader/SpineAtlasLoader.ts b/packages/spine/src/loader/SpineAtlasLoader.ts new file mode 100644 index 0000000000..922f0fb7b0 --- /dev/null +++ b/packages/spine/src/loader/SpineAtlasLoader.ts @@ -0,0 +1,107 @@ +import type { TextureAtlas } from "@esotericsoftware/spine-core"; +import { AssetPromise, Loader, LoadItem, resourceLoader, ResourceManager } from "@galacean/engine"; +import { LoaderUtils } from "./LoaderUtils"; +import { SpineLoader } from "./SpineLoader"; + +interface SpineAtlasAsset { + atlasPath: string; + imagePaths?: string[]; + imageExtensions?: string[]; +} + +@resourceLoader("SpineAtlas", ["atlas"], false) +export class SpineAtlasLoader extends Loader { + private static _groupAssetsByExtension(url: string, assetPath: SpineAtlasAsset, resourceManager: ResourceManager) { + let ext = SpineLoader._getUrlExtension(url); + if (!ext) return; + + if (ext === "atlas") { + assetPath.atlasPath = url; + } + if (["png", "jpg", "webp", "jpeg", "ktx", "ktx2"].includes(ext)) { + assetPath.imagePaths.push(url); + } + } + + private static _assignAssetPathsFromUrl(url: string, assetPath: SpineAtlasAsset, resourceManager: ResourceManager) { + const ext = SpineLoader._getUrlExtension(url); + if (ext === "atlas") { + assetPath.atlasPath = url; + // @ts-ignore + const atlasDependency = resourceManager?._virtualPathResourceMap?.[url]?.dependentAssetMap; + if (atlasDependency) { + for (let key in atlasDependency) { + const imageVirtualPath = atlasDependency[key]; + assetPath.imagePaths.push(imageVirtualPath); + } + } + } + } + + load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { + return new AssetPromise((resolve, reject) => { + const engine = resourceManager.engine; + const spineAtlasAsset = { + atlasPath: "", + imagePaths: [], + imageExtensions: [] + }; + + if (!item.urls) { + SpineAtlasLoader._assignAssetPathsFromUrl(item.url, spineAtlasAsset, resourceManager); + } else { + const urls = item.urls; + for (let i = 0, len = urls.length; i < len; i += 1) { + const url = urls[i]; + SpineAtlasLoader._groupAssetsByExtension(url, spineAtlasAsset, resourceManager); + } + } + + const { atlasPath } = spineAtlasAsset; + if (!atlasPath) { + reject( + new Error("Failed to load spine atlas. Please check the file path and ensure the file extension is included.") + ); + return; + } + + const imagePaths = spineAtlasAsset.imagePaths; + if (imagePaths.length === 0) { + // Use the parsed atlas path: `item.url` is undefined when the asset came in via `item.urls`. + LoaderUtils.loadTextureAtlas(atlasPath, engine, reject) + .then((textureAtlas) => { + resolve(textureAtlas); + }) + .catch((err) => { + reject(err); + }); + } else { + const { atlasPath, imagePaths, imageExtensions } = spineAtlasAsset; + if (imagePaths.length > 0) { + Promise.all([ + // @ts-ignore + resourceManager._request(atlasPath, { + type: "text" + }) as Promise, + LoaderUtils.loadTexturesByPaths(imagePaths, imageExtensions, engine, reject) + ]) + .then(([atlasText, textures]) => { + const textureAtlas = LoaderUtils.createTextureAtlas(atlasText, textures); + resolve(textureAtlas); + }) + .catch((err) => { + reject(err); + }); + } else { + LoaderUtils.loadTextureAtlas(atlasPath, engine, reject) + .then((textureAtlas) => { + resolve(textureAtlas); + }) + .catch((err) => { + reject(err); + }); + } + } + }); + } +} diff --git a/packages/spine/src/loader/SpineLoader.ts b/packages/spine/src/loader/SpineLoader.ts new file mode 100644 index 0000000000..41d613e822 --- /dev/null +++ b/packages/spine/src/loader/SpineLoader.ts @@ -0,0 +1,144 @@ +import type { TextureAtlas } from "@esotericsoftware/spine-core"; +import { AssetPromise, Loader, LoadItem, resourceLoader, ResourceManager } from "@galacean/engine"; +import { LoaderUtils } from "./LoaderUtils"; +import { SpineResource } from "./SpineResource"; + +type SpineAssetPath = { + atlasPath: string; + skeletonPath: string; + extraPaths: string[]; + fileExtensions?: string | string[]; +}; + +type SpineLoadContext = { + fileName: string; + spineAssetPath: SpineAssetPath; + skeletonRawData: string | ArrayBuffer; +}; + +export type SpineLoaderParams = { + fileExtensions?: string | string[]; +}; + +// Registering "json" makes this loader the default for bare `.json` urls (extension mappings are +// last-registration-wins, overriding the core JSONLoader) — matches the historical engine-spine +// behavior. Pass an explicit `type` (e.g. AssetType.JSON) to load plain JSON once this package +// is imported. +@resourceLoader("Spine", ["json", "bin", "skel"]) +export class SpineLoader extends Loader { + private static _decoder = new TextDecoder("utf-8"); + + private static _groupAssetsByExtension(url: string, assetPath: SpineAssetPath) { + const ext = SpineLoader._getUrlExtension(url); + if (!ext) return; + + if (["skel", "json", "bin"].includes(ext)) { + assetPath.skeletonPath = url; + } else if (ext === "atlas") { + assetPath.atlasPath = url; + } else { + assetPath.extraPaths.push(url); + } + } + + private static _assignAssetPathsFromUrl(url: string, assetPath: SpineAssetPath, resourceManager: ResourceManager) { + const ext = SpineLoader._getUrlExtension(url); + if (!ext) return; + assetPath.skeletonPath = url; + + // @ts-ignore + const skeletonDependency = resourceManager?._virtualPathResourceMap?.[url]?.dependentAssetMap; + if (skeletonDependency) { + assetPath.atlasPath = skeletonDependency.atlas; + } else { + const extensionPattern: RegExp = /\.(json|bin|skel)$/; + let baseUrl: string; + if (extensionPattern.test(url)) { + baseUrl = url.replace(extensionPattern, ""); + } + if (baseUrl) { + const atlasUrl = baseUrl + ".atlas"; + assetPath.atlasPath = atlasUrl; + } + } + } + + static _getUrlExtension(url: string): string | null { + const regex = /\.(\w+)(\?|$)/; + const match = url.match(regex); + return match ? match[1] : null; + } + + load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { + return new AssetPromise((resolve, reject) => { + const spineLoadContext: SpineLoadContext = { + fileName: "", + skeletonRawData: "", + spineAssetPath: { + atlasPath: null, + skeletonPath: null, + extraPaths: [] + } + }; + const { spineAssetPath } = spineLoadContext; + if (!item.urls) { + SpineLoader._assignAssetPathsFromUrl(item.url, spineAssetPath, resourceManager); + } else { + const urls = item.urls; + for (let i = 0, len = urls.length; i < len; i += 1) { + const url = urls[i]; + SpineLoader._groupAssetsByExtension(url, spineAssetPath); + } + } + + const { skeletonPath, atlasPath } = spineAssetPath; + if (!skeletonPath || !atlasPath) { + reject( + new Error( + "Failed to load spine assets. Please check the file path and ensure the file extension is included." + ) + ); + return; + } + + resourceManager + // @ts-ignore + ._request(skeletonPath, { type: "arraybuffer" }) + .then((skeletonRawData: ArrayBuffer) => { + spineLoadContext.skeletonRawData = skeletonRawData; + const skeletonString = SpineLoader._decoder.decode(skeletonRawData); + if (skeletonString.startsWith("{")) { + spineLoadContext.skeletonRawData = skeletonString; + } + return this._loadAndCreateSpineResource(spineLoadContext, resourceManager); + }) + .then(resolve) + .catch(reject); + }); + } + + private _loadAndCreateSpineResource( + spineLoadContext: SpineLoadContext, + resourceManager: ResourceManager + ): Promise { + const { engine } = resourceManager; + const { skeletonRawData, spineAssetPath } = spineLoadContext; + const { skeletonPath, atlasPath, extraPaths } = spineAssetPath; + + const atlasLoadPromise = + extraPaths.length === 0 + ? (resourceManager.load({ + url: atlasPath, + type: "SpineAtlas" + }) as unknown as Promise) + : (resourceManager.load({ + urls: [atlasPath].concat(extraPaths), + type: "SpineAtlas" + }) as unknown as Promise); + + return atlasLoadPromise.then((textureAtlas) => { + const skeletonData = LoaderUtils.createSkeletonData(skeletonRawData, textureAtlas, 0.01); + return new SpineResource(engine, skeletonData, skeletonPath); + }); + } +} diff --git a/packages/spine/src/loader/SpineResource.ts b/packages/spine/src/loader/SpineResource.ts new file mode 100644 index 0000000000..a1532ad606 --- /dev/null +++ b/packages/spine/src/loader/SpineResource.ts @@ -0,0 +1,112 @@ +import type { + AnimationState, + AnimationStateData, + MeshAttachment, + RegionAttachment, + Skeleton, + SkeletonData +} from "@esotericsoftware/spine-core"; +import { Engine, Entity, ReferResource, Texture2D } from "@galacean/engine"; +import { SpineAnimationRenderer } from "../renderer/SpineAnimationRenderer"; +import { getSpineRuntime } from "../runtime/SpineRuntimeRegistry"; + +/** + * Represents a resource that manages Spine animation data, textures, and entity templates for the Galacean engine. + * + */ +export class SpineResource extends ReferResource { + /** The url of spine resource. */ + readonly url: string; + + private _texturesInSpineAtlas: Texture2D[] = []; + private _skeletonData: SkeletonData; + private _stateData: AnimationStateData; + + private _template: Entity; + + constructor(engine: Engine, skeletonData: SkeletonData, url?: string) { + super(engine); + this.url = url; + this._skeletonData = skeletonData; + this._stateData = getSpineRuntime().createAnimationStateData(skeletonData); + this._associationTextureInSkeletonData(skeletonData); + this._createTemplate(); + } + + /** + * The skeleton data associated with this Spine resource. + */ + get skeletonData(): SkeletonData { + return this._skeletonData; + } + + /** + * The animation state data associated with this Spine resource. + */ + get stateData(): AnimationStateData { + return this._stateData; + } + + /** + * Creates and returns a new instance of the spine entity template. + * @returns A instance of the spine entity template + */ + instantiate(): Entity { + return this._template.clone(); + } + + protected override _onDestroy(): void { + super._onDestroy(); + this._disassociationSuperResource(); + this._skeletonData = null; + this._stateData = null; + } + + private _createTemplate(): void { + const name = this._extractFileName(this.url); + const spineEntity = new Entity(this.engine, name); + const spineAnimationRenderer = spineEntity.addComponent(SpineAnimationRenderer); + const runtime = getSpineRuntime(); + const skeleton = runtime.createSkeleton(this._skeletonData); + const state = runtime.createAnimationState(this._stateData); + spineAnimationRenderer._setSkeleton(skeleton); + spineAnimationRenderer._setState(state); + // @ts-ignore + spineEntity._markAsTemplate(this); + this._template = spineEntity; + } + + private _associationTextureInSkeletonData(skeletonData: SkeletonData): void { + const { skins, slots } = skeletonData; + const textures = this._texturesInSpineAtlas; + + for (let i = 0, n = skins.length; i < n; i++) { + for (let j = 0, m = slots.length; j < m; j++) { + const slot = slots[j]; + const attachment = skins[i].getAttachment(slot.index, slot.name); + const texture = (attachment)?.region?.texture.getImage(); + if (texture) { + if (!textures.includes(texture)) { + textures.push(texture); + // @ts-ignore + texture._associationSuperResource(this); + } + } + } + } + } + + private _disassociationSuperResource(): void { + const textures = this._texturesInSpineAtlas; + for (let i = 0, n = textures.length; i < n; i++) { + // @ts-ignore + textures[i]._disassociationSuperResource(this); + } + } + + private _extractFileName(url: string): string { + if (!url) return "Spine Entity"; + const match = url.match(/\/([^\/]+?)(\.[^\/]*)?$/); + return match ? match[1] : "Spine Entity"; + } +} diff --git a/packages/spine/src/loader/index.ts b/packages/spine/src/loader/index.ts new file mode 100644 index 0000000000..b7367ba692 --- /dev/null +++ b/packages/spine/src/loader/index.ts @@ -0,0 +1,5 @@ +import "./SpineLoader"; +import "./SpineAtlasLoader"; + +export { SpineResource } from "./SpineResource"; +export { LoaderUtils } from "./LoaderUtils"; diff --git a/packages/spine/src/renderer/SpineAnimationRenderer.ts b/packages/spine/src/renderer/SpineAnimationRenderer.ts new file mode 100644 index 0000000000..2be54a129c --- /dev/null +++ b/packages/spine/src/renderer/SpineAnimationRenderer.ts @@ -0,0 +1,422 @@ +import type { AnimationState, Skeleton } from "@esotericsoftware/spine-core"; +import { + assignmentClone, + BoundingBox, + Buffer, + BufferBindFlag, + BufferUsage, + deepClone, + Entity, + ignoreClone, + IndexBufferBinding, + IndexFormat, + Material, + Primitive, + Renderer, + SubPrimitive, + Texture2D, + Vector3, + VertexBufferBinding, + VertexElement, + VertexElementFormat +} from "@galacean/engine"; +import { SpineResource } from "../loader/SpineResource"; +import { SpineMaterial } from "./SpineMaterial"; +import { SpineBlendMode } from "../enums/SpineBlendMode"; +import { SpineVertexStride } from "../SpineConstant"; +import { getSpineRuntime } from "../runtime/SpineRuntimeRegistry"; +import type { ISpineRenderTarget } from "../runtime/ISpineRenderTarget"; + +/** + * Spine animation renderer, capable of rendering spine animations and providing functions for animation and skeleton manipulation. + */ +export class SpineAnimationRenderer extends Renderer implements ISpineRenderTarget { + private static _positionVertexElement = new VertexElement("POSITION", 0, VertexElementFormat.Vector3, 0); + private static _lightColorVertexElement = new VertexElement("LIGHT_COLOR", 12, VertexElementFormat.Vector4, 0); + private static _uvVertexElement = new VertexElement("TEXCOORD_0", 28, VertexElementFormat.Vector2, 0); + private static _darkColorVertexElement = new VertexElement("DARK_COLOR", 36, VertexElementFormat.Vector3, 0); + + /** @internal */ + static _materialCacheMap = new Map(); + + /** + * The spacing between z layers in world units. + */ + @assignmentClone + zSpacing = 0.001; + + /** + * Whether to use premultiplied alpha mode for rendering. + * When enabled, vertex color values are multiplied by the alpha channel. + * @remarks + If this option is enabled, the Spine editor must export textures with "Premultiply Alpha" checked. + */ + @assignmentClone + premultipliedAlpha = false; + + @assignmentClone + private _tintBlack = false; + + /** + * Whether to enable tint black feature for dark color tinting. + * + * @remarks Should be enabled when using "Tint Black" feature in Spine editor. + */ + get tintBlack(): boolean { + return this._tintBlack; + } + + set tintBlack(value: boolean) { + if (this._tintBlack !== value) { + this._tintBlack = value; + this._needResizeBuffer = true; + } + } + + /** + * Default state for spine animation. + * Contains the default animation name to be played, whether this animation should loop, the default skin name. + */ + @deepClone + readonly defaultConfig: SpineAnimationDefaultConfig = new SpineAnimationDefaultConfig(); + + /** @internal */ + @ignoreClone + _primitive: Primitive; + /** @internal */ + @ignoreClone + _subPrimitives: SubPrimitive[] = []; + /** @internal */ + @ignoreClone + _indexBuffer: Buffer; + /** @internal */ + @ignoreClone + _vertexBuffer: Buffer; + /** @internal */ + @ignoreClone + _vertices = new Float32Array(); + /** @internal */ + @ignoreClone + _indices = new Uint16Array(); + /** @internal */ + @ignoreClone + _needResizeBuffer = false; + /** @internal */ + @ignoreClone + _vertexCount = 0; + /** @internal */ + @ignoreClone + _resource: SpineResource; + /** @internal */ + @ignoreClone + _localBounds = new BoundingBox( + new Vector3(Infinity, Infinity, Infinity), + new Vector3(-Infinity, -Infinity, -Infinity) + ); + + @ignoreClone + private _skeleton: Skeleton; + @ignoreClone + private _state: AnimationState; + + /** + * The Spine.AnimationState object of this SpineAnimationRenderer. + * Manage, blend, and transition between multiple simultaneous animations effectively. + */ + get state(): AnimationState { + return this._state; + } + + /** + * The Spine.Skeleton object of this SpineAnimationRenderer. + * Manipulate bone positions, rotations, scaling + * and change spine attachment to customize character appearances dynamically during runtime. + */ + get skeleton(): Skeleton { + return this._skeleton; + } + + /** + * @internal + */ + constructor(entity: Entity) { + super(entity); + const primitive = new Primitive(this.engine); + this._primitive = primitive; + this._primitive._addReferCount(1); + primitive.addVertexElement(SpineAnimationRenderer._positionVertexElement); + primitive.addVertexElement(SpineAnimationRenderer._lightColorVertexElement); + primitive.addVertexElement(SpineAnimationRenderer._uvVertexElement); + primitive.addVertexElement(SpineAnimationRenderer._darkColorVertexElement); + } + + /** + * @internal + */ + // @ts-ignore + override _onEnable(): void { + this._applyDefaultConfig(); + } + + /** + * @internal + */ + override update(delta: number): void { + const { _state: state, _skeleton: skeleton } = this; + if (!state || !skeleton) return; + const runtime = getSpineRuntime(); + runtime.updateState(skeleton, state, delta); + runtime.buildPrimitive(skeleton, this); + this._dirtyUpdateFlag |= RendererUpdateFlags.WorldVolume; + } + + /** + * @internal + */ + // @ts-ignore + override _render(context: any): void { + const { _primitive, _subPrimitives, _materials: materials } = this; + if (!_subPrimitives) return; + // Engine 2.0 render-element model: one RenderElement per sub-primitive (no sub-element pool). + const engine = this.engine as any; + const priority = this.priority; + const distanceForSort = (this as any)._distanceForSort; + const renderElementPool = engine._renderElementPool; + const renderPipeline = context.camera._renderPipeline; + for (let i = 0, n = _subPrimitives.length; i < n; i++) { + let material = materials[i]; + if (!material) { + continue; + } + if (material.destroyed || material.shader.destroyed) { + material = engine._basicResources.meshMagentaMaterial; + } + const renderElement = renderElementPool.get(); + renderElement.set(this, material, _primitive, _subPrimitives[i]); + renderElement.priority = priority; + renderElement.distanceForSort = distanceForSort; + renderPipeline.pushRenderElement(context, renderElement); + } + } + + /** + * @internal + */ + // @ts-ignore + override _updateBounds(worldBounds: BoundingBox): void { + BoundingBox.transform(this._localBounds, this.entity.transform.worldMatrix, worldBounds); + } + + /** + * @internal + */ + _setSkeleton(skeleton: Skeleton) { + this._skeleton = skeleton; + } + + /** + * @internal + */ + _setState(state: AnimationState) { + this._state = state; + } + + /** + * @internal + */ + // @ts-ignore + override _cloneTo(target: SpineAnimationRenderer): void { + // A renderer added manually and never bound to a resource has no skeleton/state to clone. + if (!this._skeleton || !this._state) return; + const runtime = getSpineRuntime(); + // Clones share the source's immutable SkeletonData and AnimationStateData (mix config), + // so mix times configured on the resource propagate to every instance; only the + // per-instance Skeleton / AnimationState are fresh. + const skeleton = runtime.createSkeleton(this._skeleton.data); + const state = runtime.createAnimationState(this._state.data); + target._setSkeleton(skeleton); + target._setState(state); + } + + /** + * @internal + */ + override _onDestroy(): void { + this._clearMaterialCache(); + this._subPrimitives.length = 0; + const primitive = this._primitive; + if (primitive) { + primitive._addReferCount(-1); + primitive.destroy(); + this._primitive = null; + } + // destroy() is refCount-guarded; the primitive released its buffer references above. + this._vertexBuffer?.destroy(); + this._indexBuffer?.destroy(); + this._vertexBuffer = null; + this._indexBuffer = null; + this._resource = null; + this._skeleton = null; + this._state = null; + super._onDestroy(); + } + + /** + * @internal + */ + _createAndBindBuffer(vertexCount: number): void { + const { engine: _engine, _primitive } = this; + const oldVertexBuffer = this._vertexBuffer; + const oldIndexBuffer = this._indexBuffer; + this._vertexCount = vertexCount; + const stride = this.tintBlack ? SpineVertexStride.withTint : SpineVertexStride.withoutTint; + this._vertices = new Float32Array(vertexCount * stride); + this._indices = new Uint16Array(vertexCount); + const vertexStride = stride << 2; + const vertexBuffer = new Buffer(_engine, BufferBindFlag.VertexBuffer, this._vertices, BufferUsage.Dynamic); + const indexBuffer = new Buffer(_engine, BufferBindFlag.IndexBuffer, this._indices, BufferUsage.Dynamic); + this._indexBuffer = indexBuffer; + this._vertexBuffer = vertexBuffer; + const vertexBufferBinding = new VertexBufferBinding(vertexBuffer, vertexStride); + this._primitive.setVertexBufferBinding(0, vertexBufferBinding); + const indexBufferBinding = new IndexBufferBinding(indexBuffer, IndexFormat.UInt16); + _primitive.setIndexBufferBinding(indexBufferBinding); + // Rebinding released the primitive's references to the old buffers; destroy() is + // refCount-guarded, so this frees their GPU resources without waiting for gc(). + oldVertexBuffer?.destroy(); + oldIndexBuffer?.destroy(); + } + + /** + * @internal + */ + _addSubPrimitive(subPrimitive: SubPrimitive): void { + this._subPrimitives.push(subPrimitive); + } + + /** + * @internal + */ + _clearSubPrimitives(): void { + this._subPrimitives.length = 0; + } + + /** + * @internal + */ + _getMaterial(texture: Texture2D, blendMode: SpineBlendMode): Material { + const engine = this.engine; + const premultipliedAlpha = this.premultipliedAlpha; + const tintBlack = this.tintBlack; + + // tintBlack must be part of the key: it toggles a per-material macro, so renderers that + // differ only in tintBlack cannot share a material. + const key = `${texture.instanceId}_${blendMode}_${premultipliedAlpha ? 1 : 0}_${tintBlack ? 1 : 0}`; + let cached = SpineAnimationRenderer._materialCacheMap.get(key); + if (!cached) { + cached = new SpineMaterial(engine); + cached.isGCIgnored = true; + cached._cacheKey = key; + SpineAnimationRenderer._materialCacheMap.set(key, cached); + } + cached._setBlendMode(blendMode, premultipliedAlpha); + cached._setTexture(texture); + cached._setTintBlack(tintBlack); + cached._setPremultipliedAlpha(premultipliedAlpha); + return cached; + } + + private _clearMaterialCache(): void { + const materialCache = SpineAnimationRenderer._materialCacheMap; + const materials = this._materials; + for (let i = 0, len = materials.length; i < len; i += 1) { + const material = materials[i]; + // `setMaterial` is public API, so entries may be user materials or null holes; a cached + // SpineMaterial is removed by the exact key it was registered under (recomputing the key + // from the renderer's current state would miss materials cached under older settings). + if (material instanceof SpineMaterial) { + materialCache.delete(material._cacheKey); + } + } + } + + private _applyDefaultConfig(): void { + const { skeleton, state } = this; + if (skeleton && state) { + const { animationName, skinName, loop } = this.defaultConfig; + if (skinName !== "default") { + skeleton.setSkinByName(skinName); + skeleton.setToSetupPose(); + } + if (animationName) { + state.setAnimation(0, animationName, loop); + } + } + } + + /** + * * @deprecated This property is deprecated and will be removed in future releases. + * Spine resource of current spine animation. + */ + get resource(): SpineResource { + return this._resource; + } + + /** + * * @deprecated This property is deprecated and will be removed in future releases. + * Sets the Spine resource for the current animation. This property allows switching to a different `SpineResource`. + * + * @param value - The new `SpineResource` to be used for the current animation + */ + set resource(value: SpineResource) { + if (!value) { + this._state = null; + this._skeleton = null; + this._resource = null; + return; + } + this._resource = value; + const { skeletonData, stateData } = value; + const runtime = getSpineRuntime(); + const skeleton = runtime.createSkeleton(skeletonData); + const state = runtime.createAnimationState(stateData); + this._setSkeleton(skeleton); + this._setState(state); + this._applyDefaultConfig(); + } +} + +/** + * @internal + */ +export enum RendererUpdateFlags { + /** Include world position and world bounds. */ + WorldVolume = 0x1 +} + +/** + * Default state for spine animation. + * Contains the default animation name to be played, whether this animation should loop, + * the default skin name, and the default scale of the skeleton. + */ +export class SpineAnimationDefaultConfig { + /** + * Creates an instance of default config + */ + constructor( + /** + * Whether the default animation should loop @defaultValue `true. The default animation should loop` + */ + public loop: boolean = true, + + /** + * The name of the default animation @defaultValue `null. Do not play any animation by default` + */ + public animationName: string | null = null, + + /** + * The name of the default skin @defaultValue `default` + */ + public skinName: string = "default" + ) {} +} diff --git a/packages/spine/src/renderer/SpineMaterial.ts b/packages/spine/src/renderer/SpineMaterial.ts new file mode 100644 index 0000000000..bf4bec3b97 --- /dev/null +++ b/packages/spine/src/renderer/SpineMaterial.ts @@ -0,0 +1,106 @@ +import { BlendFactor, Engine, Material, Shader, ShaderProperty, Texture2D } from "@galacean/engine"; +import { SpineBlendMode } from "../enums/SpineBlendMode"; + +const { SourceAlpha, One, DestinationColor, OneMinusSourceColor, OneMinusSourceAlpha } = BlendFactor; + +/** + * Material for spine rendering. + * + * @remarks + * The shader lives as a built-in engine shader (`2D/Spine`, registered by the engine's + * `ShaderPool`). Per-material blend factors are driven through shader data properties + * (`sourceColorBlendFactor` etc.), which the `2D/Spine` shader binds into its `BlendState`. + * The constant render state (transparent queue, depth-write off, cull off) is declared in + * the shader, so this material only varies the blend factors per blend mode. + */ +export class SpineMaterial extends Material { + private static _sourceColorBlendFactorProp = ShaderProperty.getByName("sourceColorBlendFactor"); + private static _destinationColorBlendFactorProp = ShaderProperty.getByName("destinationColorBlendFactor"); + private static _sourceAlphaBlendFactorProp = ShaderProperty.getByName("sourceAlphaBlendFactor"); + private static _destinationAlphaBlendFactorProp = ShaderProperty.getByName("destinationAlphaBlendFactor"); + + /** + * @internal + * The key this material is registered under in `SpineAnimationRenderer._materialCacheMap`. + */ + _cacheKey: string; + + private _blendMode: SpineBlendMode = SpineBlendMode.Normal; + + /** + * @internal + */ + _setTintBlack(enabled: boolean) { + if (enabled) { + this.shaderData.enableMacro("RENDERER_TINT_BLACK"); + } else { + this.shaderData.disableMacro("RENDERER_TINT_BLACK"); + } + } + + /** + * @internal + */ + _setPremultipliedAlpha(enabled: boolean) { + this.shaderData.setFloat("renderer_PremultipliedAlpha", enabled ? 1 : 0); + } + + /** + * @internal + */ + _setTexture(value: Texture2D) { + this.shaderData.setTexture("material_SpineTexture", value); + } + + constructor(engine: Engine) { + super(engine, Shader.find("2D/Spine")); + this._setBlendMode(SpineBlendMode.Normal, false); + } + + /** + * @internal + */ + _setBlendMode(blendMode: SpineBlendMode, premultipliedAlpha: boolean) { + const { shaderData } = this; + const { + _sourceColorBlendFactorProp: srcColor, + _destinationColorBlendFactorProp: dstColor, + _sourceAlphaBlendFactorProp: srcAlpha, + _destinationAlphaBlendFactorProp: dstAlpha + } = SpineMaterial; + this._blendMode = blendMode; + switch (blendMode) { + case SpineBlendMode.Additive: + shaderData.setInt(srcColor, premultipliedAlpha ? One : SourceAlpha); + shaderData.setInt(dstColor, One); + shaderData.setInt(srcAlpha, One); + shaderData.setInt(dstAlpha, One); + break; + case SpineBlendMode.Multiply: + shaderData.setInt(srcColor, DestinationColor); + shaderData.setInt(dstColor, OneMinusSourceAlpha); + shaderData.setInt(srcAlpha, One); + shaderData.setInt(dstAlpha, OneMinusSourceAlpha); + break; + case SpineBlendMode.Screen: + shaderData.setInt(srcColor, One); + shaderData.setInt(dstColor, OneMinusSourceColor); + shaderData.setInt(srcAlpha, One); + shaderData.setInt(dstAlpha, OneMinusSourceColor); + break; + default: // Normal + shaderData.setInt(srcColor, premultipliedAlpha ? One : SourceAlpha); + shaderData.setInt(dstColor, OneMinusSourceAlpha); + shaderData.setInt(srcAlpha, One); + shaderData.setInt(dstAlpha, OneMinusSourceAlpha); + break; + } + } + + /** + * @internal + */ + _getBlendMode(): SpineBlendMode { + return this._blendMode; + } +} diff --git a/packages/spine/src/renderer/index.ts b/packages/spine/src/renderer/index.ts new file mode 100644 index 0000000000..aca750bda7 --- /dev/null +++ b/packages/spine/src/renderer/index.ts @@ -0,0 +1,2 @@ +export { SpineAnimationRenderer } from "./SpineAnimationRenderer"; +export { SpineMaterial } from "./SpineMaterial"; diff --git a/packages/spine/src/runtime/ISpineRenderTarget.ts b/packages/spine/src/runtime/ISpineRenderTarget.ts new file mode 100644 index 0000000000..50133ae9f8 --- /dev/null +++ b/packages/spine/src/runtime/ISpineRenderTarget.ts @@ -0,0 +1,30 @@ +import type { BoundingBox, Buffer, Material, SubPrimitive, Texture2D } from "@galacean/engine"; +import type { SpineBlendMode } from "../enums/SpineBlendMode"; + +/** + * The write target a spine core package's generator fills while building the renderable primitive. + * + * @remarks + * Implemented by `SpineAnimationRenderer`. This is the narrow, spine-core-free contract that lets the + * version-specific generator live in a core package while the buffer/material/bounds bookkeeping stays + * in the shared renderer — the analogue of how engine physics colliders expose a native handle. + */ +export interface ISpineRenderTarget { + readonly _vertices: Float32Array; + readonly _indices: Uint16Array; + readonly _vertexCount: number; + readonly _localBounds: BoundingBox; + readonly _subPrimitives: SubPrimitive[]; + readonly _vertexBuffer: Buffer; + readonly _indexBuffer: Buffer; + readonly zSpacing: number; + readonly premultipliedAlpha: boolean; + readonly tintBlack: boolean; + _needResizeBuffer: boolean; + + _createAndBindBuffer(vertexCount: number): void; + _addSubPrimitive(subPrimitive: SubPrimitive): void; + _clearSubPrimitives(): void; + _getMaterial(texture: Texture2D, blendMode: SpineBlendMode): Material; + setMaterial(index: number, material: Material): void; +} diff --git a/packages/spine/src/runtime/ISpineRuntime.ts b/packages/spine/src/runtime/ISpineRuntime.ts new file mode 100644 index 0000000000..8f3d4202ea --- /dev/null +++ b/packages/spine/src/runtime/ISpineRuntime.ts @@ -0,0 +1,52 @@ +import type { + AnimationState, + AnimationStateData, + Skeleton, + SkeletonData, + TextureAtlas +} from "@esotericsoftware/spine-core"; +import type { Texture2D } from "@galacean/engine"; +import type { ISpineRenderTarget } from "./ISpineRenderTarget"; + +/** + * The version-specific spine runtime backend. + * + * @remarks + * The engine-spine analogue of `IPhysics`: a narrow factory + stepping interface that a spine core + * package (e.g. `@galacean/engine-spine-core-4.2`) implements and registers via {@link registerSpineRuntime}. + * It owns everything that differs between spine runtime versions — parsing, object-model construction, + * world-transform stepping (e.g. spine 4.2's `Physics` argument), and the attachment-reading mesh + * generator — so the shared renderer/loader never reference a concrete spine-core version at runtime. + * + * The spine-core types in these signatures are the 4.2 line, used as the canonical type contract; a + * 3.8 backend returns structurally-3.8 objects typed against the same contract. + */ +export interface ISpineRuntime { + /** Parse the page names declared in an atlas text, used to resolve which page textures to load. */ + parseAtlasPageNames(atlasText: string): string[]; + + /** Build a `TextureAtlas`, binding the given engine textures to its pages (matched by page name). */ + createTextureAtlas(atlasText: string, textures: Texture2D[]): TextureAtlas; + + /** Parse skeleton data (json or binary) against an atlas. */ + createSkeletonData( + skeletonRawData: string | ArrayBuffer, + textureAtlas: TextureAtlas, + skeletonDataScale: number + ): SkeletonData; + + /** Create the shared animation state data for a skeleton data. */ + createAnimationStateData(skeletonData: SkeletonData): AnimationStateData; + + /** Create a skeleton instance. */ + createSkeleton(skeletonData: SkeletonData): Skeleton; + + /** Create an animation state instance. */ + createAnimationState(stateData: AnimationStateData): AnimationState; + + /** Advance the animation state and apply it to the skeleton, then update world transforms. */ + updateState(skeleton: Skeleton, state: AnimationState, delta: number): void; + + /** Build the renderable primitive for the current skeleton pose into the given target. */ + buildPrimitive(skeleton: Skeleton, target: ISpineRenderTarget): void; +} diff --git a/packages/spine/src/runtime/SpineRuntimeRegistry.ts b/packages/spine/src/runtime/SpineRuntimeRegistry.ts new file mode 100644 index 0000000000..df865bc687 --- /dev/null +++ b/packages/spine/src/runtime/SpineRuntimeRegistry.ts @@ -0,0 +1,25 @@ +import type { ISpineRuntime } from "./ISpineRuntime"; + +let _runtime: ISpineRuntime | null = null; + +/** + * Register the spine runtime backend. Called for its side effect when a spine core package + * (e.g. `@galacean/engine-spine-core-4.2`) is imported. The last registration wins. + */ +export function registerSpineRuntime(runtime: ISpineRuntime): void { + _runtime = runtime; +} + +/** + * Get the registered spine runtime backend. + * @throws If no core package has been imported. + */ +export function getSpineRuntime(): ISpineRuntime { + if (!_runtime) { + throw new Error( + "@galacean/engine-spine: no spine runtime registered. Import a spine core package, " + + 'e.g. `import "@galacean/engine-spine-core-4.2"`.' + ); + } + return _runtime; +} diff --git a/packages/spine/tsconfig.json b/packages/spine/tsconfig.json new file mode 100644 index 0000000000..588b0055c3 --- /dev/null +++ b/packages/spine/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "declaration": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "declarationDir": "types", + "emitDeclarationOnly": true, + "noImplicitOverride": true, + "sourceMap": true, + "incremental": false, + "skipLibCheck": true, + "stripInternal": true + }, + "include": ["src/**/*"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6dbf1d371..6a5d492e97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -143,6 +143,12 @@ importers: '@galacean/engine-shaderlab': specifier: workspace:* version: link:../packages/shader-lab + '@galacean/engine-spine': + specifier: workspace:* + version: link:../packages/spine + '@galacean/engine-spine-core-4.2': + specifier: workspace:* + version: link:../packages/spine-core-4.2 '@galacean/engine-toolkit': specifier: ^1.3.9 version: 1.3.9(@galacean/engine@packages+galacean) @@ -185,6 +191,15 @@ importers: '@galacean/engine-shaderlab': specifier: workspace:* version: link:../packages/shader-lab + '@galacean/engine-spine': + specifier: workspace:* + version: link:../packages/spine + '@galacean/engine-spine-core-3.8': + specifier: workspace:* + version: link:../packages/spine-core-3.8 + '@galacean/engine-spine-core-4.2': + specifier: workspace:* + version: link:../packages/spine-core-4.2 '@galacean/engine-toolkit': specifier: latest version: 1.5.3(@galacean/engine-ui@packages+ui)(@galacean/engine@packages+galacean) @@ -293,6 +308,38 @@ importers: specifier: workspace:* version: link:../design + packages/spine: + devDependencies: + '@esotericsoftware/spine-core': + specifier: ~4.2.66 + version: 4.2.119 + '@galacean/engine': + specifier: workspace:* + version: link:../galacean + + packages/spine-core-3.8: + dependencies: + '@galacean/engine-spine': + specifier: workspace:* + version: link:../spine + devDependencies: + '@galacean/engine': + specifier: workspace:* + version: link:../galacean + + packages/spine-core-4.2: + dependencies: + '@galacean/engine-spine': + specifier: workspace:* + version: link:../spine + devDependencies: + '@esotericsoftware/spine-core': + specifier: ~4.2.66 + version: 4.2.119 + '@galacean/engine': + specifier: workspace:* + version: link:../galacean + packages/ui: devDependencies: '@galacean/engine': @@ -353,6 +400,12 @@ importers: '@galacean/engine-shaderlab': specifier: workspace:* version: link:../packages/shader-lab + '@galacean/engine-spine': + specifier: workspace:* + version: link:../packages/spine + '@galacean/engine-spine-core-4.2': + specifier: workspace:* + version: link:../packages/spine-core-4.2 '@galacean/engine-ui': specifier: workspace:* version: link:../packages/ui @@ -829,6 +882,9 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@esotericsoftware/spine-core@4.2.119': + resolution: {integrity: sha512-lvaRECaQDScO758ZSAR1Fj+GWkBKVZxPdgT/gCiKqdkrjZCDu2UzgbZtdPhxnLPcKG/zsaGJkfbN4OS0wHsxZQ==} + '@fastify/deepmerge@1.3.0': resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} @@ -4583,6 +4639,8 @@ snapshots: '@eslint/js@8.57.1': {} + '@esotericsoftware/spine-core@4.2.119': {} + '@fastify/deepmerge@1.3.0': {} '@galacean/engine-toolkit-auxiliary-lines@1.3.9(@galacean/engine@packages+galacean)': diff --git a/tests/package.json b/tests/package.json index 1e4c4a4c62..2e9e18f3df 100644 --- a/tests/package.json +++ b/tests/package.json @@ -25,7 +25,9 @@ "@galacean/engine-shaderlab": "workspace:*", "@galacean/engine-physics-physx": "workspace:*", "@galacean/engine-ui": "workspace:*", - "@galacean/engine-shader": "workspace:*" + "@galacean/engine-shader": "workspace:*", + "@galacean/engine-spine": "workspace:*", + "@galacean/engine-spine-core-4.2": "workspace:*" }, "devDependencies": { "@vitest/browser": "2.1.3" diff --git a/tests/src/spine/LoaderUtils.test.ts b/tests/src/spine/LoaderUtils.test.ts new file mode 100644 index 0000000000..fa97f9afcf --- /dev/null +++ b/tests/src/spine/LoaderUtils.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import { LoaderUtils } from "../../../packages/spine/src/loader/LoaderUtils"; + +describe("LoaderUtils.getBaseUrl", () => { + it("strips the file name from a local path", () => { + expect(LoaderUtils.getBaseUrl("/assets/spine/data.json")).to.equal("/assets/spine/"); + }); + + it("returns empty string for a bare file name", () => { + expect(LoaderUtils.getBaseUrl("data.json")).to.equal(""); + }); + + it("strips the file name from an http url", () => { + expect(LoaderUtils.getBaseUrl("https://cdn.example.com/a/b/data.json")).to.equal("https://cdn.example.com/a/b/"); + }); +}); diff --git a/tests/src/spine/Pool.test.ts b/tests/src/spine/Pool.test.ts new file mode 100644 index 0000000000..0d835b408a --- /dev/null +++ b/tests/src/spine/Pool.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "vitest"; +import { ClearablePool } from "../../../packages/spine-core-4.2/src/util/ClearablePool"; +import { ReturnablePool } from "../../../packages/spine-core-4.2/src/util/ReturnablePool"; + +class Item { + value = 0; +} + +describe("ClearablePool", () => { + it("hands out distinct elements, then reuses them after clear", () => { + const pool = new ClearablePool(Item); + const a = pool.get(); + const b = pool.get(); + expect(a).to.not.equal(b); + + pool.clear(); + // clear() only resets the cursor; existing elements are reused in order. + expect(pool.get()).to.equal(a); + expect(pool.get()).to.equal(b); + }); +}); + +describe("ReturnablePool", () => { + it("pre-fills initializeCount and reuses returned elements", () => { + const pool = new ReturnablePool(Item, 2); + const a = pool.get(); + const b = pool.get(); + expect(a).to.not.equal(b); + + pool.return(a); + expect(pool.get()).to.equal(a); + }); + + it("allocates a fresh element when the pool is exhausted", () => { + const pool = new ReturnablePool(Item, 1); + const a = pool.get(); + const fresh = pool.get(); + expect(fresh).to.be.instanceOf(Item); + expect(fresh).to.not.equal(a); + }); +}); diff --git a/tests/src/spine/SpineAnimationRenderer.test.ts b/tests/src/spine/SpineAnimationRenderer.test.ts new file mode 100644 index 0000000000..48de10c2e3 --- /dev/null +++ b/tests/src/spine/SpineAnimationRenderer.test.ts @@ -0,0 +1,72 @@ +import { describe, beforeAll, expect, it } from "vitest"; +import { Entity, Material, Shader, Texture2D, WebGLEngine } from "@galacean/engine"; +import { SpineAnimationRenderer } from "../../../packages/spine/src/renderer/SpineAnimationRenderer"; +import { SpineMaterial } from "../../../packages/spine/src/renderer/SpineMaterial"; +import { SpineBlendMode } from "../../../packages/spine/src/enums/SpineBlendMode"; + +describe("SpineAnimationRenderer", () => { + let engine: WebGLEngine; + + beforeAll(async () => { + engine = await WebGLEngine.create({ canvas: document.createElement("canvas") }); + }); + + it("initializes with default config", () => { + const renderer = new Entity(engine).addComponent(SpineAnimationRenderer); + expect(renderer.defaultConfig.animationName).to.be.null; + expect(renderer.defaultConfig.skinName).to.equal("default"); + expect(renderer.premultipliedAlpha).to.be.false; + expect(renderer.tintBlack).to.be.false; + }); + + it("tintBlack setter flags a buffer resize", () => { + const renderer = new Entity(engine).addComponent(SpineAnimationRenderer); + renderer.tintBlack = true; + expect(renderer.tintBlack).to.be.true; + expect((renderer as any)._needResizeBuffer).to.be.true; + }); + + it("_getMaterial caches by texture + blendMode", () => { + const renderer = new Entity(engine).addComponent(SpineAnimationRenderer); + const texture = new Texture2D(engine, 4, 4); + const normal = renderer._getMaterial(texture, SpineBlendMode.Normal); + // Same texture + blendMode must hit the cache (regression guard for the Map[key] bug). + expect(renderer._getMaterial(texture, SpineBlendMode.Normal)).to.equal(normal); + // Different blendMode must produce a different material. + expect(renderer._getMaterial(texture, SpineBlendMode.Additive)).to.not.equal(normal); + }); + + it("_getMaterial does not share a material across tintBlack variants", () => { + const renderer = new Entity(engine).addComponent(SpineAnimationRenderer); + const texture = new Texture2D(engine, 4, 4); + const plain = renderer._getMaterial(texture, SpineBlendMode.Normal); + renderer.tintBlack = true; + const tinted = renderer._getMaterial(texture, SpineBlendMode.Normal); + // Sharing one material would let renderers that differ only in tintBlack clobber + // each other's RENDERER_TINT_BLACK macro every frame. + expect(tinted).to.not.equal(plain); + expect(renderer._getMaterial(texture, SpineBlendMode.Normal)).to.equal(tinted); + renderer.tintBlack = false; + expect(renderer._getMaterial(texture, SpineBlendMode.Normal)).to.equal(plain); + }); + + it("destroy tolerates user-set materials and removes cached entries by key", () => { + const entity = new Entity(engine); + const renderer = entity.addComponent(SpineAnimationRenderer); + const texture = new Texture2D(engine, 4, 4); + const cached = renderer._getMaterial(texture, SpineBlendMode.Normal) as SpineMaterial; + renderer.setMaterial(0, cached); + // setMaterial is public API — destroy must not assume every entry is a SpineMaterial. + renderer.setMaterial(1, new Material(engine, Shader.find("2D/Spine"))); + const cacheKey = cached._cacheKey; + expect(SpineAnimationRenderer._materialCacheMap.has(cacheKey)).to.be.true; + expect(() => entity.destroy()).to.not.throw(); + expect(SpineAnimationRenderer._materialCacheMap.has(cacheKey)).to.be.false; + }); + + it("cloning an entity whose renderer has no skeleton does not throw", () => { + const entity = new Entity(engine); + entity.addComponent(SpineAnimationRenderer); + expect(() => entity.clone()).to.not.throw(); + }); +}); diff --git a/tests/src/spine/SpineConstant.test.ts b/tests/src/spine/SpineConstant.test.ts new file mode 100644 index 0000000000..38113402c6 --- /dev/null +++ b/tests/src/spine/SpineConstant.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "vitest"; +import { SpineVertexStride } from "../../../packages/spine/src/SpineConstant"; +import { SpineBlendMode } from "../../../packages/spine/src/enums/SpineBlendMode"; + +describe("SpineVertexStride", () => { + it("is 9 floats without tint and 12 with tint black", () => { + // [x,y,z,u,v,r,g,b,a] vs [...,dr,dg,db] — must match the renderer's vertex layout. + expect(SpineVertexStride.withoutTint).to.equal(9); + expect(SpineVertexStride.withTint).to.equal(12); + }); +}); + +describe("SpineBlendMode", () => { + it("ordinals match the spine-core BlendMode order", () => { + expect(SpineBlendMode.Normal).to.equal(0); + expect(SpineBlendMode.Additive).to.equal(1); + expect(SpineBlendMode.Multiply).to.equal(2); + expect(SpineBlendMode.Screen).to.equal(3); + }); +}); diff --git a/tests/src/spine/SpineLoader.test.ts b/tests/src/spine/SpineLoader.test.ts new file mode 100644 index 0000000000..3e2903f986 --- /dev/null +++ b/tests/src/spine/SpineLoader.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { SpineLoader } from "../../../packages/spine/src/loader/SpineLoader"; + +describe("SpineLoader._getUrlExtension", () => { + it("extracts the extension for spine asset types", () => { + expect(SpineLoader._getUrlExtension("path/to/spine.json")).to.equal("json"); + expect(SpineLoader._getUrlExtension("path/to/spine.skel")).to.equal("skel"); + expect(SpineLoader._getUrlExtension("path/to/spine.atlas")).to.equal("atlas"); + }); + + it("ignores query strings", () => { + expect(SpineLoader._getUrlExtension("spine.json?v=2")).to.equal("json"); + }); + + it("returns null when there is no extension", () => { + expect(SpineLoader._getUrlExtension("spine")).to.be.null; + }); +}); diff --git a/tests/src/spine/SpineMaterial.test.ts b/tests/src/spine/SpineMaterial.test.ts new file mode 100644 index 0000000000..2bb4e42bcd --- /dev/null +++ b/tests/src/spine/SpineMaterial.test.ts @@ -0,0 +1,36 @@ +import { describe, beforeAll, expect, it } from "vitest"; +import { BlendFactor, Shader, ShaderProperty, WebGLEngine } from "@galacean/engine"; +import { SpineMaterial } from "../../../packages/spine/src/renderer/SpineMaterial"; +import { SpineBlendMode } from "../../../packages/spine/src/enums/SpineBlendMode"; + +describe("SpineMaterial", () => { + let engine: WebGLEngine; + const srcColor = ShaderProperty.getByName("sourceColorBlendFactor"); + const dstColor = ShaderProperty.getByName("destinationColorBlendFactor"); + + beforeAll(async () => { + engine = await WebGLEngine.create({ canvas: document.createElement("canvas") }); + }); + + it("uses the built-in 2D/Spine shader and defaults to Normal blend", () => { + const material = new SpineMaterial(engine); + expect(material.shader).to.equal(Shader.find("2D/Spine")); + expect(material._getBlendMode()).to.equal(SpineBlendMode.Normal); + }); + + it("additive blend sets source=SourceAlpha, destination=One", () => { + const material = new SpineMaterial(engine); + material._setBlendMode(SpineBlendMode.Additive, false); + expect(material._getBlendMode()).to.equal(SpineBlendMode.Additive); + expect(material.shaderData.getInt(srcColor)).to.equal(BlendFactor.SourceAlpha); + expect(material.shaderData.getInt(dstColor)).to.equal(BlendFactor.One); + }); + + it("premultipliedAlpha switches the source color factor to One", () => { + const material = new SpineMaterial(engine); + material._setBlendMode(SpineBlendMode.Normal, true); + expect(material.shaderData.getInt(srcColor)).to.equal(BlendFactor.One); + material._setBlendMode(SpineBlendMode.Normal, false); + expect(material.shaderData.getInt(srcColor)).to.equal(BlendFactor.SourceAlpha); + }); +}); diff --git a/tests/src/spine/SpineResource.test.ts b/tests/src/spine/SpineResource.test.ts new file mode 100644 index 0000000000..6f281f81ba --- /dev/null +++ b/tests/src/spine/SpineResource.test.ts @@ -0,0 +1,33 @@ +import { Texture2D, WebGLEngine } from "@galacean/engine"; +import { beforeAll, describe, expect, it } from "vitest"; +import { SpineResource } from "../../../packages/spine/src/loader/SpineResource"; +import { SpineTexture } from "../../../packages/spine-core-4.2/src/SpineTexture"; + +describe("SpineResource._associationTextureInSkeletonData", () => { + let engine: WebGLEngine; + + beforeAll(async () => { + engine = await WebGLEngine.create({ canvas: document.createElement("canvas") }); + }); + + it("associates the attachment's Texture2D and protects it from GC while the resource is alive", () => { + const texture2D = new Texture2D(engine, 4, 4); + const attachment = { region: { texture: new SpineTexture(texture2D) } }; + const skeletonData = { + skins: [{ getAttachment: () => attachment }], + slots: [{ index: 0, name: "slot0" }] + }; + // Stands in for the SpineResource instance: only the fields `_associationTextureInSkeletonData` + // reads/writes (`_texturesInSpineAtlas`) and the super-resource GC check reads (`refCount`). + const fakeResource = { _texturesInSpineAtlas: [] as Texture2D[], refCount: 1 }; + + // @ts-ignore + SpineResource.prototype._associationTextureInSkeletonData.call(fakeResource, skeletonData); + + expect(fakeResource._texturesInSpineAtlas).to.deep.equal([texture2D]); + // Regression guard: region.texture is already the SpineTexture, so reading `.texture` off it again + // (instead of `.getImage()`) resolved to `undefined` on the 4.2 backend, and the association above + // silently never happened - leaving the texture unprotected from a GC pass while still in use. + expect(texture2D.destroy(false, true)).to.equal(false); + }); +}); diff --git a/tests/src/spine/SpineRuntimeRegistry.test.ts b/tests/src/spine/SpineRuntimeRegistry.test.ts new file mode 100644 index 0000000000..d9234a9b8f --- /dev/null +++ b/tests/src/spine/SpineRuntimeRegistry.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; +import { registerSpineRuntime, getSpineRuntime } from "../../../packages/spine/src/runtime/SpineRuntimeRegistry"; +import type { ISpineRuntime } from "../../../packages/spine/src/runtime/ISpineRuntime"; + +describe("SpineRuntimeRegistry", () => { + // Must run first: the module singleton starts null and there is no reset. + it("throws when no runtime is registered", () => { + expect(() => getSpineRuntime()).to.throw(/no spine runtime registered/); + }); + + it("returns the registered runtime", () => { + const runtime = {} as ISpineRuntime; + registerSpineRuntime(runtime); + expect(getSpineRuntime()).to.equal(runtime); + }); + + it("last registration wins", () => { + const a = {} as ISpineRuntime; + const b = {} as ISpineRuntime; + registerSpineRuntime(a); + registerSpineRuntime(b); + expect(getSpineRuntime()).to.equal(b); + }); +}); diff --git a/tests/src/spine/SpineTexture.test.ts b/tests/src/spine/SpineTexture.test.ts new file mode 100644 index 0000000000..ac6b76e5f6 --- /dev/null +++ b/tests/src/spine/SpineTexture.test.ts @@ -0,0 +1,44 @@ +import { describe, beforeAll, expect, it } from "vitest"; +import { Texture2D, TextureFilterMode, WebGLEngine } from "@galacean/engine"; +import { TextureFilter as TextureFilter42 } from "@esotericsoftware/spine-core"; +import { SpineTexture as SpineTexture42 } from "../../../packages/spine-core-4.2/src/SpineTexture"; +import { SpineTexture as SpineTexture38 } from "../../../packages/spine-core-3.8/src/SpineTexture"; +import { TextureFilter as TextureFilter38 } from "../../../packages/spine-core-3.8/src/spine-core/Texture"; + +describe("SpineTexture.setFilters", () => { + let engine: WebGLEngine; + + beforeAll(async () => { + engine = await WebGLEngine.create({ canvas: document.createElement("canvas") }); + }); + + it("maps atlas min filters to engine filter modes (4.2)", () => { + const texture = new Texture2D(engine, 4, 4); + const spineTexture = new SpineTexture42(texture); + + spineTexture.setFilters(TextureFilter42.Nearest, TextureFilter42.Nearest); + expect(texture.filterMode).to.equal(TextureFilterMode.Point); + + spineTexture.setFilters(TextureFilter42.Linear, TextureFilter42.Linear); + expect(texture.filterMode).to.equal(TextureFilterMode.Bilinear); + + // Atlases pair a MipMap* min filter with a plain Linear mag filter — the min filter + // alone decides whether mipmap sampling (Trilinear) is requested. + spineTexture.setFilters(TextureFilter42.MipMapLinearLinear, TextureFilter42.Linear); + expect(texture.filterMode).to.equal(TextureFilterMode.Trilinear); + }); + + it("maps atlas min filters to engine filter modes (3.8)", () => { + const texture = new Texture2D(engine, 4, 4); + const spineTexture = new SpineTexture38(texture); + + spineTexture.setFilters(TextureFilter38.Nearest, TextureFilter38.Nearest); + expect(texture.filterMode).to.equal(TextureFilterMode.Point); + + spineTexture.setFilters(TextureFilter38.Linear, TextureFilter38.Linear); + expect(texture.filterMode).to.equal(TextureFilterMode.Bilinear); + + spineTexture.setFilters(TextureFilter38.MipMapLinearLinear, TextureFilter38.Linear); + expect(texture.filterMode).to.equal(TextureFilterMode.Trilinear); + }); +}); From 09fbacd9f82c461585954096c65ba7416ef56941 Mon Sep 17 00:00:00 2001 From: cptbtptpbcptdtptp Date: Fri, 10 Jul 2026 14:58:20 +0800 Subject: [PATCH 130/130] chore: release v0.0.0-experimental-2.0-game.19 --- e2e/package.json | 2 +- examples/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/design/package.json | 2 +- packages/galacean/package.json | 2 +- packages/loader/package.json | 2 +- packages/math/package.json | 2 +- packages/physics-lite/package.json | 2 +- packages/physics-physx/package.json | 2 +- packages/rhi-webgl/package.json | 2 +- packages/shader-lab/package.json | 2 +- packages/shader/package.json | 2 +- packages/spine-core-3.8/package.json | 2 +- packages/spine-core-4.2/package.json | 2 +- packages/spine/package.json | 2 +- packages/ui/package.json | 2 +- packages/xr-webxr/package.json | 2 +- packages/xr/package.json | 2 +- tests/package.json | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index e646c5dd12..fb743e744a 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-e2e", "private": true, - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "license": "MIT", "scripts": { "case": "vite serve .dev --config .dev/vite.config.js", diff --git a/examples/package.json b/examples/package.json index 59376745de..d0847d14a4 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-examples", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "private": true, "license": "MIT", "main": "dist/main.js", diff --git a/package.json b/package.json index 72b04ffdf8..feef7fc6c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-root", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "packageManager": "pnpm@9.3.0", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 499a6fc8e7..b6d08f767b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-core", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/package.json b/packages/design/package.json index 2e0f6672c8..e0a42fc2c0 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/galacean/package.json b/packages/galacean/package.json index 1f3f7bf203..9e4d0479cd 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index d223f1dd28..3e42b89824 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/math/package.json b/packages/math/package.json index 7c26d40c61..8839a44ce2 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 1fdb4a552d..5a48498a49 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 9193c74b00..ed881fc191 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 0d1d1ea9f5..6da1870132 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index 633fbd0f37..5f1bd3b797 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/package.json b/packages/shader/package.json index ea35689543..471d4ee963 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/spine-core-3.8/package.json b/packages/spine-core-3.8/package.json index 6ecce6e2e0..815ae42878 100644 --- a/packages/spine-core-3.8/package.json +++ b/packages/spine-core-3.8/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-spine-core-3.8", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/spine-core-4.2/package.json b/packages/spine-core-4.2/package.json index 5f5aa0e11e..2411077e13 100644 --- a/packages/spine-core-4.2/package.json +++ b/packages/spine-core-4.2/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-spine-core-4.2", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/spine/package.json b/packages/spine/package.json index 2b513502f3..3f49b3b2e7 100644 --- a/packages/spine/package.json +++ b/packages/spine/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-spine", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/package.json b/packages/ui/package.json index 9405e8b330..82632deda9 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index cf6921a9d7..9b7f28ed3a 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index 9a3e2244cb..20fbd1cf6e 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/tests/package.json b/tests/package.json index 2e9e18f3df..a95ffceb25 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "0.0.0-experimental-2.0-game.18", + "version": "0.0.0-experimental-2.0-game.19", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js",