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 = >target[k]; + if (targetPropertyM == null) { + target[k] = targetPropertyM = new Map(); + } else { + targetPropertyM.clear(); + } + (>sourceProperty).forEach((value, key) => { + if (key instanceof Object && (key)._remap) { + key = (key)._remap(srcRoot, targetRoot); + } + if (value instanceof Object && (value)._remap) { + value = (value)._remap(srcRoot, targetRoot); + } + targetPropertyM.set(key, value); + }); + break; + case Set: + let targetPropertyS = >target[k]; + if (targetPropertyS == null) { + target[k] = targetPropertyS = new Set(); + } else { + targetPropertyS.clear(); + } + (>sourceProperty).forEach((value) => { + if (value instanceof Object && (value)._remap) { + value = (value)._remap(srcRoot, targetRoot); + } + targetPropertyS.add(value); + }); + break; case Array: let targetPropertyA = >target[k]; const length = (>sourceProperty).length; @@ -150,7 +206,7 @@ export class CloneManager { >sourceProperty, targetPropertyA, i, - cloneMode, + cloneMode, // Pass original mode: decorated → children inherit, undecorated → children infer independently srcRoot, targetRoot, deepInstanceMap @@ -158,25 +214,27 @@ export class CloneManager { } break; default: - let targetProperty = target[k]; - // If the target property is undefined, create new instance and keep reference sharing like the source - if (!targetProperty) { - targetProperty = deepInstanceMap.get(sourceProperty); - if (!targetProperty) { - targetProperty = new sourceProperty.constructor(); - deepInstanceMap.set(sourceProperty, targetProperty); - } - target[k] = targetProperty; + // Check if we've already visited this source object (cycle detection) + if (deepInstanceMap.has(sourceProperty)) { + target[k] = deepInstanceMap.get(sourceProperty); + return; + } + + let targetPropertyD = target[k]; + if (!targetPropertyD) { + targetPropertyD = new sourceProperty.constructor(); + target[k] = targetPropertyD; } + deepInstanceMap.set(sourceProperty, targetPropertyD); if ((sourceProperty).copyFrom) { - (targetProperty).copyFrom(sourceProperty); + (targetPropertyD).copyFrom(sourceProperty); } else { const cloneModes = CloneManager.getCloneMode(sourceProperty.constructor); for (let k in sourceProperty) { CloneManager.cloneProperty( sourceProperty, - targetProperty, + targetPropertyD, k, cloneModes[k], srcRoot, @@ -184,12 +242,46 @@ export class CloneManager { deepInstanceMap ); } - (sourceProperty)._cloneTo?.(targetProperty, srcRoot, targetRoot); + (sourceProperty)._cloneTo?.(targetPropertyD, srcRoot, targetRoot); } break; } } + /** + * Infer the appropriate clone mode for an undecorated property based on its runtime type. + * This enables user custom scripts to get correct clone behavior without decorators. + */ + private static _inferCloneMode(sourceProperty: Object, targetProperty: any): CloneMode { + // If target already has an independent instance of the same type, + // deep clone to preserve isolation (e.g., constructor-created objects) + if ( + targetProperty && + targetProperty !== sourceProperty && + targetProperty.constructor === sourceProperty.constructor + ) { + return CloneMode.Deep; + } + + // Arrays need recursive processing (may contain Entity/Component refs) + if (Array.isArray(sourceProperty)) return CloneMode.Deep; + + // TypedArrays - copy data + if (ArrayBuffer.isView(sourceProperty)) return CloneMode.Deep; + + // Maps and Sets - create independent copies + if (sourceProperty instanceof Map || sourceProperty instanceof Set) return CloneMode.Deep; + + // Value types with copyFrom (math types like Vector3, Color, etc.) + if ((sourceProperty).copyFrom) return CloneMode.Deep; + + // Plain objects - deep clone (may contain Entity/Component refs) + if (sourceProperty.constructor === Object) return CloneMode.Deep; + + // Other class instances (engine resources like Material, Texture) - shared reference + return CloneMode.Assignment; + } + static deepCloneObject(source: Object, target: Object, deepInstanceMap: Map): void { for (let k in source) { CloneManager.cloneProperty(source, target, k, CloneMode.Deep, null, null, deepInstanceMap); diff --git a/packages/core/src/graphic/TransformFeedbackShader.ts b/packages/core/src/graphic/TransformFeedbackShader.ts index c200e41597..64ffd7d114 100644 --- a/packages/core/src/graphic/TransformFeedbackShader.ts +++ b/packages/core/src/graphic/TransformFeedbackShader.ts @@ -28,16 +28,17 @@ export class TransformFeedbackShader { * Get or compile a shader program for the given engine and macro combination. */ getProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram | null { - const pool = engine._getShaderProgramPool(this._id); + const map = engine._getShaderProgramMap(this._id); - let program = pool.get(macroCollection); + let program = map.get(macroCollection); if (program) return program; const { vertexSource, fragmentSource } = ShaderFactory.compilePlatformSource( engine, macroCollection, this.vertexSource, - this.fragmentSource + this.fragmentSource, + false ); program = new ShaderProgram(engine, vertexSource, fragmentSource, this.feedbackVaryings); @@ -47,7 +48,7 @@ export class TransformFeedbackShader { return null; } - pool.cache(program); + map.cache(program); return program; } } diff --git a/packages/core/src/graphic/enums/BufferBindFlag.ts b/packages/core/src/graphic/enums/BufferBindFlag.ts index 152b9d2e54..4616d1eaff 100644 --- a/packages/core/src/graphic/enums/BufferBindFlag.ts +++ b/packages/core/src/graphic/enums/BufferBindFlag.ts @@ -5,5 +5,7 @@ export enum BufferBindFlag { /** Vertex buffer binding flag. */ VertexBuffer, /** Index buffer binding flag. */ - IndexBuffer + IndexBuffer, + /** Constant buffer binding flag (WebGL2 only). */ + ConstantBuffer } diff --git a/packages/core/src/input/pointer/Pointer.ts b/packages/core/src/input/pointer/Pointer.ts index a7412bc2ed..bb0d9fb904 100644 --- a/packages/core/src/input/pointer/Pointer.ts +++ b/packages/core/src/input/pointer/Pointer.ts @@ -23,6 +23,8 @@ export class Pointer { pressedButtons: PointerButton; /** The position of the pointer in screen space pixel coordinates. */ position: Vector2 = new Vector2(); + /** The position of the pointer when it was last pressed down (in screen space pixel coordinates). */ + pressedPosition: Vector2 = new Vector2(); /** The change of the pointer. */ deltaPosition: Vector2 = new Vector2(); /** @internal */ diff --git a/packages/core/src/input/pointer/PointerEventData.ts b/packages/core/src/input/pointer/PointerEventData.ts index 89f9ae09b6..33dc41b001 100644 --- a/packages/core/src/input/pointer/PointerEventData.ts +++ b/packages/core/src/input/pointer/PointerEventData.ts @@ -1,4 +1,5 @@ import { Vector3 } from "@galacean/engine-math"; +import { Entity } from "../../Entity"; import { IPoolElement } from "../../utils/ObjectPool"; import { Pointer } from "./Pointer"; @@ -10,11 +11,17 @@ export class PointerEventData implements IPoolElement { pointer: Pointer; /** The position of the event trigger (in world space). */ worldPosition: Vector3 = new Vector3(); + /** The hit-tested target entity (deepest entity on the bubble path). */ + target: Entity = null; + /** The entity currently handling the event (same as target on non-bubbling fire, varies on bubble). */ + currentTarget: Entity = null; /** * @internal */ dispose() { this.pointer = null; + this.target = null; + this.currentTarget = null; } } diff --git a/packages/core/src/input/pointer/PointerManager.ts b/packages/core/src/input/pointer/PointerManager.ts index fc6d8a2010..83b2310c83 100644 --- a/packages/core/src/input/pointer/PointerManager.ts +++ b/packages/core/src/input/pointer/PointerManager.ts @@ -245,6 +245,7 @@ export class PointerManager implements IInput { pointer._downMap[button] = frameCount; pointer._frameEvents |= PointerEventType.Down; pointer.phase = PointerPhase.Down; + pointer.pressedPosition.copyFrom(pointer.position); break; } case "pointerup": { diff --git a/packages/core/src/input/pointer/emitter/PhysicsPointerEventEmitter.ts b/packages/core/src/input/pointer/emitter/PhysicsPointerEventEmitter.ts index 5778f924a9..5cfc2ec8cd 100644 --- a/packages/core/src/input/pointer/emitter/PhysicsPointerEventEmitter.ts +++ b/packages/core/src/input/pointer/emitter/PhysicsPointerEventEmitter.ts @@ -53,13 +53,13 @@ export class PhysicsPointerEventEmitter extends PointerEventEmitter { override processDrag(pointer: Pointer): void { const entity = this._draggedEntity; - entity && this._fireDrag(entity, this._createEventData(pointer)); + entity && this._fireDrag(entity, this._createEventData(pointer, entity, entity)); } override processDown(pointer: Pointer): void { const entity = (this._pressedEntity = this._draggedEntity = this._enteredEntity); if (entity) { - const eventData = this._createEventData(pointer); + const eventData = this._createEventData(pointer, entity, entity); this._fireDown(entity, eventData); this._fireBeginDrag(entity, eventData); } @@ -69,14 +69,14 @@ export class PhysicsPointerEventEmitter extends PointerEventEmitter { const { _enteredEntity: enteredEntity, _draggedEntity: draggedEntity } = this; if (enteredEntity) { const sameTarget = this._pressedEntity === enteredEntity; - const eventData = this._createEventData(pointer); + const eventData = this._createEventData(pointer, enteredEntity, enteredEntity); this._fireUp(enteredEntity, eventData); sameTarget && this._fireClick(enteredEntity, eventData); this._fireDrop(enteredEntity, eventData); } this._pressedEntity = null; if (draggedEntity) { - this._fireEndDrag(draggedEntity, this._createEventData(pointer)); + this._fireEndDrag(draggedEntity, this._createEventData(pointer, draggedEntity, draggedEntity)); this._draggedEntity = null; } } @@ -84,13 +84,13 @@ export class PhysicsPointerEventEmitter extends PointerEventEmitter { override processLeave(pointer: Pointer): void { const enteredEntity = this._enteredEntity; if (enteredEntity) { - this._fireExit(enteredEntity, this._createEventData(pointer)); + this._fireExit(enteredEntity, this._createEventData(pointer, enteredEntity, enteredEntity)); this._enteredEntity = null; } const draggedEntity = this._draggedEntity; if (draggedEntity) { - this._fireEndDrag(draggedEntity, this._createEventData(pointer)); + this._fireEndDrag(draggedEntity, this._createEventData(pointer, draggedEntity, draggedEntity)); this._draggedEntity = null; } this._pressedEntity = null; @@ -108,10 +108,10 @@ export class PhysicsPointerEventEmitter extends PointerEventEmitter { const enteredEntity = this._enteredEntity; if (entity !== enteredEntity) { if (enteredEntity) { - this._fireExit(enteredEntity, this._createEventData(pointer)); + this._fireExit(enteredEntity, this._createEventData(pointer, enteredEntity, enteredEntity)); } if (entity) { - this._fireEnter(entity, this._createEventData(pointer)); + this._fireEnter(entity, this._createEventData(pointer, entity, entity)); } this._enteredEntity = entity; } diff --git a/packages/core/src/input/pointer/emitter/PointerEventEmitter.ts b/packages/core/src/input/pointer/emitter/PointerEventEmitter.ts index 6f6c62cdfb..98188891e6 100644 --- a/packages/core/src/input/pointer/emitter/PointerEventEmitter.ts +++ b/packages/core/src/input/pointer/emitter/PointerEventEmitter.ts @@ -32,10 +32,12 @@ export abstract class PointerEventEmitter { protected abstract _init(): void; - protected _createEventData(pointer: Pointer): PointerEventData { + protected _createEventData(pointer: Pointer, target: Entity, currentTarget: Entity = target): PointerEventData { const data = this._pool.get(); data.pointer = pointer; data.worldPosition.copyFrom(this._hitResult.point); + data.target = target; + data.currentTarget = currentTarget; return data; } diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index e08e51ac4c..7eaf45be7b 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -1,6 +1,7 @@ import { BoundingBox } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; +import { RenderElement } from "../RenderPipeline/RenderElement"; import { Renderer, RendererUpdateFlags } from "../Renderer"; import { Logger } from "../base/Logger"; import { ignoreClone } from "../clone/CloneManager"; @@ -151,9 +152,10 @@ export class MeshRenderer extends Renderer { const { _materials: materials, _engine: engine } = this; const subMeshes = mesh.subMeshes; - const renderElement = engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); - const subRenderElementPool = engine._subRenderElementPool; + const priority = this.priority; + const distanceForSort = this._distanceForSort; + const renderElementPool = engine._renderElementPool; + const renderPipeline = context.camera._renderPipeline; for (let i = 0, n = subMeshes.length; i < n; i++) { let material = materials[i]; if (!material) { @@ -163,11 +165,38 @@ export class MeshRenderer extends Renderer { material = this.engine._basicResources.meshMagentaMaterial; } - const subRenderElement = subRenderElementPool.get(); - subRenderElement.set(this, material, mesh._primitive, subMeshes[i]); - renderElement.addSubRenderElement(subRenderElement); + const renderElement = renderElementPool.get(); + renderElement.set(this, material, mesh._primitive, subMeshes[i]); + renderElement.priority = priority; + renderElement.distanceForSort = distanceForSort; + renderPipeline.pushRenderElement(context, renderElement); } - context.camera._renderPipeline.pushRenderElement(context, renderElement); + } + + /** + * @internal + */ + override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { + if (!this._engine._hardwareRenderer.isWebGL2) return false; + return ( + preElement.primitive === curElement.primitive && + preElement.subPrimitive === curElement.subPrimitive && + preElement.material === curElement.material && + this._isFrontFaceInvert() === (curElement.component)._isFrontFaceInvert() && + this.shaderData._macroCollection.isEqual(curElement.component.shaderData._macroCollection) + ); + } + + /** + * @internal + */ + override _batch(preElement: RenderElement | null, curElement: RenderElement): void { + if (!preElement) return; + const renderers = preElement.instancedRenderers; + if (renderers.length === 0) { + renderers.push(preElement.component); + } + renderers.push(curElement.component); } private _setMesh(mesh: Mesh): void { diff --git a/packages/core/src/mesh/ModelMesh.ts b/packages/core/src/mesh/ModelMesh.ts index 27b92ced49..8ee92c4825 100644 --- a/packages/core/src/mesh/ModelMesh.ts +++ b/packages/core/src/mesh/ModelMesh.ts @@ -925,7 +925,7 @@ export class ModelMesh extends Mesh { return null; } if (!buffer.readable) { - throw "Not allowed to access data while vertex buffer readable is false."; + throw new Error("Not allowed to access data while vertex buffer readable is false."); } const vertexCount = this.vertexCount; diff --git a/packages/core/src/mesh/Skin.ts b/packages/core/src/mesh/Skin.ts index 7a9b3444b0..18489805c5 100644 --- a/packages/core/src/mesh/Skin.ts +++ b/packages/core/src/mesh/Skin.ts @@ -15,7 +15,7 @@ export class Skin extends EngineObject { inverseBindMatrices = new Array(); /** @internal */ - @ignoreClone + @deepClone _skinMatrices: Float32Array; /** @internal */ @ignoreClone diff --git a/packages/core/src/mesh/SkinnedMeshRenderer.ts b/packages/core/src/mesh/SkinnedMeshRenderer.ts index d8e83e16bb..4d7f726184 100644 --- a/packages/core/src/mesh/SkinnedMeshRenderer.ts +++ b/packages/core/src/mesh/SkinnedMeshRenderer.ts @@ -1,6 +1,7 @@ import { BoundingBox, Vector2 } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; +import { RenderElement } from "../RenderPipeline/RenderElement"; import { RendererUpdateFlags } from "../Renderer"; import { Logger } from "../base/Logger"; import { deepClone, ignoreClone } from "../clone/CloneManager"; @@ -120,6 +121,13 @@ export class SkinnedMeshRenderer extends MeshRenderer { localBounds.max._onValueChanged = this._onLocalBoundsChanged; } + /** + * @internal + */ + override _canBatch(_preElement: RenderElement, _curElement: RenderElement): boolean { + return false; + } + /** * @internal */ diff --git a/packages/core/src/particle/ParticleGenerator.ts b/packages/core/src/particle/ParticleGenerator.ts index d180c7f929..ecb488d8bf 100644 --- a/packages/core/src/particle/ParticleGenerator.ts +++ b/packages/core/src/particle/ParticleGenerator.ts @@ -112,6 +112,7 @@ export class ParticleGenerator { @ignoreClone _subPrimitive = new SubMesh(0, 0, MeshTopology.Triangles); /** @internal */ + @ignoreClone readonly _renderer: ParticleRenderer; /** @internal */ @@ -235,6 +236,8 @@ export class ParticleGenerator { } } else { this._isPlaying = false; + // Invalidate the rateOverDistance baseline so emitter movement during the stop + // interval doesn't burst on resume. if (stopMode === ParticleStopMode.StopEmittingAndClear) { this._clearActiveParticles(); this._playTime = 0; @@ -242,6 +245,8 @@ export class ParticleGenerator { this._firstActiveTransformedBoundingBox = this._firstFreeTransformedBoundingBox; this.emission._reset(); + } else { + this.emission._invalidateDistanceBaseline(); } } } @@ -257,37 +262,41 @@ export class ParticleGenerator { /** * @internal */ - _emit(playTime: number, count: number): void { - const { emission } = this; - if (emission.enabled) { - const { main } = this; - // Wait the existing particles to be retired - const notRetireParticleCount = this._getNotRetiredParticleCount(); - if (notRetireParticleCount >= main.maxParticles) { - return; - } - const position = ParticleGenerator._tempVector30; - const direction = ParticleGenerator._tempVector31; - const transform = this._renderer.entity.transform; - const shape = emission.shape; - const positionScale = main._getPositionScale(); - for (let i = 0; i < count; i++) { - if (shape?.enabled) { - shape._generatePositionAndDirection(emission._shapeRand, playTime, position, direction); - position.multiply(positionScale); - direction.normalize().multiply(positionScale); - } else { - position.set(0, 0, 0); - direction.set(0, 0, -1); - // Speed is scaled by shape scale in world simulation space - // So if no shape and in world simulation space, we shouldn't scale the speed - if (main.simulationSpace === ParticleSimulationSpace.Local) { - direction.multiply(positionScale); - } + _emit(playTime: number, count: number, emitWorldPositionOverride?: Vector3): number { + const { emission, main } = this; + if (!emission.enabled) { + return 0; + } + const budget = main.maxParticles - this._getNotRetiredParticleCount(); + if (count > budget) { + count = budget; + } + if (count <= 0) { + return 0; + } + + const position = ParticleGenerator._tempVector30; + const direction = ParticleGenerator._tempVector31; + const transform = this._renderer.entity.transform; + const shape = emission.shape; + const positionScale = main._getPositionScale(); + for (let i = 0; i < count; i++) { + if (shape?.enabled) { + shape._generatePositionAndDirection(emission._shapeRand, playTime, position, direction); + position.multiply(positionScale); + direction.normalize().multiply(positionScale); + } else { + position.set(0, 0, 0); + direction.set(0, 0, -1); + // Speed is scaled by shape scale in world simulation space + // So if no shape and in world simulation space, we shouldn't scale the speed + if (main.simulationSpace === ParticleSimulationSpace.Local) { + direction.multiply(positionScale); } - this._addNewParticle(position, direction, transform, playTime); } + this._addNewParticle(position, direction, transform, playTime, emitWorldPositionOverride); } + return count; } /** @@ -831,7 +840,13 @@ export class ParticleGenerator { } } - private _addNewParticle(position: Vector3, direction: Vector3, transform: Transform, playTime: number): void { + private _addNewParticle( + position: Vector3, + direction: Vector3, + transform: Transform, + playTime: number, + emitWorldPositionOverride?: Vector3 + ): void { const firstFreeElement = this._firstFreeElement; let nextFreeElement = firstFreeElement + 1; if (nextFreeElement >= this._currentParticleCount) { @@ -863,7 +878,7 @@ export class ParticleGenerator { let pos: Vector3, rot: Quaternion; if (main.simulationSpace === ParticleSimulationSpace.World) { - pos = transform.worldPosition; + pos = emitWorldPositionOverride ?? transform.worldPosition; rot = transform.worldRotationQuaternion; } @@ -1022,7 +1037,7 @@ export class ParticleGenerator { // Initialize feedback buffer for this particle if (this._useTransformFeedback) { - this._addFeedbackParticle(firstFreeElement, position, direction, startSpeed, transform); + this._addFeedbackParticle(firstFreeElement, position, direction, startSpeed, transform, pos); } this._firstFreeElement = nextFreeElement; @@ -1033,7 +1048,8 @@ export class ParticleGenerator { shapePosition: Vector3, direction: Vector3, startSpeed: number, - transform: Transform + transform: Transform, + emitWorldPosition?: Vector3 ): void { let position: Vector3; if (this.main.simulationSpace === ParticleSimulationSpace.Local) { @@ -1041,7 +1057,7 @@ export class ParticleGenerator { } else { position = ParticleGenerator._tempVector32; Vector3.transformByQuat(shapePosition, transform.worldRotationQuaternion, position); - position.add(transform.worldPosition); + position.add(emitWorldPosition ?? transform.worldPosition); } this._feedbackSimulator.writeParticleData( diff --git a/packages/core/src/particle/ParticleRenderer.ts b/packages/core/src/particle/ParticleRenderer.ts index 633e166989..ed6e4a7275 100644 --- a/packages/core/src/particle/ParticleRenderer.ts +++ b/packages/core/src/particle/ParticleRenderer.ts @@ -5,7 +5,7 @@ import { Renderer, RendererUpdateFlags } from "../Renderer"; import { TransformModifyFlags } from "../Transform"; import { GLCapabilityType } from "../base/Constant"; import { Logger } from "../base/Logger"; -import { deepClone, ignoreClone, shallowClone } from "../clone/CloneManager"; +import { assignmentClone, deepClone, ignoreClone, shallowClone } from "../clone/CloneManager"; import { ModelMesh } from "../mesh/ModelMesh"; import { ShaderMacro } from "../shader/ShaderMacro"; import { ShaderProperty } from "../shader/ShaderProperty"; @@ -47,7 +47,9 @@ export class ParticleRenderer extends Renderer { @ignoreClone _transformedBounds = new BoundingBox(); + @assignmentClone private _renderMode: ParticleRenderMode; + @assignmentClone private _currentRenderModeMacro: ShaderMacro; private _mesh: ModelMesh; private _supportInstancedArrays: boolean; @@ -174,9 +176,9 @@ export class ParticleRenderer extends Renderer { /** * @internal */ - override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean, batched: boolean): void { + override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean): void { //@todo: Don't need to update transform shader data, temp solution - super._updateTransformShaderData(context, onlyMVP, true); + this._updateWorldSpaceTransformShaderData(context, onlyMVP); } protected override _updateBounds(worldBounds: BoundingBox): void { const { generator } = this; @@ -251,13 +253,29 @@ export class ParticleRenderer extends Renderer { const engine = this._engine; const renderElement = engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); - const subRenderElement = engine._subRenderElementPool.get(); - subRenderElement.set(this, material, generator._primitive, generator._subPrimitive); - renderElement.addSubRenderElement(subRenderElement); + renderElement.set(this, material, generator._primitive, generator._subPrimitive); + renderElement.priority = this.priority; + renderElement.distanceForSort = this._distanceForSort; context.camera._renderPipeline.pushRenderElement(context, renderElement); } + /** + * @internal + */ + override _cloneTo(target: ParticleRenderer): void { + super._cloneTo(target); + + // ShaderData internals (_macroCollection, _propertyValueMap) are @ignoreClone, + // so the cloned shaderData only has the constructor-set Billboard macro. + // Must re-apply the source's shaderData state. + this.shaderData.cloneTo(target.shaderData); + + // Rebuild GPU resources to match cloned renderMode/mesh/maxParticles. + const gen = target.generator; + gen._reorganizeGeometryBuffers(); + gen._resizeInstanceBuffer(true, gen.main.maxParticles); + } + protected override _onDestroy(): void { const mesh = this._mesh; if (mesh) { diff --git a/packages/core/src/particle/modules/EmissionModule.ts b/packages/core/src/particle/modules/EmissionModule.ts index 108832c38c..292205f0f1 100644 --- a/packages/core/src/particle/modules/EmissionModule.ts +++ b/packages/core/src/particle/modules/EmissionModule.ts @@ -1,7 +1,8 @@ -import { Rand } from "@galacean/engine-math"; +import { MathUtil, Rand, Vector3 } from "@galacean/engine-math"; import { deepClone, ignoreClone } from "../../clone/CloneManager"; import { ShaderMacro } from "../../shader/ShaderMacro"; import { ParticleRandomSubSeeds } from "../enums/ParticleRandomSubSeeds"; +import { ParticleSimulationSpace } from "../enums/ParticleSimulationSpace"; import { Burst } from "./Burst"; import { ParticleCompositeCurve } from "./ParticleCompositeCurve"; import { ParticleGeneratorModule } from "./ParticleGeneratorModule"; @@ -14,6 +15,8 @@ export class EmissionModule extends ParticleGeneratorModule { /** @internal */ static readonly _emissionShapeMacro = ShaderMacro.getByName("RENDERER_EMISSION_SHAPE"); + private static _tempEmitPosition = new Vector3(); + /** The rate of particle emission. */ @deepClone rateOverTime: ParticleCompositeCurve = new ParticleCompositeCurve(10); @@ -29,6 +32,13 @@ export class EmissionModule extends ParticleGeneratorModule { /** @internal */ _frameRateTime: number = 0; + @ignoreClone + private _distanceAccumulator = 0; + @ignoreClone + private _lastEmitPosition = new Vector3(); + @ignoreClone + private _hasLastEmitPosition = false; + @deepClone private _bursts: Burst[] = []; @@ -130,6 +140,7 @@ export class EmissionModule extends ParticleGeneratorModule { */ _emit(lastPlayTime: number, playTime: number): void { this._emitByRateOverTime(playTime); + this._emitByRateOverDistance(lastPlayTime, playTime); this._emitByBurst(lastPlayTime, playTime); } @@ -147,6 +158,15 @@ export class EmissionModule extends ParticleGeneratorModule { _reset(): void { this._frameRateTime = 0; this._currentBurstIndex = 0; + this._invalidateDistanceBaseline(); + } + + /** + * @internal + */ + _invalidateDistanceBaseline(): void { + this._hasLastEmitPosition = false; + this._distanceAccumulator = 0; } /** @@ -171,6 +191,65 @@ export class EmissionModule extends ParticleGeneratorModule { } } + private _emitByRateOverDistance(lastPlayTime: number, playTime: number): void { + const ratePerUnit = this.rateOverDistance.evaluate(undefined, undefined); + const generator = this._generator; + + if (ratePerUnit <= 0) { + this._invalidateDistanceBaseline(); + return; + } + if (!this._hasLastEmitPosition) { + this._lastEmitPosition.copyFrom(generator._renderer.entity.transform.worldPosition); + this._hasLastEmitPosition = true; + return; + } + + const lastPos = this._lastEmitPosition; + const currentPos = generator._renderer.entity.transform.worldPosition; + const dx = currentPos.x - lastPos.x; + const dy = currentPos.y - lastPos.y; + const dz = currentPos.z - lastPos.z; + const moveLength = Math.sqrt(dx * dx + dy * dy + dz * dz); + this._distanceAccumulator += moveLength; + + const emitInterval = 1.0 / ratePerUnit; + // `+ zeroTolerance` absorbs float divide error so an exact `N*interval` accumulator doesn't drop 1 + const count = Math.floor(this._distanceAccumulator / emitInterval + MathUtil.zeroTolerance); + + if (count > 0) { + this._distanceAccumulator -= count * emitInterval; + // `subFrameAge ∈ [0, 1]`: how far back into the frame a particle was born + // (0 = newest at currentPos/playTime, 1 = oldest at lastPos/lastPlayTime). + // The initial clamp protects two edges — moveLength ≈ 0 (collapse to frame-end + // emit) and a tiny moveLength near the emitInterval edge (would put age > 1). + // Local simulation space ignores the position override but still uses emitTime. + const isWorld = generator.main.simulationSpace === ParticleSimulationSpace.World; + const invMoveLength = moveLength > MathUtil.zeroTolerance ? 1.0 / moveLength : 0; + const ageStep = emitInterval * invMoveLength; + const dt = playTime - lastPlayTime; + let subFrameAge = Math.min(this._distanceAccumulator * invMoveLength, 1.0); + const emitPos = EmissionModule._tempEmitPosition; + for (let i = 0; i < count; i++) { + if (isWorld) { + emitPos.set( + currentPos.x - dx * subFrameAge, + currentPos.y - dy * subFrameAge, + currentPos.z - dz * subFrameAge + ); + } + if (generator._emit(playTime - dt * subFrameAge, 1, isWorld ? emitPos : undefined) === 0) { + // Buffer full: settle the frame's distance budget instead of carrying it over + this._distanceAccumulator = 0; + break; + } + subFrameAge += ageStep; + } + } + + lastPos.copyFrom(currentPos); + } + private _emitByBurst(lastPlayTime: number, playTime: number): void { const main = this._generator.main; const duration = main.duration; diff --git a/packages/core/src/physics/CharacterController.ts b/packages/core/src/physics/CharacterController.ts index f3c9e6f048..ea6961b70f 100644 --- a/packages/core/src/physics/CharacterController.ts +++ b/packages/core/src/physics/CharacterController.ts @@ -1,5 +1,5 @@ import { ICharacterController } from "@galacean/engine-design"; -import { Vector3 } from "@galacean/engine-math"; +import { Quaternion, Vector3 } from "@galacean/engine-math"; import { Engine } from "../Engine"; import { Entity } from "../Entity"; import { Collider } from "./Collider"; @@ -162,6 +162,10 @@ export class CharacterController extends Collider { (this._nativeCollider).setSlopeLimit(this._slopeLimit); } + protected override _teleportToEntityTransform(worldPosition: Vector3, _worldRotation: Quaternion): void { + (this._nativeCollider).setWorldPosition(worldPosition); + } + private _syncWorldPositionFromPhysicalSpace(): void { (this._nativeCollider).getWorldPosition(this.entity.transform.worldPosition); } diff --git a/packages/core/src/physics/Collider.ts b/packages/core/src/physics/Collider.ts index a8a591fe56..5afa164403 100644 --- a/packages/core/src/physics/Collider.ts +++ b/packages/core/src/physics/Collider.ts @@ -1,4 +1,5 @@ import { ICollider, IStaticCollider } from "@galacean/engine-design"; +import { Quaternion, Vector3 } from "@galacean/engine-math"; import { BoolUpdateFlag } from "../BoolUpdateFlag"; import { deepClone, ignoreClone } from "../clone/CloneManager"; import { ICustomClone } from "../clone/ComponentCloner"; @@ -28,6 +29,16 @@ export class Collider extends Component implements ICustomClone { protected _shapes: ColliderShape[] = []; protected _collisionLayerIndex: number = 0; + /** + * Disabling a collider only detaches its native actor from the simulation + * scene; the actor is not destroyed, so on re-enable it still holds its + * pre-disable pose. The first transform sync after (re-)enable must teleport, + * never sweep — otherwise a kinematic actor drags across the scene from that + * stale pose and fires spurious contacts along the path. + */ + @ignoreClone + private _pendingReenterTeleport: boolean = false; + /** * The shapes of this collider. */ @@ -108,20 +119,29 @@ export class Collider extends Component implements ICustomClone { * @internal */ _onUpdate(): void { - if (this._updateFlag.flag) { + const shapes = this._shapes; + if (this._pendingReenterTeleport || this._updateFlag.flag) { const { transform } = this.entity; - (this._nativeCollider).setWorldTransform( - transform.worldPosition, - transform.worldRotationQuaternion - ); + if (this._pendingReenterTeleport) { + this._teleportToEntityTransform(transform.worldPosition, transform.worldRotationQuaternion); + this._pendingReenterTeleport = false; + } else { + this._syncEntityTransformToNative(transform.worldPosition, transform.worldRotationQuaternion); + } const worldScale = transform.lossyWorldScale; - const shapes = this._shapes; for (let i = 0, n = shapes.length; i < n; i++) { shapes[i]._nativeShape?.setWorldScale(worldScale); } this._updateFlag.flag = false; } + + // Drive per-shape physics update (e.g. MeshColliderShape retries pending + // native shape creation when mesh data becomes accessible asynchronously). + // No-op for shape types that don't override `_onPhysicsUpdate`. + for (let i = 0, n = shapes.length; i < n; i++) { + shapes[i]._onPhysicsUpdate(); + } } /** @@ -134,6 +154,7 @@ export class Collider extends Component implements ICustomClone { */ override _onEnableInScene(): void { this.scene.physics._addCollider(this); + this._pendingReenterTeleport = true; } /** @@ -164,6 +185,32 @@ export class Collider extends Component implements ICustomClone { this._addNativeShape(this.shapes[i]); } this._setCollisionLayer(); + // Teleport native actor to entity's current world pose. + // The native actor was created in constructor() with the entity's then-current + // worldPosition/Rotation. On clone, the entity's transform fields are deep-cloned + // AFTER the Component (and its native actor) are constructed, so the native actor's + // pose lags behind the cloned entity transform until this sync. + const { transform } = this.entity; + this._teleportToEntityTransform(transform.worldPosition, transform.worldRotationQuaternion); + } + + /** + * Teleport native actor to a world pose (instant, no implied velocity). + * Used during initialization paths (clone) where the native actor must be re-aligned + * with the entity transform after construction-time pose was based on stale defaults. + */ + protected _teleportToEntityTransform(worldPosition: Vector3, worldRotation: Quaternion): void { + (this._nativeCollider).setWorldTransform(worldPosition, worldRotation); + } + + /** + * Sync entity world transform to native actor for per-frame updates. + * Default semantics: teleport (setGlobalPose). Subclasses override to express + * physics-aware movement (e.g. DynamicCollider routes kinematic actors through + * setKinematicTarget to generate contact events on swept motion). + */ + protected _syncEntityTransformToNative(worldPosition: Vector3, worldRotation: Quaternion): void { + (this._nativeCollider).setWorldTransform(worldPosition, worldRotation); } /** diff --git a/packages/core/src/physics/Collision.ts b/packages/core/src/physics/Collision.ts index 16edfd5bb3..440864e4fe 100644 --- a/packages/core/src/physics/Collision.ts +++ b/packages/core/src/physics/Collision.ts @@ -29,8 +29,7 @@ export class Collision { */ getContacts(outContacts: ContactPoint[]): number { const nativeCollision = this._nativeCollision; - const smallerShapeId = Math.min(nativeCollision.shape0Id, nativeCollision.shape1Id); - const factor = this.shape.id === smallerShapeId ? 1 : -1; + const factor = this.shape.id === nativeCollision.shape1Id ? 1 : -1; const nativeContactPoints = nativeCollision.getContacts(); const length = nativeContactPoints.size(); for (let i = 0; i < length; i++) { diff --git a/packages/core/src/physics/DynamicCollider.ts b/packages/core/src/physics/DynamicCollider.ts index 380a3b927f..debf078b14 100644 --- a/packages/core/src/physics/DynamicCollider.ts +++ b/packages/core/src/physics/DynamicCollider.ts @@ -13,6 +13,8 @@ import { MeshColliderShape } from "./shape/MeshColliderShape"; */ export class DynamicCollider extends Collider { private static _tempVector3 = new Vector3(); + private static _tempVector3_1 = new Vector3(); + private static _tempVector3_2 = new Vector3(); private static _tempQuat = new Quaternion(); private _linearDamping = 0; @@ -33,7 +35,9 @@ export class DynamicCollider extends Collider { private _isKinematic = false; private _constraints: DynamicColliderConstraints = 0; private _collisionDetectionMode: CollisionDetectionMode = CollisionDetectionMode.Discrete; - private _sleepThreshold = 5e-3; + private _kinematicTransformSyncMode: DynamicColliderKinematicTransformSyncMode = + DynamicColliderKinematicTransformSyncMode.Target; + private _sleepThreshold: number | undefined; private _automaticCenterOfMass = true; private _automaticInertiaTensor = true; @@ -223,7 +227,7 @@ export class DynamicCollider extends Collider { * The mass-normalized energy threshold, below which objects start going to sleep. */ get sleepThreshold(): number { - return this._sleepThreshold; + return this._sleepThreshold ?? Engine._nativePhysics?.getDefaultSleepThreshold?.() ?? 5e-3; } set sleepThreshold(value: number) { @@ -325,6 +329,22 @@ export class DynamicCollider extends Collider { } } + /** + * Controls how entity transform changes are synchronized to a kinematic native actor. + * + * @remarks + * `Target` routes transform changes through {@link move}, so PhysX treats the + * actor as moving between frames and can generate swept contacts. `Teleport` + * writes the native pose directly and does not imply velocity. + */ + get kinematicTransformSyncMode(): DynamicColliderKinematicTransformSyncMode { + return this._kinematicTransformSyncMode; + } + + set kinematicTransformSyncMode(value: DynamicColliderKinematicTransformSyncMode) { + this._kinematicTransformSyncMode = value; + } + /** * @internal */ @@ -367,6 +387,33 @@ export class DynamicCollider extends Collider { this._phasedActiveInScene && (this._nativeCollider).addTorque(torque); } + /** + * Apply a force to the DynamicCollider at a given position in world space. + * The force generates both linear acceleration through the center of mass and angular + * acceleration about it (torque = (position - centerOfMass) × force). + * @param force - The force to apply, in world space + * @param position - The position where the force is applied, in world space + */ + applyForceAtPosition(force: Vector3, position: Vector3): void { + if (!this._phasedActiveInScene) return; + const nativeCollider = this._nativeCollider; + + const localCoM = DynamicCollider._tempVector3; + nativeCollider.getCenterOfMass(localCoM); + + const transform = this.entity.transform; + const worldCoM = DynamicCollider._tempVector3_1; + Vector3.transformByQuat(localCoM, transform.worldRotationQuaternion, worldCoM); + worldCoM.add(transform.worldPosition); + + const torque = DynamicCollider._tempVector3_2; + Vector3.subtract(position, worldCoM, torque); + Vector3.cross(torque, force, torque); + + nativeCollider.addForce(force); + nativeCollider.addTorque(torque); + } + /** * Moves the kinematic collider to the specified position. * @remarks Only available when {@link isKinematic} is true. @@ -433,6 +480,30 @@ export class DynamicCollider extends Collider { super.addShape(shape); } + /** + * Route per-frame entity → native transform sync to the correct physics API based + * on kinematic state. + * + * PhysX 4.x docs (PxRigidDynamic): + * "If you intend to move a kinematic actor with [setGlobalPose] and want + * collision detection, use setKinematicTarget() instead." + * + * setGlobalPose is a teleport: PhysX skips contact detection between the old + * and new pose. setKinematicTarget tells PhysX the actor is animating to the + * target during the next simulate(), enabling swept contacts. Some compatibility + * layers need transform writes to stay teleport-like, so the sync mode is + * explicit while {@link move} always keeps target semantics. + * + * @internal + */ + protected override _syncEntityTransformToNative(worldPosition: Vector3, worldRotation: Quaternion): void { + if (this._isKinematic && this._kinematicTransformSyncMode === DynamicColliderKinematicTransformSyncMode.Target) { + (this._nativeCollider).move(worldPosition, worldRotation); + } else { + super._syncEntityTransformToNative(worldPosition, worldRotation); + } + } + /** * @internal */ @@ -460,6 +531,7 @@ export class DynamicCollider extends Collider { target._angularVelocity.copyFrom(this.angularVelocity); target._centerOfMass.copyFrom(this.centerOfMass); target._inertiaTensor.copyFrom(this.inertiaTensor); + target._kinematicTransformSyncMode = this._kinematicTransformSyncMode; super._cloneTo(target); } @@ -489,7 +561,9 @@ export class DynamicCollider extends Collider { } (this._nativeCollider).setMaxAngularVelocity(this._maxAngularVelocity); (this._nativeCollider).setMaxDepenetrationVelocity(this._maxDepenetrationVelocity); - (this._nativeCollider).setSleepThreshold(this._sleepThreshold); + if (this._sleepThreshold !== undefined) { + (this._nativeCollider).setSleepThreshold(this._sleepThreshold); + } (this._nativeCollider).setSolverIterations(this._solverIterations); (this._nativeCollider).setUseGravity(this._useGravity); (this._nativeCollider).setIsKinematic(this._isKinematic); @@ -555,6 +629,16 @@ export enum CollisionDetectionMode { ContinuousSpeculative } +/** + * Kinematic transform synchronization mode. + */ +export enum DynamicColliderKinematicTransformSyncMode { + /** Synchronize transform changes through PhysX setKinematicTarget. */ + Target, + /** Synchronize transform changes by directly teleporting the native actor. */ + Teleport +} + /** * Use these flags to constrain motion of dynamic collider. */ diff --git a/packages/core/src/physics/PhysicsMaterial.ts b/packages/core/src/physics/PhysicsMaterial.ts index 3ac16e66d8..aa22bc8c24 100644 --- a/packages/core/src/physics/PhysicsMaterial.ts +++ b/packages/core/src/physics/PhysicsMaterial.ts @@ -1,6 +1,7 @@ import { IPhysicsMaterial } from "@galacean/engine-design"; import { Engine } from "../Engine"; import { PhysicsMaterialCombineMode } from "./enums/PhysicsMaterialCombineMode"; +import { ignoreClone } from "../clone/CloneManager"; /** * Material class to represent a set of surface properties. @@ -14,6 +15,7 @@ export class PhysicsMaterial { private _destroyed: boolean; /** @internal */ + @ignoreClone _nativeMaterial: IPhysicsMaterial; constructor() { @@ -21,8 +23,8 @@ export class PhysicsMaterial { this._staticFriction, this._dynamicFriction, this._bounciness, - this._bounceCombine, - this._frictionCombine + this._frictionCombine, + this._bounceCombine ); } @@ -103,4 +105,23 @@ export class PhysicsMaterial { !this._destroyed && this._nativeMaterial.destroy(); this._destroyed = true; } + + /** + * @internal + */ + _cloneTo(target: PhysicsMaterial): void { + target._syncNative(); + } + + /** + * @internal + */ + _syncNative(): void { + const nativeMaterial = this._nativeMaterial; + nativeMaterial.setStaticFriction(this._staticFriction); + nativeMaterial.setDynamicFriction(this._dynamicFriction); + nativeMaterial.setBounciness(this._bounciness); + nativeMaterial.setFrictionCombine(this._frictionCombine); + nativeMaterial.setBounceCombine(this._bounceCombine); + } } diff --git a/packages/core/src/physics/PhysicsScene.ts b/packages/core/src/physics/PhysicsScene.ts index b1f96d77dd..ce6bf2e4e8 100644 --- a/packages/core/src/physics/PhysicsScene.ts +++ b/packages/core/src/physics/PhysicsScene.ts @@ -835,7 +835,7 @@ export class PhysicsScene { if (!shape) { return false; } - return shape.collider.entity.layer & mask && shape.isSceneQuery; + return shape.collider.collisionLayer & mask && shape.isSceneQuery; }; } diff --git a/packages/core/src/physics/index.ts b/packages/core/src/physics/index.ts index 537bd367d6..f2d4cdf576 100644 --- a/packages/core/src/physics/index.ts +++ b/packages/core/src/physics/index.ts @@ -1,6 +1,11 @@ export { CharacterController } from "./CharacterController"; export { Collider } from "./Collider"; -export { CollisionDetectionMode, DynamicCollider, DynamicColliderConstraints } from "./DynamicCollider"; +export { + CollisionDetectionMode, + DynamicCollider, + DynamicColliderConstraints, + DynamicColliderKinematicTransformSyncMode +} from "./DynamicCollider"; export { HitResult } from "./HitResult"; export { PhysicsMaterial } from "./PhysicsMaterial"; export { PhysicsScene } from "./PhysicsScene"; diff --git a/packages/core/src/physics/shape/ColliderShape.ts b/packages/core/src/physics/shape/ColliderShape.ts index 2ac82319fd..e74736ff64 100644 --- a/packages/core/src/physics/shape/ColliderShape.ts +++ b/packages/core/src/physics/shape/ColliderShape.ts @@ -27,7 +27,7 @@ export abstract class ColliderShape implements ICustomClone { private _rotation: Vector3 = new Vector3(); @deepClone private _position: Vector3 = new Vector3(); - private _contactOffset: number = 0.02; + private _contactOffset: number | undefined; /** * @internal @@ -55,7 +55,7 @@ export abstract class ColliderShape implements ICustomClone { * @defaultValue 0.02 */ get contactOffset(): number { - return this._contactOffset; + return this._contactOffset ?? Engine._nativePhysics?.getDefaultContactOffset?.() ?? 0.02; } set contactOffset(value: number) { @@ -168,6 +168,18 @@ export abstract class ColliderShape implements ICustomClone { target._syncNative(); } + /** + * @internal + * + * Called once per physics update tick by `Collider._onUpdate`. Base no-op. + * + * Subclasses can override for frame-driven maintenance. Currently used by + * `MeshColliderShape` to retry native shape creation when mesh data becomes + * accessible later or PhysX cooking previously failed due to transient state + * (async resource loading, cook pipeline warmup, etc.). + */ + _onPhysicsUpdate(): void {} + /** * @internal */ @@ -183,7 +195,9 @@ export abstract class ColliderShape implements ICustomClone { if (!this._nativeShape) return; this._nativeShape.setPosition(this._position); this._nativeShape.setRotation(this._rotation); - this._nativeShape.setContactOffset(this._contactOffset); + if (this._contactOffset !== undefined) { + this._nativeShape.setContactOffset(this._contactOffset); + } this._nativeShape.setIsTrigger(this._isTrigger); this._nativeShape.setMaterial(this._material._nativeMaterial); diff --git a/packages/core/src/physics/shape/MeshColliderShape.ts b/packages/core/src/physics/shape/MeshColliderShape.ts index 51a46ab0e0..83935fe10c 100644 --- a/packages/core/src/physics/shape/MeshColliderShape.ts +++ b/packages/core/src/physics/shape/MeshColliderShape.ts @@ -16,6 +16,12 @@ export class MeshColliderShape extends ColliderShape { private _indices: Uint8Array | Uint16Array | Uint32Array | null = null; private _cookingFlags = MeshColliderShapeCookingFlag.Cleaning | MeshColliderShapeCookingFlag.VertexWelding; private _isShapeAttached = false; + /** + * `true` if a native shape creation was attempted but failed (mesh data not yet + * accessible, PhysX cooking transient failure, etc.). The `_onPhysicsUpdate` hook + * will keep retrying every frame until creation succeeds. + */ + private _pendingNativeShapeCreation = false; /** * Cooking flags for this mesh collider shape. @@ -74,15 +80,25 @@ export class MeshColliderShape extends ColliderShape { this._mesh?._addReferCount(-1); value?._addReferCount(1); this._mesh = value; - if (value && this._extractMeshData(value)) { - if (this._nativeShape) { - this._updateNativeShapeData(); + if (value) { + if (this._extractMeshData(value)) { + if (this._nativeShape) { + this._updateNativeShapeData(); + } else { + this._createNativeShape(); + } + // _createNativeShape can fail silently (cookMesh transient failure); mark pending if so + this._pendingNativeShapeCreation = !this._nativeShape; } else { - this._createNativeShape(); + // Mesh not yet accessible — keep pending so `_onPhysicsUpdate` retries later + this._destroyNativeShape(); + this._clearMeshData(); + this._pendingNativeShapeCreation = true; } } else { this._destroyNativeShape(); this._clearMeshData(); + this._pendingNativeShapeCreation = false; } } } @@ -197,10 +213,13 @@ export class MeshColliderShape extends ColliderShape { ); if (!nativeShape) { + // Cook failed — `_onPhysicsUpdate` will retry next frame + this._pendingNativeShapeCreation = true; return; } this._nativeShape = nativeShape; + this._pendingNativeShapeCreation = false; // Sync base class properties (position, rotation, contactOffset, isTrigger, material) super._syncNative(); @@ -211,4 +230,38 @@ export class MeshColliderShape extends ColliderShape { this._attachToCollider(); } } + + /** + * @internal + * Retry hook: keep attempting `_createNativeShape` until it succeeds. + * + * Triggered every physics update tick by `Collider._onUpdate`. Handles the case + * where `_cookMesh` fails on first attempt due to PhysX cooking pipeline timing + * (the mesh data was extracted successfully at `set mesh` time, but the native + * cook call returned null). No-op once a valid native shape exists. + * + * We DO NOT re-call `_extractMeshData` here — once `set mesh` finished, either: + * - extraction succeeded → `_positions` is populated and we reuse it + * - extraction failed → `_clearMeshData` cleared `_positions`, and `mesh.accessible` + * won't recover (GPU upload is one-way), so re-extracting would fail again + */ + override _onPhysicsUpdate(): void { + if (!this._pendingNativeShapeCreation || !this._mesh || !this._positions) return; + this._createNativeShape(); + } + + /** + * @internal + * After CloneManager deep-copies `_positions` / `_indices` / `_mesh` and remaps `_collider`, + * the cloned shape still has no native PhysX shape because `_nativeShape` is `@ignoreClone`. + * Cook a fresh native shape now using the already-cloned vertex/index buffers; on transient + * cook failure `_createNativeShape` sets `_pendingNativeShapeCreation = true` and + * `_onPhysicsUpdate` will retry next tick. + */ + override _cloneTo(target: MeshColliderShape): void { + super._cloneTo(target); + if (target._positions) { + target._createNativeShape(); + } + } } diff --git a/packages/core/src/shader/Shader.ts b/packages/core/src/shader/Shader.ts index bbdbc23de4..80de735c90 100644 --- a/packages/core/src/shader/Shader.ts +++ b/packages/core/src/shader/Shader.ts @@ -210,12 +210,12 @@ export class Shader implements IReferable { const passes = subShaders[i].passes; for (let j = 0, m = passes.length; j < m; j++) { const pass = passes[j]; - const passShaderProgramPools = pass._shaderProgramPools; - for (let k = passShaderProgramPools.length - 1; k >= 0; k--) { - const shaderProgramPool = passShaderProgramPools[k]; - if (shaderProgramPool.engine !== engine) continue; - shaderProgramPool._destroy(); - passShaderProgramPools.splice(k, 1); + const passShaderProgramMaps = pass._shaderProgramMaps; + for (let k = passShaderProgramMaps.length - 1; k >= 0; k--) { + const map = passShaderProgramMaps[k]; + if (map.engine !== engine) continue; + map.destroy(); + passShaderProgramMaps.splice(k, 1); } } } diff --git a/packages/core/src/shader/ShaderBlockProperty.ts b/packages/core/src/shader/ShaderBlockProperty.ts new file mode 100644 index 0000000000..fd47b4cecc --- /dev/null +++ b/packages/core/src/shader/ShaderBlockProperty.ts @@ -0,0 +1,28 @@ +/** + * @internal + * Shader block property, used to identify uniform blocks by id. + */ +export class ShaderBlockProperty { + private static _counter = 0; + private static _nameMap: Record = Object.create(null); + + /** + * Get shader block property by name. + * @param name - Name of the uniform block + * @returns Shader block property + */ + static getByName(name: string): ShaderBlockProperty { + const nameMap = ShaderBlockProperty._nameMap; + return (nameMap[name] ??= new ShaderBlockProperty(name)); + } + + /** Uniform block name. */ + readonly name: string; + /** @internal */ + readonly _uniqueId: number; + + private constructor(name: string) { + this.name = name; + this._uniqueId = ShaderBlockProperty._counter++; + } +} diff --git a/packages/core/src/shader/ShaderData.ts b/packages/core/src/shader/ShaderData.ts index 72a2f096f0..f2a8a7ccd5 100644 --- a/packages/core/src/shader/ShaderData.ts +++ b/packages/core/src/shader/ShaderData.ts @@ -607,7 +607,11 @@ export class ShaderData implements IReferable, IClone { cloneTo(target: ShaderData): void { CloneManager.deepCloneObject(this._macroCollection, target._macroCollection, new Map()); - Object.assign(target._macroMap, this._macroMap); + const targetMacroMap = target._macroMap; + for (const key in targetMacroMap) { + delete targetMacroMap[key]; + } + Object.assign(targetMacroMap, this._macroMap); const referCount = target._getReferCount(); const propertyValueMap = this._propertyValueMap; const targetPropertyValueMap = target._propertyValueMap; diff --git a/packages/core/src/shader/ShaderMacroCollection.ts b/packages/core/src/shader/ShaderMacroCollection.ts index 749abc22ee..3efb98b77c 100644 --- a/packages/core/src/shader/ShaderMacroCollection.ts +++ b/packages/core/src/shader/ShaderMacroCollection.ts @@ -158,6 +158,20 @@ export class ShaderMacroCollection { return (this._mask[index] & macro._maskValue) !== 0; } + /** + * Whether this macro collection is equal to another. + * @param other - macro collection to compare + */ + isEqual(other: ShaderMacroCollection): boolean { + if (this._length !== other._length) return false; + const mask = this._mask; + const otherMask = other._mask; + for (let i = 0, n = this._length; i < n; i++) { + if (mask[i] !== otherMask[i]) return false; + } + return true; + } + /** * Clear this macro collection. */ diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 8c11182df7..6dc63f0220 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -1,13 +1,14 @@ import { Engine } from "../Engine"; +import { InstanceBuffer } from "../RenderPipeline/InstanceBuffer"; import { PipelineStage } from "../RenderPipeline/enums/PipelineStage"; import { GLCapabilityType } from "../base/Constant"; -import { ShaderFactory } from "../shaderlib"; +import { ShaderFactory, InstanceBufferLayout } from "../shaderlib/ShaderFactory"; import { Shader } from "./Shader"; import { ShaderMacro } from "./ShaderMacro"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderPart } from "./ShaderPart"; +import { ShaderProgramMap } from "./ShaderProgramMap"; import { ShaderProgram } from "./ShaderProgram"; -import { ShaderProgramPool } from "./ShaderProgramPool"; import { ShaderProperty } from "./ShaderProperty"; import { ShaderLanguage } from "./enums/ShaderLanguage"; import { RenderState } from "./state/RenderState"; @@ -47,7 +48,7 @@ export class ShaderPass extends ShaderPart { /** @internal */ _renderStateDataMap: Record = {}; /** @internal */ - _shaderProgramPools: ShaderProgramPool[] = []; + _shaderProgramMaps: ShaderProgramMap[] = []; private _vertexSource: string; private _fragmentSource: string; @@ -110,15 +111,15 @@ export class ShaderPass extends ShaderPart { * @internal */ _getShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { - const shaderProgramPool = engine._getShaderProgramPool(this._shaderPassId, this._shaderProgramPools); - let shaderProgram = shaderProgramPool.get(macroCollection); + const shaderProgramMap = engine._getShaderProgramMap(this._shaderPassId, this._shaderProgramMaps); + let shaderProgram = shaderProgramMap.get(macroCollection); if (shaderProgram) { return shaderProgram; } - shaderProgram = this._getCanonicalShaderProgram(engine, macroCollection); + shaderProgram = this._compileShaderProgram(engine, macroCollection); - shaderProgramPool.cache(shaderProgram); + shaderProgramMap.cache(shaderProgram); return shaderProgram; } @@ -126,32 +127,46 @@ export class ShaderPass extends ShaderPart { * @internal */ _destroy(): void { - const shaderProgramPools = this._shaderProgramPools; - for (let i = 0, n = shaderProgramPools.length; i < n; i++) { - const shaderProgramPool = shaderProgramPools[i]; - shaderProgramPool._destroy(); - delete shaderProgramPool.engine._shaderProgramPools[this._shaderPassId]; + const shaderProgramMaps = this._shaderProgramMaps; + for (let i = 0, n = shaderProgramMaps.length; i < n; i++) { + const map = shaderProgramMaps[i]; + map.destroy(); + delete map.engine._shaderProgramMaps[this._shaderPassId]; } - // Clear array storing multiple engine shader program pools - shaderProgramPools.length = 0; + shaderProgramMaps.length = 0; } - private _getCanonicalShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { - if (this._platformTarget != undefined) { - return this._getShaderLabProgram(engine, macroCollection); - } + private _compileShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { + const isGPUInstance = macroCollection.isEnable(InstanceBuffer.gpuInstanceMacro); + const { vertexSource, fragmentSource, instanceLayout } = + this._platformTarget != undefined + ? this._compileShaderLabSource(engine, macroCollection, isGPUInstance) + : this._compilePlatformSource(engine, macroCollection, isGPUInstance); + + const program = new ShaderProgram(engine, vertexSource, fragmentSource); + program._instanceLayout = instanceLayout; + return program; + } - const { vertexSource, fragmentSource } = ShaderFactory.compilePlatformSource( + private _compilePlatformSource( + engine: Engine, + macroCollection: ShaderMacroCollection, + isGPUInstance: boolean + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { + return ShaderFactory.compilePlatformSource( engine, macroCollection, this._vertexSource, - this._fragmentSource + this._fragmentSource, + isGPUInstance ); - - return new ShaderProgram(engine, vertexSource, fragmentSource); } - private _getShaderLabProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { + private _compileShaderLabSource( + engine: Engine, + macroCollection: ShaderMacroCollection, + isGPUInstance: boolean + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { const isWebGL2: boolean = engine._hardwareRenderer.isWebGL2; const shaderMacroList = new Array(); ShaderMacro._getMacrosElements(macroCollection, shaderMacroList); @@ -169,6 +184,14 @@ export class ShaderPass extends ShaderPart { noIncludeVertex = Shader._shaderLab._parseMacros(noIncludeVertex, shaderMacroList); noIncludeFrag = Shader._shaderLab._parseMacros(noIncludeFrag, shaderMacroList); + let instanceLayout: InstanceBufferLayout | null = null; + if (isGPUInstance) { + const injected = ShaderFactory.injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag); + noIncludeVertex = injected.vertexSource; + noIncludeFrag = injected.fragmentSource; + instanceLayout = injected.instanceLayout; + } + if (isWebGL2 && this._platformTarget === ShaderLanguage.GLSLES100) { noIncludeVertex = ShaderFactory.convertTo300(noIncludeVertex); noIncludeFrag = ShaderFactory.convertTo300(noIncludeFrag, true); @@ -176,15 +199,16 @@ export class ShaderPass extends ShaderPart { const versionStr = isWebGL2 ? "#version 300 es" : "#version 100"; - const vertexSource = ` ${versionStr} + return { + vertexSource: ` ${versionStr} ${noIncludeVertex} - `; - const fragmentSource = ` ${versionStr} - ${isWebGL2 ? "" : ShaderFactory._shaderExtension} + `, + fragmentSource: ` ${versionStr} + ${isWebGL2 ? "" : ShaderFactory.shaderExtension} ${precisionStr} ${noIncludeFrag} - `; - - return new ShaderProgram(engine, vertexSource, fragmentSource); + `, + instanceLayout + }; } } diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index 22151e5f77..9548548db6 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -7,7 +7,9 @@ import { ShaderData } from "./ShaderData"; import { ShaderProperty } from "./ShaderProperty"; import { ShaderUniform } from "./ShaderUniform"; import { ShaderUniformBlock } from "./ShaderUniformBlock"; +import { ShaderBlockProperty } from "./ShaderBlockProperty"; import { ShaderDataGroup } from "./enums/ShaderDataGroup"; +import { InstanceBufferLayout, ShaderFactory } from "../shaderlib/ShaderFactory"; /** * Shader program, corresponding to the GPU shader program. @@ -52,7 +54,11 @@ export class ShaderProgram { /** @internal */ _uploadMaterialId: number = -1; + /** @internal */ + _instanceLayout: InstanceBufferLayout | null = null; + attributeLocation: Record = Object.create(null); + uniformBlockIds: number[] = []; // @todo: move to RHI. private _isValid: boolean; @@ -339,6 +345,9 @@ export class ShaderProgram { } const location = gl.getUniformLocation(program, name); + // UBO members have no individual location, skip them + if (location === null) return; + shaderUniform.name = name; shaderUniform.propertyId = ShaderProperty.getByName(name)._uniqueId; shaderUniform.location = location; @@ -412,9 +421,33 @@ export class ShaderProgram { shaderUniform.cacheValue = new Vector4(0, 0, 0); } break; + case gl.FLOAT_MAT2: + shaderUniform.applyFunc = shaderUniform.uploadMat2; + break; + case gl.FLOAT_MAT3: + shaderUniform.applyFunc = shaderUniform.uploadMat3; + break; case gl.FLOAT_MAT4: shaderUniform.applyFunc = isArray ? shaderUniform.uploadMat4v : shaderUniform.uploadMat4; break; + case (gl).FLOAT_MAT2x3: + shaderUniform.applyFunc = shaderUniform.uploadMat2x3; + break; + case (gl).FLOAT_MAT2x4: + shaderUniform.applyFunc = shaderUniform.uploadMat2x4; + break; + case (gl).FLOAT_MAT3x2: + shaderUniform.applyFunc = shaderUniform.uploadMat3x2; + break; + case (gl).FLOAT_MAT3x4: + shaderUniform.applyFunc = shaderUniform.uploadMat3x4; + break; + case (gl).FLOAT_MAT4x2: + shaderUniform.applyFunc = shaderUniform.uploadMat4x2; + break; + case (gl).FLOAT_MAT4x3: + shaderUniform.applyFunc = shaderUniform.uploadMat4x3; + break; case gl.SAMPLER_2D: case gl.SAMPLER_CUBE: case (gl).UNSIGNED_INT_SAMPLER_2D: @@ -476,6 +509,21 @@ export class ShaderProgram { attributeInfos.forEach(({ name }) => { this.attributeLocation[name] = gl.getAttribLocation(program, name); }); + + // Record uniform block indices and bind binding points (WebGL2 only) + if (this._engine._hardwareRenderer.isWebGL2) { + const gl2 = gl; + const bindingMap = ShaderFactory.uniformBlockBindingMap; + const blockCount = gl2.getProgramParameter(program, gl2.ACTIVE_UNIFORM_BLOCKS) ?? 0; + for (let i = 0; i < blockCount; i++) { + const id = ShaderBlockProperty.getByName(gl2.getActiveUniformBlockName(program, i))._uniqueId; + this.uniformBlockIds[i] = id; + const bindingPoint = bindingMap[id]; + if (bindingPoint !== undefined) { + gl2.uniformBlockBinding(program, i, bindingPoint); + } + } + } } private _getUniformInfos(): WebGLActiveInfo[] { diff --git a/packages/core/src/shader/ShaderProgramPool.ts b/packages/core/src/shader/ShaderProgramMap.ts similarity index 56% rename from packages/core/src/shader/ShaderProgramPool.ts rename to packages/core/src/shader/ShaderProgramMap.ts index 3b43a07afa..5673708863 100644 --- a/packages/core/src/shader/ShaderProgramPool.ts +++ b/packages/core/src/shader/ShaderProgramMap.ts @@ -2,23 +2,26 @@ import { Engine } from "../Engine"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderProgram } from "./ShaderProgram"; +type Tree = { + [key: number]: Tree | ShaderProgram; +}; + /** - * Shader program pool. + * Map keyed by ShaderMacroCollection bitmask, caching ShaderProgram instances. * @internal */ -export class ShaderProgramPool { +export class ShaderProgramMap { + engine: Engine; + private _cacheHierarchyDepth: number = 1; - private _cacheMap: Tree = Object.create(null); + private _cacheMap: Tree = Object.create(null); private _lastQueryMap: Record; private _lastQueryKey: number; - constructor(public engine: Engine) {} + constructor(engine: Engine) { + this.engine = engine; + } - /** - * Get shader program by macro collection. - * @param macros - macro collection - * @returns shader program - */ get(macros: ShaderMacroCollection): ShaderProgram | null { let cacheMap = this._cacheMap; const maskLength = macros._length; @@ -33,41 +36,31 @@ export class ShaderProgramPool { const maxEndIndex = this._cacheHierarchyDepth - 1; for (let i = 0; i < maxEndIndex; i++) { const subMask = endIndex < i ? 0 : mask[i]; - let subCacheShaders = >cacheMap[subMask]; - subCacheShaders || (cacheMap[subMask] = subCacheShaders = Object.create(null)); - cacheMap = subCacheShaders; + let subCache = cacheMap[subMask]; + subCache || (cacheMap[subMask] = subCache = Object.create(null)); + cacheMap = subCache; } const cacheKey = endIndex < maxEndIndex ? 0 : mask[maxEndIndex]; - const shader = (>cacheMap)[cacheKey]; - if (!shader) { + const value = (>cacheMap)[cacheKey]; + if (!value) { this._lastQueryKey = cacheKey; this._lastQueryMap = >cacheMap; } - return shader; + return value; } - /** - * Cache the shader program. - * - * @remarks - * The method must return an empty value after calling get() to run normally. - * - * @param shaderProgram - shader program - */ - cache(shaderProgram: ShaderProgram): void { - this._lastQueryMap[this._lastQueryKey] = shaderProgram; + cache(value: ShaderProgram): void { + this._lastQueryMap[this._lastQueryKey] = value; } - /** - * @internal - */ - _destroy(): void { - this._recursiveDestroy(0, this._cacheMap); + destroy(): void { + this._recursiveForEach(0, this._cacheMap); this._cacheMap = Object.create(null); + this._cacheHierarchyDepth = 1; } - private _recursiveDestroy(hierarchy: number, cacheMap: Tree): void { + private _recursiveForEach(hierarchy: number, cacheMap: Tree): void { if (hierarchy === this._cacheHierarchyDepth - 1) { for (let k in cacheMap) { (cacheMap[k]).destroy(); @@ -76,35 +69,30 @@ export class ShaderProgramPool { } ++hierarchy; for (let k in cacheMap) { - this._recursiveDestroy(hierarchy, >cacheMap[k]); + this._recursiveForEach(hierarchy, cacheMap[k]); } } private _resizeCacheMapHierarchy( - cacheMap: Tree, + cacheMap: Tree, hierarchy: number, currentHierarchy: number, increaseHierarchy: number ): void { - // Only expand but not shrink if (hierarchy == currentHierarchy - 1) { for (let k in cacheMap) { - const shader = cacheMap[k]; + const value = cacheMap[k]; let subCacheMap = cacheMap; for (let i = 0; i < increaseHierarchy; i++) { subCacheMap[i == 0 ? k : 0] = subCacheMap = Object.create(null); } - subCacheMap[0] = shader; + subCacheMap[0] = value; } } else { hierarchy++; for (let k in cacheMap) { - this._resizeCacheMapHierarchy(>cacheMap[k], hierarchy, currentHierarchy, increaseHierarchy); + this._resizeCacheMapHierarchy(cacheMap[k], hierarchy, currentHierarchy, increaseHierarchy); } } } } - -type Tree = { - [key: number]: Tree | T; -}; diff --git a/packages/core/src/shader/ShaderUniform.ts b/packages/core/src/shader/ShaderUniform.ts index 07017a4abc..90d9233c1b 100644 --- a/packages/core/src/shader/ShaderUniform.ts +++ b/packages/core/src/shader/ShaderUniform.ts @@ -237,6 +237,14 @@ export class ShaderUniform { this._gl.uniform4iv(shaderUniform.location, value); } + uploadMat2(shaderUniform: ShaderUniform, value: Float32Array): void { + this._gl.uniformMatrix2fv(shaderUniform.location, false, value); + } + + uploadMat3(shaderUniform: ShaderUniform, value: Float32Array): void { + this._gl.uniformMatrix3fv(shaderUniform.location, false, value); + } + uploadMat4(shaderUniform: ShaderUniform, value: Matrix): void { this._gl.uniformMatrix4fv(shaderUniform.location, false, value.elements); } @@ -245,6 +253,30 @@ export class ShaderUniform { this._gl.uniformMatrix4fv(shaderUniform.location, false, value); } + uploadMat2x3(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix2x3fv(shaderUniform.location, false, value); + } + + uploadMat2x4(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix2x4fv(shaderUniform.location, false, value); + } + + uploadMat3x2(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix3x2fv(shaderUniform.location, false, value); + } + + uploadMat3x4(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix3x4fv(shaderUniform.location, false, value); + } + + uploadMat4x2(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix4x2fv(shaderUniform.location, false, value); + } + + uploadMat4x3(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix4x3fv(shaderUniform.location, false, value); + } + uploadTexture(shaderUniform: ShaderUniform, value: Texture): void { const rhi = this._rhi; rhi.activeTexture(shaderUniform.textureIndex as GLenum); diff --git a/packages/core/src/shader/enums/ConstantBufferBindingPoint.ts b/packages/core/src/shader/enums/ConstantBufferBindingPoint.ts new file mode 100644 index 0000000000..595d159924 --- /dev/null +++ b/packages/core/src/shader/enums/ConstantBufferBindingPoint.ts @@ -0,0 +1,7 @@ +/** + * @internal + * Constant buffer binding point allocation. + */ +export enum ConstantBufferBindingPoint { + RendererInstance = 0 +} diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index e314e4409d..9e62a3fc25 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -1,13 +1,28 @@ +import { Matrix, Vector2, Vector3, Vector4 } from "@galacean/engine-math"; import { GLCapabilityType } from "../base/Constant"; import { Logger } from "../base/Logger"; import { Engine } from "../Engine"; +import { Renderer } from "../Renderer"; +import { ShaderDataGroup } from "../shader/enums/ShaderDataGroup"; import { ShaderMacro } from "../shader/ShaderMacro"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; +import { ShaderProperty } from "../shader/ShaderProperty"; +import { ShaderBlockProperty } from "../shader/ShaderBlockProperty"; +import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { ShaderLib } from "./ShaderLib"; +/** + * @internal + */ export class ShaderFactory { - /** @internal */ - static readonly _shaderExtension = [ + static readonly RENDERER_INSTANCE_BLOCK_NAME = "RendererInstanceData"; + + static readonly uniformBlockBindingMap: Record = { + [ShaderBlockProperty.getByName(ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME)._uniqueId]: + ConstantBufferBindingPoint.RendererInstance + }; + + static readonly shaderExtension = [ "GL_EXT_shader_texture_lod", "GL_OES_standard_derivatives", "GL_EXT_draw_buffers", @@ -16,35 +31,140 @@ export class ShaderFactory { .map((e) => `#extension ${e} : enable\n`) .join(""); - private static readonly _has300OutInFragReg = /\bout\s+(?:\w+\s+)?(?:vec4)\s+(?:\w+)\s*;/; // [layout(location = 0)] out [highp] vec4 [color]; + private static readonly _std140TypeInfoMap: Record = { + float: { size: 4, align: 4 }, + int: { size: 4, align: 4 }, + uint: { size: 4, align: 4 }, + vec2: { size: 8, align: 8 }, + ivec2: { size: 8, align: 8 }, + vec3: { size: 12, align: 16 }, + ivec3: { size: 12, align: 16 }, + vec4: { size: 16, align: 16 }, + ivec4: { size: 16, align: 16 }, + mat4: { size: 64, align: 16 }, + mat3x4: { size: 48, align: 16 } + }; + + private static readonly _has300OutInFragReg = /\bout\s+(?:\w+\s+)?vec4\s+\w+\s*;/; + + private static readonly _precisionStr = ` +#ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + precision highp int; +#else + precision mediump float; + precision mediump int; +#endif +`; + + // Derived built-ins re-exposed on top of `renderer_ModelMat`. + // `renderer_NormalMat` uses the cofactor (cross-product) form, which algebraically equals + // `det(M) · transpose(inverse(M))`. After `normalize()` it's directionally identical to the + // classic `transpose(inverse(M))`, but stays NaN-free when `M` is singular (e.g. any scale + // axis is 0 — common in animations that pop / hide via scale). `sign(det)` (`s` below) + // keeps mirrored matrices facing the right way + private static readonly _derivedDefines = `\ +mat3 _normalMatFromModel(mat3 m) { + vec3 c0 = cross(m[1], m[2]); + vec3 c1 = cross(m[2], m[0]); + vec3 c2 = cross(m[0], m[1]); + float s = (dot(m[0], c0) < 0.0) ? -1.0 : 1.0; + return mat3(c0 * s, c1 * s, c2 * s); +} +#define renderer_MVMat (camera_ViewMat * renderer_ModelMat) +#define renderer_MVPMat (camera_VPMat * renderer_ModelMat) +#define renderer_NormalMat mat4(_normalMatFromModel(mat3(renderer_ModelMat)))`; + + // Built-in renderer uniforms. value=true means derived (remove but not added to UBO) + private static readonly _builtinRendererUniforms: Record = { + renderer_ModelMat: false, + renderer_Layer: false, + renderer_MVMat: true, + renderer_MVPMat: true, + renderer_NormalMat: true + }; + + private static readonly _uboUniformRegex = + /^[ \t]*uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*(\[.+?\])?\s*;/gm; + + private static _packFuncMap: Record = (() => { + const packScalar = (v: Float32Array | Int32Array, o: number, val: number) => { + v[o] = val; + }; + const packVec2 = (v: Float32Array | Int32Array, o: number, val: Vector2) => { + v[o] = val.x; + v[o + 1] = val.y; + }; + const packVec3 = (v: Float32Array | Int32Array, o: number, val: Vector3) => { + v[o] = val.x; + v[o + 1] = val.y; + v[o + 2] = val.z; + }; + const packVec4 = (v: Float32Array | Int32Array, o: number, val: Vector4) => { + v[o] = val.x; + v[o + 1] = val.y; + v[o + 2] = val.z; + v[o + 3] = val.w; + }; + return { + float: packScalar, + int: packScalar, + uint: packScalar, + vec2: packVec2, + ivec2: packVec2, + vec3: packVec3, + ivec3: packVec3, + vec4: packVec4, + ivec4: packVec4, + mat4: (v: Float32Array | Int32Array, o: number, val: Matrix) => { + const e = val.elements; + for (let k = 0; k < 16; k++) v[o + k] = e[k]; + }, + // Affine mat4 stored as mat3x4: write 3 transposed rows (row3 is always 0,0,0,1) + mat3x4: (v: Float32Array | Int32Array, o: number, val: Matrix) => { + const e = val.elements; + // Row 0 + v[o] = e[0]; + v[o + 1] = e[4]; + v[o + 2] = e[8]; + v[o + 3] = e[12]; + // Row 1 + v[o + 4] = e[1]; + v[o + 5] = e[5]; + v[o + 6] = e[9]; + v[o + 7] = e[13]; + // Row 2 + v[o + 8] = e[2]; + v[o + 9] = e[6]; + v[o + 10] = e[10]; + v[o + 11] = e[14]; + } + }; + })(); static parseCustomMacros(macros: ShaderMacro[]) { return macros.map((m) => `#define ${m.value ? m.name + ` ` + m.value : m.name}\n`).join(""); } /** - * @internal * Compile vertex and fragment source with standard macros, includes, and version header. - * @param engine - Engine instance - * @param macroCollection - Current macro collection - * @param vertexSource - Raw vertex shader source (may contain #include) - * @param fragmentSource - Raw fragment shader source - * @returns Compiled { vertexSource, fragmentSource } ready for ShaderProgram */ static compilePlatformSource( engine: Engine, macroCollection: ShaderMacroCollection, vertexSource: string, - fragmentSource: string - ): { vertexSource: string; fragmentSource: string } { - const isWebGL2 = engine._hardwareRenderer.isWebGL2; + fragmentSource: string, + isGPUInstance: boolean + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { + const rhi = engine._hardwareRenderer; + const isWebGL2 = rhi.isWebGL2; const shaderMacroList = new Array(); ShaderMacro._getMacrosElements(macroCollection, shaderMacroList); shaderMacroList.push(ShaderMacro.getByName(isWebGL2 ? "GRAPHICS_API_WEBGL2" : "GRAPHICS_API_WEBGL1")); - if (engine._hardwareRenderer.canIUse(GLCapabilityType.shaderTextureLod)) { + if (rhi.canIUse(GLCapabilityType.shaderTextureLod)) { shaderMacroList.push(ShaderMacro.getByName("HAS_TEX_LOD")); } - if (engine._hardwareRenderer.canIUse(GLCapabilityType.standardDerivatives)) { + if (rhi.canIUse(GLCapabilityType.standardDerivatives)) { shaderMacroList.push(ShaderMacro.getByName("HAS_DERIVATIVES")); } @@ -55,28 +175,82 @@ export class ShaderFactory { noIncludeVertex = macroStr + noIncludeVertex; noIncludeFrag = macroStr + noIncludeFrag; + let instanceLayout: InstanceBufferLayout | null = null; + if (isGPUInstance) { + const activeMacros = new Set(); + for (let i = 0, len = shaderMacroList.length; i < len; i++) activeMacros.add(shaderMacroList[i].name); + const injected = ShaderFactory.injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag, activeMacros); + noIncludeVertex = injected.vertexSource; + noIncludeFrag = injected.fragmentSource; + instanceLayout = injected.instanceLayout; + } + if (isWebGL2) { noIncludeVertex = ShaderFactory.convertTo300(noIncludeVertex); noIncludeFrag = ShaderFactory.convertTo300(noIncludeFrag, true); } const versionStr = isWebGL2 ? "#version 300 es" : "#version 100"; - const precisionStr = ` -#ifdef GL_FRAGMENT_PRECISION_HIGH - precision highp float; - precision highp int; -#else - precision mediump float; - precision mediump int; -#endif -`; return { vertexSource: `${versionStr}\nprecision highp float;\n${noIncludeVertex}`, - fragmentSource: `${versionStr}\n${isWebGL2 ? "" : ShaderFactory._shaderExtension}${precisionStr}${noIncludeFrag}` + fragmentSource: `${versionStr}\n${isWebGL2 ? "" : ShaderFactory.shaderExtension}${ShaderFactory._precisionStr}${noIncludeFrag}`, + instanceLayout }; } + /** + * Scan VS/FS for renderer-group `uniform` declarations, replace them with a shared + * std140 UBO (instanced array), and emit `#define` remapping so original uniform + * names resolve to `rendererData[instanceID].field`. + */ + static injectInstanceUBO( + engine: Engine, + vertexSource: string, + fragmentSource: string, + activeMacros?: Set + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { + // 1. Scan & strip renderer uniforms from both stages, collect into fieldMap + const fieldMap: Record = Object.create(null); + if (activeMacros) { + vertexSource = ShaderFactory._scanInstanceUniformsWithMacros(vertexSource, fieldMap, activeMacros); + fragmentSource = ShaderFactory._scanInstanceUniformsWithMacros(fragmentSource, fieldMap, activeMacros); + } else { + vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); + fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); + } + + // Fast empty check without allocating an array + let hasField = false; + for (const _ in fieldMap) { + hasField = true; + break; + } + if (!hasField) return { vertexSource, fragmentSource, instanceLayout: null }; + + // 2. Compute std140 layout (field offsets, struct size, max instance count) + const instanceLayout = ShaderFactory._buildLayout(engine, fieldMap); + + // 3. Generate GLSL UBO block and inject into both stages + const { instanceFields } = instanceLayout; + const uboDecl = ShaderFactory._buildUBODeclaration(instanceLayout); + const fieldDefinesVS = ShaderFactory._buildFieldDefines(instanceFields, "gl_InstanceID"); + const fieldDefinesFS = ShaderFactory._buildFieldDefines(instanceFields, "v_instanceID"); + const derivedDefines = ShaderFactory._derivedDefines; + + const vsBlock = `${uboDecl}flat out int v_instanceID;\n${fieldDefinesVS}\n${derivedDefines}\n`; + const fsBlock = `${uboDecl}flat in int v_instanceID;\n${fieldDefinesFS}\n${derivedDefines}\n`; + + vertexSource = vsBlock + vertexSource; + vertexSource = vertexSource.replace( + /void\s+main\s*\(\s*\)\s*\{/, + "void main() {\n v_instanceID = gl_InstanceID;" + ); + fragmentSource = fsBlock + fragmentSource; + + return { vertexSource, fragmentSource, instanceLayout }; + } + static registerInclude(includeName: string, includeSource: string) { if (ShaderLib[includeName]) { throw `The "${includeName}" shader include already exist`; @@ -93,25 +267,21 @@ export class ShaderFactory { * since `ShaderLab` use the same parsing function but different syntax for `#include` --- `/^[ \t]*#include +"([\w\d.]+)"/gm` */ static parseIncludes(src: string, regex = /^[ \t]*#include +<([\w\d.]+)>/gm) { - function replace(match, slice) { - var replace = ShaderLib[slice]; - - if (replace === undefined) { + return src.replace(regex, (match, slice) => { + const replacement = ShaderLib[slice]; + if (replacement === undefined) { Logger.error(`Shader slice "${match.trim()}" not founded.`); return ""; } - - return ShaderFactory.parseIncludes(replace, regex); - } - - return src.replace(regex, replace); + return ShaderFactory.parseIncludes(replacement, regex); + }); } /** * Convert lower GLSL version to GLSL 300 es. * @param shader - code * @param isFrag - Whether it is a fragment shader. - * */ + */ static convertTo300(shader: string, isFrag?: boolean) { shader = shader.replace(/\bvarying\b/g, isFrag ? "in" : "out"); shader = shader.replace(/\btexture(2D|Cube)\b/g, "texture"); @@ -124,7 +294,7 @@ export class ShaderFactory { if (isFrag) { shader = shader.replace(/\bgl_FragDepthEXT\b/g, "gl_FragDepth"); - if (!ShaderFactory._has300Output(shader)) { + if (!ShaderFactory._has300OutInFragReg.test(shader)) { const isMRT = /\bgl_FragData\[.+?\]/g.test(shader); if (isMRT) { shader = shader.replace(/\bgl_FragColor\b/g, "gl_FragData[0]"); @@ -142,8 +312,162 @@ export class ShaderFactory { return shader; } - private static _has300Output(fragmentShader: string): boolean { - return ShaderFactory._has300OutInFragReg.test(fragmentShader); + private static _scanInstanceUniforms(source: string, fieldMap: Record): string { + const builtinUniforms = ShaderFactory._builtinRendererUniforms; + return source.replace(ShaderFactory._uboUniformRegex, (match, type, name, arraySize) => { + if (type.includes("sampler")) return match; + const isDerived = builtinUniforms[name]; + if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) + return match; + if (isDerived) return ""; + // Array uniforms not supported in instancing UBO, keep as regular uniform + if (arraySize) { + Logger.error(`GPU Instancing does not support array uniform "${name}${arraySize}"`); + return match; + } + // ModelMat is affine, store as mat3x4 (3 columns) to save 16 bytes per instance + fieldMap[ShaderProperty.getByName(name)._uniqueId] = + type === "mat4" && name === "renderer_ModelMat" ? "mat3x4" : type; + return ""; + }); + } + + // Matches preprocessor directives at line start. Only `#ifdef / #ifndef / #else / #endif` are + // supported; `#if` with expressions is not recognized (such blocks are treated as always-active). + private static readonly _ifdefRegex = /^[ \t]*#ifdef\s+(\w+)/; + private static readonly _ifndefRegex = /^[ \t]*#ifndef\s+(\w+)/; + private static readonly _elseRegex = /^[ \t]*#else\b/; + private static readonly _endifRegex = /^[ \t]*#endif\b/; + + /** + * Scan with preprocessor awareness, for raw GLSL paths where `#ifdef` blocks are not yet + * expanded. Uniforms inside inactive branches are skipped. + */ + private static _scanInstanceUniformsWithMacros( + source: string, + fieldMap: Record, + activeMacros: Set + ): string { + // Preprocessor branch stack: each frame tracks whether current branch is active + const branchStack: boolean[] = [true]; + const lines = source.split("\n"); + + for (let i = 0, n = lines.length; i < n; i++) { + const line = lines[i]; + + let m = line.match(ShaderFactory._ifdefRegex); + if (m) { + const parentActive = branchStack[branchStack.length - 1]; + branchStack.push(parentActive && activeMacros.has(m[1])); + continue; + } + m = line.match(ShaderFactory._ifndefRegex); + if (m) { + const parentActive = branchStack[branchStack.length - 1]; + branchStack.push(parentActive && !activeMacros.has(m[1])); + continue; + } + if (ShaderFactory._elseRegex.test(line)) { + const parentActive = branchStack.length >= 2 ? branchStack[branchStack.length - 2] : true; + const currentActive = branchStack[branchStack.length - 1]; + branchStack[branchStack.length - 1] = parentActive && !currentActive; + continue; + } + if (ShaderFactory._endifRegex.test(line)) { + if (branchStack.length > 1) branchStack.pop(); + continue; + } + if (!branchStack[branchStack.length - 1]) continue; + + lines[i] = ShaderFactory._scanInstanceUniforms(line, fieldMap); + } + + return lines.join("\n"); + } + + private static _buildLayout(engine: Engine, fieldMap: Record): InstanceBufferLayout { + const maxUBOSize = engine._hardwareRenderer.maxUniformBlockSize; + const std140Map = ShaderFactory._std140TypeInfoMap; + const instanceFields: InstanceFieldInfo[] = []; + let currentOffset = 0; + + const packFuncMap = ShaderFactory._packFuncMap; + const addField = (id: number): void => { + const type = fieldMap[id]; + const info = std140Map[type]; + if (!info) return; + currentOffset = Math.ceil(currentOffset / info.align) * info.align; + instanceFields.push({ + property: ShaderProperty._propertyIdMap[id], + type, + offset: currentOffset, + offsetInElements: currentOffset / 4, + useIntView: type[0] === "i" || type[0] === "u", + pack: packFuncMap[type] + }); + currentOffset += info.size; + }; + + // Priority fields first + const modelMatId = Renderer._worldMatrixProperty._uniqueId; + const layerId = Renderer._rendererLayerProperty._uniqueId; + if (modelMatId in fieldMap) { + addField(modelMatId); + delete fieldMap[modelMatId]; + } + if (layerId in fieldMap) { + addField(layerId); + delete fieldMap[layerId]; + } + + // Remaining fields sorted by id + const keys: number[] = []; + for (const k in fieldMap) keys.push(+k); + keys.sort((a, b) => a - b); + for (let i = 0; i < keys.length; i++) addField(keys[i]); + + const structSize = Math.ceil(currentOffset / 16) * 16; + const instanceMaxCount = Math.floor(maxUBOSize / structSize); + + return { instanceFields, instanceMaxCount, structSize }; + } + + private static _buildUBODeclaration(layout: InstanceBufferLayout): string { + const { instanceFields, instanceMaxCount } = layout; + const structLines: string[] = []; + for (let i = 0; i < instanceFields.length; i++) { + const { type, property } = instanceFields[i]; + structLines.push(` ${type} ${property.name};`); + } + return ( + `#define INSTANCE_MAX_COUNT ${instanceMaxCount}\n` + + `struct RendererInstanceStruct {\n${structLines.join("\n")}\n};\n` + + `layout(std140) uniform ${ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME} {\n` + + ` RendererInstanceStruct rendererData[INSTANCE_MAX_COUNT];\n};\n` + ); + } + + private static _buildFieldDefines(fields: InstanceFieldInfo[], idExpr: string): string { + const accessor = `rendererData[${idExpr}]`; + const lines: string[] = []; + for (let i = 0; i < fields.length; i++) { + const { type, property } = fields[i]; + const n = property.name; + if (type === "mat3x4") { + // mat3x4 stores 3 transposed rows; reconstruct column-major mat4 with row3=(0,0,0,1) + const m = `${accessor}.${n}`; + lines.push( + `#define ${n} mat4(` + + `vec4(${m}[0].x,${m}[1].x,${m}[2].x,0.0),` + + `vec4(${m}[0].y,${m}[1].y,${m}[2].y,0.0),` + + `vec4(${m}[0].z,${m}[1].z,${m}[2].z,0.0),` + + `vec4(${m}[0].w,${m}[1].w,${m}[2].w,1.0))` + ); + } else { + lines.push(`#define ${n} ${accessor}.${n}`); + } + } + return lines.join("\n"); } private static _replaceMRTShader(shader: string, result: string[]): string { @@ -166,3 +490,24 @@ export class ShaderFactory { return shader; } } + +export interface InstanceFieldInfo { + property: ShaderProperty; + type: string; + offset: number; + /** offset / 4, precomputed to avoid repeated division in upload loop */ + offsetInElements: number; + useIntView: boolean; + pack: InstancePackFunc; +} + +/** + * @internal + */ +export interface InstanceBufferLayout { + instanceFields: InstanceFieldInfo[]; + instanceMaxCount: number; + structSize: number; +} + +type InstancePackFunc = (view: Float32Array | Int32Array, offset: number, value: any) => void; diff --git a/packages/core/src/shaderlib/extra/depthOnly.vs.glsl b/packages/core/src/shaderlib/extra/depthOnly.vs.glsl index 06ea2dc057..ecaea3e6ce 100644 --- a/packages/core/src/shaderlib/extra/depthOnly.vs.glsl +++ b/packages/core/src/shaderlib/extra/depthOnly.vs.glsl @@ -2,8 +2,6 @@ #include #include #include -uniform mat4 camera_VPMat; - void main() { diff --git a/packages/core/src/shaderlib/extra/shadow-map.vs.glsl b/packages/core/src/shaderlib/extra/shadow-map.vs.glsl index fae6a4b849..2da9dc1092 100644 --- a/packages/core/src/shaderlib/extra/shadow-map.vs.glsl +++ b/packages/core/src/shaderlib/extra/shadow-map.vs.glsl @@ -3,7 +3,6 @@ #include #include #include -uniform mat4 camera_VPMat; uniform vec2 scene_ShadowBias; // x: depth bias, y: normal bias uniform vec3 scene_LightDirection; @@ -26,7 +25,7 @@ void main() { #include #include #include - + vec4 positionWS = renderer_ModelMat * position; positionWS.xyz = applyShadowBias(positionWS.xyz); diff --git a/packages/core/src/shaderlib/extra/skybox.vs.glsl b/packages/core/src/shaderlib/extra/skybox.vs.glsl index 54c7185dd8..de9692f754 100644 --- a/packages/core/src/shaderlib/extra/skybox.vs.glsl +++ b/packages/core/src/shaderlib/extra/skybox.vs.glsl @@ -1,7 +1,5 @@ #include -uniform mat4 camera_VPMat; - varying vec3 v_cubeUV; uniform float material_Rotation; diff --git a/packages/core/src/shaderlib/extra/text.fs.glsl b/packages/core/src/shaderlib/extra/text.fs.glsl index 8fe1125d69..0ea05d15d2 100644 --- a/packages/core/src/shaderlib/extra/text.fs.glsl +++ b/packages/core/src/shaderlib/extra/text.fs.glsl @@ -1,15 +1,76 @@ uniform sampler2D renderElement_TextTexture; +uniform vec2 renderElement_TextTextureSize; +uniform vec4 renderer_OutlineColor; +uniform float renderer_OutlineWidth; + +uniform vec4 renderer_UIRectClipRect; +uniform float renderer_UIRectClipEnabled; +uniform vec4 renderer_UIRectClipSoftness; +uniform float renderer_UIRectClipHardClip; varying vec2 v_uv; varying vec4 v_color; +varying vec2 v_worldPosition; -void main() +float getUIRectClipAlpha() { - vec4 texColor = texture2D(renderElement_TextTexture, v_uv); + vec4 edgeDistance = vec4( + v_worldPosition.x - renderer_UIRectClipRect.x, + v_worldPosition.y - renderer_UIRectClipRect.y, + renderer_UIRectClipRect.z - v_worldPosition.x, + renderer_UIRectClipRect.w - v_worldPosition.y + ); + vec4 hardClipFactor = step(vec4(0.0), edgeDistance); + vec4 softness = max(renderer_UIRectClipSoftness, vec4(1e-5)); + vec4 softClipFactor = clamp(edgeDistance / softness, 0.0, 1.0); + vec4 useSoftness = step(vec4(1e-5), renderer_UIRectClipSoftness); + vec4 clipFactor = mix(hardClipFactor, softClipFactor, useSoftness); + return clipFactor.x * clipFactor.y * clipFactor.z * clipFactor.w; +} + +float sampleCoverage(vec2 uv) +{ + vec4 c = texture2D(renderElement_TextTexture, uv); #ifdef GRAPHICS_API_WEBGL2 - float coverage = texColor.r; + return c.r; #else - float coverage = texColor.a; + return c.a; #endif - gl_FragColor = vec4(v_color.rgb, v_color.a * coverage); +} + +void main() +{ + float rectClipAlpha = 1.0; + if (renderer_UIRectClipEnabled > 0.5) { + rectClipAlpha = getUIRectClipAlpha(); + } + + float coverage = sampleCoverage(v_uv); + vec4 finalColor; + + if (renderer_OutlineWidth > 0.0) { + vec2 texelSize = 1.0 / renderElement_TextTextureSize; + vec2 step = texelSize * renderer_OutlineWidth; + float outlineCoverage = coverage; + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2( step.x, 0.0))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2(-step.x, 0.0))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2( 0.0, step.y))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2( 0.0, -step.y))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2( step.x * 0.7071, step.y * 0.7071))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2(-step.x * 0.7071, step.y * 0.7071))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2( step.x * 0.7071, -step.y * 0.7071))); + outlineCoverage = max(outlineCoverage, sampleCoverage(v_uv + vec2(-step.x * 0.7071, -step.y * 0.7071))); + + vec3 rgb = mix(renderer_OutlineColor.rgb, v_color.rgb, coverage); + float alpha = max(coverage, outlineCoverage * renderer_OutlineColor.a) * v_color.a; + finalColor = vec4(rgb, alpha); + } else { + finalColor = vec4(v_color.rgb, v_color.a * coverage); + } + + finalColor.a *= rectClipAlpha; + if (renderer_UIRectClipEnabled > 0.5 && renderer_UIRectClipHardClip > 0.5 && finalColor.a < 0.001) { + discard; + } + gl_FragColor = finalColor; } diff --git a/packages/core/src/shaderlib/extra/text.vs.glsl b/packages/core/src/shaderlib/extra/text.vs.glsl index 37a6b2d333..c3971d0172 100644 --- a/packages/core/src/shaderlib/extra/text.vs.glsl +++ b/packages/core/src/shaderlib/extra/text.vs.glsl @@ -1,4 +1,5 @@ uniform mat4 renderer_MVPMat; +uniform mat4 renderer_ModelMat; attribute vec3 POSITION; attribute vec2 TEXCOORD_0; @@ -6,6 +7,7 @@ attribute vec4 COLOR_0; varying vec2 v_uv; varying vec4 v_color; +varying vec2 v_worldPosition; void main() { @@ -13,4 +15,5 @@ void main() v_uv = TEXCOORD_0; v_color = COLOR_0; + v_worldPosition = POSITION.xy; } diff --git a/packages/core/src/shaderlib/normal_vert.glsl b/packages/core/src/shaderlib/normal_vert.glsl index 096a4595d1..906be70ef7 100644 --- a/packages/core/src/shaderlib/normal_vert.glsl +++ b/packages/core/src/shaderlib/normal_vert.glsl @@ -1,9 +1,10 @@ #ifndef MATERIAL_OMIT_NORMAL #ifdef RENDERER_HAS_NORMAL - v_normal = normalize( mat3(renderer_NormalMat) * normal ); + mat3 normalMat = mat3(renderer_NormalMat); + v_normal = normalize( normalMat * normal ); #if defined(RENDERER_HAS_TANGENT) && ( defined(MATERIAL_HAS_NORMALTEXTURE) || defined(MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE) || defined(MATERIAL_ENABLE_ANISOTROPY) ) - vec3 tangentW = normalize( mat3(renderer_NormalMat) * tangent.xyz ); + vec3 tangentW = normalize( normalMat * tangent.xyz ); vec3 bitangentW = cross( v_normal, tangentW ) * tangent.w; v_TBN = mat3( tangentW, bitangentW, v_normal ); diff --git a/packages/core/src/shaderlib/particle/rotation_over_lifetime_module.glsl b/packages/core/src/shaderlib/particle/rotation_over_lifetime_module.glsl index 226848bba8..66e4740d54 100644 --- a/packages/core/src/shaderlib/particle/rotation_over_lifetime_module.glsl +++ b/packages/core/src/shaderlib/particle/rotation_over_lifetime_module.glsl @@ -77,7 +77,7 @@ vec3 computeParticleRotationVec3(in vec3 rotation, in float age, in float normal #else float ageRot = renderer_ROLMaxConst.z * age; #endif - rotation += ageRot; + rotation.z += ageRot; #endif #ifdef RENDERER_ROL_CURVE_MODE @@ -86,7 +86,7 @@ vec3 computeParticleRotationVec3(in vec3 rotation, in float age, in float normal #ifdef RENDERER_ROL_IS_RANDOM_TWO lifeRotation = mix(evaluateParticleCurveCumulative(renderer_ROLMinCurveZ, normalizedAge, currentValue), lifeRotation, a_Random0.w); #endif - rotation += lifeRotation * a_ShapePositionStartLifeTime.w; + rotation.z += lifeRotation * a_ShapePositionStartLifeTime.w; #endif #endif return rotation; diff --git a/packages/core/src/shaderlib/transform_declare.glsl b/packages/core/src/shaderlib/transform_declare.glsl index 00e1d5edba..86048dcbde 100644 --- a/packages/core/src/shaderlib/transform_declare.glsl +++ b/packages/core/src/shaderlib/transform_declare.glsl @@ -1,7 +1,8 @@ -uniform mat4 renderer_LocalMat; -uniform mat4 renderer_ModelMat; uniform mat4 camera_ViewMat; uniform mat4 camera_ProjMat; +uniform mat4 camera_VPMat; + +uniform mat4 renderer_ModelMat; uniform mat4 renderer_MVMat; uniform mat4 renderer_MVPMat; uniform mat4 renderer_NormalMat; \ No newline at end of file diff --git a/packages/core/src/trail/TrailRenderer.ts b/packages/core/src/trail/TrailRenderer.ts index b8d97f4365..6485d658ca 100644 --- a/packages/core/src/trail/TrailRenderer.ts +++ b/packages/core/src/trail/TrailRenderer.ts @@ -1,7 +1,6 @@ import { BoundingBox, Color, Vector2, Vector3, Vector4 } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; -import { RenderElement } from "../RenderPipeline/RenderElement"; import { Renderer, RendererUpdateFlags } from "../Renderer"; import { deepClone, ignoreClone } from "../clone/CloneManager"; import { Buffer } from "../graphic/Buffer"; @@ -232,8 +231,9 @@ export class TrailRenderer extends Renderer { const { _firstActiveElement: firstActive, _firstFreeElement: firstFree } = this; - const renderElement = this._engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); + const priority = this.priority; + const distanceForSort = this._distanceForSort; + const renderPipeline = context.camera._renderPipeline; // spansBoundary: active points cross buffer end // wrapped: spansBoundary AND point 0 has been written (need bridge + second segment) @@ -241,13 +241,19 @@ export class TrailRenderer extends Renderer { const wrapped = spansBoundary && firstFree > 0; const mainCount = (spansBoundary ? this._currentPointCapacity - firstActive + (wrapped ? 1 : 0) : firstFree - firstActive) * 2; - this._addSubRenderElement(renderElement, material, this._mainSubPrimitive, firstActive * 2, mainCount); + this._addRenderElement( + context, + material, + this._mainSubPrimitive, + firstActive * 2, + mainCount, + priority, + distanceForSort + ); if (wrapped) { - this._addSubRenderElement(renderElement, material, this._wrapSubPrimitive, 0, firstFree * 2); + this._addRenderElement(context, material, this._wrapSubPrimitive, 0, firstFree * 2, priority, distanceForSort); } - - context.camera._renderPipeline.pushRenderElement(context, renderElement); } protected override _updateBounds(worldBounds: BoundingBox): void { @@ -588,18 +594,22 @@ export class TrailRenderer extends Renderer { this._bufferResized = false; } - private _addSubRenderElement( - renderElement: RenderElement, + private _addRenderElement( + context: RenderContext, material: Material, subPrimitive: SubPrimitive, start: number, - count: number + count: number, + priority: number, + distanceForSort: number ): void { subPrimitive.start = start; subPrimitive.count = count; - const subRenderElement = this._engine._subRenderElementPool.get(); - subRenderElement.set(this, material, this._primitive, subPrimitive); - renderElement.addSubRenderElement(subRenderElement); + const renderElement = this._engine._renderElementPool.get(); + renderElement.set(this, material, this._primitive, subPrimitive); + renderElement.priority = priority; + renderElement.distanceForSort = distanceForSort; + context.camera._renderPipeline.pushRenderElement(context, renderElement); } @ignoreClone diff --git a/packages/core/src/ui/IUICanvas.ts b/packages/core/src/ui/IUICanvas.ts index 3139b6dba8..e90e209aa2 100644 --- a/packages/core/src/ui/IUICanvas.ts +++ b/packages/core/src/ui/IUICanvas.ts @@ -10,7 +10,7 @@ export interface IUICanvas { entity: Entity; sortOrder: number; _canvasIndex: number; - _renderElement: RenderElement; + _renderElements: RenderElement[]; _canRender(camera: Camera): boolean; _prepareRender(renderContext: RenderContext): void; } diff --git a/packages/core/src/ui/UIUtils.ts b/packages/core/src/ui/UIUtils.ts index c57c8bdf1f..0e455a135c 100644 --- a/packages/core/src/ui/UIUtils.ts +++ b/packages/core/src/ui/UIUtils.ts @@ -1,14 +1,21 @@ -import { Matrix, Vector4 } from "@galacean/engine-math"; +import { Color, Matrix, Vector4 } from "@galacean/engine-math"; import { Camera } from "../Camera"; import { Engine } from "../Engine"; import { Layer } from "../Layer"; +import { Blitter } from "../RenderPipeline/Blitter"; import { RenderQueue } from "../RenderPipeline"; import { ContextRendererUpdateFlag } from "../RenderPipeline/RenderContext"; import { Scene } from "../Scene"; import { VirtualCamera } from "../VirtualCamera"; import { EngineObject } from "../base"; -import { RenderQueueType, ShaderData, ShaderDataGroup, ShaderMacro } from "../shader"; +import { CameraClearFlags } from "../enums/CameraClearFlags"; +import { Material } from "../material"; +import { RenderQueueType, Shader, ShaderData, ShaderDataGroup, ShaderMacro } from "../shader"; +import { BlendFactor } from "../shader/enums/BlendFactor"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; +import { RenderTarget } from "../texture/RenderTarget"; +import { Texture2D } from "../texture/Texture2D"; +import { TextureFormat } from "../texture/enums/TextureFormat"; import { DisorderedArray } from "../utils/DisorderedArray"; import { IUICanvas } from "./IUICanvas"; @@ -19,6 +26,11 @@ export class UIUtils { private static _virtualCamera: VirtualCamera; private static _viewport: Vector4; private static _overlayCamera: OverlayCamera; + private static _overlayRT: RenderTarget; + private static _overlayBlitMaterial: Material; + private static _clearColor = new Color(0, 0, 0, 0); + /** Flip V so that Y-up RT content maps correctly onto the default framebuffer. */ + private static _flipYScaleOffset = new Vector4(1, -1, 0, 1); static renderOverlay(engine: Engine, scene: Scene, uiCanvases: DisorderedArray): void { engine._macroCollection.enable(UIUtils._shouldSRGBCorrect); @@ -31,10 +43,19 @@ export class UIUtils { camera.engine = engine; camera.scene = scene; renderContext.camera = camera as unknown as Camera; + + const { width, height } = canvas; const { elements: projectE } = virtualCamera.projectionMatrix; const { elements: viewE } = virtualCamera.viewMatrix; - (projectE[0] = 2 / canvas.width), (projectE[5] = 2 / canvas.height), (projectE[10] = 0); - renderContext.setRenderTarget(null, viewport, 0); + (projectE[0] = 2 / width), (projectE[5] = 2 / height), (projectE[10] = 0); + + // Render to an intermediate RT with Depth24Stencil8 so that stencil-based UI Mask works. + // The default canvas framebuffer is created without a stencil buffer + // (see WebGLGraphicDevice._webGLOptions.stencil = false). + const overlayRT = UIUtils._getOverlayRT(engine, width, height); + renderContext.setRenderTarget(overlayRT, viewport, 0); + rhi.clearRenderTarget(engine, CameraClearFlags.All, UIUtils._clearColor); + for (let i = 0, n = uiCanvases.length; i < n; i++) { const uiCanvas = uiCanvases.get(i); if (uiCanvas) { @@ -44,7 +65,10 @@ export class UIUtils { renderContext.applyVirtualCamera(virtualCamera, false); uiRenderQueue.rendererUpdateFlag |= ContextRendererUpdateFlag.ProjectionMatrix; uiCanvas._prepareRender(renderContext); - uiRenderQueue.pushRenderElement(uiCanvas._renderElement); + const curElements = uiCanvas._renderElements; + for (let j = 0, m = curElements.length; j < m; j++) { + uiRenderQueue.pushRenderElement(curElements[j]); + } uiRenderQueue.batch(batcherManager); batcherManager.uploadBuffer(); uiRenderQueue.render(renderContext, "Forward"); @@ -52,9 +76,60 @@ export class UIUtils { engine._renderCount++; } } + + // Blit overlay RT to default framebuffer with premultiplied alpha blending. + // Blitter.blitTexture picks the non-flipping `blitMesh` when destination is null, + // but the RT contents are written in standard Y-up NDC, so we flip V here via + // sourceScaleOffset to match the default framebuffer orientation. + Blitter.blitTexture( + engine, + overlayRT.getColorTexture(0) as Texture2D, + null, + 0, + viewport, + UIUtils._getOverlayBlitMaterial(engine), + 0, + UIUtils._flipYScaleOffset + ); + renderContext.camera = null; engine._macroCollection.disable(UIUtils._shouldSRGBCorrect); } + + private static _getOverlayRT(engine: Engine, width: number, height: number): RenderTarget { + let rt = UIUtils._overlayRT; + if (!rt || rt.width !== width || rt.height !== height) { + if (rt) { + rt.getColorTexture(0).destroy(); + rt.destroy(); + } + const colorTexture = new Texture2D(engine, width, height, TextureFormat.R8G8B8A8, false); + colorTexture.isGCIgnored = true; + rt = new RenderTarget(engine, width, height, colorTexture, TextureFormat.Depth24Stencil8); + rt.isGCIgnored = true; + UIUtils._overlayRT = rt; + } + return rt; + } + + private static _getOverlayBlitMaterial(engine: Engine): Material { + let material = UIUtils._overlayBlitMaterial; + if (!material) { + material = new Material(engine, Shader.find("blit")); + material.isGCIgnored = true; + const renderState = material.renderState; + renderState.depthState.enabled = false; + renderState.depthState.writeEnabled = false; + const target = renderState.blendState.targetBlendState; + target.enabled = true; + target.sourceColorBlendFactor = BlendFactor.One; + target.destinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha; + target.sourceAlphaBlendFactor = BlendFactor.One; + target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha; + UIUtils._overlayBlitMaterial = material; + } + return material; + } } class OverlayCamera { diff --git a/packages/design/package.json b/packages/design/package.json index 86e73a7c33..3bbffa9096 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-design", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/design/src/physics/IPhysics.ts b/packages/design/src/physics/IPhysics.ts index 17c9b6b466..91a6c1c7c6 100644 --- a/packages/design/src/physics/IPhysics.ts +++ b/packages/design/src/physics/IPhysics.ts @@ -36,6 +36,16 @@ export interface IPhysics { */ createPhysicsScene(physicsManager: IPhysicsManager): IPhysicsScene; + /** + * Get the default contact offset for collider shapes. + */ + getDefaultContactOffset?(): number; + + /** + * Get the default sleep threshold for dynamic colliders. + */ + getDefaultSleepThreshold?(): number; + /** * Create dynamic collider. * @param position - The global position diff --git a/packages/design/src/renderingHardwareInterface/IHardwareRenderer.ts b/packages/design/src/renderingHardwareInterface/IHardwareRenderer.ts index bccb92bf65..193ef7a558 100644 --- a/packages/design/src/renderingHardwareInterface/IHardwareRenderer.ts +++ b/packages/design/src/renderingHardwareInterface/IHardwareRenderer.ts @@ -2,6 +2,8 @@ * Hardware graphics API renderer. */ export interface IHardwareRenderer { + readonly maxUniformBlockSize: number; + // todo: implements [key: string]: any; } diff --git a/packages/galacean/package.json b/packages/galacean/package.json index a2bf568984..82a1865fc0 100644 --- a/packages/galacean/package.json +++ b/packages/galacean/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/package.json b/packages/loader/package.json index 61e5825838..5e69bbe4e3 100644 --- a/packages/loader/package.json +++ b/packages/loader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-loader", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/loader/src/AudioLoader.ts b/packages/loader/src/AudioLoader.ts index 24c2b788c9..29cd7d40b7 100644 --- a/packages/loader/src/AudioLoader.ts +++ b/packages/loader/src/AudioLoader.ts @@ -9,7 +9,7 @@ import { ResourceManager, resourceLoader } from "@galacean/engine-core"; -@resourceLoader(AssetType.Audio, ["mp3", "ogg", "wav"]) +@resourceLoader(AssetType.Audio, ["mp3", "ogg", "wav", "audio", "m4a", "aac", "flac"]) class AudioLoader extends Loader { load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { return new AssetPromise((resolve, reject) => { diff --git a/packages/loader/src/SceneLoader.ts b/packages/loader/src/SceneLoader.ts index 069b2db6e2..b16fb2cc92 100644 --- a/packages/loader/src/SceneLoader.ts +++ b/packages/loader/src/SceneLoader.ts @@ -128,6 +128,15 @@ class SceneLoader extends Loader { if (fog.fogColor != undefined) scene.fogColor.copyFrom(fog.fogColor); } + // parse physics + const physics = data.scene.physics; + // PhysicsScene has a native backing only when the engine was created with a physics backend. + // Keep scene files loadable for render-only engines by ignoring serialized physics settings there. + if (physics && (engine as any)._physicsInitialized) { + if (physics.gravity != undefined) scene.physics.gravity.copyFrom(physics.gravity); + if (physics.fixedTimeStep != undefined) scene.physics.fixedTimeStep = physics.fixedTimeStep; + } + // Post Process const postProcessData = data.scene.postProcess; if (postProcessData) { diff --git a/packages/loader/src/ShaderLoader.ts b/packages/loader/src/ShaderLoader.ts index 3380ff297e..d7e714af50 100644 --- a/packages/loader/src/ShaderLoader.ts +++ b/packages/loader/src/ShaderLoader.ts @@ -9,7 +9,7 @@ import { } from "@galacean/engine-core"; import { ShaderChunkLoader } from "./ShaderChunkLoader"; -@resourceLoader(AssetType.Shader, ["gs", "gsl"]) +@resourceLoader(AssetType.Shader, ["shader"]) class ShaderLoader extends Loader { private static _builtinRegex = /^\s*\/\/\s*@builtin\s+(\w+)/; diff --git a/packages/loader/src/SpriteAtlasLoader.ts b/packages/loader/src/SpriteAtlasLoader.ts index 0bada4b334..c5a1d6a550 100644 --- a/packages/loader/src/SpriteAtlasLoader.ts +++ b/packages/loader/src/SpriteAtlasLoader.ts @@ -102,7 +102,7 @@ class SpriteAtlasLoader extends Loader { const { x: offsetLeft, y: offsetTop, z: offsetRight, w: offsetBottom } = atlasRegionOffset; sprite.atlasRegionOffset.set(offsetLeft * invW, offsetTop * invH, offsetRight * invW, offsetBottom * invH); } - config.atlasRotated && (sprite.atlasRotated = true); + sprite.atlasRotated = config.atlasRotated ?? false; } width === undefined || (sprite.width = width); height === undefined || (sprite.height = height); diff --git a/packages/loader/src/gltf/parser/GLTFParserContext.ts b/packages/loader/src/gltf/parser/GLTFParserContext.ts index 83f9f65124..b549da5ca2 100644 --- a/packages/loader/src/gltf/parser/GLTFParserContext.ts +++ b/packages/loader/src/gltf/parser/GLTFParserContext.ts @@ -116,13 +116,13 @@ export class GLTFParserContext { return AssetPromise.all([ this.get(GLTFParserType.Validator), + this.get(GLTFParserType.Scene), this.get(GLTFParserType.Texture), this.get(GLTFParserType.Material), this.get(GLTFParserType.Mesh), this.get(GLTFParserType.Skin), this.get(GLTFParserType.Animation), - this.get(GLTFParserType.AnimatorController), - this.get(GLTFParserType.Scene) + this.get(GLTFParserType.AnimatorController) ]).then(() => { const glTFResource = this.glTFResource; const animatorController = glTFResource.animatorController; diff --git a/packages/loader/src/gltf/parser/GLTFSceneParser.ts b/packages/loader/src/gltf/parser/GLTFSceneParser.ts index 6517356c97..553bf177f2 100644 --- a/packages/loader/src/gltf/parser/GLTFSceneParser.ts +++ b/packages/loader/src/gltf/parser/GLTFSceneParser.ts @@ -31,18 +31,14 @@ export class GLTFSceneParser extends GLTFParser { const sceneNodes = sceneInfo.nodes || []; let sceneRoot: Entity; - if (sceneNodes.length === 1) { - sceneRoot = context.get(GLTFParserType.Entity, sceneNodes[0]); - } else { - sceneRoot = new Entity(engine, "GLTF_ROOT"); - // @ts-ignore - sceneRoot._markAsTemplate(glTFResource); - for (let i = 0; i < sceneNodes.length; i++) { - const childEntity = context.get(GLTFParserType.Entity, sceneNodes[i]); - sceneRoot.addChild(childEntity); - } + sceneRoot = new Entity(engine, "GLTF_ROOT"); + // @ts-ignore + sceneRoot._markAsTemplate(glTFResource); + for (let i = 0; i < sceneNodes.length; i++) { + sceneRoot.addChild(context.get(GLTFParserType.Entity, sceneNodes[i])); } + (glTFResource._sceneRoots ||= [])[index] = sceneRoot; if (isDefaultScene) { glTFResource._defaultSceneRoot = sceneRoot; } @@ -200,49 +196,9 @@ export class GLTFSceneParser extends GLTFParser { if (rootBoneIndex !== -1) { BoundingBox.transform(mesh.bounds, inverseBindMatrices[rootBoneIndex], skinnedMeshRenderer.localBounds); } else { - // Root bone is not in joints list, we can only compute approximate inverse bind matrix - // Average all root bone's children inverse bind matrix - const approximateBindMatrix = new Matrix(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - let subRootBoneCount = this._computeApproximateBindMatrix( - bones, - inverseBindMatrices, - rootBone, - approximateBindMatrix - ); - - if (subRootBoneCount !== 0) { - Matrix.multiplyScalar(approximateBindMatrix, 1.0 / subRootBoneCount, approximateBindMatrix); - BoundingBox.transform(mesh.bounds, approximateBindMatrix, skinnedMeshRenderer.localBounds); - } else { - skinnedMeshRenderer.localBounds.copyFrom(mesh.bounds); - } + const inverseRootBoneWorld = new Matrix(); + Matrix.invert(rootBone.transform.worldMatrix, inverseRootBoneWorld); + BoundingBox.transform(mesh.bounds, inverseRootBoneWorld, skinnedMeshRenderer.localBounds); } } - - private _computeApproximateBindMatrix( - jointEntities: ReadonlyArray, - inverseBindMatrices: Matrix[], - rootEntity: Entity, - approximateBindMatrix: Matrix - ): number { - let subRootBoneCount = 0; - const children = rootEntity.children; - for (let i = 0, n = children.length; i < n; i++) { - const rootChild = children[i]; - const index = jointEntities.indexOf(rootChild); - if (index !== -1) { - Matrix.add(approximateBindMatrix, inverseBindMatrices[index], approximateBindMatrix); - subRootBoneCount++; - } else { - subRootBoneCount += this._computeApproximateBindMatrix( - jointEntities, - inverseBindMatrices, - rootChild, - approximateBindMatrix - ); - } - } - - return subRootBoneCount; - } } diff --git a/packages/loader/src/gltf/parser/GLTFSkinParser.test.ts b/packages/loader/src/gltf/parser/GLTFSkinParser.test.ts new file mode 100644 index 0000000000..bb5714b847 --- /dev/null +++ b/packages/loader/src/gltf/parser/GLTFSkinParser.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from "vitest"; + +describe("GLTFSkinParser rootBone resolution", () => { + async function createParser(): Promise { + (globalThis as any).window = { AudioContext: undefined, TextMetrics: undefined }; + const { GLTFSkinParser } = await import("./GLTFSkinParser"); + return new GLTFSkinParser(); + } + + it("includes skinned mesh nodes when resolving missing skin.skeleton", async () => { + const parser = await createParser(); + const sceneRoot = { name: "GLTF_ROOT" }; + const meshRoot = { name: "Character_Man", parent: sceneRoot }; + const hips = { name: "mixamorig:Hips", parent: sceneRoot }; + const spine = { name: "mixamorig:Spine", parent: hips }; + + const rootBone = (parser as any)._findSkinRootBoneByLCA( + 0, + [1, 2], + [meshRoot, hips, spine], + [{ name: "Character_Man", skin: 0 }, { name: "mixamorig:Hips" }, { name: "mixamorig:Spine" }] + ); + + expect(rootBone).toBe(sceneRoot); + }); + + it("does not promote to the scene wrapper for unrelated top-level siblings", async () => { + const parser = await createParser(); + const sceneRoot = { name: "GLTF_ROOT" }; + const characterRoot = { name: "Character_Root", parent: sceneRoot }; + const mesh = { name: "Character_Mesh", parent: characterRoot }; + const hips = { name: "mixamorig:Hips", parent: characterRoot }; + const light = { name: "Light", parent: sceneRoot }; + + const rootBone = (parser as any)._findSkinRootBoneByLCA( + 0, + [3], + [characterRoot, mesh, light, hips], + [{ name: "Character_Root" }, { name: "Character_Mesh", skin: 0 }, { name: "Light" }, { name: "mixamorig:Hips" }] + ); + + expect(rootBone).toBe(characterRoot); + }); +}); diff --git a/packages/loader/src/gltf/parser/GLTFSkinParser.ts b/packages/loader/src/gltf/parser/GLTFSkinParser.ts index 7c1580e4ca..3f11f487e5 100644 --- a/packages/loader/src/gltf/parser/GLTFSkinParser.ts +++ b/packages/loader/src/gltf/parser/GLTFSkinParser.ts @@ -39,7 +39,7 @@ export class GLTFSkinParser extends GLTFParser { const rootBone = entities[skeleton]; skin.rootBone = rootBone; } else { - const rootBone = this._findSkeletonRootBone(joints, entities); + const rootBone = this._findSkinRootBoneByLCA(index, joints, entities, glTF.nodes); if (rootBone) { skin.rootBone = rootBone; } else { @@ -53,28 +53,50 @@ export class GLTFSkinParser extends GLTFParser { return AssetPromise.resolve(skinPromise); } - private _findSkeletonRootBone(joints: number[], entities: Entity[]): Entity { - const paths = >{}; - for (const index of joints) { + private _findSkinRootBoneByLCA( + skinIndex: number, + joints: number[], + entities: Entity[], + nodes: Array<{ skin?: number }> = [] + ): Entity | null { + const nodeIndices = joints.slice(); + for (let i = 0, n = nodes.length; i < n; i++) { + if (nodes[i]?.skin === skinIndex) { + nodeIndices.push(i); + } + } + + return this._findRootBoneByLCA(nodeIndices, entities); + } + + private _findRootBoneByLCA(nodeIndices: number[], entities: Entity[]): Entity | null { + const paths: Entity[][] = []; + for (const index of nodeIndices) { const path = new Array(); let entity = entities[index]; while (entity) { path.unshift(entity); entity = entity.parent; } - paths[index] = path; + if (path.length) { + paths.push(path); + } + } + + if (!paths.length) { + return null; } - let rootNode = null; + let rootNode: Entity | null = null; for (let i = 0; ; i++) { - let path = paths[joints[0]]; + let path = paths[0]; if (i >= path.length) { return rootNode; } const entity = path[i]; - for (let j = 1, m = joints.length; j < m; j++) { - path = paths[joints[j]]; + for (let j = 1, m = paths.length; j < m; j++) { + path = paths[j]; if (i >= path.length || entity !== path[i]) { return rootNode; } diff --git a/packages/loader/src/resource-deserialize/resources/parser/ReflectionParser.ts b/packages/loader/src/resource-deserialize/resources/parser/ReflectionParser.ts index e79ea06864..fe92bbcf6a 100644 --- a/packages/loader/src/resource-deserialize/resources/parser/ReflectionParser.ts +++ b/packages/loader/src/resource-deserialize/resources/parser/ReflectionParser.ts @@ -125,7 +125,14 @@ export class ReflectionParser { if (!entity) return Promise.resolve(null); const type = Loader.getClass(value.componentType); if (!type) return Promise.resolve(null); - return Promise.resolve(entity.getComponents(type, [])[value.componentIndex] ?? null); + // Try direct components first, fallback to children search (for GLB clone entities + // where the component lives on a child entity inside the clone) + const direct = entity.getComponents(type, []); + const result = direct[value.componentIndex]; + if (result) return Promise.resolve(result); + const includeChildren: any[] = []; + entity.getComponentsIncludeChildren(type, includeChildren); + return Promise.resolve(includeChildren[value.componentIndex] ?? null); } else if (ReflectionParser._isEntityRef(value)) { return Promise.resolve(this._resolveEntityByPath(value.entityPath)); } else if (ReflectionParser._isSignalRef(value)) { diff --git a/packages/loader/src/resource-deserialize/resources/schema/SceneSchema.ts b/packages/loader/src/resource-deserialize/resources/schema/SceneSchema.ts index 81aaff21d6..1f64dcb87c 100644 --- a/packages/loader/src/resource-deserialize/resources/schema/SceneSchema.ts +++ b/packages/loader/src/resource-deserialize/resources/schema/SceneSchema.ts @@ -54,6 +54,10 @@ export interface IScene extends IHierarchyFile { fogDensity: number; fogColor: IColor; }; + physics?: { + gravity?: IVector3; + fixedTimeStep?: number; + }; postProcess?: { isActive: boolean; bloom: { diff --git a/packages/math/package.json b/packages/math/package.json index 42a52983a1..82055446bd 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-math", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/package.json b/packages/physics-lite/package.json index 30c4065480..9eaa1e74c4 100644 --- a/packages/physics-lite/package.json +++ b/packages/physics-lite/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-lite", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-lite/src/LitePhysicsMaterial.ts b/packages/physics-lite/src/LitePhysicsMaterial.ts index 7983beb394..de1a0dee53 100644 --- a/packages/physics-lite/src/LitePhysicsMaterial.ts +++ b/packages/physics-lite/src/LitePhysicsMaterial.ts @@ -2,8 +2,16 @@ import { IPhysicsMaterial } from "@galacean/engine-design"; /** * Physics material describes how to handle colliding objects (friction, bounciness). + * + * Physics-lite does not implement material effects; setters are no-ops (matching + * the convention used by `LiteColliderShape.setMaterial/setContactOffset/setIsTrigger`). + * This lets engine-side state changes (including clone `_syncNative` re-writes) flow + * through without crashing while still leaving a one-time hint via console.log on + * the first write so users notice the limitation. */ export class LitePhysicsMaterial implements IPhysicsMaterial { + private static _warned = false; + constructor( staticFriction: number, dynamicFriction: number, @@ -16,39 +24,45 @@ export class LitePhysicsMaterial implements IPhysicsMaterial { * {@inheritDoc IPhysicsMaterial.setBounciness } */ setBounciness(value: number): void { - throw "Physics-lite don't support physics material. Use Physics-PhysX instead!"; + LitePhysicsMaterial._warnOnce(); } /** * {@inheritDoc IPhysicsMaterial.setDynamicFriction } */ setDynamicFriction(value: number): void { - throw "Physics-lite don't support physics material. Use Physics-PhysX instead!"; + LitePhysicsMaterial._warnOnce(); } /** * {@inheritDoc IPhysicsMaterial.setStaticFriction } */ setStaticFriction(value: number): void { - throw "Physics-lite don't support physics material. Use Physics-PhysX instead!"; + LitePhysicsMaterial._warnOnce(); } /** * {@inheritDoc IPhysicsMaterial.setBounceCombine } */ setBounceCombine(value: number): void { - throw "Physics-lite don't support physics material. Use Physics-PhysX instead!"; + LitePhysicsMaterial._warnOnce(); } /** * {@inheritDoc IPhysicsMaterial.setFrictionCombine } */ setFrictionCombine(value: number): void { - throw "Physics-lite don't support physics material. Use Physics-PhysX instead!"; + LitePhysicsMaterial._warnOnce(); } /** * {@inheritDoc IPhysicsMaterial.destroy } */ destroy(): void {} + + private static _warnOnce(): void { + if (LitePhysicsMaterial._warned) return; + LitePhysicsMaterial._warned = true; + console.log("Physics-lite don't support physics material. Use Physics-PhysX instead!"); + } } diff --git a/packages/physics-physx/libs/physx.release.js b/packages/physics-physx/libs/physx.release.js index 426e4ca185..f816e9dce3 100644 --- a/packages/physics-physx/libs/physx.release.js +++ b/packages/physics-physx/libs/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/packages/physics-physx/libs/physx.release.simd.js b/packages/physics-physx/libs/physx.release.simd.js index bdb1d70990..45843d369b 100644 --- a/packages/physics-physx/libs/physx.release.simd.js +++ b/packages/physics-physx/libs/physx.release.simd.js @@ -1,2 +1,2 @@ -var PHYSX=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["P"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return "https://mdn.alipayobjects.com/rms/afts/file/A*6vi1SJaSJ6IAAAAAgDAAAAgAehQnAQ/physx.release.simd.wasm"}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var __abort_js=()=>abort("");var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var emval_freelist=[];var emval_handles=[0,1,,1,null,1,true,1,false,1];var BindingError=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};var throwBindingError=message=>{throw new BindingError(message)};var Emval={toValue:handle=>{if(!handle){throwBindingError(`Cannot use deleted val. handle = ${handle}`)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};class PureVirtualError extends Error{}var AsciiToString=ptr=>{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var registerInheritedInstance=(class_,ptr,instance)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){throwBindingError(`Tried to register registered instance: ${ptr}`)}else{registeredInstances[ptr]=instance}};var registeredTypes={};var getTypeName=type=>{var ptr=___getTypeName(type);var rv=AsciiToString(ptr);_free(ptr);return rv};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var unregisterInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){delete registeredInstances[ptr]}else{throwBindingError(`Tried to unregister unregistered instance: ${ptr}`)}};var detachFinalizer=handle=>{};var finalizationRegistry=false;var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var attachFinalizer=handle=>{if(!globalThis.FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var __embind_create_inheriting_constructor=(constructorName,wrapperType,properties)=>{constructorName=AsciiToString(constructorName);wrapperType=requireRegisteredType(wrapperType,"wrapper");properties=Emval.toValue(properties);var registeredClass=wrapperType.registeredClass;var wrapperPrototype=registeredClass.instancePrototype;var baseClass=registeredClass.baseClass;var baseClassPrototype=baseClass.instancePrototype;var baseConstructor=registeredClass.baseClass.constructor;var ctor=createNamedFunction(constructorName,function(...args){for(var name of registeredClass.baseClass.pureVirtualFunctions){if(this[name]===baseClassPrototype[name]){throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`)}}Object.defineProperty(this,"__parent",{value:wrapperPrototype});this["__construct"](...args)});wrapperPrototype["__construct"]=function __construct(...args){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __construct")}var inner=baseConstructor["implement"](this,...args);detachFinalizer(inner);var $$=inner.$$;inner["notifyOnDestruction"]();$$.preservePointerOnDelete=true;Object.defineProperties(this,{$$:{value:$$}});attachFinalizer(this);registerInheritedInstance(registeredClass,$$.ptr,this)};wrapperPrototype["__destruct"]=function __destruct(){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __destruct")}detachFinalizer(this);unregisterInheritedInstance(registeredClass,this.$$.ptr)};ctor.prototype=Object.create(wrapperPrototype);Object.assign(ctor.prototype,properties);return Emval.toHandle(ctor)};var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var awaitingDependencies={};var typeDependencies={};var InternalError=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}}if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};for(var[i,field]of fieldRecords.entries()){const getterReturnType=fieldTypes[i];const getter=field.getter;const getterContext=field.getterContext;const setterArgumentType=fieldTypes[i+fieldRecords.length];const setter=field.setter;const setterContext=field.setterContext;fields[field.fieldName]={read:ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o));runDestructors(destructors)},optional:getterReturnType.optional}}return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)&&!fields[fieldName].optional){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){return sharedRegisterType(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0n;let fromWireType=value=>value;if(isUnsignedType){const bitSize=size*8;fromWireType=value=>BigInt.asUintN(bitSize,value);maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>{if(typeof value=="number"){value=BigInt(value)}return value},readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})};var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var init_ClassHandle=()=>{let proto=ClassHandle.prototype;Object.assign(proto,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}});const symbolDispose=Symbol.dispose;if(symbolDispose){proto[symbolDispose]=proto["delete"]}};function ClassHandle(){}var registeredPointers={};var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupported sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this.toWireType=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this.toWireType=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this.toWireType=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var embind__requireFunction=(signature,rawFunction,isAsync=false)=>{signature=AsciiToString(signature);function makeDynCaller(){var rtn=getWasmTableEntry(rawFunction);return rtn}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};class UnboundTypeError extends Error{}var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError(`Use 'new' to construct ${name}`)}if(undefined===registeredClass.constructor_body){throw new BindingError(`${name} has no accessible constructor`)}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};function usesDestructorStack(argTypes){for(var i=1;i{var array=[];for(var i=0;i>2])}return array};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex===-1)return signature;return signature.slice(0,argsIndex)};var __embind_register_class_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn,isAsync);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}if(classType.registeredClass.__derivedClasses){for(const derivedClass of classType.registeredClass.__derivedClasses){if(!derivedClass.constructor.hasOwnProperty(methodName)){derivedClass.constructor[methodName]=func}}}return[]});return[]})};var __embind_register_class_constructor=(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var validateThis=(this_,classType,humanName)=>{if(!(this_ instanceof Object)){throwBindingError(`${humanName} with invalid "this": ${this_}`)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`)}if(!this_.$$.ptr){throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`)}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)};var __embind_register_class_property=(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{fieldName=AsciiToString(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],classType=>{classType=classType[0];var humanName=`${classType.name}.${fieldName}`;var desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])}else{desc.set=v=>throwBindingError(humanName+" is a read-only property")}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],types=>{var getterReturnType=types[0];var desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType.fromWireType(getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})};var __embind_register_constant=(name,type,value)=>{name=AsciiToString(name);whenDependentTypesAreResolved([],[type],type=>{type=type[0];Module[name]=type.fromWireType(value);return[]})};var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function getEnumValueType(rawValueType){return rawValueType===0?"object":rawValueType===1?"number":"string"}var __embind_register_enum=(rawType,name,size,isSigned,rawValueType)=>{name=AsciiToString(name);const valueType=getEnumValueType(rawValueType);switch(valueType){case"object":{function ctor(){}ctor.values={};registerType(rawType,{name,constructor:ctor,valueType,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor);break}case"number":{var keysMap={};registerType(rawType,{name,keysMap,valueType,fromWireType:c=>c,toWireType:(destructors,c)=>c,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}case"string":{var valuesMap={};var reverseMap={};var keysMap={};registerType(rawType,{name,valuesMap,reverseMap,keysMap,valueType,fromWireType:function(c){return this.reverseMap[c]},toWireType:function(destructors,c){return this.valuesMap[c]},readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}}};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=AsciiToString(name);switch(enumType.valueType){case"object":{var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value;break}case"number":{enumType.keysMap[name]=enumValue;break}case"string":{enumType.valuesMap[name]=enumValue;enumType.reverseMap[enumValue]=name;enumType.keysMap[name]=name;break}}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=AsciiToString(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker,isAsync);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0;let fromWireType=value=>value;if(isUnsignedType){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift;maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var installIndexedIterator=(proto,sizeMethodName,getMethodName)=>{const makeIterator=(size,getValue)=>{let index=0;return{next(){if(index>=size){return{done:true}}const current=index;index++;const value=getValue(current);return{value,done:false}},[Symbol.iterator](){return this}}};if(!proto[Symbol.iterator]){proto[Symbol.iterator]=function(){const size=this[sizeMethodName]();return makeIterator(size,i=>this[getMethodName](i))}}};var __embind_register_iterable=(rawClassType,rawElementType,sizeMethodName,getMethodName)=>{sizeMethodName=AsciiToString(sizeMethodName);getMethodName=AsciiToString(getMethodName);whenDependentTypesAreResolved([],[rawClassType,rawElementType],types=>{const classType=types[0];installIndexedIterator(classType.registeredClass.instancePrototype,sizeMethodName,getMethodName);return[]})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=AsciiToString(name);registerType(rawType,{name,fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})};var EmValOptionalType=Object.assign({optional:true},EmValType);var __embind_register_optional=(rawOptionalType,rawType)=>{registerType(rawOptionalType,EmValOptionalType)};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var __embind_register_std_string=(rawType,name)=>{name=AsciiToString(name);var stdStringIsUTF8=true;registerType(rawType,{name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){str=UTF8ToString(payload,length,true)}else{str="";for(var i=0;i>2]=length;if(valueIsOfTypeString){if(stdStringIsUTF8){stringToUTF8(value,ptr,length+1)}else{for(var i=0;i255){_free(base);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}}else{HEAPU8.set(value,ptr)}if(destructors!==null){destructors.push(_free,base)}return base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var UTF16Decoder=globalThis.TextDecoder?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{var idx=ptr>>1;var endIdx=findStringEnd(HEAPU16,idx,maxBytesToRead/2,ignoreNul);if(endIdx-idx>16&&UTF16Decoder)return UTF16Decoder.decode(HEAPU16.subarray(idx,endIdx));var str="";for(var i=idx;i{maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{var str="";var startIdx=ptr>>2;for(var i=0;!(i>=maxBytesToRead/4);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i65535){i++}HEAP32[outPtr>>2]=codePoint;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i65535){i++}len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=AsciiToString(name);var decodeString,encodeString,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16}else{decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32}registerType(rawType,{name,fromWireType:value=>{var length=HEAPU32[value>>2];var str=decodeString(value+4,length*charSize,true);_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:AsciiToString(fieldName),getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext,setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext})};var __embind_register_void=(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{isVoid:true,name,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var emval_methodCallers=[];var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],`parameter ${i}`)}return a};var emval_returnValue=(toReturnWire,destructorsRef,handle)=>{var destructors=[];var result=toReturnWire(destructors,handle);if(destructors.length){HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)}return result};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return AsciiToString(address)}return symbol};var __emval_create_invoker=(argCount,argTypesPtr,kind)=>{var GenericWireTypeSize=8;var[retType,...argTypes]=emval_lookupTypes(argCount,argTypesPtr);var toReturnWire=retType.toWireType.bind(retType);var argFromPtr=argTypes.map(type=>type.readValueFromPointer.bind(type));argCount--;var argN=new Array(argCount);var invokerFunction=(handle,methodName,destructorsRef,args)=>{var offset=0;for(var i=0;it.name)}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))};var __emval_invoke=(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args);var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_new_object=()=>Emval.toHandle({});var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var __emval_set_property=(handle,key,value)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var _emscripten_date_now=()=>Date.now();var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var printCharBuffers=[null,[],[]];var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};init_ClassHandle();init_RegisteredPointer();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var ___getTypeName,_free,_malloc,__emscripten_timeout,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){___getTypeName=wasmExports["Q"];_free=Module["_free"]=wasmExports["R"];_malloc=Module["_malloc"]=wasmExports["S"];__emscripten_timeout=wasmExports["U"];memory=wasmMemory=wasmExports["O"];__indirect_function_table=wasmTable=wasmExports["T"]}var wasmImports={J:__abort_js,x:__embind_create_inheriting_constructor,s:__embind_finalize_value_object,z:__embind_register_bigint,A:__embind_register_bool,b:__embind_register_class,p:__embind_register_class_class_function,e:__embind_register_class_constructor,a:__embind_register_class_function,d:__embind_register_class_property,K:__embind_register_constant,M:__embind_register_emval,l:__embind_register_enum,f:__embind_register_enum_value,y:__embind_register_float,g:__embind_register_function,o:__embind_register_integer,q:__embind_register_iterable,h:__embind_register_memory_view,r:__embind_register_optional,N:__embind_register_std_string,u:__embind_register_std_wstring,t:__embind_register_value_object,m:__embind_register_value_object_field,B:__embind_register_void,D:__emscripten_runtime_keepalive_clear,k:__emval_create_invoker,n:__emval_decref,j:__emval_invoke,v:__emval_new_cstring,L:__emval_new_object,i:__emval_run_destructors,w:__emval_set_property,E:__setitimer_js,I:_emscripten_date_now,c:_emscripten_get_now,F:_emscripten_resize_heap,G:_exit,H:_fd_write,C:_proc_exit};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} +var PHYSX=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};if(typeof __filename!="undefined"){_scriptName=__filename}else if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");var readyPromiseResolve,readyPromiseReject;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var HEAP64,HEAPU64;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["P"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("physx.release.simd.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var __abort_js=()=>abort("");var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var emval_freelist=[];var emval_handles=[0,1,,1,null,1,true,1,false,1];var BindingError=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};var throwBindingError=message=>{throw new BindingError(message)};var Emval={toValue:handle=>{if(!handle){throwBindingError(`Cannot use deleted val. handle = ${handle}`)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};class PureVirtualError extends Error{}var AsciiToString=ptr=>{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var registerInheritedInstance=(class_,ptr,instance)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){throwBindingError(`Tried to register registered instance: ${ptr}`)}else{registeredInstances[ptr]=instance}};var registeredTypes={};var getTypeName=type=>{var ptr=___getTypeName(type);var rv=AsciiToString(ptr);_free(ptr);return rv};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var unregisterInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);if(registeredInstances.hasOwnProperty(ptr)){delete registeredInstances[ptr]}else{throwBindingError(`Tried to unregister unregistered instance: ${ptr}`)}};var detachFinalizer=handle=>{};var finalizationRegistry=false;var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var attachFinalizer=handle=>{if(!globalThis.FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var __embind_create_inheriting_constructor=(constructorName,wrapperType,properties)=>{constructorName=AsciiToString(constructorName);wrapperType=requireRegisteredType(wrapperType,"wrapper");properties=Emval.toValue(properties);var registeredClass=wrapperType.registeredClass;var wrapperPrototype=registeredClass.instancePrototype;var baseClass=registeredClass.baseClass;var baseClassPrototype=baseClass.instancePrototype;var baseConstructor=registeredClass.baseClass.constructor;var ctor=createNamedFunction(constructorName,function(...args){for(var name of registeredClass.baseClass.pureVirtualFunctions){if(this[name]===baseClassPrototype[name]){throw new PureVirtualError(`Pure virtual function ${name} must be implemented in JavaScript`)}}Object.defineProperty(this,"__parent",{value:wrapperPrototype});this["__construct"](...args)});wrapperPrototype["__construct"]=function __construct(...args){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __construct")}var inner=baseConstructor["implement"](this,...args);detachFinalizer(inner);var $$=inner.$$;inner["notifyOnDestruction"]();$$.preservePointerOnDelete=true;Object.defineProperties(this,{$$:{value:$$}});attachFinalizer(this);registerInheritedInstance(registeredClass,$$.ptr,this)};wrapperPrototype["__destruct"]=function __destruct(){if(this===wrapperPrototype){throwBindingError("Pass correct 'this' to __destruct")}detachFinalizer(this);unregisterInheritedInstance(registeredClass,this.$$.ptr)};ctor.prototype=Object.create(wrapperPrototype);Object.assign(ctor.prototype,properties);return Emval.toHandle(ctor)};var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var awaitingDependencies={};var typeDependencies={};var InternalError=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}}if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};for(var[i,field]of fieldRecords.entries()){const getterReturnType=fieldTypes[i];const getter=field.getter;const getterContext=field.getterContext;const setterArgumentType=fieldTypes[i+fieldRecords.length];const setter=field.setter;const setterContext=field.setterContext;fields[field.fieldName]={read:ptr=>getterReturnType.fromWireType(getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,o));runDestructors(destructors)},optional:getterReturnType.optional}}return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)&&!fields[fieldName].optional){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){return sharedRegisterType(rawType,registeredInstance,options)}var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0n;let fromWireType=value=>value;if(isUnsignedType){const bitSize=size*8;fromWireType=value=>BigInt.asUintN(bitSize,value);maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>{if(typeof value=="number"){value=BigInt(value)}return value},readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})};var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var init_ClassHandle=()=>{let proto=ClassHandle.prototype;Object.assign(proto,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}});const symbolDispose=Symbol.dispose;if(symbolDispose){proto[symbolDispose]=proto["delete"]}};function ClassHandle(){}var registeredPointers={};var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupported sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this.toWireType=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this.toWireType=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this.toWireType=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var embind__requireFunction=(signature,rawFunction,isAsync=false)=>{signature=AsciiToString(signature);function makeDynCaller(){var rtn=getWasmTableEntry(rawFunction);return rtn}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};class UnboundTypeError extends Error{}var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError(`Use 'new' to construct ${name}`)}if(undefined===registeredClass.constructor_body){throw new BindingError(`${name} has no accessible constructor`)}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};function usesDestructorStack(argTypes){for(var i=1;i{var array=[];for(var i=0;i>2])}return array};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex===-1)return signature;return signature.slice(0,argsIndex)};var __embind_register_class_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn,isAsync);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}if(classType.registeredClass.__derivedClasses){for(const derivedClass of classType.registeredClass.__derivedClasses){if(!derivedClass.constructor.hasOwnProperty(methodName)){derivedClass.constructor[methodName]=func}}}return[]});return[]})};var __embind_register_class_constructor=(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync,isNonnullReturn)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker,isAsync);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var validateThis=(this_,classType,humanName)=>{if(!(this_ instanceof Object)){throwBindingError(`${humanName} with invalid "this": ${this_}`)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(`${humanName} incompatible with "this" of type ${this_.constructor.name}`)}if(!this_.$$.ptr){throwBindingError(`cannot call emscripten binding method ${humanName} on deleted object`)}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)};var __embind_register_class_property=(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{fieldName=AsciiToString(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],classType=>{classType=classType[0];var humanName=`${classType.name}.${fieldName}`;var desc={get(){throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>throwUnboundTypeError(`Cannot access ${humanName} due to unbound types`,[getterReturnType,setterArgumentType])}else{desc.set=v=>throwBindingError(humanName+" is a read-only property")}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],types=>{var getterReturnType=types[0];var desc={get(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType.fromWireType(getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType.toWireType(destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})};var __embind_register_constant=(name,type,value)=>{name=AsciiToString(name);whenDependentTypesAreResolved([],[type],type=>{type=type[0];Module[name]=type.fromWireType(value);return[]})};var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this.fromWireType(HEAP8[pointer])}:function(pointer){return this.fromWireType(HEAPU8[pointer])};case 2:return signed?function(pointer){return this.fromWireType(HEAP16[pointer>>1])}:function(pointer){return this.fromWireType(HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this.fromWireType(HEAP32[pointer>>2])}:function(pointer){return this.fromWireType(HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};function getEnumValueType(rawValueType){return rawValueType===0?"object":rawValueType===1?"number":"string"}var __embind_register_enum=(rawType,name,size,isSigned,rawValueType)=>{name=AsciiToString(name);const valueType=getEnumValueType(rawValueType);switch(valueType){case"object":{function ctor(){}ctor.values={};registerType(rawType,{name,constructor:ctor,valueType,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor);break}case"number":{var keysMap={};registerType(rawType,{name,keysMap,valueType,fromWireType:c=>c,toWireType:(destructors,c)=>c,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}case"string":{var valuesMap={};var reverseMap={};var keysMap={};registerType(rawType,{name,valuesMap,reverseMap,keysMap,valueType,fromWireType:function(c){return this.reverseMap[c]},toWireType:function(destructors,c){return this.valuesMap[c]},readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,keysMap);delete Module[name].argCount;break}}};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=AsciiToString(name);switch(enumType.valueType){case"object":{var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value;break}case"number":{enumType.keysMap[name]=enumValue;break}case"string":{enumType.valuesMap[name]=enumValue;enumType.reverseMap[enumValue]=name;enumType.keysMap[name]=name;break}}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync,isNonnullReturn)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=AsciiToString(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker,isAsync);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0;let fromWireType=value=>value;if(isUnsignedType){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift;maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var installIndexedIterator=(proto,sizeMethodName,getMethodName)=>{const makeIterator=(size,getValue)=>{let index=0;return{next(){if(index>=size){return{done:true}}const current=index;index++;const value=getValue(current);return{value,done:false}},[Symbol.iterator](){return this}}};if(!proto[Symbol.iterator]){proto[Symbol.iterator]=function(){const size=this[sizeMethodName]();return makeIterator(size,i=>this[getMethodName](i))}}};var __embind_register_iterable=(rawClassType,rawElementType,sizeMethodName,getMethodName)=>{sizeMethodName=AsciiToString(sizeMethodName);getMethodName=AsciiToString(getMethodName);whenDependentTypesAreResolved([],[rawClassType,rawElementType],types=>{const classType=types[0];installIndexedIterator(classType.registeredClass.instancePrototype,sizeMethodName,getMethodName);return[]})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=AsciiToString(name);registerType(rawType,{name,fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})};var EmValOptionalType=Object.assign({optional:true},EmValType);var __embind_register_optional=(rawOptionalType,rawType)=>{registerType(rawOptionalType,EmValOptionalType)};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var __embind_register_std_string=(rawType,name)=>{name=AsciiToString(name);var stdStringIsUTF8=true;registerType(rawType,{name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){str=UTF8ToString(payload,length,true)}else{str="";for(var i=0;i>2]=length;if(valueIsOfTypeString){if(stdStringIsUTF8){stringToUTF8(value,ptr,length+1)}else{for(var i=0;i255){_free(base);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}}else{HEAPU8.set(value,ptr)}if(destructors!==null){destructors.push(_free,base)}return base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var UTF16Decoder=globalThis.TextDecoder?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{var idx=ptr>>1;var endIdx=findStringEnd(HEAPU16,idx,maxBytesToRead/2,ignoreNul);if(endIdx-idx>16&&UTF16Decoder)return UTF16Decoder.decode(HEAPU16.subarray(idx,endIdx));var str="";for(var i=idx;i{maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{var str="";var startIdx=ptr>>2;for(var i=0;!(i>=maxBytesToRead/4);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i65535){i++}HEAP32[outPtr>>2]=codePoint;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i65535){i++}len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=AsciiToString(name);var decodeString,encodeString,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16}else{decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32}registerType(rawType,{name,fromWireType:value=>{var length=HEAPU32[value>>2];var str=decodeString(value+4,length*charSize,true);_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:AsciiToString(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:AsciiToString(fieldName),getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext,setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext})};var __embind_register_void=(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{isVoid:true,name,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var emval_methodCallers=[];var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],`parameter ${i}`)}return a};var emval_returnValue=(toReturnWire,destructorsRef,handle)=>{var destructors=[];var result=toReturnWire(destructors,handle);if(destructors.length){HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)}return result};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return AsciiToString(address)}return symbol};var __emval_create_invoker=(argCount,argTypesPtr,kind)=>{var GenericWireTypeSize=8;var[retType,...argTypes]=emval_lookupTypes(argCount,argTypesPtr);var toReturnWire=retType.toWireType.bind(retType);var argFromPtr=argTypes.map(type=>type.readValueFromPointer.bind(type));argCount--;var argN=new Array(argCount);var invokerFunction=(handle,methodName,destructorsRef,args)=>{var offset=0;for(var i=0;it.name)}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))};var __emval_invoke=(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args);var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_new_object=()=>Emval.toHandle({});var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var __emval_set_property=(handle,key,value)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var _emscripten_date_now=()=>Date.now();var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var printCharBuffers=[null,[],[]];var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};init_ClassHandle();init_RegisteredPointer();{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var ___getTypeName,_free,_malloc,__emscripten_timeout,memory,__indirect_function_table,wasmMemory,wasmTable;function assignWasmExports(wasmExports){___getTypeName=wasmExports["Q"];_free=Module["_free"]=wasmExports["R"];_malloc=Module["_malloc"]=wasmExports["S"];__emscripten_timeout=wasmExports["U"];memory=wasmMemory=wasmExports["O"];__indirect_function_table=wasmTable=wasmExports["T"]}var wasmImports={J:__abort_js,x:__embind_create_inheriting_constructor,s:__embind_finalize_value_object,z:__embind_register_bigint,A:__embind_register_bool,b:__embind_register_class,p:__embind_register_class_class_function,e:__embind_register_class_constructor,a:__embind_register_class_function,d:__embind_register_class_property,K:__embind_register_constant,M:__embind_register_emval,l:__embind_register_enum,f:__embind_register_enum_value,y:__embind_register_float,g:__embind_register_function,o:__embind_register_integer,q:__embind_register_iterable,h:__embind_register_memory_view,r:__embind_register_optional,N:__embind_register_std_string,u:__embind_register_std_wstring,t:__embind_register_value_object,m:__embind_register_value_object_field,B:__embind_register_void,D:__emscripten_runtime_keepalive_clear,k:__emval_create_invoker,n:__emval_decref,j:__emval_invoke,v:__emval_new_cstring,L:__emval_new_object,i:__emval_run_destructors,w:__emval_set_property,E:__setitimer_js,I:_emscripten_date_now,c:_emscripten_get_now,F:_emscripten_resize_heap,G:_exit,H:_fd_write,C:_proc_exit};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} ;return moduleRtn}})();if(typeof exports==="object"&&typeof module==="object"){module.exports=PHYSX;module.exports.default=PHYSX}else if(typeof define==="function"&&define["amd"])define([],()=>PHYSX); diff --git a/packages/physics-physx/libs/physx.release.simd.wasm b/packages/physics-physx/libs/physx.release.simd.wasm index 91e5bab34b..f52b48110a 100755 Binary files a/packages/physics-physx/libs/physx.release.simd.wasm and b/packages/physics-physx/libs/physx.release.simd.wasm differ diff --git a/packages/physics-physx/libs/physx.release.wasm b/packages/physics-physx/libs/physx.release.wasm index d9e03eee6a..3efb26a288 100755 Binary files a/packages/physics-physx/libs/physx.release.wasm and b/packages/physics-physx/libs/physx.release.wasm differ diff --git a/packages/physics-physx/package.json b/packages/physics-physx/package.json index 3cdabdd9a9..d97107856f 100644 --- a/packages/physics-physx/package.json +++ b/packages/physics-physx/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-physics-physx", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/physics-physx/src/PhysXDynamicCollider.ts b/packages/physics-physx/src/PhysXDynamicCollider.ts index 31510ae2de..05614dd5bf 100644 --- a/packages/physics-physx/src/PhysXDynamicCollider.ts +++ b/packages/physics-physx/src/PhysXDynamicCollider.ts @@ -24,6 +24,20 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli private static _tempTranslation = new Vector3(); private static _tempRotation = new Quaternion(); + /** + * Whether actor is currently kinematic. + * PhysX 拒绝在 kinematic actor 上启用 CCD(会打印警告并忽略), + * 所以 setCollisionDetectionMode 在 kinematic 状态下只缓存目标值, + * 等切回 dynamic 时再真正写到 PhysX。 + */ + private _isKinematic: boolean = false; + + /** + * Cached collision detection mode. Always reflects user's intent. + * 实际 PhysX CCD flag 可能跟这个不一致(kinematic 时强制 Discrete)。 + */ + private _collisionDetectionMode: number = CollisionDetectionMode.Discrete; + constructor(physXPhysics: PhysXPhysics, position: Vector3, rotation: Quaternion) { super(physXPhysics); const transform = this._transform(position, rotation); @@ -158,10 +172,52 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli /** * {@inheritDoc IDynamicCollider.setCollisionDetectionMode } + * + * PhysX 在 kinematic actor 上调用 setRigidBodyFlag(eENABLE_CCD, true) 会触发警告: + * "kinematic bodies with CCD enabled are not supported! CCD will be ignored" + * 虽然 PhysX 会忽略这次调用而非真的拒绝(切回 dynamic 时 flag 不会自动恢复), + * 但每次 setIsKinematic 切换都会让这个 warning 重复打印,污染日志, + * 同时让 actor 在 dynamic 状态下 CCD flag 状态不确定。 + * + * 解决: 只在 dynamic 状态时立即 apply CCD flags。kinematic 时仅缓存到 + * `_collisionDetectionMode`,等切回 dynamic 时由 setIsKinematic 重新 apply。 */ setCollisionDetectionMode(value: number): void { + this._collisionDetectionMode = value; + if (!this._isKinematic) { + this._applyCollisionDetectionFlags(value); + } + } + + /** + * {@inheritDoc IDynamicCollider.setUseGravity } + */ + setUseGravity(value: boolean): void { + this._pxActor.setActorFlag(this._physXPhysics._physX.PxActorFlag.eDISABLE_GRAVITY, !value); + } + + /** + * {@inheritDoc IDynamicCollider.setIsKinematic } + * + * 切换 kinematic 状态时同步处理 CCD flag: + * - 切到 kinematic 前先关 CCD(避免 PhysX 警告 + 让状态显式) + * - 切回 dynamic 后恢复用户期望的 CCD mode(来自 `_collisionDetectionMode` 缓存) + */ + setIsKinematic(value: boolean): void { + if (this._isKinematic === value) return; const physX = this._physXPhysics._physX; + if (value) { + this._applyCollisionDetectionFlags(CollisionDetectionMode.Discrete); + this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eKINEMATIC, true); + } else { + this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eKINEMATIC, false); + this._applyCollisionDetectionFlags(this._collisionDetectionMode); + } + this._isKinematic = value; + } + private _applyCollisionDetectionFlags(value: number): void { + const physX = this._physXPhysics._physX; switch (value) { case CollisionDetectionMode.Continuous: this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD, true); @@ -186,24 +242,6 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli } } - /** - * {@inheritDoc IDynamicCollider.setUseGravity } - */ - setUseGravity(value: boolean): void { - this._pxActor.setActorFlag(this._physXPhysics._physX.PxActorFlag.eDISABLE_GRAVITY, !value); - } - - /** - * {@inheritDoc IDynamicCollider.setIsKinematic } - */ - setIsKinematic(value: boolean): void { - if (value) { - this._pxActor.setRigidBodyFlag(this._physXPhysics._physX.PxRigidBodyFlag.eKINEMATIC, true); - } else { - this._pxActor.setRigidBodyFlag(this._physXPhysics._physX.PxRigidBodyFlag.eKINEMATIC, false); - } - } - /** * {@inheritDoc IDynamicCollider.setConstraints } */ @@ -213,34 +251,52 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli /** * {@inheritDoc IDynamicCollider.addForce } + * + * PhysX 在 kinematic actor 上调 addForce 是 no-op(doc: "kinematic bodies don't + * respond to forces")。提前 return 避免无意义的 wasm boundary cross。 + * + * Sleeping actor 不需要显式 wakeUp — wasm binding 调用 `addForce(force, eFORCE, + * autowake=true)`,PhysX 自动唤醒(已通过 `applyForce on sleeping actor` 测试验证)。 */ addForce(force: Vector3) { + if (this._isKinematic) return; this._pxActor.addForce({ x: force.x, y: force.y, z: force.z }); } /** * {@inheritDoc IDynamicCollider.addTorque } + * + * 同 addForce — kinematic 提前 return,sleeping 由 PhysX autowake 自动处理。 */ addTorque(torque: Vector3) { + if (this._isKinematic) return; this._pxActor.addTorque({ x: torque.x, y: torque.y, z: torque.z }); } /** * {@inheritDoc IDynamicCollider.move } + * + * PhysX 要求 setKinematicTarget 的 rotation 是 normalized quaternion,否则会触发 + * 内部 assertion / 警告,并把 actor 转到错误的姿态。所以在写入 wasm 边界前统一 normalize。 */ move(positionOrRotation: Vector3 | Quaternion, rotation?: Quaternion): void { + const tempTranslation = PhysXDynamicCollider._tempTranslation; + const tempRotation = PhysXDynamicCollider._tempRotation; + if (rotation) { - this._pxActor.setKinematicTarget(positionOrRotation, rotation); + tempRotation.copyFrom(rotation).normalize(); + this._pxActor.setKinematicTarget(positionOrRotation, tempRotation); return; } - const tempTranslation = PhysXDynamicCollider._tempTranslation; - const tempRotation = PhysXDynamicCollider._tempRotation; - this.getWorldTransform(tempTranslation, tempRotation); if (positionOrRotation instanceof Vector3) { + this.getWorldTransform(tempTranslation, tempRotation); + // current rotation read from PhysX is already normalized; no extra work needed this._pxActor.setKinematicTarget(positionOrRotation, tempRotation); } else { - this._pxActor.setKinematicTarget(tempTranslation, positionOrRotation); + this.getWorldTransform(tempTranslation, tempRotation); + tempRotation.copyFrom(positionOrRotation).normalize(); + this._pxActor.setKinematicTarget(tempTranslation, tempRotation); } } diff --git a/packages/physics-physx/src/PhysXPhysics.ts b/packages/physics-physx/src/PhysXPhysics.ts index 77ca0b6157..867ee68dff 100644 --- a/packages/physics-physx/src/PhysXPhysics.ts +++ b/packages/physics-physx/src/PhysXPhysics.ts @@ -57,20 +57,35 @@ export class PhysXPhysics implements IPhysics { private _tolerancesScale: any; private _wasmSIMDModeUrl: string; private _wasmModeUrl: string; + private _tolerancesScaleOptions: PhysXTolerancesScale | undefined; + private _defaultContactOffset = 0.02; + private _defaultSleepThreshold = 5e-3; /** * Create a PhysXPhysics instance. * @param runtimeMode - Runtime mode, `Auto` prefers WebAssembly SIMD if supported @see {@link PhysXRuntimeMode} * @param runtimeUrls - Manually specify the runtime URLs + * @param options - PhysX options. */ - constructor(runtimeMode: PhysXRuntimeMode = PhysXRuntimeMode.Auto, runtimeUrls?: PhysXRuntimeUrls) { + constructor(runtimeMode?: PhysXRuntimeMode, runtimeUrls?: PhysXRuntimeUrls, options?: PhysXPhysicsOptions); + constructor(options?: PhysXPhysicsOptions); + constructor( + runtimeModeOrOptions: PhysXRuntimeMode | PhysXPhysicsOptions = PhysXRuntimeMode.Auto, + runtimeUrls?: PhysXRuntimeUrls, + options?: PhysXPhysicsOptions + ) { + const isOptionsObject = typeof runtimeModeOrOptions === "object"; + const runtimeMode = isOptionsObject ? PhysXRuntimeMode.Auto : (runtimeModeOrOptions ?? PhysXRuntimeMode.Auto); + const resolvedOptions = isOptionsObject ? runtimeModeOrOptions : options; this._runTimeMode = runtimeMode; this._wasmSIMDModeUrl = runtimeUrls?.wasmSIMDModeUrl ?? - "https://mdn.alipayobjects.com/rms/afts/file/A*FHYHS4_ZL5UAAAAAQ4AAAAgAehQnAQ/physx.release.simd.js"; + "https://mdn.alipayobjects.com/rms/afts/file/A*iHrYQKBrgTAAAAAAQ4AAAAgAehQnAQ/physx.release.simd.js"; this._wasmModeUrl = runtimeUrls?.wasmModeUrl ?? - "https://mdn.alipayobjects.com/rms/afts/file/A*2fv0RLMK1d0AAAAAQ4AAAAgAehQnAQ/physx.release.js"; + "https://mdn.alipayobjects.com/rms/afts/file/A*DFuvR6Mv5C0AAAAAQ4AAAAgAehQnAQ/physx.release.js"; + this._tolerancesScaleOptions = resolvedOptions?.tolerancesScale; + this._updateScaledDefaults(this._tolerancesScaleOptions?.length ?? 1, this._tolerancesScaleOptions?.speed ?? 10); } /** @@ -156,6 +171,20 @@ export class PhysXPhysics implements IPhysics { return scene; } + /** + * {@inheritDoc IPhysics.getDefaultContactOffset } + */ + getDefaultContactOffset(): number { + return this._defaultContactOffset; + } + + /** + * {@inheritDoc IPhysics.getDefaultSleepThreshold } + */ + getDefaultSleepThreshold(): number { + return this._defaultSleepThreshold; + } + /** * {@inheritDoc IPhysics.createStaticCollider } */ @@ -279,6 +308,7 @@ export class PhysXPhysics implements IPhysics { const allocator = new physX.PxDefaultAllocator(); const pxFoundation = physX.PxCreateFoundation(version, allocator, defaultErrorCallback); const tolerancesScale = new physX.PxTolerancesScale(); + this._applyTolerancesScale(tolerancesScale); const pxPhysics = physX.PxCreatePhysics(version, pxFoundation, tolerancesScale, false, null); physX.PxInitExtensions(pxPhysics, null); @@ -302,6 +332,29 @@ export class PhysXPhysics implements IPhysics { this._allocator = allocator; this._tolerancesScale = tolerancesScale; } + + private _applyTolerancesScale(tolerancesScale: any): void { + const length = this._tolerancesScaleOptions?.length ?? tolerancesScale.length; + const speed = this._tolerancesScaleOptions?.speed ?? tolerancesScale.speed; + + this._assertPositiveFinite(length, "tolerancesScale.length"); + this._assertPositiveFinite(speed, "tolerancesScale.speed"); + + tolerancesScale.length = length; + tolerancesScale.speed = speed; + this._updateScaledDefaults(length, speed); + } + + private _assertPositiveFinite(value: number, name: string): void { + if (!Number.isFinite(value) || value <= 0) { + throw new Error(`PhysXPhysics ${name} must be a positive finite number.`); + } + } + + private _updateScaledDefaults(length: number, speed: number): void { + this._defaultContactOffset = 0.02 * length; + this._defaultSleepThreshold = 5e-5 * speed * speed; + } } enum InitializeState { @@ -316,3 +369,15 @@ interface PhysXRuntimeUrls { /*** The URL of `PhysXRuntimeMode.WebAssemblySIMD` mode. */ wasmSIMDModeUrl?: string; } + +export interface PhysXTolerancesScale { + /** Approximate object length in the simulation unit. PhysX default is 1. */ + length?: number; + /** Typical object speed in the simulation unit. PhysX default is 10. */ + speed?: number; +} + +export interface PhysXPhysicsOptions { + /** PhysX world unit scale used before PxPhysics, PxSceneDesc and PxCookingParams are created. */ + tolerancesScale?: PhysXTolerancesScale; +} diff --git a/packages/physics-physx/src/PhysXPhysicsScene.ts b/packages/physics-physx/src/PhysXPhysicsScene.ts index a56a1fabce..d026304e4d 100644 --- a/packages/physics-physx/src/PhysXPhysicsScene.ts +++ b/packages/physics-physx/src/PhysXPhysicsScene.ts @@ -29,11 +29,24 @@ export class PhysXPhysicsScene implements IPhysicsScene { private _physXPhysics: PhysXPhysics; private _physXManager: PhysXPhysicsManager; private _pxRaycastHit: any; + private _pxSweepHit: any; private _pxFilterData: any; + private _pxRaycastSweepFilterData: any; private _pxScene: any; private _physXSimulationCallbackInstance: any; + // A single persistent PhysX query filter callback is shared by raycast, + // sweep and overlap. PhysX SDK guarantees that `postFilter` is only invoked + // when `PxQueryFlag::ePOSTFILTER` is set on the query's filter data, so + // overlap (whose filter data omits POST_FILTER) safely uses the same + // callback that also handles the raycast/sweep initial-overlap skip. + // The user-supplied predicate is stored in `_currentOnQuery`; reentrant + // calls save the previous value on the call stack via a local in each + // query method, recreating C++-style RAII without an explicit stack array. + private _pxQueryCallback: any; + private _currentOnQuery: (obj: number) => boolean = null; + private _activeTriggers: DisorderedArray = new DisorderedArray(); private _contactEvents: ContactEvent[] = []; private _contactEventCount = 0; @@ -49,8 +62,13 @@ export class PhysXPhysicsScene implements IPhysicsScene { const physX = physXPhysics._physX; this._pxRaycastHit = new physX.PxRaycastHit(); + this._pxSweepHit = new physX.PxSweepHit(); this._pxFilterData = new physX.PxQueryFilterData(); this._pxFilterData.flags = new physX.PxQueryFlags(QueryFlag.STATIC | QueryFlag.DYNAMIC | QueryFlag.PRE_FILTER); + this._pxRaycastSweepFilterData = new physX.PxQueryFilterData(); + this._pxRaycastSweepFilterData.flags = new physX.PxQueryFlags( + QueryFlag.STATIC | QueryFlag.DYNAMIC | QueryFlag.PRE_FILTER | QueryFlag.POST_FILTER + ); const triggerCallback = { onContactBegin: (collision) => { @@ -91,6 +109,14 @@ export class PhysXPhysicsScene implements IPhysicsScene { ); this._pxScene = pxPhysics.createScene(sceneDesc); sceneDesc.delete(); + + this._pxQueryCallback = physX.PxQueryFilterCallback.implement({ + preFilter: (_filterData: any, index: number, _actor: any) => + this._currentOnQuery(index) ? QueryHitType.BLOCK : QueryHitType.NONE, + // distance <= 0 means initial overlap — drop the hit so subsequent hits can be considered. + // Only invoked when the query's filter data includes POST_FILTER (raycast/sweep, not overlap). + postFilter: (_filterData: any, distance: number) => (distance <= 0 ? QueryHitType.NONE : QueryHitType.BLOCK) + }); } /** @@ -207,27 +233,21 @@ export class PhysXPhysicsScene implements IPhysicsScene { const { _pxRaycastHit: pxHitResult } = this; distance = Math.min(distance, 3.4e38); // float32 max value limit in physX raycast. - const raycastCallback = { - preFilter: (filterData, index, actor) => { - if (onRaycast(index)) { - return 2; // eBLOCK - } else { - return 0; // eNONE - } - } - }; - - const pxRaycastCallback = this._physXPhysics._physX.PxQueryFilterCallback.implement(raycastCallback); - const result = this._pxScene.raycastSingle( - ray.origin, - ray.direction, - distance, - pxHitResult, - this._pxFilterData, - pxRaycastCallback - ); - - pxRaycastCallback.delete(); + const prevOnQuery = this._currentOnQuery; + this._currentOnQuery = onRaycast; + let result: boolean; + try { + result = this._pxScene.raycastSingle( + ray.origin, + ray.direction, + distance, + pxHitResult, + this._pxRaycastSweepFilterData, + this._pxQueryCallback + ); + } finally { + this._currentOnQuery = prevOnQuery; + } if (result && hit != undefined) { const { _tempPosition: position, _tempNormal: normal } = PhysXPhysicsScene; @@ -390,8 +410,12 @@ export class PhysXPhysicsScene implements IPhysicsScene { this._physXSimulationCallbackInstance.delete(); this._pxRaycastHit.delete(); + this._pxSweepHit.delete(); this._pxFilterData.flags.delete(); this._pxFilterData.delete(); + this._pxRaycastSweepFilterData.flags.delete(); + this._pxRaycastSweepFilterData.delete(); + this._pxQueryCallback.delete(); // Need to release the controller manager before release the scene. this._pxControllerManager?.release(); this._pxScene.release(); @@ -443,29 +467,25 @@ export class PhysXPhysicsScene implements IPhysicsScene { onSweep: (obj: number) => boolean, outHitResult?: (shapeUniqueID: number, distance: number, position: Vector3, normal: Vector3) => void ): boolean { + const { _pxSweepHit: pxSweepHit } = this; distance = Math.min(distance, 3.4e38); // float32 max value limit in physx sweep - const sweepCallback = { - preFilter: (filterData, index, actor) => { - if (onSweep(index)) { - return 2; // eBLOCK - } else { - return 0; // eNONE - } - } - }; - - const pxSweepCallback = this._physXPhysics._physX.PxQueryFilterCallback.implement(sweepCallback); - const pxSweepHit = new this._physXPhysics._physX.PxSweepHit(); - const result = this._pxScene.sweepSingle( - geometry, - pose, - direction, - distance, - pxSweepHit, - this._pxFilterData, - pxSweepCallback - ); + const prevOnQuery = this._currentOnQuery; + this._currentOnQuery = onSweep; + let result: boolean; + try { + result = this._pxScene.sweepSingle( + geometry, + pose, + direction, + distance, + pxSweepHit, + this._pxRaycastSweepFilterData, + this._pxQueryCallback + ); + } finally { + this._currentOnQuery = prevOnQuery; + } if (result && outHitResult != undefined) { const { _tempPosition: position, _tempNormal: normal } = PhysXPhysicsScene; @@ -474,10 +494,6 @@ export class PhysXPhysicsScene implements IPhysicsScene { normal.set(pxNormal.x, pxNormal.y, pxNormal.z); outHitResult(pxSweepHit.getShape().getUUID(), pxSweepHit.distance, position, normal); } - - pxSweepCallback.delete(); - pxSweepHit.delete(); - return result; } @@ -486,19 +502,15 @@ export class PhysXPhysicsScene implements IPhysicsScene { pose: { translation: Vector3; rotation: Quaternion }, onOverlap: (obj: number) => boolean ): number[] { - const overlapCallback = { - preFilter: (filterData, index, actor) => (onOverlap(index) ? 2 : 0) - }; - - const pxOverlapCallback = this._physXPhysics._physX.PxQueryFilterCallback.implement(overlapCallback); + const prevOnQuery = this._currentOnQuery; + this._currentOnQuery = onOverlap; const maxHits = 256; - const hits: any = (this._pxScene as any).overlapMultiple( - geometry, - pose, - maxHits, - this._pxFilterData, - pxOverlapCallback - ); + let hits: any; + try { + hits = (this._pxScene as any).overlapMultiple(geometry, pose, maxHits, this._pxFilterData, this._pxQueryCallback); + } finally { + this._currentOnQuery = prevOnQuery; + } const result = PhysXPhysicsScene._tempShapeIDs; result.length = 0; @@ -509,7 +521,6 @@ export class PhysXPhysicsScene implements IPhysicsScene { } } - pxOverlapCallback.delete(); hits?.delete(); return result; } @@ -570,6 +581,16 @@ enum QueryFlag { NO_BLOCK = 1 << 5 } +/** + * Result returned from a PhysX query filter callback (mirrors `PxQueryHitType`). + */ +enum QueryHitType { + /** Filter the hit out (no further processing). */ + NONE = 0, + /** Treat the hit as a blocking hit (terminates query for single-hit modes). */ + BLOCK = 2 +} + enum PhysicsEventState { Enter = 0, Stay = 1, diff --git a/packages/physics-physx/src/shape/PhysXColliderShape.ts b/packages/physics-physx/src/shape/PhysXColliderShape.ts index ea5a3df8b4..61b9fdf6a3 100644 --- a/packages/physics-physx/src/shape/PhysXColliderShape.ts +++ b/packages/physics-physx/src/shape/PhysXColliderShape.ts @@ -56,6 +56,7 @@ export abstract class PhysXColliderShape implements IColliderShape { constructor(physXPhysics: PhysXPhysics) { this._physXPhysics = physXPhysics; + this._contractOffset = physXPhysics.getDefaultContactOffset(); } /** diff --git a/packages/rhi-webgl/package.json b/packages/rhi-webgl/package.json index 9d22c1e383..87e0b17c18 100644 --- a/packages/rhi-webgl/package.json +++ b/packages/rhi-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-rhi-webgl", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "repository": { "url": "https://github.com/galacean/engine.git" }, diff --git a/packages/rhi-webgl/src/GLBuffer.ts b/packages/rhi-webgl/src/GLBuffer.ts index 87f635dba9..aea7b38842 100644 --- a/packages/rhi-webgl/src/GLBuffer.ts +++ b/packages/rhi-webgl/src/GLBuffer.ts @@ -21,7 +21,18 @@ export class GLBuffer implements IPlatformBuffer { const gl = rhi.gl; const glBuffer = gl.createBuffer(); const glBufferUsage = this._getGLBufferUsage(gl, bufferUsage); - const glBindTarget = type === BufferBindFlag.VertexBuffer ? gl.ARRAY_BUFFER : gl.ELEMENT_ARRAY_BUFFER; + let glBindTarget: number; + switch (type) { + case BufferBindFlag.VertexBuffer: + glBindTarget = gl.ARRAY_BUFFER; + break; + case BufferBindFlag.IndexBuffer: + glBindTarget = gl.ELEMENT_ARRAY_BUFFER; + break; + case BufferBindFlag.ConstantBuffer: + glBindTarget = (gl).UNIFORM_BUFFER; + break; + } this._gl = gl; this._glBuffer = glBuffer; this._glBufferUsage = glBufferUsage; diff --git a/packages/rhi-webgl/src/GLPrimitive.ts b/packages/rhi-webgl/src/GLPrimitive.ts index fa7d89f1e8..5a7890d735 100644 --- a/packages/rhi-webgl/src/GLPrimitive.ts +++ b/packages/rhi-webgl/src/GLPrimitive.ts @@ -118,6 +118,7 @@ export class GLPrimitive implements IPlatformPrimitive { const element = attributes[name]; if (element) { + if (!vertexBufferBindings[element.bindingIndex]) continue; const { buffer, stride } = vertexBufferBindings[element.bindingIndex]; vbo = buffer._platformBuffer._glBuffer; // prevent binding the vbo which already bound at the last loop, e.g. a buffer with multiple attributes. diff --git a/packages/rhi-webgl/src/WebGLGraphicDevice.ts b/packages/rhi-webgl/src/WebGLGraphicDevice.ts index c937742f36..7394b0409a 100644 --- a/packages/rhi-webgl/src/WebGLGraphicDevice.ts +++ b/packages/rhi-webgl/src/WebGLGraphicDevice.ts @@ -89,6 +89,8 @@ export interface WebGLGraphicDeviceOptions { * WebGL graphic device, including WebGL1.0 and WebGL2.0. */ export class WebGLGraphicDevice implements IHardwareRenderer { + maxUniformBlockSize: number; + /** @internal */ _readFrameBuffer: WebGLFramebuffer = null; /** @internal */ @@ -280,6 +282,20 @@ export class WebGLGraphicDevice implements IHardwareRenderer { return new GLTransformFeedbackPrimitive(this._gl); } + bindUniformBufferBase(bindingPoint: number, buffer: IPlatformBuffer): void { + const gl = this._gl; + gl.bindBufferBase(gl.UNIFORM_BUFFER, bindingPoint, (buffer)._glBuffer); + } + + bindUniformBlock(program: WebGLProgram, blockName: string, bindingPoint: number): number { + const gl = this._gl; + const blockIndex = gl.getUniformBlockIndex(program, blockName); + if (blockIndex !== gl.INVALID_INDEX) { + gl.uniformBlockBinding(program, blockIndex, bindingPoint); + } + return blockIndex; + } + /** * Enable GL_RASTERIZER_DISCARD (WebGL2 only). */ @@ -623,6 +639,11 @@ export class WebGLGraphicDevice implements IHardwareRenderer { if (debugRenderInfo != null) { this._renderer = gl.getParameter(debugRenderInfo.UNMASKED_RENDERER_WEBGL); } + if (this._isWebGL2) { + this.maxUniformBlockSize = (gl).getParameter( + (gl).MAX_UNIFORM_BLOCK_SIZE + ); + } } destroy(): void { diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json index c6220b9a53..0d6baf8ea5 100644 --- a/packages/shader-lab/package.json +++ b/packages/shader-lab/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shaderlab", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader-lab/src/Preprocessor.ts b/packages/shader-lab/src/Preprocessor.ts index 17e907da73..8f3e1abb27 100644 --- a/packages/shader-lab/src/Preprocessor.ts +++ b/packages/shader-lab/src/Preprocessor.ts @@ -5,6 +5,7 @@ import { ShaderLib } from "@galacean/engine"; export enum MacroValueType { Number, // 1, 1.1 Symbol, // variable name + MemberAccess, // member access, e.g. input.v_uv, v.rgb FunctionCall, // function call, e.g. clamp(a, 0.0, 1.0) Other // shaderLab does not check this } @@ -27,6 +28,7 @@ export class Preprocessor { private static readonly _macroRegex = /^\s*#define\s+(\w+)[ ]*(\(([^)]*)\))?[ ]+(\(?\w+\)?.*?)(?:\/\/.*|\/\*.*?\*\/)?\s*$/gm; private static readonly _symbolReg = /^[a-zA-Z_][a-zA-Z0-9_]*$/; + private static readonly _memberAccessReg = /^([a-zA-Z_][a-zA-Z0-9_]*)(\.[a-zA-Z_][a-zA-Z0-9_]*)+$/; private static readonly _funcCallReg = /^([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$/; private static readonly _macroDefineIncludeMap = new Map(); @@ -61,6 +63,10 @@ export class Preprocessor { const referencedName = valueType === MacroValueType.FunctionCall ? info.functionCallName : info.value; if (info.params.indexOf(referencedName) !== -1) continue; if (out.indexOf(referencedName) === -1) out.push(referencedName); + } else if (valueType === MacroValueType.MemberAccess) { + // Extract root symbol: "input.v_uv" → "input" + const rootName = info.value.substring(0, info.value.indexOf(".")); + if (out.indexOf(rootName) === -1) out.push(rootName); } else if (valueType === MacroValueType.Other) { // #if _VERBOSE Logger.warn( @@ -110,6 +116,8 @@ export class Preprocessor { valueType = MacroValueType.Number; } else if (this._symbolReg.test(value)) { valueType = MacroValueType.Symbol; + } else if (this._memberAccessReg.test(value)) { + valueType = MacroValueType.MemberAccess; } else { const callMatch = this._funcCallReg.exec(value); if (callMatch) { diff --git a/packages/shader-lab/src/codeGen/CodeGenVisitor.ts b/packages/shader-lab/src/codeGen/CodeGenVisitor.ts index 35beaa1d98..f4d560c987 100644 --- a/packages/shader-lab/src/codeGen/CodeGenVisitor.ts +++ b/packages/shader-lab/src/codeGen/CodeGenVisitor.ts @@ -31,13 +31,19 @@ export abstract class CodeGenVisitor { protected static _tmpArrayPool = new ReturnableObjectPool(TempArray, 10); + protected static readonly _memberAccessReg = /\b(\w+)\.(\w+)\b/g; + defaultCodeGen(children: NodeChild[]) { const pool = CodeGenVisitor._tmpArrayPool; let ret = pool.get(); ret.dispose(); for (const child of children) { if (child instanceof BaseToken) { - ret.array.push(child.lexeme); + if (child.type === Keyword.MACRO_DEFINE_EXPRESSION) { + ret.array.push(this._transformMacroDefineValue(child.lexeme)); + } else { + ret.array.push(child.lexeme); + } } else { ret.array.push(child.codeGen(this)); } @@ -46,6 +52,32 @@ export abstract class CodeGenVisitor { return ret.array.join(" "); } + protected _transformMacroDefineValue( + lexeme: string, + overrideMap?: Record + ): string { + const context = VisitorContext.context; + const structVarMap = overrideMap ?? context._structVarMap; + if (!structVarMap) return lexeme; + + const spaceIdx = lexeme.indexOf(" "); + if (spaceIdx === -1) return lexeme; + + const macroName = lexeme.substring(0, spaceIdx); + let value = lexeme.substring(spaceIdx); + + const reg = CodeGenVisitor._memberAccessReg; + reg.lastIndex = 0; + value = value.replace(reg, (match, varName, propName) => { + const role = structVarMap[varName]; + if (!role) return match; + context.referenceStructPropByName(role, propName); + return propName; + }); + + return macroName + value; + } + visitPostfixExpression(node: ASTNode.PostfixExpression): string { const children = node.children; const derivationLength = children.length; @@ -212,7 +244,19 @@ export abstract class CodeGenVisitor { const children = node.children; const fullType = children[0]; if (fullType instanceof ASTNode.FullySpecifiedType && fullType.typeSpecifier.isCustom) { - VisitorContext.context.referenceGlobal(fullType.type, ESymbolType.STRUCT); + const context = VisitorContext.context; + const typeLexeme = fullType.typeSpecifier.lexeme; + const role = context.getStructRole(typeLexeme); + if (role) { + // Global variable of a varying/attribute/mrt struct type (e.g. "Varyings o;"). + // Don't output as uniform; register the variable in struct var maps instead. + const ident = children[1]; + if (ident instanceof BaseToken) { + context.registerStructVar(ident.lexeme, role); + } + return ""; + } + context.referenceGlobal(fullType.type, ESymbolType.STRUCT); } return `uniform ${this.defaultCodeGen(children)}`; } @@ -351,6 +395,8 @@ export abstract class CodeGenVisitor { const fnName = fnNode.protoType.ident.lexeme; const context = VisitorContext.context; + this._collectStructVars(fnNode, context); + if (fnName == context.stageEntry) { const statements = fnNode.statements.codeGen(this); return `void main() ${statements}`; @@ -359,6 +405,74 @@ export abstract class CodeGenVisitor { } } + private _collectStructVars(fnNode: ASTNode.FunctionDefinition, context: VisitorContext): void { + const map = context._structVarMap; + // Clear previous function's mappings + for (const key in map) delete map[key]; + + // Collect from function parameters + const paramList = fnNode.protoType.parameterList; + if (paramList) { + for (const param of paramList) { + if (param.ident && param.typeInfo && typeof param.typeInfo.type === "string") { + const role = context.getStructRole(param.typeInfo.typeLexeme); + if (role) map[param.ident.lexeme] = role; + } + } + } + + // Collect from local variable declarations in function body + this._collectStructVarsFromNode(fnNode.statements, context, map); + } + + private _collectStructVarsFromNode( + node: TreeNode, + context: VisitorContext, + map: Record + ): void { + const children = node.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child instanceof ASTNode.InitDeclaratorList) { + const typeLexeme = child.typeInfo?.typeLexeme; + if (typeLexeme) { + const role = context.getStructRole(typeLexeme); + if (role) { + // Extract variable name from SingleDeclaration or comma-separated identifiers + this._extractVarNamesFromInitDeclaratorList(child, map, role); + } + } + } else if (child instanceof TreeNode) { + this._collectStructVarsFromNode(child, context, map); + } + } + } + + protected _extractVarNamesFromInitDeclaratorList( + node: ASTNode.InitDeclaratorList, + map: Record, + role: "varying" | "attribute" | "mrt" + ): void { + const children = node.children; + if (children.length === 1) { + // SingleDeclaration: type ident + const singleDecl = children[0] as ASTNode.SingleDeclaration; + const identChildren = singleDecl.children; + if (identChildren.length >= 2 && identChildren[1] instanceof BaseToken) { + map[identChildren[1].lexeme] = role; + } + } else if (children.length >= 3) { + // InitDeclaratorList , ident ... + const initDeclList = children[0]; + if (initDeclList instanceof ASTNode.InitDeclaratorList) { + this._extractVarNamesFromInitDeclaratorList(initDeclList, map, role); + } + if (children[2] instanceof BaseToken) { + map[children[2].lexeme] = role; + } + } + } + protected _reportError(loc: ShaderRange | ShaderPosition, message: string): void { // #if _VERBOSE this.errors.push(new GSError(GSErrorName.CompilationError, message, loc, ShaderLab._processingPassText)); diff --git a/packages/shader-lab/src/codeGen/GLES100.ts b/packages/shader-lab/src/codeGen/GLES100.ts index 995dccdeec..2c2e0faa93 100644 --- a/packages/shader-lab/src/codeGen/GLES100.ts +++ b/packages/shader-lab/src/codeGen/GLES100.ts @@ -47,7 +47,7 @@ export class GLES100Visitor extends GLESVisitor { return ""; } const expression = node.children[1] as ASTNode.Expression; - return `gl_FragColor = ${expression.codeGen(this)}`; + return `gl_FragColor = ${expression.codeGen(this)};`; } return super.visitJumpStatement(node); } diff --git a/packages/shader-lab/src/codeGen/GLESVisitor.ts b/packages/shader-lab/src/codeGen/GLESVisitor.ts index fbf611729f..d7596ae9ab 100644 --- a/packages/shader-lab/src/codeGen/GLESVisitor.ts +++ b/packages/shader-lab/src/codeGen/GLESVisitor.ts @@ -5,6 +5,7 @@ import { Keyword } from "../common/enums/Keyword"; import { ASTNode, TreeNode } from "../parser/AST"; import { ShaderData } from "../parser/ShaderInfo"; import { ESymbolType, FnSymbol, StructSymbol, SymbolInfo } from "../parser/symbolTable"; +import { NodeChild } from "../parser/types"; import { CodeGenVisitor } from "./CodeGenVisitor"; import { ICodeSegment } from "./types"; import { VisitorContext } from "./VisitorContext"; @@ -37,10 +38,15 @@ export abstract class GLESVisitor extends CodeGenVisitor { this.reset(); const shaderData = node.shaderData; - VisitorContext.context._passSymbolTable = shaderData.symbolTable; + const context = VisitorContext.context; + context._passSymbolTable = shaderData.symbolTable; const outerGlobalMacroDeclarations = shaderData.getOuterGlobalMacroDeclarations(); + // Build combined _globalStructVarMap from both entry functions before per-stage processing. + // This must happen here because vertex runs first and doesn't yet know fragment's variables. + this._buildGlobalStructVarMap(vertexEntry, fragmentEntry, shaderData, outerGlobalMacroDeclarations, context); + return { vertex: this._vertexMain(vertexEntry, shaderData, outerGlobalMacroDeclarations), fragment: this._fragmentMain(fragmentEntry, shaderData, outerGlobalMacroDeclarations) @@ -109,6 +115,9 @@ export abstract class GLESVisitor extends CodeGenVisitor { } }); + // Pre-register global #define member access references for this stage + this._registerGlobalMacroReferences(outerGlobalMacroDeclarations, context); + const globalCodeArray = this._globalCodeArray; VisitorContext.context.referenceGlobal(entry, ESymbolType.FN); @@ -173,6 +182,9 @@ export abstract class GLESVisitor extends CodeGenVisitor { } }); + // Pre-register global #define member access references for this stage + this._registerGlobalMacroReferences(outerGlobalMacroStatements, context); + const globalCodeArray = this._globalCodeArray; VisitorContext.context.referenceGlobal(entry, ESymbolType.FN); @@ -193,10 +205,178 @@ export abstract class GLESVisitor extends CodeGenVisitor { return globalCode; } + /** + * Build _globalStructVarMap from both entry functions before per-stage processing. + * Classifies struct types by their position in function signatures: + * - vertex param[0] → attribute, vertex return type → varying + * - fragment param[0] → varying, fragment return type → mrt + */ + private _buildGlobalStructVarMap( + vertexEntry: string, + fragmentEntry: string, + data: ShaderData, + globalMacros: ASTNode.GlobalDeclaration[], + context: VisitorContext + ): void { + const map = context._globalStructVarMap; + const lookupSymbol = GLESVisitor._lookupSymbol; + const { symbolTable } = data; + + // Map struct type names to roles based on function signature positions + const structTypeRoles: Record = Object.create(null); + + // Vertex entry: param[0] type → attribute, return type → varying + lookupSymbol.set(vertexEntry, ESymbolType.FN); + const vertexFns = symbolTable.getSymbols(lookupSymbol, true, []); + for (const fn of vertexFns) { + const proto = fn.astNode.protoType; + const param0 = proto.parameterList?.[0]; + if (param0 && typeof param0.typeInfo.type === "string") { + structTypeRoles[param0.typeInfo.typeLexeme] = "attribute"; + } + if (typeof proto.returnType.type === "string") { + structTypeRoles[proto.returnType.type] = "varying"; + } + } + + // Fragment entry: param[0] type → varying, return type → mrt + lookupSymbol.set(fragmentEntry, ESymbolType.FN); + const fragmentFns = symbolTable.getSymbols(lookupSymbol, true, []); + for (const fn of fragmentFns) { + const proto = fn.astNode.protoType; + const param0 = proto.parameterList?.[0]; + if (param0 && typeof param0.typeInfo.type === "string") { + structTypeRoles[param0.typeInfo.typeLexeme] = "varying"; + } + if (typeof proto.returnType.type === "string") { + structTypeRoles[proto.returnType.type] = "mrt"; + } + } + + // Scan all entry functions' params and local vars, classify by structTypeRoles + for (const fn of [...vertexFns, ...fragmentFns]) { + const fnNode = fn.astNode; + const paramList = fnNode.protoType.parameterList; + if (paramList) { + for (const param of paramList) { + if (param.ident && param.typeInfo && typeof param.typeInfo.type === "string") { + const role = structTypeRoles[param.typeInfo.typeLexeme]; + if (role) map[param.ident.lexeme] = role; + } + } + } + this._collectStructVarsFromBody(fnNode.statements, structTypeRoles, map); + } + + // Also scan global macros for root variable names that might be global struct variables. + // e.g. #define VSOutput_worldPos o.v_worldPos → root "o" → look up in symbol table + let hasRoles = false; + for (const _ in structTypeRoles) { + hasRoles = true; + break; + } + if (hasRoles) { + const checked = new Set(); + const symOut: SymbolInfo[] = []; + this._forEachMacroMemberAccess(globalMacros, (rootName) => { + if (map[rootName] || checked.has(rootName)) return; + checked.add(rootName); + lookupSymbol.set(rootName, ESymbolType.VAR); + symbolTable.getSymbols(lookupSymbol, true, symOut); + for (const sym of symOut) { + if (sym.dataType) { + const role = structTypeRoles[sym.dataType.typeLexeme]; + if (role) { + map[rootName] = role; + break; + } + } + } + }); + } + } + + private _collectStructVarsFromBody( + node: TreeNode, + structTypeRoles: Record, + map: Record + ): void { + const children = node.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child instanceof ASTNode.InitDeclaratorList) { + const typeLexeme = child.typeInfo?.typeLexeme; + if (typeLexeme) { + const role = structTypeRoles[typeLexeme]; + if (role) { + this._extractVarNamesFromInitDeclaratorList(child, map, role); + } + } + } else if (child instanceof TreeNode) { + this._collectStructVarsFromBody(child, structTypeRoles, map); + } + } + } + + /** + * Pre-register attribute/varying/mrt references from global #define member access patterns, + * so that declarations are emitted by _getCustomStruct for the current stage. + */ + private _registerGlobalMacroReferences(globalMacros: ASTNode.GlobalDeclaration[], context: VisitorContext): void { + const map = context._globalStructVarMap; + let hasEntries = false; + for (const _ in map) { + hasEntries = true; + break; + } + if (!hasEntries) return; + this._forEachMacroMemberAccess(globalMacros, (rootName, propName) => { + const role = map[rootName]; + if (role) context.referenceStructPropByName(role, propName); + }); + } + + /** + * Traverse global macro declarations, extracting member access patterns (e.g. "o.v_uv") + * and invoking the callback with (rootName, propName) for each match. + */ + private _forEachMacroMemberAccess( + macros: ASTNode.GlobalDeclaration[], + callback: (rootName: string, propName: string) => void + ): void { + const reg = CodeGenVisitor._memberAccessReg; + for (const macro of macros) { + this._walkMacroChildren(macro.children, reg, callback); + } + } + + private _walkMacroChildren( + children: NodeChild[], + reg: RegExp, + callback: (rootName: string, propName: string) => void + ): void { + for (const child of children) { + if (child instanceof BaseToken && child.type === Keyword.MACRO_DEFINE_EXPRESSION) { + const spaceIdx = child.lexeme.indexOf(" "); + if (spaceIdx === -1) continue; + const value = child.lexeme.substring(spaceIdx); + reg.lastIndex = 0; + let match: RegExpExecArray | null; + while ((match = reg.exec(value)) !== null) { + callback(match[1], match[2]); + } + } else if (child instanceof TreeNode) { + this._walkMacroChildren(child.children, reg, callback); + } + } + } + private _getGlobalSymbol(out: ICodeSegment[]): void { - const { _referencedGlobals } = VisitorContext.context; + const context = VisitorContext.context; + const { _referencedGlobals } = context; - const lastLength = Object.keys(_referencedGlobals).length; + let lastLength = 0; + for (const _ in _referencedGlobals) lastLength++; if (lastLength === 0) return; for (const ident in _referencedGlobals) { @@ -206,7 +386,9 @@ export abstract class GLESVisitor extends CodeGenVisitor { const symbols = _referencedGlobals[ident]; for (let i = 0; i < symbols.length; i++) { const sm = symbols[i]; - const text = sm.astNode.codeGen(this) + (sm.type === ESymbolType.VAR ? ";" : ""); + const codeGenResult = sm.astNode.codeGen(this); + if (!codeGenResult) continue; + const text = codeGenResult + (sm.type === ESymbolType.VAR ? ";" : ""); if (!sm.isInMacroBranch) { out.push({ text, @@ -216,7 +398,9 @@ export abstract class GLESVisitor extends CodeGenVisitor { } } - if (Object.keys(_referencedGlobals).length !== lastLength) { + let newLength = 0; + for (const _ in _referencedGlobals) newLength++; + if (newLength !== lastLength) { this._getGlobalSymbol(out); } } @@ -233,6 +417,7 @@ export abstract class GLESVisitor extends CodeGenVisitor { private _getGlobalMacroDeclarations(macros: ASTNode.GlobalDeclaration[], out: ICodeSegment[]): void { const context = VisitorContext.context; + const globalMap = context._globalStructVarMap; const referencedGlobals = context._referencedGlobals; const referencedGlobalMacroASTs = context._referencedGlobalMacroASTs; referencedGlobalMacroASTs.length = 0; @@ -253,7 +438,12 @@ export abstract class GLESVisitor extends CodeGenVisitor { let result: ICodeSegment[] = []; result.push( ...macro.macroExpressions.map((item) => ({ - text: item instanceof BaseToken ? item.lexeme : item.codeGen(this), + text: + item instanceof BaseToken + ? item.type === Keyword.MACRO_DEFINE_EXPRESSION + ? this._transformMacroDefineValue(item.lexeme, globalMap) + : item.lexeme + : item.codeGen(this), index: item.location.start.index })) ); @@ -264,6 +454,8 @@ export abstract class GLESVisitor extends CodeGenVisitor { .sort((a, b) => a.index - b.index) .map((item) => item.text) .join("\n"); + } else if (child instanceof BaseToken && child.type === Keyword.MACRO_DEFINE_EXPRESSION) { + text = this._transformMacroDefineValue(child.lexeme, globalMap); } else { text = macro.codeGen(this); } diff --git a/packages/shader-lab/src/codeGen/VisitorContext.ts b/packages/shader-lab/src/codeGen/VisitorContext.ts index 25211f008a..1c1972c0db 100644 --- a/packages/shader-lab/src/codeGen/VisitorContext.ts +++ b/packages/shader-lab/src/codeGen/VisitorContext.ts @@ -38,6 +38,10 @@ export class VisitorContext { _referencedMRTList: Record; _referencedGlobals: Record; _referencedGlobalMacroASTs: TreeNode[] = []; + /** Maps variable names to their struct role for function-body #define value transformation. */ + _structVarMap: Record; + /** Combined mapping from all entry functions for global #define transformation. */ + _globalStructVarMap: Record; _passSymbolTable: SymbolTable; @@ -56,6 +60,36 @@ export class VisitorContext { this._referencedMRTList = Object.create(null); this._referencedGlobals = Object.create(null); this._referencedGlobalMacroASTs.length = 0; + this._structVarMap = Object.create(null); + if (resetAll) { + this._globalStructVarMap = Object.create(null); + } + } + + getStructRole(typeLexeme: string): "varying" | "attribute" | "mrt" | undefined { + if (this.isAttributeStruct(typeLexeme)) return "attribute"; + if (this.isVaryingStruct(typeLexeme)) return "varying"; + if (this.isMRTStruct(typeLexeme)) return "mrt"; + } + + registerStructVar(varName: string, role: "varying" | "attribute" | "mrt"): void { + this._structVarMap[varName] = role; + this._globalStructVarMap[varName] = role; + } + + referenceStructPropByName(role: "varying" | "attribute" | "mrt", propName: string): void { + const list = role === "varying" ? this.varyingList : role === "attribute" ? this.attributeList : this.mrtList; + const refList = + role === "varying" + ? this._referencedVaryingList + : role === "attribute" + ? this._referencedAttributeList + : this._referencedMRTList; + if (refList[propName]) return; + const props = list.filter((item) => item.ident.lexeme === propName); + if (props.length) { + refList[propName] = props; + } } isAttributeStruct(type: string) { diff --git a/packages/shader-lab/src/macroProcessor/MacroParser.ts b/packages/shader-lab/src/macroProcessor/MacroParser.ts index d845102e1a..a2ed90c504 100644 --- a/packages/shader-lab/src/macroProcessor/MacroParser.ts +++ b/packages/shader-lab/src/macroProcessor/MacroParser.ts @@ -355,7 +355,7 @@ export class MacroParser { scanner.advance(1); scanner.skipSpace(false); const parenExpr = this._parseParenthesisExpression(scanner); - if ((operator === "!" && typeof parenExpr !== "boolean") || (operator !== "!" && typeof parenExpr !== "number")) { + if (operator !== "!" && typeof parenExpr !== "number") { this._reportError(opPos, "invalid operator.", scanner.source, scanner.file); } diff --git a/packages/shader-lab/src/parser/AST.ts b/packages/shader-lab/src/parser/AST.ts index 4c90c01444..243944df8f 100644 --- a/packages/shader-lab/src/parser/AST.ts +++ b/packages/shader-lab/src/parser/AST.ts @@ -4,7 +4,7 @@ import { ETokenType, GalaceanDataType, ShaderRange, TokenType, TypeAny } from ". import { BaseToken } from "../common/BaseToken"; import { Keyword } from "../common/enums/Keyword"; import { ParserUtils } from "../ParserUtils"; -import { Preprocessor } from "../Preprocessor"; +import { MacroValueType, Preprocessor } from "../Preprocessor"; import { ShaderLabUtils } from "../ShaderLabUtils"; import { BuiltinFunction, BuiltinVariable, NonGenericGalaceanType } from "./builtin"; import { NoneTerminal } from "./GrammarSymbol"; @@ -1424,7 +1424,12 @@ export namespace ASTNode { sa.reportWarning(this.location, `Please sure the identifier "${name}" will be declared before used.`); // #endif } else { - this.typeInfo = symbols[0].dataType?.type; + // For member access macros (e.g. #define FRAG_UV v.v_uv), the referenceSymbolNames + // contains the root variable ("v") whose type is the struct ("Varyings"), not the + // member type ("vec2"). Skip type inference in this case — keep TypeAny. + if (child instanceof BaseToken || !this._isMemberAccessMacro(sa, child)) { + this.typeInfo = symbols[0].dataType?.type; + } const currentScopeSymbol = sa.symbolTableStack.scope.getSymbol(lookupSymbol, true); if (currentScopeSymbol) { if ( @@ -1443,6 +1448,12 @@ export namespace ASTNode { } } + private _isMemberAccessMacro(sa: SemanticAnalyzer, child: MacroCallSymbol | MacroCallFunction): boolean { + const macroName = child.macroName; + const infos = sa.macroDefineList[macroName]; + return infos?.some((info) => info.valueType === MacroValueType.MemberAccess) ?? false; + } + override codeGen(visitor: CodeGenVisitor): string { return this.setCache(visitor.visitVariableIdentifier(this)); } diff --git a/packages/shader-lab/src/parser/builtin/functions.ts b/packages/shader-lab/src/parser/builtin/functions.ts index 46c0d49d29..859951641c 100644 --- a/packages/shader-lab/src/parser/builtin/functions.ts +++ b/packages/shader-lab/src/parser/builtin/functions.ts @@ -26,6 +26,39 @@ function isGenericType(t: BuiltinType) { return t >= EGenType.GenType && t <= EGenType.GSampler2DArray; } +/** + * Resolve a generic return type from the actual type of a generic parameter. + * + * For GVec4 return type, maps sampler variants to the correct vec4 type: + * sampler2D/sampler3D/samplerCube → vec4 + * isampler2D/isampler3D/... → ivec4 + * usampler2D/usampler3D/... → uvec4 + * + * For all other generic return types (GenType etc.), passes through the actual param type directly. + */ +function resolveGenericReturnType( + genericReturnType: EGenType, + actualParamType: NonGenericGalaceanType +): NonGenericGalaceanType { + if (genericReturnType === EGenType.GVec4) { + switch (actualParamType) { + case Keyword.I_SAMPLER2D: + case Keyword.I_SAMPLER3D: + case Keyword.I_SAMPLER_CUBE: + case Keyword.I_SAMPLER2D_ARRAY: + return Keyword.IVEC4; + case Keyword.U_SAMPLER2D: + case Keyword.U_SAMPLER3D: + case Keyword.U_SAMPLER_CUBE: + case Keyword.U_SAMPLER2D_ARRAY: + return Keyword.UVEC4; + default: + return Keyword.VEC4; + } + } + return actualParamType; +} + const BuiltinFunctionTable: Map = new Map(); export class BuiltinFunction { @@ -78,12 +111,14 @@ export class BuiltinFunction { const argLength = fnArgs.length; if (argLength !== parameterTypes.length) continue; // Try to match generic parameter type. - let returnType = TypeAny; + let resolvedReturnType: NonGenericGalaceanType = TypeAny; let found = true; for (let i = 0; i < argLength; i++) { const curFnArg = fnArgs[i]; if (isGenericType(curFnArg)) { - if (returnType === TypeAny) returnType = parameterTypes[i]; + if (resolvedReturnType === TypeAny) { + resolvedReturnType = resolveGenericReturnType(fn._returnType as EGenType, parameterTypes[i]); + } } else { if (curFnArg !== parameterTypes[i] && parameterTypes[i] !== TypeAny) { found = false; @@ -92,7 +127,9 @@ export class BuiltinFunction { } } if (found) { - fn._realReturnType = returnType; + fn._realReturnType = isGenericType(fn._returnType) + ? resolvedReturnType + : (fn._returnType as NonGenericGalaceanType); return fn; } } @@ -300,13 +337,14 @@ BuiltinFunction._create("textureLod", EGenType.GVec4, EGenType.GSampler3D, Keywo BuiltinFunction._create("textureLod", EGenType.GVec4, EGenType.GSamplerCube, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create("textureLod", Keyword.FLOAT, Keyword.SAMPLER2D_SHADOW, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create("textureLod", EGenType.GVec4, EGenType.GSampler2DArray, Keyword.VEC3, Keyword.FLOAT); +BuiltinFunction._create("texture2DLod", Keyword.VEC4, Keyword.SAMPLER2D, Keyword.VEC2, Keyword.FLOAT); BuiltinFunction._create("texture2DLodEXT", EGenType.GVec4, EGenType.GSampler2D, Keyword.VEC2, Keyword.FLOAT); BuiltinFunction._create("texture2DLodEXT", EGenType.GVec4, EGenType.GSampler3D, Keyword.VEC3, Keyword.FLOAT); -BuiltinFunction._create("textureCube", Keyword.SAMPLER_CUBE, Keyword.VEC3); -BuiltinFunction._create("textureCube", Keyword.SAMPLER_CUBE, Keyword.VEC3, Keyword.FLOAT); +BuiltinFunction._create("textureCube", Keyword.VEC4, Keyword.SAMPLER_CUBE, Keyword.VEC3); +BuiltinFunction._create("textureCube", Keyword.VEC4, Keyword.SAMPLER_CUBE, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create("textureCube", EGenType.GVec4, EGenType.GSamplerCube, Keyword.VEC3, Keyword.FLOAT); -BuiltinFunction._create("textureCubeLod", Keyword.SAMPLER_CUBE, Keyword.VEC3, Keyword.FLOAT); +BuiltinFunction._create("textureCubeLod", Keyword.VEC4, Keyword.SAMPLER_CUBE, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create("textureCubeLodEXT", EGenType.GVec4, EGenType.GSamplerCube, Keyword.VEC3, Keyword.FLOAT); BuiltinFunction._create( diff --git a/packages/shader/package.json b/packages/shader/package.json index e7e7eff423..87f12b1510 100644 --- a/packages/shader/package.json +++ b/packages/shader/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-shader", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/shader/src/global.d.ts b/packages/shader/src/global.d.ts index d0abf1234f..8ce5f33757 100644 --- a/packages/shader/src/global.d.ts +++ b/packages/shader/src/global.d.ts @@ -3,7 +3,7 @@ declare module "*.glsl" { export default value; } -declare module "*.gs" { +declare module "*.shader" { const value: string; export default value; } diff --git a/packages/shader/src/shaders/PBR.gs b/packages/shader/src/shaders/PBR.shader similarity index 100% rename from packages/shader/src/shaders/PBR.gs rename to packages/shader/src/shaders/PBR.shader diff --git a/packages/shader/src/shaders/Transform.glsl b/packages/shader/src/shaders/Transform.glsl index b5d1a52a68..1af76a0f0e 100644 --- a/packages/shader/src/shaders/Transform.glsl +++ b/packages/shader/src/shaders/Transform.glsl @@ -1,16 +1,17 @@ #ifndef TRANSFORM_INCLUDED #define TRANSFORM_INCLUDED -mat4 renderer_LocalMat; -mat4 renderer_ModelMat; mat4 camera_ViewMat; mat4 camera_ProjMat; -mat4 renderer_MVMat; -mat4 renderer_MVPMat; -mat4 renderer_NormalMat; +mat4 camera_VPMat; vec3 camera_Position; -vec3 camera_Forward; +vec3 camera_Forward; vec4 camera_ProjectionParams; -#endif \ No newline at end of file +mat4 renderer_ModelMat; +mat4 renderer_MVMat; +mat4 renderer_MVPMat; +mat4 renderer_NormalMat; + +#endif diff --git a/packages/shader/src/shaders/index.ts b/packages/shader/src/shaders/index.ts index de23d2e392..3a0430c975 100644 --- a/packages/shader/src/shaders/index.ts +++ b/packages/shader/src/shaders/index.ts @@ -3,7 +3,7 @@ import Common from "./Common.glsl"; import Fog from "./Fog.glsl"; import Light from "./Light.glsl"; import Normal from "./Normal.glsl"; -import PBRSource from "./PBR.gs"; +import PBRSource from "./PBR.shader"; import Shadow from "./Shadow.glsl"; import ShadowSampleTent from "./ShadowSampleTent.glsl"; import Skin from "./Skin.glsl"; diff --git a/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl b/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl index 134f2405f2..c9e964d6ec 100644 --- a/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl +++ b/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl @@ -69,10 +69,11 @@ VertexInputs getVertexInputs(Attributes attributes){ // TBN world space #ifdef RENDERER_HAS_NORMAL - inputs.normalWS = normalize( mat3(renderer_NormalMat) * normal ); + mat3 normalMat = mat3(renderer_NormalMat); + inputs.normalWS = normalize( normalMat * normal ); #ifdef RENDERER_HAS_TANGENT - vec3 tangentWS = normalize( mat3(renderer_NormalMat) * tangent.xyz ); + vec3 tangentWS = normalize( normalMat * tangent.xyz ); vec3 bitangentWS = cross( inputs.normalWS, tangentWS ) * tangent.w; inputs.tangentWS = tangentWS; @@ -85,7 +86,7 @@ VertexInputs getVertexInputs(Attributes attributes){ vec4 positionWS = renderer_ModelMat * position; inputs.positionWS = positionWS.xyz / positionWS.w; - #if SCENE_FOG_MODE != 0 + #if SCENE_FOG_MODE != 0 vec4 positionVS = renderer_MVMat * position; inputs.positionVS = positionVS.xyz / positionVS.w; #endif diff --git a/packages/ui/package.json b/packages/ui/package.json index 4b51ee5bc2..a225b732c3 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-ui", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/ui/src/Utils.ts b/packages/ui/src/Utils.ts index f47e0a8053..60040d8e75 100644 --- a/packages/ui/src/Utils.ts +++ b/packages/ui/src/Utils.ts @@ -1,10 +1,67 @@ -import { Entity } from "@galacean/engine"; +import { Entity, Matrix, Plane, Ray, Vector2, Vector3 } from "@galacean/engine"; +import { UITransform } from "./component"; import { RootCanvasModifyFlags, UICanvas } from "./component/UICanvas"; import { GroupModifyFlags, UIGroup } from "./component/UIGroup"; +import { CanvasRenderMode } from "./enums/CanvasRenderMode"; import { IElement } from "./interface/IElement"; import { IGroupAble } from "./interface/IGroupAble"; export class Utils { + static _tempRay: Ray = new Ray(); + static _tempPlane: Plane = new Plane(); + static _tempVec3: Vector3 = new Vector3(); + static _tempMat: Matrix = new Matrix(); + + /** + * Local position of a screen point in the component + */ + static screenToLocalPoint(position: Vector2, transform: UITransform, out: Vector3): Boolean { + const engine = transform.engine; + // Get root canvas + let entity = transform.entity; + let rootCanvas: UICanvas; + while (entity) { + // @ts-ignore + const components = entity._components; + for (let i = 0, n = components.length; i < n; i++) { + const component = components[i]; + if (component.enabled && component instanceof UICanvas && component._isRootCanvas) { + rootCanvas = component; + } + } + entity = entity.parent; + } + if (!rootCanvas) return false; + // Calculate ray + const ray = this._tempRay; + switch (rootCanvas._realRenderMode) { + case CanvasRenderMode.ScreenSpaceOverlay: + // Screen to world ( Assume that world units have a one-to-one relationship with pixel units ) + ray.origin.set(position.x, engine.canvas.height - position.y, 1); + ray.direction.set(0, 0, -1); + break; + case CanvasRenderMode.ScreenSpaceCamera: + rootCanvas.renderCamera.screenPointToRay(position, ray); + break; + default: + // World space not yet supported, see issue #2793 + return false; + } + // Intersect ray with UI plane to get local coordinates + const plane = this._tempPlane; + const normal = plane.normal.copyFrom(transform.worldForward); + plane.distance = -Vector3.dot(normal, transform.worldPosition); + const curDistance = ray.intersectPlane(plane); + if (curDistance >= 0 && curDistance < Number.MAX_SAFE_INTEGER) { + const hitPointWorld = ray.getPoint(curDistance, this._tempVec3); + const worldMatrixInv = this._tempMat; + Matrix.invert(transform.worldMatrix, worldMatrixInv); + Vector3.transformCoordinate(hitPointWorld, worldMatrixInv, out); + return true; + } + return false; + } + static setRootCanvasDirty(element: IElement): void { if (element._isRootCanvasDirty) return; element._isRootCanvasDirty = true; diff --git a/packages/ui/src/component/UICanvas.ts b/packages/ui/src/component/UICanvas.ts index 9e5b33c405..77dad56014 100644 --- a/packages/ui/src/component/UICanvas.ts +++ b/packages/ui/src/component/UICanvas.ts @@ -25,6 +25,7 @@ import { ResolutionAdaptationMode } from "../enums/ResolutionAdaptationMode"; import { UIHitResult } from "../input/UIHitResult"; import { IElement } from "../interface/IElement"; import { IGroupAble } from "../interface/IGroupAble"; +import { RectMask2D } from "./advanced/RectMask2D"; import { UIGroup } from "./UIGroup"; import { UIRenderer } from "./UIRenderer"; import { UITransform } from "./UITransform"; @@ -39,6 +40,7 @@ export class UICanvas extends Component implements IElement { /** @internal */ static _hierarchyCounter: number = 1; private static _tempGroupAbleList: IGroupAble[] = []; + private static _tempRectMaskList: RectMask2D[] = []; private static _tempVec3: Vector3 = new Vector3(); private static _tempMat: Matrix = new Matrix(); @@ -62,7 +64,7 @@ export class UICanvas extends Component implements IElement { _isRootCanvas: boolean = false; /** @internal */ @ignoreClone - _renderElement: any; + _renderElements: any[] = []; /** @internal */ @ignoreClone _sortDistance: number = 0; @@ -307,11 +309,10 @@ export class UICanvas extends Component implements IElement { const { engine, _realRenderMode: mode } = this; const { enableFrustumCulling, cullingMask, _frustum: frustum } = context.camera; const { frameCount } = engine.time; - // @ts-ignore - const renderElement = (this._renderElement = engine._renderElementPool.get()); + const renderElements = this._renderElements; + renderElements.length = 0; const virtualCamera = context.virtualCamera; this._updateSortDistance(virtualCamera.isOrthographic, virtualCamera.position, virtualCamera.forward); - renderElement.set(this.sortOrder, this._sortDistance); const { width, height } = engine.canvas; const renderers = this._getRenderers(); for (let i = 0, n = renderers.length; i < n; i++) { @@ -418,7 +419,8 @@ export class UICanvas extends Component implements IElement { const { _orderedRenderers: renderers, entity } = this; const uiHierarchyVersion = entity._uiHierarchyVersion; if (this._hierarchyVersion !== uiHierarchyVersion) { - renderers.length = this._walk(this.entity, renderers); + UICanvas._tempRectMaskList.length = 0; + renderers.length = this._walk(this.entity, renderers, 0, null, 0); UICanvas._tempGroupAbleList.length = 0; this._hierarchyVersion = uiHierarchyVersion; ++UICanvas._hierarchyCounter; @@ -500,10 +502,18 @@ export class UICanvas extends Component implements IElement { transform.size.set(curWidth / expectX, curHeight / expectY); } - private _walk(entity: Entity, renderers: UIRenderer[], depth = 0, group: UIGroup = null): number { + private _walk( + entity: Entity, + renderers: UIRenderer[], + depth = 0, + group: UIGroup = null, + rectMaskCount: number = 0 + ): number { // @ts-ignore const components: Component[] = entity._components; const tempGroupAbleList = UICanvas._tempGroupAbleList; + const tempRectMaskList = UICanvas._tempRectMaskList; + let rectMask: RectMask2D = null; let groupAbleCount = 0; for (let i = 0, n = components.length; i < n; i++) { const component = components[i]; @@ -515,11 +525,14 @@ export class UICanvas extends Component implements IElement { if (component._isGroupDirty) { tempGroupAbleList[groupAbleCount++] = component; } + component._setRectMasks(tempRectMaskList, rectMaskCount); } else if (component instanceof UIInteractive) { component._isRootCanvasDirty && Utils.setRootCanvas(component, this); if (component._isGroupDirty) { tempGroupAbleList[groupAbleCount++] = component; } + } else if (component instanceof RectMask2D) { + rectMask = component; } else if (component instanceof UIGroup) { component._isRootCanvasDirty && Utils.setRootCanvas(component, this); component._isGroupDirty && Utils.setGroup(component, group); @@ -529,10 +542,13 @@ export class UICanvas extends Component implements IElement { for (let i = 0; i < groupAbleCount; i++) { Utils.setGroup(tempGroupAbleList[i], group); } + if (rectMask) { + tempRectMaskList[rectMaskCount++] = rectMask; + } const children = entity.children; for (let i = 0, n = children.length; i < n; i++) { const child = children[i]; - child.isActive && (depth = this._walk(child, renderers, depth, group)); + child.isActive && (depth = this._walk(child, renderers, depth, group, rectMaskCount)); } return depth; } diff --git a/packages/ui/src/component/UIRenderer.ts b/packages/ui/src/component/UIRenderer.ts index 59a2fc434c..527ad3bd23 100644 --- a/packages/ui/src/component/UIRenderer.ts +++ b/packages/ui/src/component/UIRenderer.ts @@ -1,5 +1,5 @@ import { - BatchUtils, + VertexMergeBatcher, Color, DependentMode, Entity, @@ -11,22 +11,28 @@ import { RendererUpdateFlags, ShaderMacroCollection, ShaderProperty, + SpriteMaskInteraction, + SpriteMaskLayer, Vector3, Vector4, assignmentClone, deepClone, dependentComponents, - ignoreClone + ignoreClone, + Vector2 } from "@galacean/engine"; import { Utils } from "../Utils"; import { UIHitResult } from "../input/UIHitResult"; import { IGraphics } from "../interface/IGraphics"; +import { RectMask2D } from "./advanced/RectMask2D"; import { EntityUIModifyFlags, UICanvas } from "./UICanvas"; import { GroupModifyFlags, UIGroup } from "./UIGroup"; import { UITransform } from "./UITransform"; @dependentComponents(UITransform, DependentMode.AutoAdd) export class UIRenderer extends Renderer implements IGraphics { + /** @internal */ + static _tempVec20: Vector2 = new Vector2(); /** @internal */ static _tempVec30: Vector3 = new Vector3(); /** @internal */ @@ -37,6 +43,16 @@ export class UIRenderer extends Renderer implements IGraphics { static _tempPlane: Plane = new Plane(); /** @internal */ static _textureProperty: ShaderProperty = ShaderProperty.getByName("renderer_UITexture"); + /** @internal */ + static _rectClipRectProperty: ShaderProperty = ShaderProperty.getByName("renderer_UIRectClipRect"); + /** @internal */ + static _rectClipEnabledProperty: ShaderProperty = ShaderProperty.getByName("renderer_UIRectClipEnabled"); + /** @internal */ + static _rectClipSoftnessProperty: ShaderProperty = ShaderProperty.getByName("renderer_UIRectClipSoftness"); + /** @internal */ + static _rectClipHardClipProperty: ShaderProperty = ShaderProperty.getByName("renderer_UIRectClipHardClip"); + /** @internal */ + static _tempRect: Vector4 = new Vector4(); /** * Custom boundary for raycast detection. @@ -69,9 +85,24 @@ export class UIRenderer extends Renderer implements IGraphics { /** @internal */ @ignoreClone _subChunk; + /** @internal */ + @ignoreClone + _rectMasks: RectMask2D[] = []; + /** @internal */ + @ignoreClone + _rectMaskRect: Vector4 = new Vector4(); + /** @internal */ + @ignoreClone + _rectMaskEnabled: boolean = false; + /** @internal */ + @ignoreClone + _rectMaskSoftness: Vector4 = new Vector4(); + /** @internal */ + @ignoreClone + _rectMaskHardClip: boolean = false; @assignmentClone - private _raycastEnabled: boolean = true; + private _raycastEnabled: boolean = false; @deepClone protected _color: Color = new Color(1, 1, 1, 1); @@ -88,6 +119,30 @@ export class UIRenderer extends Renderer implements IGraphics { } } + /** + * The mask layer the ui renderer belongs to. + */ + get maskLayer(): SpriteMaskLayer { + return this._maskLayer; + } + + set maskLayer(value: SpriteMaskLayer) { + this._maskLayer = value; + } + + /** + * Interacts with the masks. + */ + get maskInteraction(): SpriteMaskInteraction { + return this._maskInteraction; + } + + set maskInteraction(value: SpriteMaskInteraction) { + if (this._maskInteraction !== value) { + this._maskInteraction = value; + } + } + /** * Whether this renderer be picked up by raycast. */ @@ -110,22 +165,25 @@ export class UIRenderer extends Renderer implements IGraphics { this._color._onValueChanged = this._onColorChanged; this._groupListener = this._groupListener.bind(this); this._rootCanvasListener = this._rootCanvasListener.bind(this); + this.shaderData.setFloat(UIRenderer._rectClipEnabledProperty, 0); + this.shaderData.setVector4(UIRenderer._rectClipSoftnessProperty, this._rectMaskSoftness); + this.shaderData.setFloat(UIRenderer._rectClipHardClipProperty, 0); } // @ts-ignore - override _canBatch(elementA, elementB): boolean { - return BatchUtils.canBatchSprite(elementA, elementB); + override _canBatch(preElement, curElement): boolean { + return VertexMergeBatcher.canBatchSprite(preElement, curElement); } // @ts-ignore - override _batch(elementA, elementB?): void { - BatchUtils.batchFor2D(elementA, elementB); + override _batch(preElement, curElement): void { + VertexMergeBatcher.batch(preElement, curElement); } // @ts-ignore - override _updateTransformShaderData(context, onlyMVP: boolean, batched: boolean): void { + override _updateTransformShaderData(context, onlyMVP: boolean): void { // @ts-ignore - super._updateTransformShaderData(context, onlyMVP, true); + this._updateWorldSpaceTransformShaderData(context, onlyMVP); } // @ts-ignore @@ -135,6 +193,7 @@ export class UIRenderer extends Renderer implements IGraphics { this._update(context); } + this._updateRectMaskClipState(); this._render(context); // union camera global macro and renderer macro. @@ -237,6 +296,17 @@ export class UIRenderer extends Renderer implements IGraphics { return this.engine._batcherManager.primitiveChunkManagerUI; } + /** + * @internal + */ + _setRectMasks(rectMasks: RectMask2D[], count: number): void { + const targetMasks = this._rectMasks; + targetMasks.length = count; + for (let i = 0; i < count; i++) { + targetMasks[i] = rectMasks[i]; + } + } + /** * @internal */ @@ -252,7 +322,11 @@ export class UIRenderer extends Renderer implements IGraphics { Matrix.invert(transform.worldMatrix, worldMatrixInv); const localPosition = UIRenderer._tempVec31; Vector3.transformCoordinate(hitPointWorld, worldMatrixInv, localPosition); - if (this._hitTest(localPosition)) { + if ( + this._hitTest(localPosition) && + this._isRaycastVisibleByRectMask(hitPointWorld) && + this._isRaycastVisibleByMask(hitPointWorld) + ) { out.component = this; out.distance = curDistance; out.entity = this.entity; @@ -278,6 +352,143 @@ export class UIRenderer extends Renderer implements IGraphics { ); } + private _isRaycastVisibleByMask(hitPointWorld: Vector3): boolean { + const maskInteraction = this._maskInteraction; + if (maskInteraction === SpriteMaskInteraction.None) { + return true; + } + // @ts-ignore + return this.scene._maskManager.isVisibleByMask(maskInteraction, this._maskLayer, hitPointWorld); + } + + private _isRaycastVisibleByRectMask(hitPointWorld: Vector3): boolean { + const rectMasks = this._rectMasks; + for (let i = 0, n = rectMasks.length; i < n; i++) { + const rectMask = rectMasks[i]; + if (!rectMask.enabled || !rectMask.entity.isActiveInHierarchy) { + continue; + } + if (!rectMask._containsWorldPoint(hitPointWorld)) { + return false; + } + } + return true; + } + + private _updateRectMaskClipState(): void { + const rectMasks = this._rectMasks; + const count = rectMasks.length; + if (count <= 0) { + this._resetRectMaskClipState(); + return; + } + + let minX = Number.NEGATIVE_INFINITY; + let minY = Number.NEGATIVE_INFINITY; + let maxX = Number.POSITIVE_INFINITY; + let maxY = Number.POSITIVE_INFINITY; + let clipSoftnessLeft = 0; + let clipSoftnessBottom = 0; + let clipSoftnessRight = 0; + let clipSoftnessTop = 0; + let clipHardClip = false; + let hasActiveMask = false; + const tempRect = UIRenderer._tempRect; + for (let i = 0; i < count; i++) { + const rectMask = rectMasks[i]; + if (!rectMask.enabled || !rectMask.entity.isActiveInHierarchy) { + continue; + } + hasActiveMask = true; + const softness = rectMask.softness; + if (!clipHardClip && rectMask.alphaClip) { + clipHardClip = true; + } + if (!rectMask._getWorldRect(tempRect)) { + minX = 1; + minY = 1; + maxX = 0; + maxY = 0; + break; + } + if (tempRect.x > minX) { + minX = tempRect.x; + clipSoftnessLeft = softness.x; + } + if (tempRect.y > minY) { + minY = tempRect.y; + clipSoftnessBottom = softness.y; + } + if (tempRect.z < maxX) { + maxX = tempRect.z; + clipSoftnessRight = softness.x; + } + if (tempRect.w < maxY) { + maxY = tempRect.w; + clipSoftnessTop = softness.y; + } + } + + if (!hasActiveMask) { + this._resetRectMaskClipState(); + return; + } + + if (minX >= maxX || minY >= maxY) { + minX = 1; + minY = 1; + maxX = 0; + maxY = 0; + clipSoftnessLeft = 0; + clipSoftnessBottom = 0; + clipSoftnessRight = 0; + clipSoftnessTop = 0; + } + + const rectMaskRect = this._rectMaskRect; + if (rectMaskRect.x !== minX || rectMaskRect.y !== minY || rectMaskRect.z !== maxX || rectMaskRect.w !== maxY) { + rectMaskRect.set(minX, minY, maxX, maxY); + this.shaderData.setVector4(UIRenderer._rectClipRectProperty, rectMaskRect); + } + + const rectMaskSoftness = this._rectMaskSoftness; + if ( + rectMaskSoftness.x !== clipSoftnessLeft || + rectMaskSoftness.y !== clipSoftnessBottom || + rectMaskSoftness.z !== clipSoftnessRight || + rectMaskSoftness.w !== clipSoftnessTop + ) { + rectMaskSoftness.set(clipSoftnessLeft, clipSoftnessBottom, clipSoftnessRight, clipSoftnessTop); + this.shaderData.setVector4(UIRenderer._rectClipSoftnessProperty, rectMaskSoftness); + } + + if (this._rectMaskHardClip !== clipHardClip) { + this._rectMaskHardClip = clipHardClip; + this.shaderData.setFloat(UIRenderer._rectClipHardClipProperty, clipHardClip ? 1 : 0); + } + + if (!this._rectMaskEnabled) { + this._rectMaskEnabled = true; + this.shaderData.setFloat(UIRenderer._rectClipEnabledProperty, 1); + } + } + + private _resetRectMaskClipState(): void { + if (this._rectMaskEnabled) { + this._rectMaskEnabled = false; + this.shaderData.setFloat(UIRenderer._rectClipEnabledProperty, 0); + } + const rectMaskSoftness = this._rectMaskSoftness; + if (rectMaskSoftness.x !== 0 || rectMaskSoftness.y !== 0 || rectMaskSoftness.z !== 0 || rectMaskSoftness.w !== 0) { + rectMaskSoftness.set(0, 0, 0, 0); + this.shaderData.setVector4(UIRenderer._rectClipSoftnessProperty, rectMaskSoftness); + } + if (this._rectMaskHardClip) { + this._rectMaskHardClip = false; + this.shaderData.setFloat(UIRenderer._rectClipHardClipProperty, 0); + } + } + protected override _onDestroy(): void { if (this._subChunk) { this._getChunkManager().freeSubChunk(this._subChunk); @@ -287,6 +498,8 @@ export class UIRenderer extends Renderer implements IGraphics { //@ts-ignore this._color._onValueChanged = null; this._color = null; + this._rectMasks = null; + this._rectMaskSoftness = null; } } diff --git a/packages/ui/src/component/UITransform.ts b/packages/ui/src/component/UITransform.ts index 9144373472..28e0087575 100644 --- a/packages/ui/src/component/UITransform.ts +++ b/packages/ui/src/component/UITransform.ts @@ -251,7 +251,49 @@ export class UITransform extends Transform { */ _parentChange(): void { this._isParentDirty = true; - this._updateWorldFlagWithParentRectChange(TransformModifyFlags.WmWpWeWqWsWus); + // Reparent invalidates the world state of the entire subtree: + // 1) `_updateWorldFlagWithParentRectChange` has an early-exit that skips + // propagation when self's world dirty flags are already all set — + // invalid after reparent. + // 2) Descendants may have cached a stale `_parentTransformCache` + // (e.g. `_getParentTransform` called while ancestor chain was partially + // constructed during clone/instantiate). Force them to re-resolve on + // next access. + this._propagateReparentDirtyUI(TransformModifyFlags.WmWpWeWqWsWus); + } + + private _propagateReparentDirtyUI(flags: number): void { + let selfChange = false; + const { _horizontalAlignment: horizontalAlignment, _verticalAlignment: verticalAlignment } = this; + if (horizontalAlignment || verticalAlignment) { + if ( + horizontalAlignment === HorizontalAlignmentMode.LeftAndRight || + verticalAlignment === VerticalAlignmentMode.TopAndBottom + ) { + this._updateSizeByAlignment(); + this._updateRectBySizeAndPivot(); + selfChange = true; + } + this._updatePositionByAlignment(); + this._setDirtyFlagTrue(TransformModifyFlags.LocalMatrix); + flags |= TransformModifyFlags.WmWp; + } + this._worldAssociatedChange(flags); + const children = this.entity.children; + for (let i = 0, n = children.length; i < n; i++) { + const transform = children[i].transform as any; + if (!transform) continue; + transform._isParentDirty = true; + if (typeof transform._propagateReparentDirtyUI === "function") { + transform._propagateReparentDirtyUI(flags); + } else if (typeof transform._propagateReparentDirty === "function") { + transform._propagateReparentDirty(flags); + } + } + if (selfChange) { + // @ts-ignore + this._entity._updateFlagManager.dispatch(UITransformModifyFlags.Size); + } } // @ts-ignore diff --git a/packages/ui/src/component/advanced/Image.ts b/packages/ui/src/component/advanced/Image.ts index b8ca6ffe55..cfb2cf41ff 100644 --- a/packages/ui/src/component/advanced/Image.ts +++ b/packages/ui/src/component/advanced/Image.ts @@ -1,15 +1,17 @@ import { BoundingBox, Entity, + FilledSpriteAssembler, ISpriteAssembler, ISpriteRenderer, MathUtil, - RenderQueueFlags, RendererUpdateFlags, SimpleSpriteAssembler, SlicedSpriteAssembler, Sprite, SpriteDrawMode, + SpriteFilledMode, + SpriteFilledOrigin, SpriteModifyFlags, SpriteTileMode, TiledSpriteAssembler, @@ -35,6 +37,14 @@ export class Image extends UIRenderer 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; /** * The draw mode of the image. @@ -56,6 +66,9 @@ export class Image extends UIRenderer implements ISpriteRenderer { case SpriteDrawMode.Tiled: this._assembler = TiledSpriteAssembler; break; + case SpriteDrawMode.Filled: + this._assembler = FilledSpriteAssembler; + break; default: break; } @@ -97,6 +110,73 @@ export class Image extends UIRenderer implements ISpriteRenderer { } } + /** + * The fill amount of the image, range from 0 to 1. (Only works in filled mode.) + */ + get filledAmount(): number { + return this._filledAmount; + } + + set filledAmount(value: number) { + value = MathUtil.clamp(value, 0, 1); + if (this._filledAmount !== value) { + this._filledAmount = value; + if (this._drawMode === SpriteDrawMode.Filled) { + this._dirtyUpdateFlag |= ImageUpdateFlags.WorldVolumeAndUV; + } + } + } + + /** + * The fill mode of the image. (Only works in filled mode.) + */ + get filledMode(): SpriteFilledMode { + return this._filledMode; + } + + set filledMode(value: SpriteFilledMode) { + if (this._filledMode !== value) { + this._filledMode = value; + this._filledOrigin = + value === SpriteFilledMode.Radial90 ? SpriteFilledOrigin.BottomLeft : SpriteFilledOrigin.Bottom; + if (this._drawMode === SpriteDrawMode.Filled) { + this._dirtyUpdateFlag |= ImageUpdateFlags.WorldVolumeAndUV; + } + } + } + + /** + * The fill origin of the image. (Only works in filled mode.) + */ + get filledOrigin(): SpriteFilledOrigin { + return this._filledOrigin; + } + + set filledOrigin(value: SpriteFilledOrigin) { + if (this._filledOrigin !== value) { + this._filledOrigin = value; + if (this._drawMode === SpriteDrawMode.Filled) { + this._dirtyUpdateFlag |= ImageUpdateFlags.WorldVolumeAndUV; + } + } + } + + /** + * Whether the fill is clockwise. (Only works in filled radial mode.) + */ + get filledClockWise(): boolean { + return this._filledClockWise; + } + + set filledClockWise(value: boolean) { + if (this._filledClockWise !== value) { + this._filledClockWise = value; + if (this._drawMode === SpriteDrawMode.Filled) { + this._dirtyUpdateFlag |= ImageUpdateFlags.WorldVolumeAndUV; + } + } + } + /** * The Sprite to render. */ @@ -236,16 +316,16 @@ export class Image extends UIRenderer implements ISpriteRenderer { } this._dirtyUpdateFlag = dirtyUpdateFlag; - // Init sub render element. const { engine } = context.camera; - const subRenderElement = engine._subRenderElementPool.get(); + const renderElement = engine._renderElementPool.get(); const subChunk = this._subChunk; - subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); if (canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay) { - subRenderElement.shaderPasses = material.shader.subShaders[0].passes; - subRenderElement.renderQueueFlags = RenderQueueFlags.All; + renderElement.subShader = material.shader.subShaders[0]; } - canvas._renderElement.addSubRenderElement(subRenderElement); + renderElement.priority = canvas.sortOrder; + renderElement.distanceForSort = canvas._sortDistance; + canvas._renderElements.push(renderElement); } @ignoreClone @@ -281,6 +361,9 @@ export class Image extends UIRenderer implements ISpriteRenderer { case SpriteDrawMode.Tiled: this._dirtyUpdateFlag |= ImageUpdateFlags.WorldVolumeUVAndColor; break; + case SpriteDrawMode.Filled: + this._dirtyUpdateFlag |= ImageUpdateFlags.WorldVolumeUVAndColor; + break; default: break; } diff --git a/packages/ui/src/component/advanced/Mask.ts b/packages/ui/src/component/advanced/Mask.ts new file mode 100644 index 0000000000..cff2fd85ab --- /dev/null +++ b/packages/ui/src/component/advanced/Mask.ts @@ -0,0 +1,80 @@ +import { BoundingBox, Entity, MaskRenderable, Vector2 } from "@galacean/engine"; +import type { IMaskRenderable } from "@galacean/engine"; +import { UIRenderer } from "../UIRenderer"; +import { UITransform } from "../UITransform"; + +/** + * UI component that uses a sprite to mask child UI renderers via stencil. + */ +export class Mask extends MaskRenderable(UIRenderer) { + /** + * @internal + */ + override _getChunkManager() { + // @ts-ignore + return this.engine._batcherManager.primitiveChunkManagerMask; + } + + /** + * @internal + */ + constructor(entity: Entity) { + super(entity); + this._initMask(); + this.raycastEnabled = false; + } + + /** + * @internal + */ + // @ts-ignore + _cloneTo(target: Mask): void { + // @ts-ignore + super._cloneTo(target); + this._cloneMaskData(target); + } + + protected override _updateBounds(worldBounds: BoundingBox): void { + const rootCanvas = this._getRootCanvas(); + if (this.sprite && rootCanvas) { + this._updateMaskBounds(worldBounds); + } else { + const { worldPosition } = this._transformEntity.transform; + worldBounds.min.copyFrom(worldPosition); + worldBounds.max.copyFrom(worldPosition); + } + } + + /** + * @inheritdoc + */ + protected override _render(context): void { + this._renderMask(0); + } + + /** + * @inheritdoc + */ + protected override _onDestroy(): void { + this._destroyMaskResources(); + + super._onDestroy(); + + if (this._subChunk) { + this._getChunkManager().freeSubChunk(this._subChunk); + this._subChunk = null; + } + } + + override _getSpriteWidth(): number { + return (this._transformEntity.transform).size.x; + } + + override _getSpriteHeight(): number { + return (this._transformEntity.transform).size.y; + } + + override _getSpritePivot(): Vector2 { + return (this._transformEntity.transform).pivot; + } +} diff --git a/packages/ui/src/component/advanced/RectMask2D.ts b/packages/ui/src/component/advanced/RectMask2D.ts new file mode 100644 index 0000000000..9cba135e84 --- /dev/null +++ b/packages/ui/src/component/advanced/RectMask2D.ts @@ -0,0 +1,157 @@ +import { + Component, + DependentMode, + Entity, + Vector2, + Vector3, + Vector4, + assignmentClone, + deepClone, + dependentComponents +} from "@galacean/engine"; +import { UICanvas } from "../UICanvas"; +import { UITransform } from "../UITransform"; + +/** + * UI component that clips descendant graphics by an axis-aligned rectangle. + */ +@dependentComponents(UITransform, DependentMode.AutoAdd) +export class RectMask2D extends Component { + private static _tempRect: Vector4 = new Vector4(); + private static _tempCorner0: Vector3 = new Vector3(); + private static _tempCorner1: Vector3 = new Vector3(); + private static _tempCorner2: Vector3 = new Vector3(); + private static _tempCorner3: Vector3 = new Vector3(); + + @deepClone + private _softness: Vector2 = new Vector2(0, 0); + @assignmentClone + private _alphaClip: boolean = false; + + /** + * Soft clipping width on X/Y axis in world space. + */ + get softness(): Vector2 { + return this._softness; + } + + set softness(value: Vector2) { + const softness = this._softness; + if (softness === value) { + return; + } + if (softness.x !== value.x || softness.y !== value.y) { + softness.copyFrom(value); + this._clampSoftness(); + } + } + + /** + * Whether to enable hard clip (discard) when outside the rect. + */ + get alphaClip(): boolean { + return this._alphaClip; + } + + set alphaClip(value: boolean) { + this._alphaClip = value; + } + + /** + * @internal + */ + _getWorldRect(out: Vector4): boolean { + const transform = this.entity.transform; + const { x: width, y: height } = transform.size; + if (!width || !height) { + return false; + } + + const { x: pivotX, y: pivotY } = transform.pivot; + const left = -width * pivotX; + const right = width * (1 - pivotX); + const bottom = -height * pivotY; + const top = height * (1 - pivotY); + + const worldMatrix = transform.worldMatrix; + const corner0 = RectMask2D._tempCorner0; + const corner1 = RectMask2D._tempCorner1; + const corner2 = RectMask2D._tempCorner2; + const corner3 = RectMask2D._tempCorner3; + Vector3.transformCoordinate(corner0.set(left, bottom, 0), worldMatrix, corner0); + Vector3.transformCoordinate(corner1.set(left, top, 0), worldMatrix, corner1); + Vector3.transformCoordinate(corner2.set(right, bottom, 0), worldMatrix, corner2); + Vector3.transformCoordinate(corner3.set(right, top, 0), worldMatrix, corner3); + + const minX = Math.min(corner0.x, corner1.x, corner2.x, corner3.x); + const minY = Math.min(corner0.y, corner1.y, corner2.y, corner3.y); + const maxX = Math.max(corner0.x, corner1.x, corner2.x, corner3.x); + const maxY = Math.max(corner0.y, corner1.y, corner2.y, corner3.y); + out.set(minX, minY, maxX, maxY); + return true; + } + + /** + * @internal + */ + _containsWorldPoint(worldPoint: Vector3): boolean { + const worldRect = RectMask2D._tempRect; + if (!this._getWorldRect(worldRect)) { + return false; + } + const { x, y } = worldPoint; + return x >= worldRect.x && x <= worldRect.z && y >= worldRect.y && y <= worldRect.w; + } + + constructor(entity: Entity) { + super(entity); + this._onSoftnessChanged = this._onSoftnessChanged.bind(this); + // @ts-ignore + this._softness._onValueChanged = this._onSoftnessChanged; + } + + // @ts-ignore + override _onEnableInScene(): void { + this.entity._updateUIHierarchyVersion(UICanvas._hierarchyCounter); + } + + // @ts-ignore + override _onDisableInScene(): void { + this.entity._updateUIHierarchyVersion(UICanvas._hierarchyCounter); + } + + // @ts-ignore + override _cloneTo(target: RectMask2D): void { + // RectMask2D extends Component directly; Component.prototype 上没有 _cloneTo, + // 不能 super._cloneTo(target) — 会拿到 undefined 报 "Cannot read properties of undefined (reading 'call')"。 + // (Image/Mask 走 Renderer 链路所以能 super;RectMask2D 不在 Renderer 链路里。) + const targetSoftness = target._softness; + // @ts-ignore + targetSoftness._onValueChanged = null; + targetSoftness.copyFrom(this._softness); + target._clampSoftness(); + // @ts-ignore + targetSoftness._onValueChanged = target._onSoftnessChanged; + } + + protected override _onDestroy(): void { + // @ts-ignore + this._softness._onValueChanged = null; + this._softness = null; + super._onDestroy(); + } + + private _onSoftnessChanged(): void { + this._clampSoftness(); + } + + private _clampSoftness(): void { + const softness = this._softness; + if (softness.x < 0) { + softness.x = 0; + } + if (softness.y < 0) { + softness.y = 0; + } + } +} diff --git a/packages/ui/src/component/advanced/Text.ts b/packages/ui/src/component/advanced/Text.ts index 0eb5edeb93..638fc2e97d 100644 --- a/packages/ui/src/component/advanced/Text.ts +++ b/packages/ui/src/component/advanced/Text.ts @@ -1,13 +1,13 @@ import { BoundingBox, CharRenderInfo, + Color, Engine, Entity, Font, FontStyle, ITextRenderer, OverflowMode, - RenderQueueFlags, RendererUpdateFlags, ShaderData, ShaderDataGroup, @@ -18,7 +18,9 @@ import { TextVerticalAlignment, Texture2D, Vector3, + VertexMergeBatcher, assignmentClone, + deepClone, ignoreClone } from "@galacean/engine"; import { CanvasRenderMode } from "../../enums/CanvasRenderMode"; @@ -31,6 +33,9 @@ import { UITransform, UITransformModifyFlags } from "../UITransform"; */ export class Text extends UIRenderer implements ITextRenderer { private static _textTextureProperty = ShaderProperty.getByName("renderElement_TextTexture"); + private static _textTextureSizeProperty = ShaderProperty.getByName("renderElement_TextTextureSize"); + private static _outlineColorProperty = ShaderProperty.getByName("renderer_OutlineColor"); + private static _outlineWidthProperty = ShaderProperty.getByName("renderer_OutlineWidth"); private static _worldPositions = [new Vector3(), new Vector3(), new Vector3(), new Vector3()]; private static _charRenderInfos: CharRenderInfo[] = []; @@ -60,6 +65,10 @@ export class Text extends UIRenderer implements ITextRenderer { private _enableWrapping: boolean = false; @assignmentClone private _overflowMode: OverflowMode = OverflowMode.Overflow; + @deepClone + private _outlineColor: Color = new Color(0, 0, 0, 1); + @ignoreClone + private _outlineWidth: number = 0; /** * Rendering string for the Text. @@ -206,14 +215,32 @@ export class Text extends UIRenderer implements ITextRenderer { } /** - * The mask layer the sprite renderer belongs to. + * The outline width in pixels. 0 means outline is disabled. Clamped to [0, 8]. + */ + get outlineWidth(): number { + return this._outlineWidth; + } + + set outlineWidth(value: number) { + value = Math.max(0, Math.min(value, 3)); + if (this._outlineWidth !== value) { + this._outlineWidth = value; + this.shaderData.setFloat(Text._outlineWidthProperty, value); + this._setDirtyFlagTrue(DirtyFlag.Position); + } + } + + /** + * The outline color. Only effective when outlineWidth > 0. */ - get maskLayer(): number { - return this._maskLayer; + get outlineColor(): Color { + return this._outlineColor; } - set maskLayer(value: number) { - this._maskLayer = value; + set outlineColor(value: Color) { + if (this._outlineColor !== value) { + this._outlineColor.copyFrom(value); + } } /** @@ -247,6 +274,11 @@ export class Text extends UIRenderer implements ITextRenderer { this.raycastEnabled = false; // @ts-ignore this.setMaterial(engine._basicResources.textDefaultMaterial); + const shaderData = this.shaderData; + shaderData.setFloat(Text._outlineWidthProperty, this._outlineWidth); + shaderData.setColor(Text._outlineColorProperty, this._outlineColor); + // @ts-ignore + this._outlineColor._onValueChanged = this._onOutlineColorChanged.bind(this); } /** @@ -272,6 +304,7 @@ export class Text extends UIRenderer implements ITextRenderer { super._cloneTo(target); target.font = this._font; target._subFont = this._subFont; + target.outlineWidth = this._outlineWidth; } /** @@ -310,10 +343,17 @@ export class Text extends UIRenderer implements ITextRenderer { */ _onRootCanvasModify(flag: RootCanvasModifyFlags): void { if (flag === RootCanvasModifyFlags.ReferenceResolutionPerUnit) { - this._setDirtyFlagTrue(DirtyFlag.LocalPositionBounds); + this._setDirtyFlagTrue(DirtyFlag.Position); } } + /** + * @internal + */ + override _canBatch(preElement, curElement): boolean { + return VertexMergeBatcher.canBatchText(preElement, curElement); + } + protected override _updateBounds(worldBounds: BoundingBox): void { const transform = this._transformEntity.transform; const { x: width, y: height } = transform.size; @@ -350,23 +390,31 @@ export class Text extends UIRenderer implements ITextRenderer { } const engine = context.camera.engine; - const textSubRenderElementPool = engine._textSubRenderElementPool; + const textRenderElementPool = engine._textRenderElementPool; const material = this.getMaterial(); - const renderElement = canvas._renderElement; + const renderElements = canvas._renderElements; + const priority = canvas.sortOrder; + const distanceForSort = canvas._sortDistance; const textChunks = this._textChunks; const isOverlay = canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay; + const textTextureSize = Text._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); + const renderElement = textRenderElementPool.get(); + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk); // @ts-ignore - subRenderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); - subRenderElement.shaderData.setTexture(Text._textTextureProperty, texture); + renderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); + renderElement.shaderData.setTexture(Text._textTextureProperty, texture); + renderElement.shaderData.setVector2( + Text._textTextureSizeProperty, + textTextureSize.set(texture.width, texture.height) + ); if (isOverlay) { - subRenderElement.shaderPasses = material.shader.subShaders[0].passes; - subRenderElement.renderQueueFlags = RenderQueueFlags.All; + renderElement.subShader = material.shader.subShaders[0]; } - renderElement.addSubRenderElement(subRenderElement); + renderElement.priority = priority; + renderElement.distanceForSort = distanceForSort; + renderElements.push(renderElement); } } @@ -532,10 +580,11 @@ export class Text extends UIRenderer implements ITextRenderer { charRenderInfo.texture = charFont._getTextureByIndex(charInfo.index); charRenderInfo.uvs = charInfo.uvs; const { w, ascent, descent } = charInfo; - const left = (startX + offsetWidth) * pixelsPerUnitReciprocal; - const right = (startX + w + offsetWidth) * pixelsPerUnitReciprocal; - const top = (startY + ascent + offsetHeight) * pixelsPerUnitReciprocal; - const bottom = (startY - descent + offsetHeight) * pixelsPerUnitReciprocal; + const ow = this._outlineWidth * pixelsPerUnitReciprocal; + const left = (startX + offsetWidth) * pixelsPerUnitReciprocal - ow; + const right = (startX + w + offsetWidth) * pixelsPerUnitReciprocal + ow; + const top = (startY + ascent + offsetHeight) * pixelsPerUnitReciprocal + ow; + const bottom = (startY - descent + offsetHeight) * pixelsPerUnitReciprocal - ow; localPositions.set(left, top, right, bottom); i === firstLine && (maxY = Math.max(maxY, top)); minY = Math.min(minY, bottom); @@ -632,6 +681,10 @@ export class Text extends UIRenderer implements ITextRenderer { const vertices = subChunk.chunk.vertices; const indices = (subChunk.indices = []); const charRenderInfos = textChunk.charRenderInfos; + const ow = this._outlineWidth; + const texture = textChunk.texture; + const owU = ow > 0 ? ow / texture.width : 0; + const owV = ow > 0 ? ow / texture.height : 0; for (let i = 0, ii = 0, io = 0, vo = subChunk.vertexArea.start + 3; i < count; ++i, io += 4) { const charRenderInfo = charRenderInfos[i]; charRenderInfo.indexInChunk = i; @@ -641,10 +694,13 @@ export class Text extends UIRenderer implements ITextRenderer { indices[ii++] = tempIndices[j] + io; } - // Set uv and color for vertices + // Set uv and color for vertices, expand uv outward by outline width for (let j = 0; j < 4; ++j, vo += 9) { const uv = charRenderInfo.uvs[j]; - uv.copyToArray(vertices, vo); + const su = j === 1 || j === 2 ? 1 : -1; + const sv = j >= 2 ? 1 : -1; + vertices[vo] = uv.x + owU * su; + vertices[vo + 1] = uv.y + owV * sv; vertices[vo + 2] = r; vertices[vo + 3] = g; vertices[vo + 4] = b; @@ -673,6 +729,11 @@ export class Text extends UIRenderer implements ITextRenderer { } textChunks.length = 0; } + + @ignoreClone + private _onOutlineColorChanged(): void { + this.shaderData.setColor(Text._outlineColorProperty, this._outlineColor); + } } class TextChunk { diff --git a/packages/ui/src/component/index.ts b/packages/ui/src/component/index.ts index 1f89431265..8329d1f39a 100644 --- a/packages/ui/src/component/index.ts +++ b/packages/ui/src/component/index.ts @@ -1,11 +1,13 @@ -export { UICanvas } from "./UICanvas"; -export { UIGroup } from "./UIGroup"; -export { UIRenderer } from "./UIRenderer"; -export { UITransform } from "./UITransform"; export { Button } from "./advanced/Button"; export { Image } from "./advanced/Image"; +export { Mask } from "./advanced/Mask"; +export { RectMask2D } from "./advanced/RectMask2D"; export { Text } from "./advanced/Text"; export { ColorTransition } from "./interactive/transition/ColorTransition"; export { ScaleTransition } from "./interactive/transition/ScaleTransition"; export { SpriteTransition } from "./interactive/transition/SpriteTransition"; export { Transition } from "./interactive/transition/Transition"; +export { UICanvas } from "./UICanvas"; +export { UIGroup } from "./UIGroup"; +export { UIRenderer } from "./UIRenderer"; +export { UITransform } from "./UITransform"; diff --git a/packages/ui/src/input/UIPointerEventEmitter.ts b/packages/ui/src/input/UIPointerEventEmitter.ts index 83bf8f1484..d36e529876 100644 --- a/packages/ui/src/input/UIPointerEventEmitter.ts +++ b/packages/ui/src/input/UIPointerEventEmitter.ts @@ -91,11 +91,11 @@ export class UIPointerEventEmitter extends PointerEventEmitter { } } if (camera.clearFlags & CameraClearFlags.Color) { - this._updateRaycast(null); + this._updateRaycast(null, pointer); return; } } - this._updateRaycast(null); + this._updateRaycast(null, pointer); } } @@ -128,10 +128,7 @@ export class UIPointerEventEmitter extends PointerEventEmitter { if (pressedPath.length > 0) { const common = UIPointerEventEmitter._tempArray0; if (this._findCommonInPath(enteredPath, pressedPath, common)) { - const eventData = this._createEventData(pointer); - for (let i = 0, n = common.length; i < n; i++) { - this._fireClick(common[i], eventData); - } + this._bubble(common, pointer, this._fireClick); common.length = 0; } } @@ -170,18 +167,17 @@ export class UIPointerEventEmitter extends PointerEventEmitter { this._enteredPath.length = this._pressedPath.length = this._draggedPath.length = 0; } - private _updateRaycast(element: UIRenderer, pointer: Pointer = null): void { + private _updateRaycast(element: UIRenderer | null, pointer: Pointer): void { const enteredPath = this._enteredPath; const curPath = this._composedPath(element, UIPointerEventEmitter._path); const add = UIPointerEventEmitter._tempArray0; const del = UIPointerEventEmitter._tempArray1; if (this._findDiffInPath(enteredPath, curPath, add, del)) { - const eventData = this._createEventData(pointer); for (let i = 0, n = add.length; i < n; i++) { - this._fireEnter(add[i], eventData); + this._fireEnter(add[i], this._createEventData(pointer, add[i], add[i])); } for (let i = 0, n = del.length; i < n; i++) { - this._fireExit(del[i], eventData); + this._fireExit(del[i], this._createEventData(pointer, del[i], del[i])); } const length = (enteredPath.length = curPath.length); @@ -193,7 +189,7 @@ export class UIPointerEventEmitter extends PointerEventEmitter { curPath.length = 0; } - private _composedPath(element: UIRenderer, path: Entity[]): Entity[] { + private _composedPath(element: UIRenderer | null, path: Entity[]): Entity[] { if (!element) { path.length = 0; return path; @@ -256,8 +252,9 @@ export class UIPointerEventEmitter extends PointerEventEmitter { private _bubble(path: Entity[], pointer: Pointer, fireEvent: FireEvent): void { const length = path.length; if (length <= 0) return; - const eventData = this._createEventData(pointer); + const eventData = this._createEventData(pointer, path[0]); for (let i = 0; i < length; i++) { + eventData.currentTarget = path[i]; fireEvent(path[i], eventData); } } diff --git a/packages/ui/src/shader/global.d.ts b/packages/ui/src/shader/global.d.ts index d0abf1234f..8ce5f33757 100644 --- a/packages/ui/src/shader/global.d.ts +++ b/packages/ui/src/shader/global.d.ts @@ -3,7 +3,7 @@ declare module "*.glsl" { export default value; } -declare module "*.gs" { +declare module "*.shader" { const value: string; export default value; } diff --git a/packages/ui/src/shader/uiDefault.fs.glsl b/packages/ui/src/shader/uiDefault.fs.glsl index e4028405de..aa4ca2e0e9 100644 --- a/packages/ui/src/shader/uiDefault.fs.glsl +++ b/packages/ui/src/shader/uiDefault.fs.glsl @@ -1,14 +1,43 @@ #include uniform sampler2D renderer_UITexture; +uniform vec4 renderer_UIRectClipRect; +uniform float renderer_UIRectClipEnabled; +uniform vec4 renderer_UIRectClipSoftness; +uniform float renderer_UIRectClipHardClip; varying vec2 v_uv; varying vec4 v_color; +varying vec2 v_worldPosition; + +float getUIRectClipAlpha() { + vec4 edgeDistance = vec4( + v_worldPosition.x - renderer_UIRectClipRect.x, + v_worldPosition.y - renderer_UIRectClipRect.y, + renderer_UIRectClipRect.z - v_worldPosition.x, + renderer_UIRectClipRect.w - v_worldPosition.y + ); + vec4 hardClipFactor = step(vec4(0.0), edgeDistance); + vec4 softness = max(renderer_UIRectClipSoftness, vec4(1e-5)); + vec4 softClipFactor = clamp(edgeDistance / softness, 0.0, 1.0); + vec4 useSoftness = step(vec4(1e-5), renderer_UIRectClipSoftness); + vec4 clipFactor = mix(hardClipFactor, softClipFactor, useSoftness); + return clipFactor.x * clipFactor.y * clipFactor.z * clipFactor.w; +} void main() { + float rectClipAlpha = 1.0; + if (renderer_UIRectClipEnabled > 0.5) { + rectClipAlpha = getUIRectClipAlpha(); + } + vec4 baseColor = texture2DSRGB(renderer_UITexture, v_uv); vec4 finalColor = baseColor * v_color; + finalColor.a *= rectClipAlpha; + if (renderer_UIRectClipEnabled > 0.5 && renderer_UIRectClipHardClip > 0.5 && finalColor.a < 0.001) { + discard; + } #ifdef ENGINE_SHOULD_SRGB_CORRECT finalColor = outputSRGBCorrection(finalColor); #endif gl_FragColor = finalColor; -} \ No newline at end of file +} diff --git a/packages/ui/src/shader/uiDefault.vs.glsl b/packages/ui/src/shader/uiDefault.vs.glsl index 2a6b45be4e..52345d9abf 100644 --- a/packages/ui/src/shader/uiDefault.vs.glsl +++ b/packages/ui/src/shader/uiDefault.vs.glsl @@ -1,4 +1,5 @@ uniform mat4 renderer_MVPMat; +uniform mat4 renderer_ModelMat; attribute vec3 POSITION; attribute vec2 TEXCOORD_0; @@ -6,10 +7,12 @@ attribute vec4 COLOR_0; varying vec2 v_uv; varying vec4 v_color; +varying vec2 v_worldPosition; void main() { gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); v_uv = TEXCOORD_0; v_color = COLOR_0; + v_worldPosition = POSITION.xy; } diff --git a/packages/xr-webxr/package.json b/packages/xr-webxr/package.json index 9663c2321f..f87fbc528a 100644 --- a/packages/xr-webxr/package.json +++ b/packages/xr-webxr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr-webxr", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/packages/xr/package.json b/packages/xr/package.json index ec2e3cc79e..14945b47d9 100644 --- a/packages/xr/package.json +++ b/packages/xr/package.json @@ -1,6 +1,6 @@ { "name": "@galacean/engine-xr", - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed565e145b..f6dbf1d371 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,11 +45,11 @@ importers: specifier: latest version: 0.5.22 '@typescript-eslint/eslint-plugin': - specifier: ^6.1.0 - version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) + specifier: ^8.58.1 + version: 8.58.1(@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) '@typescript-eslint/parser': - specifier: ^6.1.0 - version: 6.21.0(eslint@8.57.1)(typescript@5.6.3) + specifier: ^8.58.1 + version: 8.58.1(eslint@8.57.1)(typescript@5.6.3) '@vitest/coverage-v8': specifier: 2.1.3 version: 2.1.3(@vitest/browser@2.1.3(@types/node@18.19.64)(@vitest/spy@2.1.3)(typescript@5.6.3)(vite@5.4.11(@types/node@18.19.64)(sass@1.81.0)(terser@5.44.1))(vitest@2.1.3))(vitest@2.1.3(@types/node@18.19.64)(@vitest/browser@2.1.3)(msw@2.6.5(@types/node@18.19.64)(typescript@5.6.3))(sass@1.81.0)(terser@5.44.1)) @@ -63,7 +63,7 @@ importers: specifier: ^13 version: 13.6.9 eslint: - specifier: ^8.44.0 + specifier: ^8.57.1 version: 8.57.1 eslint-config-prettier: specifier: ^8.8.0 @@ -78,8 +78,8 @@ importers: specifier: ^8.0.0 version: 8.0.3 lint-staged: - specifier: ^10.5.3 - version: 10.5.4 + specifier: ^16.4.0 + version: 16.4.0 nyc: specifier: ^15.1.0 version: 15.1.0 @@ -188,6 +188,9 @@ importers: '@galacean/engine-toolkit': specifier: latest version: 1.5.3(@galacean/engine-ui@packages+ui)(@galacean/engine@packages+galacean) + '@galacean/engine-toolkit-stats': + specifier: latest + version: 1.6.0(@galacean/engine@packages+galacean) '@galacean/engine-ui': specifier: workspace:* version: link:../packages/ui @@ -804,10 +807,20 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/eslintrc@2.1.4': resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -935,6 +948,11 @@ packages: peerDependencies: '@galacean/engine': ^1.5.0 + '@galacean/engine-toolkit-stats@1.6.0': + resolution: {integrity: sha512-63LLxTWg15xR000jbtEONnK6lBBMylvl5m+3VqqC7b09YAuMWlm9CuPfaM8dlbctOYT6nmPu9bpQiq3JfdgtWg==} + peerDependencies: + '@galacean/engine': '>=1.6.0-0' + '@galacean/engine-toolkit@1.3.9': resolution: {integrity: sha512-sxE7QfzH61O9Q1wtwnjIEjcg3n0ZZVz9B6CyqBLOWyWgWsZmefcjZLnnH4HIkvc5ZLNA+gMuJ1ekmwJgfkck+g==} @@ -1481,9 +1499,6 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} @@ -1508,9 +1523,6 @@ packages: '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - '@types/semver@7.5.8': - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - '@types/statuses@2.0.5': resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} @@ -1520,63 +1532,64 @@ packages: '@types/webxr@0.5.22': resolution: {integrity: sha512-Vr6Stjv5jPRqH690f5I5GLjVk8GSsoQSYJ2FVd/3jJF7KaqfwPi3ehfBS96mlQ2kPCwZaX6U0rG2+NGHBKkA/A==} - '@typescript-eslint/eslint-plugin@6.21.0': - resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/eslint-plugin@8.58.1': + resolution: {integrity: sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser': ^8.58.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@6.21.0': - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/parser@8.58.1': + resolution: {integrity: sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@6.21.0': - resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/project-service@8.58.1': + resolution: {integrity: sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@6.21.0': - resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/scope-manager@8.58.1': + resolution: {integrity: sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.58.1': + resolution: {integrity: sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@6.21.0': - resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/type-utils@8.58.1': + resolution: {integrity: sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/typescript-estree@6.21.0': - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/types@8.58.1': + resolution: {integrity: sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.58.1': + resolution: {integrity: sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@6.21.0': - resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/utils@8.58.1': + resolution: {integrity: sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@6.21.0': - resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/visitor-keys@8.58.1': + resolution: {integrity: sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -1694,14 +1707,14 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1710,6 +1723,10 @@ packages: resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1722,6 +1739,10 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1748,10 +1769,6 @@ packages: array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} @@ -1760,10 +1777,6 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} @@ -1771,6 +1784,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1785,6 +1802,10 @@ packages: brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1882,13 +1903,13 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} - cli-truncate@2.1.0: - resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} - engines: {node: '>=8'} + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} @@ -1914,13 +1935,13 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} - engines: {node: '>= 6'} - commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -2017,6 +2038,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -2029,9 +2059,6 @@ packages: resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} engines: {node: '>=4'} - dedent@0.7.0: - resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} - deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -2080,10 +2107,6 @@ packages: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} @@ -2113,6 +2136,9 @@ packages: engines: {node: '>= 8.6'} hasBin: true + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2126,14 +2152,14 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -2322,6 +2348,10 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2365,9 +2395,8 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - execa@4.1.0: - resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} - engines: {node: '>=10'} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} @@ -2387,10 +2416,6 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -2411,6 +2436,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2489,13 +2523,14 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} - get-own-enumerable-property-symbols@3.0.2: - resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} - get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -2568,10 +2603,6 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} @@ -2632,10 +2663,6 @@ packages: http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - human-signals@1.1.1: - resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} - engines: {node: '>=8.12.0'} - human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -2649,6 +2676,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + immutable@5.0.2: resolution: {integrity: sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw==} @@ -2693,6 +2724,10 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -2707,10 +2742,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-obj@1.0.1: - resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} - engines: {node: '>=0.10.0'} - is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} @@ -2726,10 +2757,6 @@ packages: is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - is-regexp@1.0.0: - resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} - engines: {node: '>=0.10.0'} - is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -2745,10 +2772,6 @@ packages: is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -2875,18 +2898,14 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lint-staged@10.5.4: - resolution: {integrity: sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg==} + lint-staged@16.4.0: + resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==} + engines: {node: '>=20.17'} hasBin: true - listr2@3.14.0: - resolution: {integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==} - engines: {node: '>=10.0.0'} - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} @@ -2905,13 +2924,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - log-update@4.0.0: - resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} - engines: {node: '>=10'} + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} loupe@3.1.2: resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} @@ -2977,10 +2992,6 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -2990,14 +3001,14 @@ packages: engines: {node: '>=4.0.0'} hasBin: true - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} @@ -3006,13 +3017,13 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -3119,10 +3130,6 @@ packages: resolution: {integrity: sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==} engines: {node: '>=4'} - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3151,14 +3158,14 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -3194,10 +3201,6 @@ packages: resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} engines: {node: '>=8'} - p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -3279,6 +3282,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} @@ -3300,9 +3307,6 @@ packages: engines: {node: '>=18'} hasBin: true - please-upgrade-node@3.2.0: - resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} - postcss@8.4.49: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} @@ -3446,9 +3450,9 @@ packages: responselike@1.0.2: resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} @@ -3514,9 +3518,6 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -3549,6 +3550,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + serialize-error@7.0.1: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} @@ -3593,17 +3599,13 @@ packages: resolution: {integrity: sha512-pEjMUbwJ5Pl/6Vn6FsamXHXItJXSRftcibixDmNCWbWhic0hzHrwkMZo0IZ7fMRH9KxcWDFSkzhccB4285PutA==} engines: {node: '>=4.2'} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - slice-ansi@3.0.0: - resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} - engines: {node: '>=8'} + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} - slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} @@ -3658,10 +3660,6 @@ packages: strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - string-argv@0.3.1: - resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} - engines: {node: '>=0.6.19'} - string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -3674,16 +3672,20 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.0: + resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} + engines: {node: '>=20'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - stringify-object@3.3.0: - resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} - engines: {node: '>=4'} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3692,14 +3694,14 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -3764,10 +3766,18 @@ packages: tinyexec@0.3.1: resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + tinyglobby@0.2.10: resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + tinypool@1.0.2: resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -3800,11 +3810,11 @@ packages: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} - ts-api-utils@1.4.0: - resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} - engines: {node: '>=16'} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} peerDependencies: - typescript: '>=4.2.0' + typescript: '>=4.8.4' ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} @@ -4092,6 +4102,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -4127,6 +4141,11 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -4539,8 +4558,15 @@ snapshots: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.12.2': {} + '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 @@ -4662,6 +4688,10 @@ snapshots: dependencies: '@galacean/engine': link:packages/galacean + '@galacean/engine-toolkit-stats@1.6.0(@galacean/engine@packages+galacean)': + dependencies: + '@galacean/engine': link:packages/galacean + '@galacean/engine-toolkit@1.3.9(@galacean/engine@packages+galacean)': dependencies: '@galacean/engine-toolkit-auxiliary-lines': 1.3.9(@galacean/engine@packages+galacean) @@ -5149,8 +5179,6 @@ snapshots: '@types/estree@1.0.6': {} - '@types/json-schema@7.0.15': {} - '@types/keyv@3.1.4': dependencies: '@types/node': 18.19.64 @@ -5175,99 +5203,102 @@ snapshots: dependencies: '@types/node': 18.19.64 - '@types/semver@7.5.8': {} - '@types/statuses@2.0.5': {} '@types/tough-cookie@4.0.5': {} '@types/webxr@0.5.22': {} - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.7 + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.58.1(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/type-utils': 8.58.1(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 8.58.1(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.58.1 eslint: 8.57.1 - graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 7.0.5 natural-compare: 1.4.0 - semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: + ts-api-utils: 2.5.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.7 + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.58.1 + debug: 4.4.3 eslint: 8.57.1 - optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@6.21.0': + '@typescript-eslint/project-service@8.58.1(typescript@5.6.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.6.3) + '@typescript-eslint/types': 8.58.1 + debug: 4.4.3 + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.58.1': dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/visitor-keys': 8.58.1 - '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/tsconfig-utils@8.58.1(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.6.3) - debug: 4.3.7 + typescript: 5.6.3 + + '@typescript-eslint/type-utils@8.58.1(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.6.3) + '@typescript-eslint/utils': 8.58.1(eslint@8.57.1)(typescript@5.6.3) + debug: 4.4.3 eslint: 8.57.1 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: + ts-api-utils: 2.5.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@6.21.0': {} + '@typescript-eslint/types@8.58.1': {} - '@typescript-eslint/typescript-estree@6.21.0(typescript@5.6.3)': + '@typescript-eslint/typescript-estree@8.58.1(typescript@5.6.3)': dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.7 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: + '@typescript-eslint/project-service': 8.58.1(typescript@5.6.3) + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.6.3) + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/visitor-keys': 8.58.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/utils@8.58.1(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.6.3) eslint: 8.57.1 - semver: 7.6.3 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - - typescript - '@typescript-eslint/visitor-keys@6.21.0': + '@typescript-eslint/visitor-keys@8.58.1': dependencies: - '@typescript-eslint/types': 6.21.0 - eslint-visitor-keys: 3.4.3 + '@typescript-eslint/types': 8.58.1 + eslint-visitor-keys: 5.0.1 '@ungap/structured-clone@1.2.0': {} @@ -5455,16 +5486,20 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ansi-colors@4.1.3: {} - ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -5473,6 +5508,8 @@ snapshots: ansi-styles@6.2.1: {} + ansi-styles@6.2.3: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -5498,18 +5535,16 @@ snapshots: array-ify@1.0.0: {} - array-union@2.1.0: {} - arrify@1.0.1: {} assertion-error@2.0.1: {} - astral-regex@2.0.0: {} - at-least-node@1.0.0: {} balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + binary-extensions@2.3.0: {} boolean@3.2.0: @@ -5524,6 +5559,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -5649,14 +5688,14 @@ snapshots: clean-stack@2.2.0: {} - cli-cursor@3.1.0: + cli-cursor@5.0.0: dependencies: - restore-cursor: 3.1.0 + restore-cursor: 5.1.0 - cli-truncate@2.1.0: + cli-truncate@5.2.0: dependencies: - slice-ansi: 3.0.0 - string-width: 4.2.3 + slice-ansi: 8.0.0 + string-width: 8.2.0 cli-width@4.1.0: {} @@ -5684,11 +5723,11 @@ snapshots: colorette@2.0.20: {} + commander@14.0.3: {} + commander@2.20.3: optional: true - commander@6.2.1: {} - commondir@1.0.1: {} compare-func@2.0.0: @@ -5785,6 +5824,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.3: + dependencies: + ms: 2.1.3 + decamelize-keys@1.1.1: dependencies: decamelize: 1.2.0 @@ -5796,8 +5839,6 @@ snapshots: dependencies: mimic-response: 1.0.1 - dedent@0.7.0: {} - deep-eql@5.0.2: {} deep-is@0.1.4: {} @@ -5838,10 +5879,6 @@ snapshots: diff@4.0.2: {} - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - doctrine@3.0.0: dependencies: esutils: 2.0.3 @@ -5868,6 +5905,8 @@ snapshots: transitivePeerDependencies: - supports-color + emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -5879,13 +5918,10 @@ snapshots: dependencies: once: 1.4.0 - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - env-paths@2.2.1: {} + environment@1.1.0: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -6062,6 +6098,8 @@ snapshots: eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@5.0.1: {} + eslint@8.57.1: dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) @@ -6135,17 +6173,7 @@ snapshots: esutils@2.0.3: {} - execa@4.1.0: - dependencies: - cross-spawn: 7.0.5 - get-stream: 5.2.0 - human-signals: 1.1.1 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 + eventemitter3@5.0.4: {} execa@8.0.1: dependencies: @@ -6174,14 +6202,6 @@ snapshots: fast-diff@1.3.0: {} - fast-glob@3.3.2: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -6198,6 +6218,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -6279,6 +6303,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.5.0: {} + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -6288,8 +6314,6 @@ snapshots: hasown: 2.0.2 optional: true - get-own-enumerable-property-symbols@3.0.2: {} - get-package-type@0.1.0: {} get-stdin@8.0.0: {} @@ -6387,15 +6411,6 @@ snapshots: gopd: 1.0.1 optional: true - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - gopd@1.0.1: dependencies: get-intrinsic: 1.2.4 @@ -6459,14 +6474,14 @@ snapshots: http-cache-semantics@4.1.1: {} - human-signals@1.1.1: {} - human-signals@5.0.0: {} husky@8.0.3: {} ignore@5.3.2: {} + ignore@7.0.5: {} + immutable@5.0.2: {} import-fresh@3.3.0: @@ -6501,6 +6516,10 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.5.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -6511,8 +6530,6 @@ snapshots: is-number@7.0.0: {} - is-obj@1.0.1: {} - is-obj@2.0.0: {} is-path-inside@3.0.3: {} @@ -6523,8 +6540,6 @@ snapshots: dependencies: '@types/estree': 1.0.6 - is-regexp@1.0.0: {} - is-stream@2.0.1: {} is-stream@3.0.0: {} @@ -6535,8 +6550,6 @@ snapshots: is-typedarray@1.0.0: {} - is-unicode-supported@0.1.0: {} - is-windows@1.0.2: {} isarray@1.0.0: {} @@ -6671,38 +6684,23 @@ snapshots: lines-and-columns@1.2.4: {} - lint-staged@10.5.4: + lint-staged@16.4.0: dependencies: - chalk: 4.1.2 - cli-truncate: 2.1.0 - commander: 6.2.1 - cosmiconfig: 7.1.0 - debug: 4.3.7 - dedent: 0.7.0 - enquirer: 2.4.1 - execa: 4.1.0 - listr2: 3.14.0(enquirer@2.4.1) - log-symbols: 4.1.0 - micromatch: 4.0.8 - normalize-path: 3.0.0 - please-upgrade-node: 3.2.0 - string-argv: 0.3.1 - stringify-object: 3.3.0 - transitivePeerDependencies: - - supports-color + commander: 14.0.3 + listr2: 9.0.5 + picomatch: 4.0.4 + string-argv: 0.3.2 + tinyexec: 1.1.1 + yaml: 2.8.3 - listr2@3.14.0(enquirer@2.4.1): + listr2@9.0.5: dependencies: - cli-truncate: 2.1.0 + cli-truncate: 5.2.0 colorette: 2.0.20 - log-update: 4.0.0 - p-map: 4.0.0 + eventemitter3: 5.0.4 + log-update: 6.1.0 rfdc: 1.4.1 - rxjs: 7.8.1 - through: 2.3.8 - wrap-ansi: 7.0.0 - optionalDependencies: - enquirer: 2.4.1 + wrap-ansi: 9.0.2 locate-path@5.0.0: dependencies: @@ -6718,17 +6716,13 @@ snapshots: lodash@4.17.21: {} - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - - log-update@4.0.0: + log-update@6.1.0: dependencies: - ansi-escapes: 4.3.2 - cli-cursor: 3.1.0 - slice-ansi: 4.0.0 - wrap-ansi: 6.2.0 + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.2 loupe@3.1.2: {} @@ -6797,30 +6791,29 @@ snapshots: merge-stream@2.0.0: {} - merge2@1.4.1: {} - micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 + optional: true mime@2.6.0: {} - mimic-fn@2.1.0: {} - mimic-fn@4.0.0: {} + mimic-function@5.0.1: {} + mimic-response@1.0.1: {} min-indent@1.0.1: {} - minimatch@3.1.2: + minimatch@10.2.5: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 5.0.5 - minimatch@9.0.3: + minimatch@3.1.2: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 1.1.11 minimatch@9.0.5: dependencies: @@ -6934,10 +6927,6 @@ snapshots: pify: 3.0.0 optional: true - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - npm-run-path@5.3.0: dependencies: path-key: 4.0.0 @@ -6994,14 +6983,14 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - onetime@6.0.0: dependencies: mimic-fn: 4.0.0 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + opener@1.5.2: {} optionator@0.9.4: @@ -7037,10 +7026,6 @@ snapshots: dependencies: aggregate-error: 3.1.0 - p-map@4.0.0: - dependencies: - aggregate-error: 3.1.0 - p-try@2.2.0: {} package-hash@4.0.0: @@ -7100,6 +7085,8 @@ snapshots: picomatch@4.0.2: {} + picomatch@4.0.4: {} + pify@3.0.0: optional: true @@ -7121,10 +7108,6 @@ snapshots: optionalDependencies: fsevents: 2.3.2 - please-upgrade-node@3.2.0: - dependencies: - semver-compare: 1.0.0 - postcss@8.4.49: dependencies: nanoid: 3.3.7 @@ -7261,10 +7244,10 @@ snapshots: dependencies: lowercase-keys: 1.0.1 - restore-cursor@3.1.0: + restore-cursor@5.1.0: dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 + onetime: 7.0.0 + signal-exit: 4.1.0 reusify@1.0.4: {} @@ -7355,10 +7338,6 @@ snapshots: dependencies: queue-microtask: 1.2.3 - rxjs@7.8.1: - dependencies: - tslib: 2.8.1 - safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -7371,7 +7350,8 @@ snapshots: optionalDependencies: '@parcel/watcher': 2.5.0 - semver-compare@1.0.0: {} + semver-compare@1.0.0: + optional: true semver@5.7.2: {} @@ -7381,6 +7361,8 @@ snapshots: semver@7.6.3: {} + semver@7.7.4: {} + serialize-error@7.0.1: dependencies: type-fest: 0.13.1 @@ -7416,19 +7398,15 @@ snapshots: skip-regex@1.0.2: {} - slash@3.0.0: {} - - slice-ansi@3.0.0: + slice-ansi@7.1.2: dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.1.0 - slice-ansi@4.0.0: + slice-ansi@8.0.0: dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 source-map-js@1.2.1: {} @@ -7482,8 +7460,6 @@ snapshots: strict-event-emitter@0.5.1: {} - string-argv@0.3.1: {} - string-argv@0.3.2: {} string-width@4.2.3: @@ -7498,6 +7474,17 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.1.0 + + string-width@8.2.0: + dependencies: + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -7506,12 +7493,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - stringify-object@3.3.0: - dependencies: - get-own-enumerable-property-symbols: 3.0.2 - is-obj: 1.0.1 - is-regexp: 1.0.0 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -7520,9 +7501,11 @@ snapshots: dependencies: ansi-regex: 6.1.0 - strip-bom@4.0.0: {} + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 - strip-final-newline@2.0.0: {} + strip-bom@4.0.0: {} strip-final-newline@3.0.0: {} @@ -7592,11 +7575,18 @@ snapshots: tinyexec@0.3.1: {} + tinyexec@1.1.1: {} + tinyglobby@0.2.10: dependencies: fdir: 6.4.2(picomatch@4.0.2) picomatch: 4.0.2 + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinypool@1.0.2: {} tinyrainbow@1.2.0: {} @@ -7620,7 +7610,7 @@ snapshots: trim-newlines@3.0.1: {} - ts-api-utils@1.4.0(typescript@5.6.3): + ts-api-utils@2.5.0(typescript@5.6.3): dependencies: typescript: 5.6.3 @@ -7738,7 +7728,7 @@ snapshots: vite-node@2.1.5(@types/node@18.19.64)(sass@1.81.0)(terser@5.44.1): dependencies: cac: 6.7.14 - debug: 4.3.7 + debug: 4.4.3 es-module-lexer: 1.5.4 pathe: 1.1.2 vite: 5.4.11(@types/node@18.19.64)(sass@1.81.0)(terser@5.44.1) @@ -7831,7 +7821,7 @@ snapshots: '@vitest/spy': 2.1.5 '@vitest/utils': 2.1.5 chai: 5.1.2 - debug: 4.3.7 + debug: 4.4.3 expect-type: 1.1.0 magic-string: 0.30.12 pathe: 1.1.2 @@ -7892,6 +7882,12 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + wrappy@1.0.2: {} write-file-atomic@3.0.3: @@ -7913,6 +7909,8 @@ snapshots: yaml@1.10.2: {} + yaml@2.8.3: {} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 diff --git a/rollup-plugin-glsl.js b/rollup-plugin-glsl.js index cc545c623b..3a48c3efc9 100644 --- a/rollup-plugin-glsl.js +++ b/rollup-plugin-glsl.js @@ -35,7 +35,7 @@ function compressShader(code) { export default function glsl(userOptions = {}) { const options = Object.assign( { - include: ["**/*.vs", "**/*.fs", "**/*.vert", "**/*.frag", "**/*.glsl"] + include: ["**/*.vs", "**/*.fs", "**/*.vert", "**/*.frag", "**/*.glsl", "**/*.shader"] }, userOptions ); diff --git a/rollup.config.js b/rollup.config.js index b72def7cb8..dd57ae8a1b 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -32,7 +32,7 @@ const extensions = [".js", ".jsx", ".ts", ".tsx"]; const mainFields = NODE_ENV === "development" ? ["debug", "module", "main"] : undefined; const glslPlugin = glsl({ - include: [/\.(glsl|gs)$/], + include: [/\.(glsl|shader)$/], compress: false }); @@ -90,7 +90,7 @@ function config({ location, pkgJson, verboseMode }) { glslifyPluginIdx, 1, glsl({ - include: [/\.(glsl|gs)$/], + include: [/\.(glsl|shader)$/], compress: true }) ); @@ -157,7 +157,7 @@ function config({ location, pkgJson, verboseMode }) { glslifyPluginIdx, 1, glsl({ - include: [/\.(glsl|gs)$/], + include: [/\.(glsl|shader)$/], compress: true }) ); diff --git a/tests/package.json b/tests/package.json index bcf0debcb4..54d04b4aa5 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-tests", "private": true, - "version": "2.0.0-alpha.24", + "version": "0.0.0-experimental-2.0-game.14", "license": "MIT", "main": "dist/main.js", "module": "dist/module.js", diff --git a/tests/src/core/Animator.test.ts b/tests/src/core/Animator.test.ts index c37d476a92..69026b5a1e 100644 --- a/tests/src/core/Animator.test.ts +++ b/tests/src/core/Animator.test.ts @@ -238,6 +238,89 @@ describe("Animator test", function () { expect(layerState).to.eq(2); }); + it("crossFade advances with per-instance playData speed instead of shared AnimatorState speed", () => { + const sharedStates = animator.animatorController.layers[0].stateMachine.states; + const sharedWalkState = sharedStates.find((state) => state.name === "Walk"); + const sharedRunState = sharedStates.find((state) => state.name === "Run"); + const oldWalkSpeed = sharedWalkState.speed; + const oldRunSpeed = sharedRunState.speed; + + try { + animator.play("Walk"); + animator.crossFade("Run", 1.0, 0); + + const layerData = animator["_animatorLayersData"][0]; + layerData.srcPlayData.speed = 0.25; + layerData.destPlayData.speed = 0.25; + sharedWalkState.speed = 10; + sharedRunState.speed = 10; + + const srcPlayedTime = layerData.srcPlayData.playedTime; + const destPlayedTime = layerData.destPlayData.playedTime; + // @ts-ignore + animator.engine.time._frameCount++; + animator.update(0.2); + + expect(layerData.srcPlayData.playedTime - srcPlayedTime).toBeCloseTo(0.05, 5); + expect(layerData.destPlayData.playedTime - destPlayedTime).toBeCloseTo(0.05, 5); + } finally { + sharedWalkState.speed = oldWalkSpeed; + sharedRunState.speed = oldRunSpeed; + } + }); + + it("playData wrapMode overrides shared AnimatorState wrapMode per instance", () => { + const sharedWalkState = animator.animatorController.layers[0].stateMachine.states.find( + (state) => state.name === "Walk" + ); + const oldWrapMode = sharedWalkState.wrapMode; + + try { + sharedWalkState.wrapMode = WrapMode.Loop; + animator.play("Walk"); + + const layerData = animator["_animatorLayersData"][0]; + const playData = layerData.srcPlayData; + playData.wrapMode = WrapMode.Once; + + expect(sharedWalkState.wrapMode).to.eq(WrapMode.Loop); + + // @ts-ignore + animator.engine.time._frameCount++; + animator.update(playData.state.clip.length + 0.1); + + expect(layerData.layerState).to.eq(LayerState.Finished); + } finally { + sharedWalkState.wrapMode = oldWrapMode; + } + }); + + it("playData wrapMode does not leak between animators sharing one controller", () => { + const sharedWalkState = animator.animatorController.layers[0].stateMachine.states.find( + (state) => state.name === "Walk" + ); + const oldWrapMode = sharedWalkState.wrapMode; + const otherEntity = new Entity(engine); + const otherAnimator = otherEntity.addComponent(Animator); + otherAnimator.animatorController = animator.animatorController; + + try { + sharedWalkState.wrapMode = WrapMode.Loop; + animator.play("Walk"); + otherAnimator.play("Walk"); + + const playData = animator["_animatorLayersData"][0].srcPlayData; + const otherPlayData = otherAnimator["_animatorLayersData"][0].srcPlayData; + playData.wrapMode = WrapMode.Once; + + expect(otherPlayData.wrapMode).to.eq(WrapMode.Loop); + expect(sharedWalkState.wrapMode).to.eq(WrapMode.Loop); + } finally { + sharedWalkState.wrapMode = oldWrapMode; + otherEntity.destroy(); + } + }); + it("cross fade in fixed time", () => { const runState = animator.findAnimatorState("Run"); animator.play("Walk"); @@ -307,7 +390,7 @@ describe("Animator test", function () { const additiveLayer = new AnimatorControllerLayer("additiveLayer"); additiveLayer.stateMachine = animatorStateMachine; const mask = AnimatorLayerMask.createByEntity(animator.entity); - mask.setPathMaskActive("_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04", false, true); + mask.setPathMaskActive("root/_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04", false, true); additiveLayer.mask = mask; additiveLayer.blendingMode = AnimatorLayerBlendingMode.Additive; animatorController.addLayer(additiveLayer); @@ -319,12 +402,12 @@ describe("Animator test", function () { animator.play("Walk", 0); animator.play("Run", 1); - const parentEntity = animator.entity.findByPath("_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/"); + const parentEntity = animator.entity.findByPath("root/_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/"); const targetEntity = animator.entity.findByPath( - "_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04" + "root/_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04" ); const childEntity = animator.entity.findByPath( - "_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04/b_Head_05" + "root/_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04/b_Head_05" ); let layerData = animator["_animatorLayersData"][1]; @@ -338,7 +421,7 @@ describe("Animator test", function () { expect(childLayerCurveOwner.isActive).to.eq(false); animator.animatorController.removeLayer(1); - mask.removePathMask("_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04/b_Head_05"); + mask.removePathMask("root/_rootJoint/b_Root_00/b_Hip_01/b_Spine01_02/b_Spine02_03/b_Neck_04/b_Head_05"); animator.animatorController.addLayer(additiveLayer); animator.play("Run", 1); layerData = animator["_animatorLayersData"][1]; @@ -350,7 +433,7 @@ describe("Animator test", function () { animator.play("Walk"); class TestScript extends Script { - event0(): void { } + event0(): void {} } const testScript = animator.entity.addComponent(TestScript); @@ -366,6 +449,58 @@ describe("Animator test", function () { expect(testScriptSpy).toHaveBeenCalledTimes(1); }); + it("does not refire animation events when a once clip reaches the end", () => { + const entity = new Entity(engine); + const onceAnimator = entity.addComponent(Animator); + const controller = new AnimatorController(engine); + const layer = new AnimatorControllerLayer("Base Layer"); + controller.addLayer(layer); + + const state = layer.stateMachine.addState("once"); + state.wrapMode = WrapMode.Once; + + const clip = new AnimationClip("once-clip"); + const curve = new AnimationFloatCurve(); + const start = new Keyframe(); + const end = new Keyframe(); + start.time = 0; + start.value = 0; + end.time = 1; + end.value = 1; + curve.addKey(start); + curve.addKey(end); + clip.addCurveBinding("", Transform, "position.x", curve); + + class TestScript extends Script { + event0(): void {} + } + + const event0 = new AnimationEvent(); + event0.functionName = "event0"; + event0.time = 0.5; + clip.addEvent(event0); + state.clip = clip; + onceAnimator.animatorController = controller; + + const testScript = entity.addComponent(TestScript); + const testScriptSpy = vi.spyOn(testScript, "event0"); + + try { + onceAnimator.play("once"); + // @ts-ignore + onceAnimator.engine.time._frameCount++; + onceAnimator.update(0.75); + expect(testScriptSpy).toHaveBeenCalledTimes(1); + + // @ts-ignore + onceAnimator.engine.time._frameCount++; + onceAnimator.update(0.5); + expect(testScriptSpy).toHaveBeenCalledTimes(1); + } finally { + entity.destroy(); + } + }); + it("stateMachine", () => { animator.animatorController.addParameter("playerSpeed", 1); const stateMachine = animator.animatorController.layers[0].stateMachine; @@ -792,8 +927,8 @@ describe("Animator test", function () { animator.animatorController = animatorController; class TestScript extends StateMachineScript { - onStateEnter(animator) { } - onStateExit(animator) { } + onStateEnter(animator) {} + onStateExit(animator) {} } const testScript = state1.addStateMachineScript(TestScript); @@ -1046,6 +1181,48 @@ describe("Animator test", function () { expect(animator.entity.clone().getComponent(Animator).animatorController).to.eq(animator.animatorController); }); + it("samples self-name-prefixed curve paths on wrapped roots", () => { + const wrappedRoot = new Entity(engine, "GLTF_ROOT"); + const hips = new Entity(engine, "mixamorig:Hips"); + const spine = new Entity(engine, "mixamorig:Spine"); + hips.parent = wrappedRoot; + spine.parent = hips; + + const clip = new AnimationClip("idle"); + const hipsCurve = new AnimationFloatCurve(); + const spineCurve = new AnimationFloatCurve(); + const hipsStart = new Keyframe(); + const hipsEnd = new Keyframe(); + hipsStart.time = 0; + hipsStart.value = 0; + hipsEnd.time = 0.1; + hipsEnd.value = 1; + hipsCurve.addKey(hipsStart); + hipsCurve.addKey(hipsEnd); + + const spineStart = new Keyframe(); + const spineEnd = new Keyframe(); + spineStart.time = 0; + spineStart.value = 0; + spineEnd.time = 0.1; + spineEnd.value = 1; + spineCurve.addKey(spineStart); + spineCurve.addKey(spineEnd); + + clip.addCurveBinding("mixamorig:Hips", Transform, "position.x", hipsCurve); + clip.addCurveBinding("mixamorig:Hips/mixamorig:Spine", Transform, "position.y", spineCurve); + + expect(wrappedRoot.findByPath("mixamorig:Hips")).to.eq(hips); + expect(wrappedRoot.findByPath("mixamorig:Hips/mixamorig:Spine")).to.eq(spine); + + // @ts-ignore + clip._sampleAnimation(wrappedRoot, 0.1); + + expect(wrappedRoot.transform.position.x).to.eq(0); + expect(hips.transform.position.x).to.eq(1); + expect(spine.transform.position.y).to.eq(1); + }); + it("anyState transition interrupts crossFade", () => { const { animatorController } = animator; animatorController.addParameter("interrupt", false); diff --git a/tests/src/core/AnimatorHang.test.ts b/tests/src/core/AnimatorHang.test.ts new file mode 100644 index 0000000000..99a8a5ba14 --- /dev/null +++ b/tests/src/core/AnimatorHang.test.ts @@ -0,0 +1,20 @@ +import { Animator, Camera } from "@galacean/engine-core"; +import "@galacean/engine-loader"; +import type { GLTFResource } from "@galacean/engine-loader"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { describe, expect, it } from "vitest"; +import { glbResource } from "./model/fox"; +const canvasDOM = document.createElement("canvas"); +canvasDOM.width = 1024; +canvasDOM.height = 1024; +describe("Canvas 1024 test", async function () { + const engine = await WebGLEngine.create({ canvas: canvasDOM }); + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity(); + rootEntity.addComponent(Camera); + const resource = await engine.resourceManager.load(glbResource); + const defaultSceneRoot = resource.defaultSceneRoot; + rootEntity.addChild(defaultSceneRoot); + const animator = defaultSceneRoot.getComponent(Animator); + it("loaded", () => { expect(animator).not.eq(null); }); +}); diff --git a/tests/src/core/Camera.test.ts b/tests/src/core/Camera.test.ts index 52ae7890d1..5f77077f7f 100644 --- a/tests/src/core/Camera.test.ts +++ b/tests/src/core/Camera.test.ts @@ -245,10 +245,64 @@ describe("camera test", function () { expect(Math.abs(ray.direction.z)).not.eq(Infinity); }); + it("screenPointToRay should ignore inherited scale from parent entity", () => { + // Simulate UICanvas scenario: camera is a child of a scaled parent entity + const scene = engine.sceneManager.scenes[0]; + const parentEntity = scene.createRootEntity("scaledParent"); + parentEntity.transform.setScale(1.5, 1.5, 1.5); + parentEntity.transform.setPosition(100, 200, 0); + + const childEntity = parentEntity.createChild("cameraChild"); + childEntity.transform.setPosition(0, 0, 500); + const scaledCamera = childEntity.addComponent(Camera); + scaledCamera.isOrthographic = true; + scaledCamera.orthographicSize = 5; + scaledCamera.nearClipPlane = 0.1; + scaledCamera.farClipPlane = 1000; + + // A camera without inherited scale at the same world position/rotation for comparison + const refEntity = scene.createRootEntity("refCamera"); + refEntity.transform.setWorldPosition( + childEntity.transform.worldPosition.x, + childEntity.transform.worldPosition.y, + childEntity.transform.worldPosition.z + ); + const refCamera = refEntity.addComponent(Camera); + refCamera.isOrthographic = true; + refCamera.orthographicSize = 5; + refCamera.nearClipPlane = 0.1; + refCamera.farClipPlane = 1000; + + // Both cameras should produce the same ray for the same screen point + const screenPoint = new Vector2(128, 128); + const rayScaled = scaledCamera.screenPointToRay(screenPoint, new Ray()); + const rayRef = refCamera.screenPointToRay(screenPoint, new Ray()); + + expect(rayScaled.origin.x).to.be.closeTo(rayRef.origin.x, 0.001); + expect(rayScaled.origin.y).to.be.closeTo(rayRef.origin.y, 0.001); + expect(rayScaled.direction.x).to.be.closeTo(rayRef.direction.x, 0.001); + expect(rayScaled.direction.y).to.be.closeTo(rayRef.direction.y, 0.001); + expect(rayScaled.direction.z).to.be.closeTo(rayRef.direction.z, 0.001); + + // Round-trip: worldToViewportPoint -> viewportToWorldPoint should be accurate + const worldPoint = new Vector3(105, 210, 0); + const viewportPoint = scaledCamera.worldToViewportPoint(worldPoint, new Vector3()); + const recoveredPoint = scaledCamera.viewportToWorldPoint(viewportPoint, new Vector3()); + expect(recoveredPoint.x).to.be.closeTo(worldPoint.x, 0.01); + expect(recoveredPoint.y).to.be.closeTo(worldPoint.y, 0.01); + expect(recoveredPoint.z).to.be.closeTo(worldPoint.z, 0.01); + + // Clean up + scaledCamera.destroy(); + refCamera.destroy(); + parentEntity.destroy(); + refEntity.destroy(); + }); + /* Attention: - Below methods will change the default view of current Camera. - If executed in advance, it will affect the expected results of other test cases, + Below methods will change the default view of current Camera. + If executed in advance, it will affect the expected results of other test cases, so it should be placed at the end of the test case execution. */ it("projection matrix", () => { diff --git a/tests/src/core/CloneUtils.test.ts b/tests/src/core/CloneUtils.test.ts index 7d7195f323..80ffbe27dd 100644 --- a/tests/src/core/CloneUtils.test.ts +++ b/tests/src/core/CloneUtils.test.ts @@ -87,6 +87,26 @@ class NestedObjectScript extends Script { config: { target: Entity; label: string } = { target: null, label: "" }; } +/** Script with UNDECORATED array of entities (no @deepClone) */ +class UndecoratedArrayScript extends Script { + entities: Entity[] = []; +} + +/** Script with UNDECORATED nested object containing entity refs */ +class UndecoratedObjectScript extends Script { + config: { target: Entity; label: string } = { target: null, label: "" }; +} + +/** Script with UNDECORATED nested array of arrays containing entities */ +class NestedArrayScript extends Script { + groups: Entity[][] = []; +} + +/** Script with UNDECORATED Map containing entity values */ +class MapRefScript extends Script { + entityMap: Map = new Map(); +} + /** Script for testing multiple same-type components on one entity */ class CounterScript extends Script { value: number = 0; @@ -244,7 +264,9 @@ describe("Clone remap", async () => { expect(clonedScript.speed).eq(42); expect(clonedScript.name2).eq("test"); expect(clonedScript.flag).eq(true); - expect(clonedScript.data).eq(obj); + // Plain objects are now deep cloned (independent copy) for undecorated properties + expect(clonedScript.data).not.eq(obj); + expect((clonedScript.data).x).eq(1); rootEntity.destroy(); }); @@ -873,4 +895,142 @@ describe("Clone remap", async () => { rootEntity.destroy(); }); }); + + describe("Undecorated array auto clone + remap (type inference)", () => { + it("undecorated entity array should create new array and remap elements", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const childA = parent.createChild("childA"); + const childB = parent.createChild("childB"); + const script = parent.addComponent(UndecoratedArrayScript); + script.entities = [childA, childB]; + + const cloned = parent.clone(); + const cs = cloned.getComponent(UndecoratedArrayScript); + + expect(cs.entities).not.eq(script.entities); + expect(cs.entities.length).eq(2); + expect(cs.entities[0]).not.eq(childA); + expect(cs.entities[1]).not.eq(childB); + expect(cs.entities[0]).eq(cloned.children[0]); + expect(cs.entities[1]).eq(cloned.children[1]); + + rootEntity.destroy(); + }); + + it("undecorated entity array with external ref keeps original", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const child = parent.createChild("child"); + const external = rootEntity.createChild("external"); + const script = parent.addComponent(UndecoratedArrayScript); + script.entities = [child, external]; + + const cloned = parent.clone(); + const cs = cloned.getComponent(UndecoratedArrayScript); + + expect(cs.entities[0]).eq(cloned.children[0]); + expect(cs.entities[1]).eq(external); + + rootEntity.destroy(); + }); + + it("undecorated empty array stays empty with independent reference", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const script = parent.addComponent(UndecoratedArrayScript); + script.entities = []; + + const cloned = parent.clone(); + const cs = cloned.getComponent(UndecoratedArrayScript); + + expect(cs.entities).not.eq(script.entities); + expect(cs.entities.length).eq(0); + + rootEntity.destroy(); + }); + }); + + describe("Undecorated nested object auto clone + remap (type inference)", () => { + it("undecorated object with entity ref should deep clone and remap", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const child = parent.createChild("child"); + const script = parent.addComponent(UndecoratedObjectScript); + script.config = { target: child, label: "hello" }; + + const cloned = parent.clone(); + const cs = cloned.getComponent(UndecoratedObjectScript); + + expect(cs.config).not.eq(script.config); + expect(cs.config.label).eq("hello"); + expect(cs.config.target).not.eq(child); + expect(cs.config.target).eq(cloned.children[0]); + + rootEntity.destroy(); + }); + + it("undecorated object with external entity ref keeps original", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const external = rootEntity.createChild("external"); + const script = parent.addComponent(UndecoratedObjectScript); + script.config = { target: external, label: "ext" }; + + const cloned = parent.clone(); + const cs = cloned.getComponent(UndecoratedObjectScript); + + expect(cs.config.target).eq(external); + expect(cs.config.label).eq("ext"); + + rootEntity.destroy(); + }); + }); + + describe("Nested array of arrays with entity refs (type inference)", () => { + it("undecorated nested entity arrays should recursively clone and remap", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const childA = parent.createChild("childA"); + const childB = parent.createChild("childB"); + const childC = parent.createChild("childC"); + const script = parent.addComponent(NestedArrayScript); + script.groups = [[childA, childB], [childC]]; + + const cloned = parent.clone(); + const cs = cloned.getComponent(NestedArrayScript); + + expect(cs.groups).not.eq(script.groups); + expect(cs.groups.length).eq(2); + expect(cs.groups[0]).not.eq(script.groups[0]); + expect(cs.groups[1]).not.eq(script.groups[1]); + expect(cs.groups[0][0]).eq(cloned.children[0]); + expect(cs.groups[0][1]).eq(cloned.children[1]); + expect(cs.groups[1][0]).eq(cloned.children[2]); + + rootEntity.destroy(); + }); + }); + + describe("Map with entity values (type inference)", () => { + it("undecorated Map should create new Map and remap entity values", () => { + const rootEntity = scene.createRootEntity("root"); + const parent = rootEntity.createChild("parent"); + const child = parent.createChild("child"); + const external = rootEntity.createChild("external"); + const script = parent.addComponent(MapRefScript); + script.entityMap.set("internal", child); + script.entityMap.set("external", external); + + const cloned = parent.clone(); + const cs = cloned.getComponent(MapRefScript); + + expect(cs.entityMap).not.eq(script.entityMap); + expect(cs.entityMap.size).eq(2); + expect(cs.entityMap.get("internal")).eq(cloned.children[0]); + expect(cs.entityMap.get("external")).eq(external); + + rootEntity.destroy(); + }); + }); }); diff --git a/tests/src/core/Entity.test.ts b/tests/src/core/Entity.test.ts index 2fe8f908ad..212ee84286 100644 --- a/tests/src/core/Entity.test.ts +++ b/tests/src/core/Entity.test.ts @@ -320,6 +320,19 @@ describe("Entity", async () => { expect(parent.findByPath("child/grandson")).eq(grandson2); }); + it("findByPath accepts self-name prefix", () => { + const parent = new Entity(engine, "parent"); + parent.parent = scene.getRootEntity(); + const child = new Entity(engine, "child"); + child.parent = parent; + const grandson = new Entity(engine, "grandson"); + grandson.parent = child; + + expect(parent.findByPath("parent")).eq(parent); + expect(parent.findByPath("parent/child")).eq(child); + expect(parent.findByPath("parent/child/grandson")).eq(grandson); + }); + it("clearChildren", () => { const parent = new Entity(engine, "parent"); @@ -328,8 +341,29 @@ describe("Entity", async () => { child.parent = parent; const child2 = new Entity(engine, "child2"); child2.parent = parent; + + const parentModifyCount = [0, 0, 0]; + const childModifyCount = [0, 0, 0]; + const child2ModifyCount = [0, 0, 0]; + // @ts-ignore + parent._registerModifyListener((flag: EntityModifyFlags) => ++parentModifyCount[flag]); + // @ts-ignore + child._registerModifyListener((flag: EntityModifyFlags) => ++childModifyCount[flag]); + // @ts-ignore + child2._registerModifyListener((flag: EntityModifyFlags) => ++child2ModifyCount[flag]); + parent.clearChildren(); expect(parent.children.length).eq(0); + + // Parent should receive a single `Child` modify event for the whole clear so + // listeners (e.g. UICanvas) can invalidate their cached state. + expect(parentModifyCount[EntityModifyFlags.Child]).eq(1); + // Each detached child should receive a `Parent` modify event. + expect(childModifyCount[EntityModifyFlags.Parent]).eq(1); + expect(child2ModifyCount[EntityModifyFlags.Parent]).eq(1); + // Sibling index must be reset so the entity is treated as lonely afterwards. + expect(child.siblingIndex).eq(-1); + expect(child2.siblingIndex).eq(-1); }); it("sibling index", () => { const root = scene.createRootEntity(); @@ -681,4 +715,4 @@ describe("Entity", async () => { expect(script.onDestroy).toHaveBeenCalledTimes(1); }); }); -}); \ No newline at end of file +}); diff --git a/tests/src/core/ShaderData.test.ts b/tests/src/core/ShaderData.test.ts new file mode 100644 index 0000000000..6903c67150 --- /dev/null +++ b/tests/src/core/ShaderData.test.ts @@ -0,0 +1,184 @@ +import { CloneManager, ShaderData, ShaderDataGroup, ShaderMacro } from "@galacean/engine-core"; +import { CloneMode } from "@galacean/engine-core/src/clone/enums/CloneMode"; +import { describe, expect, it } from "vitest"; + +describe("ShaderData", () => { + describe("Macro operations", () => { + it("enableMacro and disableMacro", () => { + const shaderData = new ShaderData(ShaderDataGroup.Renderer); + const macro = ShaderMacro.getByName("TEST_ENABLE_DISABLE"); + + shaderData.enableMacro("TEST_ENABLE_DISABLE"); + expect(shaderData._macroCollection.isEnable(macro)).to.be.true; + + const macros = shaderData.getMacros() as ShaderMacro[]; + expect(macros).to.have.lengthOf(1); + expect(macros[0]).to.equal(macro); + + shaderData.disableMacro("TEST_ENABLE_DISABLE"); + expect(shaderData._macroCollection.isEnable(macro)).to.be.false; + expect(shaderData.getMacros() as ShaderMacro[]).to.have.lengthOf(0); + }); + + it("enableMacro with value replaces same-name macro", () => { + const shaderData = new ShaderData(ShaderDataGroup.Renderer); + + shaderData.enableMacro("TEST_VALUE_MACRO", "1"); + const macro1 = ShaderMacro.getByName("TEST_VALUE_MACRO", "1"); + expect(shaderData._macroCollection.isEnable(macro1)).to.be.true; + + shaderData.enableMacro("TEST_VALUE_MACRO", "2"); + const macro2 = ShaderMacro.getByName("TEST_VALUE_MACRO", "2"); + expect(shaderData._macroCollection.isEnable(macro1)).to.be.false; + expect(shaderData._macroCollection.isEnable(macro2)).to.be.true; + expect(shaderData.getMacros() as ShaderMacro[]).to.have.lengthOf(1); + }); + }); + + describe("cloneTo", () => { + it("should produce identical macros in target", () => { + const source = new ShaderData(ShaderDataGroup.Renderer); + source.enableMacro("CLONE_MACRO_A"); + source.enableMacro("CLONE_MACRO_B"); + + const target = new ShaderData(ShaderDataGroup.Renderer); + source.cloneTo(target); + + const macroA = ShaderMacro.getByName("CLONE_MACRO_A"); + const macroB = ShaderMacro.getByName("CLONE_MACRO_B"); + expect(target._macroCollection.isEnable(macroA)).to.be.true; + expect(target._macroCollection.isEnable(macroB)).to.be.true; + + const targetMacros = target.getMacros() as ShaderMacro[]; + expect(targetMacros).to.have.lengthOf(2); + }); + + it("should clear stale macros in target before cloning", () => { + const source = new ShaderData(ShaderDataGroup.Renderer); + source.enableMacro("SOURCE_ONLY_MACRO"); + + const target = new ShaderData(ShaderDataGroup.Renderer); + target.enableMacro("TARGET_STALE_MACRO"); + + source.cloneTo(target); + + const staleMacro = ShaderMacro.getByName("TARGET_STALE_MACRO"); + const sourceMacro = ShaderMacro.getByName("SOURCE_ONLY_MACRO"); + + expect(target._macroCollection.isEnable(staleMacro)).to.be.false; + const targetMacros = target.getMacros() as ShaderMacro[]; + expect(targetMacros).to.have.lengthOf(1); + expect(targetMacros[0]).to.equal(sourceMacro); + + const macroMap = (target as any)._macroMap; + for (const key in macroMap) { + expect(Number(key)).to.equal(macroMap[key]._nameId); + } + }); + + it("should not have duplicate macros with same name under different keys", () => { + const target = new ShaderData(ShaderDataGroup.Renderer); + target.enableMacro("MACRO_X"); + target.enableMacro("MACRO_Y"); + target.enableMacro("MACRO_Z"); + + const source = new ShaderData(ShaderDataGroup.Renderer); + source.enableMacro("MACRO_Y"); + + source.cloneTo(target); + + const targetMacros = target.getMacros() as ShaderMacro[]; + expect(targetMacros).to.have.lengthOf(1); + expect(targetMacros[0].name).to.equal("MACRO_Y"); + + const names = targetMacros.map((m) => m.name); + const uniqueNames = [...new Set(names)]; + expect(names.length).to.equal(uniqueNames.length); + }); + + it("clone() should produce a clean independent copy", () => { + const source = new ShaderData(ShaderDataGroup.Renderer); + source.enableMacro("CLONE_INDEPENDENT_A"); + source.enableMacro("CLONE_INDEPENDENT_B"); + + const cloned = source.clone(); + + const macroA = ShaderMacro.getByName("CLONE_INDEPENDENT_A"); + const macroB = ShaderMacro.getByName("CLONE_INDEPENDENT_B"); + expect(cloned._macroCollection.isEnable(macroA)).to.be.true; + expect(cloned._macroCollection.isEnable(macroB)).to.be.true; + + source.disableMacro("CLONE_INDEPENDENT_A"); + expect(cloned._macroCollection.isEnable(macroA)).to.be.true; + }); + }); + + describe("CloneManager", () => { + it("default cloneMode deep-clones same-constructor objects", () => { + const sourceObj = { name: "B", id: 2 }; + const targetObj = { name: "A", id: 1 }; + const source = { _field: sourceObj }; + const target = { _field: targetObj }; + + CloneManager.cloneProperty(source, target, "_field", undefined, null, null, new Map()); + + expect(target._field).to.equal(targetObj); + expect(targetObj.name).to.equal("B"); + expect(targetObj.id).to.equal(2); + }); + + it("CloneMode.Assignment prevents singleton corruption", () => { + const macroA = ShaderMacro.getByName("SINGLETON_FIX_A"); + const macroB = ShaderMacro.getByName("SINGLETON_FIX_B"); + + const source = { _macro: macroB }; + const target = { _macro: macroA }; + + CloneManager.cloneProperty(source, target, "_macro", CloneMode.Assignment, null, null, new Map()); + + expect(macroA.name).to.equal("SINGLETON_FIX_A"); + expect(macroA._nameId).to.not.equal(macroB._nameId); + expect(target._macro).to.equal(macroB); + }); + + it("should not infinite loop on circular references", () => { + // Simulate AnimatorState ↔ AnimatorStateTransition cycle: + // State has transitions array, each Transition has destinationState pointing back to a State + class FakeState { + transitions: FakeTransition[] = []; + } + class FakeTransition { + destinationState: FakeState = null; + } + + // Build circular graph: stateA → transitionAB → stateB → transitionBA → stateA + const srcStateA = new FakeState(); + const srcStateB = new FakeState(); + const srcTransAB = new FakeTransition(); + const srcTransBA = new FakeTransition(); + srcTransAB.destinationState = srcStateB; + srcTransBA.destinationState = srcStateA; + srcStateA.transitions = [srcTransAB]; + srcStateB.transitions = [srcTransBA]; + + // Target has its own independent state graph + const tgtStateA = new FakeState(); + const tgtStateB = new FakeState(); + const tgtTransAB = new FakeTransition(); + const tgtTransBA = new FakeTransition(); + tgtTransAB.destinationState = tgtStateB; + tgtTransBA.destinationState = tgtStateA; + tgtStateA.transitions = [tgtTransAB]; + tgtStateB.transitions = [tgtTransBA]; + + const source = { state: srcStateA }; + const target = { state: tgtStateA }; + + // This must not hang — should complete within milliseconds + CloneManager.cloneProperty(source, target, "state", CloneMode.Deep, null, null, new Map()); + + // After clone, target's state graph should reflect source's data + expect(target.state.transitions).to.have.lengthOf(1); + }); + }); +}); diff --git a/tests/src/core/Transform.test.ts b/tests/src/core/Transform.test.ts index 58ffe3f4a8..bdc8dbc884 100644 --- a/tests/src/core/Transform.test.ts +++ b/tests/src/core/Transform.test.ts @@ -1,4 +1,4 @@ -import { deepClone, Entity, Scene, Transform } from "@galacean/engine-core"; +import { deepClone, Entity, Scene, Transform, TransformModifyFlags } from "@galacean/engine-core"; import { Vector2, Vector3 } from "@galacean/engine-math"; import { WebGLEngine } from "@galacean/engine-rhi-webgl"; import { beforeAll, describe, expect, it } from "vitest"; @@ -74,6 +74,174 @@ describe("Transform test", function () { expect(parent.transform.instanceId).eq(child.transform._getParentTransform()?.instanceId); }); + it("Reparent propagates world matrix dirty to deep descendants after clone", () => { + // Build source hierarchy: source -> middle -> inner (with local offset) + const source = new Entity(engine, "source"); + const srcMiddle = source.createChild("middle"); + const srcInner = srcMiddle.createChild("inner"); + srcInner.transform.setPosition(10, 20, 30); + + // Clone (equivalent to PrefabResource.instantiate) + const clone = source.clone(); + const cloneMiddle = clone.findByName("middle")!; + const cloneInner = cloneMiddle.findByName("inner")!; + + // Access cloneInner.worldMatrix before adding clone to a positioned parent. + const worldBeforeReparent = cloneInner.transform.worldMatrix; + expect(worldBeforeReparent.elements[12]).to.equal(10); + expect(worldBeforeReparent.elements[13]).to.equal(20); + expect(worldBeforeReparent.elements[14]).to.equal(30); + + // Reparent the clone under a positioned root — same as `table.addChild(levelNode)`. + const root = scene.createRootEntity("reparent-root"); + root.transform.setPosition(1000, 2000, 3000); + root.addChild(clone); + + // cloneInner.worldMatrix must reflect root's offset (deep descendant of moved subtree). + const worldAfterReparent = cloneInner.transform.worldMatrix; + expect(worldAfterReparent.elements[12]).to.equal(1010); + expect(worldAfterReparent.elements[13]).to.equal(2020); + expect(worldAfterReparent.elements[14]).to.equal(3030); + }); + + it("Reparent invalidates descendant world caches even when parent has all world flags set (engine dirty-flag bug)", () => { + // Reproduces a Galacean 2.0-alpha.24 engine bug observed in Screw game: + // Transform._parentChange() calls _updateAllWorldFlag which early-exits + // if `this` already has all target world dirty flags set. This skips + // propagation to descendants. After reparent, a descendant whose + // WorldMatrix flag was previously cleared keeps returning stale cache. + const parent = new Entity(engine, "parent"); + const child = parent.createChild("child"); + child.transform.setPosition(10, 20, 30); + + // 1) Access child.worldMatrix to CLEAR child's WorldMatrix dirty flag. + // The access also clears parent's WorldMatrix flag (chain compute). + const cached = child.transform.worldMatrix; + expect(cached.elements[12]).to.equal(10); + + // 2) Force parent into the failure state: + // "all world dirty flags set" (as if never accessed) — simulates the + // post-clone / lifecycle state where PARENT's world hasn't been read + // but a DESCENDANT's world was. + // @ts-ignore - white-box access for precise engine bug reproduction + parent.transform._dirtyFlag |= TransformModifyFlags.WmWpWeWqWsWus; + + // Sanity: child's WorldMatrix is CLEAR, parent has ALL world flags SET. + // @ts-ignore + expect(child.transform._dirtyFlag & TransformModifyFlags.WorldMatrix).to.equal(0); + // @ts-ignore + expect(parent.transform._dirtyFlag & TransformModifyFlags.WmWpWeWqWsWus).to.equal( + TransformModifyFlags.WmWpWeWqWsWus + ); + + // 3) Reparent `parent` under a positioned root (triggers _parentChange on parent). + const root = scene.createRootEntity("reparent-root"); + root.transform.setPosition(1000, 2000, 3000); + root.addChild(parent); + + // 4) Child's worldMatrix MUST now reflect root's offset. + // Under the bug: early-exit in _updateAllWorldFlag skips propagation → child's + // WorldMatrix flag stays CLEAR → getter returns stale cached (10, 20, 30). + const afterReparent = child.transform.worldMatrix; + expect(afterReparent.elements[12]).to.equal(1010); + expect(afterReparent.elements[13]).to.equal(2020); + expect(afterReparent.elements[14]).to.equal(3030); + }); + + it("Reparent re-resolves descendant parent cache even when cached as null", () => { + // Reproduces the second half of the Galacean 2.0-alpha.24 bug: a descendant + // whose `_parentTransformCache` was resolved to `null` (because + // `_getParentTransform` was called while its ancestor chain was partially + // constructed) keeps returning identity worldMatrix even after the + // ancestor chain is fully wired up. + const parent = new Entity(engine, "parent"); + const child = parent.createChild("child"); + child.transform.setPosition(10, 20, 30); + + // Force child's parent cache to null with `_isParentDirty = false` — + // simulates the state observed in Screw where layer-001 had + // `_parentTransformCache = null, _isParentDirty = false` after clone. + // @ts-ignore + child.transform._parentTransformCache = null; + // @ts-ignore + child.transform._isParentDirty = false; + + // Add parent under a positioned root. If _parentChange on parent fails to + // invalidate child's parent cache, child.worldMatrix returns identity. + const root = scene.createRootEntity("cache-null-root"); + root.transform.setPosition(500, 600, 700); + root.addChild(parent); + + const after = child.transform.worldMatrix; + expect(after.elements[12]).to.equal(510); // 500 + 10 + expect(after.elements[13]).to.equal(620); // 600 + 20 + expect(after.elements[14]).to.equal(730); // 700 + 30 + }); + + it("_cloneTo invalidates world cache cleared by other components' ctor reads", () => { + // Reproduces the Transform._cloneTo dirty-flag bug surfaced by the + // billiard-aim-line fix (see notes/3D台球游戏联机版/2026-05-15-billiard-aim-line-kinematic-pair.md). + // + // The cloneComponent sequence calls Component._cloneTo for each component + // in order. Components added BEFORE Transform on a cloned entity may read + // `entity.transform.worldPosition` in their ctor (DynamicCollider does this + // to seed the native PxRigidDynamic pose) — that getter clears WorldPosition + // & WorldMatrix dirty flags as a side effect of caching the computed value. + // + // When Transform._cloneTo later writes new local values, those world-derived + // caches are stale (they still hold the pre-clone defaults). The fix re-dirties + // & dispatches the world flag set so subsequent reads recompute correctly. + const source = new Entity(engine, "source"); + source.transform.setPosition(1, 2, 3); + + // Target entity in pre-_cloneTo state: default transform, but a "Component ctor" + // already queried worldPosition (simulates DynamicCollider native ctor side effect). + const target = new Entity(engine, "target"); + const beforeClone = target.transform.worldPosition; + expect(beforeClone.x).to.equal(0); + // White-box: WorldPosition dirty flag was cleared by the getter call above. + // @ts-ignore + expect(target.transform._dirtyFlag & TransformModifyFlags.WorldPosition).to.equal(0); + + // Exercise _cloneTo (the same call the cloneComponent path makes). + // @ts-ignore - internal API + source.transform._cloneTo(target.transform); + + // After _cloneTo, target.worldPosition must reflect cloned (1,2,3), not stale (0,0,0). + const afterClone = target.transform.worldPosition; + expect(afterClone.x).to.equal(1); + expect(afterClone.y).to.equal(2); + expect(afterClone.z).to.equal(3); + }); + + it("_cloneTo dispatches world-flag change to registered listeners (Collider._updateFlag fires)", () => { + // Reproduces the listener-side of the same bug. Collider._updateFlag is a + // BoolUpdateFlag registered via entity.registerWorldChangeFlag(); Collider._onUpdate + // checks it to decide whether to push transform to the native physics actor. + // + // If Transform._cloneTo only re-dirties local flags but doesn't dispatch the + // world flag set, the listener stays `false` after clone → Collider never + // re-syncs the native actor pose → physics state mismatch (Bug A / Joint clone + // box2 flies at 299 m/s). + const source = new Entity(engine, "source"); + source.transform.setPosition(7, 8, 9); + + const target = new Entity(engine, "target"); + // Simulate Component ctor: register a world-change listener BEFORE _cloneTo. + const updateFlag = target.registerWorldChangeFlag(); + // Then read worldPosition (mirrors DynamicCollider native ctor) — this clears + // both the dirty flag AND the listener's `flag` if it was true. + target.transform.worldPosition; + updateFlag.flag = false; + + // @ts-ignore - internal API + source.transform._cloneTo(target.transform); + + // The listener MUST observe the change; otherwise downstream consumers + // (Collider._onUpdate, animation rigs, etc.) silently miss the clone update. + expect(updateFlag.flag).to.equal(true); + }); + it("Subclasses of Transform", () => { // Create by constructor const entity0 = new Entity(engine, "entity"); diff --git a/tests/src/core/audio/AudioSourcePendingPlayback.test.ts b/tests/src/core/audio/AudioSourcePendingPlayback.test.ts new file mode 100644 index 0000000000..5e7e4645a1 --- /dev/null +++ b/tests/src/core/audio/AudioSourcePendingPlayback.test.ts @@ -0,0 +1,358 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { AudioManager, AudioSource } from "@galacean/engine-core"; + +class MockGainNode { + gain = { + setValueAtTime: vi.fn() + }; + + connect = vi.fn(); +} + +class MockBufferSourceNode { + buffer: unknown = null; + loop = false; + onended: (() => void) | null = null; + playbackRate = { + value: 1 + }; + + connect = vi.fn(); + disconnect = vi.fn(); + start = vi.fn(); + stop = vi.fn(); +} + +class MockAudioContext { + static shouldResumeSucceed = true; + static resumeResultQueue: Array | Error> | null = null; + + currentTime = 0; + destination = {}; + onstatechange: (() => void) | null = null; + state: AudioContextState = "suspended"; + + createBufferSource(): AudioBufferSourceNode { + return new MockBufferSourceNode() as unknown as AudioBufferSourceNode; + } + + createGain(): GainNode { + return new MockGainNode() as unknown as GainNode; + } + + resume(): Promise { + const queuedResult = MockAudioContext.resumeResultQueue?.shift(); + if (queuedResult instanceof Promise) { + return queuedResult; + } + if (queuedResult instanceof Error) { + return Promise.reject(queuedResult); + } + if (!MockAudioContext.shouldResumeSucceed) { + return Promise.reject(new Error("autoplay blocked")); + } + this.state = "running"; + this.onstatechange?.(); + return Promise.resolve(); + } + + suspend(): Promise { + this.state = "suspended"; + this.onstatechange?.(); + return Promise.resolve(); + } +} + +async function flushAsync(): Promise { + await Promise.resolve(); + await Promise.resolve(); +} + +function createAudioSource(): AudioSource { + const audioSource = new AudioSource({ + _isActiveInHierarchy: true, + _isActiveInScene: true, + _removeComponent() {}, + engine: {} + } as any); + + audioSource.clip = { + _addReferCount() {}, + _getAudioSource() { + return {}; + } + } as any; + + return audioSource; +} + +describe("AudioSource pending playback", () => { + beforeEach(() => { + (window as any).AudioContext = MockAudioContext; + (AudioManager as any)._context = null; + (AudioManager as any)._gainNode = null; + (AudioManager as any)._needsUserGestureResume = false; + (AudioManager as any)._pendingSources = new Set(); + (AudioManager as any)._playingSources = new Set(); + (AudioManager as any)._interruptedSources = new Set(); + (AudioManager as any)._foregroundRestoreTimer = undefined; + (AudioManager as any)._hidden = false; + MockAudioContext.shouldResumeSucceed = true; + MockAudioContext.resumeResultQueue = null; + AudioManager._playingCount = 0; + }); + + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + document.replaceChildren(); + }); + + it("replays pending playback on the next user gesture after autoplay blocking", async () => { + const audioSource = createAudioSource(); + + vi.spyOn(console, "warn").mockImplementation(() => {}); + MockAudioContext.shouldResumeSucceed = false; + + audioSource.play(); + await flushAsync(); + + expect((audioSource as any)._pendingPlay).to.be.true; + expect((AudioManager as any)._pendingSources.size).to.equal(1); + expect((AudioManager as any)._needsUserGestureResume).to.be.false; + expect(audioSource.isPlaying).to.be.false; + + MockAudioContext.shouldResumeSucceed = true; + document.dispatchEvent(new Event("click")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.true; + expect((audioSource as any)._pendingPlay).to.be.false; + expect((AudioManager as any)._pendingSources.size).to.equal(0); + expect((AudioManager as any)._needsUserGestureResume).to.be.false; + }); + + it("cancels pending playback before the unlocking gesture arrives", async () => { + const audioSource = createAudioSource(); + + vi.spyOn(console, "warn").mockImplementation(() => {}); + MockAudioContext.shouldResumeSucceed = false; + + audioSource.play(); + await flushAsync(); + + audioSource.stop(); + expect((audioSource as any)._pendingPlay).to.be.false; + expect((AudioManager as any)._pendingSources.size).to.equal(0); + + MockAudioContext.shouldResumeSucceed = true; + document.dispatchEvent(new Event("click")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.false; + expect((audioSource as any)._pendingPlay).to.be.false; + }); + + it("keeps resume a no-op until a context already exists", async () => { + expect((AudioManager as any)._context).to.be.null; + + await AudioManager.resume(); + + expect((AudioManager as any)._context).to.be.null; + }); + + it("does not resume foreground audio before a hide event", async () => { + createAudioSource(); + const context = (AudioManager as any)._context as MockAudioContext; + + vi.spyOn(document, "hidden", "get").mockReturnValue(false); + const resumeSpy = vi.spyOn(context, "resume"); + const suspendSpy = vi.spyOn(AudioManager, "suspend"); + + context.state = "suspended"; + AudioManager._playingCount = 1; + + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + expect(resumeSpy).not.toHaveBeenCalled(); + expect(suspendSpy).not.toHaveBeenCalled(); + expect((AudioManager as any)._needsUserGestureResume).to.be.false; + }); + + it("recreates interrupted source nodes from a foreground gesture", async () => { + const audioSource = createAudioSource(); + const context = (AudioManager as any)._context as MockAudioContext; + + context.state = "running"; + audioSource.play(); + + const firstSourceNode = (audioSource as any)._sourceNode as MockBufferSourceNode; + expect(audioSource.isPlaying).to.be.true; + expect(AudioManager._playingCount).to.equal(1); + + const hiddenSpy = vi.spyOn(document, "hidden", "get").mockReturnValue(true); + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + expect(firstSourceNode.stop).toHaveBeenCalledTimes(1); + expect(audioSource.isPlaying).to.be.false; + expect(AudioManager._playingCount).to.equal(0); + expect((AudioManager as any)._interruptedSources.size).to.equal(1); + + hiddenSpy.mockReturnValue(false); + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.false; + expect((AudioManager as any)._interruptedSources.size).to.equal(1); + expect((AudioManager as any)._needsUserGestureResume).to.be.true; + + document.dispatchEvent(new Event("touchend")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.true; + expect(AudioManager._playingCount).to.equal(1); + expect((AudioManager as any)._interruptedSources.size).to.equal(0); + expect((audioSource as any)._sourceNode).not.to.equal(firstSourceNode); + }); + + it("recovers interrupted source nodes from foreground retry after the restore delay", async () => { + vi.useFakeTimers(); + const audioSource = createAudioSource(); + const context = (AudioManager as any)._context as MockAudioContext; + + context.state = "running"; + audioSource.play(); + + const hiddenSpy = vi.spyOn(document, "hidden", "get").mockReturnValue(true); + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + hiddenSpy.mockReturnValue(false); + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.false; + + await vi.advanceTimersByTimeAsync(299); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.false; + + await vi.advanceTimersByTimeAsync(1); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.true; + expect((AudioManager as any)._interruptedSources.size).to.equal(0); + }); + + it("handles document pagehide/pageshow and mouseup recovery", async () => { + const audioSource = createAudioSource(); + const context = (AudioManager as any)._context as MockAudioContext; + + context.state = "running"; + audioSource.play(); + + document.dispatchEvent(new Event("pagehide")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.false; + expect((AudioManager as any)._interruptedSources.size).to.equal(1); + + document.dispatchEvent(new Event("pageshow")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.false; + expect((AudioManager as any)._needsUserGestureResume).to.be.true; + + document.dispatchEvent(new Event("mouseup")); + await flushAsync(); + + expect(audioSource.isPlaying).to.be.true; + expect((AudioManager as any)._interruptedSources.size).to.equal(0); + }); + + it("keeps gesture recovery when foreground resume fails", async () => { + vi.useFakeTimers(); + const audioSource = createAudioSource(); + const context = (AudioManager as any)._context as MockAudioContext; + + vi.spyOn(console, "warn").mockImplementation(() => {}); + const hiddenSpy = vi.spyOn(document, "hidden", "get").mockReturnValue(true); + const resumeSpy = vi.spyOn(context, "resume"); + const suspendSpy = vi.spyOn(AudioManager, "suspend"); + + context.state = "running"; + audioSource.play(); + + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + MockAudioContext.shouldResumeSucceed = false; + hiddenSpy.mockReturnValue(false); + document.dispatchEvent(new Event("visibilitychange")); + await flushAsync(); + + expect(resumeSpy).not.toHaveBeenCalled(); + expect(suspendSpy).toHaveBeenCalledTimes(2); + expect((AudioManager as any)._needsUserGestureResume).to.be.true; + + await vi.advanceTimersByTimeAsync(299); + await flushAsync(); + + expect(resumeSpy).not.toHaveBeenCalled(); + expect(suspendSpy).toHaveBeenCalledTimes(2); + expect((AudioManager as any)._needsUserGestureResume).to.be.true; + + await vi.advanceTimersByTimeAsync(1); + await flushAsync(); + + expect(resumeSpy).toHaveBeenCalledTimes(1); + expect(suspendSpy).toHaveBeenCalledTimes(3); + expect((AudioManager as any)._needsUserGestureResume).to.be.true; + + document.dispatchEvent(new Event("click")); + await flushAsync(); + + expect(resumeSpy).toHaveBeenCalledTimes(2); + expect((AudioManager as any)._needsUserGestureResume).to.be.true; + + MockAudioContext.shouldResumeSucceed = true; + document.dispatchEvent(new Event("click")); + await flushAsync(); + + expect(resumeSpy).toHaveBeenCalledTimes(3); + expect(context.state).to.equal("running"); + expect((AudioManager as any)._needsUserGestureResume).to.be.false; + }); + + it("retries context.resume inside a later user gesture even if an earlier resume is still pending", async () => { + createAudioSource(); + const context = (AudioManager as any)._context as MockAudioContext; + const firstResume = new Promise(() => {}); + + MockAudioContext.resumeResultQueue = [firstResume]; + const resumeSpy = vi.spyOn(context, "resume"); + + AudioManager.resume().catch(() => {}); + await flushAsync(); + + expect(resumeSpy).toHaveBeenCalledTimes(1); + + MockAudioContext.resumeResultQueue = [ + Promise.resolve().then(() => { + context.state = "running"; + context.onstatechange?.(); + }) + ]; + (AudioManager as any)._needsUserGestureResume = true; + + document.dispatchEvent(new Event("click")); + await flushAsync(); + + expect(resumeSpy).toHaveBeenCalledTimes(2); + expect(context.state).to.equal("running"); + expect((AudioManager as any)._needsUserGestureResume).to.be.false; + }); +}); diff --git a/tests/src/core/particle/ParticleStopResume.test.ts b/tests/src/core/particle/ParticleStopResume.test.ts new file mode 100644 index 0000000000..0e0db6ac05 --- /dev/null +++ b/tests/src/core/particle/ParticleStopResume.test.ts @@ -0,0 +1,142 @@ +import { + Burst, + Camera, + ParticleCompositeCurve, + ParticleRenderer, + ParticleStopMode, + Scene +} from "@galacean/engine-core"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { beforeAll, describe, expect, it } from "vitest"; + +describe("ParticleGenerator stop/resume timeline", () => { + let engine: WebGLEngine; + let scene: Scene; + + beforeAll(async () => { + engine = await WebGLEngine.create({ canvas: document.createElement("canvas") }); + scene = engine.sceneManager.activeScene; + const root = scene.createRootEntity("root"); + const camera = root.createChild("Camera"); + camera.addComponent(Camera); + camera.transform.setPosition(0, 0, 10); + }); + + /** + * Drive `generator._update(dt)` directly so we control the timeline. + * `ParticleRenderer._update` would call this with `engine.time.deltaTime`, + * which is wall-clock and not reproducible. + */ + function tick(generator: any, frames: number, dt: number): void { + for (let i = 0; i < frames; i++) { + generator._update(dt); + } + } + + it("rate-over-time: stop -> idle -> play emits a catch-up batch in one frame", () => { + const entity = scene.createRootEntity("rate"); + const renderer = entity.addComponent(ParticleRenderer); + const generator = renderer.generator; + generator.useAutoRandomSeed = false; + generator.main.duration = 1; + generator.main.startLifetime.constant = 1; + generator.main.maxParticles = 10000; + generator.emission.rateOverTime.constant = 100; + + let totalEmitted = 0; + const origEmit = (generator as any)._emit.bind(generator); + (generator as any)._emit = (playTime: number, count: number) => { + totalEmitted += count; + origEmit(playTime, count); + }; + + generator.play(); + // Run 0.5s at 60fps -> ~50 particles + tick(generator, 30, 1 / 60); + const emittedDuringPlay = totalEmitted; + const playTimeAfterPlay = generator._playTime; + + generator.stop(true, ParticleStopMode.StopEmitting); + const playTimeAtStop = generator._playTime; + const emittedAtStop = totalEmitted; + + // Idle 4.5s while stopped -> _emit must not run, but _playTime drifts + tick(generator, 270, 1 / 60); + const playTimeAfterIdle = generator._playTime; + const emittedDuringIdle = totalEmitted - emittedAtStop; + + generator.play(); + // Single frame after resume + tick(generator, 1, 1 / 60); + const emittedFirstFrameAfterResume = totalEmitted - emittedAtStop; + + entity.destroy(); + + // eslint-disable-next-line no-console + console.log("[bug-repro/rate]", { + emittedDuringPlay, + playTimeAfterPlay, + playTimeAtStop, + playTimeAfterIdle, + emittedDuringIdle, + emittedFirstFrameAfterResume + }); + + expect(emittedDuringIdle).toBe(0); + // Buggy behavior: emits a large catch-up batch (~ idleSeconds * rate). + expect(emittedFirstFrameAfterResume).toBeGreaterThan(100); + expect(playTimeAfterIdle - playTimeAtStop).toBeGreaterThan(4); + }); + + it("burst: stop -> idle -> play replays multiple cycles of bursts", () => { + const entity = scene.createRootEntity("burst"); + const renderer = entity.addComponent(ParticleRenderer); + const generator = renderer.generator; + generator.useAutoRandomSeed = false; + generator.main.duration = 1; + generator.main.isLoop = true; + generator.main.startLifetime.constant = 1; + generator.main.maxParticles = 10000; + generator.emission.rateOverTime.constant = 0; + generator.emission.addBurst(new Burst(0, new ParticleCompositeCurve(10))); + + let totalEmitted = 0; + const origEmit = (generator as any)._emit.bind(generator); + (generator as any)._emit = (playTime: number, count: number) => { + totalEmitted += count; + origEmit(playTime, count); + }; + + generator.play(); + // 1 full cycle -> burst at t=0 fires once (10 particles at frame 0) + tick(generator, 60, 1 / 60); + const emittedAfterOneSecond = totalEmitted; + + generator.stop(true, ParticleStopMode.StopEmitting); + const playTimeAtStop = generator._playTime; + const emittedAtStop = totalEmitted; + + // Idle 4 cycles + tick(generator, 240, 1 / 60); + const playTimeAfterIdle = generator._playTime; + + generator.play(); + tick(generator, 1, 1 / 60); // first frame after resume + const emittedFirstFrameAfterResume = totalEmitted - emittedAtStop; + + entity.destroy(); + + // eslint-disable-next-line no-console + console.log("[bug-repro/burst]", { + emittedAfterOneSecond, + playTimeAtStop, + playTimeAfterIdle, + emittedFirstFrameAfterResume + }); + + // After fix this should be 0 (next burst at t=0 of the new cycle hasn't reached yet). + // Buggy behavior: replays the burst once because `_currentBurstIndex` is 0 and + // `_emitBySubBurst(lastPlayTime, playTime, ...)` sees burst.time === startTime. + expect(emittedFirstFrameAfterResume).toBe(10); + }); +}); diff --git a/tests/src/core/particle/RateOverDistance.test.ts b/tests/src/core/particle/RateOverDistance.test.ts new file mode 100644 index 0000000000..483892f109 --- /dev/null +++ b/tests/src/core/particle/RateOverDistance.test.ts @@ -0,0 +1,326 @@ +import { + Camera, + Engine, + Entity, + ParticleMaterial, + ParticleRenderer, + ParticleSimulationSpace, + ParticleStopMode +} from "@galacean/engine-core"; +import { Color, Vector3 } from "@galacean/engine-math"; +import { WebGLEngine } from "@galacean/engine"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; + +function tick(engine: Engine, times: { value: number }, deltaMs: number = 100): void { + //@ts-ignore + engine._vSyncCount = Infinity; + //@ts-ignore + engine._time._lastSystemTime = 0; + performance.now = function () { + times.value += deltaMs; + return times.value; + }; + engine.update(); +} + +function buildEmitter(engine: Engine, name: string): { entity: Entity; renderer: ParticleRenderer } { + const scene = engine.sceneManager.activeScene; + const entity = scene.createRootEntity(name); + const renderer = entity.addComponent(ParticleRenderer); + const material = new ParticleMaterial(engine); + material.baseColor = new Color(1, 1, 1, 1); + renderer.setMaterial(material); + + const generator = renderer.generator; + generator.useAutoRandomSeed = false; + generator.main.duration = 5; + generator.main.isLoop = false; + generator.main.maxParticles = 1000; + generator.main.startLifetime.constant = 10; + // Zero out the time-based path so only rateOverDistance contributes. + generator.emission.rateOverTime.constant = 0; + return { entity, renderer }; +} + +describe("EmissionModule rateOverDistance", () => { + let engine: Engine; + let elapsed: { value: number }; + + beforeAll(async function () { + engine = await WebGLEngine.create({ + canvas: document.createElement("canvas") + }); + + const scene = engine.sceneManager.activeScene; + const cameraEntity = scene.createRootEntity("Camera"); + cameraEntity.addComponent(Camera); + cameraEntity.transform.setPosition(0, 0, 10); + + engine.run(); + elapsed = { value: 0 }; + }); + + afterAll(function () { + engine.destroy(); + }); + + it("defaults to 0 (no emission triggered by movement)", () => { + const { entity, renderer } = buildEmitter(engine, "default-zero"); + const generator = renderer.generator; + expect(generator.emission.rateOverDistance.evaluate(undefined, undefined)).to.eq(0); + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + + tick(engine, elapsed); + entity.transform.setPosition(10, 0, 0); + tick(engine, elapsed); + + expect(generator._getAliveParticleCount()).to.eq(0); + entity.destroy(); + }); + + it("emits ratePerUnit × distance particles", () => { + const { entity, renderer } = buildEmitter(engine, "rate-times-distance"); + const generator = renderer.generator; + generator.emission.rateOverDistance.constant = 10; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + + // First tick syncs the baseline position. + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(0); + + // Move 2 units → 10 * 2 = 20 particles. + entity.transform.setPosition(2, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(20); + + entity.destroy(); + }); + + it("accumulates sub-interval movement across frames", () => { + const { entity, renderer } = buildEmitter(engine, "subinterval-carry"); + const generator = renderer.generator; + // 1 particle per 0.5 units → 2 particles per unit. + generator.emission.rateOverDistance.constant = 2; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + tick(engine, elapsed); // baseline sync + + // 3 moves of 0.3 units each = 0.9 total. With interval 0.5, that's + // exactly 1 emission (at 0.5) plus 0.4 carried forward. + entity.transform.setPosition(0.3, 0, 0); + tick(engine, elapsed); + entity.transform.setPosition(0.6, 0, 0); + tick(engine, elapsed); + entity.transform.setPosition(0.9, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(1); + + // Move another 0.6 units → accumulator hits 1.0, then 0 carried fwd. Emit 2 more. + entity.transform.setPosition(1.5, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(3); + + entity.destroy(); + }); + + it("static emitter never emits via distance", () => { + const { entity, renderer } = buildEmitter(engine, "static-emitter"); + const generator = renderer.generator; + generator.emission.rateOverDistance.constant = 100; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + tick(engine, elapsed); + tick(engine, elapsed); + tick(engine, elapsed); + + expect(generator._getAliveParticleCount()).to.eq(0); + entity.destroy(); + }); + + it("stop+clear resets the distance accumulator", () => { + const { entity, renderer } = buildEmitter(engine, "reset-on-clear"); + const generator = renderer.generator; + generator.emission.rateOverDistance.constant = 10; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + tick(engine, elapsed); + entity.transform.setPosition(0.5, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(5); + + // After clear+play, the next tick must re-sync from the *current* position, + // not diff against the pre-clear baseline — so jumping back to origin + // should not emit anything until the next move. + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + entity.transform.setPosition(0, 0, 0); + generator.play(); + tick(engine, elapsed); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(0); + + entity.destroy(); + }); + + it("distributes particles along the movement path in World space", () => { + const { entity, renderer } = buildEmitter(engine, "world-space-distribution"); + const generator = renderer.generator; + generator.main.simulationSpace = ParticleSimulationSpace.World; + // 1 particle per unit; move 4 units → 4 particles spaced at world x = 1, 2, 3, 4 + generator.emission.rateOverDistance.constant = 1; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + + tick(engine, elapsed); // baseline sync at (0,0,0) + + entity.transform.setPosition(4, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(4); + + // Verify each particle's stored world position is along [0,4] on x axis, not all at x=4. + // Particles are written sequentially starting at firstActiveElement=0. + //@ts-ignore - test reaches into instance buffer to verify spatial distribution + const verts = (generator as any)._instanceVertices as Float32Array; + // Per-instance stride = 168 bytes / 4 = 42 floats; world position lives at offset 27. + const stride = 42; + const xs: number[] = []; + for (let i = 0; i < 4; i++) { + xs.push(verts[i * stride + 27]); + } + xs.sort((a, b) => a - b); + // Expect roughly [1, 2, 3, 4] — accept loose tolerance for float ops. + expect(xs[0]).to.be.closeTo(1, 1e-4); + expect(xs[1]).to.be.closeTo(2, 1e-4); + expect(xs[2]).to.be.closeTo(3, 1e-4); + expect(xs[3]).to.be.closeTo(4, 1e-4); + + entity.destroy(); + }); + + it("distributes emit time along the movement path in World space", () => { + const { entity, renderer } = buildEmitter(engine, "world-space-time-distribution"); + const generator = renderer.generator; + generator.main.simulationSpace = ParticleSimulationSpace.World; + // 1 particle per unit; move 4 units across one 100ms tick → 4 particles, + // each born at a sub-frame offset. + generator.emission.rateOverDistance.constant = 1; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + + tick(engine, elapsed); // baseline sync at (0,0,0) + entity.transform.setPosition(4, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(4); + + //@ts-ignore - reach into instance buffer to read per-particle emit time + const verts = (generator as any)._instanceVertices as Float32Array; + const stride = 42; + // a_DirectionTime is at byte 16 → float 4; the .w slot (emit time) is float 4+3=7. + const timeFloatOffset = 7; + const times: number[] = []; + for (let i = 0; i < 4; i++) { + times.push(verts[i * stride + timeFloatOffset]); + } + times.sort((a, b) => a - b); + + // The 4 emit times must form an arithmetic sequence (constant step = sStep * dt), + // and they must not all be equal — that's exactly the bug we're guarding against, + // where COL / SOL / FOL would otherwise render them as a uniform stamp. + const diff0 = times[1] - times[0]; + const diff1 = times[2] - times[1]; + const diff2 = times[3] - times[2]; + expect(diff0).to.be.greaterThan(1e-4); + expect(diff1).to.be.closeTo(diff0, 1e-4); + expect(diff2).to.be.closeTo(diff0, 1e-4); + + entity.destroy(); + }); + + it("clamps count and discards accumulator on teleport-sized moves", () => { + const { entity, renderer } = buildEmitter(engine, "teleport-clamp"); + const generator = renderer.generator; + generator.main.maxParticles = 50; + // Rate 10/unit × 10000 unit jump would otherwise demand 100,000 emissions + // in one frame — millions of `_addNewParticle` calls hitting the buffer-full + // early return. + generator.emission.rateOverDistance.constant = 10; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + tick(engine, elapsed); // baseline sync at (0,0,0) + + entity.transform.setPosition(10000, 0, 0); // teleport + tick(engine, elapsed); + + // Alive count must not exceed the configured cap. + expect(generator._getAliveParticleCount()).to.be.lessThanOrEqual(50); + + // Next frame without movement: accumulator should have been reset to 0 + // (residue dropped), so no further emission. + const aliveAfterTeleport = generator._getAliveParticleCount(); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(aliveAfterTeleport); + + entity.destroy(); + }); + + it("does not burst on play() after emitter moves while stopped", () => { + const { entity, renderer } = buildEmitter(engine, "no-burst-on-replay"); + const generator = renderer.generator; + generator.emission.rateOverDistance.constant = 10; + + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + tick(engine, elapsed); // baseline sync at (0,0,0) + + // Stop *without* clear — accumulator/baseline retained by the old impl. + generator.stop(true, ParticleStopMode.StopEmitting); + // Emitter teleports a few units while stopped (kept inside the camera frustum + // so renderer culling doesn't mask the test). + entity.transform.setPosition(3, 0, 0); + generator.play(); + + // First tick after play() must resync the baseline at the new position, + // not diff (3 - 0) and dump 30 particles in one shot. + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(0); + + // Subsequent movement still emits normally — diffed against the resynced baseline. + entity.transform.setPosition(4, 0, 0); + tick(engine, elapsed); + expect(generator._getAliveParticleCount()).to.eq(10); + + entity.destroy(); + }); + + it("returns actual emitted count when buffer is full", () => { + // Documents the `_emit` return-value contract that lets distance emission detect + // mid-loop buffer exhaustion (and drop residual accumulator to avoid spinning + // through millions of no-op iterations on a teleport). + const { entity, renderer } = buildEmitter(engine, "emit-returns-actual"); + const generator = renderer.generator; + generator.main.maxParticles = 10; + generator.stop(true, ParticleStopMode.StopEmittingAndClear); + generator.play(); + tick(engine, elapsed); + + // Request 100, buffer only has 10 slots. + //@ts-ignore — reach into the internal _emit contract + const emitted = generator._emit(generator._playTime, 100); + expect(emitted).to.eq(10); + + // Subsequent request returns 0 — buffer is saturated. + //@ts-ignore + expect(generator._emit(generator._playTime, 5)).to.eq(0); + + entity.destroy(); + }); +}); diff --git a/tests/src/core/physics/Collision.test.ts b/tests/src/core/physics/Collision.test.ts index 870bc546bc..6f7e886df3 100644 --- a/tests/src/core/physics/Collision.test.ts +++ b/tests/src/core/physics/Collision.test.ts @@ -1,4 +1,13 @@ -import { BoxColliderShape, DynamicCollider, Entity, Engine, Script, StaticCollider } from "@galacean/engine-core"; +import { + BoxColliderShape, + DynamicCollider, + DynamicColliderConstraints, + Entity, + Engine, + Script, + SphereColliderShape, + StaticCollider +} from "@galacean/engine-core"; import { Vector3 } from "@galacean/engine-math"; import { PhysXPhysics } from "@galacean/engine-physics-physx"; import { WebGLEngine } from "@galacean/engine-rhi-webgl"; @@ -22,6 +31,20 @@ describe("Collision", function () { return boxEntity; } + function addSphere(radius: number, pos: Vector3) { + const sphereEntity = rootEntity.createChild("SphereEntity"); + sphereEntity.transform.setPosition(pos.x, pos.y, pos.z); + + const sphereShape = new SphereColliderShape(); + sphereShape.material.dynamicFriction = 0; + sphereShape.material.staticFriction = 0; + sphereShape.radius = radius; + const sphereCollider = sphereEntity.addComponent(DynamicCollider); + sphereCollider.addShape(sphereShape); + sphereCollider.useGravity = false; + return sphereEntity; + } + function formatValue(value: number) { return Math.round(value * 100000) / 100000; } @@ -164,4 +187,239 @@ describe("Collision", function () { engine.sceneManager.activeScene.physics._update(1); }); }); + + it("reports contact normal from static other shape to dynamic self shape", function () { + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, 0, 0); + const dynamicBox = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(-3, 0, 0)); + const staticBox = addBox(new Vector3(1, 1, 1), StaticCollider, new Vector3(0, 0, 0)); + + return new Promise((done) => { + dynamicBox.addComponent( + class extends Script { + onCollisionEnter(other: Collision): void { + expect(other.shape).toBe(staticBox.getComponent(StaticCollider).shapes[0]); + const contacts = []; + other.getContacts(contacts); + expect(contacts.length).toBeGreaterThan(0); + expect(formatValue(contacts[0].normal.x)).toBe(-1); + + done(); + } + } + ); + + dynamicBox.getComponent(DynamicCollider).applyForce(new Vector3(1000, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + }); + }); + + it("reports billiard hitBall sphere normal from kinematic other to dynamic target self", function () { + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, 0, 0); + const targetBall = addSphere(0.5, new Vector3(0, 0, 0)); + const hitBall = addSphere(0.5, new Vector3(-3, 0, 0)); + const hitCollider = hitBall.getComponent(DynamicCollider); + hitCollider.isKinematic = true; + + return new Promise((done) => { + targetBall.addComponent( + class extends Script { + onCollisionEnter(other: Collision): void { + expect(other.shape).toBe(hitCollider.shapes[0]); + const contacts = []; + other.getContacts(contacts); + expect(contacts.length).toBeGreaterThan(0); + + const contactNormal = contacts[0].normal; + expect(formatValue(contactNormal.x)).toBe(1); + expect(formatValue(contactNormal.y)).toBe(0); + expect(formatValue(contactNormal.z)).toBe(0); + + const hitToTarget = new Vector3(); + Vector3.subtract(targetBall.transform.worldPosition, hitBall.transform.worldPosition, hitToTarget); + hitToTarget.normalize(); + expect(formatValue(Vector3.dot(contactNormal, hitToTarget))).toBe(1); + + const scaledNormal = new Vector3(); + Vector3.scale(contactNormal, 2 * Vector3.dot(hitToTarget, contactNormal), scaledNormal); + const reflected = new Vector3(); + Vector3.subtract(hitToTarget, scaledNormal, reflected); + reflected.normalize(); + expect(formatValue(reflected.x)).toBe(-1); + + done(); + } + } + ); + + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + hitCollider.move(new Vector3(-0.9, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + }); + }); + + // ────────────────────────────────────────────────────────────────────────────── + // Kinematic-pair collision callback (3D billiard aim-line use case). + // PhysX 4.x defaults suppress kineKine + staticKine pairs. SceneBinding already + // sets kineKineFilteringMode = eKEEP and staticKineFilteringMode = eKEEP, and + // filter shader returns eNOTIFY_TOUCH_FOUND/PERSISTS/LOST for all pairs. + // Question: does the kinematic pair actually fire onCollisionEnter when a + // kinematic actor is moved into another actor's volume via setWorldTransform + // (i.e. setGlobalPose teleport, NOT setKinematicTarget)? + // + // Cocos parity expectation: yes — Cocos PhysX backend fires onCollisionEnter + // for kinematic↔dynamic and kinematic↔kinematic pairs on overlap. + // ────────────────────────────────────────────────────────────────────────────── + + function probeKinematicCallback(opts: { + aKine: boolean; + bKine: boolean; + timeoutMs?: number; + }): Promise<{ fired: boolean }> { + return new Promise((resolve) => { + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, 0, 0); + const boxA = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(-3, 0, 0)); + const boxB = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(3, 0, 0)); + const colA = boxA.getComponent(DynamicCollider); + const colB = boxB.getComponent(DynamicCollider); + colA.useGravity = false; + colB.useGravity = false; + colA.isKinematic = opts.aKine; + colB.isKinematic = opts.bKine; + + let fired = false; + boxA.addComponent( + class extends Script { + onCollisionEnter(_other: Collision): void { + fired = true; + resolve({ fired: true }); + } + } + ); + + // Step a few frames to let PhysX settle initial state. + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // Teleport B onto A → expect onCollisionEnter. + boxB.transform.setPosition(-3, 0, 0); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + if (!fired) resolve({ fired: false }); + }); + } + + // Probes that the standard transform→PhysX sync path routes correctly for + // kinematic actors. With the fix in PhysXDynamicCollider.setWorldTransform, + // moving a kinematic actor via transform.setPosition() goes through + // setKinematicTarget(), which lets PhysX detect contact and fire the callback. + it("kinematic-kinematic overlap via transform.setPosition fires onCollisionEnter", async function () { + const r = await probeKinematicCallback({ aKine: true, bKine: true }); + expect(r.fired).toBe(true); + }); + + it("kinematic-dynamic overlap via transform.setPosition fires onCollisionEnter", async function () { + const r = await probeKinematicCallback({ aKine: true, bKine: false }); + expect(r.fired).toBe(true); + }); + + it("dynamic-dynamic overlap via transform.setPosition fires onCollisionEnter", async function () { + const r = await probeKinematicCallback({ aKine: false, bKine: false }); + expect(r.fired).toBe(true); + }); + + // Probe whether "dynamic actor + freeze 6 constraints + teleport via setGlobalPose" + // can substitute for a kinematic actor and still trigger contact callbacks. + // This is the proposed fix path for the 3D billiard hitBall: ditch kinematic, + // use a fully-frozen dynamic actor that is moved via setWorldPosition. + it("HYPOTHESIS: kine-kine fires onCollisionEnter when moved via setKinematicTarget (not setGlobalPose)", function () { + return new Promise((resolve, reject) => { + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, 0, 0); + const boxA = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(-3, 0, 0)); + const boxB = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(3, 0, 0)); + const colA = boxA.getComponent(DynamicCollider); + const colB = boxB.getComponent(DynamicCollider); + colA.useGravity = false; + colB.useGravity = false; + colA.isKinematic = true; + colB.isKinematic = true; + + let fired = false; + boxA.addComponent( + class extends Script { + onCollisionEnter(_other: Collision): void { + fired = true; + resolve(); + } + } + ); + + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // Move B onto A via DynamicCollider.move() — this internally calls setKinematicTarget. + colB.move(new Vector3(-3, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + if (!fired) reject(new Error("kine-kine setKinematicTarget did NOT fire onCollisionEnter")); + }); + }); + + it("dynamic + frozen-6 + teleport: overlap fires onCollisionEnter (fix candidate)", function () { + return new Promise((resolve) => { + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, 0, 0); + const boxA = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(-3, 0, 0)); + const boxB = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(3, 0, 0)); + const colA = boxA.getComponent(DynamicCollider); + const colB = boxB.getComponent(DynamicCollider); + // Both fully frozen — emulates the cocos kinematic semantics (no gravity, no movement). + const FREEZE_ALL = + DynamicColliderConstraints.FreezePositionX | + DynamicColliderConstraints.FreezePositionY | + DynamicColliderConstraints.FreezePositionZ | + DynamicColliderConstraints.FreezeRotationX | + DynamicColliderConstraints.FreezeRotationY | + DynamicColliderConstraints.FreezeRotationZ; + colA.constraints = FREEZE_ALL; + colB.constraints = FREEZE_ALL; + colA.useGravity = false; + colB.useGravity = false; + colA.isKinematic = false; + colB.isKinematic = false; + + let fired = false; + boxA.addComponent( + class extends Script { + onCollisionEnter(_other: Collision): void { + fired = true; + resolve(); + } + } + ); + + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + boxB.transform.setPosition(-3, 0, 0); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + if (!fired) { + expect.fail("expected onCollisionEnter to fire for dynamic-frozen pair after teleport"); + } + }); + }); }); diff --git a/tests/src/core/physics/DynamicCollider.test.ts b/tests/src/core/physics/DynamicCollider.test.ts index ae6771d05a..b441f300d4 100644 --- a/tests/src/core/physics/DynamicCollider.test.ts +++ b/tests/src/core/physics/DynamicCollider.test.ts @@ -6,6 +6,7 @@ import { DynamicCollider, DynamicColliderConstraints, CollisionDetectionMode, + DynamicColliderKinematicTransformSyncMode, StaticCollider, PlaneColliderShape } from "@galacean/engine-core"; @@ -278,6 +279,256 @@ describe("DynamicCollider", function () { expect(formatValue(boxCollider.inertiaTensor.y)).eq(1); }); + it("applyForceAtPosition - at center of mass produces only linear acceleration", function () { + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.mass = 1; + boxCollider.useGravity = false; + boxCollider.centerOfMass = new Vector3(0, 0, 0); + boxCollider.inertiaTensor = new Vector3(1, 1, 1); + + boxCollider.applyForceAtPosition(new Vector3(1, 0, 0), new Vector3(0, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(boxCollider.linearVelocity.x)).eq(0.01667); + expect(formatValue(boxCollider.angularVelocity.x)).eq(0); + expect(formatValue(boxCollider.angularVelocity.y)).eq(0); + expect(formatValue(boxCollider.angularVelocity.z)).eq(0); + }); + + it("applyForceAtPosition - offset produces torque = (position - CoM) × force", function () { + // Reference: same physical setup but using applyForce + applyTorque(τ) directly. + // applyForceAtPosition(F, P) must produce the same result. + const setupBox = () => { + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const collider = box.getComponent(DynamicCollider); + collider.mass = 1; + collider.useGravity = false; + collider.centerOfMass = new Vector3(0, 0, 0); + collider.inertiaTensor = new Vector3(1, 1, 1); + return collider; + }; + + const force = new Vector3(1, 0, 0); + const worldPos = new Vector3(0, 1, 0); // r = (0,1,0) - (0,0,0) = (0,1,0); r × F = (0,0,-1) + + const reference = setupBox(); + reference.applyForce(force); + reference.applyTorque(new Vector3(0, 0, -1)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + const refLinear = reference.linearVelocity.clone(); + const refAngular = reference.angularVelocity.clone(); + + rootEntity.clearChildren(); + + const target = setupBox(); + target.applyForceAtPosition(force, worldPos); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(target.linearVelocity.x)).eq(formatValue(refLinear.x)); + expect(formatValue(target.linearVelocity.y)).eq(formatValue(refLinear.y)); + expect(formatValue(target.linearVelocity.z)).eq(formatValue(refLinear.z)); + expect(formatValue(target.angularVelocity.x)).eq(formatValue(refAngular.x)); + expect(formatValue(target.angularVelocity.y)).eq(formatValue(refAngular.y)); + expect(formatValue(target.angularVelocity.z)).eq(formatValue(refAngular.z)); + expect(formatValue(target.angularVelocity.z)).lessThan(0); + }); + + it("applyForceAtPosition - respects centerOfMass offset (no torque when applied at CoM)", function () { + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.mass = 1; + boxCollider.useGravity = false; + // Shift CoM to local (1, 0, 0). With entity at origin and identity rotation, world CoM = (1, 0, 0). + boxCollider.centerOfMass = new Vector3(1, 0, 0); + boxCollider.inertiaTensor = new Vector3(1, 1, 1); + + // Applying force at world (1,0,0) means r = 0 → no torque. + boxCollider.applyForceAtPosition(new Vector3(0, 0, 1), new Vector3(1, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(boxCollider.linearVelocity.z)).eq(0.01667); + expect(formatValue(boxCollider.angularVelocity.x)).eq(0); + expect(formatValue(boxCollider.angularVelocity.y)).eq(0); + expect(formatValue(boxCollider.angularVelocity.z)).eq(0); + }); + + it("applyForceAtPosition - respects entity world rotation when transforming local CoM", function () { + // entity rotated 90° around Y, CoM local (1, 0, 0) → world CoM offset (0, 0, -1) + // Apply force at world (0,0,-1), r=0, expect no torque. + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + box.transform.rotate(new Vector3(0, 90, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.mass = 1; + boxCollider.useGravity = false; + boxCollider.centerOfMass = new Vector3(1, 0, 0); + boxCollider.inertiaTensor = new Vector3(1, 1, 1); + + boxCollider.applyForceAtPosition(new Vector3(1, 0, 0), new Vector3(0, 0, -1)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(boxCollider.linearVelocity.x)).eq(0.01667); + expect(formatValue(boxCollider.angularVelocity.x)).eq(0); + expect(formatValue(boxCollider.angularVelocity.y)).eq(0); + expect(formatValue(boxCollider.angularVelocity.z)).eq(0); + }); + + it("applyForceAtPosition - position + rotation + CoM offset + r != 0 (full worldCoM coverage)", function () { + // Stress-test all three terms of worldCoM = entity.worldPos + worldRot * localCoM: + // entity position (5, 0, 0) — exercises the translation term + // entity rotation 90° around Y — exercises the rotation term (localCoM (1,0,0) → world (0,0,-1)) + // CoM local (1, 0, 0) — exercises the local CoM lookup + // worldCoM = (5,0,0) + (0,0,-1) = (5, 0, -1) + // force F = (1, 0, 0) at P = (5, 1, -1) + // r = P - worldCoM = (0, 1, 0) + // τ = r × F = (0, 0, -1) + // If any single term in worldCoM is wrong (missing translate / wrong quat / wrong localCoM), + // r becomes non-(0,1,0) and τ has spurious x/y components → catches the bug. + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(5, 0, 0)); + box.transform.rotate(new Vector3(0, 90, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.mass = 1; + boxCollider.useGravity = false; + boxCollider.centerOfMass = new Vector3(1, 0, 0); + boxCollider.inertiaTensor = new Vector3(1, 1, 1); + + boxCollider.applyForceAtPosition(new Vector3(1, 0, 0), new Vector3(5, 1, -1)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(boxCollider.linearVelocity.x)).eq(0.01667); + expect(formatValue(boxCollider.angularVelocity.x)).eq(0); + expect(formatValue(boxCollider.angularVelocity.y)).eq(0); + expect(formatValue(boxCollider.angularVelocity.z)).lessThan(0); + }); + + it("applyForceAtPosition - works after entity.clone() (defends prefab clone path)", function () { + // R6/R8 both surfaced because clone() bypasses setters and leaves native state inconsistent. + // applyForceAtPosition reads native getCenterOfMass + entity.transform.worldRotationQuaternion. + // If a future change adds @ignoreClone fields or relies on setter side-effects, this test + // will catch the regression by exercising the API on a cloned collider. + const source = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const sourceCollider = source.getComponent(DynamicCollider); + sourceCollider.mass = 1; + sourceCollider.useGravity = false; + sourceCollider.centerOfMass = new Vector3(0, 0, 0); + sourceCollider.inertiaTensor = new Vector3(1, 1, 1); + + const cloneEntity = source.clone(); + source.destroy(); + rootEntity.addChild(cloneEntity); + cloneEntity.transform.setPosition(0, 0, 0); + + const cloneCollider = cloneEntity.getComponent(DynamicCollider); + // r = (0,1,0), F = (1,0,0), τ = (0,0,-1) → expect negative angular z + cloneCollider.applyForceAtPosition(new Vector3(1, 0, 0), new Vector3(0, 1, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(cloneCollider.linearVelocity.x)).eq(0.01667); + expect(formatValue(cloneCollider.angularVelocity.z)).lessThan(0); + expect(formatValue(cloneCollider.angularVelocity.x)).eq(0); + expect(formatValue(cloneCollider.angularVelocity.y)).eq(0); + }); + + it("applyForce on sleeping actor must wake up and apply force", function () { + // Validates whether PhysX wasm `addForce(force, eFORCE, autowake=true)` actually wakes a + // sleeping actor on its own — or whether the engine's explicit wakeUp() call is required. + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.mass = 1; + boxCollider.useGravity = false; + boxCollider.linearDamping = 0; + + boxCollider.sleep(); + expect(boxCollider.isSleeping()).toBe(true); + + boxCollider.applyForce(new Vector3(1, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + expect(formatValue(boxCollider.linearVelocity.x)).eq(0.01667); + expect(boxCollider.isSleeping()).toBe(false); + }); + + it("applyForce after kinematic→dynamic switch (mimic billiards game break flow)", function () { + // Game pattern: all balls set kinematic at init, switched back to dynamic on break, + // then applyForce. Verifies the original 'force lost' bug was actually from this path. + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.mass = 1; + boxCollider.useGravity = false; + boxCollider.linearDamping = 0; + + boxCollider.isKinematic = true; + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + boxCollider.isKinematic = false; + + boxCollider.applyForce(new Vector3(1, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + expect(formatValue(boxCollider.linearVelocity.x)).eq(0.01667); + }); + + it("fixedTimeStep 1/60 vs 1/480: PhysX applyForce delivers 8x smaller dv at finer step", function () { + // ultrathink probe: does cocos-style `fixedTimeStep(true)→1/480` actually give + // *more force* than `fixedTimeStep(false)→1/60` in PhysX? + // + // Theory: + // Bullet (Cocos): clearForces runs ONCE after all substeps → dv = F·frame_dt/m + // → substep count doesn't affect dv. + // PhysX (Galacean): force cleared per simulate() call → only the first substep + // in a frame applies the force → dv = F·fixedTimeStep/m + // → 1/480 gives dv 8x smaller than 1/60. + // + // This test confirms the PhysX behavior empirically and quantifies the gap. + const scene = engine.sceneManager.activeScene; + const originalFTS = scene.physics.fixedTimeStep; + + const probe = (fts: number) => { + rootEntity.clearChildren(); + scene.physics.fixedTimeStep = fts; + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const c = box.getComponent(DynamicCollider); + c.mass = 1; + c.useGravity = false; + c.linearDamping = 0; + c.angularDamping = 0; + c.applyForce(new Vector3(100, 0, 0)); + // Advance exactly one *frame* of wall time. Galacean's _update loops simulate + // until frame_dt accumulates: 1/60 → 1 substep; 1/480 → 8 substeps. + // @ts-ignore + scene.physics._update(1 / 60); + return c.linearVelocity.x; + }; + + const dv_1_60 = probe(1 / 60); + const dv_1_480 = probe(1 / 480); + + console.info( + `[fixedTimeStep probe] applyForce(F=100) over 1 frame (1/60s):\n` + + ` 1/60 step → dv = ${dv_1_60.toFixed(4)} m/s\n` + + ` 1/480 step → dv = ${dv_1_480.toFixed(4)} m/s\n` + + ` ratio (1/60 / 1/480) = ${(dv_1_60 / dv_1_480).toFixed(3)} (theory: 8)` + ); + + scene.physics.fixedTimeStep = originalFTS; + + // Theory: dv_1_60 = F·(1/60)/m = 100/60 ≈ 1.667 + expect(dv_1_60).toBeCloseTo(100 / 60, 2); + // Theory: dv_1_480 = F·(1/480)/m = 100/480 ≈ 0.208 + expect(dv_1_480).toBeCloseTo(100 / 480, 2); + // Ratio must be 8 (PhysX clears force per simulate) + expect(dv_1_60 / dv_1_480).toBeCloseTo(8, 1); + }); + it("maxAngularVelocity", function () { const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); const boxCollider = box.getComponent(DynamicCollider); @@ -398,6 +649,48 @@ describe("DynamicCollider", function () { expect(box.transform.position.y).below(1); }); + it("teleports kinematic target collider on re-enable instead of sweeping from stale native pose", function () { + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(-10, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + boxCollider.useGravity = false; + boxCollider.isKinematic = true; + boxCollider.kinematicTransformSyncMode = DynamicColliderKinematicTransformSyncMode.Target; + + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + // @ts-ignore - intentionally observe the native boundary used by Collider sync. + const nativeCollider = boxCollider._nativeCollider; + const originalMove = nativeCollider.move.bind(nativeCollider); + const originalSetWorldTransform = nativeCollider.setWorldTransform.bind(nativeCollider); + let moveCalls = 0; + let setWorldTransformCalls = 0; + nativeCollider.move = (...args: Parameters) => { + moveCalls++; + return originalMove(...args); + }; + nativeCollider.setWorldTransform = (...args: Parameters) => { + setWorldTransformCalls++; + return originalSetWorldTransform(...args); + }; + + try { + box.isActive = false; + box.transform.setPosition(10, 0, 0); + box.isActive = true; + + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + + expect(moveCalls).eq(0); + expect(setWorldTransformCalls).eq(1); + expect(formatValue(box.transform.position.x)).eq(10); + } finally { + nativeCollider.move = originalMove; + nativeCollider.setWorldTransform = originalSetWorldTransform; + } + }); + it("constraints", function () { const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); const boxCollider = box.getComponent(DynamicCollider); @@ -442,6 +735,52 @@ describe("DynamicCollider", function () { ).toBeTruthy(); }); + it("R0: CCD mode survives kinematic toggle (PhysX rejects CCD on kinematic)", function () { + // RED verification for R0 fix: + // PhysX 4.1.1 forbids CCD on kinematic actors. The fix caches the user-intended mode + // and re-applies on kinematic→dynamic. Without the fix, switching to kinematic loses + // the CCD flag and a subsequent dynamic switch does not restore it. + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + // @ts-ignore + const physX = boxCollider._nativeCollider._physXPhysics._physX; + const ccdFlag = () => + // @ts-ignore + boxCollider._nativeCollider._pxActor.getRigidBodyFlags(physX.PxRigidBodyFlag.eENABLE_CCD); + + boxCollider.collisionDetectionMode = CollisionDetectionMode.Continuous; + expect(ccdFlag()).toBeTruthy(); + + boxCollider.isKinematic = true; + expect(ccdFlag()).toBeFalsy(); + + boxCollider.isKinematic = false; + expect(ccdFlag()).toBeTruthy(); + expect(boxCollider.collisionDetectionMode).toEqual(CollisionDetectionMode.Continuous); + }); + + it("R0: setCollisionDetectionMode in kinematic state defers application", function () { + // RED verification: while kinematic, the CCD flag should not be touched (PhysX warns). + // User's intent is cached and applied on next dynamic switch. + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + // @ts-ignore + const physX = boxCollider._nativeCollider._physXPhysics._physX; + const ccdFlag = () => + // @ts-ignore + boxCollider._nativeCollider._pxActor.getRigidBodyFlags(physX.PxRigidBodyFlag.eENABLE_CCD); + + boxCollider.isKinematic = true; + expect(ccdFlag()).toBeFalsy(); + + boxCollider.collisionDetectionMode = CollisionDetectionMode.Continuous; + expect(ccdFlag()).toBeFalsy(); + expect(boxCollider.collisionDetectionMode).toEqual(CollisionDetectionMode.Continuous); + + boxCollider.isKinematic = false; + expect(ccdFlag()).toBeTruthy(); + }); + it("sleep", function () { const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); const boxCollider = box.getComponent(DynamicCollider); diff --git a/tests/src/core/physics/MeshColliderShape.test.ts b/tests/src/core/physics/MeshColliderShape.test.ts index dfb12cf5c0..1b85709d29 100644 --- a/tests/src/core/physics/MeshColliderShape.test.ts +++ b/tests/src/core/physics/MeshColliderShape.test.ts @@ -31,11 +31,7 @@ class CollisionScript extends Script { * @param indices - Optional triangle indices * @returns A ModelMesh with readable data */ -function createModelMesh( - engine: WebGLEngine, - positions: number[], - indices?: number[] -): ModelMesh { +function createModelMesh(engine: WebGLEngine, positions: number[], indices?: number[]): ModelMesh { const mesh = new ModelMesh(engine); const vec3Positions: Vector3[] = []; for (let i = 0; i < positions.length; i += 3) { @@ -107,11 +103,7 @@ describe("MeshColliderShape PhysX", () => { const meshShape = new MeshColliderShape(); const meshMaterial = meshShape.material; // Ground plane at y=0, CCW winding -> normal +Y - const mesh = createModelMesh( - engine, - [-10, 0, -10, 10, 0, -10, -10, 0, 10, 10, 0, 10], - [0, 2, 1, 1, 2, 3] - ); + const mesh = createModelMesh(engine, [-10, 0, -10, 10, 0, -10, -10, 0, 10, 10, 0, 10], [0, 2, 1, 1, 2, 3]); meshShape.mesh = mesh; groundCollider.addShape(meshShape); @@ -190,6 +182,57 @@ describe("MeshColliderShape PhysX", () => { defaultMaterial?.destroy(); material?.destroy(); }); + + it("R6: cloned MeshColliderShape rebuilds its native PhysX shape", async () => { + // RED verification for R6 fix: + // MeshColliderShape's `_nativeShape` is `@ignoreClone` — without an + // override of `_cloneTo` cooking a fresh shape from cloned vertex/index + // buffers, the cloned entity has no physical surface (sphere falls + // straight through). + const groundEntity = root.createChild("meshGroundForClone"); + groundEntity.transform.setPosition(0, 0, 0); + const groundCollider = groundEntity.addComponent(StaticCollider); + const meshShape = new MeshColliderShape(); + const meshMaterial = meshShape.material; + const mesh = createModelMesh(engine, [-10, 0, -10, 10, 0, -10, -10, 0, 10, 10, 0, 10], [0, 2, 1, 1, 2, 3]); + meshShape.mesh = mesh; + groundCollider.addShape(meshShape); + + const clonedGround = groundEntity.clone(); + // Move the original aside so the cloned ground is the only surface below the sphere. + groundEntity.transform.setPosition(1000, 0, 0); + root.addChild(clonedGround); + clonedGround.transform.setPosition(0, 0, 0); + + const clonedShape = clonedGround.getComponent(StaticCollider).shapes[0] as MeshColliderShape; + // @ts-ignore — inspect that the cloned shape actually has a usable native PhysX handle + expect(clonedShape._nativeShape).not.toBeNull(); + // @ts-ignore + expect(clonedShape._nativeShape._pxShape).toBeDefined(); + + const sphereEntity = root.createChild("sphereForClone"); + sphereEntity.transform.setPosition(0, 2, 0); + const dynamicCollider = sphereEntity.addComponent(DynamicCollider); + const sphereShape = new SphereColliderShape(); + const sphereMaterial = sphereShape.material; + sphereShape.radius = 0.5; + dynamicCollider.addShape(sphereShape); + + for (let i = 0; i < 60; i++) { + physicsScene._update(1 / 60); + } + + // Sphere lands on cloned ground (y > -1), not falls forever (y < -10). + const sphereY = sphereEntity.transform.position.y; + expect(sphereY).toBeGreaterThan(-1); + expect(sphereY).toBeLessThan(2); + + groundEntity.destroy(); + clonedGround.destroy(); + sphereEntity.destroy(); + meshMaterial?.destroy(); + sphereMaterial?.destroy(); + }); }); describe("Convex Mesh (Dynamic)", () => { @@ -265,9 +308,10 @@ describe("MeshColliderShape PhysX", () => { const meshShape = new MeshColliderShape(); const meshMaterial = meshShape.material; meshShape.isConvex = true; - const mesh = createModelMesh(engine, [ - -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1 - ]); + const mesh = createModelMesh( + engine, + [-1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1] + ); meshShape.mesh = mesh; meshShape.isTrigger = true; triggerCollider.addShape(meshShape); @@ -417,11 +461,7 @@ describe("MeshColliderShape PhysX", () => { staticCollider.addShape(meshShape); // Update mesh - const mesh2 = createModelMesh( - engine, - [0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0], - [0, 1, 2, 3, 4, 5] - ); + const mesh2 = createModelMesh(engine, [0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0], [0, 1, 2, 3, 4, 5]); meshShape.mesh = mesh2; expect(staticCollider.shapes.length).toBe(1); @@ -695,11 +735,7 @@ describe("MeshColliderShape PhysX", () => { expect(meshShape._nativeShape).toBeNull(); // Re-enable with new mesh - const mesh2 = createModelMesh( - engine, - [0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0], - [0, 1, 2, 3, 4, 5] - ); + const mesh2 = createModelMesh(engine, [0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0], [0, 1, 2, 3, 4, 5]); meshShape.mesh = mesh2; // @ts-ignore expect(meshShape._nativeShape).not.toBeNull(); diff --git a/tests/src/core/physics/PhysicsMaterial.test.ts b/tests/src/core/physics/PhysicsMaterial.test.ts index 7433197ade..7bfd0e0e88 100644 --- a/tests/src/core/physics/PhysicsMaterial.test.ts +++ b/tests/src/core/physics/PhysicsMaterial.test.ts @@ -79,6 +79,56 @@ describe("PhysicsMaterial", () => { expect(formatValue(boxEntity2.transform.position.y)).eq(0); }); + it("cloned collider shape material keeps native values", () => { + const scene = engine.sceneManager.activeScene; + const originalGravity = scene.physics.gravity.clone(); + const originalFixedTimeStep = scene.physics.fixedTimeStep; + scene.physics.gravity = new Vector3(0, 0, 0); + scene.physics.fixedTimeStep = 1 / 60; + + try { + const wallEntity = addBox(new Vector3(1, 8, 8), StaticCollider, new Vector3(0, 0, 0)); + const wallMaterial = wallEntity.getComponent(StaticCollider).shapes[0].material; + wallMaterial.bounciness = 1; + wallMaterial.dynamicFriction = 0; + wallMaterial.staticFriction = 0; + wallMaterial.bounceCombine = PhysicsMaterialCombineMode.Multiply; + wallMaterial.frictionCombine = PhysicsMaterialCombineMode.Multiply; + + const sourceEntity = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(-3, 0, 0)); + const sourceCollider = sourceEntity.getComponent(DynamicCollider); + sourceCollider.linearDamping = 0; + sourceCollider.angularDamping = 0; + sourceCollider.automaticCenterOfMass = false; + sourceCollider.automaticInertiaTensor = false; + + const sourceMaterial = sourceCollider.shapes[0].material; + sourceMaterial.bounciness = 1; + sourceMaterial.dynamicFriction = 0; + sourceMaterial.staticFriction = 0; + sourceMaterial.bounceCombine = PhysicsMaterialCombineMode.Multiply; + sourceMaterial.frictionCombine = PhysicsMaterialCombineMode.Multiply; + + const cloneEntity = sourceEntity.clone(); + sourceEntity.destroy(); + rootEntity.addChild(cloneEntity); + cloneEntity.transform.setPosition(-3, 0, 0); + + const cloneCollider = cloneEntity.getComponent(DynamicCollider); + cloneCollider.linearVelocity = new Vector3(10, 0, 0); + + for (let i = 0; i < 40; i++) { + // @ts-ignore + scene.physics._update(scene.physics.fixedTimeStep); + } + + expect(cloneCollider.linearVelocity.x).lessThan(-1); + } finally { + scene.physics.gravity = originalGravity; + scene.physics.fixedTimeStep = originalFixedTimeStep; + } + }); + it("bounceCombine Average", () => { const boxEntity = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(0, 5, 0)); const ground = addPlane(0, -0.5, 0); diff --git a/tests/src/core/physics/PhysicsScene.test.ts b/tests/src/core/physics/PhysicsScene.test.ts index 406306a4b6..a62067db59 100644 --- a/tests/src/core/physics/PhysicsScene.test.ts +++ b/tests/src/core/physics/PhysicsScene.test.ts @@ -12,8 +12,7 @@ import { Scene, Script, SphereColliderShape, - StaticCollider, - OverlapHitResult + StaticCollider } from "@galacean/engine-core"; import { Ray, Vector3, Quaternion } from "@galacean/engine-math"; import { LitePhysics } from "@galacean/engine-physics-lite"; @@ -469,16 +468,15 @@ describe("Physics Test", () => { expect(outHitResult.normal).to.be.deep.include({ x: 0, y: 0, z: 0 }); expect(outHitResult.entity).to.be.null; - // Test that return origin point if origin is inside collider. + // Test that initial overlap is skipped when ray origin is inside collider. + // Use a strictly-inside origin (2.9,2.9,2.9) rather than the box corner + // (3,3,3), which is a boundary point whose hit/miss depends on PhysX edge + // tolerance and can flake regardless of the initial-overlap-skip behavior. boxShape.size = new Vector3(6, 6, 6); - ray = new Ray(new Vector3(3, 3, 3), new Vector3(0, -1, 0).normalize()); - expect(physicsScene.raycast(ray, outHitResult)).to.eq(true); + ray = new Ray(new Vector3(2.9, 2.9, 2.9), new Vector3(0, -1, 0).normalize()); + expect(physicsScene.raycast(ray, outHitResult)).to.eq(false); expect(outHitResult.distance).to.be.eq(0); - expect(outHitResult.point).to.be.deep.include({ x: 3, y: 3, z: 3 }); - expect(outHitResult.normal.x).to.be.eq(0); - expect(outHitResult.normal.y).to.be.eq(1); - expect(outHitResult.normal.z).to.be.eq(0); - expect(outHitResult.entity).to.be.eq(raycastTestRoot); + expect(outHitResult.entity).to.be.null; // Test that raycast works correctly if shape is not at origin of coordinate. boxShape.size = new Vector3(1, 1, 1); @@ -508,6 +506,7 @@ describe("Physics Test", () => { rootEntityCharacter.transform.position = new Vector3(0, 0, 0); const characterController = rootEntityCharacter.addComponent(CharacterController); + characterController.collisionLayer = Layer.Layer3; const boxShape2 = new BoxColliderShape(); boxShape2.size.set(1, 1, 1); boxShape2.position = new Vector3(0, 0, 0); @@ -554,7 +553,7 @@ describe("Physics Test", () => { const halfExtents = new Vector3(0.5, 0.5, 0.5); const direction = new Vector3(0, 1, 0); const orientation = new Quaternion(); - expect(physicsScene.boxCast(center, halfExtents, direction, orientation)).to.eq(false); + expect(physicsScene.boxCast(center, halfExtents, direction)).to.eq(false); // Test boxCast with hit direction.set(-1, -1, -1); @@ -584,7 +583,7 @@ describe("Physics Test", () => { physicsScene.boxCast(center, halfExtents, direction, orientation, 0.1, Layer.Everything, outHitResult) ).to.eq(false); - // Test boxCast when box is inside collider + // Test that initial overlap is skipped when sweep starts inside a collider. center.set(0, 0, 0); expect( physicsScene.boxCast( @@ -596,8 +595,7 @@ describe("Physics Test", () => { Layer.Everything, outHitResult ) - ).to.eq(true); - expect(outHitResult.distance).to.be.eq(0); + ).to.eq(false); // Test boxCast with rotation Quaternion.rotationEuler(0, Math.PI / 4, 0, orientation); @@ -728,12 +726,11 @@ describe("Physics Test", () => { // Test sphereCast with distance limit expect(physicsScene.sphereCast(center, radius, direction, 0.1, Layer.Everything, outHitResult)).to.eq(false); - // Test sphereCast when sphere is inside collider + // Test that initial overlap is skipped when sphere starts inside a collider. center.set(0, 0, 0); expect( physicsScene.sphereCast(center, radius, direction, Number.MAX_VALUE, Layer.Everything, outHitResult) - ).to.eq(true); - expect(outHitResult.distance).to.be.eq(0); + ).to.eq(false); // Test sphereCast with multiple colliders const collider2 = sweepTestRoot.addComponent(StaticCollider); @@ -855,7 +852,7 @@ describe("Physics Test", () => { physicsScene.capsuleCast(center, radius, height, direction, orientation, 0.1, Layer.Everything, outHitResult) ).to.eq(false); - // Test capsuleCast when capsule is inside collider + // Test that initial overlap is skipped when capsule starts inside a collider. center.set(0, 0, 0); expect( physicsScene.capsuleCast( @@ -868,8 +865,7 @@ describe("Physics Test", () => { Layer.Everything, outHitResult ) - ).to.eq(true); - expect(outHitResult.distance).to.be.eq(0); + ).to.eq(false); // Test capsuleCast with rotation Quaternion.rotationEuler(0, Math.PI / 4, 0, orientation); @@ -1511,14 +1507,16 @@ describe("Physics Test", () => { collisionTestScript.useLite = false; // Test that collision works correctly, A is dynamic and kinematic, B is static. + // SceneDesc.staticKineFilteringMode = eKEEP + Collider.move() routing kinematic + // to setKinematicTarget make static-kinematic pairs generate contact events. resetSpy(); setColliderProps(entity1, true, false, true); setColliderProps(entity2, false, false, false); updatePhysics(physicsMgr); - expect(collisionTestScript.onCollisionEnter).not.toHaveBeenCalled(); - expect(collisionTestScript.onCollisionStay).not.toHaveBeenCalled(); - expect(collisionTestScript.onCollisionExit).not.toHaveBeenCalled(); + expect(collisionTestScript.onCollisionEnter).toHaveBeenCalled(); + expect(collisionTestScript.onCollisionStay).toHaveBeenCalled(); + expect(collisionTestScript.onCollisionExit).toHaveBeenCalled(); expect(collisionTestScript.onTriggerEnter).not.toHaveBeenCalled(); expect(collisionTestScript.onTriggerStay).not.toHaveBeenCalled(); expect(collisionTestScript.onTriggerExit).not.toHaveBeenCalled(); @@ -1631,14 +1629,16 @@ describe("Physics Test", () => { collisionTestScript.useLite = false; // Test that collision works correctly, both A,B are dynamic, kinematic. + // SceneDesc.kineKineFilteringMode = eKEEP + Collider.move() routing kinematic + // to setKinematicTarget make kine-kine pairs generate contact events. resetSpy(); setColliderProps(entity1, true, false, true); setColliderProps(entity2, true, false, true); updatePhysics(physicsMgr); - expect(collisionTestScript.onCollisionEnter).not.toHaveBeenCalled(); - expect(collisionTestScript.onCollisionStay).not.toHaveBeenCalled(); - expect(collisionTestScript.onCollisionExit).not.toHaveBeenCalled(); + expect(collisionTestScript.onCollisionEnter).toHaveBeenCalled(); + expect(collisionTestScript.onCollisionStay).toHaveBeenCalled(); + expect(collisionTestScript.onCollisionExit).toHaveBeenCalled(); expect(collisionTestScript.onTriggerEnter).not.toHaveBeenCalled(); expect(collisionTestScript.onTriggerStay).not.toHaveBeenCalled(); expect(collisionTestScript.onTriggerExit).not.toHaveBeenCalled(); diff --git a/tests/src/loader/GLTFLoader.test.ts b/tests/src/loader/GLTFLoader.test.ts index 7b35b7b330..164dd788aa 100644 --- a/tests/src/loader/GLTFLoader.test.ts +++ b/tests/src/loader/GLTFLoader.test.ts @@ -39,6 +39,156 @@ beforeAll(async function () { @registerGLTFParser(GLTFParserType.Schema) class GLTFCustomJSONParser extends GLTFParser { parse(context: GLTFParserContext) { + if (context.glTFResource.url.endsWith("testSkinRoot.gltf")) { + context.buffers = [new ArrayBuffer(128)]; + return Promise.resolve({ + asset: { + version: "2.0" + }, + scene: 0, + scenes: [ + { + nodes: [0, 1] + } + ], + nodes: [ + { + name: "Character_Man", + skin: 0 + }, + { + name: "mixamorig:Hips", + children: [2] + }, + { + name: "mixamorig:Spine" + } + ], + skins: [ + { + inverseBindMatrices: 0, + joints: [1, 2] + } + ], + accessors: [ + { + bufferView: 0, + byteOffset: 0, + componentType: 5126, + count: 2, + type: "MAT4" + } + ], + bufferViews: [ + { + buffer: 0, + byteOffset: 0, + byteLength: 128 + } + ], + buffers: [ + { + byteLength: 128 + } + ] + }); + } + + if (context.glTFResource.url.endsWith("testSkinRootBounds.gltf")) { + const buffer = new ArrayBuffer(152); + const floats = new Float32Array(buffer); + // Inverse bind matrices for Hips and Spine. Their bind pose world x is + // Character_Group(3) + Hips(10), so inverse bind translates by -13. + floats.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -13, 0, 0, 1], 0); + floats.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -13, 0, 0, 1], 16); + floats.set([9, -1, -1, 11, 1, 1], 32); + context.buffers = [buffer]; + return Promise.resolve({ + asset: { + version: "2.0" + }, + scene: 0, + scenes: [ + { + nodes: [0] + } + ], + nodes: [ + { + name: "Character_Group", + translation: [3, 0, 0], + children: [1, 2] + }, + { + name: "Character_Man", + mesh: 0, + skin: 0 + }, + { + name: "mixamorig:Hips", + translation: [10, 0, 0], + children: [3] + }, + { + name: "mixamorig:Spine" + } + ], + skins: [ + { + inverseBindMatrices: 0, + joints: [2, 3] + } + ], + meshes: [ + { + primitives: [ + { + attributes: { + POSITION: 1 + }, + mode: 4 + } + ] + } + ], + accessors: [ + { + bufferView: 0, + byteOffset: 0, + componentType: 5126, + count: 2, + type: "MAT4" + }, + { + bufferView: 1, + byteOffset: 0, + componentType: 5126, + count: 2, + type: "VEC3", + min: [9, -1, -1], + max: [11, 1, 1] + } + ], + bufferViews: [ + { + buffer: 0, + byteOffset: 0, + byteLength: 128 + }, + { + buffer: 0, + byteOffset: 128, + byteLength: 24 + } + ], + buffers: [ + { + byteLength: 152 + } + ] + }); + } + const glTF = { buffers: [ { @@ -393,7 +543,7 @@ beforeAll(async function () { afterAll(() => { @registerGLTFParser(GLTFParserType.Schema) - class test extends GLTFSchemaParser { } + class test extends GLTFSchemaParser {} }); describe("glTF Loader test", function () { @@ -481,6 +631,17 @@ describe("glTF Loader test", function () { expect(renderer).to.exist; expect(renderer.blendShapeWeights).to.deep.include([1, 1]); }); + + it("single-root animation root channel should bind to the root node path", async () => { + const glTFResource: GLTFResource = await engine.resourceManager.load({ + type: AssetType.GLTF, + url: "mock/path/testA.gltf" + }); + + const clip = glTFResource.animations?.[0]; + expect(clip).to.exist; + expect(clip.curveBindings[0].relativePath).to.equal("entity1"); + }); }); describe("glTF instance test", function () { @@ -517,6 +678,50 @@ describe("glTF instance test", function () { }); }); +describe("glTF scene root structure", function () { + it("Single root scene should have GLTF_ROOT container", async () => { + const glTFResource: GLTFResource = await engine.resourceManager.load({ + type: AssetType.GLTF, + url: "mock/path/testRoot.gltf" + }); + const { defaultSceneRoot } = glTFResource; + + // Should always create GLTF_ROOT container, even for single-root scenes + expect(defaultSceneRoot.name).to.equal("GLTF_ROOT"); + expect(defaultSceneRoot.children.length).to.equal(1); + expect(defaultSceneRoot.children[0].name).to.equal("entity1"); + }); + + it("Multi-root skins without skeleton should use the scene wrapper as rootBone", async () => { + const glTFResource: GLTFResource = await engine.resourceManager.load({ + type: AssetType.GLTF, + url: "mock/path/testSkinRoot.gltf" + }); + const { defaultSceneRoot, skins } = glTFResource; + + expect(defaultSceneRoot.name).to.equal("GLTF_ROOT"); + expect(defaultSceneRoot.children.length).to.equal(2); + expect(skins[0].rootBone).to.equal(defaultSceneRoot); + }); + + it("Skinned mesh bounds should stay in rootBone space when inferred rootBone is outside joints", async () => { + const glTFResource: GLTFResource = await engine.resourceManager.load({ + type: AssetType.GLTF, + url: "mock/path/testSkinRootBounds.gltf" + }); + const { defaultSceneRoot, skins } = glTFResource; + const characterGroup = defaultSceneRoot.children[0]; + const characterMesh = characterGroup.children[0]; + const renderer = characterMesh.getComponent(SkinnedMeshRenderer); + + expect(skins[0].rootBone).to.equal(characterGroup); + expect(renderer.localBounds.min.x).to.be.closeTo(6, 1e-5); + expect(renderer.localBounds.max.x).to.be.closeTo(8, 1e-5); + expect(renderer.bounds.min.x).to.be.closeTo(9, 1e-5); + expect(renderer.bounds.max.x).to.be.closeTo(11, 1e-5); + }); +}); + describe("glTF instance test", function () { it("GLTFResource destroy directly", async () => { const glTFResource: GLTFResource = await engine.resourceManager.load({ diff --git a/tests/src/loader/ScenePhysics.test.ts b/tests/src/loader/ScenePhysics.test.ts new file mode 100644 index 0000000000..74a1a452cf --- /dev/null +++ b/tests/src/loader/ScenePhysics.test.ts @@ -0,0 +1,50 @@ +import { AssetType, BackgroundMode, Scene } from "@galacean/engine"; +import "@galacean/engine-loader"; +import { PhysXPhysics } from "@galacean/engine-physics-physx"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; + +let engine: WebGLEngine; + +beforeAll(async () => { + engine = await WebGLEngine.create({ + canvas: document.createElement("canvas"), + physics: new PhysXPhysics() + }); +}); + +afterAll(() => { + engine?.destroy(); +}); + +describe("SceneLoader physics settings", () => { + it("applies serialized scene physics settings to PhysicsScene", async () => { + const sceneData = { + name: "physics-scene", + entities: [], + scene: { + background: { + mode: BackgroundMode.SolidColor, + color: { r: 0, g: 0, b: 0, a: 1 } + }, + physics: { + gravity: { x: 0, y: -3200, z: 0 }, + fixedTimeStep: 1 / 120 + } + }, + files: [] + }; + const sceneUrl = + URL.createObjectURL(new Blob([JSON.stringify(sceneData)], { type: "application/json" })) + "#.scene"; + + const scene = await engine.resourceManager.load({ + url: sceneUrl, + type: AssetType.Scene + }); + + expect(scene.physics.gravity.x).toBe(0); + expect(scene.physics.gravity.y).toBe(-3200); + expect(scene.physics.gravity.z).toBe(0); + expect(scene.physics.fixedTimeStep).toBe(1 / 120); + }); +}); diff --git a/tests/src/loader/StrippedEntity.test.ts b/tests/src/loader/StrippedEntity.test.ts new file mode 100644 index 0000000000..6f078d1d17 --- /dev/null +++ b/tests/src/loader/StrippedEntity.test.ts @@ -0,0 +1,277 @@ +/** + * Tests stripped entities that proxy internal prefab-instance entities. + * + * A stripped entity can resolve to an entity inside a nested prefab instance and + * serve as the parent for newly added entities. + */ +import { expect, beforeAll, afterAll, describe, it } from "vitest"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import type { IHierarchyFile } from "@galacean/engine-loader"; +import { Vector3 } from "@galacean/engine-math"; +import { PrefabParser } from "../../../packages/loader/src/prefab/PrefabParser"; + +let engine: WebGLEngine; + +function getResourceObjectPool(): Record { + return (engine.resourceManager as unknown as { _objectPool: Record })._objectPool; +} + +function cachePrefab(url: string, prefab: unknown): void { + getResourceObjectPool()[url] = prefab; +} + +function removeCachedPrefab(url: string): void { + delete getResourceObjectPool()[url]; +} + +beforeAll(async () => { + const canvas = document.createElement("canvas"); + canvas.width = 256; + canvas.height = 256; + engine = await WebGLEngine.create({ canvas }); +}); + +afterAll(() => { + engine?.destroy(); +}); + +describe("IStrippedEntity as addedEntities mechanism", () => { + it("keeps nested prefab children after instantiation", async () => { + const nestedPrefabData: IHierarchyFile = { + entities: [ + { id: "0", name: "nestedRoot", components: [], children: ["1", "2"] }, + { id: "1", name: "boneHand", parent: "0", components: [] }, + { id: "2", name: "otherChild", parent: "0", components: [] } + ] + }; + const nestedPrefab = await PrefabParser.parse(engine, "sanity.prefab", nestedPrefabData); + const inst = nestedPrefab.instantiate(); + const names = inst.children.map((c) => c.name); + expect(names).toContain("boneHand"); + expect(names).toContain("otherChild"); + inst.destroy(); + }); + + it("should attach new child entity under an internal entity of a nested prefab", async () => { + // Nested prefab: + // root + // - boneHand (proxied by the stripped entity) + // - otherChild + const nestedPrefabData: IHierarchyFile = { + entities: [ + { id: "0", name: "nestedRoot", components: [], children: ["1", "2"] }, + { id: "1", name: "boneHand", parent: "0", components: [] }, + { id: "2", name: "otherChild", parent: "0", components: [] } + ] + }; + const nestedPrefab = await PrefabParser.parse(engine, "char.prefab", nestedPrefabData); + cachePrefab("char.prefab", nestedPrefab); + + // Outer prefab: + // - IRefEntity instantiating the nested prefab (only direct child of outerRoot) + // - IStrippedEntity proxying to boneHand, not in outerRoot.children + // - weaponslot listed in stripped entity's children + const outerPrefabData: IHierarchyFile = { + entities: [ + { + id: "outerRoot", + name: "Character", + components: [], + children: ["charInst"] + }, + { + id: "charInst", + name: "char", + parent: "outerRoot", + components: [], + assetUrl: "char.prefab", + isClone: true, + modifications: [], + removedEntities: [], + removedComponents: [] + } as any, + { + // Do not set `name`: _applyEntityData would override the internal entity's name. + strippedId: "boneHandle", + prefabInstanceId: "charInst", + prefabSource: { assetId: "", entityId: "0" }, + components: [], + children: ["weaponslot"] + } as any, + { + id: "weaponslot", + name: "weaponslot1", + parent: "boneHandle", + components: [], + position: { x: 0.5, y: 0.25, z: 0 } + } + ] + }; + + const outerPrefab = await PrefabParser.parse(engine, "outer.prefab", outerPrefabData); + const root = outerPrefab.instantiate(); + + const charInst = root.children.find((c) => c.name === "char"); + expect(charInst, "char instance should be a child of outerRoot").not.toBeUndefined(); + + const boneHand = charInst!.children.find((c) => c.name === "boneHand"); + expect(boneHand, "boneHand should exist inside nested prefab").not.toBeUndefined(); + + const weaponslot = boneHand!.children.find((c) => c.name === "weaponslot1"); + expect(weaponslot, "weaponslot1 should be attached as child of boneHand via stripped entity").not.toBeUndefined(); + expect(weaponslot!.transform.position.x).toBeCloseTo(0.5, 5); + expect(weaponslot!.transform.position.y).toBeCloseTo(0.25, 5); + expect(weaponslot!.transform.position.z).toBeCloseTo(0, 5); + + root.destroy(); + removeCachedPrefab("char.prefab"); + }); + + it("should support multiple stripped entities targeting different internal entities (LeftHand / RightHand socket case)", async () => { + // Nested prefab: root -> [LeftHand, RightHand] + const nestedPrefabData: IHierarchyFile = { + entities: [ + { id: "0", name: "nestedRoot", components: [], children: ["1", "2"] }, + { id: "1", name: "LeftHand", parent: "0", components: [] }, + { id: "2", name: "RightHand", parent: "0", components: [] } + ] + }; + const nestedPrefab = await PrefabParser.parse(engine, "body.prefab", nestedPrefabData); + cachePrefab("body.prefab", nestedPrefab); + + // entityId is the path string generated by _generateInstanceContext: + // nestedRoot = "" + // nestedRoot.children[0] = LeftHand -> "0" + // nestedRoot.children[1] = RightHand -> "1" + const outerPrefabData: IHierarchyFile = { + entities: [ + { + id: "outerRoot", + name: "Player", + components: [], + children: ["bodyInst"] + }, + { + id: "bodyInst", + name: "body", + parent: "outerRoot", + components: [], + assetUrl: "body.prefab", + isClone: true, + modifications: [], + removedEntities: [], + removedComponents: [] + } as any, + { + strippedId: "lhHandle", + prefabInstanceId: "bodyInst", + prefabSource: { assetId: "", entityId: "0" }, + components: [], + children: ["ws1"] + } as any, + { + strippedId: "rhHandle", + prefabInstanceId: "bodyInst", + prefabSource: { assetId: "", entityId: "1" }, + components: [], + children: ["ws2", "ws3"] + } as any, + { id: "ws1", name: "weaponslot1", parent: "lhHandle", components: [] }, + { id: "ws2", name: "weaponslot2", parent: "rhHandle", components: [] }, + { id: "ws3", name: "weaponslot3", parent: "rhHandle", components: [] } + ] + }; + + const outerPrefab = await PrefabParser.parse(engine, "player.prefab", outerPrefabData); + const root = outerPrefab.instantiate(); + const bodyInst = root.children.find((c) => c.name === "body")!; + const leftHand = bodyInst.children.find((c) => c.name === "LeftHand")!; + const rightHand = bodyInst.children.find((c) => c.name === "RightHand")!; + + const slot1 = leftHand.children.find((c) => c.name === "weaponslot1"); + const slot2 = rightHand.children.find((c) => c.name === "weaponslot2"); + const slot3 = rightHand.children.find((c) => c.name === "weaponslot3"); + + expect(slot1, "weaponslot1 under LeftHand").not.toBeUndefined(); + expect(slot2, "weaponslot2 under RightHand").not.toBeUndefined(); + expect(slot3, "weaponslot3 under RightHand").not.toBeUndefined(); + + // No new entities should remain as direct children of outerRoot after reparenting + expect(root.children.find((c) => c.name === "weaponslot1")).toBeUndefined(); + expect(root.children.find((c) => c.name === "lh-proxy")).toBeUndefined(); + + root.destroy(); + removeCachedPrefab("body.prefab"); + }); + + it("should propagate internal entity transform changes to the attached child (bone-drives-weapon behavior)", async () => { + // When an internal entity moves, the child attached through the stripped + // entity follows through normal hierarchy transforms. + const nestedPrefabData: IHierarchyFile = { + entities: [ + { id: "0", name: "nestedRoot", components: [], children: ["1"] }, + { id: "1", name: "bone", parent: "0", components: [] } + ] + }; + const nestedPrefab = await PrefabParser.parse(engine, "skel.prefab", nestedPrefabData); + cachePrefab("skel.prefab", nestedPrefab); + + const outerPrefabData: IHierarchyFile = { + entities: [ + { + id: "outerRoot", + name: "Actor", + components: [], + children: ["skelInst"] + }, + { + id: "skelInst", + name: "skel", + parent: "outerRoot", + components: [], + assetUrl: "skel.prefab", + isClone: true, + modifications: [], + removedEntities: [], + removedComponents: [] + } as any, + { + strippedId: "boneHandle", + prefabInstanceId: "skelInst", + prefabSource: { assetId: "", entityId: "0" }, + components: [], + children: ["weapon"] + } as any, + { + id: "weapon", + name: "weapon", + parent: "boneHandle", + components: [], + position: { x: 1, y: 0, z: 0 } + } + ] + }; + + const outerPrefab = await PrefabParser.parse(engine, "actor.prefab", outerPrefabData); + const root = outerPrefab.instantiate(); + const bone = root.children.find((c) => c.name === "skel")!.children.find((c) => c.name === "bone")!; + const weapon = bone.children.find((c) => c.name === "weapon")!; + + // Initial: bone at origin, weapon world position = (1, 0, 0) + const worldPos = new Vector3(); + worldPos.copyFrom(weapon.transform.worldPosition); + expect(worldPos.x).toBeCloseTo(1, 5); + expect(worldPos.y).toBeCloseTo(0, 5); + expect(worldPos.z).toBeCloseTo(0, 5); + + // Simulate transform animation: move bone to (10, 5, -3) + bone.transform.setPosition(10, 5, -3); + worldPos.copyFrom(weapon.transform.worldPosition); + expect(worldPos.x).toBeCloseTo(11, 5); // 10 + 1 + expect(worldPos.y).toBeCloseTo(5, 5); // 5 + 0 + expect(worldPos.z).toBeCloseTo(-3, 5); // -3 + 0 + + root.destroy(); + removeCachedPrefab("skel.prefab"); + }); +}); diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts index e998533e5e..b1d5085044 100644 --- a/tests/src/shader-lab/ShaderLab.test.ts +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -247,8 +247,128 @@ describe("ShaderLab", async () => { glslValidate(engine, shaderSource, shaderLabRelease); }); + it("macro-negate-number (!0, !1 in #if expressions)", async () => { + const shaderSource = await readFile("./shaders/macro-negate-number.shader"); + glslValidate(engine, shaderSource, shaderLabVerbose); + glslValidate(engine, shaderSource, shaderLabRelease); + }); + it("mrt-struct", async () => { const shaderSource = await readFile("./shaders/mrt-struct.shader"); glslValidate(engine, shaderSource, shaderLabRelease); }); + + it("texture-generic (GVec4 → vec4 resolve)", async () => { + const shaderSource = await readFile("./shaders/texture-generic.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + }); + + it("generic-return-type (builtin generic return as arg to user function)", async () => { + const shaderSource = await readFile("./shaders/generic-return-type.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + }); + + it("define-struct-access-global (global #define with struct member access)", async () => { + const shaderSource = await readFile("./shaders/define-struct-access-global.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + + const shader = shaderLabVerbose._parseShaderSource(shaderSource); + const passSource = shader.subShaders[0].passes[0]; + const { vertex, fragment } = shaderLabVerbose._parseShaderPass( + passSource.contents, + passSource.vertexEntry, + passSource.fragmentEntry, + 0, + "" + )!; + + const expectedVert = await readFile("./expected/define-struct-access-global.vert.glsl"); + const expectedFrag = await readFile("./expected/define-struct-access-global.frag.glsl"); + expect(vertex).to.equal(expectedVert); + expect(fragment).to.equal(expectedFrag); + }); + + it("define-struct-access (function-body #define with struct member access)", async () => { + const shaderSource = await readFile("./shaders/define-struct-access.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + + const shader = shaderLabVerbose._parseShaderSource(shaderSource); + const passSource = shader.subShaders[0].passes[0]; + const { vertex, fragment } = shaderLabVerbose._parseShaderPass( + passSource.contents, + passSource.vertexEntry, + passSource.fragmentEntry, + 0, + "" + )!; + + const expectedVert = await readFile("./expected/define-struct-access.vert.glsl"); + const expectedFrag = await readFile("./expected/define-struct-access.frag.glsl"); + expect(vertex).to.equal(expectedVert); + expect(fragment).to.equal(expectedFrag); + }); + + it("macro-member-access-builtin-arg (Cocos FSInput pattern: member access macro as builtin fn arg)", async () => { + const shaderSource = await readFile("./shaders/macro-member-access-builtin-arg.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + + // Also verify verbose mode (semantic analysis) succeeds — this was the original bug: + // member access macros like #define FSInput_worldNormal v.v_normal.xyz resolved to + // struct type "Varyings" instead of TypeAny, causing builtin overload matching to fail. + const shader = shaderLabVerbose._parseShaderSource(shaderSource); + const passSource = shader.subShaders[0].passes[0]; + const { vertex, fragment } = shaderLabVerbose._parseShaderPass( + passSource.contents, + passSource.vertexEntry, + passSource.fragmentEntry, + 0, + "" + )!; + + expect(vertex).to.be.a("string").and.not.empty; + expect(fragment).to.be.a("string").and.not.empty; + + // Verify key builtins are present in output (macros expanded correctly) + expect(fragment).to.contain("normalize"); + expect(fragment).to.contain("dot"); + expect(fragment).to.contain("texture2D"); + }); + + it("global-varying-var (Cocos VSOutput pattern: global Varyings var with #define macros)", async () => { + const shaderSource = await readFile("./shaders/global-varying-var.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + + // Verify verbose mode: global "Varyings o;" should not produce "uniform Varyings o;" + // and should not duplicate varying declarations. + const shader = shaderLabVerbose._parseShaderSource(shaderSource); + const passSource = shader.subShaders[0].passes[0]; + const { vertex, fragment } = shaderLabVerbose._parseShaderPass( + passSource.contents, + passSource.vertexEntry, + passSource.fragmentEntry, + 0, + "" + )!; + + expect(vertex).to.be.a("string").and.not.empty; + expect(fragment).to.be.a("string").and.not.empty; + + // No "uniform Varyings o;" in output + expect(vertex).to.not.contain("uniform Varyings"); + expect(fragment).to.not.contain("uniform Varyings"); + + // Macros should be transformed: "o.v_worldPos" → "v_worldPos" + expect(vertex).to.contain("#define VSOutput_worldPos v_worldPos"); + expect(vertex).to.contain("#define VSOutput_worldNormal v_normal.xyz"); + + // No duplicate varying declarations + const varyingMatches = vertex.match(/varying vec3 v_worldPos/g); + expect(varyingMatches).to.have.lengthOf(1); + }); + + it("frag-return-vec4 (Cocos pattern: fragment entry returns vec4 instead of void)", async () => { + const shaderSource = await readFile("./shaders/frag-return-vec4.shader"); + glslValidate(engine, shaderSource, shaderLabRelease); + glslValidate(engine, shaderSource, shaderLabVerbose); + }); }); diff --git a/tests/src/shader-lab/ShaderValidate.ts b/tests/src/shader-lab/ShaderValidate.ts index da6d83407b..7689c721bd 100644 --- a/tests/src/shader-lab/ShaderValidate.ts +++ b/tests/src/shader-lab/ShaderValidate.ts @@ -65,7 +65,7 @@ export function glslValidate( }); // @ts-ignore - const shaderProgram = shaderPass._getCanonicalShaderProgram(engine, macroMockCollection); + const shaderProgram = shaderPass._compileShaderProgram(engine, macroMockCollection); expect(shaderProgram.isValid).to.be.true; }); }); diff --git a/tests/src/shader-lab/expected/define-struct-access-global.frag.glsl b/tests/src/shader-lab/expected/define-struct-access-global.frag.glsl new file mode 100644 index 0000000000..e766b6e6a1 --- /dev/null +++ b/tests/src/shader-lab/expected/define-struct-access-global.frag.glsl @@ -0,0 +1,10 @@ +varying vec2 v_uv; + +uniform sampler2D u_texture; +#define ATTR_POS POSITION + +#define VARYING_UV v_uv + +#define FRAG_UV v_uv + +void main() { gl_FragColor = texture2D ( u_texture , FRAG_UV ) ; } \ No newline at end of file diff --git a/tests/src/shader-lab/expected/define-struct-access-global.vert.glsl b/tests/src/shader-lab/expected/define-struct-access-global.vert.glsl new file mode 100644 index 0000000000..782ad3ee52 --- /dev/null +++ b/tests/src/shader-lab/expected/define-struct-access-global.vert.glsl @@ -0,0 +1,16 @@ +uniform mat4 renderer_MVPMat; +attribute vec4 POSITION; +attribute vec2 TEXCOORD_0; + +varying vec2 v_uv; + +#define ATTR_POS POSITION + +#define VARYING_UV v_uv + +#define FRAG_UV v_uv + +void main() { +gl_Position = renderer_MVPMat * ATTR_POS ; +VARYING_UV = TEXCOORD_0 ; + } \ No newline at end of file diff --git a/tests/src/shader-lab/expected/define-struct-access.frag.glsl b/tests/src/shader-lab/expected/define-struct-access.frag.glsl new file mode 100644 index 0000000000..2c8ba502ac --- /dev/null +++ b/tests/src/shader-lab/expected/define-struct-access.frag.glsl @@ -0,0 +1,11 @@ +varying vec2 v_uv; +varying vec3 v_normal; + +uniform sampler2D u_texture; +uniform vec3 u_lightDir; +void main() { #define FRAG_UV v_uv + +#define FRAG_NORMAL v_normal + +float NdotL = dot ( FRAG_NORMAL , u_lightDir ) ; +gl_FragColor = texture2D ( u_texture , FRAG_UV ) * NdotL ; } \ No newline at end of file diff --git a/tests/src/shader-lab/expected/define-struct-access.vert.glsl b/tests/src/shader-lab/expected/define-struct-access.vert.glsl new file mode 100644 index 0000000000..1ee6d28522 --- /dev/null +++ b/tests/src/shader-lab/expected/define-struct-access.vert.glsl @@ -0,0 +1,18 @@ +uniform mat4 renderer_MVPMat; +attribute vec4 POSITION; +attribute vec2 TEXCOORD_0; + +varying vec2 v_uv; +varying vec3 v_normal; + +void main() { +#define ATTR_POS POSITION + +#define VARYING_UV v_uv + +#define VARYING_NORMAL v_normal + +gl_Position = renderer_MVPMat * ATTR_POS ; +VARYING_UV = TEXCOORD_0 ; +VARYING_NORMAL = vec3 ( 0.0 , 1.0 , 0.0 ) ; + } \ No newline at end of file diff --git a/tests/src/shader-lab/shaders/define-struct-access-global.shader b/tests/src/shader-lab/shaders/define-struct-access-global.shader new file mode 100644 index 0000000000..a8abffe570 --- /dev/null +++ b/tests/src/shader-lab/shaders/define-struct-access-global.shader @@ -0,0 +1,31 @@ +Shader "define-struct-access-global-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec4 POSITION; vec2 TEXCOORD_0; }; + struct Varyings { vec2 v_uv; }; + + VertexShader = vert; + FragmentShader = frag; + + sampler2D u_texture; + + // Global #define referencing entry function parameter names + #define ATTR_POS attr.POSITION + #define VARYING_UV o.v_uv + #define FRAG_UV v.v_uv + + Varyings vert(Attributes attr) { + Varyings o; + gl_Position = renderer_MVPMat * ATTR_POS; + VARYING_UV = attr.TEXCOORD_0; + return o; + } + + void frag(Varyings v) { + gl_FragColor = texture2D(u_texture, FRAG_UV); + } + } + } +} diff --git a/tests/src/shader-lab/shaders/define-struct-access.shader b/tests/src/shader-lab/shaders/define-struct-access.shader new file mode 100644 index 0000000000..a28171dc40 --- /dev/null +++ b/tests/src/shader-lab/shaders/define-struct-access.shader @@ -0,0 +1,34 @@ +Shader "define-struct-access-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec4 POSITION; vec2 TEXCOORD_0; }; + struct Varyings { vec2 v_uv; vec3 v_normal; }; + + VertexShader = vert; + FragmentShader = frag; + + sampler2D u_texture; + vec3 u_lightDir; + + Varyings vert(Attributes attr) { + Varyings o; + #define ATTR_POS attr.POSITION + #define VARYING_UV o.v_uv + #define VARYING_NORMAL o.v_normal + gl_Position = renderer_MVPMat * ATTR_POS; + VARYING_UV = attr.TEXCOORD_0; + VARYING_NORMAL = vec3(0.0, 1.0, 0.0); + return o; + } + + void frag(Varyings v) { + #define FRAG_UV v.v_uv + #define FRAG_NORMAL v.v_normal + float NdotL = dot(FRAG_NORMAL, u_lightDir); + gl_FragColor = texture2D(u_texture, FRAG_UV) * NdotL; + } + } + } +} diff --git a/tests/src/shader-lab/shaders/frag-return-vec4.shader b/tests/src/shader-lab/shaders/frag-return-vec4.shader new file mode 100644 index 0000000000..9b3ea3d4ca --- /dev/null +++ b/tests/src/shader-lab/shaders/frag-return-vec4.shader @@ -0,0 +1,41 @@ +Shader "frag-return-vec4-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec3 POSITION; vec3 NORMAL; vec2 TEXCOORD_0; }; + struct Varyings { vec3 v_worldPos; vec4 v_normal; vec2 v_uv; }; + + VertexShader = vert; + FragmentShader = frag; + + sampler2D u_texture; + vec3 u_lightDir; + + vec3 SRGBToLinear(vec3 gamma) { return gamma * gamma; } + vec3 LinearToSRGB(vec3 linear) { return sqrt(linear); } + + vec4 SurfacesFragmentModifyBaseColorAndTransparency(Varyings input) { + vec4 color = texture2D(u_texture, input.v_uv); + color.rgb = SRGBToLinear(color.rgb); + return color; + } + + Varyings vert(Attributes attr) { + Varyings o; + vec4 pos = renderer_MVPMat * vec4(attr.POSITION, 1.0); + o.v_worldPos = pos.xyz; + o.v_normal = vec4(attr.NORMAL, 1.0); + o.v_uv = attr.TEXCOORD_0; + gl_Position = pos; + return o; + } + + vec4 frag(Varyings input) { + vec4 color = SurfacesFragmentModifyBaseColorAndTransparency(input); + color.rgb = LinearToSRGB(color.rgb); + return color; + } + } + } +} diff --git a/tests/src/shader-lab/shaders/generic-return-type.shader b/tests/src/shader-lab/shaders/generic-return-type.shader new file mode 100644 index 0000000000..72e96b7f3d --- /dev/null +++ b/tests/src/shader-lab/shaders/generic-return-type.shader @@ -0,0 +1,52 @@ +Shader "generic-return-type-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec4 POSITION; }; + struct Varyings { vec2 uv; vec3 worldNormal; vec3 worldTangent; }; + + VertexShader = vert; + FragmentShader = frag; + + // Test: user-defined function with concrete parameter types + vec3 CalculateNormalFromTangentSpace(vec3 normalFromTangentSpace, float normalStrength, vec3 normal, vec3 tangent, float mirrorNormal) { + vec3 binormal = cross(normal, tangent) * mirrorNormal; + return (normalFromTangentSpace.x * normalStrength) * normalize(tangent) + + (normalFromTangentSpace.y * normalStrength) * normalize(binormal) + + normalFromTangentSpace.z * normalize(normal); + } + + sampler2D u_normalMap; + vec4 u_scaleAndStrength; + + Varyings vert(Attributes attr) { + Varyings o; + gl_Position = renderer_MVPMat * attr.POSITION; + o.uv = attr.POSITION.xy; + o.worldNormal = attr.POSITION.xyz; + o.worldTangent = attr.POSITION.xyz; + return o; + } + + void frag(Varyings v) { + vec3 normal = v.worldNormal; + + // Test: builtin generic functions (normalize) on swizzled values, + // passed as arguments to a user-defined function. + // normalize(vec3) should not produce EGenType.GenType (200) as return type, + // it should produce TypeAny so overload matching succeeds. + vec3 nmmp = texture(u_normalMap, v.uv).xyz - vec3(0.5); + normal = CalculateNormalFromTangentSpace( + nmmp, + u_scaleAndStrength.w, + normalize(normal.xyz), + normalize(v.worldTangent), + 1.0 + ); + + gl_FragColor = vec4(normalize(normal), 1.0); + } + } + } +} diff --git a/tests/src/shader-lab/shaders/global-varying-var.shader b/tests/src/shader-lab/shaders/global-varying-var.shader new file mode 100644 index 0000000000..bd6a5cf477 --- /dev/null +++ b/tests/src/shader-lab/shaders/global-varying-var.shader @@ -0,0 +1,42 @@ +Shader "global-varying-var-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec3 POSITION; vec3 NORMAL; vec2 TEXCOORD_0; }; + struct Varyings { vec3 v_worldPos; vec4 v_normal; vec2 v_uv; vec4 v_shadowBiasAndProbeId; }; + + VertexShader = vert; + FragmentShader = frag; + + sampler2D u_texture; + vec3 u_lightDir; + + #define VSOutput_worldPos o.v_worldPos + #define VSOutput_worldNormal o.v_normal.xyz + #define VSOutput_faceSideSign o.v_normal.w + #define VSOutput_texcoord o.v_uv + #define VSOutput_shadowBias o.v_shadowBiasAndProbeId.xy + + Varyings o; + + Varyings vert(Attributes input) { + mat4 matWorld = renderer_MVPMat; + vec4 pos = matWorld * vec4(input.POSITION, 1.0); + VSOutput_worldPos = pos.xyz; + VSOutput_worldNormal = input.NORMAL; + VSOutput_faceSideSign = 1.0; + VSOutput_texcoord = input.TEXCOORD_0; + VSOutput_shadowBias = vec2(0.0, 0.0); + gl_Position = pos; + return o; + } + + void frag(Varyings v) { + vec3 N = normalize(v.v_normal.xyz); + float NdotL = dot(N, u_lightDir); + gl_FragColor = texture2D(u_texture, v.v_uv) * NdotL; + } + } + } +} diff --git a/tests/src/shader-lab/shaders/macro-member-access-builtin-arg.shader b/tests/src/shader-lab/shaders/macro-member-access-builtin-arg.shader new file mode 100644 index 0000000000..70ae36f3bc --- /dev/null +++ b/tests/src/shader-lab/shaders/macro-member-access-builtin-arg.shader @@ -0,0 +1,49 @@ +Shader "macro-member-access-builtin-arg-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec4 POSITION; vec3 NORMAL; vec2 TEXCOORD_0; }; + struct Varyings { vec2 v_uv; vec4 v_normal; vec3 v_worldPos; }; + + VertexShader = vert; + FragmentShader = frag; + + sampler2D u_texture; + vec3 u_lightDir; + vec3 u_cameraPos; + + // Cocos-style FSInput macros: member access used as builtin function args + #define FSInput_worldNormal v.v_normal.xyz + #define FSInput_faceSideSign v.v_normal.w + #define FSInput_worldPos v.v_worldPos + #define FSInput_texcoord v.v_uv + + Varyings vert(Attributes attr) { + Varyings o; + gl_Position = renderer_MVPMat * attr.POSITION; + o.v_uv = attr.TEXCOORD_0; + o.v_normal = vec4(attr.NORMAL, 1.0); + o.v_worldPos = attr.POSITION.xyz; + return o; + } + + void frag(Varyings v) { + // normalize() with member access macro as arg + vec3 N = normalize(FSInput_worldNormal); + + // dot() with member access macro as arg + float NdotL = dot(N, u_lightDir); + + // texture2D() with member access macro as arg + vec4 albedo = texture2D(u_texture, FSInput_texcoord); + + // mix() with member access macro and scalar macro + vec3 viewDir = normalize(u_cameraPos - FSInput_worldPos); + float rim = 1.0 - dot(N, viewDir); + + gl_FragColor = albedo * NdotL + vec4(vec3(rim), 0.0); + } + } + } +} diff --git a/tests/src/shader-lab/shaders/macro-negate-number.shader b/tests/src/shader-lab/shaders/macro-negate-number.shader new file mode 100644 index 0000000000..a224605393 --- /dev/null +++ b/tests/src/shader-lab/shaders/macro-negate-number.shader @@ -0,0 +1,41 @@ +Shader "macro-negate-number-test" { + SubShader "default" { + Pass "default" { + struct a2v { + vec4 POSITION; + }; + + mat4 renderer_MVPMat; + + #define SCENE_FOG_MODE 1 + + VertexShader = vert; + FragmentShader = frag; + + void vert(a2v v) { + gl_Position = renderer_MVPMat * v.POSITION; + } + + void frag() { + vec4 color = vec4(1.0); + + // Test: !0 should evaluate to true (like C preprocessor) + #if SCENE_FOG_MODE != 4 && (!0 || SCENE_FOG_MODE) + color = vec4(0.0, 1.0, 0.0, 1.0); + #endif + + // Test: !1 should evaluate to false + #if !1 + color = vec4(1.0, 0.0, 0.0, 1.0); + #endif + + // Test: !0 alone + #if !0 + color = vec4(0.0, 0.0, 1.0, 1.0); + #endif + + gl_FragColor = color; + } + } + } +} diff --git a/tests/src/shader-lab/shaders/mrt-struct.shader b/tests/src/shader-lab/shaders/mrt-struct.shader index f29056199e..85a47193c9 100644 --- a/tests/src/shader-lab/shaders/mrt-struct.shader +++ b/tests/src/shader-lab/shaders/mrt-struct.shader @@ -1,4 +1,4 @@ -Shader "/mrt-test.gs" { +Shader "/mrt-test.shader" { SubShader "Default" { diff --git a/tests/src/shader-lab/shaders/texture-generic.shader b/tests/src/shader-lab/shaders/texture-generic.shader new file mode 100644 index 0000000000..a27789175c --- /dev/null +++ b/tests/src/shader-lab/shaders/texture-generic.shader @@ -0,0 +1,94 @@ +Shader "texture-generic-test" { + SubShader "Default" { + Pass "Forward" { + mat4 renderer_MVPMat; + + struct Attributes { vec4 POSITION; }; + struct Varyings { vec2 uv; }; + + VertexShader = vert; + FragmentShader = frag; + + // Test: user-defined function taking vec4, called with texture() return value. + // texture(sampler2D, vec2) returns GVec4 which must resolve to vec4. + float decode32(vec4 rgba) { + rgba = rgba * 255.0; + float Sign = 1.0 - step(128.0, rgba[3] + 0.5) * 2.0; + float Exponent = 2.0 * mod(float(int(rgba[3] + 0.5)), 128.0) + step(128.0, rgba[2] + 0.5) - 127.0; + float Mantissa = mod(float(int(rgba[2] + 0.5)), 128.0) * 65536.0 + rgba[1] * 256.0 + rgba[0] + 8388608.0; + return Sign * exp2(Exponent - 23.0) * Mantissa; + } + + sampler2D u_texture; + + // Test: texture() result passed to user function (GVec4 → vec4 resolve) + mat4 getJointMatrix(float i) { + float x = 4.0 * i; + vec4 v1 = vec4( + decode32(texture(u_texture, vec2((x + 0.5) / 1024.0, 0.5))), + decode32(texture(u_texture, vec2((x + 1.5) / 1024.0, 0.5))), + decode32(texture(u_texture, vec2((x + 2.5) / 1024.0, 0.5))), + decode32(texture(u_texture, vec2((x + 3.5) / 1024.0, 0.5))) + ); + return mat4(v1, v1, v1, vec4(0.0, 0.0, 0.0, 1.0)); + } + + // Test: texture() result used directly in arithmetic (GVec4 → vec4) + vec4 sampleAndScale(sampler2D tex, vec2 coord, float scale) { + vec4 color = texture(tex, coord); + return color * scale; + } + + // Test: textureLod also returns GVec4 + vec4 sampleLod(sampler2D tex, vec2 coord, float lod) { + vec4 color = textureLod(tex, coord, lod); + return color; + } + + // Test: texture2DLod (ES 1.0 function, concrete types) + vec4 sampleLod2D(sampler2D tex, vec2 coord, float lod) { + return texture2DLod(tex, coord, lod); + } + + // Test: texture2DLod result passed to user function + float decodeLod2D(sampler2D tex, vec2 coord) { + return decode32(texture2DLod(tex, coord, 0.0)); + } + + // Test: texture() with samplerCube (GVec4 → vec4 resolve via GSamplerCube) + samplerCube u_cubeMap; + vec4 sampleCube(samplerCube tex, vec3 dir) { + return texture(tex, dir); + } + + // Test: textureLod with samplerCube (GVec4 → vec4 resolve via GSamplerCube) + vec4 sampleCubeLod(samplerCube tex, vec3 dir, float lod) { + return textureLod(tex, dir, lod); + } + + // Test: texture(samplerCube) result passed to user function (vec4 param matching) + float decodeCube(vec4 rgba) { + return rgba.r + rgba.g; + } + float sampleAndDecode(samplerCube tex, vec3 dir) { + return decodeCube(texture(tex, dir)); + } + + Varyings vert(Attributes attr) { + Varyings o; + gl_Position = renderer_MVPMat * attr.POSITION; + o.uv = attr.POSITION.xy; + return o; + } + + void frag(Varyings v) { + vec4 sampled = sampleAndScale(u_texture, v.uv, 1.0); + vec4 lodSampled = sampleLod(u_texture, v.uv, 0.0); + vec4 cubeSampled = sampleCube(u_cubeMap, vec3(v.uv, 1.0)); + vec4 cubeLodSampled = sampleCubeLod(u_cubeMap, vec3(v.uv, 1.0), 0.0); + float decoded = sampleAndDecode(u_cubeMap, vec3(v.uv, 1.0)); + gl_FragColor = sampled + lodSampled + cubeSampled + cubeLodSampled + vec4(decoded); + } + } + } +} diff --git a/tests/src/ui/Mask.test.ts b/tests/src/ui/Mask.test.ts new file mode 100644 index 0000000000..d002fb27d7 --- /dev/null +++ b/tests/src/ui/Mask.test.ts @@ -0,0 +1,76 @@ +import { Sprite, SpriteMaskInteraction, SpriteMaskLayer, Texture2D } from "@galacean/engine-core"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { CanvasRenderMode, Image, Mask, UICanvas, UITransform } from "@galacean/engine-ui"; +import { describe, expect, it } from "vitest"; + +describe("Mask", async () => { + const canvas = document.createElement("canvas"); + const engine = await WebGLEngine.create({ canvas }); + const webCanvas = engine.canvas; + webCanvas.width = 300; + webCanvas.height = 300; + const scene = engine.sceneManager.scenes[0]; + const root = scene.createRootEntity("root"); + + const canvasEntity = root.createChild("canvas"); + const rootCanvas = canvasEntity.addComponent(UICanvas); + rootCanvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; + rootCanvas.referenceResolutionPerUnit = 50; + rootCanvas.referenceResolution.set(300, 300); + + const imageEntity = canvasEntity.createChild("image"); + const image = imageEntity.addComponent(Image); + (imageEntity.transform).size.set(300, 300); + + const maskEntity = canvasEntity.createChild("mask"); + const mask = maskEntity.addComponent(Mask); + (maskEntity.transform).size.set(100, 100); + mask.sprite = createSolidSprite(engine); + + it("Set and Get sprite", () => { + const texture = new Texture2D(engine, 1, 1); + const sprite = new Sprite(engine, texture); + mask.sprite = sprite; + expect(mask.sprite).to.eq(sprite); + + mask.sprite = null; + expect(mask.sprite).to.eq(null); + + mask.sprite = createSolidSprite(engine); + expect(mask.sprite).not.to.eq(null); + }); + + it("Set and Get alphaCutoff", () => { + expect(mask.alphaCutoff).to.eq(0.5); + mask.alphaCutoff = 0.2; + expect(mask.alphaCutoff).to.eq(0.2); + }); + + it("Set and Get influenceLayers", () => { + expect(mask.influenceLayers).to.eq(SpriteMaskLayer.Everything); + mask.influenceLayers = SpriteMaskLayer.Layer1; + expect(mask.influenceLayers).to.eq(SpriteMaskLayer.Layer1); + mask.influenceLayers = SpriteMaskLayer.Everything; + }); + + it("Set and Get flipX/flipY", () => { + mask.flipX = true; + expect(mask.flipX).to.eq(true); + mask.flipY = true; + expect(mask.flipY).to.eq(true); + mask.flipX = false; + mask.flipY = false; + }); + + it("UI image maskInteraction default is None", () => { + expect(image.maskInteraction).to.eq(SpriteMaskInteraction.None); + image.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + expect(image.maskInteraction).to.eq(SpriteMaskInteraction.VisibleInsideMask); + }); +}); + +function createSolidSprite(engine: WebGLEngine): Sprite { + const texture = new Texture2D(engine, 1, 1); + texture.setPixelBuffer(new Uint8Array([255, 255, 255, 255])); + return new Sprite(engine, texture); +} diff --git a/tests/src/ui/RectMask2D.test.ts b/tests/src/ui/RectMask2D.test.ts new file mode 100644 index 0000000000..47286e97eb --- /dev/null +++ b/tests/src/ui/RectMask2D.test.ts @@ -0,0 +1,79 @@ +import { Vector2, Vector3, Vector4 } from "@galacean/engine-math"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { CanvasRenderMode, RectMask2D, UICanvas, UITransform } from "@galacean/engine-ui"; +import { describe, expect, it } from "vitest"; + +describe("RectMask2D", async () => { + const canvas = document.createElement("canvas"); + const engine = await WebGLEngine.create({ canvas }); + const webCanvas = engine.canvas; + webCanvas.width = 300; + webCanvas.height = 300; + const scene = engine.sceneManager.scenes[0]; + const root = scene.createRootEntity("root"); + + const canvasEntity = root.createChild("canvas"); + const rootCanvas = canvasEntity.addComponent(UICanvas); + rootCanvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; + rootCanvas.referenceResolutionPerUnit = 1; + rootCanvas.referenceResolution.set(300, 300); + + it("should return false when mask size is zero", () => { + const maskEntity = canvasEntity.createChild("mask-zero"); + const rectMask = maskEntity.addComponent(RectMask2D); + const transform = maskEntity.transform as UITransform; + transform.size.set(0, 100); + const worldRect = new Vector4(); + + expect(rectMask._getWorldRect(worldRect)).to.eq(false); + }); + + it("should clamp negative softness values", () => { + const rectMask = canvasEntity.createChild("mask-softness").addComponent(RectMask2D); + + rectMask.softness.set(-4, 6); + expect(rectMask.softness.x).to.eq(0); + expect(rectMask.softness.y).to.eq(6); + + rectMask.softness = new Vector2(5, -3); + expect(rectMask.softness.x).to.eq(5); + expect(rectMask.softness.y).to.eq(0); + }); + + it("should toggle alphaClip", () => { + const rectMask = canvasEntity.createChild("mask-alphaclip").addComponent(RectMask2D); + expect(rectMask.alphaClip).to.eq(false); + rectMask.alphaClip = true; + expect(rectMask.alphaClip).to.eq(true); + }); + + it("should compute world rect when size and pivot set", () => { + const maskEntity = canvasEntity.createChild("mask-rect"); + const transform = maskEntity.transform as UITransform; + transform.pivot.set(0.5, 0.5); + transform.size.set(100, 80); + transform.setPosition(0, 0, 0); + const rectMask = maskEntity.addComponent(RectMask2D); + const worldRect = new Vector4(); + expect(rectMask._getWorldRect(worldRect)).to.eq(true); + // The canvas applies adaptation; just verify width/height match + expect(worldRect.z - worldRect.x).to.be.closeTo(100, 1); + expect(worldRect.w - worldRect.y).to.be.closeTo(80, 1); + }); + + it("should test contains world point", () => { + const maskEntity = canvasEntity.createChild("mask-contains"); + const transform = maskEntity.transform as UITransform; + transform.pivot.set(0.5, 0.5); + transform.size.set(100, 100); + transform.setPosition(0, 0, 0); + const rectMask = maskEntity.addComponent(RectMask2D); + + const rect = new Vector4(); + rectMask._getWorldRect(rect); + const cx = (rect.x + rect.z) * 0.5; + const cy = (rect.y + rect.w) * 0.5; + expect(rectMask._containsWorldPoint(new Vector3(cx, cy, 0))).to.eq(true); + expect(rectMask._containsWorldPoint(new Vector3(rect.x - 5, rect.y - 5, 0))).to.eq(false); + }); +}); diff --git a/tests/src/ui/UIEvent.test.ts b/tests/src/ui/UIEvent.test.ts index d2057a26cb..dc49457ba1 100644 --- a/tests/src/ui/UIEvent.test.ts +++ b/tests/src/ui/UIEvent.test.ts @@ -74,17 +74,20 @@ describe("UIEvent", async () => { // Add Image const imageEntity1 = canvasEntity.createChild("Image1"); - imageEntity1.addComponent(Image); + const image1 = imageEntity1.addComponent(Image); + image1.raycastEnabled = true; (imageEntity1.transform).size.set(300, 300); const script1 = imageEntity1.addComponent(TestScript); const imageEntity2 = imageEntity1.createChild("Image2"); - imageEntity2.addComponent(Image); + const image2 = imageEntity2.addComponent(Image); + image2.raycastEnabled = true; (imageEntity2.transform).size.set(200, 200); const script2 = imageEntity2.addComponent(TestScript); const imageEntity3 = imageEntity2.createChild("Image3"); const image3 = imageEntity3.addComponent(Image); + image3.raycastEnabled = true; (imageEntity3.transform).size.set(100, 100); const script3 = imageEntity3.addComponent(TestScript); @@ -93,6 +96,7 @@ describe("UIEvent", async () => { const { _pointerManager: pointerManager } = inputManager; const { _target: target } = pointerManager; const { left, top } = target.getBoundingClientRect(); + target.dispatchEvent(generatePointerEvent("pointerdown", 2, left + 2, top + 2)); engine.update(); diff --git a/tests/src/ui/UIInteractive.test.ts b/tests/src/ui/UIInteractive.test.ts index 8ee57381a8..96ab8be99f 100644 --- a/tests/src/ui/UIInteractive.test.ts +++ b/tests/src/ui/UIInteractive.test.ts @@ -1,7 +1,16 @@ import { Camera, PointerEventData, Script, SpriteDrawMode } from "@galacean/engine-core"; import { Color, Vector3 } from "@galacean/engine-math"; import { WebGLEngine } from "@galacean/engine-rhi-webgl"; -import { Button, ColorTransition, Image, ScaleTransition, Text, UICanvas, UIGroup, UITransform } from "@galacean/engine-ui"; +import { + Button, + ColorTransition, + Image, + ScaleTransition, + Text, + UICanvas, + UIGroup, + UITransform +} from "@galacean/engine-ui"; import { describe, expect, it, vi } from "vitest"; class ClickHandler extends Script { @@ -12,7 +21,7 @@ class ClickHandler extends Script { this.callCount++; } - handleClickWithPrefix(prefix: string) { + handleClickWithPrefix(_event: PointerEventData, prefix: string) { this.callCount++; this.lastPrefix = prefix; } @@ -40,7 +49,7 @@ describe("Button", async () => { const canvasEntity = root.createChild("canvas"); canvasEntity.addComponent(UIGroup); - const commonTextEntity = canvasEntity.createChild("commonText") + const commonTextEntity = canvasEntity.createChild("commonText"); const commonText = commonTextEntity.addComponent(Text); // Create button @@ -55,7 +64,6 @@ describe("Button", async () => { text.color.set(0, 0, 0, 1); const button = buttonEntity.addComponent(Button); - it("Set and Get", () => { // Click let clickCount = 0; @@ -135,7 +143,7 @@ describe("Button", async () => { const cloneButton = cloneButtonEntity.getComponent(Button); const cloneTransitions = cloneButton.transitions; const cloneTransition = cloneTransitions[0]; - expect(cloneTransition.target).to.eq(cloneButtonEntity.getComponent(Image)) + expect(cloneTransition.target).to.eq(cloneButtonEntity.getComponent(Image)); const cloneTransitionOne = cloneTransitions[1]; expect(cloneTransitionOne.target).to.eq(cloneButtonEntity.findByName("Text").getComponent(Text)); diff --git a/tests/vitest.config.ts b/tests/vitest.config.ts index 91a68ffbca..e11fe737fa 100644 --- a/tests/vitest.config.ts +++ b/tests/vitest.config.ts @@ -1,9 +1,18 @@ +import glsl from "../rollup-plugin-glsl"; import { defineProject } from "vitest/config"; export default defineProject({ server: { port: 51204 }, + plugins: [ + glsl({ + include: [/\.(glsl|gs)$/] + }) + ], + resolve: { + mainFields: ["debug", "module", "main"] + }, optimizeDeps: { exclude: [ "@galacean/engine",