@holoscript/core 8.0.7 → 8.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/holoscript.cjs +5 -1
- package/dist/GLTFPipeline-L76YTXKA.js +15 -0
- package/dist/GLTFPipeline-ZWJN7GI6.cjs +36 -0
- package/dist/HoloCompositionParser-K76KTEEE.js +12 -0
- package/dist/HoloCompositionParser-UZH6Y4HO.cjs +45 -0
- package/dist/HoloScriptCodeParser-2D4ERFUZ.js +6 -0
- package/dist/HoloScriptCodeParser-UDRLDA7I.cjs +23 -0
- package/dist/HoloScriptPlusParser-FTMCGWXD.js +13 -0
- package/dist/HoloScriptPlusParser-LQR5GIKF.cjs +30 -0
- package/dist/HoloScriptRuntime-UTRLJI5L.js +12 -0
- package/dist/HoloScriptRuntime-YBYXJHJN.cjs +21 -0
- package/dist/SceneIRCompiler-63KA3M2F.js +17 -0
- package/dist/SceneIRCompiler-KWUFHZ4O.cjs +42 -0
- package/dist/{USDZExporter-AUHZODOF.cjs → USDZExporter-ML4UMIGC.cjs} +3 -3
- package/dist/{USDZExporter-GXB4AUSG.js → USDZExporter-ZFKLQ76G.js} +3 -3
- package/dist/agents/index.cjs +1 -1
- package/dist/agents/index.js +1 -1
- package/dist/audit/AuditLogger.d.ts +106 -0
- package/dist/audit/AuditQueryBuilder.d.ts +79 -0
- package/dist/audit/ComplianceReporter.d.ts +90 -0
- package/dist/audit/index.d.ts +13 -0
- package/dist/{chunk-FGMDMJM7.js → chunk-2BYAUHBO.js} +3 -3
- package/dist/{chunk-EDPYCLRC.cjs → chunk-2E6PP5UE.cjs} +83 -59
- package/dist/{chunk-GASCWJK5.js → chunk-2EGGAZT3.js} +65 -287
- package/dist/{chunk-SIEXCILW.js → chunk-2I3XAGMF.js} +3 -3
- package/dist/{chunk-OS533P33.cjs → chunk-2KKOYZYI.cjs} +1074 -1655
- package/dist/chunk-2MQKNDNH.js +51 -0
- package/dist/{chunk-7DMMMBCY.cjs → chunk-2X3IPCBK.cjs} +10 -10
- package/dist/chunk-2ZOTBO25.cjs +202 -0
- package/dist/chunk-356N4NPM.cjs +152 -0
- package/dist/{chunk-X3GJPRVK.cjs → chunk-36NPLOI4.cjs} +92 -81
- package/dist/{chunk-SLU6UPJI.cjs → chunk-37HHY7RH.cjs} +5 -5
- package/dist/{chunk-YDPEHCCI.cjs → chunk-3CDDRUER.cjs} +3 -118
- package/dist/chunk-3F7ZZWIN.js +1651 -0
- package/dist/chunk-3ILD7TKP.cjs +187 -0
- package/dist/{chunk-X27QRG4C.cjs → chunk-3IUHTGDD.cjs} +153 -41
- package/dist/{chunk-PXNZ6TME.js → chunk-4376VHNJ.js} +60 -36
- package/dist/{chunk-YKGPCZEE.cjs → chunk-4FIBOJW6.cjs} +10 -10
- package/dist/chunk-4Q7RKQQI.cjs +14195 -0
- package/dist/{chunk-YCM6QQRT.cjs → chunk-566ZB4LZ.cjs} +180 -8
- package/dist/{chunk-2Y3V22SG.js → chunk-5CURIYGT.js} +3 -3
- package/dist/{chunk-QBWNJXNR.js → chunk-5FYMCVBY.js} +5 -5
- package/dist/{chunk-ZA34AN3R.js → chunk-5LVFTGKH.js} +841 -12
- package/dist/chunk-63AUAIL6.js +48 -0
- package/dist/chunk-655FATJU.js +110 -0
- package/dist/{chunk-TQ377OLB.cjs → chunk-65S62L4D.cjs} +28 -22
- package/dist/chunk-6NTAZN5Z.cjs +502 -0
- package/dist/chunk-6TRX6AR2.js +9376 -0
- package/dist/chunk-6ZCCLKS2.cjs +51 -0
- package/dist/{chunk-MDR235RZ.js → chunk-734OB5SK.js} +721 -8
- package/dist/{chunk-Q2Q34NF3.js → chunk-77QI552M.js} +6 -6
- package/dist/{chunk-WKX5AMOY.js → chunk-7I74JWPW.js} +3 -3
- package/dist/{chunk-5FSKKMR7.cjs → chunk-7IDJDMPU.cjs} +232 -4
- package/dist/chunk-7VBKRGIZ.cjs +847 -0
- package/dist/{chunk-KCPHASCO.js → chunk-AATYYLYK.js} +5 -5
- package/dist/{chunk-WFKVS2K4.cjs → chunk-AUZRKIHQ.cjs} +1101 -55
- package/dist/{chunk-DTGBOSEM.cjs → chunk-AWXFEAQL.cjs} +12 -12
- package/dist/{chunk-THCK6ECW.js → chunk-B3CWTCK2.js} +231 -3
- package/dist/chunk-BCP4E7MD.cjs +997 -0
- package/dist/chunk-BG7RPYJP.js +91 -0
- package/dist/{chunk-VF257YKO.js → chunk-BP4KTMLY.js} +117 -5
- package/dist/{chunk-YN6IETRZ.js → chunk-BQJ3YBF3.js} +6424 -987
- package/dist/{chunk-KOUAQL2B.cjs → chunk-CANAL6CT.cjs} +1569 -168
- package/dist/{chunk-FDSGHPAK.js → chunk-CBSDJF3B.js} +3 -3
- package/dist/{chunk-FEWUHQNM.cjs → chunk-CFVK26P4.cjs} +4 -4
- package/dist/{chunk-M7K4LE66.cjs → chunk-CI33NINL.cjs} +4 -4
- package/dist/{chunk-2OWDB4FB.js → chunk-CKV5NYAE.js} +6 -6
- package/dist/{chunk-LPS5AMQD.cjs → chunk-CM4NHXVO.cjs} +24 -24
- package/dist/chunk-D3HSRXYB.js +115 -0
- package/dist/{chunk-FNHJRI32.js → chunk-DBH3D2MU.js} +1561 -160
- package/dist/{chunk-GW3LKD5C.js → chunk-DJNH4HFW.js} +3 -3
- package/dist/{chunk-2GCNOI2U.cjs → chunk-DN6F4DHH.cjs} +9 -3
- package/dist/chunk-DQRJ5RJG.js +432 -0
- package/dist/{chunk-C7ZBSV2Q.js → chunk-DUG3OQAS.js} +106 -43
- package/dist/chunk-ECUYJOJ2.cjs +199 -0
- package/dist/chunk-EEMTCAK7.js +182 -0
- package/dist/{chunk-3SKLAUDD.cjs → chunk-EI3J6LTW.cjs} +359 -581
- package/dist/{chunk-SSSN7VAM.js → chunk-EM2OGPYE.js} +4 -4
- package/dist/{chunk-56VOWURB.cjs → chunk-ENZCIQZC.cjs} +522 -453
- package/dist/chunk-ESBXUYOC.cjs +836 -0
- package/dist/{chunk-L5U4W6JC.js → chunk-ESSEF6U2.js} +4 -4
- package/dist/chunk-ET3AN4W3.js +824 -0
- package/dist/chunk-EXKSFGSX.cjs +14 -14
- package/dist/{chunk-I76GIKN7.cjs → chunk-F3YT6GUF.cjs} +17 -17
- package/dist/{chunk-WRWEX5ZF.js → chunk-F75DZBBI.js} +3 -3
- package/dist/{chunk-4JGRWHZJ.js → chunk-FG6PYGIZ.js} +12 -6
- package/dist/chunk-FGGJQRLZ.js +14 -14
- package/dist/{chunk-SXOSDZD6.js → chunk-FWCHPCD4.js} +128 -10
- package/dist/{chunk-XW6IXEQH.cjs → chunk-G35CAQ7I.cjs} +5 -5
- package/dist/{chunk-7VMGP2FW.js → chunk-G6KACNMO.js} +132 -188
- package/dist/{chunk-FJPADH77.cjs → chunk-GAWJJAGT.cjs} +6 -6
- package/dist/chunk-GGR7QUKK.cjs +4724 -0
- package/dist/chunk-GIBF4BHH.js +14098 -0
- package/dist/{chunk-AWB7QYY6.cjs → chunk-GILYQ5VH.cjs} +6 -6
- package/dist/{chunk-FWVYAMNW.cjs → chunk-GKFCHGCM.cjs} +4 -4
- package/dist/{chunk-Y3ZFVUUU.js → chunk-GLEJ2TLS.js} +3 -3
- package/dist/{chunk-3GWWJT7L.js → chunk-GP3OKSKT.js} +13 -13
- package/dist/chunk-GTIPOEHN.js +196 -0
- package/dist/{chunk-LMJ5EBV7.cjs → chunk-GZPUA2TL.cjs} +16 -15
- package/dist/{chunk-IELX2OKI.js → chunk-H4LJ4HEA.js} +3 -115
- package/dist/{chunk-ACVWFGBI.cjs → chunk-HCX4SMFK.cjs} +3 -3
- package/dist/{chunk-HYNTS5CP.js → chunk-HHRZ6STP.js} +24 -2
- package/dist/chunk-HIDEIEFG.js +48 -0
- package/dist/{chunk-W6RW64BB.cjs → chunk-HSWBIDZM.cjs} +228 -5
- package/dist/{chunk-GSOUNWEL.js → chunk-HVX7QQYK.js} +1035 -1616
- package/dist/{chunk-TRCGG42I.cjs → chunk-IFW5IW4L.cjs} +6502 -1019
- package/dist/{chunk-F5LKJ6F5.cjs → chunk-IJU6AS67.cjs} +32 -28
- package/dist/{chunk-TVANHNJ7.js → chunk-IK6Z2HNC.js} +173 -3
- package/dist/{chunk-QGMUTVVH.cjs → chunk-IS7NTKY5.cjs} +20 -20
- package/dist/{chunk-YFVQWSNJ.js → chunk-IXWGYWH6.js} +3 -3
- package/dist/chunk-J2DAS2PL.js +148 -0
- package/dist/chunk-JBQXZIWB.js +148 -0
- package/dist/chunk-JBYBMBMW.js +261 -0
- package/dist/{chunk-LWUOUCYL.js → chunk-JEJ7TT54.js} +5 -5
- package/dist/{chunk-DUCB6LGL.js → chunk-JOTCTHN5.js} +211 -5
- package/dist/{chunk-47RUFYKR.js → chunk-K56UVEXV.js} +3 -3
- package/dist/{chunk-XSFNHP2E.js → chunk-KB26H447.js} +3 -3
- package/dist/{chunk-3NGDZZR4.js → chunk-KEYG7HGY.js} +430 -37
- package/dist/{chunk-DY72BMU5.js → chunk-KFVNCW4Q.js} +4 -4
- package/dist/chunk-KPGC7SK2.js +192 -0
- package/dist/chunk-KROXXBK5.cjs +53 -0
- package/dist/{chunk-ZPZAVHXL.cjs → chunk-LB3ZZMKT.cjs} +137 -19
- package/dist/chunk-LDYZWNJW.cjs +1654 -0
- package/dist/{chunk-ULMSSHCJ.cjs → chunk-LMB555QB.cjs} +405 -38
- package/dist/{chunk-CBJY23VL.js → chunk-MD4IVS42.js} +3 -3
- package/dist/{chunk-VMLKSV26.js → chunk-MDDWEBCX.js} +226 -3
- package/dist/chunk-MUWH4QK5.cjs +94 -0
- package/dist/{chunk-T6W3Z3SH.cjs → chunk-N2ILK7BF.cjs} +4 -4
- package/dist/{chunk-6EX3I5P4.cjs → chunk-N74QRJGP.cjs} +6 -6
- package/dist/chunk-NFFHLX37.cjs +132 -0
- package/dist/chunk-NJSYWX7H.js +190 -0
- package/dist/{chunk-SH5PPVK7.cjs → chunk-NQN5LFN4.cjs} +165 -221
- package/dist/chunk-NTQRFOPP.cjs +56 -0
- package/dist/{chunk-YFWRJW4B.js → chunk-NWMJPTVL.js} +51 -55
- package/dist/{chunk-63ZGNR2T.cjs → chunk-OC6GV5QH.cjs} +42 -42
- package/dist/{chunk-7E7GCQCK.js → chunk-OG3MBHM3.js} +9 -3
- package/dist/chunk-OMNHTCS4.cjs +264 -0
- package/dist/chunk-OQXB6NNC.cjs +117 -0
- package/dist/{chunk-Y57W2CT5.cjs → chunk-OV46CCA2.cjs} +51 -55
- package/dist/{chunk-6PRZIHGL.cjs → chunk-OV6HHKLE.cjs} +21 -266
- package/dist/{chunk-C37BHB7N.cjs → chunk-P26YYPOO.cjs} +5 -5
- package/dist/{chunk-2BZHWUSR.cjs → chunk-P7JEKJTZ.cjs} +16 -16
- package/dist/{chunk-NGBXXZLL.cjs → chunk-PDXX5UI4.cjs} +45 -45
- package/dist/{chunk-FHQFPYV4.js → chunk-PJFTTSKE.js} +8 -7
- package/dist/{chunk-GVFJ6T4S.cjs → chunk-PVODJEJ7.cjs} +721 -8
- package/dist/{chunk-ZZ7RO2UQ.js → chunk-Q265VZJ4.js} +3 -3
- package/dist/{chunk-E7JP3BS3.cjs → chunk-Q47JEE2C.cjs} +4 -4
- package/dist/chunk-QBY7LRN2.js +281 -0
- package/dist/{chunk-Z6PMDS4G.js → chunk-QDQWKSU3.js} +1090 -53
- package/dist/{chunk-GH6C2QGW.cjs → chunk-QEDMOGF3.cjs} +857 -16
- package/dist/{chunk-5WUNN6ZN.cjs → chunk-QHSAVOOL.cjs} +24 -2
- package/dist/chunk-QJNCDVYM.cjs +137 -0
- package/dist/chunk-QM7FJB5W.js +4717 -0
- package/dist/chunk-QQFB4RT2.cjs +448 -0
- package/dist/chunk-QSYBHQAD.cjs +196 -0
- package/dist/{chunk-UGXHYIWT.cjs → chunk-QUJ3ZB55.cjs} +28 -28
- package/dist/{chunk-2YSNA2F2.cjs → chunk-QVJMDVYZ.cjs} +5 -5
- package/dist/chunk-RAJMAX6D.js +131 -0
- package/dist/{chunk-VVKVQZI6.js → chunk-RAN3ECAS.js} +3 -3
- package/dist/chunk-RKGUZBOC.js +79 -0
- package/dist/{chunk-HLFDZ35S.cjs → chunk-RRQIAJLQ.cjs} +8 -8
- package/dist/{chunk-PABONWKI.js → chunk-RXQNRZ5F.js} +3 -3
- package/dist/{chunk-7ZHYEITD.cjs → chunk-S4O7CFHQ.cjs} +10 -11
- package/dist/chunk-SE7ANBGN.js +128 -0
- package/dist/{chunk-X2AVEAZM.cjs → chunk-SJIRCTIS.cjs} +176 -2
- package/dist/{chunk-WXQ57LAO.js → chunk-SQXAZHDG.js} +4 -4
- package/dist/chunk-SR73L5ZP.cjs +84 -0
- package/dist/{chunk-OABEO6HH.js → chunk-SZOLTWWD.js} +2349 -214
- package/dist/{chunk-5JEU52DW.js → chunk-TPZ3BN2D.js} +3 -3
- package/dist/{chunk-QFU53KRH.js → chunk-TSTFYGGX.js} +4 -5
- package/dist/{chunk-EBS32AJI.cjs → chunk-TZCFKZ6Z.cjs} +5 -5
- package/dist/chunk-UCVZXQ63.js +724 -0
- package/dist/chunk-UGQ3QNV6.js +992 -0
- package/dist/chunk-UNMVGZVW.js +142 -0
- package/dist/{chunk-TEVX443G.js → chunk-UTUD6GJI.js} +178 -6
- package/dist/chunk-UXBYA7DV.cjs +155 -0
- package/dist/chunk-V6GXFSK7.cjs +443 -0
- package/dist/chunk-VHORVGCG.cjs +761 -0
- package/dist/{chunk-B4EKV6HQ.js → chunk-VKCTYLDN.js} +500 -442
- package/dist/{chunk-O3MGZDCT.cjs → chunk-VTEXJOSD.cjs} +63 -63
- package/dist/chunk-VVAAZITA.cjs +287 -0
- package/dist/{chunk-V7AKE5VT.cjs → chunk-W2GA3EQB.cjs} +435 -42
- package/dist/chunk-W4GABFCR.js +37 -0
- package/dist/chunk-WKEK4NCA.js +490 -0
- package/dist/{chunk-5A4TCHOC.cjs → chunk-WNYXT4TE.cjs} +4 -4
- package/dist/{chunk-S762UEV5.js → chunk-WTH23ISI.js} +6 -6
- package/dist/{chunk-BTKZGHAV.js → chunk-WWNCM473.js} +10 -6
- package/dist/{chunk-7X5PNIQJ.js → chunk-X3YFRH6K.js} +27 -25
- package/dist/{chunk-4YNYPHH3.cjs → chunk-X75H75UY.cjs} +2351 -213
- package/dist/{chunk-LYJJI7ZK.cjs → chunk-XCUDDFKC.cjs} +216 -9
- package/dist/chunk-YAO4HPJL.cjs +120 -0
- package/dist/chunk-YKY2JBER.cjs +9443 -0
- package/dist/chunk-YP5CHEYU.cjs +154 -0
- package/dist/{chunk-WRNEH5MO.js → chunk-YW3I2FQL.js} +18 -7
- package/dist/{chunk-BRNUPDHJ.js → chunk-Z557DVYN.js} +386 -19
- package/dist/{chunk-VSACG2AR.js → chunk-Z6LZUYZF.js} +21 -264
- package/dist/chunk-ZHPMP447.cjs +43 -0
- package/dist/{chunk-LODTHGST.cjs → chunk-ZK7KX5CD.cjs} +133 -70
- package/dist/{chunk-CCPSNT6B.cjs → chunk-ZRPALBQB.cjs} +5 -5
- package/dist/chunk-ZVFHPMC5.js +824 -0
- package/dist/chunk-ZVQMKUGJ.js +443 -0
- package/dist/cli/holoscript-runner.cjs +86 -48
- package/dist/cli/holoscript-runner.js +54 -16
- package/dist/codebase/index.cjs +1 -1
- package/dist/codebase/index.js +1 -1
- package/dist/compiler/agent-inference.cjs +11 -11
- package/dist/compiler/agent-inference.js +9 -6
- package/dist/compiler/android-xr.cjs +13 -12
- package/dist/compiler/android-xr.js +11 -10
- package/dist/compiler/android.cjs +12 -12
- package/dist/compiler/android.js +10 -10
- package/dist/compiler/business-quest.cjs +10 -10
- package/dist/compiler/business-quest.js +2 -2
- package/dist/compiler/coco.cjs +1 -1
- package/dist/compiler/coco.js +1 -1
- package/dist/compiler/context.cjs +9 -9
- package/dist/compiler/context.js +6 -6
- package/dist/compiler/daimon-seed.cjs +36 -0
- package/dist/compiler/daimon-seed.js +12 -0
- package/dist/compiler/domain-block-utils.cjs +205 -156
- package/dist/compiler/domain-block-utils.js +8 -7
- package/dist/compiler/dtdl.cjs +10 -10
- package/dist/compiler/dtdl.js +7 -7
- package/dist/compiler/gltf-pipeline.cjs +9 -9
- package/dist/compiler/gltf-pipeline.js +2 -2
- package/dist/compiler/godot.cjs +10 -9
- package/dist/compiler/godot.js +9 -8
- package/dist/compiler/incremental.cjs +11 -11
- package/dist/compiler/incremental.js +6 -6
- package/dist/compiler/index.cjs +2585 -3273
- package/dist/compiler/index.d.ts +149 -35
- package/dist/compiler/index.js +2197 -3102
- package/dist/compiler/ios.cjs +11 -11
- package/dist/compiler/ios.js +9 -9
- package/dist/compiler/llm-provider-capabilities.cjs +9 -9
- package/dist/compiler/llm-provider-capabilities.js +6 -6
- package/dist/compiler/nodetoy.cjs +1 -1
- package/dist/compiler/nodetoy.js +1 -1
- package/dist/compiler/openxr.cjs +11 -10
- package/dist/compiler/openxr.js +10 -9
- package/dist/compiler/remotion.cjs +1 -1
- package/dist/compiler/remotion.js +1 -1
- package/dist/compiler/reproducibility.cjs +1 -1
- package/dist/compiler/reproducibility.js +1 -1
- package/dist/compiler/sdf.cjs +11 -10
- package/dist/compiler/sdf.js +9 -8
- package/dist/compiler/semantic-scene.cjs +7 -7
- package/dist/compiler/semantic-scene.js +2 -2
- package/dist/compiler/state.cjs +9 -9
- package/dist/compiler/state.js +7 -7
- package/dist/compiler/trait-composition.cjs +11 -11
- package/dist/compiler/trait-composition.js +7 -7
- package/dist/compiler/traits/ProvenanceSemiring.d.ts +167 -0
- package/dist/compiler/traits/Semiring.d.ts +26 -0
- package/dist/compiler/unity.cjs +11 -10
- package/dist/compiler/unity.js +10 -9
- package/dist/compiler/unreal.cjs +12 -11
- package/dist/compiler/unreal.js +10 -9
- package/dist/compiler/urdf.cjs +18 -17
- package/dist/compiler/urdf.js +9 -8
- package/dist/compiler/usd-physics.cjs +11 -10
- package/dist/compiler/usd-physics.js +8 -7
- package/dist/compiler/visionos.cjs +11 -10
- package/dist/compiler/visionos.js +10 -9
- package/dist/compiler/vrchat.cjs +11 -10
- package/dist/compiler/vrchat.js +9 -8
- package/dist/compiler/wasm.cjs +14 -13
- package/dist/compiler/wasm.js +9 -8
- package/dist/compiler/webgpu.cjs +10 -9
- package/dist/compiler/webgpu.js +9 -8
- package/dist/constants.cjs +398 -0
- package/dist/constants.js +9 -0
- package/dist/coordinators/index.cjs +4 -1
- package/dist/coordinators/index.js +4 -1
- package/dist/debugger.cjs +11 -9
- package/dist/debugger.js +9 -7
- package/dist/{dist-ACXG6IH5.cjs → dist-GRMOP2UL.cjs} +341 -65
- package/dist/{dist-VD4L6V7Q.js → dist-ZBUKU2JG.js} +333 -63
- package/dist/entries/interop.cjs +23 -23
- package/dist/entries/interop.js +3 -3
- package/dist/entries/scripting.cjs +12 -11
- package/dist/entries/scripting.js +8 -7
- package/dist/evolution/index.cjs +66 -0
- package/dist/evolution/index.d.ts +162 -0
- package/dist/evolution/index.js +17 -0
- package/dist/hololand/index.cjs +41 -37
- package/dist/hololand/index.js +3 -3
- package/dist/index.cjs +12394 -3224
- package/dist/index.d.ts +260 -18
- package/dist/index.js +10755 -2182
- package/dist/keyword-registry/PluginKeywordRegistry.d.ts +138 -0
- package/dist/keyword-registry/index.cjs +29 -0
- package/dist/keyword-registry/index.d.ts +1 -0
- package/dist/keyword-registry/index.js +4 -0
- package/dist/math/tropical-spmv.cjs +11 -11
- package/dist/math/tropical-spmv.d.ts +1 -0
- package/dist/math/tropical-spmv.js +2 -2
- package/dist/math/tropicalSpmv.d.ts +43 -0
- package/dist/math/vec3.cjs +1 -1
- package/dist/math/vec3.js +1 -1
- package/dist/matter/StagedMatter.d.ts +82 -0
- package/dist/matter/index.cjs +17 -0
- package/dist/matter/index.d.ts +1 -0
- package/dist/matter/index.js +4 -0
- package/dist/{ml-dsa-Z7JDKW4L.js → ml-dsa-J6VKVT4K.js} +3 -3
- package/dist/{ml-dsa-FSKBTE3W.cjs → ml-dsa-MTIK4ZUQ.cjs} +23 -23
- package/dist/paper-0c-spike/index.cjs +1 -1
- package/dist/paper-0c-spike/index.js +1 -1
- package/dist/parameter-envelope/ParameterEnvelope.d.ts +128 -0
- package/dist/parameter-envelope/index.cjs +17 -0
- package/dist/parameter-envelope/index.d.ts +2 -0
- package/dist/parameter-envelope/index.js +4 -0
- package/dist/parser/HoloCompositionTypes.d.ts +2013 -0
- package/dist/parser/PipelineParser.d.ts +162 -0
- package/dist/parser.cjs +36 -20
- package/dist/parser.js +13 -9
- package/dist/plugin-manifest/ManifestAutoLoader.d.ts +35 -0
- package/dist/plugin-manifest/PluginManifest.d.ts +156 -0
- package/dist/plugin-manifest/index.cjs +25 -0
- package/dist/plugin-manifest/index.d.ts +2 -0
- package/dist/plugin-manifest/index.js +4 -0
- package/dist/plugin-solver-contract/PluginSolverContract.d.ts +288 -0
- package/dist/plugin-solver-contract/index.cjs +41 -0
- package/dist/plugin-solver-contract/index.d.ts +1 -0
- package/dist/plugin-solver-contract/index.js +4 -0
- package/dist/policy/ContentPolicyGate.d.ts +51 -0
- package/dist/policy/PolicyPack.d.ts +120 -0
- package/dist/policy/blocklist.d.ts +32 -0
- package/dist/policy/defaults.d.ts +44 -0
- package/dist/policy/index.cjs +106 -0
- package/dist/policy/index.d.ts +17 -0
- package/dist/policy/index.js +5 -0
- package/dist/policy/jsonLogic.d.ts +31 -0
- package/dist/policy/types.d.ts +174 -0
- package/dist/proof-composition/ProofCompositionLaw.d.ts +93 -0
- package/dist/proof-composition/index.cjs +25 -0
- package/dist/proof-composition/index.d.ts +1 -0
- package/dist/proof-composition/index.js +4 -0
- package/dist/receipts/hash-policy.d.ts +85 -0
- package/dist/reconstruction/index.cjs +75 -47
- package/dist/reconstruction/index.js +5 -5
- package/dist/runtime/plugin-trait-registrar.d.ts +36 -0
- package/dist/runtime.cjs +81 -79
- package/dist/runtime.js +9 -7
- package/dist/scene-presets-EDKYZ5GW.js +4 -0
- package/dist/scene-presets-NCW2CFRS.cjs +17 -0
- package/dist/self-improvement/index.cjs +1 -1
- package/dist/self-improvement/index.js +1 -1
- package/dist/{src-PT2FERDV.cjs → src-G3D7ZATQ.cjs} +545 -21
- package/dist/{src-J6CM4AUR.js → src-UXYA7HM6.js} +539 -16
- package/dist/storage/index.cjs +10 -10
- package/dist/storage/index.js +3 -3
- package/dist/testing.cjs +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/index.cjs +3631 -0
- package/dist/tools/index.js +3581 -0
- package/dist/traitDocs/traitDocs.cjs +9 -9
- package/dist/traitDocs/traitDocs.d.ts +1 -0
- package/dist/traitDocs/traitDocs.js +3 -3
- package/dist/traits/ControlLoopTrait.d.ts +154 -0
- package/dist/traits/KinematicChainTrait.d.ts +116 -0
- package/dist/traits/SensorSamplingTrait.d.ts +154 -0
- package/dist/traits/TraitTypes.d.ts +270 -0
- package/dist/traits/TransactionTrait.d.ts +121 -0
- package/dist/traits/botanical-lotus.cjs +54 -50
- package/dist/traits/botanical-lotus.js +2 -5
- package/dist/traits/control-loop.cjs +21 -0
- package/dist/traits/control-loop.d.ts +1 -0
- package/dist/traits/control-loop.js +4 -0
- package/dist/traits/engines/cloth-verlet.d.ts +65 -0
- package/dist/traits/engines/index.cjs +19 -19
- package/dist/traits/engines/index.d.ts +15 -275
- package/dist/traits/engines/index.js +3 -3
- package/dist/traits/engines/motion-data-schema.d.ts +119 -0
- package/dist/traits/engines/motion-matching.d.ts +102 -0
- package/dist/traits/engines/noise.d.ts +25 -0
- package/dist/traits/engines/onnx-adapter.d.ts +173 -0
- package/dist/traits/engines/onnx-motion-matching.d.ts +163 -0
- package/dist/traits/engines/pfnn-network.d.ts +92 -0
- package/dist/traits/engines/synthetic-walk-cycle.d.ts +38 -0
- package/dist/traits/engines/tensor-ops.d.ts +15 -0
- package/dist/traits/index.cjs +2789 -1259
- package/dist/traits/index.d.ts +35 -0
- package/dist/traits/index.js +1692 -411
- package/dist/traits/kinematic-chain.cjs +25 -0
- package/dist/traits/kinematic-chain.d.ts +1 -0
- package/dist/traits/kinematic-chain.js +4 -0
- package/dist/traits/sensor-sampling.cjs +33 -0
- package/dist/traits/sensor-sampling.d.ts +1 -0
- package/dist/traits/sensor-sampling.js +4 -0
- package/dist/traits/simulation-solver-factory.cjs +1 -1
- package/dist/traits/simulation-solver-factory.js +1 -1
- package/dist/traits/transaction.cjs +29 -0
- package/dist/traits/transaction.d.ts +1 -0
- package/dist/traits/transaction.js +4 -0
- package/dist/traits/webcam-gaze.cjs +1 -1
- package/dist/traits/webcam-gaze.js +1 -1
- package/dist/type-checker.cjs +7 -6
- package/dist/type-checker.js +5 -4
- package/dist/types/AdvancedTypeSystem.d.ts +489 -0
- package/dist/types/HoloScriptPlus.d.ts +810 -0
- package/dist/types/base.d.ts +248 -0
- package/dist/types.d.ts +799 -0
- package/dist/world/index.cjs +3 -4
- package/dist/world/index.js +3 -4
- package/dist/world-model/index.cjs +1 -1
- package/dist/world-model/index.js +1 -1
- package/dist/wot/index.cjs +1 -1
- package/dist/wot/index.js +1 -1
- package/package.json +80 -13
- package/dist/GLTFPipeline-3EJLL3TS.cjs +0 -38
- package/dist/GLTFPipeline-3OWJV2VK.js +0 -17
- package/dist/HoloScriptPlusParser-ADWRY5OS.cjs +0 -698
- package/dist/HoloScriptPlusParser-JLJDXHSC.js +0 -693
- package/dist/HoloScriptPlusParser-QCGFPWIL.cjs +0 -27
- package/dist/HoloScriptPlusParser-WENAFEFV.js +0 -10
- package/dist/HoloScriptRuntime-GGRVEU4T.cjs +0 -19
- package/dist/HoloScriptRuntime-GJK6VJPH.js +0 -10
- package/dist/chunk-22Q5A6RV.js +0 -979
- package/dist/chunk-4C57PR42.js +0 -468
- package/dist/chunk-4W5G5QVL.cjs +0 -847
- package/dist/chunk-5ET6L4NS.js +0 -487
- package/dist/chunk-C4GSLCHE.cjs +0 -7153
- package/dist/chunk-F2G6CWFT.js +0 -7089
- package/dist/chunk-G6PNEASF.cjs +0 -814
- package/dist/chunk-GTA4Q3A3.js +0 -1491
- package/dist/chunk-HCCQLAA4.cjs +0 -74
- package/dist/chunk-HFSVMKM3.cjs +0 -72
- package/dist/chunk-KAIVDEIG.cjs +0 -4539
- package/dist/chunk-KQU64CIX.cjs +0 -1508
- package/dist/chunk-MSYSQ2AJ.js +0 -4533
- package/dist/chunk-NHJLP37F.js +0 -70
- package/dist/chunk-NHYRRAGI.cjs +0 -490
- package/dist/chunk-OIQYIIO3.cjs +0 -473
- package/dist/chunk-OL46QLBJ.js +0 -61
- package/dist/chunk-SKZPC5AG.cjs +0 -984
- package/dist/chunk-SLHLXUMO.js +0 -445
- package/dist/chunk-UOIGFQXC.cjs +0 -9750
- package/dist/chunk-UOL6Y5TD.cjs +0 -759
- package/dist/chunk-UVFI7XIR.cjs +0 -452
- package/dist/chunk-VEPGBETF.js +0 -844
- package/dist/chunk-XQ5MWMP2.js +0 -756
- package/dist/chunk-XT6JY5W5.js +0 -9684
- package/dist/chunk-YRRAUB35.js +0 -811
- package/dist/compiler/GasOptimizationAnalyzer.d.ts +0 -73
- package/dist/compiler/NFTMarketplaceCompiler.d.ts +0 -23
- package/dist/compiler/ar.cjs +0 -23
- package/dist/compiler/ar.js +0 -11
- package/dist/compiler/babylon.cjs +0 -21
- package/dist/compiler/babylon.js +0 -15
- package/dist/compiler/multi-layer.cjs +0 -24
- package/dist/compiler/multi-layer.js +0 -15
- package/dist/compiler/playcanvas.cjs +0 -19
- package/dist/compiler/playcanvas.js +0 -13
- package/dist/compiler/r3f.cjs +0 -39
- package/dist/compiler/r3f.d.ts +0 -22
- package/dist/compiler/r3f.js +0 -17
- package/dist/compiler/vrr.cjs +0 -24
- package/dist/compiler/vrr.js +0 -12
- package/dist/parser/NFTMarketplaceTypes.d.ts +0 -191
- package/dist/playwright-3AT3AKTV.cjs +0 -98673
- package/dist/playwright-VZPDVUEQ.js +0 -98660
|
@@ -0,0 +1,3581 @@
|
|
|
1
|
+
import { __require } from '../chunk-W4GABFCR.js';
|
|
2
|
+
import * as readline from 'readline';
|
|
3
|
+
import { hexToRGBA, createDefaultMaterialDef, rgbaToHex } from '@holoscript/engine/rendering/MaterialLibrary';
|
|
4
|
+
export { createDefaultMaterialDef, hexToRGBA, rgbaToHex } from '@holoscript/engine/rendering/MaterialLibrary';
|
|
5
|
+
|
|
6
|
+
var ErrorFormatter = class {
|
|
7
|
+
/**
|
|
8
|
+
* Format error with source context and helpful visual indicators
|
|
9
|
+
*
|
|
10
|
+
* @param error - The error object containing message, location, and suggestion
|
|
11
|
+
* @param sourceCode - Optional source code to show contextual error location
|
|
12
|
+
* @returns Formatted error string with visual cues and source context
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const formatted = ErrorFormatter.formatError({
|
|
17
|
+
* message: "Unexpected token",
|
|
18
|
+
* location: { line: 5, column: 12 },
|
|
19
|
+
* suggestion: "Try adding a semicolon"
|
|
20
|
+
* }, sourceCode);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
static formatError(error, sourceCode) {
|
|
24
|
+
const { message, location, suggestion, token: _token } = error;
|
|
25
|
+
let formatted = `
|
|
26
|
+
\u274C Error: ${message}
|
|
27
|
+
`;
|
|
28
|
+
if (location) {
|
|
29
|
+
formatted += ` at line ${location.line}, column ${location.column}
|
|
30
|
+
`;
|
|
31
|
+
if (sourceCode) {
|
|
32
|
+
const lines = sourceCode.split("\n");
|
|
33
|
+
const errorLine = lines[location.line - 1];
|
|
34
|
+
if (errorLine) {
|
|
35
|
+
formatted += `
|
|
36
|
+
${location.line} | ${errorLine}
|
|
37
|
+
`;
|
|
38
|
+
formatted += ` | ${" ".repeat(location.column - 1)}^
|
|
39
|
+
`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (suggestion) {
|
|
44
|
+
formatted += `
|
|
45
|
+
\u{1F4A1} Suggestion: ${suggestion}
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
return formatted;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Format multiple errors with summary and limit display to first 5
|
|
52
|
+
*
|
|
53
|
+
* @param errors - Array of error objects to format
|
|
54
|
+
* @returns Formatted string with error count summary and individual error details
|
|
55
|
+
*/
|
|
56
|
+
static formatErrors(errors) {
|
|
57
|
+
if (errors.length === 0) return "";
|
|
58
|
+
let formatted = `
|
|
59
|
+
\u274C Found ${errors.length} error${errors.length !== 1 ? "s" : ""}:
|
|
60
|
+
`;
|
|
61
|
+
for (let i = 0; i < Math.min(errors.length, 5); i++) {
|
|
62
|
+
formatted += this.formatError(errors[i]);
|
|
63
|
+
}
|
|
64
|
+
if (errors.length > 5) {
|
|
65
|
+
formatted += `
|
|
66
|
+
... and ${errors.length - 5} more error${errors.length - 5 !== 1 ? "s" : ""}
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
return formatted;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Format success message
|
|
73
|
+
*/
|
|
74
|
+
static formatSuccess(message, details) {
|
|
75
|
+
let formatted = `
|
|
76
|
+
\u2705 ${message}
|
|
77
|
+
`;
|
|
78
|
+
if (details) {
|
|
79
|
+
if (typeof details === "object") {
|
|
80
|
+
formatted += JSON.stringify(details, null, 2) + "\n";
|
|
81
|
+
} else {
|
|
82
|
+
formatted += `${details}
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return formatted;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Format help text
|
|
90
|
+
*/
|
|
91
|
+
static formatHelp() {
|
|
92
|
+
return `
|
|
93
|
+
HoloScript+ REPL v1.0.0
|
|
94
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
95
|
+
|
|
96
|
+
Commands:
|
|
97
|
+
help - Show this help message
|
|
98
|
+
clear - Clear the screen
|
|
99
|
+
vars - List all variables
|
|
100
|
+
types - List all types
|
|
101
|
+
profile - Show performance profile
|
|
102
|
+
exit - Exit REPL
|
|
103
|
+
|
|
104
|
+
Shortcuts:
|
|
105
|
+
.create orb#name - Create orb
|
|
106
|
+
.position x y z - Set position
|
|
107
|
+
.property key val - Set property
|
|
108
|
+
|
|
109
|
+
Examples:
|
|
110
|
+
> orb#myOrb { position: [0, 0, 0] }
|
|
111
|
+
> myOrb.position = [1, 1, 1]
|
|
112
|
+
> match state { "idle" => { ... } }
|
|
113
|
+
|
|
114
|
+
For more info, visit: https://github.com/brianonbased-dev/holoscript
|
|
115
|
+
`.trim();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var HoloScriptREPL = class {
|
|
119
|
+
constructor(parser, runtime) {
|
|
120
|
+
this.variables = /* @__PURE__ */ new Map();
|
|
121
|
+
this.types = /* @__PURE__ */ new Map();
|
|
122
|
+
this.history = [];
|
|
123
|
+
this.parser = parser;
|
|
124
|
+
this.runtime = runtime;
|
|
125
|
+
this.rl = readline.createInterface({
|
|
126
|
+
input: process.stdin,
|
|
127
|
+
output: process.stdout,
|
|
128
|
+
history: this.history,
|
|
129
|
+
historySize: 100
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Start REPL
|
|
134
|
+
*/
|
|
135
|
+
async start() {
|
|
136
|
+
console.debug("\n\u{1F97D} HoloScript+ REPL v1.0.0");
|
|
137
|
+
console.debug('Type "help" for commands, "exit" to quit\n');
|
|
138
|
+
await this.repl();
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Main REPL loop
|
|
142
|
+
*/
|
|
143
|
+
async repl() {
|
|
144
|
+
const prompt = () => {
|
|
145
|
+
this.rl.question("> ", async (input) => {
|
|
146
|
+
try {
|
|
147
|
+
const trimmed = input.trim();
|
|
148
|
+
if (!trimmed) {
|
|
149
|
+
prompt();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (trimmed.startsWith(".")) {
|
|
153
|
+
this.handleCommand(trimmed);
|
|
154
|
+
prompt();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
switch (trimmed.toLowerCase()) {
|
|
158
|
+
case "help":
|
|
159
|
+
console.debug(ErrorFormatter.formatHelp());
|
|
160
|
+
prompt();
|
|
161
|
+
return;
|
|
162
|
+
case "clear":
|
|
163
|
+
console.clear();
|
|
164
|
+
prompt();
|
|
165
|
+
return;
|
|
166
|
+
case "vars":
|
|
167
|
+
this.showVariables();
|
|
168
|
+
prompt();
|
|
169
|
+
return;
|
|
170
|
+
case "types":
|
|
171
|
+
this.showTypes();
|
|
172
|
+
prompt();
|
|
173
|
+
return;
|
|
174
|
+
case "profile":
|
|
175
|
+
this.showProfile();
|
|
176
|
+
prompt();
|
|
177
|
+
return;
|
|
178
|
+
case "exit":
|
|
179
|
+
this.rl.close();
|
|
180
|
+
console.debug("\n\u{1F44B} Goodbye!\n");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const result = await this.evaluate(trimmed);
|
|
184
|
+
if (result !== void 0 && result !== null) {
|
|
185
|
+
this.displayResult(result);
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error(ErrorFormatter.formatError(error));
|
|
189
|
+
}
|
|
190
|
+
prompt();
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
prompt();
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Handle dot commands
|
|
197
|
+
*/
|
|
198
|
+
handleCommand(command) {
|
|
199
|
+
const parts = command.slice(1).split(" ");
|
|
200
|
+
const cmd = parts[0];
|
|
201
|
+
switch (cmd) {
|
|
202
|
+
case "create":
|
|
203
|
+
console.debug(`Creating: ${parts.slice(1).join(" ")}`);
|
|
204
|
+
break;
|
|
205
|
+
case "position":
|
|
206
|
+
console.debug(`Position: x=${parts[1]}, y=${parts[2]}, z=${parts[3]}`);
|
|
207
|
+
break;
|
|
208
|
+
case "property":
|
|
209
|
+
console.debug(`Property: ${parts[1]} = ${parts[2]}`);
|
|
210
|
+
break;
|
|
211
|
+
default:
|
|
212
|
+
console.debug(`Unknown command: .${cmd}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Evaluate HoloScript code
|
|
217
|
+
*/
|
|
218
|
+
async evaluate(code) {
|
|
219
|
+
try {
|
|
220
|
+
const parseResult = this.parser.parse(code);
|
|
221
|
+
if (parseResult.errors && parseResult.errors.length > 0) {
|
|
222
|
+
throw {
|
|
223
|
+
message: "Parse error",
|
|
224
|
+
errors: parseResult.errors
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const result = await this.runtime.execute(parseResult.ast);
|
|
228
|
+
return result;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Show variables
|
|
235
|
+
*/
|
|
236
|
+
showVariables() {
|
|
237
|
+
if (this.variables.size === 0) {
|
|
238
|
+
console.debug("No variables defined");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
console.debug("\n\u{1F4E6} Variables:");
|
|
242
|
+
for (const [name, value] of this.variables) {
|
|
243
|
+
console.debug(` ${name}: ${this.formatValue(value)}`);
|
|
244
|
+
}
|
|
245
|
+
console.debug();
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Show types
|
|
249
|
+
*/
|
|
250
|
+
showTypes() {
|
|
251
|
+
if (this.types.size === 0) {
|
|
252
|
+
console.debug("No custom types defined");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
console.debug("\n\u{1F3F7}\uFE0F Types:");
|
|
256
|
+
for (const [name, type] of this.types) {
|
|
257
|
+
console.debug(` ${name}: ${JSON.stringify(type)}`);
|
|
258
|
+
}
|
|
259
|
+
console.debug();
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Show performance profile
|
|
263
|
+
*/
|
|
264
|
+
showProfile() {
|
|
265
|
+
console.debug("\n\u23F1\uFE0F Performance Profile:");
|
|
266
|
+
console.debug(" (Profiling data would be displayed here)");
|
|
267
|
+
console.debug();
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Display result
|
|
271
|
+
*/
|
|
272
|
+
displayResult(result) {
|
|
273
|
+
const formatted = this.formatValue(result);
|
|
274
|
+
console.debug(`=> ${formatted}`);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Format value for display
|
|
278
|
+
*/
|
|
279
|
+
formatValue(value) {
|
|
280
|
+
if (value === null) return "null";
|
|
281
|
+
if (value === void 0) return "undefined";
|
|
282
|
+
if (typeof value === "string") return `"${value}"`;
|
|
283
|
+
if (typeof value === "number") return `${value}`;
|
|
284
|
+
if (typeof value === "boolean") return `${value}`;
|
|
285
|
+
if (Array.isArray(value)) {
|
|
286
|
+
return `[ ${value.map((v) => this.formatValue(v)).join(", ")} ]`;
|
|
287
|
+
}
|
|
288
|
+
if (typeof value === "object" && value !== null) {
|
|
289
|
+
const pairs = Object.entries(value).map(([k, v]) => `${k}: ${this.formatValue(v)}`).join(", ");
|
|
290
|
+
return `{ ${pairs} }`;
|
|
291
|
+
}
|
|
292
|
+
return String(value);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
async function startREPL(parser, runtime) {
|
|
296
|
+
const repl = new HoloScriptREPL(parser, runtime);
|
|
297
|
+
await repl.start();
|
|
298
|
+
}
|
|
299
|
+
var HotReloadWatcher = class {
|
|
300
|
+
constructor() {
|
|
301
|
+
this.watchers = /* @__PURE__ */ new Map();
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Watch file for changes
|
|
305
|
+
*/
|
|
306
|
+
watch(filePath, callback) {
|
|
307
|
+
const fs = __require("fs");
|
|
308
|
+
fs.watchFile(filePath, { interval: 1e3 }, async () => {
|
|
309
|
+
try {
|
|
310
|
+
console.debug(`
|
|
311
|
+
\u{1F504} Reloading: ${filePath}`);
|
|
312
|
+
await callback();
|
|
313
|
+
console.debug("\u2705 Reload complete\n");
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error(ErrorFormatter.formatError(error));
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
this.watchers.set(filePath, true);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Stop watching
|
|
322
|
+
*/
|
|
323
|
+
unwatch(filePath) {
|
|
324
|
+
const fs = __require("fs");
|
|
325
|
+
fs.unwatchFile(filePath);
|
|
326
|
+
this.watchers.delete(filePath);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Stop all watchers
|
|
330
|
+
*/
|
|
331
|
+
clear() {
|
|
332
|
+
const fs = __require("fs");
|
|
333
|
+
for (const filePath of this.watchers.keys()) {
|
|
334
|
+
fs.unwatchFile(filePath);
|
|
335
|
+
}
|
|
336
|
+
this.watchers.clear();
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
var SourceMapGenerator = class {
|
|
340
|
+
constructor() {
|
|
341
|
+
this.mappings = [];
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Add mapping
|
|
345
|
+
*/
|
|
346
|
+
addMapping(generatedLine, generatedColumn, sourceLine, sourceColumn, name) {
|
|
347
|
+
this.mappings.push({
|
|
348
|
+
generatedLine,
|
|
349
|
+
generatedColumn,
|
|
350
|
+
sourceLine,
|
|
351
|
+
sourceColumn,
|
|
352
|
+
name
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Generate source map
|
|
357
|
+
*/
|
|
358
|
+
generate(sourceFile, generatedFile) {
|
|
359
|
+
return {
|
|
360
|
+
version: 3,
|
|
361
|
+
file: generatedFile,
|
|
362
|
+
sourceRoot: "",
|
|
363
|
+
sources: [sourceFile],
|
|
364
|
+
sourcesContent: [],
|
|
365
|
+
mappings: this.encodeMappings(),
|
|
366
|
+
names: []
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Encode mappings in VLQ format
|
|
371
|
+
*/
|
|
372
|
+
encodeMappings() {
|
|
373
|
+
return this.mappings.map((m) => `${m.generatedLine}:${m.generatedColumn}->${m.sourceLine}:${m.sourceColumn}`).join(";");
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
var _MaterialEditor = class _MaterialEditor {
|
|
377
|
+
constructor(config = {}) {
|
|
378
|
+
this.materials = /* @__PURE__ */ new Map();
|
|
379
|
+
this.currentMaterialId = null;
|
|
380
|
+
this.container = null;
|
|
381
|
+
this.previewCanvas = null;
|
|
382
|
+
this.previewRenderer = null;
|
|
383
|
+
// THREE.WebGLRenderer
|
|
384
|
+
this.previewScene = null;
|
|
385
|
+
// THREE.Scene
|
|
386
|
+
this.previewCamera = null;
|
|
387
|
+
// THREE.Camera
|
|
388
|
+
this.previewSphere = null;
|
|
389
|
+
this.config = {
|
|
390
|
+
container: config.container,
|
|
391
|
+
livePreview: config.livePreview ?? true,
|
|
392
|
+
previewResolution: config.previewResolution ?? 512,
|
|
393
|
+
autoSave: config.autoSave ?? true,
|
|
394
|
+
debug: config.debug ?? false,
|
|
395
|
+
theme: config.theme ?? "dark"
|
|
396
|
+
};
|
|
397
|
+
if (this.config.container) {
|
|
398
|
+
this.initializeUI();
|
|
399
|
+
}
|
|
400
|
+
if (this.config.livePreview) {
|
|
401
|
+
this.initializePreview();
|
|
402
|
+
}
|
|
403
|
+
if (this.config.debug) {
|
|
404
|
+
console.debug("[MaterialEditor] Initialized", this.config);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Initialize editor UI
|
|
409
|
+
*/
|
|
410
|
+
initializeUI() {
|
|
411
|
+
if (!this.config.container) return;
|
|
412
|
+
this.container = this.config.container;
|
|
413
|
+
this.container.innerHTML = this.generateEditorHTML();
|
|
414
|
+
this.previewCanvas = this.container.querySelector(
|
|
415
|
+
"#material-preview-canvas"
|
|
416
|
+
);
|
|
417
|
+
this.attachEventListeners();
|
|
418
|
+
this.updatePresetsList();
|
|
419
|
+
if (this.config.debug) {
|
|
420
|
+
console.debug("[MaterialEditor] UI initialized");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Generate editor HTML
|
|
425
|
+
*/
|
|
426
|
+
generateEditorHTML() {
|
|
427
|
+
const theme = this.config.theme === "dark" ? "material-editor-dark" : "material-editor-light";
|
|
428
|
+
return `
|
|
429
|
+
<div class="material-editor ${theme}">
|
|
430
|
+
<!-- Preview Panel -->
|
|
431
|
+
<div class="preview-panel">
|
|
432
|
+
<div class="panel-header">
|
|
433
|
+
<h3>Preview</h3>
|
|
434
|
+
<div class="preview-controls">
|
|
435
|
+
<button class="preview-btn" data-action="rotate" title="Rotate">\u{1F504}</button>
|
|
436
|
+
<button class="preview-btn" data-action="reset" title="Reset View">\u21BB</button>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
<div class="preview-content">
|
|
440
|
+
<canvas id="material-preview-canvas"></canvas>
|
|
441
|
+
</div>
|
|
442
|
+
</div>
|
|
443
|
+
|
|
444
|
+
<!-- Properties Panel -->
|
|
445
|
+
<div class="properties-panel">
|
|
446
|
+
<div class="panel-header">
|
|
447
|
+
<h3>Material Properties</h3>
|
|
448
|
+
</div>
|
|
449
|
+
<div class="properties-content">
|
|
450
|
+
<div class="property-group">
|
|
451
|
+
<label>Material Type</label>
|
|
452
|
+
<select id="material-type" class="property-select">
|
|
453
|
+
<option value="standard">Standard</option>
|
|
454
|
+
<option value="physical">Physical</option>
|
|
455
|
+
<option value="metal">Metal</option>
|
|
456
|
+
<option value="glass">Glass</option>
|
|
457
|
+
<option value="emissive">Emissive</option>
|
|
458
|
+
<option value="toon">Toon</option>
|
|
459
|
+
</select>
|
|
460
|
+
</div>
|
|
461
|
+
|
|
462
|
+
<div class="property-group">
|
|
463
|
+
<label>
|
|
464
|
+
Base Color
|
|
465
|
+
<span class="value-display" id="color-value">#888888</span>
|
|
466
|
+
</label>
|
|
467
|
+
<input type="color" id="base-color" class="color-input" value="#888888">
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
<div class="property-group">
|
|
471
|
+
<label>
|
|
472
|
+
Metalness
|
|
473
|
+
<span class="value-display" id="metalness-value">0.5</span>
|
|
474
|
+
</label>
|
|
475
|
+
<input type="range" id="metalness" min="0" max="1" step="0.01" value="0.5" class="property-slider">
|
|
476
|
+
</div>
|
|
477
|
+
|
|
478
|
+
<div class="property-group">
|
|
479
|
+
<label>
|
|
480
|
+
Roughness
|
|
481
|
+
<span class="value-display" id="roughness-value">0.5</span>
|
|
482
|
+
</label>
|
|
483
|
+
<input type="range" id="roughness" min="0" max="1" step="0.01" value="0.5" class="property-slider">
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
<div class="property-group">
|
|
487
|
+
<label>
|
|
488
|
+
Opacity
|
|
489
|
+
<span class="value-display" id="opacity-value">1.0</span>
|
|
490
|
+
</label>
|
|
491
|
+
<input type="range" id="opacity" min="0" max="1" step="0.01" value="1.0" class="property-slider">
|
|
492
|
+
</div>
|
|
493
|
+
|
|
494
|
+
<div class="property-group" id="emissive-group" style="display: none;">
|
|
495
|
+
<label>
|
|
496
|
+
Emissive Color
|
|
497
|
+
<span class="value-display" id="emissive-color-value">#00ff00</span>
|
|
498
|
+
</label>
|
|
499
|
+
<input type="color" id="emissive-color" class="color-input" value="#00ff00">
|
|
500
|
+
</div>
|
|
501
|
+
|
|
502
|
+
<div class="property-group" id="emissive-intensity-group" style="display: none;">
|
|
503
|
+
<label>
|
|
504
|
+
Emissive Intensity
|
|
505
|
+
<span class="value-display" id="emissive-intensity-value">1.0</span>
|
|
506
|
+
</label>
|
|
507
|
+
<input type="range" id="emissive-intensity" min="0" max="5" step="0.1" value="1.0" class="property-slider">
|
|
508
|
+
</div>
|
|
509
|
+
|
|
510
|
+
<div class="property-group">
|
|
511
|
+
<button id="save-material-btn" class="action-btn">Save Material</button>
|
|
512
|
+
</div>
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
|
|
516
|
+
<!-- Presets Panel -->
|
|
517
|
+
<div class="presets-panel">
|
|
518
|
+
<div class="panel-header">
|
|
519
|
+
<h3>Material Presets</h3>
|
|
520
|
+
</div>
|
|
521
|
+
<div class="presets-content" id="presets-list">
|
|
522
|
+
<!-- Populated dynamically -->
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
|
|
527
|
+
<style>
|
|
528
|
+
.material-editor {
|
|
529
|
+
display: grid;
|
|
530
|
+
grid-template-columns: 1fr 350px;
|
|
531
|
+
grid-template-rows: 1fr 200px;
|
|
532
|
+
gap: 1px;
|
|
533
|
+
width: 100%;
|
|
534
|
+
height: 100%;
|
|
535
|
+
background: rgba(255, 255, 255, 0.05);
|
|
536
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.material-editor-dark {
|
|
540
|
+
background: #1a1a1a;
|
|
541
|
+
color: #e0e0e0;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.material-editor-light {
|
|
545
|
+
background: #f5f5f5;
|
|
546
|
+
color: #333;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.preview-panel {
|
|
550
|
+
grid-column: 1;
|
|
551
|
+
grid-row: 1 / 3;
|
|
552
|
+
display: flex;
|
|
553
|
+
flex-direction: column;
|
|
554
|
+
background: rgba(30, 30, 30, 0.95);
|
|
555
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.properties-panel {
|
|
559
|
+
grid-column: 2;
|
|
560
|
+
grid-row: 1;
|
|
561
|
+
display: flex;
|
|
562
|
+
flex-direction: column;
|
|
563
|
+
background: rgba(30, 30, 30, 0.95);
|
|
564
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.presets-panel {
|
|
568
|
+
grid-column: 2;
|
|
569
|
+
grid-row: 2;
|
|
570
|
+
display: flex;
|
|
571
|
+
flex-direction: column;
|
|
572
|
+
background: rgba(30, 30, 30, 0.95);
|
|
573
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.panel-header {
|
|
577
|
+
display: flex;
|
|
578
|
+
justify-content: space-between;
|
|
579
|
+
align-items: center;
|
|
580
|
+
padding: 10px 15px;
|
|
581
|
+
background: rgba(0, 0, 0, 0.2);
|
|
582
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.panel-header h3 {
|
|
586
|
+
margin: 0;
|
|
587
|
+
font-size: 14px;
|
|
588
|
+
font-weight: 600;
|
|
589
|
+
color: #60a5fa;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.preview-controls {
|
|
593
|
+
display: flex;
|
|
594
|
+
gap: 5px;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.preview-btn {
|
|
598
|
+
padding: 4px 8px;
|
|
599
|
+
background: rgba(96, 165, 250, 0.1);
|
|
600
|
+
border: 1px solid rgba(96, 165, 250, 0.3);
|
|
601
|
+
border-radius: 4px;
|
|
602
|
+
color: #60a5fa;
|
|
603
|
+
cursor: pointer;
|
|
604
|
+
font-size: 14px;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.preview-btn:hover {
|
|
608
|
+
background: rgba(96, 165, 250, 0.2);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.preview-content {
|
|
612
|
+
flex: 1;
|
|
613
|
+
display: flex;
|
|
614
|
+
align-items: center;
|
|
615
|
+
justify-content: center;
|
|
616
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
617
|
+
padding: 20px;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
#material-preview-canvas {
|
|
621
|
+
max-width: 100%;
|
|
622
|
+
max-height: 100%;
|
|
623
|
+
border-radius: 8px;
|
|
624
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.properties-content {
|
|
628
|
+
flex: 1;
|
|
629
|
+
overflow-y: auto;
|
|
630
|
+
padding: 15px;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.property-group {
|
|
634
|
+
margin-bottom: 20px;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.property-group label {
|
|
638
|
+
display: block;
|
|
639
|
+
font-size: 13px;
|
|
640
|
+
margin-bottom: 8px;
|
|
641
|
+
color: #cbd5e0;
|
|
642
|
+
font-weight: 500;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.value-display {
|
|
646
|
+
float: right;
|
|
647
|
+
color: #60a5fa;
|
|
648
|
+
font-size: 12px;
|
|
649
|
+
font-weight: 600;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.property-slider {
|
|
653
|
+
width: 100%;
|
|
654
|
+
height: 6px;
|
|
655
|
+
border-radius: 3px;
|
|
656
|
+
background: rgba(96, 165, 250, 0.2);
|
|
657
|
+
outline: none;
|
|
658
|
+
-webkit-appearance: none;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.property-slider::-webkit-slider-thumb {
|
|
662
|
+
-webkit-appearance: none;
|
|
663
|
+
width: 16px;
|
|
664
|
+
height: 16px;
|
|
665
|
+
border-radius: 50%;
|
|
666
|
+
background: #60a5fa;
|
|
667
|
+
cursor: pointer;
|
|
668
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.property-select {
|
|
672
|
+
width: 100%;
|
|
673
|
+
padding: 8px;
|
|
674
|
+
background: rgba(0, 0, 0, 0.3);
|
|
675
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
676
|
+
border-radius: 4px;
|
|
677
|
+
color: #fff;
|
|
678
|
+
font-size: 13px;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.color-input {
|
|
682
|
+
width: 100%;
|
|
683
|
+
height: 40px;
|
|
684
|
+
padding: 0;
|
|
685
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
686
|
+
border-radius: 4px;
|
|
687
|
+
background: transparent;
|
|
688
|
+
cursor: pointer;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.action-btn {
|
|
692
|
+
width: 100%;
|
|
693
|
+
padding: 10px;
|
|
694
|
+
background: rgba(34, 197, 94, 0.2);
|
|
695
|
+
border: 1px solid rgba(34, 197, 94, 0.4);
|
|
696
|
+
border-radius: 6px;
|
|
697
|
+
color: #4ade80;
|
|
698
|
+
cursor: pointer;
|
|
699
|
+
font-size: 13px;
|
|
700
|
+
font-weight: 500;
|
|
701
|
+
transition: all 0.2s;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.action-btn:hover {
|
|
705
|
+
background: rgba(34, 197, 94, 0.3);
|
|
706
|
+
border-color: rgba(34, 197, 94, 0.6);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.presets-content {
|
|
710
|
+
padding: 10px;
|
|
711
|
+
overflow-y: auto;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.presets-grid {
|
|
715
|
+
display: grid;
|
|
716
|
+
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
|
717
|
+
gap: 10px;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.preset-card {
|
|
721
|
+
padding: 10px;
|
|
722
|
+
background: rgba(0, 0, 0, 0.3);
|
|
723
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
724
|
+
border-radius: 6px;
|
|
725
|
+
cursor: pointer;
|
|
726
|
+
transition: all 0.2s;
|
|
727
|
+
text-align: center;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
.preset-card:hover {
|
|
731
|
+
background: rgba(96, 165, 250, 0.1);
|
|
732
|
+
border-color: rgba(96, 165, 250, 0.3);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.preset-color {
|
|
736
|
+
width: 100%;
|
|
737
|
+
height: 50px;
|
|
738
|
+
border-radius: 4px;
|
|
739
|
+
margin-bottom: 8px;
|
|
740
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
.preset-name {
|
|
744
|
+
font-size: 11px;
|
|
745
|
+
color: #94a3b8;
|
|
746
|
+
}
|
|
747
|
+
</style>
|
|
748
|
+
`;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Initialize 3D preview
|
|
752
|
+
*/
|
|
753
|
+
initializePreview() {
|
|
754
|
+
const THREE = globalThis.THREE;
|
|
755
|
+
if (!this.previewCanvas || !THREE) return;
|
|
756
|
+
this.previewScene = new THREE.Scene();
|
|
757
|
+
this.previewScene.background = null;
|
|
758
|
+
this.previewCamera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
|
|
759
|
+
this.previewCamera.position.set(0, 0, 5);
|
|
760
|
+
this.previewRenderer = new THREE.WebGLRenderer({
|
|
761
|
+
canvas: this.previewCanvas,
|
|
762
|
+
alpha: true,
|
|
763
|
+
antialias: true
|
|
764
|
+
});
|
|
765
|
+
this.previewRenderer.setSize(this.config.previewResolution, this.config.previewResolution);
|
|
766
|
+
this.previewRenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
767
|
+
const geometry = new THREE.SphereGeometry(1.5, 64, 64);
|
|
768
|
+
const material = new THREE.MeshStandardMaterial({
|
|
769
|
+
color: 8947848,
|
|
770
|
+
metalness: 0.5,
|
|
771
|
+
roughness: 0.5
|
|
772
|
+
});
|
|
773
|
+
this.previewSphere = new THREE.Mesh(geometry, material);
|
|
774
|
+
this.previewScene.add(this.previewSphere);
|
|
775
|
+
const ambientLight = new THREE.AmbientLight(16777215, 0.4);
|
|
776
|
+
this.previewScene.add(ambientLight);
|
|
777
|
+
const directionalLight = new THREE.DirectionalLight(16777215, 1);
|
|
778
|
+
directionalLight.position.set(5, 5, 5);
|
|
779
|
+
this.previewScene.add(directionalLight);
|
|
780
|
+
const rimLight = new THREE.DirectionalLight(6333946, 0.5);
|
|
781
|
+
rimLight.position.set(-5, 0, -5);
|
|
782
|
+
this.previewScene.add(rimLight);
|
|
783
|
+
this.animatePreview();
|
|
784
|
+
if (this.config.debug) {
|
|
785
|
+
console.debug("[MaterialEditor] Preview initialized");
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Animate preview sphere
|
|
790
|
+
*/
|
|
791
|
+
animatePreview() {
|
|
792
|
+
if (!this.previewRenderer || !this.previewScene || !this.previewCamera) return;
|
|
793
|
+
const animate = () => {
|
|
794
|
+
requestAnimationFrame(animate);
|
|
795
|
+
if (this.previewSphere) {
|
|
796
|
+
this.previewSphere.rotation[1] += 5e-3;
|
|
797
|
+
}
|
|
798
|
+
this.previewRenderer.render(this.previewScene, this.previewCamera);
|
|
799
|
+
};
|
|
800
|
+
animate();
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Attach event listeners
|
|
804
|
+
*/
|
|
805
|
+
attachEventListeners() {
|
|
806
|
+
if (!this.container) return;
|
|
807
|
+
const typeSelect = this.container.querySelector("#material-type");
|
|
808
|
+
typeSelect?.addEventListener("change", (e) => {
|
|
809
|
+
const type = e.target.value;
|
|
810
|
+
this.updateMaterialType(type);
|
|
811
|
+
});
|
|
812
|
+
const colorInput = this.container.querySelector("#base-color");
|
|
813
|
+
colorInput?.addEventListener("input", (e) => {
|
|
814
|
+
const color = e.target.value;
|
|
815
|
+
document.getElementById("color-value").textContent = color;
|
|
816
|
+
this.updatePreviewMaterial({ color });
|
|
817
|
+
});
|
|
818
|
+
const metalnessInput = this.container.querySelector("#metalness");
|
|
819
|
+
metalnessInput?.addEventListener("input", (e) => {
|
|
820
|
+
const value = parseFloat(e.target.value);
|
|
821
|
+
document.getElementById("metalness-value").textContent = value.toFixed(2);
|
|
822
|
+
this.updatePreviewMaterial({ metalness: value });
|
|
823
|
+
});
|
|
824
|
+
const roughnessInput = this.container.querySelector("#roughness");
|
|
825
|
+
roughnessInput?.addEventListener("input", (e) => {
|
|
826
|
+
const value = parseFloat(e.target.value);
|
|
827
|
+
document.getElementById("roughness-value").textContent = value.toFixed(2);
|
|
828
|
+
this.updatePreviewMaterial({ roughness: value });
|
|
829
|
+
});
|
|
830
|
+
const opacityInput = this.container.querySelector("#opacity");
|
|
831
|
+
opacityInput?.addEventListener("input", (e) => {
|
|
832
|
+
const value = parseFloat(e.target.value);
|
|
833
|
+
document.getElementById("opacity-value").textContent = value.toFixed(2);
|
|
834
|
+
this.updatePreviewMaterial({ opacity: value, transparent: value < 1 });
|
|
835
|
+
});
|
|
836
|
+
const emissiveColorInput = this.container.querySelector("#emissive-color");
|
|
837
|
+
emissiveColorInput?.addEventListener("input", (e) => {
|
|
838
|
+
const color = e.target.value;
|
|
839
|
+
document.getElementById("emissive-color-value").textContent = color;
|
|
840
|
+
this.updatePreviewMaterial({ emissive: color });
|
|
841
|
+
});
|
|
842
|
+
const emissiveIntensityInput = this.container.querySelector(
|
|
843
|
+
"#emissive-intensity"
|
|
844
|
+
);
|
|
845
|
+
emissiveIntensityInput?.addEventListener("input", (e) => {
|
|
846
|
+
const value = parseFloat(e.target.value);
|
|
847
|
+
document.getElementById("emissive-intensity-value").textContent = value.toFixed(1);
|
|
848
|
+
this.updatePreviewMaterial({ emissiveIntensity: value });
|
|
849
|
+
});
|
|
850
|
+
const saveBtn = this.container.querySelector("#save-material-btn");
|
|
851
|
+
saveBtn?.addEventListener("click", () => {
|
|
852
|
+
this.saveMaterial();
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Update material type
|
|
857
|
+
*/
|
|
858
|
+
updateMaterialType(type) {
|
|
859
|
+
const emissiveGroup = document.getElementById("emissive-group");
|
|
860
|
+
const emissiveIntensityGroup = document.getElementById("emissive-intensity-group");
|
|
861
|
+
if (type === "emissive") {
|
|
862
|
+
if (emissiveGroup) emissiveGroup.style.display = "block";
|
|
863
|
+
if (emissiveIntensityGroup) emissiveIntensityGroup.style.display = "block";
|
|
864
|
+
} else {
|
|
865
|
+
if (emissiveGroup) emissiveGroup.style.display = "none";
|
|
866
|
+
if (emissiveIntensityGroup) emissiveIntensityGroup.style.display = "none";
|
|
867
|
+
}
|
|
868
|
+
if (this.config.debug) {
|
|
869
|
+
console.debug("[MaterialEditor] Material type changed:", type);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Update preview material (THREE.js sphere).
|
|
874
|
+
* Accepts hex color strings for THREE.js Color.set() compatibility.
|
|
875
|
+
*/
|
|
876
|
+
updatePreviewMaterial(params) {
|
|
877
|
+
if (!this.previewSphere) return;
|
|
878
|
+
const material = this.previewSphere.material;
|
|
879
|
+
if (params.color !== void 0) {
|
|
880
|
+
material.color.set(params.color);
|
|
881
|
+
}
|
|
882
|
+
if (params.metalness !== void 0) {
|
|
883
|
+
material.metalness = params.metalness;
|
|
884
|
+
}
|
|
885
|
+
if (params.roughness !== void 0) {
|
|
886
|
+
material.roughness = params.roughness;
|
|
887
|
+
}
|
|
888
|
+
if (params.opacity !== void 0) {
|
|
889
|
+
material.opacity = params.opacity;
|
|
890
|
+
}
|
|
891
|
+
if (params.transparent !== void 0) {
|
|
892
|
+
material.transparent = params.transparent;
|
|
893
|
+
}
|
|
894
|
+
if (params.emissive !== void 0) {
|
|
895
|
+
material.emissive.set(params.emissive);
|
|
896
|
+
}
|
|
897
|
+
if (params.emissiveIntensity !== void 0) {
|
|
898
|
+
material.emissiveIntensity = params.emissiveIntensity;
|
|
899
|
+
}
|
|
900
|
+
material.needsUpdate = true;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Save current material.
|
|
904
|
+
* Returns a fully-formed MaterialDef (the unified PBR type).
|
|
905
|
+
*/
|
|
906
|
+
saveMaterial() {
|
|
907
|
+
const id = `material_${Date.now()}`;
|
|
908
|
+
const material = this.getCurrentMaterial();
|
|
909
|
+
material.id = id;
|
|
910
|
+
this.materials.set(id, material);
|
|
911
|
+
if (this.config.debug) {
|
|
912
|
+
console.debug("[MaterialEditor] Material saved:", material);
|
|
913
|
+
}
|
|
914
|
+
return material;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Get current material configuration from the editor UI.
|
|
918
|
+
* Reads hex color values from DOM inputs and converts to RGBA objects
|
|
919
|
+
* for the unified MaterialDef type.
|
|
920
|
+
*/
|
|
921
|
+
getCurrentMaterial() {
|
|
922
|
+
const typeSelect = document.getElementById("material-type");
|
|
923
|
+
const colorInput = document.getElementById("base-color");
|
|
924
|
+
const metalnessInput = document.getElementById("metalness");
|
|
925
|
+
const roughnessInput = document.getElementById("roughness");
|
|
926
|
+
const opacityInput = document.getElementById("opacity");
|
|
927
|
+
const emissiveColorInput = document.getElementById("emissive-color");
|
|
928
|
+
const emissiveIntensityInput = document.getElementById(
|
|
929
|
+
"emissive-intensity"
|
|
930
|
+
);
|
|
931
|
+
const materialType = typeSelect?.value || "standard";
|
|
932
|
+
const colorHex = colorInput?.value || "#888888";
|
|
933
|
+
const albedo = hexToRGBA(colorHex);
|
|
934
|
+
const opacity = parseFloat(opacityInput?.value || "1.0");
|
|
935
|
+
albedo.a = opacity;
|
|
936
|
+
const material = createDefaultMaterialDef("", "Custom Material", materialType);
|
|
937
|
+
material.albedo = albedo;
|
|
938
|
+
material.metallic = parseFloat(metalnessInput?.value || "0.5");
|
|
939
|
+
material.roughness = parseFloat(roughnessInput?.value || "0.5");
|
|
940
|
+
if (opacity < 1) {
|
|
941
|
+
material.blendMode = "transparent";
|
|
942
|
+
}
|
|
943
|
+
if (materialType === "emissive") {
|
|
944
|
+
const emissiveHex = emissiveColorInput?.value || "#00ff00";
|
|
945
|
+
const emissiveRgba = hexToRGBA(emissiveHex);
|
|
946
|
+
material.emission = { r: emissiveRgba.r, g: emissiveRgba.g, b: emissiveRgba.b };
|
|
947
|
+
material.emissionStrength = parseFloat(emissiveIntensityInput?.value || "1.0");
|
|
948
|
+
}
|
|
949
|
+
return material;
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Load a MaterialDef into the editor UI.
|
|
953
|
+
* Converts RGBA objects back to hex strings for DOM color inputs.
|
|
954
|
+
*/
|
|
955
|
+
loadMaterial(material) {
|
|
956
|
+
this.currentMaterialId = material.id;
|
|
957
|
+
const colorHex = rgbaToHex(material.albedo);
|
|
958
|
+
const emissiveHex = rgbaToHex(material.emission);
|
|
959
|
+
const typeSelect = document.getElementById("material-type");
|
|
960
|
+
const colorInput = document.getElementById("base-color");
|
|
961
|
+
const metalnessInput = document.getElementById("metalness");
|
|
962
|
+
const roughnessInput = document.getElementById("roughness");
|
|
963
|
+
const opacityInput = document.getElementById("opacity");
|
|
964
|
+
const emissiveColorInput = document.getElementById("emissive-color");
|
|
965
|
+
const emissiveIntensityInput = document.getElementById(
|
|
966
|
+
"emissive-intensity"
|
|
967
|
+
);
|
|
968
|
+
if (typeSelect) typeSelect.value = material.materialType || "standard";
|
|
969
|
+
if (colorInput) colorInput.value = colorHex;
|
|
970
|
+
if (metalnessInput) metalnessInput.value = material.metallic.toString();
|
|
971
|
+
if (roughnessInput) roughnessInput.value = material.roughness.toString();
|
|
972
|
+
if (opacityInput) opacityInput.value = (material.albedo.a ?? 1).toString();
|
|
973
|
+
if (emissiveColorInput) {
|
|
974
|
+
emissiveColorInput.value = emissiveHex;
|
|
975
|
+
}
|
|
976
|
+
if (emissiveIntensityInput) {
|
|
977
|
+
emissiveIntensityInput.value = material.emissionStrength.toString();
|
|
978
|
+
}
|
|
979
|
+
document.getElementById("color-value").textContent = colorHex;
|
|
980
|
+
document.getElementById("metalness-value").textContent = material.metallic.toFixed(2);
|
|
981
|
+
document.getElementById("roughness-value").textContent = material.roughness.toFixed(2);
|
|
982
|
+
document.getElementById("opacity-value").textContent = (material.albedo.a ?? 1).toFixed(2);
|
|
983
|
+
if (material.emissionStrength > 0) {
|
|
984
|
+
document.getElementById("emissive-color-value").textContent = emissiveHex;
|
|
985
|
+
document.getElementById("emissive-intensity-value").textContent = material.emissionStrength.toFixed(1);
|
|
986
|
+
}
|
|
987
|
+
this.updateMaterialType(material.materialType || "standard");
|
|
988
|
+
this.updatePreviewMaterial({
|
|
989
|
+
color: colorHex,
|
|
990
|
+
metalness: material.metallic,
|
|
991
|
+
roughness: material.roughness,
|
|
992
|
+
opacity: material.albedo.a ?? 1,
|
|
993
|
+
transparent: material.blendMode === "transparent",
|
|
994
|
+
emissive: emissiveHex,
|
|
995
|
+
emissiveIntensity: material.emissionStrength
|
|
996
|
+
});
|
|
997
|
+
if (this.config.debug) {
|
|
998
|
+
console.debug("[MaterialEditor] Material loaded:", material.name);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Load a preset by name.
|
|
1003
|
+
* Merges preset overrides with default MaterialDef values.
|
|
1004
|
+
*/
|
|
1005
|
+
loadPreset(presetName) {
|
|
1006
|
+
const preset = _MaterialEditor.PRESETS.find((p) => p.name === presetName);
|
|
1007
|
+
if (!preset) return;
|
|
1008
|
+
const material = {
|
|
1009
|
+
...createDefaultMaterialDef(
|
|
1010
|
+
`preset_${presetName.toLowerCase()}`,
|
|
1011
|
+
presetName,
|
|
1012
|
+
preset.material.materialType
|
|
1013
|
+
),
|
|
1014
|
+
...preset.material,
|
|
1015
|
+
id: `preset_${presetName.toLowerCase()}`,
|
|
1016
|
+
name: presetName
|
|
1017
|
+
};
|
|
1018
|
+
this.loadMaterial(material);
|
|
1019
|
+
if (this.config.debug) {
|
|
1020
|
+
console.debug("[MaterialEditor] Preset loaded:", presetName);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Update presets list
|
|
1025
|
+
*/
|
|
1026
|
+
updatePresetsList() {
|
|
1027
|
+
const presetsList = document.getElementById("presets-list");
|
|
1028
|
+
if (!presetsList) return;
|
|
1029
|
+
let html = '<div class="presets-grid">';
|
|
1030
|
+
for (const preset of _MaterialEditor.PRESETS) {
|
|
1031
|
+
html += `
|
|
1032
|
+
<div class="preset-card" data-preset="${preset.name}">
|
|
1033
|
+
<div class="preset-color" style="background: ${preset.previewColor};"></div>
|
|
1034
|
+
<div class="preset-name">${preset.name}</div>
|
|
1035
|
+
</div>
|
|
1036
|
+
`;
|
|
1037
|
+
}
|
|
1038
|
+
html += "</div>";
|
|
1039
|
+
presetsList.innerHTML = html;
|
|
1040
|
+
presetsList.querySelectorAll(".preset-card").forEach((card) => {
|
|
1041
|
+
card.addEventListener("click", (e) => {
|
|
1042
|
+
const presetName = e.currentTarget.getAttribute("data-preset");
|
|
1043
|
+
if (presetName) {
|
|
1044
|
+
this.loadPreset(presetName);
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Get all materials (returns unified MaterialDef objects)
|
|
1051
|
+
*/
|
|
1052
|
+
getMaterials() {
|
|
1053
|
+
return Array.from(this.materials.values());
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Get presets
|
|
1057
|
+
*/
|
|
1058
|
+
static getPresets() {
|
|
1059
|
+
return _MaterialEditor.PRESETS;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Dispose editor
|
|
1063
|
+
*/
|
|
1064
|
+
dispose() {
|
|
1065
|
+
if (this.previewRenderer) {
|
|
1066
|
+
this.previewRenderer.dispose();
|
|
1067
|
+
this.previewRenderer = null;
|
|
1068
|
+
}
|
|
1069
|
+
this.materials.clear();
|
|
1070
|
+
if (this.config.debug) {
|
|
1071
|
+
console.debug("[MaterialEditor] Disposed");
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
// THREE.Mesh
|
|
1076
|
+
// Material presets (using unified MaterialDef schema)
|
|
1077
|
+
_MaterialEditor.PRESETS = [
|
|
1078
|
+
{
|
|
1079
|
+
name: "Concrete",
|
|
1080
|
+
category: "Construction",
|
|
1081
|
+
material: {
|
|
1082
|
+
materialType: "standard",
|
|
1083
|
+
albedo: { r: 0.502, g: 0.502, b: 0.502, a: 1 },
|
|
1084
|
+
metallic: 0,
|
|
1085
|
+
roughness: 0.9
|
|
1086
|
+
},
|
|
1087
|
+
previewColor: "#808080"
|
|
1088
|
+
},
|
|
1089
|
+
{
|
|
1090
|
+
name: "Metal",
|
|
1091
|
+
category: "Metal",
|
|
1092
|
+
material: {
|
|
1093
|
+
materialType: "metal",
|
|
1094
|
+
albedo: { r: 0.533, g: 0.533, b: 0.533, a: 1 },
|
|
1095
|
+
metallic: 1,
|
|
1096
|
+
roughness: 0.2
|
|
1097
|
+
},
|
|
1098
|
+
previewColor: "#888888"
|
|
1099
|
+
},
|
|
1100
|
+
{
|
|
1101
|
+
name: "Gold",
|
|
1102
|
+
category: "Metal",
|
|
1103
|
+
material: {
|
|
1104
|
+
materialType: "metal",
|
|
1105
|
+
albedo: { r: 1, g: 0.843, b: 0, a: 1 },
|
|
1106
|
+
metallic: 1,
|
|
1107
|
+
roughness: 0.15
|
|
1108
|
+
},
|
|
1109
|
+
previewColor: "#ffd700"
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
name: "Copper",
|
|
1113
|
+
category: "Metal",
|
|
1114
|
+
material: {
|
|
1115
|
+
materialType: "metal",
|
|
1116
|
+
albedo: { r: 0.722, g: 0.451, b: 0.2, a: 1 },
|
|
1117
|
+
metallic: 1,
|
|
1118
|
+
roughness: 0.25
|
|
1119
|
+
},
|
|
1120
|
+
previewColor: "#b87333"
|
|
1121
|
+
},
|
|
1122
|
+
{
|
|
1123
|
+
name: "Chrome",
|
|
1124
|
+
category: "Metal",
|
|
1125
|
+
material: {
|
|
1126
|
+
materialType: "metal",
|
|
1127
|
+
albedo: { r: 0.8, g: 0.8, b: 0.8, a: 1 },
|
|
1128
|
+
metallic: 1,
|
|
1129
|
+
roughness: 0.05
|
|
1130
|
+
},
|
|
1131
|
+
previewColor: "#cccccc"
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
name: "Wood",
|
|
1135
|
+
category: "Natural",
|
|
1136
|
+
material: {
|
|
1137
|
+
materialType: "standard",
|
|
1138
|
+
albedo: { r: 0.545, g: 0.435, b: 0.278, a: 1 },
|
|
1139
|
+
metallic: 0,
|
|
1140
|
+
roughness: 0.7
|
|
1141
|
+
},
|
|
1142
|
+
previewColor: "#8b6f47"
|
|
1143
|
+
},
|
|
1144
|
+
{
|
|
1145
|
+
name: "Plastic",
|
|
1146
|
+
category: "Synthetic",
|
|
1147
|
+
material: {
|
|
1148
|
+
materialType: "physical",
|
|
1149
|
+
albedo: { r: 1, g: 0.42, b: 0.42, a: 1 },
|
|
1150
|
+
metallic: 0,
|
|
1151
|
+
roughness: 0.4
|
|
1152
|
+
},
|
|
1153
|
+
previewColor: "#ff6b6b"
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
name: "Glass",
|
|
1157
|
+
category: "Transparent",
|
|
1158
|
+
material: {
|
|
1159
|
+
materialType: "glass",
|
|
1160
|
+
albedo: { r: 1, g: 1, b: 1, a: 0.3 },
|
|
1161
|
+
metallic: 0,
|
|
1162
|
+
roughness: 0,
|
|
1163
|
+
blendMode: "transparent",
|
|
1164
|
+
doubleSided: true
|
|
1165
|
+
},
|
|
1166
|
+
previewColor: "rgba(255, 255, 255, 0.3)"
|
|
1167
|
+
},
|
|
1168
|
+
{
|
|
1169
|
+
name: "Rubber",
|
|
1170
|
+
category: "Synthetic",
|
|
1171
|
+
material: {
|
|
1172
|
+
materialType: "standard",
|
|
1173
|
+
albedo: { r: 0.2, g: 0.2, b: 0.2, a: 1 },
|
|
1174
|
+
metallic: 0,
|
|
1175
|
+
roughness: 0.8
|
|
1176
|
+
},
|
|
1177
|
+
previewColor: "#333333"
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
name: "Ceramic",
|
|
1181
|
+
category: "Natural",
|
|
1182
|
+
material: {
|
|
1183
|
+
materialType: "physical",
|
|
1184
|
+
albedo: { r: 0.941, g: 0.941, b: 0.941, a: 1 },
|
|
1185
|
+
metallic: 0,
|
|
1186
|
+
roughness: 0.3
|
|
1187
|
+
},
|
|
1188
|
+
previewColor: "#f0f0f0"
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
name: "Neon",
|
|
1192
|
+
category: "Emissive",
|
|
1193
|
+
material: {
|
|
1194
|
+
materialType: "emissive",
|
|
1195
|
+
albedo: { r: 0, g: 1, b: 0, a: 1 },
|
|
1196
|
+
emission: { r: 0, g: 1, b: 0 },
|
|
1197
|
+
emissionStrength: 2
|
|
1198
|
+
},
|
|
1199
|
+
previewColor: "#00ff00"
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
name: "Glow",
|
|
1203
|
+
category: "Emissive",
|
|
1204
|
+
material: {
|
|
1205
|
+
materialType: "emissive",
|
|
1206
|
+
albedo: { r: 0.376, g: 0.647, b: 0.98, a: 1 },
|
|
1207
|
+
emission: { r: 0.376, g: 0.647, b: 0.98 },
|
|
1208
|
+
emissionStrength: 1.5
|
|
1209
|
+
},
|
|
1210
|
+
previewColor: "#60a5fa"
|
|
1211
|
+
}
|
|
1212
|
+
];
|
|
1213
|
+
var MaterialEditor = _MaterialEditor;
|
|
1214
|
+
|
|
1215
|
+
// src/tools/materialPresetQuickPick.ts
|
|
1216
|
+
function getMaterialEditorBuiltinPresets() {
|
|
1217
|
+
return MaterialEditor.getPresets();
|
|
1218
|
+
}
|
|
1219
|
+
function listMaterialEditorQuickPickPresetNames() {
|
|
1220
|
+
return MaterialEditor.getPresets().map((p) => p.name);
|
|
1221
|
+
}
|
|
1222
|
+
function listMaterialEditorQuickPickPresetsByCategory() {
|
|
1223
|
+
const m = /* @__PURE__ */ new Map();
|
|
1224
|
+
for (const p of MaterialEditor.getPresets()) {
|
|
1225
|
+
const list = m.get(p.category) ?? [];
|
|
1226
|
+
list.push(p.name);
|
|
1227
|
+
m.set(p.category, list);
|
|
1228
|
+
}
|
|
1229
|
+
return m;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/tools/SceneInspector.ts
|
|
1233
|
+
var SceneInspector = class {
|
|
1234
|
+
constructor(config = {}) {
|
|
1235
|
+
this.composition = null;
|
|
1236
|
+
this.renderer = null;
|
|
1237
|
+
this.entities = /* @__PURE__ */ new Map();
|
|
1238
|
+
this.selectedEntityId = null;
|
|
1239
|
+
this.performanceHistory = [];
|
|
1240
|
+
this.currentFrame = 0;
|
|
1241
|
+
this.lastUpdateTime = 0;
|
|
1242
|
+
this.physicsVisualization = {
|
|
1243
|
+
showBounds: false,
|
|
1244
|
+
showVelocity: false,
|
|
1245
|
+
showForces: false,
|
|
1246
|
+
showContacts: false,
|
|
1247
|
+
showCenterOfMass: false
|
|
1248
|
+
};
|
|
1249
|
+
this.config = {
|
|
1250
|
+
enabled: config.enabled ?? true,
|
|
1251
|
+
showPerformance: config.showPerformance ?? true,
|
|
1252
|
+
showHierarchy: config.showHierarchy ?? true,
|
|
1253
|
+
showProperties: config.showProperties ?? true,
|
|
1254
|
+
showStatistics: config.showStatistics ?? true,
|
|
1255
|
+
performanceHistorySize: config.performanceHistorySize ?? 300,
|
|
1256
|
+
updateFrequency: config.updateFrequency ?? 10
|
|
1257
|
+
// 10 Hz
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Initialize inspector with composition
|
|
1262
|
+
*/
|
|
1263
|
+
initialize(composition, renderer) {
|
|
1264
|
+
this.composition = composition;
|
|
1265
|
+
this.renderer = renderer || null;
|
|
1266
|
+
this.buildEntityHierarchy();
|
|
1267
|
+
console.debug("[SceneInspector] Initialized with", this.entities.size, "entities");
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Build entity hierarchy from composition
|
|
1271
|
+
*/
|
|
1272
|
+
buildEntityHierarchy() {
|
|
1273
|
+
if (!this.composition) return;
|
|
1274
|
+
this.entities.clear();
|
|
1275
|
+
for (const obj of this.composition.objects || []) {
|
|
1276
|
+
const getProp = (key) => obj.properties?.find((p) => p.key === key)?.value;
|
|
1277
|
+
const pos = getProp("position");
|
|
1278
|
+
const rot = getProp("rotation");
|
|
1279
|
+
const scl = getProp("scale");
|
|
1280
|
+
const inspectorEntity = {
|
|
1281
|
+
id: obj.name,
|
|
1282
|
+
name: obj.name,
|
|
1283
|
+
type: obj.type || "entity",
|
|
1284
|
+
parentId: null,
|
|
1285
|
+
// HoloScript doesn't have hierarchy yet, all root-level
|
|
1286
|
+
childrenIds: [],
|
|
1287
|
+
transform: {
|
|
1288
|
+
// @ts-expect-error
|
|
1289
|
+
position: pos ? [pos[0], pos[1], pos[2]] : [0, 0, 0],
|
|
1290
|
+
// @ts-expect-error
|
|
1291
|
+
rotation: rot ? [rot[0], rot[1], rot[2]] : [0, 0, 0],
|
|
1292
|
+
// @ts-expect-error
|
|
1293
|
+
scale: scl ? [scl[0], scl[1], scl[2]] : [1, 1, 1]
|
|
1294
|
+
},
|
|
1295
|
+
properties: this.extractProperties(obj),
|
|
1296
|
+
// @ts-expect-error
|
|
1297
|
+
traits: obj.traits?.map((t) => t.name) || [],
|
|
1298
|
+
visible: true,
|
|
1299
|
+
active: true
|
|
1300
|
+
};
|
|
1301
|
+
this.entities.set(inspectorEntity.id, inspectorEntity);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Extract properties from entity
|
|
1306
|
+
*/
|
|
1307
|
+
extractProperties(entity) {
|
|
1308
|
+
const props = {};
|
|
1309
|
+
for (const prop of entity.properties || []) {
|
|
1310
|
+
if (["geometry", "material"].includes(prop.key)) {
|
|
1311
|
+
props[prop.key] = prop.value;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
for (const trait of entity.traits || []) {
|
|
1315
|
+
if (trait.config) {
|
|
1316
|
+
props[trait.name] = trait.config;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
return props;
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Update inspector (call per frame)
|
|
1323
|
+
*/
|
|
1324
|
+
update(deltaTime) {
|
|
1325
|
+
if (!this.config.enabled) return;
|
|
1326
|
+
this.currentFrame++;
|
|
1327
|
+
const now = performance.now();
|
|
1328
|
+
if (now - this.lastUpdateTime >= 1e3 / this.config.updateFrequency) {
|
|
1329
|
+
this.recordPerformanceFrame(deltaTime);
|
|
1330
|
+
this.lastUpdateTime = now;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Record performance frame
|
|
1335
|
+
*/
|
|
1336
|
+
recordPerformanceFrame(deltaTime) {
|
|
1337
|
+
const stats = this.renderer?.getStatistics();
|
|
1338
|
+
const frame = {
|
|
1339
|
+
frameNumber: this.currentFrame,
|
|
1340
|
+
frameTime: deltaTime * 1e3,
|
|
1341
|
+
// Convert to ms
|
|
1342
|
+
fps: stats?.fps || 0,
|
|
1343
|
+
physicsTime: 0,
|
|
1344
|
+
// Would come from physics engine
|
|
1345
|
+
renderTime: deltaTime * 1e3 * 0.6,
|
|
1346
|
+
// Estimate
|
|
1347
|
+
syncTime: deltaTime * 1e3 * 0.1,
|
|
1348
|
+
// Estimate
|
|
1349
|
+
timestamp: performance.now()
|
|
1350
|
+
};
|
|
1351
|
+
this.performanceHistory.push(frame);
|
|
1352
|
+
if (this.performanceHistory.length > this.config.performanceHistorySize) {
|
|
1353
|
+
this.performanceHistory.shift();
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Get entity hierarchy (for tree view)
|
|
1358
|
+
*/
|
|
1359
|
+
getEntityHierarchy() {
|
|
1360
|
+
return Array.from(this.entities.values());
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Get entity by ID
|
|
1364
|
+
*/
|
|
1365
|
+
getEntity(id) {
|
|
1366
|
+
return this.entities.get(id);
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Select entity
|
|
1370
|
+
*/
|
|
1371
|
+
selectEntity(id) {
|
|
1372
|
+
if (this.entities.has(id)) {
|
|
1373
|
+
this.selectedEntityId = id;
|
|
1374
|
+
console.debug("[SceneInspector] Selected entity:", id);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Get selected entity
|
|
1379
|
+
*/
|
|
1380
|
+
getSelectedEntity() {
|
|
1381
|
+
return this.selectedEntityId ? this.entities.get(this.selectedEntityId) || null : null;
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Update entity property (live editing)
|
|
1385
|
+
*/
|
|
1386
|
+
updateEntityProperty(id, path, value) {
|
|
1387
|
+
const entity = this.entities.get(id);
|
|
1388
|
+
if (!entity) return false;
|
|
1389
|
+
const parts = path.split(".");
|
|
1390
|
+
let target = entity;
|
|
1391
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1392
|
+
target = target[parts[i]];
|
|
1393
|
+
if (!target) return false;
|
|
1394
|
+
}
|
|
1395
|
+
const lastKey = parts[parts.length - 1];
|
|
1396
|
+
target[lastKey] = value;
|
|
1397
|
+
console.debug("[SceneInspector] Updated", id, path, "=", value);
|
|
1398
|
+
return true;
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Get scene statistics
|
|
1402
|
+
*/
|
|
1403
|
+
getStatistics() {
|
|
1404
|
+
const rendererStats = this.renderer?.getStatistics();
|
|
1405
|
+
const recentFrames = this.performanceHistory.slice(-60);
|
|
1406
|
+
const fpsList = recentFrames.map((f) => f.fps).filter((fps) => fps > 0);
|
|
1407
|
+
const averageFPS = fpsList.length > 0 ? fpsList.reduce((a, b) => a + b, 0) / fpsList.length : 0;
|
|
1408
|
+
const minFPS = fpsList.length > 0 ? Math.min(...fpsList) : 0;
|
|
1409
|
+
const maxFPS = fpsList.length > 0 ? Math.max(...fpsList) : 0;
|
|
1410
|
+
return {
|
|
1411
|
+
totalEntities: this.entities.size,
|
|
1412
|
+
activeEntities: Array.from(this.entities.values()).filter((e) => e.active).length,
|
|
1413
|
+
visibleEntities: Array.from(this.entities.values()).filter((e) => e.visible).length,
|
|
1414
|
+
totalVertices: 0,
|
|
1415
|
+
// Would come from renderer
|
|
1416
|
+
totalTriangles: rendererStats?.triangles || 0,
|
|
1417
|
+
drawCalls: rendererStats?.drawCalls || 0,
|
|
1418
|
+
textureMemory: (rendererStats?.memoryUsage?.textures || 0) / (1024 * 1024),
|
|
1419
|
+
geometryMemory: (rendererStats?.memoryUsage?.geometries || 0) / (1024 * 1024),
|
|
1420
|
+
currentFPS: rendererStats?.fps || 0,
|
|
1421
|
+
averageFPS: Math.round(averageFPS),
|
|
1422
|
+
minFPS: Math.round(minFPS),
|
|
1423
|
+
maxFPS: Math.round(maxFPS)
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Get performance history
|
|
1428
|
+
*/
|
|
1429
|
+
getPerformanceHistory() {
|
|
1430
|
+
return this.performanceHistory;
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Toggle physics visualization
|
|
1434
|
+
*/
|
|
1435
|
+
togglePhysicsVisualization(type) {
|
|
1436
|
+
this.physicsVisualization[type] = !this.physicsVisualization[type];
|
|
1437
|
+
console.debug(
|
|
1438
|
+
"[SceneInspector] Physics visualization:",
|
|
1439
|
+
type,
|
|
1440
|
+
"=",
|
|
1441
|
+
this.physicsVisualization[type]
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Get physics visualization settings
|
|
1446
|
+
*/
|
|
1447
|
+
getPhysicsVisualization() {
|
|
1448
|
+
return { ...this.physicsVisualization };
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Generate HTML for inspector UI
|
|
1452
|
+
*/
|
|
1453
|
+
generateInspectorHTML() {
|
|
1454
|
+
const stats = this.getStatistics();
|
|
1455
|
+
const selectedEntity = this.getSelectedEntity();
|
|
1456
|
+
return `
|
|
1457
|
+
<div class="scene-inspector" style="position: absolute; top: 0; right: 0; width: 400px; height: 100vh; background: rgba(0, 0, 0, 0.9); color: #fff; font-family: monospace; font-size: 12px; overflow-y: auto; z-index: 1000;">
|
|
1458
|
+
|
|
1459
|
+
<!-- Header -->
|
|
1460
|
+
<div style="padding: 15px; border-bottom: 2px solid #4ecdc4; background: #1a1a2e;">
|
|
1461
|
+
<h2 style="margin: 0; color: #4ecdc4;">\u{1F50D} Scene Inspector</h2>
|
|
1462
|
+
<div style="color: #aaa; margin-top: 5px;">Frame: ${this.currentFrame}</div>
|
|
1463
|
+
</div>
|
|
1464
|
+
|
|
1465
|
+
<!-- Statistics Panel -->
|
|
1466
|
+
${this.config.showStatistics ? `
|
|
1467
|
+
<div style="padding: 15px; border-bottom: 1px solid #333;">
|
|
1468
|
+
<h3 style="margin: 0 0 10px 0; color: #ffd93d;">\u{1F4CA} Statistics</h3>
|
|
1469
|
+
<div style="line-height: 1.6;">
|
|
1470
|
+
<div>Entities: ${stats.totalEntities} (${stats.activeEntities} active)</div>
|
|
1471
|
+
<div>FPS: ${stats.currentFPS} (avg: ${stats.averageFPS}, min: ${stats.minFPS})</div>
|
|
1472
|
+
<div>Draw Calls: ${stats.drawCalls}</div>
|
|
1473
|
+
<div>Triangles: ${stats.totalTriangles.toLocaleString()}</div>
|
|
1474
|
+
<div>Memory: ${(stats.textureMemory + stats.geometryMemory).toFixed(1)} MB</div>
|
|
1475
|
+
</div>
|
|
1476
|
+
</div>
|
|
1477
|
+
` : ""}
|
|
1478
|
+
|
|
1479
|
+
<!-- Hierarchy Panel -->
|
|
1480
|
+
${this.config.showHierarchy ? `
|
|
1481
|
+
<div style="padding: 15px; border-bottom: 1px solid #333;">
|
|
1482
|
+
<h3 style="margin: 0 0 10px 0; color: #95e1d3;">\u{1F333} Hierarchy</h3>
|
|
1483
|
+
<div style="max-height: 300px; overflow-y: auto;">
|
|
1484
|
+
${this.getEntityHierarchy().map(
|
|
1485
|
+
(entity) => `
|
|
1486
|
+
<div
|
|
1487
|
+
data-entity-id="${entity.id}"
|
|
1488
|
+
style="padding: 5px; margin: 2px 0; background: ${this.selectedEntityId === entity.id ? "#4ecdc4" : "#2a2a3e"}; cursor: pointer; border-radius: 4px;"
|
|
1489
|
+
onclick="window.inspectorSelectEntity('${entity.id}')"
|
|
1490
|
+
>
|
|
1491
|
+
<div style="font-weight: bold;">${entity.name}</div>
|
|
1492
|
+
<div style="color: #aaa; font-size: 10px;">${entity.type} \xB7 ${entity.traits.join(", ")}</div>
|
|
1493
|
+
</div>
|
|
1494
|
+
`
|
|
1495
|
+
).join("")}
|
|
1496
|
+
</div>
|
|
1497
|
+
</div>
|
|
1498
|
+
` : ""}
|
|
1499
|
+
|
|
1500
|
+
<!-- Properties Panel -->
|
|
1501
|
+
${this.config.showProperties && selectedEntity ? `
|
|
1502
|
+
<div style="padding: 15px; border-bottom: 1px solid #333;">
|
|
1503
|
+
<h3 style="margin: 0 0 10px 0; color: #ff6b6b;">\u2699\uFE0F Properties</h3>
|
|
1504
|
+
<div style="line-height: 1.8;">
|
|
1505
|
+
<div style="color: #4ecdc4; margin-bottom: 10px; font-size: 14px; font-weight: bold;">${selectedEntity.name}</div>
|
|
1506
|
+
|
|
1507
|
+
<div style="margin: 10px 0;">
|
|
1508
|
+
<div style="color: #ffd93d;">Transform:</div>
|
|
1509
|
+
<div style="margin-left: 10px;">
|
|
1510
|
+
<div>Position: ${selectedEntity.transform.position.map((v) => v.toFixed(2)).join(", ")}</div>
|
|
1511
|
+
<div>Rotation: ${selectedEntity.transform.rotation.map((v) => v.toFixed(2)).join(", ")}</div>
|
|
1512
|
+
<div>Scale: ${selectedEntity.transform.scale.map((v) => v.toFixed(2)).join(", ")}</div>
|
|
1513
|
+
</div>
|
|
1514
|
+
</div>
|
|
1515
|
+
|
|
1516
|
+
<div style="margin: 10px 0;">
|
|
1517
|
+
<div style="color: #ffd93d;">Traits:</div>
|
|
1518
|
+
<div style="margin-left: 10px;">${selectedEntity.traits.join(", ") || "None"}</div>
|
|
1519
|
+
</div>
|
|
1520
|
+
|
|
1521
|
+
<div style="margin: 10px 0;">
|
|
1522
|
+
<div style="color: #ffd93d;">Properties:</div>
|
|
1523
|
+
<div style="margin-left: 10px;">
|
|
1524
|
+
${Object.entries(selectedEntity.properties).map(
|
|
1525
|
+
([key, value]) => `
|
|
1526
|
+
<div>${key}: ${typeof value === "object" ? JSON.stringify(value) : value}</div>
|
|
1527
|
+
`
|
|
1528
|
+
).join("")}
|
|
1529
|
+
</div>
|
|
1530
|
+
</div>
|
|
1531
|
+
</div>
|
|
1532
|
+
</div>
|
|
1533
|
+
` : ""}
|
|
1534
|
+
|
|
1535
|
+
<!-- Performance Graph -->
|
|
1536
|
+
${this.config.showPerformance ? `
|
|
1537
|
+
<div style="padding: 15px;">
|
|
1538
|
+
<h3 style="margin: 0 0 10px 0; color: #95e1d3;">\u26A1 Performance</h3>
|
|
1539
|
+
<canvas id="performance-graph" width="370" height="100" style="background: #1a1a2e; border-radius: 4px;"></canvas>
|
|
1540
|
+
</div>
|
|
1541
|
+
` : ""}
|
|
1542
|
+
|
|
1543
|
+
</div>
|
|
1544
|
+
|
|
1545
|
+
<script>
|
|
1546
|
+
// Entity selection handler
|
|
1547
|
+
window.inspectorSelectEntity = function(entityId) {
|
|
1548
|
+
console.debug('Selected entity:', entityId);
|
|
1549
|
+
// Would call inspector.selectEntity(entityId) and refresh UI
|
|
1550
|
+
};
|
|
1551
|
+
|
|
1552
|
+
// Render performance graph
|
|
1553
|
+
${this.config.showPerformance ? `
|
|
1554
|
+
(function() {
|
|
1555
|
+
const canvas = document.getElementById('performance-graph');
|
|
1556
|
+
if (!canvas) return;
|
|
1557
|
+
|
|
1558
|
+
const ctx = canvas.getContext('2d');
|
|
1559
|
+
const history = ${JSON.stringify(this.performanceHistory.slice(-60))};
|
|
1560
|
+
|
|
1561
|
+
// Clear canvas
|
|
1562
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1563
|
+
|
|
1564
|
+
// Draw graph
|
|
1565
|
+
const maxFPS = 60;
|
|
1566
|
+
const width = canvas.width;
|
|
1567
|
+
const height = canvas.height;
|
|
1568
|
+
|
|
1569
|
+
ctx.strokeStyle = '#4ecdc4';
|
|
1570
|
+
ctx.lineWidth = 2;
|
|
1571
|
+
ctx.beginPath();
|
|
1572
|
+
|
|
1573
|
+
history.forEach((frame, i) => {
|
|
1574
|
+
const x = (i / history.length) * width;
|
|
1575
|
+
const y = height - (frame.fps / maxFPS) * height;
|
|
1576
|
+
|
|
1577
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
1578
|
+
else ctx.lineTo(x, y);
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
ctx.stroke();
|
|
1582
|
+
|
|
1583
|
+
// Draw 60 FPS line
|
|
1584
|
+
ctx.strokeStyle = '#ffd93d';
|
|
1585
|
+
ctx.lineWidth = 1;
|
|
1586
|
+
ctx.setLineDash([5, 5]);
|
|
1587
|
+
ctx.beginPath();
|
|
1588
|
+
ctx.moveTo(0, 0);
|
|
1589
|
+
ctx.lineTo(width, 0);
|
|
1590
|
+
ctx.stroke();
|
|
1591
|
+
ctx.setLineDash([]);
|
|
1592
|
+
|
|
1593
|
+
// Draw labels
|
|
1594
|
+
ctx.fillStyle = '#fff';
|
|
1595
|
+
ctx.font = '10px monospace';
|
|
1596
|
+
ctx.fillText('60 FPS', 5, 10);
|
|
1597
|
+
ctx.fillText('0 FPS', 5, height - 5);
|
|
1598
|
+
})();
|
|
1599
|
+
` : ""}
|
|
1600
|
+
</script>
|
|
1601
|
+
`;
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Enable/disable inspector
|
|
1605
|
+
*/
|
|
1606
|
+
setEnabled(enabled) {
|
|
1607
|
+
this.config.enabled = enabled;
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Get configuration
|
|
1611
|
+
*/
|
|
1612
|
+
getConfig() {
|
|
1613
|
+
return { ...this.config };
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
|
|
1617
|
+
// src/tools/VisualEditor.ts
|
|
1618
|
+
var VisualEditor = class {
|
|
1619
|
+
constructor(config = {}) {
|
|
1620
|
+
this.entities = /* @__PURE__ */ new Map();
|
|
1621
|
+
this.selectedEntityIds = /* @__PURE__ */ new Set();
|
|
1622
|
+
this.composition = null;
|
|
1623
|
+
this.history = [];
|
|
1624
|
+
this.historyIndex = -1;
|
|
1625
|
+
this.maxHistorySize = 100;
|
|
1626
|
+
this.container = null;
|
|
1627
|
+
this.hierarchyPanel = null;
|
|
1628
|
+
this.propertiesPanel = null;
|
|
1629
|
+
this.viewportPanel = null;
|
|
1630
|
+
this.toolbarPanel = null;
|
|
1631
|
+
this.config = {
|
|
1632
|
+
container: config.container ?? null,
|
|
1633
|
+
autoSave: config.autoSave ?? true,
|
|
1634
|
+
autoSaveInterval: config.autoSaveInterval ?? 3e4,
|
|
1635
|
+
// 30 seconds
|
|
1636
|
+
debug: config.debug ?? false,
|
|
1637
|
+
gridSnap: config.gridSnap ?? true,
|
|
1638
|
+
gridSize: config.gridSize ?? 1,
|
|
1639
|
+
enable3DPreview: config.enable3DPreview ?? true,
|
|
1640
|
+
theme: config.theme ?? "dark"
|
|
1641
|
+
};
|
|
1642
|
+
if (this.config.container) {
|
|
1643
|
+
this.initializeUI();
|
|
1644
|
+
}
|
|
1645
|
+
if (this.config.autoSave) {
|
|
1646
|
+
this.startAutoSave();
|
|
1647
|
+
}
|
|
1648
|
+
if (this.config.debug) {
|
|
1649
|
+
console.debug("[VisualEditor] Initialized", this.config);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Initialize editor UI
|
|
1654
|
+
*/
|
|
1655
|
+
initializeUI() {
|
|
1656
|
+
if (!this.config.container) return;
|
|
1657
|
+
this.container = this.config.container;
|
|
1658
|
+
this.container.innerHTML = this.generateEditorHTML();
|
|
1659
|
+
this.hierarchyPanel = this.container.querySelector(".editor-hierarchy");
|
|
1660
|
+
this.propertiesPanel = this.container.querySelector(".editor-properties");
|
|
1661
|
+
this.viewportPanel = this.container.querySelector(".editor-viewport");
|
|
1662
|
+
this.toolbarPanel = this.container.querySelector(".editor-toolbar");
|
|
1663
|
+
this.attachEventListeners();
|
|
1664
|
+
if (this.config.debug) {
|
|
1665
|
+
console.debug("[VisualEditor] UI initialized");
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Generate editor HTML
|
|
1670
|
+
*/
|
|
1671
|
+
generateEditorHTML() {
|
|
1672
|
+
const theme = this.config.theme === "dark" ? "editor-dark" : "editor-light";
|
|
1673
|
+
return `
|
|
1674
|
+
<div class="holostudio-editor ${theme}">
|
|
1675
|
+
<!-- Toolbar -->
|
|
1676
|
+
<div class="editor-toolbar">
|
|
1677
|
+
<div class="toolbar-section">
|
|
1678
|
+
<button class="toolbar-btn" data-action="new" title="New Composition">
|
|
1679
|
+
<span class="icon">\u{1F4C4}</span> New
|
|
1680
|
+
</button>
|
|
1681
|
+
<button class="toolbar-btn" data-action="open" title="Open Composition">
|
|
1682
|
+
<span class="icon">\u{1F4C1}</span> Open
|
|
1683
|
+
</button>
|
|
1684
|
+
<button class="toolbar-btn" data-action="save" title="Save Composition">
|
|
1685
|
+
<span class="icon">\u{1F4BE}</span> Save
|
|
1686
|
+
</button>
|
|
1687
|
+
</div>
|
|
1688
|
+
<div class="toolbar-section">
|
|
1689
|
+
<button class="toolbar-btn" data-action="undo" title="Undo">
|
|
1690
|
+
<span class="icon">\u21B6</span>
|
|
1691
|
+
</button>
|
|
1692
|
+
<button class="toolbar-btn" data-action="redo" title="Redo">
|
|
1693
|
+
<span class="icon">\u21B7</span>
|
|
1694
|
+
</button>
|
|
1695
|
+
</div>
|
|
1696
|
+
<div class="toolbar-section">
|
|
1697
|
+
<button class="toolbar-btn" data-action="play" title="Preview">
|
|
1698
|
+
<span class="icon">\u25B6</span> Preview
|
|
1699
|
+
</button>
|
|
1700
|
+
</div>
|
|
1701
|
+
</div>
|
|
1702
|
+
|
|
1703
|
+
<!-- Main Content -->
|
|
1704
|
+
<div class="editor-content">
|
|
1705
|
+
<!-- Left Sidebar: Hierarchy -->
|
|
1706
|
+
<div class="editor-panel editor-hierarchy">
|
|
1707
|
+
<div class="panel-header">
|
|
1708
|
+
<h3>Hierarchy</h3>
|
|
1709
|
+
<button class="panel-btn" data-action="add-entity" title="Add Entity">+</button>
|
|
1710
|
+
</div>
|
|
1711
|
+
<div class="panel-content" id="hierarchy-content">
|
|
1712
|
+
<!-- Populated dynamically -->
|
|
1713
|
+
</div>
|
|
1714
|
+
</div>
|
|
1715
|
+
|
|
1716
|
+
<!-- Center: Viewport -->
|
|
1717
|
+
<div class="editor-panel editor-viewport">
|
|
1718
|
+
<div class="panel-header">
|
|
1719
|
+
<h3>Viewport</h3>
|
|
1720
|
+
<div class="viewport-controls">
|
|
1721
|
+
<button class="panel-btn" data-action="grid-toggle" title="Toggle Grid">
|
|
1722
|
+
<span class="icon">\u229E</span>
|
|
1723
|
+
</button>
|
|
1724
|
+
<button class="panel-btn" data-action="gizmo-toggle" title="Toggle Gizmo">
|
|
1725
|
+
<span class="icon">\u22B9</span>
|
|
1726
|
+
</button>
|
|
1727
|
+
</div>
|
|
1728
|
+
</div>
|
|
1729
|
+
<div class="panel-content viewport-content" id="viewport-content">
|
|
1730
|
+
<canvas id="editor-canvas"></canvas>
|
|
1731
|
+
<div class="viewport-overlay">
|
|
1732
|
+
<div class="viewport-stats">
|
|
1733
|
+
<div>Entities: <span id="entity-count">0</span></div>
|
|
1734
|
+
<div>Selected: <span id="selected-count">0</span></div>
|
|
1735
|
+
</div>
|
|
1736
|
+
</div>
|
|
1737
|
+
</div>
|
|
1738
|
+
</div>
|
|
1739
|
+
|
|
1740
|
+
<!-- Right Sidebar: Properties -->
|
|
1741
|
+
<div class="editor-panel editor-properties">
|
|
1742
|
+
<div class="panel-header">
|
|
1743
|
+
<h3>Properties</h3>
|
|
1744
|
+
</div>
|
|
1745
|
+
<div class="panel-content" id="properties-content">
|
|
1746
|
+
<div class="no-selection">
|
|
1747
|
+
<p>No entity selected</p>
|
|
1748
|
+
<p class="hint">Select an entity in the Hierarchy or Viewport</p>
|
|
1749
|
+
</div>
|
|
1750
|
+
</div>
|
|
1751
|
+
</div>
|
|
1752
|
+
</div>
|
|
1753
|
+
|
|
1754
|
+
<!-- Bottom Panel: Asset Library & Traits -->
|
|
1755
|
+
<div class="editor-bottom">
|
|
1756
|
+
<div class="panel-header">
|
|
1757
|
+
<h3>Traits & Assets</h3>
|
|
1758
|
+
</div>
|
|
1759
|
+
<div class="panel-content traits-library">
|
|
1760
|
+
<!-- Populated dynamically -->
|
|
1761
|
+
</div>
|
|
1762
|
+
</div>
|
|
1763
|
+
</div>
|
|
1764
|
+
|
|
1765
|
+
<style>
|
|
1766
|
+
.holostudio-editor {
|
|
1767
|
+
width: 100%;
|
|
1768
|
+
height: 100%;
|
|
1769
|
+
display: flex;
|
|
1770
|
+
flex-direction: column;
|
|
1771
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
|
|
1772
|
+
overflow: hidden;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
.editor-dark {
|
|
1776
|
+
background: #1a1a1a;
|
|
1777
|
+
color: #e0e0e0;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
.editor-light {
|
|
1781
|
+
background: #f5f5f5;
|
|
1782
|
+
color: #333;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
/* Toolbar */
|
|
1786
|
+
.editor-toolbar {
|
|
1787
|
+
display: flex;
|
|
1788
|
+
gap: 20px;
|
|
1789
|
+
padding: 10px 15px;
|
|
1790
|
+
background: rgba(0, 0, 0, 0.3);
|
|
1791
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
.toolbar-section {
|
|
1795
|
+
display: flex;
|
|
1796
|
+
gap: 5px;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
.toolbar-btn {
|
|
1800
|
+
display: flex;
|
|
1801
|
+
align-items: center;
|
|
1802
|
+
gap: 5px;
|
|
1803
|
+
padding: 6px 12px;
|
|
1804
|
+
background: rgba(96, 165, 250, 0.1);
|
|
1805
|
+
border: 1px solid rgba(96, 165, 250, 0.3);
|
|
1806
|
+
border-radius: 6px;
|
|
1807
|
+
color: #60a5fa;
|
|
1808
|
+
cursor: pointer;
|
|
1809
|
+
font-size: 13px;
|
|
1810
|
+
transition: all 0.2s;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
.toolbar-btn:hover {
|
|
1814
|
+
background: rgba(96, 165, 250, 0.2);
|
|
1815
|
+
border-color: rgba(96, 165, 250, 0.5);
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
.icon {
|
|
1819
|
+
font-size: 16px;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
/* Main Content */
|
|
1823
|
+
.editor-content {
|
|
1824
|
+
display: flex;
|
|
1825
|
+
flex: 1;
|
|
1826
|
+
gap: 1px;
|
|
1827
|
+
overflow: hidden;
|
|
1828
|
+
background: rgba(255, 255, 255, 0.05);
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
.editor-panel {
|
|
1832
|
+
display: flex;
|
|
1833
|
+
flex-direction: column;
|
|
1834
|
+
background: rgba(30, 30, 30, 0.95);
|
|
1835
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
.editor-hierarchy {
|
|
1839
|
+
width: 250px;
|
|
1840
|
+
flex-shrink: 0;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
.editor-viewport {
|
|
1844
|
+
flex: 1;
|
|
1845
|
+
min-width: 0;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
.editor-properties {
|
|
1849
|
+
width: 300px;
|
|
1850
|
+
flex-shrink: 0;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
.panel-header {
|
|
1854
|
+
display: flex;
|
|
1855
|
+
justify-content: space-between;
|
|
1856
|
+
align-items: center;
|
|
1857
|
+
padding: 10px 15px;
|
|
1858
|
+
background: rgba(0, 0, 0, 0.2);
|
|
1859
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
.panel-header h3 {
|
|
1863
|
+
margin: 0;
|
|
1864
|
+
font-size: 14px;
|
|
1865
|
+
font-weight: 600;
|
|
1866
|
+
color: #60a5fa;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
.panel-btn {
|
|
1870
|
+
padding: 4px 8px;
|
|
1871
|
+
background: rgba(96, 165, 250, 0.1);
|
|
1872
|
+
border: 1px solid rgba(96, 165, 250, 0.3);
|
|
1873
|
+
border-radius: 4px;
|
|
1874
|
+
color: #60a5fa;
|
|
1875
|
+
cursor: pointer;
|
|
1876
|
+
font-size: 14px;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
.panel-btn:hover {
|
|
1880
|
+
background: rgba(96, 165, 250, 0.2);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
.panel-content {
|
|
1884
|
+
flex: 1;
|
|
1885
|
+
overflow-y: auto;
|
|
1886
|
+
padding: 10px;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
/* Viewport */
|
|
1890
|
+
.viewport-content {
|
|
1891
|
+
position: relative;
|
|
1892
|
+
padding: 0;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
#editor-canvas {
|
|
1896
|
+
width: 100%;
|
|
1897
|
+
height: 100%;
|
|
1898
|
+
display: block;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
.viewport-overlay {
|
|
1902
|
+
position: absolute;
|
|
1903
|
+
top: 10px;
|
|
1904
|
+
left: 10px;
|
|
1905
|
+
pointer-events: none;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
.viewport-stats {
|
|
1909
|
+
background: rgba(0, 0, 0, 0.7);
|
|
1910
|
+
padding: 8px 12px;
|
|
1911
|
+
border-radius: 6px;
|
|
1912
|
+
font-size: 12px;
|
|
1913
|
+
font-family: 'Courier New', monospace;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
.viewport-stats div {
|
|
1917
|
+
margin-bottom: 3px;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
.viewport-controls {
|
|
1921
|
+
display: flex;
|
|
1922
|
+
gap: 5px;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
/* Properties Panel */
|
|
1926
|
+
.no-selection {
|
|
1927
|
+
text-align: center;
|
|
1928
|
+
padding: 40px 20px;
|
|
1929
|
+
color: #888;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
.no-selection .hint {
|
|
1933
|
+
font-size: 12px;
|
|
1934
|
+
margin-top: 10px;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
/* Bottom Panel */
|
|
1938
|
+
.editor-bottom {
|
|
1939
|
+
height: 200px;
|
|
1940
|
+
background: rgba(30, 30, 30, 0.95);
|
|
1941
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
.traits-library {
|
|
1945
|
+
display: grid;
|
|
1946
|
+
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
1947
|
+
gap: 10px;
|
|
1948
|
+
}
|
|
1949
|
+
</style>
|
|
1950
|
+
`;
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Attach event listeners
|
|
1954
|
+
*/
|
|
1955
|
+
attachEventListeners() {
|
|
1956
|
+
if (!this.container) return;
|
|
1957
|
+
this.container.querySelectorAll("[data-action]").forEach((btn) => {
|
|
1958
|
+
btn.addEventListener("click", (e) => {
|
|
1959
|
+
const action = e.currentTarget.getAttribute("data-action");
|
|
1960
|
+
this.handleAction(action);
|
|
1961
|
+
});
|
|
1962
|
+
});
|
|
1963
|
+
document.addEventListener("keydown", (e) => {
|
|
1964
|
+
this.handleKeyboardShortcut(e);
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* Handle toolbar action
|
|
1969
|
+
*/
|
|
1970
|
+
handleAction(action) {
|
|
1971
|
+
if (!action) return;
|
|
1972
|
+
switch (action) {
|
|
1973
|
+
case "new":
|
|
1974
|
+
this.createNewComposition();
|
|
1975
|
+
break;
|
|
1976
|
+
case "save":
|
|
1977
|
+
this.saveComposition();
|
|
1978
|
+
break;
|
|
1979
|
+
case "undo":
|
|
1980
|
+
this.undo();
|
|
1981
|
+
break;
|
|
1982
|
+
case "redo":
|
|
1983
|
+
this.redo();
|
|
1984
|
+
break;
|
|
1985
|
+
case "add-entity":
|
|
1986
|
+
this.addEntity();
|
|
1987
|
+
break;
|
|
1988
|
+
case "play":
|
|
1989
|
+
this.previewComposition();
|
|
1990
|
+
break;
|
|
1991
|
+
default:
|
|
1992
|
+
if (this.config.debug) {
|
|
1993
|
+
console.debug("[VisualEditor] Unhandled action:", action);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Handle keyboard shortcut
|
|
1999
|
+
*/
|
|
2000
|
+
handleKeyboardShortcut(e) {
|
|
2001
|
+
const isCtrl = e.ctrlKey || e.metaKey;
|
|
2002
|
+
if (isCtrl && e.key === "s") {
|
|
2003
|
+
e.preventDefault();
|
|
2004
|
+
this.saveComposition();
|
|
2005
|
+
} else if (isCtrl && e.key === "z") {
|
|
2006
|
+
e.preventDefault();
|
|
2007
|
+
if (e.shiftKey) {
|
|
2008
|
+
this.redo();
|
|
2009
|
+
} else {
|
|
2010
|
+
this.undo();
|
|
2011
|
+
}
|
|
2012
|
+
} else if (e.key === "Delete") {
|
|
2013
|
+
e.preventDefault();
|
|
2014
|
+
this.deleteSelectedEntities();
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Create new composition
|
|
2019
|
+
*/
|
|
2020
|
+
createNewComposition() {
|
|
2021
|
+
this.composition = {
|
|
2022
|
+
type: "Composition",
|
|
2023
|
+
name: "Untitled",
|
|
2024
|
+
objects: [],
|
|
2025
|
+
templates: [],
|
|
2026
|
+
spatialGroups: [],
|
|
2027
|
+
lights: [],
|
|
2028
|
+
imports: [],
|
|
2029
|
+
timelines: [],
|
|
2030
|
+
audio: [],
|
|
2031
|
+
zones: [],
|
|
2032
|
+
transitions: [],
|
|
2033
|
+
conditionals: [],
|
|
2034
|
+
iterators: [],
|
|
2035
|
+
npcs: [],
|
|
2036
|
+
quests: [],
|
|
2037
|
+
abilities: [],
|
|
2038
|
+
dialogues: [],
|
|
2039
|
+
stateMachines: [],
|
|
2040
|
+
achievements: [],
|
|
2041
|
+
talentTrees: [],
|
|
2042
|
+
shapes: []
|
|
2043
|
+
};
|
|
2044
|
+
this.entities.clear();
|
|
2045
|
+
this.selectedEntityIds.clear();
|
|
2046
|
+
this.history = [];
|
|
2047
|
+
this.historyIndex = -1;
|
|
2048
|
+
this.updateHierarchy();
|
|
2049
|
+
this.updateProperties();
|
|
2050
|
+
if (this.config.debug) {
|
|
2051
|
+
console.debug("[VisualEditor] New composition created");
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Load composition
|
|
2056
|
+
*/
|
|
2057
|
+
loadComposition(composition) {
|
|
2058
|
+
this.composition = composition;
|
|
2059
|
+
this.entities.clear();
|
|
2060
|
+
this.selectedEntityIds.clear();
|
|
2061
|
+
let entityIndex = 0;
|
|
2062
|
+
for (const obj of composition.objects || []) {
|
|
2063
|
+
const entity = this.holoObjectToEntity(obj, entityIndex++);
|
|
2064
|
+
this.entities.set(entity.id, entity);
|
|
2065
|
+
}
|
|
2066
|
+
this.updateHierarchy();
|
|
2067
|
+
this.updateProperties();
|
|
2068
|
+
if (this.config.debug) {
|
|
2069
|
+
console.debug("[VisualEditor] Composition loaded:", composition.name);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Convert HoloObject to EditorEntity
|
|
2074
|
+
*/
|
|
2075
|
+
holoObjectToEntity(obj, index) {
|
|
2076
|
+
const getProp = (key) => obj.properties?.find((p) => p.key === key)?.value;
|
|
2077
|
+
const pos = getProp("position");
|
|
2078
|
+
const rot = getProp("rotation");
|
|
2079
|
+
const scl = getProp("scale");
|
|
2080
|
+
return {
|
|
2081
|
+
id: `entity_${index}`,
|
|
2082
|
+
name: obj.name || `Object ${index}`,
|
|
2083
|
+
type: "object",
|
|
2084
|
+
parentId: null,
|
|
2085
|
+
children: [],
|
|
2086
|
+
transform: {
|
|
2087
|
+
position: pos ? [pos[0], pos[1], pos[2]] : [0, 0, 0],
|
|
2088
|
+
rotation: rot ? [rot[0], rot[1], rot[2]] : [0, 0, 0],
|
|
2089
|
+
scale: scl ? [scl[0], scl[1], scl[2]] : [1, 1, 1]
|
|
2090
|
+
},
|
|
2091
|
+
geometry: getProp("geometry"),
|
|
2092
|
+
material: getProp("material"),
|
|
2093
|
+
traits: (obj.traits || []).map((t) => ({
|
|
2094
|
+
name: t.name,
|
|
2095
|
+
properties: t.config,
|
|
2096
|
+
category: this.getTraitCategory(t.name)
|
|
2097
|
+
})),
|
|
2098
|
+
properties: obj.properties?.filter(
|
|
2099
|
+
(p) => !["position", "rotation", "scale", "geometry", "material"].includes(p.key)
|
|
2100
|
+
).reduce((acc, p) => ({ ...acc, [p.key]: p.value }), {}) || {},
|
|
2101
|
+
visible: true,
|
|
2102
|
+
locked: false,
|
|
2103
|
+
selected: false
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
/**
|
|
2107
|
+
* Get trait category
|
|
2108
|
+
*/
|
|
2109
|
+
getTraitCategory(traitName) {
|
|
2110
|
+
if (traitName.startsWith("physics")) return "Physics";
|
|
2111
|
+
if (traitName.startsWith("render")) return "Rendering";
|
|
2112
|
+
if (["grabbable", "climbable", "teleport"].includes(traitName)) return "VR";
|
|
2113
|
+
return "General";
|
|
2114
|
+
}
|
|
2115
|
+
/**
|
|
2116
|
+
* Save composition
|
|
2117
|
+
*/
|
|
2118
|
+
saveComposition() {
|
|
2119
|
+
if (!this.composition) return null;
|
|
2120
|
+
const objects = [];
|
|
2121
|
+
for (const entity of this.entities.values()) {
|
|
2122
|
+
const properties = [];
|
|
2123
|
+
properties.push({
|
|
2124
|
+
type: "ObjectProperty",
|
|
2125
|
+
key: "position",
|
|
2126
|
+
value: entity.transform.position
|
|
2127
|
+
});
|
|
2128
|
+
properties.push({
|
|
2129
|
+
type: "ObjectProperty",
|
|
2130
|
+
key: "rotation",
|
|
2131
|
+
value: entity.transform.rotation
|
|
2132
|
+
});
|
|
2133
|
+
properties.push({ type: "ObjectProperty", key: "scale", value: entity.transform.scale });
|
|
2134
|
+
if (entity.geometry)
|
|
2135
|
+
properties.push({ type: "ObjectProperty", key: "geometry", value: entity.geometry });
|
|
2136
|
+
if (entity.material)
|
|
2137
|
+
properties.push({
|
|
2138
|
+
type: "ObjectProperty",
|
|
2139
|
+
key: "material",
|
|
2140
|
+
value: entity.material
|
|
2141
|
+
});
|
|
2142
|
+
for (const [k, v] of Object.entries(entity.properties)) {
|
|
2143
|
+
properties.push({ type: "ObjectProperty", key: k, value: v });
|
|
2144
|
+
}
|
|
2145
|
+
const obj = {
|
|
2146
|
+
type: "Object",
|
|
2147
|
+
name: entity.name,
|
|
2148
|
+
properties,
|
|
2149
|
+
traits: entity.traits.map((t) => ({
|
|
2150
|
+
type: "ObjectTrait",
|
|
2151
|
+
name: t.name,
|
|
2152
|
+
config: t.properties || {}
|
|
2153
|
+
})),
|
|
2154
|
+
children: []
|
|
2155
|
+
};
|
|
2156
|
+
objects.push(obj);
|
|
2157
|
+
}
|
|
2158
|
+
this.composition.objects = objects;
|
|
2159
|
+
if (this.config.debug) {
|
|
2160
|
+
console.debug("[VisualEditor] Composition saved:", this.composition);
|
|
2161
|
+
}
|
|
2162
|
+
return this.composition;
|
|
2163
|
+
}
|
|
2164
|
+
/**
|
|
2165
|
+
* Export composition as .holo code
|
|
2166
|
+
*/
|
|
2167
|
+
exportToCode() {
|
|
2168
|
+
if (!this.composition) return "";
|
|
2169
|
+
let code = `composition "${this.composition.name}" {
|
|
2170
|
+
`;
|
|
2171
|
+
for (const entity of this.entities.values()) {
|
|
2172
|
+
code += ` object "${entity.name}" {
|
|
2173
|
+
`;
|
|
2174
|
+
for (const trait of entity.traits) {
|
|
2175
|
+
if (trait.properties && Object.keys(trait.properties).length > 0) {
|
|
2176
|
+
const propsStr = Object.entries(trait.properties).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
|
|
2177
|
+
code += ` @${trait.name}(${propsStr})
|
|
2178
|
+
`;
|
|
2179
|
+
} else {
|
|
2180
|
+
code += ` @${trait.name}
|
|
2181
|
+
`;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
if (entity.geometry) {
|
|
2185
|
+
code += ` geometry: "${entity.geometry}"
|
|
2186
|
+
`;
|
|
2187
|
+
}
|
|
2188
|
+
code += ` position: [${entity.transform.position.join(", ")}]
|
|
2189
|
+
`;
|
|
2190
|
+
if (entity.transform.rotation.some((r) => r !== 0)) {
|
|
2191
|
+
code += ` rotation: [${entity.transform.rotation.join(", ")}]
|
|
2192
|
+
`;
|
|
2193
|
+
}
|
|
2194
|
+
if (entity.transform.scale.some((s) => s !== 1)) {
|
|
2195
|
+
code += ` scale: [${entity.transform.scale.join(", ")}]
|
|
2196
|
+
`;
|
|
2197
|
+
}
|
|
2198
|
+
if (entity.material) {
|
|
2199
|
+
code += ` material: ${JSON.stringify(entity.material)}
|
|
2200
|
+
`;
|
|
2201
|
+
}
|
|
2202
|
+
code += ` }
|
|
2203
|
+
`;
|
|
2204
|
+
}
|
|
2205
|
+
code += `}
|
|
2206
|
+
`;
|
|
2207
|
+
return code;
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Add new entity
|
|
2211
|
+
*/
|
|
2212
|
+
addEntity(type = "box") {
|
|
2213
|
+
const id = `entity_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
2214
|
+
const entity = {
|
|
2215
|
+
id,
|
|
2216
|
+
name: `${type.charAt(0).toUpperCase() + type.slice(1)} ${this.entities.size + 1}`,
|
|
2217
|
+
type: "object",
|
|
2218
|
+
parentId: null,
|
|
2219
|
+
children: [],
|
|
2220
|
+
transform: {
|
|
2221
|
+
position: [0, 0, 0],
|
|
2222
|
+
rotation: [0, 0, 0],
|
|
2223
|
+
scale: [1, 1, 1]
|
|
2224
|
+
},
|
|
2225
|
+
geometry: type,
|
|
2226
|
+
material: {
|
|
2227
|
+
type: "standard",
|
|
2228
|
+
color: "#888888"
|
|
2229
|
+
},
|
|
2230
|
+
traits: [],
|
|
2231
|
+
properties: {},
|
|
2232
|
+
visible: true,
|
|
2233
|
+
locked: false,
|
|
2234
|
+
selected: false
|
|
2235
|
+
};
|
|
2236
|
+
this.entities.set(id, entity);
|
|
2237
|
+
this.addToHistory("create", id, void 0, entity);
|
|
2238
|
+
this.updateHierarchy();
|
|
2239
|
+
if (this.config.debug) {
|
|
2240
|
+
console.debug("[VisualEditor] Entity added:", entity.name);
|
|
2241
|
+
}
|
|
2242
|
+
return entity;
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Delete selected entities
|
|
2246
|
+
*/
|
|
2247
|
+
deleteSelectedEntities() {
|
|
2248
|
+
for (const id of this.selectedEntityIds) {
|
|
2249
|
+
const entity = this.entities.get(id);
|
|
2250
|
+
if (entity && !entity.locked) {
|
|
2251
|
+
this.addToHistory("delete", id, entity, void 0);
|
|
2252
|
+
this.entities.delete(id);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
this.selectedEntityIds.clear();
|
|
2256
|
+
this.updateHierarchy();
|
|
2257
|
+
this.updateProperties();
|
|
2258
|
+
if (this.config.debug) {
|
|
2259
|
+
console.debug("[VisualEditor] Deleted entities");
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* Select entity
|
|
2264
|
+
*/
|
|
2265
|
+
selectEntity(id, multiSelect = false) {
|
|
2266
|
+
if (!multiSelect) {
|
|
2267
|
+
for (const entity2 of this.entities.values()) {
|
|
2268
|
+
entity2.selected = false;
|
|
2269
|
+
}
|
|
2270
|
+
this.selectedEntityIds.clear();
|
|
2271
|
+
}
|
|
2272
|
+
const entity = this.entities.get(id);
|
|
2273
|
+
if (entity) {
|
|
2274
|
+
entity.selected = true;
|
|
2275
|
+
this.selectedEntityIds.add(id);
|
|
2276
|
+
this.updateProperties();
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
/**
|
|
2280
|
+
* Update entity property
|
|
2281
|
+
*/
|
|
2282
|
+
updateEntityProperty(id, path, value) {
|
|
2283
|
+
const entity = this.entities.get(id);
|
|
2284
|
+
if (!entity || entity.locked) return;
|
|
2285
|
+
const previousState = { ...entity };
|
|
2286
|
+
const keys = path.split(".");
|
|
2287
|
+
let target = entity;
|
|
2288
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
2289
|
+
target = target[keys[i]];
|
|
2290
|
+
}
|
|
2291
|
+
target[keys[keys.length - 1]] = value;
|
|
2292
|
+
this.addToHistory("modify", id, previousState, entity);
|
|
2293
|
+
this.updateProperties();
|
|
2294
|
+
if (this.config.debug) {
|
|
2295
|
+
console.debug("[VisualEditor] Property updated:", path, "=", value);
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Add history entry
|
|
2300
|
+
*/
|
|
2301
|
+
addToHistory(action, entityId, previousState, newState) {
|
|
2302
|
+
this.history = this.history.slice(0, this.historyIndex + 1);
|
|
2303
|
+
this.history.push({
|
|
2304
|
+
action,
|
|
2305
|
+
entityId,
|
|
2306
|
+
previousState,
|
|
2307
|
+
newState,
|
|
2308
|
+
timestamp: Date.now()
|
|
2309
|
+
});
|
|
2310
|
+
this.historyIndex++;
|
|
2311
|
+
if (this.history.length > this.maxHistorySize) {
|
|
2312
|
+
this.history.shift();
|
|
2313
|
+
this.historyIndex--;
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* Undo last action
|
|
2318
|
+
*/
|
|
2319
|
+
undo() {
|
|
2320
|
+
if (this.historyIndex < 0) return;
|
|
2321
|
+
const entry = this.history[this.historyIndex];
|
|
2322
|
+
if (entry.action === "create") {
|
|
2323
|
+
this.entities.delete(entry.entityId);
|
|
2324
|
+
} else if (entry.action === "delete") {
|
|
2325
|
+
this.entities.set(entry.entityId, entry.previousState);
|
|
2326
|
+
} else if (entry.action === "modify") {
|
|
2327
|
+
this.entities.set(entry.entityId, entry.previousState);
|
|
2328
|
+
}
|
|
2329
|
+
this.historyIndex--;
|
|
2330
|
+
this.updateHierarchy();
|
|
2331
|
+
this.updateProperties();
|
|
2332
|
+
if (this.config.debug) {
|
|
2333
|
+
console.debug("[VisualEditor] Undo:", entry.action);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Redo last undone action
|
|
2338
|
+
*/
|
|
2339
|
+
redo() {
|
|
2340
|
+
if (this.historyIndex >= this.history.length - 1) return;
|
|
2341
|
+
this.historyIndex++;
|
|
2342
|
+
const entry = this.history[this.historyIndex];
|
|
2343
|
+
if (entry.action === "create") {
|
|
2344
|
+
this.entities.set(entry.entityId, entry.newState);
|
|
2345
|
+
} else if (entry.action === "delete") {
|
|
2346
|
+
this.entities.delete(entry.entityId);
|
|
2347
|
+
} else if (entry.action === "modify") {
|
|
2348
|
+
this.entities.set(entry.entityId, entry.newState);
|
|
2349
|
+
}
|
|
2350
|
+
this.updateHierarchy();
|
|
2351
|
+
this.updateProperties();
|
|
2352
|
+
if (this.config.debug) {
|
|
2353
|
+
console.debug("[VisualEditor] Redo:", entry.action);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
/**
|
|
2357
|
+
* Update hierarchy panel
|
|
2358
|
+
*/
|
|
2359
|
+
updateHierarchy() {
|
|
2360
|
+
if (!this.hierarchyPanel) return;
|
|
2361
|
+
const content = this.hierarchyPanel.querySelector("#hierarchy-content");
|
|
2362
|
+
if (!content) return;
|
|
2363
|
+
let html = '<div class="hierarchy-list">';
|
|
2364
|
+
for (const entity of this.entities.values()) {
|
|
2365
|
+
const selectedClass = entity.selected ? "selected" : "";
|
|
2366
|
+
const visibleIcon = entity.visible ? "\u{1F441}" : "\u{1F6AB}";
|
|
2367
|
+
const lockedIcon = entity.locked ? "\u{1F512}" : "";
|
|
2368
|
+
html += `
|
|
2369
|
+
<div class="hierarchy-item ${selectedClass}" data-entity-id="${entity.id}">
|
|
2370
|
+
<span class="entity-icon">${this.getEntityIcon(entity.type)}</span>
|
|
2371
|
+
<span class="entity-name">${entity.name}</span>
|
|
2372
|
+
<span class="entity-status">${visibleIcon} ${lockedIcon}</span>
|
|
2373
|
+
</div>
|
|
2374
|
+
`;
|
|
2375
|
+
}
|
|
2376
|
+
html += "</div>";
|
|
2377
|
+
content.innerHTML = html;
|
|
2378
|
+
const entityCount = document.getElementById("entity-count");
|
|
2379
|
+
const selectedCount = document.getElementById("selected-count");
|
|
2380
|
+
if (entityCount) entityCount.textContent = this.entities.size.toString();
|
|
2381
|
+
if (selectedCount) selectedCount.textContent = this.selectedEntityIds.size.toString();
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* Get entity icon
|
|
2385
|
+
*/
|
|
2386
|
+
getEntityIcon(type) {
|
|
2387
|
+
const icons = {
|
|
2388
|
+
object: "\u{1F4E6}",
|
|
2389
|
+
light: "\u{1F4A1}",
|
|
2390
|
+
camera: "\u{1F4F7}",
|
|
2391
|
+
group: "\u{1F4C1}"
|
|
2392
|
+
};
|
|
2393
|
+
return icons[type] || "\u{1F4E6}";
|
|
2394
|
+
}
|
|
2395
|
+
/**
|
|
2396
|
+
* Update properties panel
|
|
2397
|
+
*/
|
|
2398
|
+
updateProperties() {
|
|
2399
|
+
if (!this.propertiesPanel) return;
|
|
2400
|
+
const content = this.propertiesPanel.querySelector("#properties-content");
|
|
2401
|
+
if (!content) return;
|
|
2402
|
+
if (this.selectedEntityIds.size === 0) {
|
|
2403
|
+
content.innerHTML = `
|
|
2404
|
+
<div class="no-selection">
|
|
2405
|
+
<p>No entity selected</p>
|
|
2406
|
+
<p class="hint">Select an entity in the Hierarchy or Viewport</p>
|
|
2407
|
+
</div>
|
|
2408
|
+
`;
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
const entityId = Array.from(this.selectedEntityIds)[0];
|
|
2412
|
+
const entity = this.entities.get(entityId);
|
|
2413
|
+
if (!entity) return;
|
|
2414
|
+
const html = `
|
|
2415
|
+
<div class="properties-inspector">
|
|
2416
|
+
<div class="property-group">
|
|
2417
|
+
<h4>Transform</h4>
|
|
2418
|
+
<div class="property-row">
|
|
2419
|
+
<label>Position</label>
|
|
2420
|
+
<div class="vector3-input">
|
|
2421
|
+
<input type="number" value="${entity.transform.position[0]}" step="0.1" data-property="transform.position.0">
|
|
2422
|
+
<input type="number" value="${entity.transform.position[1]}" step="0.1" data-property="transform.position.1">
|
|
2423
|
+
<input type="number" value="${entity.transform.position[2]}" step="0.1" data-property="transform.position.2">
|
|
2424
|
+
</div>
|
|
2425
|
+
</div>
|
|
2426
|
+
<div class="property-row">
|
|
2427
|
+
<label>Rotation</label>
|
|
2428
|
+
<div class="vector3-input">
|
|
2429
|
+
<input type="number" value="${entity.transform.rotation[0]}" step="1" data-property="transform.rotation.0">
|
|
2430
|
+
<input type="number" value="${entity.transform.rotation[1]}" step="1" data-property="transform.rotation.1">
|
|
2431
|
+
<input type="number" value="${entity.transform.rotation[2]}" step="1" data-property="transform.rotation.2">
|
|
2432
|
+
</div>
|
|
2433
|
+
</div>
|
|
2434
|
+
<div class="property-row">
|
|
2435
|
+
<label>Scale</label>
|
|
2436
|
+
<div class="vector3-input">
|
|
2437
|
+
<input type="number" value="${entity.transform.scale[0]}" step="0.1" data-property="transform.scale.0">
|
|
2438
|
+
<input type="number" value="${entity.transform.scale[1]}" step="0.1" data-property="transform.scale.1">
|
|
2439
|
+
<input type="number" value="${entity.transform.scale[2]}" step="0.1" data-property="transform.scale.2">
|
|
2440
|
+
</div>
|
|
2441
|
+
</div>
|
|
2442
|
+
</div>
|
|
2443
|
+
|
|
2444
|
+
<div class="property-group">
|
|
2445
|
+
<h4>Traits</h4>
|
|
2446
|
+
<div class="traits-list">
|
|
2447
|
+
${entity.traits.map((t) => `<div class="trait-badge">${t.name}</div>`).join("")}
|
|
2448
|
+
</div>
|
|
2449
|
+
<button class="add-trait-btn">+ Add Trait</button>
|
|
2450
|
+
</div>
|
|
2451
|
+
</div>
|
|
2452
|
+
|
|
2453
|
+
<style>
|
|
2454
|
+
.properties-inspector {
|
|
2455
|
+
padding: 10px;
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
.property-group {
|
|
2459
|
+
margin-bottom: 20px;
|
|
2460
|
+
padding: 10px;
|
|
2461
|
+
background: rgba(0, 0, 0, 0.2);
|
|
2462
|
+
border-radius: 6px;
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
.property-group h4 {
|
|
2466
|
+
margin: 0 0 10px 0;
|
|
2467
|
+
font-size: 13px;
|
|
2468
|
+
color: #60a5fa;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
.property-row {
|
|
2472
|
+
margin-bottom: 10px;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
.property-row label {
|
|
2476
|
+
display: block;
|
|
2477
|
+
font-size: 12px;
|
|
2478
|
+
margin-bottom: 5px;
|
|
2479
|
+
color: #94a3b8;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
.vector3-input {
|
|
2483
|
+
display: grid;
|
|
2484
|
+
grid-template-columns: 1fr 1fr 1fr;
|
|
2485
|
+
gap: 5px;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
.vector3-input input {
|
|
2489
|
+
padding: 5px;
|
|
2490
|
+
background: rgba(0, 0, 0, 0.3);
|
|
2491
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
2492
|
+
border-radius: 4px;
|
|
2493
|
+
color: #fff;
|
|
2494
|
+
font-size: 12px;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
.traits-list {
|
|
2498
|
+
display: flex;
|
|
2499
|
+
flex-wrap: wrap;
|
|
2500
|
+
gap: 5px;
|
|
2501
|
+
margin-bottom: 10px;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
.trait-badge {
|
|
2505
|
+
padding: 4px 8px;
|
|
2506
|
+
background: rgba(96, 165, 250, 0.2);
|
|
2507
|
+
border: 1px solid rgba(96, 165, 250, 0.4);
|
|
2508
|
+
border-radius: 12px;
|
|
2509
|
+
font-size: 11px;
|
|
2510
|
+
color: #60a5fa;
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
.add-trait-btn {
|
|
2514
|
+
width: 100%;
|
|
2515
|
+
padding: 6px;
|
|
2516
|
+
background: rgba(34, 197, 94, 0.1);
|
|
2517
|
+
border: 1px solid rgba(34, 197, 94, 0.3);
|
|
2518
|
+
border-radius: 4px;
|
|
2519
|
+
color: #4ade80;
|
|
2520
|
+
cursor: pointer;
|
|
2521
|
+
font-size: 12px;
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
.add-trait-btn:hover {
|
|
2525
|
+
background: rgba(34, 197, 94, 0.2);
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
.hierarchy-list {
|
|
2529
|
+
padding: 5px;
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
.hierarchy-item {
|
|
2533
|
+
display: flex;
|
|
2534
|
+
align-items: center;
|
|
2535
|
+
gap: 8px;
|
|
2536
|
+
padding: 6px 10px;
|
|
2537
|
+
margin-bottom: 2px;
|
|
2538
|
+
border-radius: 4px;
|
|
2539
|
+
cursor: pointer;
|
|
2540
|
+
font-size: 13px;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
.hierarchy-item:hover {
|
|
2544
|
+
background: rgba(96, 165, 250, 0.1);
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
.hierarchy-item.selected {
|
|
2548
|
+
background: rgba(96, 165, 250, 0.3);
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
.entity-icon {
|
|
2552
|
+
font-size: 14px;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
.entity-name {
|
|
2556
|
+
flex: 1;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
.entity-status {
|
|
2560
|
+
font-size: 11px;
|
|
2561
|
+
}
|
|
2562
|
+
</style>
|
|
2563
|
+
`;
|
|
2564
|
+
content.innerHTML = html;
|
|
2565
|
+
}
|
|
2566
|
+
/**
|
|
2567
|
+
* Preview composition
|
|
2568
|
+
*/
|
|
2569
|
+
previewComposition() {
|
|
2570
|
+
const composition = this.saveComposition();
|
|
2571
|
+
if (!composition) return;
|
|
2572
|
+
if (this.config.debug) {
|
|
2573
|
+
console.debug("[VisualEditor] Preview:", composition);
|
|
2574
|
+
}
|
|
2575
|
+
this.container?.dispatchEvent(
|
|
2576
|
+
new CustomEvent("holostudio:preview", {
|
|
2577
|
+
detail: { composition }
|
|
2578
|
+
})
|
|
2579
|
+
);
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* Start auto-save
|
|
2583
|
+
*/
|
|
2584
|
+
startAutoSave() {
|
|
2585
|
+
if (typeof window === "undefined") return;
|
|
2586
|
+
this.autoSaveTimer = window.setInterval(() => {
|
|
2587
|
+
this.saveComposition();
|
|
2588
|
+
if (this.config.debug) {
|
|
2589
|
+
console.debug("[VisualEditor] Auto-saved");
|
|
2590
|
+
}
|
|
2591
|
+
}, this.config.autoSaveInterval);
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Stop auto-save
|
|
2595
|
+
*/
|
|
2596
|
+
stopAutoSave() {
|
|
2597
|
+
if (this.autoSaveTimer) {
|
|
2598
|
+
clearInterval(this.autoSaveTimer);
|
|
2599
|
+
this.autoSaveTimer = void 0;
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
/**
|
|
2603
|
+
* Dispose editor
|
|
2604
|
+
*/
|
|
2605
|
+
dispose() {
|
|
2606
|
+
this.stopAutoSave();
|
|
2607
|
+
this.entities.clear();
|
|
2608
|
+
this.selectedEntityIds.clear();
|
|
2609
|
+
this.history = [];
|
|
2610
|
+
if (this.config.debug) {
|
|
2611
|
+
console.debug("[VisualEditor] Disposed");
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
/**
|
|
2615
|
+
* Get all entities
|
|
2616
|
+
*/
|
|
2617
|
+
getEntities() {
|
|
2618
|
+
return Array.from(this.entities.values());
|
|
2619
|
+
}
|
|
2620
|
+
/**
|
|
2621
|
+
* Get selected entities
|
|
2622
|
+
*/
|
|
2623
|
+
getSelectedEntities() {
|
|
2624
|
+
return Array.from(this.selectedEntityIds).map((id) => this.entities.get(id)).filter(Boolean);
|
|
2625
|
+
}
|
|
2626
|
+
/**
|
|
2627
|
+
* Get composition
|
|
2628
|
+
*/
|
|
2629
|
+
getComposition() {
|
|
2630
|
+
return this.saveComposition();
|
|
2631
|
+
}
|
|
2632
|
+
};
|
|
2633
|
+
|
|
2634
|
+
// src/tools/MixamoIntegration.ts
|
|
2635
|
+
var MIXAMO_API_BASE = "https://www.mixamo.com/api/v1";
|
|
2636
|
+
var MAX_RETRIES = 5;
|
|
2637
|
+
var INITIAL_BACKOFF_MS = 1e3;
|
|
2638
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
2639
|
+
var SUPPORTED_FORMATS = ["fbx", "glb"];
|
|
2640
|
+
var MixamoError = class extends Error {
|
|
2641
|
+
constructor(message, code) {
|
|
2642
|
+
super(message);
|
|
2643
|
+
this.name = "MixamoError";
|
|
2644
|
+
this.code = code;
|
|
2645
|
+
}
|
|
2646
|
+
};
|
|
2647
|
+
var MixamoRateLimitError = class extends MixamoError {
|
|
2648
|
+
constructor(retryAfterSeconds, attemptNumber) {
|
|
2649
|
+
super(
|
|
2650
|
+
`Mixamo API rate limit exceeded. Retry after ${retryAfterSeconds}s (attempt ${attemptNumber}).`,
|
|
2651
|
+
"RATE_LIMIT_EXCEEDED"
|
|
2652
|
+
);
|
|
2653
|
+
this.name = "MixamoRateLimitError";
|
|
2654
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
2655
|
+
this.attemptNumber = attemptNumber;
|
|
2656
|
+
}
|
|
2657
|
+
};
|
|
2658
|
+
var MixamoTopologyError = class extends MixamoError {
|
|
2659
|
+
constructor(issues) {
|
|
2660
|
+
const errorIssues = issues.filter((i) => i.severity === "error");
|
|
2661
|
+
const summary = errorIssues.map((i) => i.message).join("; ");
|
|
2662
|
+
super(`Mesh topology unsupported for auto-rigging: ${summary}`, "UNSUPPORTED_TOPOLOGY");
|
|
2663
|
+
this.name = "MixamoTopologyError";
|
|
2664
|
+
this.issues = issues;
|
|
2665
|
+
}
|
|
2666
|
+
};
|
|
2667
|
+
var MixamoFormatError = class extends MixamoError {
|
|
2668
|
+
constructor(providedFormat) {
|
|
2669
|
+
super(
|
|
2670
|
+
`Unsupported mesh format "${providedFormat}". Supported formats: ${SUPPORTED_FORMATS.join(", ")}.`,
|
|
2671
|
+
"UNSUPPORTED_FORMAT"
|
|
2672
|
+
);
|
|
2673
|
+
this.name = "MixamoFormatError";
|
|
2674
|
+
this.providedFormat = providedFormat;
|
|
2675
|
+
this.supportedFormats = SUPPORTED_FORMATS;
|
|
2676
|
+
}
|
|
2677
|
+
};
|
|
2678
|
+
var MixamoAPI = class {
|
|
2679
|
+
constructor(config) {
|
|
2680
|
+
this.config = {
|
|
2681
|
+
credentials: config.credentials,
|
|
2682
|
+
maxRetries: config.maxRetries ?? MAX_RETRIES,
|
|
2683
|
+
initialBackoffMs: config.initialBackoffMs ?? INITIAL_BACKOFF_MS,
|
|
2684
|
+
debug: config.debug ?? false,
|
|
2685
|
+
timeoutMs: config.timeoutMs ?? 6e4
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
// -----------------------------------------------------------------------
|
|
2689
|
+
// Public API
|
|
2690
|
+
// -----------------------------------------------------------------------
|
|
2691
|
+
/**
|
|
2692
|
+
* Upload a character mesh to Mixamo for rigging.
|
|
2693
|
+
*
|
|
2694
|
+
* Supports FBX and GLB formats. Validates mesh topology before upload
|
|
2695
|
+
* and throws `MixamoTopologyError` for unsupported geometries.
|
|
2696
|
+
*
|
|
2697
|
+
* @param data - Raw mesh file data as Buffer or ArrayBuffer.
|
|
2698
|
+
* @param filename - Original filename (used to detect format from extension).
|
|
2699
|
+
* @returns Upload result with character ID and mesh metadata.
|
|
2700
|
+
*
|
|
2701
|
+
* @throws {MixamoFormatError} If the file format is not FBX or GLB.
|
|
2702
|
+
* @throws {MixamoTopologyError} If the mesh has unsupported topology.
|
|
2703
|
+
* @throws {MixamoRateLimitError} If the API rate limit is exceeded after max retries.
|
|
2704
|
+
* @throws {MixamoError} For other API errors.
|
|
2705
|
+
*/
|
|
2706
|
+
async uploadCharacter(data, filename) {
|
|
2707
|
+
const format = this.detectFormat(filename);
|
|
2708
|
+
const topologyIssues = this.validateTopology(data, format);
|
|
2709
|
+
const errors = topologyIssues.filter((i) => i.severity === "error");
|
|
2710
|
+
if (errors.length > 0) {
|
|
2711
|
+
throw new MixamoTopologyError(topologyIssues);
|
|
2712
|
+
}
|
|
2713
|
+
if (topologyIssues.length > 0 && this.config.debug) {
|
|
2714
|
+
console.warn(
|
|
2715
|
+
"[MixamoAPI] Topology warnings:",
|
|
2716
|
+
topologyIssues.map((i) => i.message)
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
const formData = this.createFormData(data, filename, format);
|
|
2720
|
+
const response = await this.requestWithRetry("POST", "/characters/upload", formData, {
|
|
2721
|
+
isFormData: true
|
|
2722
|
+
});
|
|
2723
|
+
const result = {
|
|
2724
|
+
characterId: response.character_id,
|
|
2725
|
+
filename,
|
|
2726
|
+
format,
|
|
2727
|
+
vertexCount: response.vertex_count,
|
|
2728
|
+
polygonCount: response.polygon_count,
|
|
2729
|
+
topologyValid: response.topology_valid,
|
|
2730
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2731
|
+
};
|
|
2732
|
+
if (this.config.debug) {
|
|
2733
|
+
console.debug("[MixamoAPI] Character uploaded:", result.characterId);
|
|
2734
|
+
}
|
|
2735
|
+
return result;
|
|
2736
|
+
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Auto-rig an uploaded character.
|
|
2739
|
+
*
|
|
2740
|
+
* Triggers Mixamo's server-side auto-rigging pipeline which detects
|
|
2741
|
+
* joint positions and generates a humanoid skeleton.
|
|
2742
|
+
*
|
|
2743
|
+
* @param characterId - The character ID from a previous upload.
|
|
2744
|
+
* @param rigType - Rig type to generate. Default: 'humanoid'.
|
|
2745
|
+
* @returns Rig result with bone count and quality score.
|
|
2746
|
+
*
|
|
2747
|
+
* @throws {MixamoRateLimitError} If the API rate limit is exceeded after max retries.
|
|
2748
|
+
* @throws {MixamoError} For other API errors (e.g. character not found).
|
|
2749
|
+
*/
|
|
2750
|
+
async autoRig(characterId, rigType = "humanoid") {
|
|
2751
|
+
const response = await this.requestWithRetry("POST", `/characters/${encodeURIComponent(characterId)}/rig`, {
|
|
2752
|
+
rig_type: rigType
|
|
2753
|
+
});
|
|
2754
|
+
const result = {
|
|
2755
|
+
characterId: response.character_id,
|
|
2756
|
+
rigType: response.rig_type,
|
|
2757
|
+
boneCount: response.bone_count,
|
|
2758
|
+
qualityScore: response.quality_score,
|
|
2759
|
+
success: response.success,
|
|
2760
|
+
warnings: response.warnings ?? [],
|
|
2761
|
+
riggedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2762
|
+
};
|
|
2763
|
+
if (this.config.debug) {
|
|
2764
|
+
console.debug(
|
|
2765
|
+
`[MixamoAPI] Character rigged: ${result.characterId} (${result.boneCount} bones, quality: ${result.qualityScore})`
|
|
2766
|
+
);
|
|
2767
|
+
}
|
|
2768
|
+
return result;
|
|
2769
|
+
}
|
|
2770
|
+
/**
|
|
2771
|
+
* List available animations from the Mixamo library.
|
|
2772
|
+
*
|
|
2773
|
+
* Supports pagination, category filtering, and text search.
|
|
2774
|
+
*
|
|
2775
|
+
* @param options - Listing options (page, limit, category, query, motionType).
|
|
2776
|
+
* @returns Paginated list of animations.
|
|
2777
|
+
*
|
|
2778
|
+
* @throws {MixamoRateLimitError} If the API rate limit is exceeded after max retries.
|
|
2779
|
+
* @throws {MixamoError} For other API errors.
|
|
2780
|
+
*/
|
|
2781
|
+
async listAnimations(options = {}) {
|
|
2782
|
+
const params = new URLSearchParams();
|
|
2783
|
+
if (options.page) params.set("page", String(options.page));
|
|
2784
|
+
if (options.limit) params.set("limit", String(options.limit));
|
|
2785
|
+
if (options.category) params.set("category", options.category);
|
|
2786
|
+
if (options.query) params.set("query", options.query);
|
|
2787
|
+
if (options.motionType) params.set("motion_type", options.motionType);
|
|
2788
|
+
const queryString = params.toString();
|
|
2789
|
+
const endpoint = `/animations${queryString ? `?${queryString}` : ""}`;
|
|
2790
|
+
const response = await this.requestWithRetry("GET", endpoint);
|
|
2791
|
+
const result = {
|
|
2792
|
+
animations: response.animations.map((a) => ({
|
|
2793
|
+
id: a.id,
|
|
2794
|
+
name: a.name,
|
|
2795
|
+
category: a.category,
|
|
2796
|
+
duration: a.duration,
|
|
2797
|
+
loopable: a.loopable,
|
|
2798
|
+
motionType: a.motion_type,
|
|
2799
|
+
thumbnailUrl: a.thumbnail_url
|
|
2800
|
+
})),
|
|
2801
|
+
total: response.total,
|
|
2802
|
+
page: response.page,
|
|
2803
|
+
limit: response.limit,
|
|
2804
|
+
hasMore: response.has_more
|
|
2805
|
+
};
|
|
2806
|
+
if (this.config.debug) {
|
|
2807
|
+
console.debug(
|
|
2808
|
+
`[MixamoAPI] Listed ${result.animations.length} animations (${result.total} total)`
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
return result;
|
|
2812
|
+
}
|
|
2813
|
+
/**
|
|
2814
|
+
* Download an animation applied to a specific character.
|
|
2815
|
+
*
|
|
2816
|
+
* The animation is retargeted to the character's skeleton and returned
|
|
2817
|
+
* as raw file data in the requested format (FBX or GLB).
|
|
2818
|
+
*
|
|
2819
|
+
* @param options - Download options (characterId, animationId, format, etc.)
|
|
2820
|
+
* @returns Download result with raw file data and metadata.
|
|
2821
|
+
*
|
|
2822
|
+
* @throws {MixamoFormatError} If the requested format is not supported.
|
|
2823
|
+
* @throws {MixamoRateLimitError} If the API rate limit is exceeded after max retries.
|
|
2824
|
+
* @throws {MixamoError} For other API errors.
|
|
2825
|
+
*/
|
|
2826
|
+
async downloadAnimation(options) {
|
|
2827
|
+
const format = options.format ?? "fbx";
|
|
2828
|
+
if (!SUPPORTED_FORMATS.includes(format)) {
|
|
2829
|
+
throw new MixamoFormatError(format);
|
|
2830
|
+
}
|
|
2831
|
+
const response = await this.requestWithRetry(
|
|
2832
|
+
"POST",
|
|
2833
|
+
`/characters/${encodeURIComponent(options.characterId)}/animations/${encodeURIComponent(options.animationId)}/download`,
|
|
2834
|
+
{
|
|
2835
|
+
format,
|
|
2836
|
+
include_skin: options.includeSkin ?? false,
|
|
2837
|
+
fps: options.fps ?? 30,
|
|
2838
|
+
keyframe_reduction: options.keyframeReduction ?? 0
|
|
2839
|
+
},
|
|
2840
|
+
{ binary: true }
|
|
2841
|
+
);
|
|
2842
|
+
const suggestedFilename = `${options.characterId}_${options.animationId}.${format}`;
|
|
2843
|
+
const result = {
|
|
2844
|
+
data: response,
|
|
2845
|
+
contentType: format === "fbx" ? "application/octet-stream" : "model/gltf-binary",
|
|
2846
|
+
filename: suggestedFilename,
|
|
2847
|
+
sizeBytes: response.byteLength,
|
|
2848
|
+
format
|
|
2849
|
+
};
|
|
2850
|
+
if (this.config.debug) {
|
|
2851
|
+
console.debug(
|
|
2852
|
+
`[MixamoAPI] Animation downloaded: ${result.filename} (${result.sizeBytes} bytes)`
|
|
2853
|
+
);
|
|
2854
|
+
}
|
|
2855
|
+
return result;
|
|
2856
|
+
}
|
|
2857
|
+
// -----------------------------------------------------------------------
|
|
2858
|
+
// Format Detection & Validation
|
|
2859
|
+
// -----------------------------------------------------------------------
|
|
2860
|
+
/**
|
|
2861
|
+
* Detect mesh format from filename extension.
|
|
2862
|
+
*
|
|
2863
|
+
* @param filename - The mesh filename.
|
|
2864
|
+
* @returns The detected format.
|
|
2865
|
+
* @throws {MixamoFormatError} If the extension is not recognized.
|
|
2866
|
+
*/
|
|
2867
|
+
detectFormat(filename) {
|
|
2868
|
+
const ext = filename.split(".").pop()?.toLowerCase();
|
|
2869
|
+
if (ext === "fbx") return "fbx";
|
|
2870
|
+
if (ext === "glb" || ext === "gltf") return "glb";
|
|
2871
|
+
throw new MixamoFormatError(ext ?? "unknown");
|
|
2872
|
+
}
|
|
2873
|
+
/**
|
|
2874
|
+
* Validate mesh topology for auto-rigging compatibility.
|
|
2875
|
+
*
|
|
2876
|
+
* Performs pre-flight checks on the mesh data to detect issues that
|
|
2877
|
+
* would cause the auto-rigging pipeline to fail or produce poor results.
|
|
2878
|
+
*
|
|
2879
|
+
* @param data - Raw mesh file data.
|
|
2880
|
+
* @param format - The mesh format.
|
|
2881
|
+
* @returns Array of topology issues found (may be empty if mesh is clean).
|
|
2882
|
+
*/
|
|
2883
|
+
validateTopology(data, format) {
|
|
2884
|
+
const issues = [];
|
|
2885
|
+
const byteLength = data instanceof ArrayBuffer ? data.byteLength : data.length;
|
|
2886
|
+
if (byteLength < 100) {
|
|
2887
|
+
issues.push({
|
|
2888
|
+
code: "DEGENERATE_FACES",
|
|
2889
|
+
message: "File is too small to contain valid mesh data.",
|
|
2890
|
+
severity: "error"
|
|
2891
|
+
});
|
|
2892
|
+
return issues;
|
|
2893
|
+
}
|
|
2894
|
+
if (format === "fbx") {
|
|
2895
|
+
const header = new Uint8Array(
|
|
2896
|
+
data instanceof ArrayBuffer ? data : data.buffer,
|
|
2897
|
+
data instanceof ArrayBuffer ? 0 : data.byteOffset,
|
|
2898
|
+
Math.min(23, byteLength)
|
|
2899
|
+
);
|
|
2900
|
+
const magic = String.fromCharCode(...header.slice(0, 20));
|
|
2901
|
+
if (!magic.startsWith("Kaydara FBX Binary")) {
|
|
2902
|
+
if (byteLength < 1e3) {
|
|
2903
|
+
issues.push({
|
|
2904
|
+
code: "DEGENERATE_FACES",
|
|
2905
|
+
message: "FBX file does not contain a valid binary header. ASCII FBX may have limited support.",
|
|
2906
|
+
severity: "warning"
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
if (format === "glb") {
|
|
2912
|
+
const header = new Uint8Array(
|
|
2913
|
+
data instanceof ArrayBuffer ? data : data.buffer,
|
|
2914
|
+
data instanceof ArrayBuffer ? 0 : data.byteOffset,
|
|
2915
|
+
Math.min(4, byteLength)
|
|
2916
|
+
);
|
|
2917
|
+
const glbMagic = header[0] === 103 && header[1] === 108 && header[2] === 84 && header[3] === 70;
|
|
2918
|
+
if (!glbMagic) {
|
|
2919
|
+
issues.push({
|
|
2920
|
+
code: "DEGENERATE_FACES",
|
|
2921
|
+
message: "GLB file does not contain a valid glTF binary header.",
|
|
2922
|
+
severity: "error"
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
const estimatedPolygons = byteLength / 50;
|
|
2927
|
+
if (estimatedPolygons > 5e5) {
|
|
2928
|
+
issues.push({
|
|
2929
|
+
code: "EXCESSIVE_POLYGONS",
|
|
2930
|
+
message: `Estimated polygon count (~${Math.round(estimatedPolygons).toLocaleString()}) exceeds recommended limit of 500,000 for auto-rigging. Consider decimating the mesh.`,
|
|
2931
|
+
severity: "warning"
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
if (estimatedPolygons > 2e6) {
|
|
2935
|
+
issues.push({
|
|
2936
|
+
code: "EXCESSIVE_POLYGONS",
|
|
2937
|
+
message: `Estimated polygon count (~${Math.round(estimatedPolygons).toLocaleString()}) far exceeds the maximum of 2,000,000 for auto-rigging.`,
|
|
2938
|
+
severity: "error"
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2941
|
+
return issues;
|
|
2942
|
+
}
|
|
2943
|
+
// -----------------------------------------------------------------------
|
|
2944
|
+
// HTTP Utilities
|
|
2945
|
+
// -----------------------------------------------------------------------
|
|
2946
|
+
/**
|
|
2947
|
+
* Create form data for file upload.
|
|
2948
|
+
*/
|
|
2949
|
+
createFormData(data, filename, format) {
|
|
2950
|
+
return {
|
|
2951
|
+
file: data,
|
|
2952
|
+
filename,
|
|
2953
|
+
format,
|
|
2954
|
+
type: "character"
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2957
|
+
/**
|
|
2958
|
+
* Execute an HTTP request with exponential backoff retry for rate limits.
|
|
2959
|
+
*
|
|
2960
|
+
* @param method - HTTP method.
|
|
2961
|
+
* @param endpoint - API endpoint path (appended to base URL).
|
|
2962
|
+
* @param body - Request body (JSON or form data).
|
|
2963
|
+
* @param options - Additional request options.
|
|
2964
|
+
* @returns Parsed response data.
|
|
2965
|
+
*/
|
|
2966
|
+
async requestWithRetry(method, endpoint, body, options) {
|
|
2967
|
+
const baseUrl = this.config.credentials.apiBase ?? MIXAMO_API_BASE;
|
|
2968
|
+
const url = `${baseUrl}${endpoint}`;
|
|
2969
|
+
let lastError = null;
|
|
2970
|
+
for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
|
|
2971
|
+
try {
|
|
2972
|
+
const headers = {
|
|
2973
|
+
Authorization: `Bearer ${this.config.credentials.accessToken}`,
|
|
2974
|
+
Accept: options?.binary ? "application/octet-stream" : "application/json"
|
|
2975
|
+
};
|
|
2976
|
+
const fetchOptions = {
|
|
2977
|
+
method,
|
|
2978
|
+
headers,
|
|
2979
|
+
signal: AbortSignal.timeout(this.config.timeoutMs)
|
|
2980
|
+
};
|
|
2981
|
+
if (body && method !== "GET") {
|
|
2982
|
+
if (options?.isFormData) {
|
|
2983
|
+
headers["Content-Type"] = "multipart/form-data";
|
|
2984
|
+
fetchOptions.body = JSON.stringify(body);
|
|
2985
|
+
} else {
|
|
2986
|
+
headers["Content-Type"] = "application/json";
|
|
2987
|
+
fetchOptions.body = JSON.stringify(body);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
const response = await fetch(url, fetchOptions);
|
|
2991
|
+
if (response.status === 429) {
|
|
2992
|
+
const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0", 10);
|
|
2993
|
+
const backoffMs = Math.min(
|
|
2994
|
+
retryAfter > 0 ? retryAfter * 1e3 : this.config.initialBackoffMs * Math.pow(2, attempt),
|
|
2995
|
+
MAX_BACKOFF_MS
|
|
2996
|
+
);
|
|
2997
|
+
if (this.config.debug) {
|
|
2998
|
+
console.warn(
|
|
2999
|
+
`[MixamoAPI] Rate limited (attempt ${attempt + 1}/${this.config.maxRetries}). Retrying in ${backoffMs}ms...`
|
|
3000
|
+
);
|
|
3001
|
+
}
|
|
3002
|
+
if (attempt === this.config.maxRetries - 1) {
|
|
3003
|
+
throw new MixamoRateLimitError(Math.ceil(backoffMs / 1e3), attempt + 1);
|
|
3004
|
+
}
|
|
3005
|
+
await this.sleep(backoffMs);
|
|
3006
|
+
continue;
|
|
3007
|
+
}
|
|
3008
|
+
if (!response.ok) {
|
|
3009
|
+
let errorMessage = `Mixamo API error: ${response.status} ${response.statusText}`;
|
|
3010
|
+
try {
|
|
3011
|
+
const errorBody = await response.json();
|
|
3012
|
+
if (errorBody.message) {
|
|
3013
|
+
errorMessage = errorBody.message;
|
|
3014
|
+
}
|
|
3015
|
+
if (errorBody.error) {
|
|
3016
|
+
errorMessage = errorBody.error;
|
|
3017
|
+
}
|
|
3018
|
+
} catch {
|
|
3019
|
+
}
|
|
3020
|
+
throw new MixamoError(errorMessage, `HTTP_${response.status}`);
|
|
3021
|
+
}
|
|
3022
|
+
if (options?.binary) {
|
|
3023
|
+
return await response.arrayBuffer();
|
|
3024
|
+
}
|
|
3025
|
+
return await response.json();
|
|
3026
|
+
} catch (error) {
|
|
3027
|
+
if (error instanceof MixamoRateLimitError || error instanceof MixamoTopologyError || error instanceof MixamoFormatError) {
|
|
3028
|
+
throw error;
|
|
3029
|
+
}
|
|
3030
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3031
|
+
if (attempt < this.config.maxRetries - 1) {
|
|
3032
|
+
const backoffMs = Math.min(
|
|
3033
|
+
this.config.initialBackoffMs * Math.pow(2, attempt),
|
|
3034
|
+
MAX_BACKOFF_MS
|
|
3035
|
+
);
|
|
3036
|
+
if (this.config.debug) {
|
|
3037
|
+
console.warn(
|
|
3038
|
+
`[MixamoAPI] Request failed (attempt ${attempt + 1}/${this.config.maxRetries}): ${lastError.message}. Retrying in ${backoffMs}ms...`
|
|
3039
|
+
);
|
|
3040
|
+
}
|
|
3041
|
+
await this.sleep(backoffMs);
|
|
3042
|
+
continue;
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
throw new MixamoError(
|
|
3047
|
+
`Request failed after ${this.config.maxRetries} attempts: ${lastError?.message ?? "Unknown error"}`,
|
|
3048
|
+
"MAX_RETRIES_EXCEEDED"
|
|
3049
|
+
);
|
|
3050
|
+
}
|
|
3051
|
+
/**
|
|
3052
|
+
* Sleep for a given number of milliseconds.
|
|
3053
|
+
*/
|
|
3054
|
+
sleep(ms) {
|
|
3055
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3056
|
+
}
|
|
3057
|
+
};
|
|
3058
|
+
var MixamoPresetMapper = class {
|
|
3059
|
+
constructor() {
|
|
3060
|
+
this.mappings = /* @__PURE__ */ new Map();
|
|
3061
|
+
this.reverseIndex = /* @__PURE__ */ new Map();
|
|
3062
|
+
this.loadDefaultMappings();
|
|
3063
|
+
}
|
|
3064
|
+
/**
|
|
3065
|
+
* Get the Mixamo animation mapping for a preset name.
|
|
3066
|
+
*
|
|
3067
|
+
* @param presetName - HoloScript animation preset name (e.g. 'walk', 'idle').
|
|
3068
|
+
* @returns The mapping, or undefined if no mapping exists.
|
|
3069
|
+
*/
|
|
3070
|
+
getMixamoId(presetName) {
|
|
3071
|
+
return this.mappings.get(presetName.toLowerCase());
|
|
3072
|
+
}
|
|
3073
|
+
/**
|
|
3074
|
+
* Get the HoloScript preset name for a Mixamo animation ID.
|
|
3075
|
+
*
|
|
3076
|
+
* @param mixamoId - Mixamo animation ID.
|
|
3077
|
+
* @returns The preset name, or undefined if no reverse mapping exists.
|
|
3078
|
+
*/
|
|
3079
|
+
getPresetName(mixamoId) {
|
|
3080
|
+
return this.reverseIndex.get(mixamoId);
|
|
3081
|
+
}
|
|
3082
|
+
/**
|
|
3083
|
+
* Get all preset-to-Mixamo mappings.
|
|
3084
|
+
*
|
|
3085
|
+
* @returns Array of all mappings.
|
|
3086
|
+
*/
|
|
3087
|
+
getAllMappings() {
|
|
3088
|
+
return Array.from(this.mappings.values());
|
|
3089
|
+
}
|
|
3090
|
+
/**
|
|
3091
|
+
* Check if a mapping exists for a given preset name.
|
|
3092
|
+
*
|
|
3093
|
+
* @param presetName - HoloScript animation preset name.
|
|
3094
|
+
*/
|
|
3095
|
+
hasMapping(presetName) {
|
|
3096
|
+
return this.mappings.has(presetName.toLowerCase());
|
|
3097
|
+
}
|
|
3098
|
+
/**
|
|
3099
|
+
* Register a custom preset-to-Mixamo mapping.
|
|
3100
|
+
*
|
|
3101
|
+
* @param mapping - The mapping to register.
|
|
3102
|
+
*/
|
|
3103
|
+
registerMapping(mapping) {
|
|
3104
|
+
const key = mapping.presetName.toLowerCase();
|
|
3105
|
+
this.mappings.set(key, mapping);
|
|
3106
|
+
this.reverseIndex.set(mapping.mixamoAnimationId, key);
|
|
3107
|
+
for (const altId of mapping.alternativeIds) {
|
|
3108
|
+
this.reverseIndex.set(altId, key);
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
/**
|
|
3112
|
+
* Search for mappings by Mixamo clip name (partial, case-insensitive).
|
|
3113
|
+
*
|
|
3114
|
+
* @param query - Search query string.
|
|
3115
|
+
* @returns Array of matching mappings.
|
|
3116
|
+
*/
|
|
3117
|
+
searchByClipName(query) {
|
|
3118
|
+
const q = query.toLowerCase();
|
|
3119
|
+
const results = [];
|
|
3120
|
+
for (const mapping of this.mappings.values()) {
|
|
3121
|
+
if (mapping.mixamoClipName.toLowerCase().includes(q)) {
|
|
3122
|
+
results.push(mapping);
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
return results;
|
|
3126
|
+
}
|
|
3127
|
+
/**
|
|
3128
|
+
* Load the default preset-to-Mixamo mappings for all 15 canonical presets.
|
|
3129
|
+
*
|
|
3130
|
+
* Mixamo animation IDs are based on the Mixamo REST API's internal
|
|
3131
|
+
* identifiers. These map to the canonical clip names defined in
|
|
3132
|
+
* @holoscript/animation-presets.
|
|
3133
|
+
*/
|
|
3134
|
+
loadDefaultMappings() {
|
|
3135
|
+
const defaults = [
|
|
3136
|
+
{
|
|
3137
|
+
presetName: "walk",
|
|
3138
|
+
mixamoAnimationId: "14667",
|
|
3139
|
+
mixamoClipName: "Walking",
|
|
3140
|
+
alternativeIds: ["14668", "14670", "14671", "14672"],
|
|
3141
|
+
speedAdjustment: 1
|
|
3142
|
+
},
|
|
3143
|
+
{
|
|
3144
|
+
presetName: "run",
|
|
3145
|
+
mixamoAnimationId: "14524",
|
|
3146
|
+
mixamoClipName: "Running",
|
|
3147
|
+
alternativeIds: ["14525", "14526", "14527", "14528"],
|
|
3148
|
+
speedAdjustment: 1
|
|
3149
|
+
},
|
|
3150
|
+
{
|
|
3151
|
+
presetName: "idle",
|
|
3152
|
+
mixamoAnimationId: "14330",
|
|
3153
|
+
mixamoClipName: "Idle",
|
|
3154
|
+
alternativeIds: ["14331", "14332", "14333"],
|
|
3155
|
+
speedAdjustment: 1
|
|
3156
|
+
},
|
|
3157
|
+
{
|
|
3158
|
+
presetName: "jump",
|
|
3159
|
+
mixamoAnimationId: "14340",
|
|
3160
|
+
mixamoClipName: "Jump",
|
|
3161
|
+
alternativeIds: ["14341", "14342", "14343", "14344"],
|
|
3162
|
+
speedAdjustment: 1
|
|
3163
|
+
},
|
|
3164
|
+
{
|
|
3165
|
+
presetName: "attack",
|
|
3166
|
+
mixamoAnimationId: "14084",
|
|
3167
|
+
mixamoClipName: "Sword And Shield Attack",
|
|
3168
|
+
alternativeIds: ["14085", "14086", "14088", "14090"],
|
|
3169
|
+
speedAdjustment: 1
|
|
3170
|
+
},
|
|
3171
|
+
{
|
|
3172
|
+
presetName: "dance",
|
|
3173
|
+
mixamoAnimationId: "14189",
|
|
3174
|
+
mixamoClipName: "Hip Hop Dancing",
|
|
3175
|
+
alternativeIds: ["14190", "14191", "14192", "14193"],
|
|
3176
|
+
speedAdjustment: 1
|
|
3177
|
+
},
|
|
3178
|
+
{
|
|
3179
|
+
presetName: "wave",
|
|
3180
|
+
mixamoAnimationId: "14660",
|
|
3181
|
+
mixamoClipName: "Waving",
|
|
3182
|
+
alternativeIds: ["14661"],
|
|
3183
|
+
speedAdjustment: 1
|
|
3184
|
+
},
|
|
3185
|
+
{
|
|
3186
|
+
presetName: "speak",
|
|
3187
|
+
mixamoAnimationId: "14591",
|
|
3188
|
+
mixamoClipName: "Talking",
|
|
3189
|
+
alternativeIds: ["14592", "14593"],
|
|
3190
|
+
speedAdjustment: 1
|
|
3191
|
+
},
|
|
3192
|
+
{
|
|
3193
|
+
presetName: "sit",
|
|
3194
|
+
mixamoAnimationId: "14564",
|
|
3195
|
+
mixamoClipName: "Sitting",
|
|
3196
|
+
alternativeIds: ["14565", "14566"],
|
|
3197
|
+
speedAdjustment: 1
|
|
3198
|
+
},
|
|
3199
|
+
{
|
|
3200
|
+
presetName: "sleep",
|
|
3201
|
+
mixamoAnimationId: "14572",
|
|
3202
|
+
mixamoClipName: "Sleeping Idle",
|
|
3203
|
+
alternativeIds: ["14573"],
|
|
3204
|
+
speedAdjustment: 1
|
|
3205
|
+
},
|
|
3206
|
+
{
|
|
3207
|
+
presetName: "crouch",
|
|
3208
|
+
mixamoAnimationId: "14180",
|
|
3209
|
+
mixamoClipName: "Crouching Idle",
|
|
3210
|
+
alternativeIds: ["14181", "14182", "14183"],
|
|
3211
|
+
speedAdjustment: 1
|
|
3212
|
+
},
|
|
3213
|
+
{
|
|
3214
|
+
presetName: "swim",
|
|
3215
|
+
mixamoAnimationId: "14610",
|
|
3216
|
+
mixamoClipName: "Swimming",
|
|
3217
|
+
alternativeIds: ["14611", "14612", "14613"],
|
|
3218
|
+
speedAdjustment: 0.9
|
|
3219
|
+
},
|
|
3220
|
+
{
|
|
3221
|
+
presetName: "fly",
|
|
3222
|
+
mixamoAnimationId: "14250",
|
|
3223
|
+
mixamoClipName: "Flying",
|
|
3224
|
+
alternativeIds: ["14251", "14252", "14253"],
|
|
3225
|
+
speedAdjustment: 0.8
|
|
3226
|
+
},
|
|
3227
|
+
{
|
|
3228
|
+
presetName: "climb",
|
|
3229
|
+
mixamoAnimationId: "14160",
|
|
3230
|
+
mixamoClipName: "Climbing",
|
|
3231
|
+
alternativeIds: ["14161", "14162", "14163"],
|
|
3232
|
+
speedAdjustment: 0.8
|
|
3233
|
+
},
|
|
3234
|
+
{
|
|
3235
|
+
presetName: "emote",
|
|
3236
|
+
mixamoAnimationId: "14220",
|
|
3237
|
+
mixamoClipName: "Cheering",
|
|
3238
|
+
alternativeIds: ["14221", "14222", "14223"],
|
|
3239
|
+
speedAdjustment: 1
|
|
3240
|
+
}
|
|
3241
|
+
];
|
|
3242
|
+
for (const mapping of defaults) {
|
|
3243
|
+
this.registerMapping(mapping);
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
};
|
|
3247
|
+
var CharacterTemplateRegistry = class {
|
|
3248
|
+
/**
|
|
3249
|
+
* Creates a new CharacterTemplateRegistry pre-loaded with default templates.
|
|
3250
|
+
* Pass `false` to create an empty registry.
|
|
3251
|
+
*/
|
|
3252
|
+
constructor(loadDefaults = true) {
|
|
3253
|
+
this.templates = /* @__PURE__ */ new Map();
|
|
3254
|
+
if (loadDefaults) {
|
|
3255
|
+
this.loadDefaultTemplates();
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
// -----------------------------------------------------------------------
|
|
3259
|
+
// Lookup
|
|
3260
|
+
// -----------------------------------------------------------------------
|
|
3261
|
+
/**
|
|
3262
|
+
* Get a template by its unique identifier.
|
|
3263
|
+
*
|
|
3264
|
+
* @param id - Template ID.
|
|
3265
|
+
* @returns The template, or undefined if not found.
|
|
3266
|
+
*/
|
|
3267
|
+
get(id) {
|
|
3268
|
+
return this.templates.get(id);
|
|
3269
|
+
}
|
|
3270
|
+
/**
|
|
3271
|
+
* Check if a template exists.
|
|
3272
|
+
*
|
|
3273
|
+
* @param id - Template ID.
|
|
3274
|
+
*/
|
|
3275
|
+
has(id) {
|
|
3276
|
+
return this.templates.has(id);
|
|
3277
|
+
}
|
|
3278
|
+
// -----------------------------------------------------------------------
|
|
3279
|
+
// Filtering
|
|
3280
|
+
// -----------------------------------------------------------------------
|
|
3281
|
+
/**
|
|
3282
|
+
* Get all templates matching a specific archetype.
|
|
3283
|
+
*
|
|
3284
|
+
* @param archetype - The character archetype to filter by.
|
|
3285
|
+
* @returns Array of matching templates.
|
|
3286
|
+
*/
|
|
3287
|
+
getByArchetype(archetype) {
|
|
3288
|
+
const results = [];
|
|
3289
|
+
for (const template of this.templates.values()) {
|
|
3290
|
+
if (template.archetype === archetype) {
|
|
3291
|
+
results.push(template);
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
return results;
|
|
3295
|
+
}
|
|
3296
|
+
/**
|
|
3297
|
+
* Get all templates matching a specific rig type.
|
|
3298
|
+
*
|
|
3299
|
+
* @param rigType - Rig type (e.g. "humanoid", "quadruped").
|
|
3300
|
+
* @returns Array of matching templates.
|
|
3301
|
+
*/
|
|
3302
|
+
getByRigType(rigType) {
|
|
3303
|
+
const results = [];
|
|
3304
|
+
for (const template of this.templates.values()) {
|
|
3305
|
+
if (template.rigType === rigType) {
|
|
3306
|
+
results.push(template);
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
return results;
|
|
3310
|
+
}
|
|
3311
|
+
/**
|
|
3312
|
+
* Search templates by name, description, or tags.
|
|
3313
|
+
*
|
|
3314
|
+
* @param query - Search query (case-insensitive substring match).
|
|
3315
|
+
* @returns Array of matching templates.
|
|
3316
|
+
*/
|
|
3317
|
+
search(query) {
|
|
3318
|
+
const q = query.toLowerCase();
|
|
3319
|
+
const results = [];
|
|
3320
|
+
for (const template of this.templates.values()) {
|
|
3321
|
+
const matchesName = template.name.toLowerCase().includes(q);
|
|
3322
|
+
const matchesDescription = template.description.toLowerCase().includes(q);
|
|
3323
|
+
const matchesTags = template.tags.some((t) => t.toLowerCase().includes(q));
|
|
3324
|
+
if (matchesName || matchesDescription || matchesTags) {
|
|
3325
|
+
results.push(template);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
return results;
|
|
3329
|
+
}
|
|
3330
|
+
// -----------------------------------------------------------------------
|
|
3331
|
+
// Enumeration
|
|
3332
|
+
// -----------------------------------------------------------------------
|
|
3333
|
+
/**
|
|
3334
|
+
* Get all registered templates.
|
|
3335
|
+
*/
|
|
3336
|
+
getAll() {
|
|
3337
|
+
return Array.from(this.templates.values());
|
|
3338
|
+
}
|
|
3339
|
+
/**
|
|
3340
|
+
* Get all available archetypes that have at least one template.
|
|
3341
|
+
*/
|
|
3342
|
+
getAvailableArchetypes() {
|
|
3343
|
+
const archetypes = /* @__PURE__ */ new Set();
|
|
3344
|
+
for (const template of this.templates.values()) {
|
|
3345
|
+
archetypes.add(template.archetype);
|
|
3346
|
+
}
|
|
3347
|
+
return Array.from(archetypes);
|
|
3348
|
+
}
|
|
3349
|
+
/**
|
|
3350
|
+
* Get the total number of registered templates.
|
|
3351
|
+
*/
|
|
3352
|
+
get size() {
|
|
3353
|
+
return this.templates.size;
|
|
3354
|
+
}
|
|
3355
|
+
// -----------------------------------------------------------------------
|
|
3356
|
+
// Registration
|
|
3357
|
+
// -----------------------------------------------------------------------
|
|
3358
|
+
/**
|
|
3359
|
+
* Register a custom character template.
|
|
3360
|
+
*
|
|
3361
|
+
* @param template - The template to register.
|
|
3362
|
+
*/
|
|
3363
|
+
register(template) {
|
|
3364
|
+
this.templates.set(template.id, template);
|
|
3365
|
+
}
|
|
3366
|
+
/**
|
|
3367
|
+
* Remove a template from the registry.
|
|
3368
|
+
*
|
|
3369
|
+
* @param id - Template ID to remove.
|
|
3370
|
+
* @returns `true` if the template was found and removed.
|
|
3371
|
+
*/
|
|
3372
|
+
unregister(id) {
|
|
3373
|
+
return this.templates.delete(id);
|
|
3374
|
+
}
|
|
3375
|
+
// -----------------------------------------------------------------------
|
|
3376
|
+
// Default Templates
|
|
3377
|
+
// -----------------------------------------------------------------------
|
|
3378
|
+
/**
|
|
3379
|
+
* Load the 12 default pre-rigged character templates.
|
|
3380
|
+
*
|
|
3381
|
+
* These cover common humanoid archetypes used in spatial computing,
|
|
3382
|
+
* VR, game development, and animation workflows.
|
|
3383
|
+
*/
|
|
3384
|
+
loadDefaultTemplates() {
|
|
3385
|
+
const defaults = [
|
|
3386
|
+
// ---- Male Adult Archetypes ----
|
|
3387
|
+
{
|
|
3388
|
+
id: "xbot",
|
|
3389
|
+
name: "X Bot",
|
|
3390
|
+
archetype: "male-adult",
|
|
3391
|
+
description: "Mixamo default male humanoid. Clean topology, production-ready skeleton with 65 bones. Ideal for prototyping and general-purpose character animation.",
|
|
3392
|
+
mixamoCharacterId: "mixamo-xbot-v1",
|
|
3393
|
+
rigType: "humanoid",
|
|
3394
|
+
boneCount: 65,
|
|
3395
|
+
polygonCount: 12456,
|
|
3396
|
+
availableFormats: ["fbx", "glb"],
|
|
3397
|
+
defaultAnimations: ["idle", "walk", "run", "jump"],
|
|
3398
|
+
thumbnailUrl: "https://www.mixamo.com/thumbnails/xbot.png",
|
|
3399
|
+
tags: ["male", "humanoid", "default", "robot", "prototype"],
|
|
3400
|
+
isDefault: true
|
|
3401
|
+
},
|
|
3402
|
+
{
|
|
3403
|
+
id: "brute",
|
|
3404
|
+
name: "Brute",
|
|
3405
|
+
archetype: "male-adult",
|
|
3406
|
+
description: "Muscular male character with heavy build. Suitable for warrior, fighter, and action archetypes. Enhanced upper body deformation.",
|
|
3407
|
+
mixamoCharacterId: "mixamo-brute-v1",
|
|
3408
|
+
rigType: "humanoid",
|
|
3409
|
+
boneCount: 65,
|
|
3410
|
+
polygonCount: 18234,
|
|
3411
|
+
availableFormats: ["fbx", "glb"],
|
|
3412
|
+
defaultAnimations: ["idle", "walk", "attack"],
|
|
3413
|
+
thumbnailUrl: "https://www.mixamo.com/thumbnails/brute.png",
|
|
3414
|
+
tags: ["male", "humanoid", "muscular", "warrior", "heavy"],
|
|
3415
|
+
isDefault: true
|
|
3416
|
+
},
|
|
3417
|
+
// ---- Female Adult Archetypes ----
|
|
3418
|
+
{
|
|
3419
|
+
id: "ybot",
|
|
3420
|
+
name: "Y Bot",
|
|
3421
|
+
archetype: "female-adult",
|
|
3422
|
+
description: "Mixamo default female humanoid. Clean topology, production-ready skeleton with 65 bones. Ideal for prototyping and general-purpose character animation.",
|
|
3423
|
+
mixamoCharacterId: "mixamo-ybot-v1",
|
|
3424
|
+
rigType: "humanoid",
|
|
3425
|
+
boneCount: 65,
|
|
3426
|
+
polygonCount: 11892,
|
|
3427
|
+
availableFormats: ["fbx", "glb"],
|
|
3428
|
+
defaultAnimations: ["idle", "walk", "run", "dance"],
|
|
3429
|
+
thumbnailUrl: "https://www.mixamo.com/thumbnails/ybot.png",
|
|
3430
|
+
tags: ["female", "humanoid", "default", "robot", "prototype"],
|
|
3431
|
+
isDefault: true
|
|
3432
|
+
},
|
|
3433
|
+
{
|
|
3434
|
+
id: "maria",
|
|
3435
|
+
name: "Maria",
|
|
3436
|
+
archetype: "female-adult",
|
|
3437
|
+
description: "Realistic female character with detailed facial blend shapes. Suitable for cinematic sequences and social VR.",
|
|
3438
|
+
mixamoCharacterId: "mixamo-maria-v1",
|
|
3439
|
+
rigType: "humanoid",
|
|
3440
|
+
boneCount: 67,
|
|
3441
|
+
polygonCount: 22340,
|
|
3442
|
+
availableFormats: ["fbx", "glb"],
|
|
3443
|
+
defaultAnimations: ["idle", "walk", "speak", "wave"],
|
|
3444
|
+
thumbnailUrl: "https://www.mixamo.com/thumbnails/maria.png",
|
|
3445
|
+
tags: ["female", "humanoid", "realistic", "facial", "cinematic"],
|
|
3446
|
+
isDefault: true
|
|
3447
|
+
},
|
|
3448
|
+
// ---- Child Archetypes ----
|
|
3449
|
+
{
|
|
3450
|
+
id: "ty",
|
|
3451
|
+
name: "Ty",
|
|
3452
|
+
archetype: "male-child",
|
|
3453
|
+
description: "Young male character with child proportions. Adjusted bone hierarchy for accurate child movement and shorter stride length.",
|
|
3454
|
+
mixamoCharacterId: "mixamo-ty-v1",
|
|
3455
|
+
rigType: "humanoid",
|
|
3456
|
+
boneCount: 63,
|
|
3457
|
+
polygonCount: 9876,
|
|
3458
|
+
availableFormats: ["fbx", "glb"],
|
|
3459
|
+
defaultAnimations: ["idle", "walk", "run", "jump"],
|
|
3460
|
+
thumbnailUrl: "https://www.mixamo.com/thumbnails/ty.png",
|
|
3461
|
+
tags: ["child", "male", "humanoid", "young", "small"],
|
|
3462
|
+
isDefault: true
|
|
3463
|
+
},
|
|
3464
|
+
{
|
|
3465
|
+
id: "lily",
|
|
3466
|
+
name: "Lily",
|
|
3467
|
+
archetype: "female-child",
|
|
3468
|
+
description: "Young female character with child proportions. Suitable for educational, family-friendly, and social VR applications.",
|
|
3469
|
+
mixamoCharacterId: "mixamo-lily-v1",
|
|
3470
|
+
rigType: "humanoid",
|
|
3471
|
+
boneCount: 63,
|
|
3472
|
+
polygonCount: 10234,
|
|
3473
|
+
availableFormats: ["fbx", "glb"],
|
|
3474
|
+
defaultAnimations: ["idle", "walk", "dance", "wave"],
|
|
3475
|
+
thumbnailUrl: "https://www.mixamo.com/thumbnails/lily.png",
|
|
3476
|
+
tags: ["child", "female", "humanoid", "young", "small"],
|
|
3477
|
+
isDefault: true
|
|
3478
|
+
},
|
|
3479
|
+
// ---- Robot Archetypes ----
|
|
3480
|
+
{
|
|
3481
|
+
id: "android",
|
|
3482
|
+
name: "Android Unit",
|
|
3483
|
+
archetype: "robot-humanoid",
|
|
3484
|
+
description: "Humanoid robot with mechanical joint articulation. Rigid body segments connected by revolute joints. Suitable for sci-fi and robotics simulations.",
|
|
3485
|
+
mixamoCharacterId: "mixamo-android-v1",
|
|
3486
|
+
rigType: "humanoid",
|
|
3487
|
+
boneCount: 60,
|
|
3488
|
+
polygonCount: 15678,
|
|
3489
|
+
availableFormats: ["fbx", "glb"],
|
|
3490
|
+
defaultAnimations: ["idle", "walk", "run"],
|
|
3491
|
+
thumbnailUrl: "https://www.mixamo.com/thumbnails/android.png",
|
|
3492
|
+
tags: ["robot", "humanoid", "mechanical", "sci-fi", "android"],
|
|
3493
|
+
isDefault: true
|
|
3494
|
+
},
|
|
3495
|
+
{
|
|
3496
|
+
id: "mech-worker",
|
|
3497
|
+
name: "Mech Worker",
|
|
3498
|
+
archetype: "robot-mechanical",
|
|
3499
|
+
description: "Industrial mechanical robot with hydraulic limbs. Heavy-duty construction archetype with simplified joint hierarchy for mechanical animation.",
|
|
3500
|
+
mixamoCharacterId: "mixamo-mechworker-v1",
|
|
3501
|
+
rigType: "humanoid",
|
|
3502
|
+
boneCount: 48,
|
|
3503
|
+
polygonCount: 20456,
|
|
3504
|
+
availableFormats: ["fbx", "glb"],
|
|
3505
|
+
defaultAnimations: ["idle", "walk", "attack"],
|
|
3506
|
+
thumbnailUrl: "https://www.mixamo.com/thumbnails/mechworker.png",
|
|
3507
|
+
tags: ["robot", "mechanical", "industrial", "heavy", "hydraulic"],
|
|
3508
|
+
isDefault: true
|
|
3509
|
+
},
|
|
3510
|
+
// ---- Creature Archetypes ----
|
|
3511
|
+
{
|
|
3512
|
+
id: "drake",
|
|
3513
|
+
name: "Drake",
|
|
3514
|
+
archetype: "creature-bipedal",
|
|
3515
|
+
description: "Bipedal dragon-like creature with wing attachments. Modified humanoid rig with additional wing and tail bone chains.",
|
|
3516
|
+
mixamoCharacterId: "mixamo-drake-v1",
|
|
3517
|
+
rigType: "humanoid",
|
|
3518
|
+
boneCount: 78,
|
|
3519
|
+
polygonCount: 24890,
|
|
3520
|
+
availableFormats: ["fbx", "glb"],
|
|
3521
|
+
defaultAnimations: ["idle", "walk", "attack", "fly"],
|
|
3522
|
+
thumbnailUrl: "https://www.mixamo.com/thumbnails/drake.png",
|
|
3523
|
+
tags: ["creature", "bipedal", "dragon", "fantasy", "wings", "tail"],
|
|
3524
|
+
isDefault: true
|
|
3525
|
+
},
|
|
3526
|
+
{
|
|
3527
|
+
id: "wolf",
|
|
3528
|
+
name: "Wolf",
|
|
3529
|
+
archetype: "creature-quadruped",
|
|
3530
|
+
description: "Quadruped wolf with full locomotion rig. Four-legged skeleton hierarchy with spine flexibility and tail chain.",
|
|
3531
|
+
mixamoCharacterId: "mixamo-wolf-v1",
|
|
3532
|
+
rigType: "quadruped",
|
|
3533
|
+
boneCount: 52,
|
|
3534
|
+
polygonCount: 14567,
|
|
3535
|
+
availableFormats: ["fbx", "glb"],
|
|
3536
|
+
defaultAnimations: ["idle", "walk", "run"],
|
|
3537
|
+
thumbnailUrl: "https://www.mixamo.com/thumbnails/wolf.png",
|
|
3538
|
+
tags: ["creature", "quadruped", "wolf", "animal", "four-legged"],
|
|
3539
|
+
isDefault: true
|
|
3540
|
+
},
|
|
3541
|
+
// ---- Stylized Archetypes ----
|
|
3542
|
+
{
|
|
3543
|
+
id: "paladin",
|
|
3544
|
+
name: "Paladin",
|
|
3545
|
+
archetype: "armored-knight",
|
|
3546
|
+
description: "Armored knight with plate armor and cape physics. Heavy character suitable for medieval and fantasy combat scenarios.",
|
|
3547
|
+
mixamoCharacterId: "mixamo-paladin-v1",
|
|
3548
|
+
rigType: "humanoid",
|
|
3549
|
+
boneCount: 68,
|
|
3550
|
+
polygonCount: 28456,
|
|
3551
|
+
availableFormats: ["fbx", "glb"],
|
|
3552
|
+
defaultAnimations: ["idle", "walk", "attack", "crouch"],
|
|
3553
|
+
thumbnailUrl: "https://www.mixamo.com/thumbnails/paladin.png",
|
|
3554
|
+
tags: ["humanoid", "armored", "knight", "medieval", "fantasy", "heavy"],
|
|
3555
|
+
isDefault: true
|
|
3556
|
+
},
|
|
3557
|
+
{
|
|
3558
|
+
id: "trooper",
|
|
3559
|
+
name: "Trooper",
|
|
3560
|
+
archetype: "sci-fi-soldier",
|
|
3561
|
+
description: "Sci-fi soldier with tactical gear and visor. Modern military archetype suitable for FPS, action, and tactical simulations.",
|
|
3562
|
+
mixamoCharacterId: "mixamo-trooper-v1",
|
|
3563
|
+
rigType: "humanoid",
|
|
3564
|
+
boneCount: 65,
|
|
3565
|
+
polygonCount: 19234,
|
|
3566
|
+
availableFormats: ["fbx", "glb"],
|
|
3567
|
+
defaultAnimations: ["idle", "walk", "run", "crouch", "attack"],
|
|
3568
|
+
thumbnailUrl: "https://www.mixamo.com/thumbnails/trooper.png",
|
|
3569
|
+
tags: ["humanoid", "soldier", "sci-fi", "military", "tactical", "fps"],
|
|
3570
|
+
isDefault: true
|
|
3571
|
+
}
|
|
3572
|
+
];
|
|
3573
|
+
for (const template of defaults) {
|
|
3574
|
+
this.templates.set(template.id, template);
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
};
|
|
3578
|
+
|
|
3579
|
+
export { CharacterTemplateRegistry, ErrorFormatter, HoloScriptREPL, HotReloadWatcher, MaterialEditor, MixamoAPI, MixamoError, MixamoFormatError, MixamoPresetMapper, MixamoRateLimitError, MixamoTopologyError, SceneInspector, SourceMapGenerator, VisualEditor, getMaterialEditorBuiltinPresets, listMaterialEditorQuickPickPresetNames, listMaterialEditorQuickPickPresetsByCategory, startREPL };
|
|
3580
|
+
//# sourceMappingURL=index.js.map
|
|
3581
|
+
//# sourceMappingURL=index.js.map
|