diff --git a/.gitignore b/.gitignore
index 249ab8c2d5..7b036d513c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,7 +38,8 @@ playwright-report/
# Claude Code files
.claude/
CLAUDE.md
-
+docs/superpowers
+notes/
.husky/post-checkout
.husky/post-commit
.husky/pre-push
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/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/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/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/.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 91e5bab34b..f52b48110a 100755
Binary files a/e2e/.dev/physx.release.simd.wasm and b/e2e/.dev/physx.release.simd.wasm differ
diff --git a/e2e/.dev/physx.release.wasm b/e2e/.dev/physx.release.wasm
index d9e03eee6a..3efb26a288 100755
Binary files a/e2e/.dev/physx.release.wasm and b/e2e/.dev/physx.release.wasm differ
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/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);
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/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/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/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/e2e/package.json b/e2e/package.json
index 34a2f56922..8f8cde0b5f 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.14",
"license": "MIT",
"scripts": {
"case": "vite serve .dev --config .dev/vite.config.js",
diff --git a/examples/package.json b/examples/package.json
index a57e6c4187..cf39d4de73 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.14",
"private": true,
"license": "MIT",
"main": "dist/main.js",
@@ -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/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/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/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/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/package.json b/package.json
index 6be276f782..4e741f9f78 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.14",
"packageManager": "pnpm@9.3.0",
"private": true,
"scripts": {
@@ -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/package.json b/packages/core/package.json
index be735da868..bbd150d689 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.14",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
diff --git a/packages/core/src/2d/assembler/FilledSpriteAssembler.ts b/packages/core/src/2d/assembler/FilledSpriteAssembler.ts
new file mode 100644
index 0000000000..b5e9797119
--- /dev/null
+++ b/packages/core/src/2d/assembler/FilledSpriteAssembler.ts
@@ -0,0 +1,613 @@
+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[15];
+
+ 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[15];
+
+ // 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[15];
+
+ // 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[15];
+
+ // ---------------
+ // 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/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/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/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;
}
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/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/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/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts
index 141f352d35..771faead34 100644
--- a/packages/core/src/2d/sprite/SpriteMask.ts
+++ b/packages/core/src/2d/sprite/SpriteMask.ts
@@ -1,46 +1,25 @@
import { BoundingBox } from "@galacean/engine-math";
import { Entity } from "../../Entity";
-import { RenderQueueFlags } from "../../RenderPipeline/BasicRenderPipeline";
-import { BatchUtils } from "../../RenderPipeline/BatchUtils";
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 { 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;
@@ -50,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).
@@ -69,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;
}
}
@@ -92,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;
}
}
@@ -104,93 +76,20 @@ 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._renderElement.addSubRenderElement(new SubRenderElement());
- this._onSpriteChange = this._onSpriteChange.bind(this);
+ this._initMask();
}
/**
* @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);
}
/**
@@ -198,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(elementA: SubRenderElement, elementB: SubRenderElement): boolean {
- return BatchUtils.canBatchSpriteMask(elementA, elementB);
- }
-
- /**
- * @internal
- */
- override _batch(elementA: SubRenderElement, elementB?: SubRenderElement): void {
- BatchUtils.batchFor2D(elementA, elementB);
- }
-
- /**
- * @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);
}
/**
@@ -239,147 +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 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);
+ 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/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts
index c1b183ee7a..06bd3d3c6c 100644
--- a/packages/core/src/2d/sprite/SpriteRenderer.ts
+++ b/packages/core/src/2d/sprite/SpriteRenderer.ts
@@ -1,19 +1,22 @@
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";
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.
*/
@@ -278,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);
}
/**
@@ -295,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);
}
/**
@@ -377,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);
}
@@ -438,6 +519,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:
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/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts
index 143e46a7da..215a762869 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 { BatchUtils } from "../../RenderPipeline/BatchUtils";
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 { 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.
*/
@@ -309,8 +346,13 @@ 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
+ this._outlineColor._onValueChanged = this._onOutlineColorChanged.bind(this);
}
/**
@@ -337,6 +379,7 @@ export class TextRenderer extends Renderer implements ITextRenderer {
super._cloneTo(target);
target.font = this._font;
target._subFont = this._subFont;
+ target.outlineWidth = this._outlineWidth;
}
/**
@@ -373,23 +416,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.canBatchText(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 +473,27 @@ 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;
+ const textTextureSize = TextRenderer._tempVec20;
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.shaderData.setVector2(
+ TextRenderer._textTextureSizeProperty,
+ textTextureSize.set(texture.width, texture.height)
+ );
+ renderElement.priority = priority;
+ renderElement.distanceForSort = distanceForSort;
+ renderPipeline.pushRenderElement(context, renderElement);
}
- camera._renderPipeline.pushRenderElement(context, renderElement);
}
private _resetSubFont(): void {
@@ -604,10 +654,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);
@@ -698,6 +749,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;
@@ -707,10 +762,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;
@@ -743,6 +801,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/Camera.ts b/packages/core/src/Camera.ts
index 53b689dd84..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;
@@ -934,7 +936,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;
}
diff --git a/packages/core/src/ComponentsManager.ts b/packages/core/src/ComponentsManager.ts
index e9b9a1cc0d..7df837a336 100644
--- a/packages/core/src/ComponentsManager.ts
+++ b/packages/core/src/ComponentsManager.ts
@@ -36,9 +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;
this._activeCameras.add(camera);
@@ -270,15 +267,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/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/Entity.ts b/packages/core/src/Entity.ts
index 08503f5498..5b0c929ac1 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";
@@ -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;
/**
@@ -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 {
+ } 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`);
}
}
@@ -403,6 +401,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);
@@ -410,6 +413,8 @@ export class Entity extends EngineObject {
activeChangeFlag && child._processInActive(activeChangeFlag);
Entity._traverseSetOwnerScene(child, null); // Must after child._processInActive().
+
+ child._setParentChange();
}
children.length = 0;
}
@@ -565,24 +570,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;
}
/**
@@ -679,53 +684,46 @@ 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);
- }
- }
-
- 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);
+ const children = this._children;
+ for (let i = 0, n = children.length; i < n; i++) {
+ children[i]._getComponentsInChildren(type, results);
}
- 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/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts
index dee2231a0a..28049597dd 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,15 +131,17 @@ export class BasicRenderPipeline {
this._cascadedShadowCasterPass.onRender(context);
}
- const batcherManager = engine._batcherManager;
cullingResults.reset();
// Depth use camera's view and projection matrix
this._cullingResults.setRenderUpdateFlagTrue(ContextRendererUpdateFlag.viewProjectionMatrix);
context.applyVirtualCamera(camera._virtualCamera, depthPassEnabled);
- this._prepareRender(context);
+ this._prepareWorldRender(context);
- cullingResults.sortBatch(batcherManager);
+ const batcherManager = engine._batcherManager;
+ cullingResults.sort();
+ this._prepareUIRender(context);
+ cullingResults.batch(batcherManager);
batcherManager.uploadBuffer();
if (depthPassEnabled) {
@@ -369,58 +370,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 +424,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 +441,7 @@ export class BasicRenderPipeline {
cullingResults.transparentQueue.pushRenderElement(renderElement);
break;
}
- renderElement.renderQueueFlags |= flag;
+ pushedQueueFlags |= flag;
}
}
@@ -482,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) {
@@ -504,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];
@@ -516,7 +516,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/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/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/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/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..f9ee9fb0a4 100644
--- a/packages/core/src/RenderPipeline/RenderQueue.ts
+++ b/packages/core/src/RenderPipeline/RenderQueue.ts
@@ -3,10 +3,11 @@ import { BasicResources, RenderStateElementMap } from "../BasicResources";
import { Utils } from "../Utils";
import { RenderQueueType, Shader } from "../shader";
import { ShaderMacroCollection } from "../shader/ShaderMacroCollection";
+import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint";
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) {}
@@ -36,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);
}
@@ -45,8 +54,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 +66,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 +100,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 +148,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 +182,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 +200,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 +243,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..67565f8071
--- /dev/null
+++ b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts
@@ -0,0 +1,121 @@
+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;
+
+ 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 &&
+ 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
+ );
+ }
+
+ /**
+ * 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;
+ }
+
+ 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..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 { BatchUtils } from "./BatchUtils";
+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 ab9f743c0c..c6444d5354 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
@@ -46,12 +47,8 @@ export class Renderer extends Component {
_globalShaderMacro: ShaderMacroCollection = new ShaderMacroCollection();
@ignoreClone
_renderFrameCount: number;
- /** @internal */
@assignmentClone
_maskInteraction: SpriteMaskInteraction = SpriteMaskInteraction.None;
- /** @internal */
- @ignoreClone
- _batchedTransformShaderData: boolean = false;
@assignmentClone
_maskLayer: SpriteMaskLayer = SpriteMaskLayer.Layer0;
@@ -74,8 +71,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 +379,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 +387,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/SceneManager.ts b/packages/core/src/SceneManager.ts
index 92de60d66b..1fa51ba265 100644
--- a/packages/core/src/SceneManager.ts
+++ b/packages/core/src/SceneManager.ts
@@ -91,10 +91,21 @@ 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.getArray();
+ const scenes = this._scenes.getLoopArray();
for (let i = 0, n = scenes.length; i < n; i++) {
scenes[i].destroy();
}
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,
diff --git a/packages/core/src/Transform.ts b/packages/core/src/Transform.ts
index f822474dcc..2c9821426c 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);
+ }
+ }
}
/**
@@ -597,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/animation/Animator.ts b/packages/core/src/animation/Animator.ts
index 180c2d1356..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";
@@ -218,13 +219,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` 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
+ */
+ 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 +635,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);
@@ -753,8 +772,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);
@@ -883,7 +902,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 +1008,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);
@@ -1452,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/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..7adc90c2f4 100644
--- a/packages/core/src/animation/internal/AnimatorStatePlayData.ts
+++ b/packages/core/src/animation/internal/AnimatorStatePlayData.ts
@@ -1,20 +1,60 @@
+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, wrapMode).
*/
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;
+ /** 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) ──
+
+ /** 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 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 +67,8 @@ export class AnimatorStatePlayData {
this.clipTime = state.clipStartTime * state.clip.length;
this.currentEventIndex = 0;
this.isForward = true;
+ this.speed = state.speed;
+ this.wrapMode = state.wrapMode;
this.state._transitionCollection.needResetCurrentCheckIndex = true;
}
@@ -47,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/packages/core/src/asset/AssetType.ts b/packages/core/src/asset/AssetType.ts
index 17ee100043..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 and mp3. */
+ /** AudioClip, include ogg, wav, mp3, m4a, aac and flac. */
Audio = "Audio",
/** Project asset. */
Project = "project",
diff --git a/packages/core/src/asset/ResourceManager.ts b/packages/core/src/asset/ResourceManager.ts
index 0395b2b29b..94cb7ee946 100644
--- a/packages/core/src/asset/ResourceManager.ts
+++ b/packages/core/src/asset/ResourceManager.ts
@@ -340,7 +340,12 @@ 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) {
throw `asset type should be specified: ${assetInfo.url}`;
}
diff --git a/packages/core/src/audio/AudioManager.ts b/packages/core/src/audio/AudioManager.ts
index ad93f5ed59..b1c4c5651b 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.
*/
@@ -7,15 +13,22 @@ 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();
+ 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.
* @returns A promise that resolves when the audio context is suspended
*/
static suspend(): Promise {
- return AudioManager._context.suspend();
+ AudioManager._suspendActiveSourcesForInterruption();
+ return AudioManager._context?.suspend() ?? Promise.resolve();
}
/**
@@ -24,14 +37,48 @@ export class AudioManager {
* @returns A promise that resolves when the audio context is resumed
*/
static resume(): Promise {
- return (AudioManager._resumePromise ??= AudioManager._context
- .resume()
- .then(() => {
- AudioManager._needsUserGestureResume = false;
- })
- .finally(() => {
- AudioManager._resumePromise = null;
- }));
+ const context = AudioManager._context;
+ if (!context) {
+ 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();
+ });
+ }
+
+ /** @internal */
+ static _registerPendingSource(source: ResumableAudioSource): void {
+ AudioManager._pendingSources.add(source);
+ }
+
+ /** @internal */
+ 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);
}
/**
@@ -41,11 +88,12 @@ export class AudioManager {
let context = AudioManager._context;
if (!context) {
AudioManager._context = context = new window.AudioContext();
- 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);
+ context.onstatechange = AudioManager._onContextStateChange;
+ if (!AudioManager._eventsBound) {
+ AudioManager._eventsBound = true;
+ AudioManager._bindLifecycleEvents();
+ AudioManager._bindGestureEvents();
+ }
}
return context;
}
@@ -70,22 +118,153 @@ export class AudioManager {
return AudioManager.getContext().state === "running";
}
- 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;
+ private static _onContextStateChange(): void {
+ if (AudioManager._context?.state === "running") {
+ if (AudioManager._hidden || AudioManager._needsUserGestureResume) {
+ return;
+ }
+ AudioManager._needsUserGestureResume = false;
+ AudioManager._resumePendingSources();
+ AudioManager._resumeInterruptedSources();
+ }
+ }
+
+ 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 _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 _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();
+ });
+ }
+
+ 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(() => {});
+ }
+
+ private static _onShown(): void {
+ if (!AudioManager._hidden) {
+ return;
+ }
+ AudioManager._hidden = false;
+
+ if (AudioManager._hasResumeWork()) {
+ AudioManager._prepareGestureResume();
+ AudioManager._scheduleForegroundRestore();
}
}
private static _resumeAfterInterruption(): void {
- if (AudioManager._needsUserGestureResume) {
+ 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 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/packages/core/src/audio/AudioSource.ts b/packages/core/src/audio/AudioSource.ts
index 29a3ed8bc1..3dacf53ec3 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,23 +171,27 @@ export class AudioSource extends Component {
* Stops playing the clip.
*/
stop(): void {
- this._pendingPlay = false;
+ 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;
}
/**
* Pauses playing the clip.
*/
pause(): void {
- this._pendingPlay = false;
+ this._cancelPendingPlayback();
+ AudioManager._unregisterInterruptedSource(this);
if (this._isPlaying) {
this._clearSourceNode();
@@ -211,6 +199,7 @@ export class AudioSource extends Component {
this._pausedTime = AudioManager.getContext().currentTime;
this._isPlaying = false;
AudioManager._playingCount--;
+ AudioManager._unregisterPlayingSource(this);
}
}
@@ -250,34 +239,120 @@ 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();
+ }
+
+ /** @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;
}
+
+ private _cancelPendingPlayback(): void {
+ if (!this._pendingPlay) {
+ return;
+ }
+
+ this._pendingPlay = false;
+ AudioManager._unregisterPendingSource(this);
+ }
}
diff --git a/packages/core/src/clone/CloneManager.ts b/packages/core/src/clone/CloneManager.ts
index be46462982..b6f94ce248 100644
--- a/packages/core/src/clone/CloneManager.ts
+++ b/packages/core/src/clone/CloneManager.ts
@@ -105,21 +105,46 @@ export class CloneManager {
): void {
const sourceProperty = source[k];
- // 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;
}
+ // 2. Explicit ignore
if (cloneMode === CloneMode.Ignore) return;
- // Primitives, undecorated, or @assignmentClone: direct assign
- if (!(sourceProperty instanceof Object) || cloneMode === undefined || cloneMode === CloneMode.Assignment) {
+ // 3. Primitives / null / undefined - direct assign
+ if (!(sourceProperty instanceof Object)) {
target[k] = sourceProperty;
return;
}
- // @shallowClone / @deepClone: deep copy complex objects
+ // 4. Determine effective clone mode
+ let effectiveCloneMode: CloneMode = cloneMode;
+ if (effectiveCloneMode === undefined) {
+ // Undecorated: infer from runtime type
+ effectiveCloneMode = CloneManager._inferCloneMode(sourceProperty, target[k]);
+ } 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 &&
+ targetProperty !== sourceProperty &&
+ targetProperty.constructor === sourceProperty.constructor
+ ) {
+ effectiveCloneMode = CloneMode.Deep;
+ }
+ }
+
+ // 5. Assignment - direct reference copy
+ if (effectiveCloneMode === CloneMode.Assignment) {
+ target[k] = sourceProperty;
+ return;
+ }
+
+ // 6. Shallow/Deep clone for complex types
const type = sourceProperty.constructor;
switch (type) {
case Uint8Array:
@@ -137,6 +162,37 @@ export class CloneManager {
targetPropertyT.set(sourceProperty);
}
break;
+ case Map:
+ let targetPropertyM =