diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj index e76a83e..59dd0aa 100644 --- a/Assembly-CSharp.csproj +++ b/Assembly-CSharp.csproj @@ -53,6 +53,7 @@ + diff --git a/Assets/Needle/Components.codegen/DishIngredientsTap.cs b/Assets/Needle/Components.codegen/DishIngredientsTap.cs new file mode 100644 index 0000000..800ba20 --- /dev/null +++ b/Assets/Needle/Components.codegen/DishIngredientsTap.cs @@ -0,0 +1,14 @@ +// NEEDLE_CODEGEN_START +// auto generated code - do not edit directly + +#pragma warning disable + +namespace Needle.Typescript.GeneratedComponents +{ + public partial class DishIngredientsTap : UnityEngine.MonoBehaviour + { + public void onPointerClick(UnityEngine.EventSystems.PointerEventData @_args) {} + } +} + +// NEEDLE_CODEGEN_END \ No newline at end of file diff --git a/Assets/Needle/Components.codegen/DishIngredientsTap.cs.meta b/Assets/Needle/Components.codegen/DishIngredientsTap.cs.meta new file mode 100644 index 0000000..d610618 --- /dev/null +++ b/Assets/Needle/Components.codegen/DishIngredientsTap.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 53682507d1bf626e741afef604c0b9f0 \ No newline at end of file diff --git a/Assets/Needle/Components.codegen/MenuController.cs b/Assets/Needle/Components.codegen/MenuController.cs index 38ac0a4..8603d48 100644 --- a/Assets/Needle/Components.codegen/MenuController.cs +++ b/Assets/Needle/Components.codegen/MenuController.cs @@ -7,16 +7,12 @@ namespace Needle.Typescript.GeneratedComponents { public partial class MenuController : UnityEngine.MonoBehaviour { - public bool @isMobile = false; - public bool @isDesktop = false; - public bool @isXR = false; public UnityEngine.Transform[] @dishes; public UnityEngine.Transform @webXROrigin; - [UnityEngine.Tooltip("Local-space vertical bob amplitude (meters). Set to 0 to disable.")] + [UnityEngine.Tooltip("Local-space vertical bob amplitude (meters). Set to 0 to disable. Peak-to-peak motion is 2× this value; 0.004 ≈ 8 mm total — subtle but visible.")] public float @dishBobAmplitude = 0.004f; [UnityEngine.Tooltip("Bob angular speed (radians per second).")] public float @dishBobSpeed = 1f; - public float @selectedDishIndex = 0f; public void OnEnable() {} public void onEnterXR(object @args) {} public void onLeaveXR(object @_args) {} @@ -26,7 +22,9 @@ namespace Needle.Typescript.GeneratedComponents public void setupMobileControls() {} public void setupDesktopControls() {} public void createMenuMobileControls() {} + public void prepareForAR() {} public float getDishSlotCount() { return default; } + public void registerDishRoot(UnityEngine.Transform @dish) {} public string getPickerLabel() { return default; } public void selectPreviousDish() {} public void selectNextDish() {} diff --git a/Assets/Resources/Dishes/quistibi_scan.glb b/Assets/Resources/Dishes/quistibi_scan.glb new file mode 100644 index 0000000..2670d29 --- /dev/null +++ b/Assets/Resources/Dishes/quistibi_scan.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14cd38d9fb9b9ba4aadaf6482df8bfdf2dc7346dc2368a589d7fe927718e9482 +size 17003200 diff --git a/Assets/Resources/Dishes/quistibi_scan.glb.meta b/Assets/Resources/Dishes/quistibi_scan.glb.meta new file mode 100644 index 0000000..9c79e24 --- /dev/null +++ b/Assets/Resources/Dishes/quistibi_scan.glb.meta @@ -0,0 +1,73 @@ +fileFormatVersion: 2 +guid: 776567fc7344c7b408f89845795466e3 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 804e1ce4c496647cfa3f1a1134187c71, type: 3} + _removeEmptyRootObjects: 1 + _scaleFactor: 1 + _deduplicateResources: 0 + _deduplicatedStatistics: + meshCountBefore: 0 + meshCountAfter: 0 + textureCountBefore: 0 + textureCountAfter: 0 + _maximumLod: 300 + _readWriteEnabled: 1 + _generateColliders: 0 + _addColliders: 0 + _swapUvs: 0 + _generateLightmapUVs: 0 + _importBlendShapeNames: 1 + _blendShapeFrameWeight: + _option: 0 + _multiplier: 1 + _importNormals: 0 + _importTangents: 0 + _importCamera: 2 + _importAnimations: 2 + _mecanimHumanoidFlip: 0 + _addAnimatorComponent: 0 + _animationLoopTime: 1 + _animationLoopPose: 0 + _importMaterials: 1 + _enableGpuInstancing: 0 + _texturesReadWriteEnabled: 1 + _generateMipMaps: 1 + _useSceneNameIdentifier: 1 + _textureCompression: -50 + _gltfAsset: 'Generator: Sketchfab-16.95.0 + + Version: 2.0 + + + + Extras: + + +' + optimizeGameObjects: 0 + materials: + - {instanceID: 0} + textures: + - {instanceID: 0} + - {instanceID: 0} + hasSceneData: 1 + hasAnimationData: 0 + hasMaterialData: 1 + hasTextureData: 1 + animations: [] + _extensions: [] + _textures: + - texture: {instanceID: 0} + shouldBeLinear: 0 + shouldBeNormalMap: 0 + - texture: {instanceID: 0} + shouldBeLinear: 1 + shouldBeNormalMap: 1 + _mainAssetIdentifier: scenes/Sketchfab_Scene + _importPlugins: [] diff --git a/Assets/Scenes/MenuScene.unity b/Assets/Scenes/MenuScene.unity index 4302eb1..775eee8 100644 --- a/Assets/Scenes/MenuScene.unity +++ b/Assets/Scenes/MenuScene.unity @@ -571,6 +571,84 @@ Transform: m_CorrespondingSourceObject: {fileID: 8897093224900880590, guid: 82b3eac92005fee47a7a02b0e77a8231, type: 3} m_PrefabInstance: {fileID: 1684256003} m_PrefabAsset: {fileID: 0} +--- !u!1001 &1784117311 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 1792418156} + m_Modifications: + - target: {fileID: 8128668236925612103, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_Name + value: quistibi_scan + objectReference: {fileID: 0} + - target: {fileID: 8128668236925612103, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_IsActive + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalScale.x + value: 1.1699198 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalScale.y + value: 1.1699204 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalScale.z + value: 1.1699204 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalPosition.x + value: 0.476 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalPosition.y + value: -0.002 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalPosition.z + value: 0.464 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalRotation.w + value: 0.7071068 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalRotation.x + value: -0.7071068 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalRotation.y + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: -5645937942226959685, guid: 776567fc7344c7b408f89845795466e3, type: 3} +--- !u!4 &1784117312 stripped +Transform: + m_CorrespondingSourceObject: {fileID: 8897093224900880590, guid: 776567fc7344c7b408f89845795466e3, type: 3} + m_PrefabInstance: {fileID: 1784117311} + m_PrefabAsset: {fileID: 0} --- !u!1 &1792418155 GameObject: m_ObjectHideFlags: 0 @@ -602,6 +680,7 @@ Transform: m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1684256004} + - {fileID: 1784117312} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1792418157 @@ -621,7 +700,10 @@ MonoBehaviour: isXR: 0 dishes: - {fileID: 1684256004} + - {fileID: 1784117312} webXROrigin: {fileID: 0} + dishBobAmplitude: 0.004 + dishBobSpeed: 1 selectedDishIndex: 0 --- !u!1660057539 &9223372036854775807 SceneRoots: diff --git a/Needle/MenuScene/assets/MenuScene.glb b/Needle/MenuScene/assets/MenuScene.glb index 65f141b..0a4fe34 100644 --- a/Needle/MenuScene/assets/MenuScene.glb +++ b/Needle/MenuScene/assets/MenuScene.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:917b8caa2332a76e9c43953b20f123506a74c2c7a026da095c3cc9d6ac0c96c0 -size 4262692 +oid sha256:b74ca01b300b168151ec4651e42025a654612fe4a24682c8a3692b9de0fa61ba +size 4262696 diff --git a/Needle/MenuScene/dist/assets/MenuScene.glb b/Needle/MenuScene/dist/assets/MenuScene.glb index a57c507..b738897 100644 Binary files a/Needle/MenuScene/dist/assets/MenuScene.glb and b/Needle/MenuScene/dist/assets/MenuScene.glb differ diff --git a/Needle/MenuScene/dist/include/dishes/quistibi_scan.glb b/Needle/MenuScene/dist/include/dishes/quistibi_scan.glb new file mode 100644 index 0000000..f69e4ef Binary files /dev/null and b/Needle/MenuScene/dist/include/dishes/quistibi_scan.glb differ diff --git a/Needle/MenuScene/dist/include/poster.webp b/Needle/MenuScene/dist/include/poster.webp index 706de64..ff304cc 100644 Binary files a/Needle/MenuScene/dist/include/poster.webp and b/Needle/MenuScene/dist/include/poster.webp differ diff --git a/Needle/MenuScene/dist/index.html b/Needle/MenuScene/dist/index.html index b28dbb9..751fa1c 100644 --- a/Needle/MenuScene/dist/index.html +++ b/Needle/MenuScene/dist/index.html @@ -3,7 +3,7 @@ - + @@ -20,9 +20,9 @@ - - - + + + @@ -37,36 +37,35 @@ - + - - - - + + + + - - + + - - + + - + Previous Next - View in AR diff --git a/Needle/MenuScene/dist/index.html.gz b/Needle/MenuScene/dist/index.html.gz index 99b7420..af7e214 100644 Binary files a/Needle/MenuScene/dist/index.html.gz and b/Needle/MenuScene/dist/index.html.gz differ diff --git a/Needle/MenuScene/dist/needle-app.js b/Needle/MenuScene/dist/needle-app.js index cc54f27..33a484d 100644 --- a/Needle/MenuScene/dist/needle-app.js +++ b/Needle/MenuScene/dist/needle-app.js @@ -53,7 +53,7 @@ if (!customElements.get(componentName)) { while(this.basePath.endsWith('/')) { this.basePath = this.basePath.slice(0, -1); } - script.src = this.getAttribute('script-src') || `${this.basePath}/assets/index-dZODtGdh.js`; + script.src = this.getAttribute('script-src') || `${this.basePath}/assets/index-CCjWXS7N.js`; this.shadowRoot.appendChild(script); this.needleEngine = document.createElement('needle-engine'); diff --git a/Needle/MenuScene/dist/needle.buildinfo.json b/Needle/MenuScene/dist/needle.buildinfo.json index c31c6ae..8a63609 100644 --- a/Needle/MenuScene/dist/needle.buildinfo.json +++ b/Needle/MenuScene/dist/needle.buildinfo.json @@ -1 +1 @@ -{"time":"2026-04-19T21:15:07.053Z","totalsize":20524931,"files":[{"path":"assets/favicon.DtN_Fmav.ico","hash":"8d99ceeacb8facc2b07ea2ce437fc23f6d7ce42492e9abbb7bb98d82749abc96","size":15086},{"path":"assets/generateMeshBVH.worker-BRfdalWV.js","hash":"26dbdd8c06867454f8cf046d7963692ba10f4b6eebd5cbde83227908dbb2a886","size":390545},{"path":"assets/generateMeshBVH.worker-BRfdalWV.js.gz","hash":"7eae0d7a22c7346403d73cf42a22b1f8d7008922462bcb217a00f66c2a2fb97a","size":96965},{"path":"assets/GenerateMeshBVHWorker.DMD7ZOpN.js","hash":"c39f757743906680af5dc17743a0082c4385e230ab5006b427953bdec6f4f76a","size":80},{"path":"assets/gltf-progressive.BBJGcb3s.js","hash":"069fa050ccdccf55776c11cf63bafff2ec45e7720da48695187772034a17b0ce","size":40226},{"path":"assets/gltf-progressive.BBJGcb3s.js.gz","hash":"a4adb3cb3cf3124f78482d0595afa825555b0f0b45905969ae866f34dae36d3c","size":13752},{"path":"assets/gltf-progressive.worker-DbINgLUC.js","hash":"d95d7d669ac99e74ab7945b52f825793c8977a9b6045aa7f202b867a6295abd4","size":533027},{"path":"assets/gltf-progressive.worker-DbINgLUC.js.gz","hash":"9ce0dcfd79441e40258af9801a62c13ed8bb454de617b28a81951b22c5bc3f1c","size":140931},{"path":"assets/image_0_57d6f3ff-437a-5d8c-b498-70006fea7ac6.glb","hash":"0db87165cd73638d7ec59063a3259c82ea31f8b75268c45ef3f67cfe7548b0f8","size":679988},{"path":"assets/image_0_7e20cd65-055c-55da-abc5-33ed0d041c6e.glb","hash":"53c83a58d520bd38e3ba9b3ad40540d71666f2ead4a6f1c93ffa215b5b2f15fe","size":124860},{"path":"assets/image_0_dd91963e-6e03-5980-9515-0c01036a3d8f.glb","hash":"446789d32b3f3a151ebdf42cd6a22cf25944234393f5022c1a682f5be9aab538","size":183456},{"path":"assets/image_1_57d6f3ff-437a-5d8c-b498-70006fea7ac6.glb","hash":"352aa276516fe71f30a76fa65dd6e839062bacc9d6eecf448fb7e28491222c58","size":246428},{"path":"assets/image_1_7e20cd65-055c-55da-abc5-33ed0d041c6e.glb","hash":"c559996edec4895eb15174625cbe1f1bcfe9488c87b85235099a31bddf87bb4b","size":45120},{"path":"assets/image_1_dd91963e-6e03-5980-9515-0c01036a3d8f.glb","hash":"0c7b03966f59d3f2bd6217f4c363ce74188507b4f6e922c2a6ea9bafb31b2e6b","size":67748},{"path":"assets/image_2_57d6f3ff-437a-5d8c-b498-70006fea7ac6.glb","hash":"9b71a30bc0e2c87719c028c110e4a61968c39857f75de3e29aae3db617f6a6b1","size":89784},{"path":"assets/image_2_7e20cd65-055c-55da-abc5-33ed0d041c6e.glb","hash":"816d323f323b3628bfc34f9fa9c8a1e9442fbcda2e02dc6ed79b8c230f6f8984","size":15152},{"path":"assets/image_2_dd91963e-6e03-5980-9515-0c01036a3d8f.glb","hash":"1e5b6d5e1c318a28363844b6dc76d9573b390f672131e5ae9efd6621e7f6f60c","size":18884},{"path":"assets/image_3_57d6f3ff-437a-5d8c-b498-70006fea7ac6.glb","hash":"8b2d1b2a903db4a4c829893af959f07e942250dd36b906c782f91bc24768cf6f","size":30088},{"path":"assets/image_4_57d6f3ff-437a-5d8c-b498-70006fea7ac6.glb","hash":"246b638b12f0db5738d9132f1e228ccd9e65259d6aee7ea9f446054778faed91","size":11476},{"path":"assets/index-dZODtGdh.js","hash":"53f310571088e5219c067ad7a2691d38be20d1cc690b74e837e44e04ff4e9681","size":4649},{"path":"assets/index-dZODtGdh.js.gz","hash":"536e984a0a3799719e42c2dc27b2cf718641a474453c21a27ed5e4f927394841","size":2016},{"path":"assets/index.2XwOwFJz.css","hash":"25faeee214aadb9a8bfcaf563f5297c14dbebe5bb03ac0f5c2e95c4bb8801cb9","size":1317},{"path":"assets/index.2XwOwFJz.css.gz","hash":"628d1722e3b1dbeb9a4b29bb1ab09ba911c26a2ad0d32860b92c3cd450334109","size":656},{"path":"assets/JsMaterialXCore.B97gKRq_.wasm","hash":"6b0d149adcb290fac326c754c878a5d8389b2fe7e6c5dbaf22129b2e83673762","size":1410873},{"path":"assets/JsMaterialXCore.DGnKx6GJ.js","hash":"3458954c3137edcdf72a873eb4a92fc8fd947676a10c7dab1d2ef8e501ce5c07","size":65},{"path":"assets/JsMaterialXGenShader.data.DOxudtll.txt","hash":"9eaf101c24d568abe8b21a35cec29df32a9d8ee18986787abcada3ac95ffacc5","size":1512300},{"path":"assets/JsMaterialXGenShader.data.vJnq-SJK.js","hash":"8469e42ae946745a483b829903a8ebe26775c6084a19f85928633c5bbe5cca59","size":65},{"path":"assets/JsMaterialXGenShader.DFP9U7Gu.js","hash":"b3026b30cb3dc86f025d4a21b8befe5b9cbbd2b3d471ecc18c0dd7ed591ce0bb","size":65},{"path":"assets/JsMaterialXGenShader.P2pWAaz9.wasm","hash":"09adab7b142ea4dc768ac40b7947a5eac5c7556a50dbf60281db9c11d555a98e","size":2564784},{"path":"assets/materialx.CnHmG--Y.js","hash":"46e0e6b37df7b132f6340e2bd84211f916c24933868671ba4b5d3a507f8d878c","size":140597},{"path":"assets/materialx.CnHmG--Y.js.gz","hash":"2a3d121d401ee013951712a5ad6a4d7d023201b4ea28625d9d07791999a8a80e","size":42394},{"path":"assets/materialx.eMaybKAQ.js","hash":"d25f196b1725a13ce1bc579a42fe162335314670daf1e24c973b45b7eda95140","size":233},{"path":"assets/materialx.eMaybKAQ.js.gz","hash":"a2c260cb703b423b85ba96b38f3c00a9b1021882ec5feb13338ca1fa3db8db3e","size":168},{"path":"assets/MenuScene.glb","hash":"88ec0ff950e40a36028ca29a1c4d83e2884cdb7203df1f8c19c478bb7161f617","size":117520},{"path":"assets/N8AO.C2WXsanG.js","hash":"c53e27ed55ec7273d644739fde121bb6afd4027a9e7f56aa71e16899fb5287c5","size":121},{"path":"assets/N8AO.C2WXsanG.js.gz","hash":"53f936a93e15460b4c340dabe2b8dbd080c6fcf4822c97562763d6e2daf82d16","size":119},{"path":"assets/needle-engine-particles.Ce6eiXqY.js","hash":"03af01b65c130f1e9ff0fbad2a554d590680d9c23ace434fd42288a3998a1e26","size":57273},{"path":"assets/needle-engine-particles.Ce6eiXqY.js.gz","hash":"e99791161730c5a10347663ce2521bbe718f24cd2b97970b8f57341ba29d7912","size":15291},{"path":"assets/needle-engine-ui.D3ZoIJG5.js","hash":"5860867c4c37373a06ef562bebb7cc6ff6bf858a94e8b3050807b590c816ade3","size":549800},{"path":"assets/needle-engine-ui.D3ZoIJG5.js.gz","hash":"a25a4afcf598e94674a27628ce545a6b43aa31a728bb044f0caabdcd72b24db3","size":156626},{"path":"assets/needle-engine-webxr.DiiWSXeh.js","hash":"56160dc3d43ffb24abe7b29d4a16fa216d54bf0c8b9647f7be61a4c2baadbf22","size":339590},{"path":"assets/needle-engine-webxr.DiiWSXeh.js.gz","hash":"482f17358a4f6478a662a8c81b819e28d4af8c35318345155940981bf390c85b","size":95876},{"path":"assets/needle-engine.BJP2NBoC.js","hash":"baa3d6660ade76a55ba22625b3d4ce682507fa967afb4b15a7122c840b77ea7d","size":8806},{"path":"assets/needle-engine.BJP2NBoC.js.gz","hash":"042d5bd562bfc7e4d89e76b88e15dd365ee678fc4f22ab846cc6be909a35aeeb","size":2125},{"path":"assets/needle-engine.CE4b2dOg.js","hash":"926d66907210eb8304c8ebb022b07804b2528a1cb0fcb3a693dafb920cf9af4b","size":83},{"path":"assets/needle-engine.D6xdy26G.js","hash":"493a70cfc7882efd95a49064109d497fa52da8cb21ebead82269844699410225","size":8399},{"path":"assets/needle-engine.D6xdy26G.js.gz","hash":"ae0ffbee3b9df88dfbdadc6c9b7ef7e3a526dd2a9fd1dca7d3e189004eb1feb3","size":2824},{"path":"assets/needle-engine.dep.B3nA4wQ5.js","hash":"903cb1e6d479f412d33f3c6f420da30914ebdc32f2fa5af317ef1b6a83c19d46","size":157},{"path":"assets/needle-engine.dep.B3nA4wQ5.js.gz","hash":"f3ef06e5c09c66b910c15a96d56be694dfc1043699234de32880009cc425418d","size":146},{"path":"assets/needle-engine.dep.BzZBR-mM.js","hash":"446200bd2574e013d8968923320e1092907cfb8f4890ae1a2007ad3a2efb4ec5","size":91},{"path":"assets/needle-engine.dep.DFJFZC0m.js","hash":"fb9b21b29e7bdd4af6e14c3716d27ffc9642107224b17a3cc1cf383c57355f80","size":3621},{"path":"assets/needle-engine.dep.DFJFZC0m.js.gz","hash":"a52e033c1ec67b0da0e40f27167b672650f08af40a738a27ba51a4b8f226276d","size":1473},{"path":"assets/needle-engine.DZYzd-GQ.js","hash":"2e3c834d36d21032447648e4ec17afb5315c500344b489d93b9066e3c523dfa9","size":17976},{"path":"assets/needle-engine.DZYzd-GQ.js.gz","hash":"5ea03027df6df24fe710894e32b7d24bbfb2119e8e491ffcb61039d3ad44683b","size":7317},{"path":"assets/needle-engine.extras.B9pwA3ET.js","hash":"1334d355762b37d56bbe4ed48e482236bf18381f57003cc54b77dab5fd3a4aaa","size":287996},{"path":"assets/needle-engine.extras.B9pwA3ET.js.gz","hash":"101fb849d07568372c929552685d7e8caaf6928f701cbfdf93a81ed2c20ddb7b","size":77870},{"path":"assets/needle-engine.extras.dI9yZHT7.js","hash":"cc5c294a2ade49c6f6e5e678622765268f195e0d49f73715943621565b1adf07","size":45379},{"path":"assets/needle-engine.extras.dI9yZHT7.js.gz","hash":"b3378081032b6aa7a774132c9e3fe4d362ee699d9d96b60b5bfdb271958141e5","size":11721},{"path":"assets/needle-engine@5.0.3.js","hash":"4add4c8b8b470fa0389fe4451e76fdd9e46ceafbdc02bfc73d72c66b61e42905","size":102370},{"path":"assets/needle-engine@5.0.3.js.gz","hash":"18a34f825e37f2a69359a5664866e7382b94d29e7b4882f950c17c5bedf702f5","size":32234},{"path":"assets/peerjs.UdR1_Ath.js","hash":"4d1141e782b5074ff0bc6471d1496892bf4cc3d37bff6b26dbb543ff3215f511","size":139970},{"path":"assets/peerjs.UdR1_Ath.js.gz","hash":"d655e3293942f858436231a5f1c98cacebc4f12b462bbbf7091d91fca6b1804f","size":35531},{"path":"assets/postprocessing.ao.BRYreHJS.js","hash":"9a752a5cdb636bec813221103a7070018fee1303940ad177475bc4f01eb0f3a3","size":162950},{"path":"assets/postprocessing.ao.BRYreHJS.js.gz","hash":"13b3f7db36f6c50bb3de7dffee4ac60670f9267ac4acf052f75b699752dee620","size":77949},{"path":"assets/postprocessing.Wy71DpiM.js","hash":"a13d1598fd4cc60a06e264fef9ed207185963a01b21473cc30dfdcd11307b2b1","size":318819},{"path":"assets/postprocessing.Wy71DpiM.js.gz","hash":"205160a2233b8646a9c1ec6c6f17a414067d811299e3f13f5332a6ce5b66ac61","size":111140},{"path":"assets/rapier3d.DBFmcAzL.js","hash":"dee0e85e47299f58b6af45b9e0d3ec8e87365a2bcfbfe521c7ca7731f721c031","size":2236518},{"path":"assets/rapier3d.DBFmcAzL.js.gz","hash":"756ae1c26b91e37df8728ee0e438208e9138a328daaac20c91d9c9ec0be6a9d1","size":829360},{"path":"assets/register_types.C6IEiUJa.js","hash":"55fd44ef3074619f9040df2ff0c7fce7c90a5357d7b5ca49a7f70f023ceca50c","size":8447},{"path":"assets/register_types.C6IEiUJa.js.gz","hash":"aafb3719c9d855f17b5d783736597767b2dcb6123175a6ad21c6905cdff90cda","size":2819},{"path":"assets/rolldown-runtime.CYVBeYYp.js","hash":"c62a259caf983f4ea8940cae8d9abe35924f21613de275b1102b5f9e6243c126","size":685},{"path":"assets/rolldown-runtime.CYVBeYYp.js.gz","hash":"52bb3183225ba42d42738dbbe972d6bd0b05d307269e38b8c3ca2a672979ba57","size":417},{"path":"assets/three-examples.extras@0.169.19.js","hash":"5dc9c34c94bc4ed886e7a06561292a396deacdb33c8c69e92b3ee84d4f88f3e5","size":467},{"path":"assets/three-examples.extras@0.169.19.js.gz","hash":"7ea7b34cc92c9f75c1aa5ef464e818eacaafeb4b9bea3f6e8fb485e487a0b342","size":312},{"path":"assets/three-examples@0.169.19.js","hash":"b0161353f6b95d0ba8b7eae4ccc3a5238dbcdf1eb4627a3faa743aa400496f66","size":341977},{"path":"assets/three-examples@0.169.19.js.gz","hash":"7b60c10406058b2ae13a58244f290e21db2d795302986078082c06cffbbc1520","size":107289},{"path":"assets/three-mesh-bvh.B-oXEGo2.js","hash":"9dff9b6b9018b998059a57cde938ad273062e25a06232f019bbc5a6b0ef4b7c7","size":54824},{"path":"assets/three-mesh-bvh.B-oXEGo2.js.gz","hash":"4c10233f3f98214c2604e628406d76cd9419b3629e42f2b4c356f5b15b15e093","size":17657},{"path":"assets/three-mesh-ui.CgGzZ3ml.js","hash":"1e33cd1d93ac2c9e4cea4d6b35094401cce041850d374f105513a44283b5884d","size":113137},{"path":"assets/three-mesh-ui.CgGzZ3ml.js.gz","hash":"6f016243f03011b37c43bcb2b307876075338430f185ea3c82ecfc67bdcc9e70","size":26682},{"path":"assets/three-quarks.DALdLWPD.js","hash":"4be7a10535ad22e8333ffa70d687606c5778dd03fddc7298c4f665911bc9c452","size":148379},{"path":"assets/three-quarks.DALdLWPD.js.gz","hash":"17ba7fe25a94d37f6d7d6d56c8c6cb2c748b4d6c297e67e72a4cbad96316268c","size":34112},{"path":"assets/three@0.169.19.js","hash":"e867c49a8afe22f7d79b960e57640ac4427d50a587a878b0e560cbb5a42d00ef","size":965775},{"path":"assets/three@0.169.19.js.gz","hash":"3ece83a0fb0555973130bc83e5eeecc11c4409975e0aec455441b37f920490de","size":249564},{"path":"include/dishes/cc0_-_pizza_salami.glb","hash":"4210647f4c2ccf10106892dada9946dda3e8ba9fdb99a37a66e8ac96f01d19f8","size":4148396},{"path":"include/poster.webp","hash":"be256c367edaff3e95d103002147e20d8c212322c289cea4f03591d01eb9a2e0","size":11252},{"path":"index.html","hash":"63961d27d7a68b6cf698e6eba925506db5bf19f3b8361191984bf1ced7aa49ee","size":7594},{"path":"index.html.gz","hash":"341bcd137ba2f1699a66abbaed0a9e026d345ce9992a28e61994cba6df1ccab9","size":2822},{"path":"needle-app.js","hash":"ff8e96b9fbdc0bec52fad70fdcaa306e1cc97dbc9883c615de216234e906bb08","size":3505}]} \ No newline at end of file +{"time":"2026-06-23T10:35:45.771Z","totalsize":20542056,"files":[{"path":"assets/favicon.DtN_Fmav.ico","hash":"8d99ceeacb8facc2b07ea2ce437fc23f6d7ce42492e9abbb7bb98d82749abc96","size":15086},{"path":"assets/generateMeshBVH.worker-BRfdalWV.js","hash":"26dbdd8c06867454f8cf046d7963692ba10f4b6eebd5cbde83227908dbb2a886","size":390545},{"path":"assets/generateMeshBVH.worker-BRfdalWV.js.gz","hash":"7eae0d7a22c7346403d73cf42a22b1f8d7008922462bcb217a00f66c2a2fb97a","size":96965},{"path":"assets/GenerateMeshBVHWorker.u88EPSS1.js","hash":"28d68fb456a1c66f9f8ef2e161fd8ef3a58f76f0e4e1a32984a35c1ba1d49afe","size":80},{"path":"assets/gltf-progressive.BBJGcb3s.js","hash":"069fa050ccdccf55776c11cf63bafff2ec45e7720da48695187772034a17b0ce","size":40226},{"path":"assets/gltf-progressive.BBJGcb3s.js.gz","hash":"a4adb3cb3cf3124f78482d0595afa825555b0f0b45905969ae866f34dae36d3c","size":13752},{"path":"assets/gltf-progressive.worker-DbINgLUC.js","hash":"d95d7d669ac99e74ab7945b52f825793c8977a9b6045aa7f202b867a6295abd4","size":533027},{"path":"assets/gltf-progressive.worker-DbINgLUC.js.gz","hash":"9ce0dcfd79441e40258af9801a62c13ed8bb454de617b28a81951b22c5bc3f1c","size":140931},{"path":"assets/image_0_57d6f3ff-437a-5d8c-b498-70006fea7ac6.glb","hash":"e08064e5d9e0ceff475630981e5b42bd76da3338cf10e9f4f485c1d99de50bb4","size":679988},{"path":"assets/image_0_7e20cd65-055c-55da-abc5-33ed0d041c6e.glb","hash":"df9328955dce908a964f12c32ef4166837f6337cd2a8f6a1419f208f8a07e6dc","size":124860},{"path":"assets/image_0_dd91963e-6e03-5980-9515-0c01036a3d8f.glb","hash":"7d2621867226dfc3a4e48db09b7b110f79c5a28c76a40e3798f0a67afeadd8b7","size":183456},{"path":"assets/image_1_57d6f3ff-437a-5d8c-b498-70006fea7ac6.glb","hash":"a23189cf900cf92c16624f0cf75b16a299876f3fb89bd392fea05fa0b7e7a7b7","size":246428},{"path":"assets/image_1_7e20cd65-055c-55da-abc5-33ed0d041c6e.glb","hash":"ecdeeefcb973c98214198b007da889afc96071b135d4e1c3ba73f191a658b0eb","size":45120},{"path":"assets/image_1_dd91963e-6e03-5980-9515-0c01036a3d8f.glb","hash":"98d5ec0810f3399eb745108867ba01afde6286bd200c75dfc5cf61d514310d14","size":67748},{"path":"assets/image_2_57d6f3ff-437a-5d8c-b498-70006fea7ac6.glb","hash":"378af4fd4d4bed8a149371fe13e11996c092cfa171005d91b05a30b5862e8c13","size":89784},{"path":"assets/image_2_7e20cd65-055c-55da-abc5-33ed0d041c6e.glb","hash":"c4df3b220f99d0a484c6008abb77b73efb57c1f0a3f12205baa3cb34e3ef2941","size":15152},{"path":"assets/image_2_dd91963e-6e03-5980-9515-0c01036a3d8f.glb","hash":"ab7962731f89b648b9fb4ca81c48030c524767604b3ea45203836fd7d2f0511c","size":18884},{"path":"assets/image_3_57d6f3ff-437a-5d8c-b498-70006fea7ac6.glb","hash":"3db4d8f407ec2cd13d02a7cbf7681178091455be2503156ac420dda25e21c29e","size":30088},{"path":"assets/image_4_57d6f3ff-437a-5d8c-b498-70006fea7ac6.glb","hash":"8dfb1b3a6609cb96c4c7f4ea183d22d4647c6647148cefb86c2b3dc2592e88a9","size":11476},{"path":"assets/index-CCjWXS7N.js","hash":"fe25ccaeb311c7b8c4d0815591ed4b3147f24640150605e4d44f8ee8a235f8a8","size":6676},{"path":"assets/index-CCjWXS7N.js.gz","hash":"96af1504152829350b13734a34aa449546ccaae0b87e68c3a926eb6375d74fa0","size":2903},{"path":"assets/index.a_pIm1gH.css","hash":"4175198b6c6933e9f1bb52b35f939d44f7002ffc0048627418dffe33da4e0856","size":1178},{"path":"assets/index.a_pIm1gH.css.gz","hash":"1c1c4f17023abec1d465fc26560d0c750969cab9ed5eedc765a2433adef11665","size":621},{"path":"assets/JsMaterialXCore.B97gKRq_.wasm","hash":"6b0d149adcb290fac326c754c878a5d8389b2fe7e6c5dbaf22129b2e83673762","size":1410873},{"path":"assets/JsMaterialXCore.DGnKx6GJ.js","hash":"3458954c3137edcdf72a873eb4a92fc8fd947676a10c7dab1d2ef8e501ce5c07","size":65},{"path":"assets/JsMaterialXGenShader.data.DOxudtll.txt","hash":"9eaf101c24d568abe8b21a35cec29df32a9d8ee18986787abcada3ac95ffacc5","size":1512300},{"path":"assets/JsMaterialXGenShader.data.vJnq-SJK.js","hash":"8469e42ae946745a483b829903a8ebe26775c6084a19f85928633c5bbe5cca59","size":65},{"path":"assets/JsMaterialXGenShader.DFP9U7Gu.js","hash":"b3026b30cb3dc86f025d4a21b8befe5b9cbbd2b3d471ecc18c0dd7ed591ce0bb","size":65},{"path":"assets/JsMaterialXGenShader.P2pWAaz9.wasm","hash":"09adab7b142ea4dc768ac40b7947a5eac5c7556a50dbf60281db9c11d555a98e","size":2564784},{"path":"assets/materialx.CnHmG--Y.js","hash":"46e0e6b37df7b132f6340e2bd84211f916c24933868671ba4b5d3a507f8d878c","size":140597},{"path":"assets/materialx.CnHmG--Y.js.gz","hash":"2a3d121d401ee013951712a5ad6a4d7d023201b4ea28625d9d07791999a8a80e","size":42394},{"path":"assets/materialx.eMaybKAQ.js","hash":"d25f196b1725a13ce1bc579a42fe162335314670daf1e24c973b45b7eda95140","size":233},{"path":"assets/materialx.eMaybKAQ.js.gz","hash":"a2c260cb703b423b85ba96b38f3c00a9b1021882ec5feb13338ca1fa3db8db3e","size":168},{"path":"assets/MenuScene.glb","hash":"60a07a5e6277e7976dfa7ae9410badf40ea1f65fa48d2bf00e7a803a95d868f6","size":117564},{"path":"assets/N8AO.C2WXsanG.js","hash":"c53e27ed55ec7273d644739fde121bb6afd4027a9e7f56aa71e16899fb5287c5","size":121},{"path":"assets/N8AO.C2WXsanG.js.gz","hash":"53f936a93e15460b4c340dabe2b8dbd080c6fcf4822c97562763d6e2daf82d16","size":119},{"path":"assets/needle-engine-particles.BlGcRsIu.js","hash":"e3e1643ecc6285745606c069c9c2cd62828ba284c865f6c0040177d3b0b325d2","size":57273},{"path":"assets/needle-engine-particles.BlGcRsIu.js.gz","hash":"9dacc43789df44973b0814e6afef18417c67835d4981e46b5a3aec5ca6a26846","size":15296},{"path":"assets/needle-engine-ui.B94mye74.js","hash":"d84588edbca7932767018fbed4bf8a3aea5a4f46fbedfda26b601800d6d7a06b","size":550663},{"path":"assets/needle-engine-ui.B94mye74.js.gz","hash":"60602b40cf81f5f75528f39ccd1624bee6f6f682951400d028e781fe09dca90b","size":157400},{"path":"assets/needle-engine-webxr.BmCsvEVe.js","hash":"0cbec8d6016f25014c25ba60c64811394595b76cbcd1873f6e1fc3fdc378c3cd","size":341370},{"path":"assets/needle-engine-webxr.BmCsvEVe.js.gz","hash":"26c3cc57b6dc0f02b9d67a81e601dd107dcf9c842508f7352b57716031cee230","size":96292},{"path":"assets/needle-engine.BJP2NBoC.js","hash":"baa3d6660ade76a55ba22625b3d4ce682507fa967afb4b15a7122c840b77ea7d","size":8806},{"path":"assets/needle-engine.BJP2NBoC.js.gz","hash":"042d5bd562bfc7e4d89e76b88e15dd365ee678fc4f22ab846cc6be909a35aeeb","size":2125},{"path":"assets/needle-engine.BTmOVilx.js","hash":"2cd14522767a1244ef979adb0f7d530bc405b78c5cc1c524ea5c17f50553245a","size":18098},{"path":"assets/needle-engine.BTmOVilx.js.gz","hash":"9b37b1cdce5212b5e3dbe86eed9e4e253fdddbb6d7178222a20264001cf8e051","size":7368},{"path":"assets/needle-engine.B_Cg9kXC.js","hash":"f4f40546243a5b4e306829f1767ade0243e03d622e8782ba15ca762a94a482f8","size":83},{"path":"assets/needle-engine.D6xdy26G.js","hash":"493a70cfc7882efd95a49064109d497fa52da8cb21ebead82269844699410225","size":8399},{"path":"assets/needle-engine.D6xdy26G.js.gz","hash":"ae0ffbee3b9df88dfbdadc6c9b7ef7e3a526dd2a9fd1dca7d3e189004eb1feb3","size":2824},{"path":"assets/needle-engine.dep.B3nA4wQ5.js","hash":"903cb1e6d479f412d33f3c6f420da30914ebdc32f2fa5af317ef1b6a83c19d46","size":157},{"path":"assets/needle-engine.dep.B3nA4wQ5.js.gz","hash":"f3ef06e5c09c66b910c15a96d56be694dfc1043699234de32880009cc425418d","size":146},{"path":"assets/needle-engine.dep.BzZBR-mM.js","hash":"446200bd2574e013d8968923320e1092907cfb8f4890ae1a2007ad3a2efb4ec5","size":91},{"path":"assets/needle-engine.dep.DFJFZC0m.js","hash":"fb9b21b29e7bdd4af6e14c3716d27ffc9642107224b17a3cc1cf383c57355f80","size":3621},{"path":"assets/needle-engine.dep.DFJFZC0m.js.gz","hash":"a52e033c1ec67b0da0e40f27167b672650f08af40a738a27ba51a4b8f226276d","size":1473},{"path":"assets/needle-engine.extras.CMyNTAvj.js","hash":"af650a4a9af57ac00839b1a0f8361d974c5733a336d6dc8d12f49a3de92e4338","size":288300},{"path":"assets/needle-engine.extras.CMyNTAvj.js.gz","hash":"83cb34c51e0142595b6c032d4d89235e5a52bc845c9cb7f8e0761eda74e620d1","size":77936},{"path":"assets/needle-engine.extras.D6MD-rCg.js","hash":"779c3d5a607e470b7de0d49b04d82e92b2c82adeb34a237888fe4257cc32816b","size":45412},{"path":"assets/needle-engine.extras.D6MD-rCg.js.gz","hash":"a8c3f17ef43aace6aea9ebad3cc3e3f2a8c86e34cd6ab530c1e7e281fb57bc0f","size":11739},{"path":"assets/needle-engine@5.0.7.js","hash":"acaaceca9c2c8f0ca959a51e9308dc244c17c40594f55af84d4187b85c5dd596","size":104693},{"path":"assets/needle-engine@5.0.7.js.gz","hash":"152919e15118c5de419f5e07796491bddf91587f7de4226e5433186b41974ddc","size":32849},{"path":"assets/peerjs.UdR1_Ath.js","hash":"4d1141e782b5074ff0bc6471d1496892bf4cc3d37bff6b26dbb543ff3215f511","size":139970},{"path":"assets/peerjs.UdR1_Ath.js.gz","hash":"d655e3293942f858436231a5f1c98cacebc4f12b462bbbf7091d91fca6b1804f","size":35531},{"path":"assets/postprocessing.ao.BRYreHJS.js","hash":"9a752a5cdb636bec813221103a7070018fee1303940ad177475bc4f01eb0f3a3","size":162950},{"path":"assets/postprocessing.ao.BRYreHJS.js.gz","hash":"13b3f7db36f6c50bb3de7dffee4ac60670f9267ac4acf052f75b699752dee620","size":77949},{"path":"assets/postprocessing.Wy71DpiM.js","hash":"a13d1598fd4cc60a06e264fef9ed207185963a01b21473cc30dfdcd11307b2b1","size":318819},{"path":"assets/postprocessing.Wy71DpiM.js.gz","hash":"205160a2233b8646a9c1ec6c6f17a414067d811299e3f13f5332a6ce5b66ac61","size":111140},{"path":"assets/rapier3d.DBFmcAzL.js","hash":"dee0e85e47299f58b6af45b9e0d3ec8e87365a2bcfbfe521c7ca7731f721c031","size":2236518},{"path":"assets/rapier3d.DBFmcAzL.js.gz","hash":"756ae1c26b91e37df8728ee0e438208e9138a328daaac20c91d9c9ec0be6a9d1","size":829360},{"path":"assets/register_types.CljvFwvY.js","hash":"4953cbf40e8aba116b8c7bde9913fb3b49668128da4ca0d4be583f94aef05b7f","size":9101},{"path":"assets/register_types.CljvFwvY.js.gz","hash":"ac6b6d6bc932ed8c5d94c86918f0de121cae3487b7ddef23b53ca23b6df7bf41","size":3093},{"path":"assets/rolldown-runtime.CYVBeYYp.js","hash":"c62a259caf983f4ea8940cae8d9abe35924f21613de275b1102b5f9e6243c126","size":685},{"path":"assets/rolldown-runtime.CYVBeYYp.js.gz","hash":"52bb3183225ba42d42738dbbe972d6bd0b05d307269e38b8c3ca2a672979ba57","size":417},{"path":"assets/three-examples.extras@0.169.19.js","hash":"5dc9c34c94bc4ed886e7a06561292a396deacdb33c8c69e92b3ee84d4f88f3e5","size":467},{"path":"assets/three-examples.extras@0.169.19.js.gz","hash":"7ea7b34cc92c9f75c1aa5ef464e818eacaafeb4b9bea3f6e8fb485e487a0b342","size":312},{"path":"assets/three-examples@0.169.19.js","hash":"b0161353f6b95d0ba8b7eae4ccc3a5238dbcdf1eb4627a3faa743aa400496f66","size":341977},{"path":"assets/three-examples@0.169.19.js.gz","hash":"7b60c10406058b2ae13a58244f290e21db2d795302986078082c06cffbbc1520","size":107289},{"path":"assets/three-mesh-bvh.B-oXEGo2.js","hash":"9dff9b6b9018b998059a57cde938ad273062e25a06232f019bbc5a6b0ef4b7c7","size":54824},{"path":"assets/three-mesh-bvh.B-oXEGo2.js.gz","hash":"4c10233f3f98214c2604e628406d76cd9419b3629e42f2b4c356f5b15b15e093","size":17657},{"path":"assets/three-mesh-ui.CgGzZ3ml.js","hash":"1e33cd1d93ac2c9e4cea4d6b35094401cce041850d374f105513a44283b5884d","size":113137},{"path":"assets/three-mesh-ui.CgGzZ3ml.js.gz","hash":"6f016243f03011b37c43bcb2b307876075338430f185ea3c82ecfc67bdcc9e70","size":26682},{"path":"assets/three-quarks.CKtEpqVy.js","hash":"4be7a10535ad22e8333ffa70d687606c5778dd03fddc7298c4f665911bc9c452","size":148379},{"path":"assets/three-quarks.CKtEpqVy.js.gz","hash":"17ba7fe25a94d37f6d7d6d56c8c6cb2c748b4d6c297e67e72a4cbad96316268c","size":34112},{"path":"assets/three@0.169.19.js","hash":"e867c49a8afe22f7d79b960e57640ac4427d50a587a878b0e560cbb5a42d00ef","size":965775},{"path":"assets/three@0.169.19.js.gz","hash":"3ece83a0fb0555973130bc83e5eeecc11c4409975e0aec455441b37f920490de","size":249564},{"path":"include/dishes/cc0_-_pizza_salami.glb","hash":"4210647f4c2ccf10106892dada9946dda3e8ba9fdb99a37a66e8ac96f01d19f8","size":4148396},{"path":"include/poster.webp","hash":"56eb4efebe34b7992a3f71e684a2b4b8269262c5a6577ed26c4801438feb3183","size":17398},{"path":"index.html","hash":"26a331a933e940f623d063aaae19d45ff645245917cd534977c8d656d0eb9c21","size":7510},{"path":"index.html.gz","hash":"b68ae05be63b5f23e4f2c6c14030291ac67df8817c1a88adb111f93b34c1f57d","size":2803},{"path":"needle-app.js","hash":"36a50ec8b690d7dc4674137c5108b2ddafccc32246e52116b8f1c3c0f1a555ee","size":3505}]} \ No newline at end of file diff --git a/Needle/MenuScene/include/dishes/quistibi_scan.glb b/Needle/MenuScene/include/dishes/quistibi_scan.glb new file mode 100644 index 0000000..f69e4ef Binary files /dev/null and b/Needle/MenuScene/include/dishes/quistibi_scan.glb differ diff --git a/Needle/MenuScene/include/poster.webp b/Needle/MenuScene/include/poster.webp index 10d47e3..1295254 100644 --- a/Needle/MenuScene/include/poster.webp +++ b/Needle/MenuScene/include/poster.webp @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:040eaa6fc1886df527c36ba590d5bcb97a956283e8685eb8ec0427c301d9becc -size 10526 +oid sha256:dd759762510a606d637e7b29e98870d9570cda3baba852c902aa9ddf6facacda +size 10474 diff --git a/Needle/MenuScene/package-lock.json b/Needle/MenuScene/package-lock.json index fa6a19c..f0ac312 100644 --- a/Needle/MenuScene/package-lock.json +++ b/Needle/MenuScene/package-lock.json @@ -8,7 +8,7 @@ "name": "my-needle-engine-project", "version": "1.0.0", "dependencies": { - "@needle-tools/engine": "5.0.3", + "@needle-tools/engine": "5.0.7", "three": "npm:@needle-tools/three@0.169.19" }, "devDependencies": { @@ -755,7 +755,7 @@ } }, "node_modules/@needle-tools/engine": { - "version": "5.0.3", + "version": "5.0.7", "dependencies": { "@dimforge/rapier3d-compat": "0.19.3", "@needle-tools/gltf-progressive": "3.4.0-beta.3", diff --git a/Needle/MenuScene/package.json b/Needle/MenuScene/package.json index 8ec2fc9..e3208e3 100644 --- a/Needle/MenuScene/package.json +++ b/Needle/MenuScene/package.json @@ -10,7 +10,7 @@ "build:dev": "vite build" }, "dependencies": { - "@needle-tools/engine": "5.0.3", + "@needle-tools/engine": "5.0.7", "three": "npm:@needle-tools/three@0.169.19" }, "devDependencies": { diff --git a/Needle/MenuScene/src/arIngredientsMenuButton.ts b/Needle/MenuScene/src/arIngredientsMenuButton.ts new file mode 100644 index 0000000..12fc4a0 --- /dev/null +++ b/Needle/MenuScene/src/arIngredientsMenuButton.ts @@ -0,0 +1,5 @@ +/** + * AR ingredients UI is initialized from `ingredientsPopup.ts` (spatial menu + dish tap). + * This file is kept so existing `main.ts` import order stays stable. + */ +import "./ingredientsPopup.js"; diff --git a/Needle/MenuScene/src/assetPicker.ts b/Needle/MenuScene/src/assetPicker.ts index 50daade..d46773e 100644 --- a/Needle/MenuScene/src/assetPicker.ts +++ b/Needle/MenuScene/src/assetPicker.ts @@ -1,4 +1,6 @@ -import { NeedleXRSession, findObjectOfType } from "@needle-tools/engine"; +import { NeedleXRSession } from "@needle-tools/engine"; +import { findMenuController } from "./findMenuController.js"; +import { whenCatalogDishesReady } from "./loadCatalogDishes.js"; import type { MenuController } from "./scripts/MenuController.js"; /** @@ -54,7 +56,7 @@ function initAssetPicker(): void { const bindMenuController = async (): Promise => { try { const ctx = await needle.getContext(); - menuController = findObjectOfType(MenuController, ctx); + menuController = findMenuController(ctx); } catch { menuController = null; } @@ -79,6 +81,9 @@ function initAssetPicker(): void { syncUi(); try { const ctx = await needle.getContext(); + await whenCatalogDishesReady(); + menuController = findMenuController(ctx); + menuController?.prepareForAR(); await NeedleXRSession.start("immersive-ar", undefined, ctx); } catch (err) { console.warn("[assetPicker] Failed to start AR session:", err); @@ -110,6 +115,7 @@ function initAssetPicker(): void { }); needle.addEventListener("loadfinished", () => void bindMenuController()); + needle.addEventListener("menu-dishes-updated", () => void bindMenuController()); whenDomReady(() => { requestAnimationFrame(() => void bindMenuController()); diff --git a/Needle/MenuScene/src/dishMeshFallback.ts b/Needle/MenuScene/src/dishMeshFallback.ts index 8a1fbfd..2002390 100644 --- a/Needle/MenuScene/src/dishMeshFallback.ts +++ b/Needle/MenuScene/src/dishMeshFallback.ts @@ -1,6 +1,6 @@ -import { findObjectOfType } from "@needle-tools/engine"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; import { Object3D } from "three"; +import { findMenuController } from "./findMenuController.js"; import type { MenuController } from "./scripts/MenuController.js"; /** @@ -26,13 +26,16 @@ function resolveIncludeUrl(relative: string): string { return new URL(relative, new URL(normalized, window.location.href)).href; } -async function ensureDishMeshes(menu: MenuController): Promise { +export async function ensureDishMeshes( + menu: MenuController, +): Promise { const loader = new GLTFLoader(); for (const dish of menu.dishes) { if (!dish) continue; const rel = DISH_GLBS[dish.name]; if (!rel) continue; if (countRenderableMeshes(dish) > 0) continue; + if (dish.children.some((child) => child.name === `${dish.name}_mesh`)) continue; const url = resolveIncludeUrl(rel); const gltf = await loader.loadAsync(url); @@ -48,7 +51,7 @@ function initDishMeshFallback(): void { const run = async (): Promise => { try { const ctx = await needle.getContext(); - const menu = findObjectOfType(MenuController, ctx); + const menu = findMenuController(ctx); if (menu) await ensureDishMeshes(menu); } catch (err) { console.warn("[dishMeshFallback] Could not inject dish meshes:", err); @@ -56,6 +59,7 @@ function initDishMeshFallback(): void { }; needle.addEventListener("loadfinished", () => void run()); + needle.addEventListener("menu-dishes-updated", () => void run()); } initDishMeshFallback(); diff --git a/Needle/MenuScene/src/dishPlacement.ts b/Needle/MenuScene/src/dishPlacement.ts new file mode 100644 index 0000000..c290434 --- /dev/null +++ b/Needle/MenuScene/src/dishPlacement.ts @@ -0,0 +1,66 @@ +import { Object3D, Vector3 } from "three"; + +/** + * Shared anchor for every dish under the Dishes object — matches exported pizza in MenuScene.glb. + * Unity local (+0.48022, 0, 0.48978) becomes glTF/Three.js X negated on export. + */ +const SHARED_DISH_POSITION = new Vector3(-0.48022, 0, 0.48978); + +type DishPlacement = { + position?: Vector3; + /** Euler radians (x, y, z) on the dish wrapper. Omit for (0, 0, 0). */ + rotation?: Vector3; + scale?: Vector3; +}; + +/** + * Per-dish scene overrides under Dishes (from MenuScene.unity). + * Sketchfab scans already bake axis conversion in the GLB — wrapper rotation stays at 0. + */ +const DISH_PLACEMENTS: Record = { + quistibi_scan: { + rotation: new Vector3(0, 0, 0), + scale: new Vector3(1.1699198, 1.1699204, 1.1699204), + }, +}; + +/** + * Keep orientation from the GLB file but move scale/offset to the scene wrapper (like Unity import). + */ +function prepareImportedSceneRoot(gltfScene: Object3D): void { + gltfScene.position.set(0, 0, 0); + gltfScene.scale.set(1, 1, 1); + gltfScene.updateMatrix(); +} + +/** + * Wrap a catalog-loaded GLB scene with a dish root that mirrors Unity's prefab instance transform. + */ +export function wrapCatalogDishRoot( + gltfScene: Object3D, + dishName: string, +): Object3D { + const placement = DISH_PLACEMENTS[dishName]; + + prepareImportedSceneRoot(gltfScene); + + const wrapper = new Object3D(); + wrapper.name = dishName; + wrapper.position.copy(placement?.position ?? SHARED_DISH_POSITION); + + const rot = placement?.rotation ?? new Vector3(0, 0, 0); + wrapper.rotation.set(rot.x, rot.y, rot.z, "XYZ"); + + wrapper.scale.copy(placement?.scale ?? new Vector3(1, 1, 1)); + + wrapper.add(gltfScene); + wrapper.updateMatrixWorld(true); + return wrapper; +} + +export function ensureDishVisible(dish: Object3D, active: boolean): void { + dish.visible = active; + dish.traverse((child) => { + child.visible = active; + }); +} diff --git a/Needle/MenuScene/src/enableXR.ts b/Needle/MenuScene/src/enableXR.ts index 5ec2298..d6c4830 100644 --- a/Needle/MenuScene/src/enableXR.ts +++ b/Needle/MenuScene/src/enableXR.ts @@ -10,10 +10,14 @@ onStart((context) => { let webxr = context.scene.getComponentInChildren(WebXR); if (!webxr) { webxr = context.scene.addComponent(WebXR); - webxr.createARButton = true; - webxr.createVRButton = true; } + // Unity-exported WebXR often serializes these off; only the `if (!webxr)` branch used to set them, + // which hid Enter AR / Enter VR / Send to Quest in NeedleMenu for exported scenes. + webxr.createARButton = true; + webxr.createVRButton = true; + webxr.createSendToQuestButton = true; + webxr.autoPlace = true; webxr.autoCenter = true; webxr.arScale = 1; diff --git a/Needle/MenuScene/src/findMenuController.ts b/Needle/MenuScene/src/findMenuController.ts new file mode 100644 index 0000000..f5fcf33 --- /dev/null +++ b/Needle/MenuScene/src/findMenuController.ts @@ -0,0 +1,19 @@ +import { findObjectOfType, TypeStore } from "@needle-tools/engine"; +import type { Object3D } from "three"; +import type { MenuController } from "./scripts/MenuController.js"; + +/** + * Resolve MenuController at runtime via TypeStore (registered in generated/register_types.ts). + * Avoids passing the class constructor from modules that may load before registration. + */ +export function findMenuController( + context: { scene: Object3D }, +): MenuController | null { + const ctor = TypeStore.get("MenuController"); + if (!ctor) return null; + + return ( + findObjectOfType(ctor, context.scene) ?? + findObjectOfType(ctor, context as never) + ); +} diff --git a/Needle/MenuScene/src/generated/gen.js b/Needle/MenuScene/src/generated/gen.js index b6cbb97..447ede2 100644 --- a/Needle/MenuScene/src/generated/gen.js +++ b/Needle/MenuScene/src/generated/gen.js @@ -19,9 +19,9 @@ document.addEventListener("DOMContentLoaded", () => const needleEngine = document.querySelector("needle-engine"); if(needleEngine && needleEngine.getAttribute("src") === null) { - needleEngine.setAttribute("hash", "1776799769658"); + needleEngine.setAttribute("hash", "1782211605003"); needleEngine.setAttribute("src", JSON.stringify(needle_exported_files)); } }); -console.log("Made\ with\ ♥\ by\ 🌵\ Needle\ -\ https://needle\.tools\ —\ Version\ 5\.0\.3"); +console.log("Made\ with\ ♥\ by\ 🌵\ Needle\ -\ https://needle\.tools\ —\ Version\ 5\.0\.7"); diff --git a/Needle/MenuScene/src/generated/meta.json b/Needle/MenuScene/src/generated/meta.json index 3114565..62b7da9 100644 --- a/Needle/MenuScene/src/generated/meta.json +++ b/Needle/MenuScene/src/generated/meta.json @@ -5,7 +5,7 @@ "deployOnly": false, "needleEditor": null, "gzip": true, - "generator": "Unity 6000.4.3f1, Needle Engine Integration @5.0.3", + "generator": "Unity 6000.4.3f1, Needle Engine Integration @5.0.7", "license": { "team": "org_vl8u9dsjhx6b" }, diff --git a/Needle/MenuScene/src/generated/register_types.ts b/Needle/MenuScene/src/generated/register_types.ts index 702b5f3..2d3b9c8 100644 --- a/Needle/MenuScene/src/generated/register_types.ts +++ b/Needle/MenuScene/src/generated/register_types.ts @@ -3,12 +3,14 @@ import { TypeStore } from "@needle-tools/engine" // Import types import { ARObjectController } from "../scripts/ARObjectController.js"; +import { DishIngredientsTap } from "../scripts/DishIngredientsTap.js"; import { MenuController } from "../scripts/MenuController.js"; import { PostProcessingVolumeController } from "../scripts/PostProcessingVolumeController.js"; // Register types export function registerTypes() { TypeStore.add("ARObjectController", ARObjectController); + TypeStore.add("DishIngredientsTap", DishIngredientsTap); TypeStore.add("MenuController", MenuController); TypeStore.add("PostProcessingVolumeController", PostProcessingVolumeController); } diff --git a/Needle/MenuScene/src/ingredientsPopup.ts b/Needle/MenuScene/src/ingredientsPopup.ts new file mode 100644 index 0000000..3094c18 --- /dev/null +++ b/Needle/MenuScene/src/ingredientsPopup.ts @@ -0,0 +1,297 @@ +import { GameObject, onBeforeRender, onStart } from "@needle-tools/engine"; +import type { Context } from "@needle-tools/engine"; +import { + Box3, + CanvasTexture, + DoubleSide, + Mesh, + MeshBasicMaterial, + Object3D, + PlaneGeometry, + Vector3, +} from "three"; +import { findMenuController } from "./findMenuController.js"; +import type { MenuController } from "./scripts/MenuController.js"; +import { DishIngredientsTap } from "./scripts/DishIngredientsTap.js"; + +/** Dish root names → ingredient lines (extend when adding dishes). */ +export const INGREDIENTS_BY_DISH_NAME: Record = { + "cc0_-_pizza_salami": ["Tomato sauce", "Mozzarella", "Salami", "Oregano", "Olive oil"], +}; + +const PLANE_WORLD_WIDTH = 0.28; +const PLANE_OFFSET_Y = 0.08; +const TEX_W = 512; +const TEX_H = 384; + +let arSessionActive = false; +let ingredientsMenuButton: HTMLElement | null = null; + +let markerMesh: Mesh | null = null; +let markerDishName: string | null = null; +let markerDishRoot: Object3D | null = null; + +const _box = new Box3(); +const _anchor = new Vector3(); +const _viewPos = new Vector3(); + +/** World position of the camera the user is viewing through (XR eye in AR, main camera otherwise). */ +function getViewCameraWorldPosition(context: Context, out: Vector3): boolean { + const xrCam = context.xrCamera; + if (context.isInXR && xrCam) { + const eye = xrCam.cameras.length > 0 ? xrCam.cameras[0] : xrCam; + eye.getWorldPosition(out); + return true; + } + + const cam = context.mainCamera; + if (!cam) return false; + cam.getWorldPosition(out); + return true; +} + +export function isArSessionActive(context: Context): boolean { + if (arSessionActive) return true; + const xr = context.xr as { running?: boolean; mode?: string } | undefined; + return xr?.running === true && xr?.mode === "immersive-ar"; +} + +function roundedRect( + c: CanvasRenderingContext2D, + x: number, + y: number, + w: number, + h: number, + r: number, +): void { + const rr = Math.min(r, w / 2, h / 2); + c.moveTo(x + rr, y); + c.arcTo(x + w, y, x + w, y + h, rr); + c.arcTo(x + w, y + h, x, y + h, rr); + c.arcTo(x, y + h, x, y, rr); + c.arcTo(x, y, x + w, y, rr); + c.closePath(); +} + +function drawIngredientsTexture( + dishName: string, + ingredients: string[], +): HTMLCanvasElement { + const canvas = document.createElement("canvas"); + canvas.width = TEX_W; + canvas.height = TEX_H; + const g = canvas.getContext("2d")!; + const pad = 28; + const corner = 20; + const innerW = TEX_W - pad * 2; + const innerH = TEX_H - pad * 2; + + g.fillStyle = "rgba(18, 18, 24, 0.92)"; + g.beginPath(); + roundedRect(g, pad, pad, innerW, innerH, corner); + g.fill(); + g.strokeStyle = "rgba(255, 255, 255, 0.12)"; + g.lineWidth = 2; + g.stroke(); + + const title = dishName.replace(/_/g, " "); + g.fillStyle = "#f2f2f7"; + g.font = "bold 28px system-ui, Segoe UI, sans-serif"; + g.textAlign = "left"; + g.fillText("Ingredients", pad + 18, pad + 48); + + g.font = "600 22px system-ui, Segoe UI, sans-serif"; + g.fillStyle = "rgba(242, 242, 247, 0.92)"; + g.fillText(title, pad + 18, pad + 88); + + g.font = "22px system-ui, Segoe UI, sans-serif"; + g.fillStyle = "rgba(242, 242, 247, 0.88)"; + let y = pad + 130; + + if (ingredients.length > 0) { + for (const line of ingredients) { + g.fillText(`• ${line}`, pad + 26, y); + y += 34; + } + } else { + g.fillStyle = "rgba(242, 242, 247, 0.7)"; + g.fillText("No ingredients listed yet.", pad + 26, y); + } + + return canvas; +} + +function disposeMarker(): void { + if (!markerMesh) return; + markerMesh.removeFromParent(); + markerMesh.geometry.dispose(); + const mat = markerMesh.material as MeshBasicMaterial; + if (mat.map) mat.map.dispose(); + mat.dispose(); + markerMesh = null; + markerDishName = null; + markerDishRoot = null; +} + +function computeAnchorAboveDish(dishRoot: Object3D, liftAboveTop: number): void { + _box.setFromObject(dishRoot); + if (_box.isEmpty()) { + dishRoot.getWorldPosition(_anchor); + _anchor.y += liftAboveTop; + } else { + _anchor.set( + (_box.min.x + _box.max.x) / 2, + _box.max.y + liftAboveTop, + (_box.min.z + _box.max.z) / 2, + ); + } +} + +function updateMarkerInWorld(context: Context): void { + if (!markerMesh || !markerDishRoot) return; + computeAnchorAboveDish(markerDishRoot, PLANE_OFFSET_Y); + markerMesh.position.copy(_anchor); + if (getViewCameraWorldPosition(context, _viewPos)) { + markerMesh.lookAt(_viewPos); + } +} + +function resolveDishRoot(menu: MenuController, dishName: string): Object3D | null { + return menu.dishes.find((d) => d?.name === dishName) ?? null; +} + +export function hideIngredientsPanel(): void { + disposeMarker(); +} + +export function showIngredientsPanel(context: Context, dishName: string): void { + if (!isArSessionActive(context)) return; + + const menu = findMenuController(context); + if (!menu) return; + + const dishRoot = resolveDishRoot(menu, dishName); + if (!dishRoot) return; + + const ingredients = INGREDIENTS_BY_DISH_NAME[dishName] ?? []; + + disposeMarker(); + + const canvas = drawIngredientsTexture(dishName, ingredients); + const map = new CanvasTexture(canvas); + map.needsUpdate = true; + const aspect = TEX_W / TEX_H; + const geo = new PlaneGeometry(PLANE_WORLD_WIDTH, PLANE_WORLD_WIDTH / aspect); + const mat = new MeshBasicMaterial({ + map, + transparent: true, + side: DoubleSide, + depthWrite: false, + }); + + markerMesh = new Mesh(geo, mat); + markerMesh.name = "IngredientsInfoPlane"; + markerMesh.renderOrder = 10; + markerMesh.frustumCulled = false; + context.scene.add(markerMesh); + + markerDishName = dishName; + markerDishRoot = dishRoot; + updateMarkerInWorld(context); +} + +export function toggleIngredientsPanel(context: Context, dishName: string): void { + if (!isArSessionActive(context)) return; + + if (markerMesh && markerDishName === dishName) { + hideIngredientsPanel(); + return; + } + showIngredientsPanel(context, dishName); +} + +function showIngredientsForActiveDish(context: Context): void { + const menu = findMenuController(context); + if (!menu) return; + const dish = menu.dishes[menu.selectedDishIndex]; + if (!dish) return; + toggleIngredientsPanel(context, dish.name); +} + +function removeIngredientsMenuButton(): void { + ingredientsMenuButton?.remove(); + ingredientsMenuButton = null; +} + +function addIngredientsMenuButton(context: Context): void { + if (ingredientsMenuButton) return; + + ingredientsMenuButton = context.menu.appendChild({ + label: "Ingredients", + icon: "restaurant", + title: "Show ingredients above the dish in AR", + priority: 6, + onClick: () => showIngredientsForActiveDish(context), + }) as HTMLElement; +} + +export function attachDishTapHandlers(menu: MenuController): void { + for (const dish of menu.dishes) { + if (!dish) continue; + + if (!GameObject.getComponent(dish, DishIngredientsTap)) { + dish.addComponent(DishIngredientsTap); + } + + dish.traverse((child) => { + const mesh = child as { isMesh?: boolean }; + if (!mesh.isMesh) return; + if (!GameObject.getComponent(child, DishIngredientsTap)) { + child.addComponent(DishIngredientsTap); + } + }); + } +} + +function bindArSessionUi(context: Context): void { + const needle = document.querySelector("needle-engine"); + if (!needle || needle.hasAttribute("data-ingredients-ar-bound")) return; + needle.setAttribute("data-ingredients-ar-bound", "true"); + + needle.addEventListener("enter-ar", () => { + arSessionActive = true; + addIngredientsMenuButton(context); + }); + + needle.addEventListener("exit-ar", () => { + arSessionActive = false; + hideIngredientsPanel(); + removeIngredientsMenuButton(); + }); +} + +function tryAttachTapHandlers(context: Context): void { + const menu = findMenuController(context); + if (menu) attachDishTapHandlers(menu); +} + +function initIngredientsArUi(context: Context): void { + context.menu.showSpatialMenu(true); + bindArSessionUi(context); + + const needle = document.querySelector("needle-engine"); + needle?.addEventListener("loadfinished", () => tryAttachTapHandlers(context)); + needle?.addEventListener("menu-dishes-updated", () => tryAttachTapHandlers(context)); + + tryAttachTapHandlers(context); +} + +onStart((context) => { + initIngredientsArUi(context); +}); + +// After LateUpdate so the WebXR rig + xrCamera reflect the current head pose. +onBeforeRender((context) => { + if (!isArSessionActive(context) || !markerMesh) return; + updateMarkerInWorld(context); +}); diff --git a/Needle/MenuScene/src/loadCatalogDishes.ts b/Needle/MenuScene/src/loadCatalogDishes.ts new file mode 100644 index 0000000..18d609f --- /dev/null +++ b/Needle/MenuScene/src/loadCatalogDishes.ts @@ -0,0 +1,106 @@ +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; +import { Object3D } from "three"; +import { findMenuController } from "./findMenuController.js"; +import { wrapCatalogDishRoot } from "./dishPlacement.js"; +import { ensureDishMeshes } from "./dishMeshFallback.js"; + +/** + * Dish roots expected in the menu but missing from the exported MenuScene.glb. + * Keys must match Unity GameObject names. Re-export from Unity to embed meshes instead. + */ +export const CATALOG_DISH_GLBS: Record = { + quistibi_scan: "include/dishes/quistibi_scan.glb", +}; + +function resolveIncludeUrl(relative: string): string { + const base = import.meta.env.BASE_URL ?? "./"; + const normalized = base.endsWith("/") ? base : `${base}/`; + return new URL(relative, new URL(normalized, window.location.href)).href; +} + +function notifyDishesUpdated(): void { + document.querySelector("needle-engine")?.dispatchEvent( + new CustomEvent("menu-dishes-updated"), + ); +} + +function findExistingDishRoot( + menu: NonNullable>, + dishName: string, +): Object3D | undefined { + const registered = menu.dishes.find((d) => d?.name === dishName); + if (registered) return registered; + return menu.gameObject.children.find((child) => child.name === dishName); +} + +async function loadCatalogDishesForMenu( + menu: NonNullable>, +): Promise { + const loader = new GLTFLoader(); + let added = 0; + + for (const [dishName, relativePath] of Object.entries(CATALOG_DISH_GLBS)) { + if (findExistingDishRoot(menu, dishName)) continue; + + try { + const gltf = await loader.loadAsync(resolveIncludeUrl(relativePath)); + if (findExistingDishRoot(menu, dishName)) continue; + + const root = wrapCatalogDishRoot(gltf.scene, dishName); + menu.gameObject.add(root); + menu.registerDishRoot(root); + added += 1; + console.log(`[loadCatalogDishes] Loaded catalog dish: ${dishName}`); + } catch (err) { + console.warn( + `[loadCatalogDishes] Could not load "${dishName}" from ${relativePath}. ` + + "Copy the GLB to Needle/MenuScene/include/dishes/ or re-export from Unity.", + err, + ); + } + } + + if (added === 0 && menu.getDishSlotCount() <= 1) { + console.warn( + "[loadCatalogDishes] Menu has only one dish slot. In Unity: assign all dishes on MenuController, " + + "keep them active, then re-export MenuScene.glb.", + ); + } + + return added; +} + +let catalogLoadPromise: Promise | null = null; + +function initCatalogDishLoader(): void { + const needle = document.querySelector("needle-engine"); + if (!needle) return; + + const run = (): Promise => { + if (catalogLoadPromise) return catalogLoadPromise; + + catalogLoadPromise = (async () => { + try { + const ctx = await needle.getContext(); + const menu = findMenuController(ctx); + if (!menu) return; + const added = await loadCatalogDishesForMenu(menu); + await ensureDishMeshes(menu); + if (added > 0) notifyDishesUpdated(); + } catch (err) { + console.warn("[loadCatalogDishes] Failed:", err); + } + })(); + + return catalogLoadPromise; + }; + + needle.addEventListener("loadfinished", () => void run()); +} + +initCatalogDishLoader(); + +/** Wait until catalog dish loading has finished (no-op if already done). */ +export function whenCatalogDishesReady(): Promise { + return catalogLoadPromise ?? Promise.resolve(); +} diff --git a/Needle/MenuScene/src/main.ts b/Needle/MenuScene/src/main.ts index 6ee3a3a..43fd438 100644 --- a/Needle/MenuScene/src/main.ts +++ b/Needle/MenuScene/src/main.ts @@ -1,5 +1,9 @@ +import("./generated/register_types.ts"); import("@needle-tools/engine") /* async import of needle engine */; import "./disableCameraControls"; +import "./menuSkybox"; import "./dishMeshFallback"; import "./enableXR"; +import "./ingredientsPopup"; +import "./loadCatalogDishes"; import "./assetPicker"; \ No newline at end of file diff --git a/Needle/MenuScene/src/menuSkybox.ts b/Needle/MenuScene/src/menuSkybox.ts new file mode 100644 index 0000000..6bbb5ac --- /dev/null +++ b/Needle/MenuScene/src/menuSkybox.ts @@ -0,0 +1,28 @@ +import { loadPMREM, onStart, type Context } from "@needle-tools/engine"; + +const NEEDLE_HDRI_BASE = "https://cdn.needle.tools/static/hdris"; + +/** + * Single outdoor sky HDRI for the whole menu (FastHDR .ktx2 on Needle CDN). + * "Overcast Soil Puresky" — soft overcast sky, neutral lighting for all dishes. + * @see https://cloud.needle.tools/hdris + */ +export const MENU_SKYBOX_URL = `${NEEDLE_HDRI_BASE}/overcast_soil_puresky_2k.pmrem.ktx2`; + +let applied = false; + +/** Apply shared sky background + image-based lighting once for the menu scene. */ +export async function applyMenuSkybox(context: Context): Promise { + if (applied) return; + + const tex = await loadPMREM(MENU_SKYBOX_URL, context.renderer); + if (!tex) return; + + applied = true; + context.scene.background = tex; + context.scene.environment = tex; +} + +onStart((context) => { + void applyMenuSkybox(context); +}); diff --git a/Needle/MenuScene/src/scripts/DishIngredientsTap.ts b/Needle/MenuScene/src/scripts/DishIngredientsTap.ts new file mode 100644 index 0000000..84a9555 --- /dev/null +++ b/Needle/MenuScene/src/scripts/DishIngredientsTap.ts @@ -0,0 +1,34 @@ +import { Behaviour } from "@needle-tools/engine"; +import type { PointerEventData } from "@needle-tools/engine"; +import { findMenuController } from "../findMenuController.js"; +import { isArSessionActive, toggleIngredientsPanel } from "../ingredientsPopup.js"; +import type { Object3D } from "three"; + +/** + * On mesh tap in AR, open the ingredients panel for the parent dish root. + */ +export class DishIngredientsTap extends Behaviour { + onPointerClick(_args: PointerEventData): void { + if (!isArSessionActive(this.context)) return; + + const dishRoot = this.resolveDishRoot(); + if (!dishRoot) return; + + toggleIngredientsPanel(this.context, dishRoot.name); + } + + private resolveDishRoot(): Object3D | null { + const menu = findMenuController(this.context); + if (menu) { + for (const dish of menu.dishes) { + if (!dish) continue; + let node: Object3D | null = this.gameObject; + while (node) { + if (node === dish) return dish; + node = node.parent; + } + } + } + return this.gameObject; + } +} diff --git a/Needle/MenuScene/src/scripts/MenuController.ts b/Needle/MenuScene/src/scripts/MenuController.ts index 061dd1b..ea3d859 100644 --- a/Needle/MenuScene/src/scripts/MenuController.ts +++ b/Needle/MenuScene/src/scripts/MenuController.ts @@ -2,11 +2,14 @@ import { Behaviour, DeviceUtilities, GameObject, + findObjectOfType, + Renderer, serializable, USDZExporter, type NeedleXREventArgs, } from "@needle-tools/engine"; import { Object3D } from "three"; +import { ensureDishVisible } from "../dishPlacement.js"; const dishBaseY = new WeakMap(); @@ -48,7 +51,13 @@ export class MenuController extends Behaviour { const params = new URLSearchParams(window.location.search); this.dishName = params.get("dishName") ?? ""; - if (this.webXROrigin) this.usdzExporter = this.webXROrigin.getComponent(USDZExporter) ?? undefined; + if (this.webXROrigin) { + this.usdzExporter = this.webXROrigin.getComponent(USDZExporter) ?? undefined; + } + if (!this.usdzExporter) { + this.usdzExporter = + findObjectOfType(USDZExporter, this.context) ?? undefined; + } if (this.dishName) { let matched = false; @@ -66,6 +75,7 @@ export class MenuController extends Behaviour { this.restoreDishBaseY(dish); } GameObject.setActive(dish, on); + ensureDishVisible(dish, on); }); if (!matched) { this.ensureOnlySelectedDishVisible(); @@ -94,6 +104,7 @@ export class MenuController extends Behaviour { if (args.xr.mode === "immersive-ar") { this.arSessionBobPaused = true; this.snapActiveDishToBaseY(); + this.prepareForAR(); } } @@ -243,6 +254,22 @@ export class MenuController extends Behaviour { document.addEventListener("touchend", onTouchEnd, { passive: false }); } + private setDishActive(dish: Object3D | null | undefined, active: boolean): void { + if (!dish) return; + GameObject.setActive(dish, active); + ensureDishVisible(dish, active); + dish.traverse((child) => { + const renderer = GameObject.getComponent(child, Renderer); + if (renderer) renderer.enabled = active; + }); + } + + /** Call before / during AR so only the selected dish is exported and rendered. */ + prepareForAR(): void { + this.ensureOnlySelectedDishVisible(); + this.updateUSDZExporterTarget(); + } + private getValidDishIndices(): number[] { return this.dishes.map((dish, index) => (dish != null ? index : -1)).filter((index) => index >= 0); } @@ -264,7 +291,7 @@ export class MenuController extends Behaviour { if (!active) { this.restoreDishBaseY(d); } - GameObject.setActive(this.dishes[i], active); + this.setDishActive(this.dishes[i], active); }); } @@ -285,6 +312,15 @@ export class MenuController extends Behaviour { return this.getValidDishIndices().length; } + /** Register a dish root loaded at runtime (e.g. catalog GLB) and refresh visibility. */ + registerDishRoot(dish: Object3D): void { + const already = this.dishes.some((d) => d === dish || d?.name === dish.name); + if (already) return; + this.dishes.push(dish); + this.ensureOnlySelectedDishVisible(); + this.updateUSDZExporterTarget(); + } + /** Label for the asset picker (object name from Unity when set, else Dish i / n). */ getPickerLabel(): string { const valid = this.getValidDishIndices(); @@ -306,12 +342,12 @@ export class MenuController extends Behaviour { if (pos < 0) pos = 0; this.restoreDishBaseY(this.dishes[valid[pos]]); - GameObject.setActive(this.dishes[valid[pos]], false); + this.setDishActive(this.dishes[valid[pos]], false); pos = (pos - 1 + valid.length) % valid.length; this.selectedDishIndex = valid[pos]; - GameObject.setActive(this.dishes[this.selectedDishIndex], true); + this.setDishActive(this.dishes[this.selectedDishIndex], true); - this.updateUSDZExporterTarget(); + this.prepareForAR(); } selectNextDish(): void { @@ -322,17 +358,23 @@ export class MenuController extends Behaviour { if (pos < 0) pos = 0; this.restoreDishBaseY(this.dishes[valid[pos]]); - GameObject.setActive(this.dishes[valid[pos]], false); + this.setDishActive(this.dishes[valid[pos]], false); pos = (pos + 1) % valid.length; this.selectedDishIndex = valid[pos]; - GameObject.setActive(this.dishes[this.selectedDishIndex], true); + this.setDishActive(this.dishes[this.selectedDishIndex], true); - this.updateUSDZExporterTarget(); + this.prepareForAR(); } private updateUSDZExporterTarget(): void { const dish = this.dishes[this.selectedDishIndex]; - if (this.usdzExporter && dish) { + if (!dish) return; + + if (!this.usdzExporter) { + this.usdzExporter = + findObjectOfType(USDZExporter, this.context) ?? undefined; + } + if (this.usdzExporter) { this.usdzExporter.objectToExport = dish; } } diff --git a/Needle/SampleScene/src/enableXR.ts b/Needle/SampleScene/src/enableXR.ts index 5ec2298..d6c4830 100644 --- a/Needle/SampleScene/src/enableXR.ts +++ b/Needle/SampleScene/src/enableXR.ts @@ -10,10 +10,14 @@ onStart((context) => { let webxr = context.scene.getComponentInChildren(WebXR); if (!webxr) { webxr = context.scene.addComponent(WebXR); - webxr.createARButton = true; - webxr.createVRButton = true; } + // Unity-exported WebXR often serializes these off; only the `if (!webxr)` branch used to set them, + // which hid Enter AR / Enter VR / Send to Quest in NeedleMenu for exported scenes. + webxr.createARButton = true; + webxr.createVRButton = true; + webxr.createSendToQuestButton = true; + webxr.autoPlace = true; webxr.autoCenter = true; webxr.arScale = 1; diff --git a/Packages/manifest.json b/Packages/manifest.json index 103bd9e..f892f31 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,5 +1,6 @@ { "dependencies": { + "com.needle.engine-exporter": "5.0.7", "com.unity.ai.navigation": "2.0.12", "com.unity.collab-proxy": "2.12.4", "com.unity.ide.rider": "3.0.39", @@ -11,6 +12,7 @@ "com.unity.timeline": "1.8.12", "com.unity.ugui": "2.0.0", "com.unity.visualscripting": "1.9.11", + "org.khronos.unitygltf": "2.19.5", "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.adaptiveperformance": "1.0.0", "com.unity.modules.ai": "1.0.0", @@ -44,8 +46,7 @@ "com.unity.modules.video": "1.0.0", "com.unity.modules.vr": "1.0.0", "com.unity.modules.wind": "1.0.0", - "com.unity.modules.xr": "1.0.0", - "com.needle.engine-exporter": "5.0.3" + "com.unity.modules.xr": "1.0.0" }, "scopedRegistries": [ { @@ -57,4 +58,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 5461755..5c487f8 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -1,11 +1,11 @@ { "dependencies": { "com.needle.engine-exporter": { - "version": "5.0.3", + "version": "5.0.7", "depth": 0, "source": "registry", "dependencies": { - "org.khronos.unitygltf": "2.19.2", + "org.khronos.unitygltf": "2.19.5", "com.unity.modules.animation": "1.0.0", "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.modules.uielements": "1.0.0", @@ -69,7 +69,7 @@ }, "com.unity.collections": { "version": "6.4.0", - "depth": 2, + "depth": 1, "source": "builtin", "dependencies": { "com.unity.burst": "1.8.23", @@ -114,7 +114,7 @@ }, "com.unity.mathematics": { "version": "1.3.3", - "depth": 2, + "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" @@ -139,7 +139,7 @@ }, "com.unity.nuget.mono-cecil": { "version": "1.11.6", - "depth": 3, + "depth": 2, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" @@ -217,7 +217,7 @@ }, "com.unity.test-framework.performance": { "version": "3.4.0", - "depth": 3, + "depth": 2, "source": "registry", "dependencies": { "com.unity.test-framework": "1.1.33", @@ -257,8 +257,8 @@ "url": "https://packages.unity.com" }, "org.khronos.unitygltf": { - "version": "2.19.2", - "depth": 1, + "version": "2.19.5", + "depth": 0, "source": "registry", "dependencies": { "com.unity.nuget.newtonsoft-json": "2.0.0",