Compare commits
2 Commits
ac0aaef0e4
...
add-deploy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
beb8261554 | ||
|
|
d632c583d4 |
@@ -53,6 +53,7 @@
|
|||||||
<Compile Include="Assets\TutorialInfo\Scripts\Readme.cs" />
|
<Compile Include="Assets\TutorialInfo\Scripts\Readme.cs" />
|
||||||
<Compile Include="Assets\Needle\Components.codegen\PostProcessingVolumeController.cs" />
|
<Compile Include="Assets\Needle\Components.codegen\PostProcessingVolumeController.cs" />
|
||||||
<Compile Include="Assets\Needle\Components.codegen\ARObjectController.cs" />
|
<Compile Include="Assets\Needle\Components.codegen\ARObjectController.cs" />
|
||||||
|
<Compile Include="Assets\Needle\Components.codegen\DishIngredientsTap.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="UnityEngine">
|
<Reference Include="UnityEngine">
|
||||||
|
|||||||
14
Assets/Needle/Components.codegen/DishIngredientsTap.cs
Normal file
14
Assets/Needle/Components.codegen/DishIngredientsTap.cs
Normal file
@@ -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
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 53682507d1bf626e741afef604c0b9f0
|
||||||
@@ -7,16 +7,12 @@ namespace Needle.Typescript.GeneratedComponents
|
|||||||
{
|
{
|
||||||
public partial class MenuController : UnityEngine.MonoBehaviour
|
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[] @dishes;
|
||||||
public UnityEngine.Transform @webXROrigin;
|
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;
|
public float @dishBobAmplitude = 0.004f;
|
||||||
[UnityEngine.Tooltip("Bob angular speed (radians per second).")]
|
[UnityEngine.Tooltip("Bob angular speed (radians per second).")]
|
||||||
public float @dishBobSpeed = 1f;
|
public float @dishBobSpeed = 1f;
|
||||||
public float @selectedDishIndex = 0f;
|
|
||||||
public void OnEnable() {}
|
public void OnEnable() {}
|
||||||
public void onEnterXR(object @args) {}
|
public void onEnterXR(object @args) {}
|
||||||
public void onLeaveXR(object @_args) {}
|
public void onLeaveXR(object @_args) {}
|
||||||
@@ -26,7 +22,9 @@ namespace Needle.Typescript.GeneratedComponents
|
|||||||
public void setupMobileControls() {}
|
public void setupMobileControls() {}
|
||||||
public void setupDesktopControls() {}
|
public void setupDesktopControls() {}
|
||||||
public void createMenuMobileControls() {}
|
public void createMenuMobileControls() {}
|
||||||
|
public void prepareForAR() {}
|
||||||
public float getDishSlotCount() { return default; }
|
public float getDishSlotCount() { return default; }
|
||||||
|
public void registerDishRoot(UnityEngine.Transform @dish) {}
|
||||||
public string getPickerLabel() { return default; }
|
public string getPickerLabel() { return default; }
|
||||||
public void selectPreviousDish() {}
|
public void selectPreviousDish() {}
|
||||||
public void selectNextDish() {}
|
public void selectNextDish() {}
|
||||||
|
|||||||
BIN
Assets/Resources/Dishes/quistibi_scan.glb
LFS
Normal file
BIN
Assets/Resources/Dishes/quistibi_scan.glb
LFS
Normal file
Binary file not shown.
73
Assets/Resources/Dishes/quistibi_scan.glb.meta
Normal file
73
Assets/Resources/Dishes/quistibi_scan.glb.meta
Normal file
@@ -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: '<b>Generator: </b>Sketchfab-16.95.0
|
||||||
|
|
||||||
|
<b>Version: </b>2.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<b>Extras:
|
||||||
|
</b>
|
||||||
|
|
||||||
|
'
|
||||||
|
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: []
|
||||||
@@ -571,6 +571,84 @@ Transform:
|
|||||||
m_CorrespondingSourceObject: {fileID: 8897093224900880590, guid: 82b3eac92005fee47a7a02b0e77a8231, type: 3}
|
m_CorrespondingSourceObject: {fileID: 8897093224900880590, guid: 82b3eac92005fee47a7a02b0e77a8231, type: 3}
|
||||||
m_PrefabInstance: {fileID: 1684256003}
|
m_PrefabInstance: {fileID: 1684256003}
|
||||||
m_PrefabAsset: {fileID: 0}
|
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
|
--- !u!1 &1792418155
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -602,6 +680,7 @@ Transform:
|
|||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children:
|
m_Children:
|
||||||
- {fileID: 1684256004}
|
- {fileID: 1684256004}
|
||||||
|
- {fileID: 1784117312}
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!114 &1792418157
|
--- !u!114 &1792418157
|
||||||
@@ -621,7 +700,10 @@ MonoBehaviour:
|
|||||||
isXR: 0
|
isXR: 0
|
||||||
dishes:
|
dishes:
|
||||||
- {fileID: 1684256004}
|
- {fileID: 1684256004}
|
||||||
|
- {fileID: 1784117312}
|
||||||
webXROrigin: {fileID: 0}
|
webXROrigin: {fileID: 0}
|
||||||
|
dishBobAmplitude: 0.004
|
||||||
|
dishBobSpeed: 1
|
||||||
selectedDishIndex: 0
|
selectedDishIndex: 0
|
||||||
--- !u!1660057539 &9223372036854775807
|
--- !u!1660057539 &9223372036854775807
|
||||||
SceneRoots:
|
SceneRoots:
|
||||||
|
|||||||
Binary file not shown.
BIN
Needle/MenuScene/dist/assets/MenuScene.glb
vendored
BIN
Needle/MenuScene/dist/assets/MenuScene.glb
vendored
Binary file not shown.
BIN
Needle/MenuScene/dist/include/dishes/quistibi_scan.glb
vendored
Normal file
BIN
Needle/MenuScene/dist/include/dishes/quistibi_scan.glb
vendored
Normal file
Binary file not shown.
BIN
Needle/MenuScene/dist/include/poster.webp
vendored
BIN
Needle/MenuScene/dist/include/poster.webp
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 40 KiB |
29
Needle/MenuScene/dist/index.html
vendored
29
Needle/MenuScene/dist/index.html
vendored
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<link rel="modulepreload" as="script" href="./assets/three@0.169.19.js">
|
<link rel="modulepreload" as="script" href="./assets/three@0.169.19.js">
|
||||||
<link rel="modulepreload" as="script" href="./assets/three-examples@0.169.19.js">
|
<link rel="modulepreload" as="script" href="./assets/three-examples@0.169.19.js">
|
||||||
<link rel="modulepreload" as="script" href="./assets/needle-engine@5.0.3.js">
|
<link rel="modulepreload" as="script" href="./assets/needle-engine@5.0.7.js">
|
||||||
|
|
||||||
<link rel="preload" as="fetch" fetchpriority="low" href="./assets/MenuScene.glb" type="model/gltf+json" crossorigin>
|
<link rel="preload" as="fetch" fetchpriority="low" href="./assets/MenuScene.glb" type="model/gltf+json" crossorigin>
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||||
@@ -20,9 +20,9 @@
|
|||||||
<meta name="og:image:width" content="1080">
|
<meta name="og:image:width" content="1080">
|
||||||
<meta name="og:image:height" content="1080">
|
<meta name="og:image:height" content="1080">
|
||||||
<meta name="og:title" content="Menu Scene">
|
<meta name="og:title" content="Menu Scene">
|
||||||
<meta name="generator" content="Unity 6000.4.3f1, Needle Engine Integration @5.0.3">
|
<meta name="generator" content="Unity 6000.4.3f1, Needle Engine Integration @5.0.7">
|
||||||
<meta name="needle-engine" content="5.0.3">
|
<meta name="needle-engine" content="5.0.7">
|
||||||
<meta name="needle:buildtime" content="2026-04-19T21:14:58.726Z">
|
<meta name="needle:buildtime" content="2026-06-23T10:34:28.095Z">
|
||||||
|
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="./assets/favicon.DtN_Fmav.ico">
|
<link rel="icon" href="./assets/favicon.DtN_Fmav.ico">
|
||||||
@@ -37,36 +37,35 @@
|
|||||||
<meta name="robots" content="index,follow">
|
<meta name="robots" content="index,follow">
|
||||||
<meta name="url" content="https://localhost:3000">
|
<meta name="url" content="https://localhost:3000">
|
||||||
|
|
||||||
<script type="module" crossorigin src="./assets/index-dZODtGdh.js"></script>
|
<script type="module" crossorigin src="./assets/index-CCjWXS7N.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="./assets/rolldown-runtime.CYVBeYYp.js">
|
<link rel="modulepreload" crossorigin href="./assets/rolldown-runtime.CYVBeYYp.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/three@0.169.19.js">
|
<link rel="modulepreload" crossorigin href="./assets/three@0.169.19.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/materialx.CnHmG--Y.js">
|
<link rel="modulepreload" crossorigin href="./assets/materialx.CnHmG--Y.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/three-examples@0.169.19.js">
|
<link rel="modulepreload" crossorigin href="./assets/three-examples@0.169.19.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/gltf-progressive.BBJGcb3s.js">
|
<link rel="modulepreload" crossorigin href="./assets/gltf-progressive.BBJGcb3s.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/three-mesh-ui.CgGzZ3ml.js">
|
<link rel="modulepreload" crossorigin href="./assets/three-mesh-ui.CgGzZ3ml.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/needle-engine-ui.D3ZoIJG5.js">
|
<link rel="modulepreload" crossorigin href="./assets/needle-engine-ui.B94mye74.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/needle-engine-webxr.DiiWSXeh.js">
|
<link rel="modulepreload" crossorigin href="./assets/needle-engine-webxr.BmCsvEVe.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/three-quarks.DALdLWPD.js">
|
<link rel="modulepreload" crossorigin href="./assets/three-quarks.CKtEpqVy.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/needle-engine-particles.Ce6eiXqY.js">
|
<link rel="modulepreload" crossorigin href="./assets/needle-engine-particles.BlGcRsIu.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/postprocessing.Wy71DpiM.js">
|
<link rel="modulepreload" crossorigin href="./assets/postprocessing.Wy71DpiM.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/needle-engine.extras.dI9yZHT7.js">
|
<link rel="modulepreload" crossorigin href="./assets/needle-engine.extras.D6MD-rCg.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/needle-engine.extras.B9pwA3ET.js">
|
<link rel="modulepreload" crossorigin href="./assets/needle-engine.extras.CMyNTAvj.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/three-mesh-bvh.B-oXEGo2.js">
|
<link rel="modulepreload" crossorigin href="./assets/three-mesh-bvh.B-oXEGo2.js">
|
||||||
<link rel="modulepreload" crossorigin href="./assets/needle-engine@5.0.3.js">
|
<link rel="modulepreload" crossorigin href="./assets/needle-engine@5.0.7.js">
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index.2XwOwFJz.css">
|
<link rel="stylesheet" crossorigin href="./assets/index.a_pIm1gH.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<script>/* needle: fix for peerjs */ window.global = window; var parcelRequire;</script>
|
<script>/* needle: fix for peerjs */ window.global = window; var parcelRequire;</script>
|
||||||
|
|
||||||
<needle-engine>
|
<needle-engine camera-controls="false">
|
||||||
<div id="asset-picker" class="ar desktop">
|
<div id="asset-picker" class="ar desktop">
|
||||||
<div class="asset-picker__inner">
|
<div class="asset-picker__inner">
|
||||||
<button type="button" id="asset-picker-prev" aria-label="Previous model">Previous</button>
|
<button type="button" id="asset-picker-prev" aria-label="Previous model">Previous</button>
|
||||||
<span id="asset-picker-label" class="asset-picker__label"></span>
|
<span id="asset-picker-label" class="asset-picker__label"></span>
|
||||||
<span id="asset-picker-index" class="asset-picker__index" aria-live="polite"></span>
|
<span id="asset-picker-index" class="asset-picker__index" aria-live="polite"></span>
|
||||||
<button type="button" id="asset-picker-next" aria-label="Next model">Next</button>
|
<button type="button" id="asset-picker-next" aria-label="Next model">Next</button>
|
||||||
<button type="button" id="asset-picker-ar" aria-label="Start augmented reality">View in AR</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</needle-engine>
|
</needle-engine>
|
||||||
|
|||||||
BIN
Needle/MenuScene/dist/index.html.gz
vendored
BIN
Needle/MenuScene/dist/index.html.gz
vendored
Binary file not shown.
2
Needle/MenuScene/dist/needle-app.js
vendored
2
Needle/MenuScene/dist/needle-app.js
vendored
@@ -53,7 +53,7 @@ if (!customElements.get(componentName)) {
|
|||||||
while(this.basePath.endsWith('/')) {
|
while(this.basePath.endsWith('/')) {
|
||||||
this.basePath = this.basePath.slice(0, -1);
|
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.shadowRoot.appendChild(script);
|
||||||
|
|
||||||
this.needleEngine = document.createElement('needle-engine');
|
this.needleEngine = document.createElement('needle-engine');
|
||||||
|
|||||||
2
Needle/MenuScene/dist/needle.buildinfo.json
vendored
2
Needle/MenuScene/dist/needle.buildinfo.json
vendored
File diff suppressed because one or more lines are too long
BIN
Needle/MenuScene/include/dishes/quistibi_scan.glb
Normal file
BIN
Needle/MenuScene/include/dishes/quistibi_scan.glb
Normal file
Binary file not shown.
Binary file not shown.
4
Needle/MenuScene/package-lock.json
generated
4
Needle/MenuScene/package-lock.json
generated
@@ -8,7 +8,7 @@
|
|||||||
"name": "my-needle-engine-project",
|
"name": "my-needle-engine-project",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@needle-tools/engine": "5.0.3",
|
"@needle-tools/engine": "5.0.7",
|
||||||
"three": "npm:@needle-tools/three@0.169.19"
|
"three": "npm:@needle-tools/three@0.169.19"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -755,7 +755,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@needle-tools/engine": {
|
"node_modules/@needle-tools/engine": {
|
||||||
"version": "5.0.3",
|
"version": "5.0.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dimforge/rapier3d-compat": "0.19.3",
|
"@dimforge/rapier3d-compat": "0.19.3",
|
||||||
"@needle-tools/gltf-progressive": "3.4.0-beta.3",
|
"@needle-tools/gltf-progressive": "3.4.0-beta.3",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"build:dev": "vite build"
|
"build:dev": "vite build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@needle-tools/engine": "5.0.3",
|
"@needle-tools/engine": "5.0.7",
|
||||||
"three": "npm:@needle-tools/three@0.169.19"
|
"three": "npm:@needle-tools/three@0.169.19"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
5
Needle/MenuScene/src/arIngredientsMenuButton.ts
Normal file
5
Needle/MenuScene/src/arIngredientsMenuButton.ts
Normal file
@@ -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";
|
||||||
@@ -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";
|
import type { MenuController } from "./scripts/MenuController.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,7 +56,7 @@ function initAssetPicker(): void {
|
|||||||
const bindMenuController = async (): Promise<void> => {
|
const bindMenuController = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const ctx = await needle.getContext();
|
const ctx = await needle.getContext();
|
||||||
menuController = findObjectOfType(MenuController, ctx);
|
menuController = findMenuController(ctx);
|
||||||
} catch {
|
} catch {
|
||||||
menuController = null;
|
menuController = null;
|
||||||
}
|
}
|
||||||
@@ -79,6 +81,9 @@ function initAssetPicker(): void {
|
|||||||
syncUi();
|
syncUi();
|
||||||
try {
|
try {
|
||||||
const ctx = await needle.getContext();
|
const ctx = await needle.getContext();
|
||||||
|
await whenCatalogDishesReady();
|
||||||
|
menuController = findMenuController(ctx);
|
||||||
|
menuController?.prepareForAR();
|
||||||
await NeedleXRSession.start("immersive-ar", undefined, ctx);
|
await NeedleXRSession.start("immersive-ar", undefined, ctx);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("[assetPicker] Failed to start AR session:", err);
|
console.warn("[assetPicker] Failed to start AR session:", err);
|
||||||
@@ -110,6 +115,7 @@ function initAssetPicker(): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
needle.addEventListener("loadfinished", () => void bindMenuController());
|
needle.addEventListener("loadfinished", () => void bindMenuController());
|
||||||
|
needle.addEventListener("menu-dishes-updated", () => void bindMenuController());
|
||||||
|
|
||||||
whenDomReady(() => {
|
whenDomReady(() => {
|
||||||
requestAnimationFrame(() => void bindMenuController());
|
requestAnimationFrame(() => void bindMenuController());
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { findObjectOfType } from "@needle-tools/engine";
|
|
||||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
||||||
import { Object3D } from "three";
|
import { Object3D } from "three";
|
||||||
|
import { findMenuController } from "./findMenuController.js";
|
||||||
import type { MenuController } from "./scripts/MenuController.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;
|
return new URL(relative, new URL(normalized, window.location.href)).href;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureDishMeshes(menu: MenuController): Promise<void> {
|
export async function ensureDishMeshes(
|
||||||
|
menu: MenuController,
|
||||||
|
): Promise<void> {
|
||||||
const loader = new GLTFLoader();
|
const loader = new GLTFLoader();
|
||||||
for (const dish of menu.dishes) {
|
for (const dish of menu.dishes) {
|
||||||
if (!dish) continue;
|
if (!dish) continue;
|
||||||
const rel = DISH_GLBS[dish.name];
|
const rel = DISH_GLBS[dish.name];
|
||||||
if (!rel) continue;
|
if (!rel) continue;
|
||||||
if (countRenderableMeshes(dish) > 0) continue;
|
if (countRenderableMeshes(dish) > 0) continue;
|
||||||
|
if (dish.children.some((child) => child.name === `${dish.name}_mesh`)) continue;
|
||||||
|
|
||||||
const url = resolveIncludeUrl(rel);
|
const url = resolveIncludeUrl(rel);
|
||||||
const gltf = await loader.loadAsync(url);
|
const gltf = await loader.loadAsync(url);
|
||||||
@@ -48,7 +51,7 @@ function initDishMeshFallback(): void {
|
|||||||
const run = async (): Promise<void> => {
|
const run = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const ctx = await needle.getContext();
|
const ctx = await needle.getContext();
|
||||||
const menu = findObjectOfType(MenuController, ctx);
|
const menu = findMenuController(ctx);
|
||||||
if (menu) await ensureDishMeshes(menu);
|
if (menu) await ensureDishMeshes(menu);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("[dishMeshFallback] Could not inject dish meshes:", err);
|
console.warn("[dishMeshFallback] Could not inject dish meshes:", err);
|
||||||
@@ -56,6 +59,7 @@ function initDishMeshFallback(): void {
|
|||||||
};
|
};
|
||||||
|
|
||||||
needle.addEventListener("loadfinished", () => void run());
|
needle.addEventListener("loadfinished", () => void run());
|
||||||
|
needle.addEventListener("menu-dishes-updated", () => void run());
|
||||||
}
|
}
|
||||||
|
|
||||||
initDishMeshFallback();
|
initDishMeshFallback();
|
||||||
|
|||||||
66
Needle/MenuScene/src/dishPlacement.ts
Normal file
66
Needle/MenuScene/src/dishPlacement.ts
Normal file
@@ -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<string, DishPlacement> = {
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -10,9 +10,13 @@ onStart((context) => {
|
|||||||
let webxr = context.scene.getComponentInChildren(WebXR);
|
let webxr = context.scene.getComponentInChildren(WebXR);
|
||||||
if (!webxr) {
|
if (!webxr) {
|
||||||
webxr = context.scene.addComponent(WebXR);
|
webxr = context.scene.addComponent(WebXR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.createARButton = true;
|
||||||
webxr.createVRButton = true;
|
webxr.createVRButton = true;
|
||||||
}
|
webxr.createSendToQuestButton = true;
|
||||||
|
|
||||||
webxr.autoPlace = true;
|
webxr.autoPlace = true;
|
||||||
webxr.autoCenter = true;
|
webxr.autoCenter = true;
|
||||||
|
|||||||
19
Needle/MenuScene/src/findMenuController.ts
Normal file
19
Needle/MenuScene/src/findMenuController.ts
Normal file
@@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -19,9 +19,9 @@ document.addEventListener("DOMContentLoaded", () =>
|
|||||||
const needleEngine = document.querySelector("needle-engine");
|
const needleEngine = document.querySelector("needle-engine");
|
||||||
if(needleEngine && needleEngine.getAttribute("src") === null)
|
if(needleEngine && needleEngine.getAttribute("src") === null)
|
||||||
{
|
{
|
||||||
needleEngine.setAttribute("hash", "1776799769658");
|
needleEngine.setAttribute("hash", "1782211605003");
|
||||||
needleEngine.setAttribute("src", JSON.stringify(needle_exported_files));
|
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");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"deployOnly": false,
|
"deployOnly": false,
|
||||||
"needleEditor": null,
|
"needleEditor": null,
|
||||||
"gzip": true,
|
"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": {
|
"license": {
|
||||||
"team": "org_vl8u9dsjhx6b"
|
"team": "org_vl8u9dsjhx6b"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { TypeStore } from "@needle-tools/engine"
|
|||||||
|
|
||||||
// Import types
|
// Import types
|
||||||
import { ARObjectController } from "../scripts/ARObjectController.js";
|
import { ARObjectController } from "../scripts/ARObjectController.js";
|
||||||
|
import { DishIngredientsTap } from "../scripts/DishIngredientsTap.js";
|
||||||
import { MenuController } from "../scripts/MenuController.js";
|
import { MenuController } from "../scripts/MenuController.js";
|
||||||
import { PostProcessingVolumeController } from "../scripts/PostProcessingVolumeController.js";
|
import { PostProcessingVolumeController } from "../scripts/PostProcessingVolumeController.js";
|
||||||
|
|
||||||
// Register types
|
// Register types
|
||||||
export function registerTypes() {
|
export function registerTypes() {
|
||||||
TypeStore.add("ARObjectController", ARObjectController);
|
TypeStore.add("ARObjectController", ARObjectController);
|
||||||
|
TypeStore.add("DishIngredientsTap", DishIngredientsTap);
|
||||||
TypeStore.add("MenuController", MenuController);
|
TypeStore.add("MenuController", MenuController);
|
||||||
TypeStore.add("PostProcessingVolumeController", PostProcessingVolumeController);
|
TypeStore.add("PostProcessingVolumeController", PostProcessingVolumeController);
|
||||||
}
|
}
|
||||||
|
|||||||
297
Needle/MenuScene/src/ingredientsPopup.ts
Normal file
297
Needle/MenuScene/src/ingredientsPopup.ts
Normal file
@@ -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<string, string[]> = {
|
||||||
|
"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);
|
||||||
|
});
|
||||||
106
Needle/MenuScene/src/loadCatalogDishes.ts
Normal file
106
Needle/MenuScene/src/loadCatalogDishes.ts
Normal file
@@ -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<string, string> = {
|
||||||
|
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<ReturnType<typeof findMenuController>>,
|
||||||
|
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<ReturnType<typeof findMenuController>>,
|
||||||
|
): Promise<number> {
|
||||||
|
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<void> | null = null;
|
||||||
|
|
||||||
|
function initCatalogDishLoader(): void {
|
||||||
|
const needle = document.querySelector("needle-engine");
|
||||||
|
if (!needle) return;
|
||||||
|
|
||||||
|
const run = (): Promise<void> => {
|
||||||
|
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<void> {
|
||||||
|
return catalogLoadPromise ?? Promise.resolve();
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import("./generated/register_types.ts");
|
||||||
import("@needle-tools/engine") /* async import of needle engine */;
|
import("@needle-tools/engine") /* async import of needle engine */;
|
||||||
import "./disableCameraControls";
|
import "./disableCameraControls";
|
||||||
|
import "./menuSkybox";
|
||||||
import "./dishMeshFallback";
|
import "./dishMeshFallback";
|
||||||
import "./enableXR";
|
import "./enableXR";
|
||||||
|
import "./ingredientsPopup";
|
||||||
|
import "./loadCatalogDishes";
|
||||||
import "./assetPicker";
|
import "./assetPicker";
|
||||||
28
Needle/MenuScene/src/menuSkybox.ts
Normal file
28
Needle/MenuScene/src/menuSkybox.ts
Normal file
@@ -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<void> {
|
||||||
|
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);
|
||||||
|
});
|
||||||
34
Needle/MenuScene/src/scripts/DishIngredientsTap.ts
Normal file
34
Needle/MenuScene/src/scripts/DishIngredientsTap.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,14 @@ import {
|
|||||||
Behaviour,
|
Behaviour,
|
||||||
DeviceUtilities,
|
DeviceUtilities,
|
||||||
GameObject,
|
GameObject,
|
||||||
|
findObjectOfType,
|
||||||
|
Renderer,
|
||||||
serializable,
|
serializable,
|
||||||
USDZExporter,
|
USDZExporter,
|
||||||
type NeedleXREventArgs,
|
type NeedleXREventArgs,
|
||||||
} from "@needle-tools/engine";
|
} from "@needle-tools/engine";
|
||||||
import { Object3D } from "three";
|
import { Object3D } from "three";
|
||||||
|
import { ensureDishVisible } from "../dishPlacement.js";
|
||||||
|
|
||||||
const dishBaseY = new WeakMap<Object3D, number>();
|
const dishBaseY = new WeakMap<Object3D, number>();
|
||||||
|
|
||||||
@@ -48,7 +51,13 @@ export class MenuController extends Behaviour {
|
|||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
this.dishName = params.get("dishName") ?? "";
|
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) {
|
if (this.dishName) {
|
||||||
let matched = false;
|
let matched = false;
|
||||||
@@ -66,6 +75,7 @@ export class MenuController extends Behaviour {
|
|||||||
this.restoreDishBaseY(dish);
|
this.restoreDishBaseY(dish);
|
||||||
}
|
}
|
||||||
GameObject.setActive(dish, on);
|
GameObject.setActive(dish, on);
|
||||||
|
ensureDishVisible(dish, on);
|
||||||
});
|
});
|
||||||
if (!matched) {
|
if (!matched) {
|
||||||
this.ensureOnlySelectedDishVisible();
|
this.ensureOnlySelectedDishVisible();
|
||||||
@@ -94,6 +104,7 @@ export class MenuController extends Behaviour {
|
|||||||
if (args.xr.mode === "immersive-ar") {
|
if (args.xr.mode === "immersive-ar") {
|
||||||
this.arSessionBobPaused = true;
|
this.arSessionBobPaused = true;
|
||||||
this.snapActiveDishToBaseY();
|
this.snapActiveDishToBaseY();
|
||||||
|
this.prepareForAR();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +254,22 @@ export class MenuController extends Behaviour {
|
|||||||
document.addEventListener("touchend", onTouchEnd, { passive: false });
|
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[] {
|
private getValidDishIndices(): number[] {
|
||||||
return this.dishes.map((dish, index) => (dish != null ? index : -1)).filter((index) => index >= 0);
|
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) {
|
if (!active) {
|
||||||
this.restoreDishBaseY(d);
|
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;
|
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). */
|
/** Label for the asset picker (object name from Unity when set, else Dish i / n). */
|
||||||
getPickerLabel(): string {
|
getPickerLabel(): string {
|
||||||
const valid = this.getValidDishIndices();
|
const valid = this.getValidDishIndices();
|
||||||
@@ -306,12 +342,12 @@ export class MenuController extends Behaviour {
|
|||||||
if (pos < 0) pos = 0;
|
if (pos < 0) pos = 0;
|
||||||
|
|
||||||
this.restoreDishBaseY(this.dishes[valid[pos]]);
|
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;
|
pos = (pos - 1 + valid.length) % valid.length;
|
||||||
this.selectedDishIndex = valid[pos];
|
this.selectedDishIndex = valid[pos];
|
||||||
GameObject.setActive(this.dishes[this.selectedDishIndex], true);
|
this.setDishActive(this.dishes[this.selectedDishIndex], true);
|
||||||
|
|
||||||
this.updateUSDZExporterTarget();
|
this.prepareForAR();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectNextDish(): void {
|
selectNextDish(): void {
|
||||||
@@ -322,17 +358,23 @@ export class MenuController extends Behaviour {
|
|||||||
if (pos < 0) pos = 0;
|
if (pos < 0) pos = 0;
|
||||||
|
|
||||||
this.restoreDishBaseY(this.dishes[valid[pos]]);
|
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;
|
pos = (pos + 1) % valid.length;
|
||||||
this.selectedDishIndex = valid[pos];
|
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 {
|
private updateUSDZExporterTarget(): void {
|
||||||
const dish = this.dishes[this.selectedDishIndex];
|
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;
|
this.usdzExporter.objectToExport = dish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ onStart((context) => {
|
|||||||
let webxr = context.scene.getComponentInChildren(WebXR);
|
let webxr = context.scene.getComponentInChildren(WebXR);
|
||||||
if (!webxr) {
|
if (!webxr) {
|
||||||
webxr = context.scene.addComponent(WebXR);
|
webxr = context.scene.addComponent(WebXR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.createARButton = true;
|
||||||
webxr.createVRButton = true;
|
webxr.createVRButton = true;
|
||||||
}
|
webxr.createSendToQuestButton = true;
|
||||||
|
|
||||||
webxr.autoPlace = true;
|
webxr.autoPlace = true;
|
||||||
webxr.autoCenter = true;
|
webxr.autoCenter = true;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"com.needle.engine-exporter": "5.0.7",
|
||||||
"com.unity.ai.navigation": "2.0.12",
|
"com.unity.ai.navigation": "2.0.12",
|
||||||
"com.unity.collab-proxy": "2.12.4",
|
"com.unity.collab-proxy": "2.12.4",
|
||||||
"com.unity.ide.rider": "3.0.39",
|
"com.unity.ide.rider": "3.0.39",
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
"com.unity.timeline": "1.8.12",
|
"com.unity.timeline": "1.8.12",
|
||||||
"com.unity.ugui": "2.0.0",
|
"com.unity.ugui": "2.0.0",
|
||||||
"com.unity.visualscripting": "1.9.11",
|
"com.unity.visualscripting": "1.9.11",
|
||||||
|
"org.khronos.unitygltf": "2.19.5",
|
||||||
"com.unity.modules.accessibility": "1.0.0",
|
"com.unity.modules.accessibility": "1.0.0",
|
||||||
"com.unity.modules.adaptiveperformance": "1.0.0",
|
"com.unity.modules.adaptiveperformance": "1.0.0",
|
||||||
"com.unity.modules.ai": "1.0.0",
|
"com.unity.modules.ai": "1.0.0",
|
||||||
@@ -44,8 +46,7 @@
|
|||||||
"com.unity.modules.video": "1.0.0",
|
"com.unity.modules.video": "1.0.0",
|
||||||
"com.unity.modules.vr": "1.0.0",
|
"com.unity.modules.vr": "1.0.0",
|
||||||
"com.unity.modules.wind": "1.0.0",
|
"com.unity.modules.wind": "1.0.0",
|
||||||
"com.unity.modules.xr": "1.0.0",
|
"com.unity.modules.xr": "1.0.0"
|
||||||
"com.needle.engine-exporter": "5.0.3"
|
|
||||||
},
|
},
|
||||||
"scopedRegistries": [
|
"scopedRegistries": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"com.needle.engine-exporter": {
|
"com.needle.engine-exporter": {
|
||||||
"version": "5.0.3",
|
"version": "5.0.7",
|
||||||
"depth": 0,
|
"depth": 0,
|
||||||
"source": "registry",
|
"source": "registry",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"org.khronos.unitygltf": "2.19.2",
|
"org.khronos.unitygltf": "2.19.5",
|
||||||
"com.unity.modules.animation": "1.0.0",
|
"com.unity.modules.animation": "1.0.0",
|
||||||
"com.unity.modules.unitywebrequest": "1.0.0",
|
"com.unity.modules.unitywebrequest": "1.0.0",
|
||||||
"com.unity.modules.uielements": "1.0.0",
|
"com.unity.modules.uielements": "1.0.0",
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
},
|
},
|
||||||
"com.unity.collections": {
|
"com.unity.collections": {
|
||||||
"version": "6.4.0",
|
"version": "6.4.0",
|
||||||
"depth": 2,
|
"depth": 1,
|
||||||
"source": "builtin",
|
"source": "builtin",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"com.unity.burst": "1.8.23",
|
"com.unity.burst": "1.8.23",
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
},
|
},
|
||||||
"com.unity.mathematics": {
|
"com.unity.mathematics": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"depth": 2,
|
"depth": 1,
|
||||||
"source": "registry",
|
"source": "registry",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"url": "https://packages.unity.com"
|
"url": "https://packages.unity.com"
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
},
|
},
|
||||||
"com.unity.nuget.mono-cecil": {
|
"com.unity.nuget.mono-cecil": {
|
||||||
"version": "1.11.6",
|
"version": "1.11.6",
|
||||||
"depth": 3,
|
"depth": 2,
|
||||||
"source": "registry",
|
"source": "registry",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"url": "https://packages.unity.com"
|
"url": "https://packages.unity.com"
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
},
|
},
|
||||||
"com.unity.test-framework.performance": {
|
"com.unity.test-framework.performance": {
|
||||||
"version": "3.4.0",
|
"version": "3.4.0",
|
||||||
"depth": 3,
|
"depth": 2,
|
||||||
"source": "registry",
|
"source": "registry",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"com.unity.test-framework": "1.1.33",
|
"com.unity.test-framework": "1.1.33",
|
||||||
@@ -257,8 +257,8 @@
|
|||||||
"url": "https://packages.unity.com"
|
"url": "https://packages.unity.com"
|
||||||
},
|
},
|
||||||
"org.khronos.unitygltf": {
|
"org.khronos.unitygltf": {
|
||||||
"version": "2.19.2",
|
"version": "2.19.5",
|
||||||
"depth": 1,
|
"depth": 0,
|
||||||
"source": "registry",
|
"source": "registry",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"com.unity.nuget.newtonsoft-json": "2.0.0",
|
"com.unity.nuget.newtonsoft-json": "2.0.0",
|
||||||
|
|||||||
1
deployment-note.txt
Normal file
1
deployment-note.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This file was added on a new branch for testing branch creation and pull request flow.
|
||||||
Reference in New Issue
Block a user