diff --git a/.htaccess b/.htaccess
new file mode 100644
index 000000000..5e8e3d2e2
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,13 @@
+
+# Caching
+
+# 1 week for images, videos
+
+Header set Cache-Control "public, max-age=604800, no-transform"
+
+
+
+Header set Cache-Control "public, max-age=120, must-revalidate"
+
+
+
\ No newline at end of file
diff --git a/.preview/404.png b/.preview/404.png
new file mode 100644
index 000000000..32389ed11
Binary files /dev/null and b/.preview/404.png differ
diff --git a/.preview/ configuration.png b/.preview/ configuration.png
new file mode 100644
index 000000000..5744309c1
Binary files /dev/null and b/.preview/ configuration.png differ
diff --git a/.preview/@serializable and other decorators.png b/.preview/@serializable and other decorators.png
new file mode 100644
index 000000000..16ea6afa2
Binary files /dev/null and b/.preview/@serializable and other decorators.png differ
diff --git a/.preview/additional modules.png b/.preview/additional modules.png
new file mode 100644
index 000000000..c337c2f91
Binary files /dev/null and b/.preview/additional modules.png differ
diff --git a/.preview/automatic component generation.png b/.preview/automatic component generation.png
new file mode 100644
index 000000000..e3087aedc
Binary files /dev/null and b/.preview/automatic component generation.png differ
diff --git a/.preview/backlog mermaid.png b/.preview/backlog mermaid.png
new file mode 100644
index 000000000..fba46cab1
Binary files /dev/null and b/.preview/backlog mermaid.png differ
diff --git a/.preview/backlog.png b/.preview/backlog.png
new file mode 100644
index 000000000..3fdff728d
Binary files /dev/null and b/.preview/backlog.png differ
diff --git a/.preview/bike configurator.png b/.preview/bike configurator.png
new file mode 100644
index 000000000..cfba5f434
Binary files /dev/null and b/.preview/bike configurator.png differ
diff --git a/.preview/castle builder.png b/.preview/castle builder.png
new file mode 100644
index 000000000..09382a4c8
Binary files /dev/null and b/.preview/castle builder.png differ
diff --git a/.preview/community: contributions.png b/.preview/community: contributions.png
new file mode 100644
index 000000000..37b6d74db
Binary files /dev/null and b/.preview/community: contributions.png differ
diff --git a/.preview/contributions: ericcraft mh.png b/.preview/contributions: ericcraft mh.png
new file mode 100644
index 000000000..f81ccf6c0
Binary files /dev/null and b/.preview/contributions: ericcraft mh.png differ
diff --git a/.preview/contributions: kipash.png b/.preview/contributions: kipash.png
new file mode 100644
index 000000000..0782c1118
Binary files /dev/null and b/.preview/contributions: kipash.png differ
diff --git a/.preview/contributions: krisrok.png b/.preview/contributions: krisrok.png
new file mode 100644
index 000000000..6d75491af
Binary files /dev/null and b/.preview/contributions: krisrok.png differ
diff --git a/.preview/contributions: llllkatjallll.png b/.preview/contributions: llllkatjallll.png
new file mode 100644
index 000000000..33ec506a5
Binary files /dev/null and b/.preview/contributions: llllkatjallll.png differ
diff --git a/.preview/contributions: marwie.png b/.preview/contributions: marwie.png
new file mode 100644
index 000000000..d8b49c495
Binary files /dev/null and b/.preview/contributions: marwie.png differ
diff --git a/.preview/contributions: robyer1.png b/.preview/contributions: robyer1.png
new file mode 100644
index 000000000..053cd23f4
Binary files /dev/null and b/.preview/contributions: robyer1.png differ
diff --git a/.preview/contributions: web3kev.png b/.preview/contributions: web3kev.png
new file mode 100644
index 000000000..648e2f90c
Binary files /dev/null and b/.preview/contributions: web3kev.png differ
diff --git a/.preview/creating and using components.png b/.preview/creating and using components.png
new file mode 100644
index 000000000..e8c6a7e67
Binary files /dev/null and b/.preview/creating and using components.png differ
diff --git a/.preview/custom integrations.png b/.preview/custom integrations.png
new file mode 100644
index 000000000..6bdb8385d
Binary files /dev/null and b/.preview/custom integrations.png differ
diff --git a/.preview/deployment and optimization.png b/.preview/deployment and optimization.png
new file mode 100644
index 000000000..19915bf2f
Binary files /dev/null and b/.preview/deployment and optimization.png differ
diff --git a/.preview/embedding.png b/.preview/embedding.png
new file mode 100644
index 000000000..c4f2e9b9d
Binary files /dev/null and b/.preview/embedding.png differ
diff --git a/.preview/ericcraft mh: quicklook vertical image tracker.png b/.preview/ericcraft mh: quicklook vertical image tracker.png
new file mode 100644
index 000000000..ed4f52bd3
Binary files /dev/null and b/.preview/ericcraft mh: quicklook vertical image tracker.png differ
diff --git a/.preview/everywhere actions.png b/.preview/everywhere actions.png
new file mode 100644
index 000000000..127656e3f
Binary files /dev/null and b/.preview/everywhere actions.png differ
diff --git a/.preview/examples.png b/.preview/examples.png
new file mode 100644
index 000000000..20508fdf9
Binary files /dev/null and b/.preview/examples.png differ
diff --git a/.preview/exporting assets to gltf.png b/.preview/exporting assets to gltf.png
new file mode 100644
index 000000000..008f201fc
Binary files /dev/null and b/.preview/exporting assets to gltf.png differ
diff --git a/.preview/features overview.png b/.preview/features overview.png
new file mode 100644
index 000000000..7bfd49fbd
Binary files /dev/null and b/.preview/features overview.png differ
diff --git a/.preview/frameworks, bundlers, html.png b/.preview/frameworks, bundlers, html.png
new file mode 100644
index 000000000..985381dca
Binary files /dev/null and b/.preview/frameworks, bundlers, html.png differ
diff --git a/.preview/getting started & installation.png b/.preview/getting started & installation.png
new file mode 100644
index 000000000..230d1c3e1
Binary files /dev/null and b/.preview/getting started & installation.png differ
diff --git a/.preview/getting started.png b/.preview/getting started.png
new file mode 100644
index 000000000..f401faf1d
Binary files /dev/null and b/.preview/getting started.png differ
diff --git a/.preview/how to debug.png b/.preview/how to debug.png
new file mode 100644
index 000000000..1f9f0d8e9
Binary files /dev/null and b/.preview/how to debug.png differ
diff --git a/.preview/kipash: calculate pointer world position.png b/.preview/kipash: calculate pointer world position.png
new file mode 100644
index 000000000..b72efe461
Binary files /dev/null and b/.preview/kipash: calculate pointer world position.png differ
diff --git a/.preview/krisrok: always open in specific browser.png b/.preview/krisrok: always open in specific browser.png
new file mode 100644
index 000000000..276a595d6
Binary files /dev/null and b/.preview/krisrok: always open in specific browser.png differ
diff --git a/.preview/llllkatjallll: custom vr button that appears only on headsets and not on mobile phones.png b/.preview/llllkatjallll: custom vr button that appears only on headsets and not on mobile phones.png
new file mode 100644
index 000000000..8a2a19939
Binary files /dev/null and b/.preview/llllkatjallll: custom vr button that appears only on headsets and not on mobile phones.png differ
diff --git a/.preview/llllkatjallll: set fallback material for usdz exporter.png b/.preview/llllkatjallll: set fallback material for usdz exporter.png
new file mode 100644
index 000000000..6bfe0d653
Binary files /dev/null and b/.preview/llllkatjallll: set fallback material for usdz exporter.png differ
diff --git a/.preview/marwie: camera video background.png b/.preview/marwie: camera video background.png
new file mode 100644
index 000000000..346140a1b
Binary files /dev/null and b/.preview/marwie: camera video background.png differ
diff --git a/.preview/marwie: code contribution example.png b/.preview/marwie: code contribution example.png
new file mode 100644
index 000000000..11bcffda6
Binary files /dev/null and b/.preview/marwie: code contribution example.png differ
diff --git a/.preview/marwie: control a timeline by scroll.png b/.preview/marwie: control a timeline by scroll.png
new file mode 100644
index 000000000..123a11834
Binary files /dev/null and b/.preview/marwie: control a timeline by scroll.png differ
diff --git a/.preview/marwie: everywhere action emphasize on click.png b/.preview/marwie: everywhere action emphasize on click.png
new file mode 100644
index 000000000..869a3abf8
Binary files /dev/null and b/.preview/marwie: everywhere action emphasize on click.png differ
diff --git a/.preview/marwie: usdz hide object on start.png b/.preview/marwie: usdz hide object on start.png
new file mode 100644
index 000000000..95017c717
Binary files /dev/null and b/.preview/marwie: usdz hide object on start.png differ
diff --git a/.preview/mercedes benz showcase.png b/.preview/mercedes benz showcase.png
new file mode 100644
index 000000000..7bf74204c
Binary files /dev/null and b/.preview/mercedes benz showcase.png differ
diff --git a/.preview/meta test.png b/.preview/meta test.png
new file mode 100644
index 000000000..723b0d97e
Binary files /dev/null and b/.preview/meta test.png differ
diff --git a/.preview/monster hands.png b/.preview/monster hands.png
new file mode 100644
index 000000000..83122ee22
Binary files /dev/null and b/.preview/monster hands.png differ
diff --git a/.preview/needle core components.png b/.preview/needle core components.png
new file mode 100644
index 000000000..72c5237db
Binary files /dev/null and b/.preview/needle core components.png differ
diff --git a/.preview/needle engine.png b/.preview/needle engine.png
new file mode 100644
index 000000000..47b2ec5e7
Binary files /dev/null and b/.preview/needle engine.png differ
diff --git a/.preview/needle.png b/.preview/needle.png
new file mode 100644
index 000000000..f4844c2f0
Binary files /dev/null and b/.preview/needle.png differ
diff --git a/.preview/networking.png b/.preview/networking.png
new file mode 100644
index 000000000..635f2027b
Binary files /dev/null and b/.preview/networking.png differ
diff --git a/.preview/robyer1: ar move scale rotate controls for needle on mobile.png b/.preview/robyer1: ar move scale rotate controls for needle on mobile.png
new file mode 100644
index 000000000..70eead008
Binary files /dev/null and b/.preview/robyer1: ar move scale rotate controls for needle on mobile.png differ
diff --git a/.preview/robyer1: microphone access in a browser window and streamed playback.png b/.preview/robyer1: microphone access in a browser window and streamed playback.png
new file mode 100644
index 000000000..981057835
Binary files /dev/null and b/.preview/robyer1: microphone access in a browser window and streamed playback.png differ
diff --git a/.preview/samples projects.png b/.preview/samples projects.png
new file mode 100644
index 000000000..f8fab5b64
Binary files /dev/null and b/.preview/samples projects.png differ
diff --git a/.preview/scripting examples.png b/.preview/scripting examples.png
new file mode 100644
index 000000000..7aa11f85a
Binary files /dev/null and b/.preview/scripting examples.png differ
diff --git a/.preview/scripting in needle engine.png b/.preview/scripting in needle engine.png
new file mode 100644
index 000000000..a7243c650
Binary files /dev/null and b/.preview/scripting in needle engine.png differ
diff --git a/.preview/scripting introduction for unity developers.png b/.preview/scripting introduction for unity developers.png
new file mode 100644
index 000000000..31c979a93
Binary files /dev/null and b/.preview/scripting introduction for unity developers.png differ
diff --git a/.preview/summary.png b/.preview/summary.png
new file mode 100644
index 000000000..8f155ecc4
Binary files /dev/null and b/.preview/summary.png differ
diff --git a/.preview/support.png b/.preview/support.png
new file mode 100644
index 000000000..c8fa464b6
Binary files /dev/null and b/.preview/support.png differ
diff --git a/.preview/technical overview.png b/.preview/technical overview.png
new file mode 100644
index 000000000..3a72c3749
Binary files /dev/null and b/.preview/technical overview.png differ
diff --git a/.preview/testimonials.png b/.preview/testimonials.png
new file mode 100644
index 000000000..eb3decd22
Binary files /dev/null and b/.preview/testimonials.png differ
diff --git a/.preview/testing on local devices.png b/.preview/testing on local devices.png
new file mode 100644
index 000000000..3a4af0fcf
Binary files /dev/null and b/.preview/testing on local devices.png differ
diff --git a/.preview/three.png b/.preview/three.png
new file mode 100644
index 000000000..eac4d7b3b
Binary files /dev/null and b/.preview/three.png differ
diff --git a/.preview/tower defense.png b/.preview/tower defense.png
new file mode 100644
index 000000000..e58017856
Binary files /dev/null and b/.preview/tower defense.png differ
diff --git a/.preview/unity: editor sync.png b/.preview/unity: editor sync.png
new file mode 100644
index 000000000..bc2a314f3
Binary files /dev/null and b/.preview/unity: editor sync.png differ
diff --git a/.preview/using needle engine directly from html.png b/.preview/using needle engine directly from html.png
new file mode 100644
index 000000000..1c909c026
Binary files /dev/null and b/.preview/using needle engine directly from html.png differ
diff --git a/.preview/vision.png b/.preview/vision.png
new file mode 100644
index 000000000..02dd187be
Binary files /dev/null and b/.preview/vision.png differ
diff --git a/.preview/vr & ar.png b/.preview/vr & ar.png
new file mode 100644
index 000000000..bb81b93b9
Binary files /dev/null and b/.preview/vr & ar.png differ
diff --git a/.preview/web3kev: network instantiation of multiple objects.png b/.preview/web3kev: network instantiation of multiple objects.png
new file mode 100644
index 000000000..0747bd934
Binary files /dev/null and b/.preview/web3kev: network instantiation of multiple objects.png differ
diff --git a/.preview/web3kev: squeeze to scale object or world in vr.png b/.preview/web3kev: squeeze to scale object or world in vr.png
new file mode 100644
index 000000000..24977d090
Binary files /dev/null and b/.preview/web3kev: squeeze to scale object or world in vr.png differ
diff --git a/.preview/web3kev: vertical move in vr using the right joystick quest.png b/.preview/web3kev: vertical move in vr using the right joystick quest.png
new file mode 100644
index 000000000..28a69a851
Binary files /dev/null and b/.preview/web3kev: vertical move in vr using the right joystick quest.png differ
diff --git a/404.html b/404.html
new file mode 100644
index 000000000..bc454297a
--- /dev/null
+++ b/404.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+ Needle Engine Documentation
+
+
+
+
+
+
+
+
+
diff --git a/android-chrome-144x144.png b/android-chrome-144x144.png
new file mode 100644
index 000000000..9680622fd
Binary files /dev/null and b/android-chrome-144x144.png differ
diff --git a/assets/404.html-DQk9SM3x.js b/assets/404.html-DQk9SM3x.js
new file mode 100644
index 000000000..72bf8f901
--- /dev/null
+++ b/assets/404.html-DQk9SM3x.js
@@ -0,0 +1 @@
+import{_ as t,o as n,c as o,a}from"./app-CRZRGfEE.js";const l={};function r(s,e){return n(),o("div",null,e[0]||(e[0]=[a("p",null,"404 Not Found",-1)]))}const i=t(l,[["render",r],["__file","404.html.vue"]]),d=JSON.parse('{"path":"/404.html","title":"","lang":"en-US","frontmatter":{"layout":"NotFound","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/404.png"}],["meta",{"name":"og:description","content":"d"}]],"description":"d"},"headers":[],"git":{},"filePathRelative":null}');export{i as comp,d as data};
diff --git a/assets/NoDownloadYet-CimAPJue.js b/assets/NoDownloadYet-CimAPJue.js
new file mode 100644
index 000000000..f486fab3b
--- /dev/null
+++ b/assets/NoDownloadYet-CimAPJue.js
@@ -0,0 +1 @@
+import{_ as a,f as o,g as t}from"./app-CRZRGfEE.js";const n={methods:{checkQueryParams:s}};function s(){return typeof window>"u"?!1:new URLSearchParams(window.location.search).has("dl")}function c(e,d,u,f,l,r){return r.checkQueryParams()?t("",!0):o(e.$slots,"default",{key:0})}const i=a(n,[["render",c],["__file","NoDownloadYet.vue"]]);export{i as default};
diff --git a/assets/SUMMARY.html-o_nWAvKL.js b/assets/SUMMARY.html-o_nWAvKL.js
new file mode 100644
index 000000000..f36e47898
--- /dev/null
+++ b/assets/SUMMARY.html-o_nWAvKL.js
@@ -0,0 +1 @@
+import{_ as i,r as a,o as s,c as d,a as t,b as n,w as o,d as r}from"./app-CRZRGfEE.js";const u={};function m(p,e){const l=a("RouteLink");return s(),d("div",null,[e[17]||(e[17]=t("h1",{id:"get-started",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#get-started"},[t("span",null,"Get Started")])],-1)),t("ul",null,[t("li",null,[n(l,{to:"/getting-started/"},{default:o(()=>e[0]||(e[0]=[r("Getting Started โญ")])),_:1}),t("ul",null,[t("li",null,[n(l,{to:"/features-overview.html"},{default:o(()=>e[1]||(e[1]=[r("Feature Overview")])),_:1})]),t("li",null,[n(l,{to:"/export.html"},{default:o(()=>e[2]||(e[2]=[r("Export")])),_:1})]),t("li",null,[n(l,{to:"/project-structure.html"},{default:o(()=>e[3]||(e[3]=[r("Project Structure")])),_:1})]),t("li",null,[n(l,{to:"/xr.html"},{default:o(()=>e[4]||(e[4]=[r("VR and AR")])),_:1})]),t("li",null,[n(l,{to:"/scripting.html"},{default:o(()=>e[5]||(e[5]=[r("Scripting")])),_:1})]),t("li",null,[n(l,{to:"/html.html"},{default:o(()=>e[6]||(e[6]=[r("HTML")])),_:1})]),t("li",null,[n(l,{to:"/deployment.html"},{default:o(()=>e[7]||(e[7]=[r("Deployment")])),_:1})])])])]),e[18]||(e[18]=t("h1",{id:"samples",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#samples"},[t("span",null,"Samples")])],-1)),t("ul",null,[t("li",null,[n(l,{to:"/samples-and-modules.html"},{default:o(()=>e[8]||(e[8]=[r("Samples and Examples")])),_:1}),t("ul",null,[t("li",null,[n(l,{to:"/for-unity-developers.html"},{default:o(()=>e[9]||(e[9]=[r("Tips for Unity Developers")])),_:1})]),t("li",null,[n(l,{to:"/examples.html"},{default:o(()=>e[10]||(e[10]=[r("Showcase")])),_:1})]),t("li",null,[n(l,{to:"/samples-and-modules.html"},{default:o(()=>e[11]||(e[11]=[r("Project Samples, Examples and Modules")])),_:1})]),t("li",null,[n(l,{to:"/faq.html"},{default:o(()=>e[12]||(e[12]=[r("FAQ")])),_:1})])])])]),e[19]||(e[19]=t("h1",{id:"dive-deeper",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#dive-deeper"},[t("span",null,"Dive Deeper ๐ ")])],-1)),t("ul",null,[t("li",null,[n(l,{to:"/vision.html"},{default:o(()=>e[13]||(e[13]=[r("Our Vision")])),_:1}),t("ul",null,[t("li",null,[n(l,{to:"/technical-overview.html"},{default:o(()=>e[14]||(e[14]=[r("Technical Overview")])),_:1})]),t("li",null,[n(l,{to:"/component-reference.html"},{default:o(()=>e[15]||(e[15]=[r("Component Reference")])),_:1})]),t("li",null,[n(l,{to:"/debugging.html"},{default:o(()=>e[16]||(e[16]=[r("Debugging")])),_:1})])])])])])}const g=i(u,[["render",m],["__file","SUMMARY.html.vue"]]),b=JSON.parse('{"path":"/SUMMARY.html","title":"Get Started","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/summary.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{"updatedTime":1726487589000},"filePathRelative":"SUMMARY.md"}');export{g as comp,b as data};
diff --git a/assets/action-Dm_y7Sbx.js b/assets/action-Dm_y7Sbx.js
new file mode 100644
index 000000000..f0ec6be40
--- /dev/null
+++ b/assets/action-Dm_y7Sbx.js
@@ -0,0 +1 @@
+import{_ as o,o as t,c as s,f as a,t as r,g as c}from"./app-CRZRGfEE.js";const i={props:{href:String,subtitle:String}},l=["href"],_={key:0,class:"subtitle"};function f(n,d,e,u,h,p){return t(),s("a",{class:"action no-external-link-icon",href:e.href},[a(n.$slots,"default",{},void 0,!0),e.subtitle?(t(),s("span",_,r(e.subtitle),1)):c("",!0)],8,l)}const b=o(i,[["render",f],["__scopeId","data-v-705b9710"],["__file","action.vue"]]);export{b as default};
diff --git a/assets/actiongroup-gFGdAuKO.js b/assets/actiongroup-gFGdAuKO.js
new file mode 100644
index 000000000..014e1535a
--- /dev/null
+++ b/assets/actiongroup-gFGdAuKO.js
@@ -0,0 +1 @@
+import{_ as e,o as t,c as s,f as c}from"./app-CRZRGfEE.js";const r={},a={class:"actiongroup"};function n(o,_,p,i,f,l){return t(),s("div",a,[c(o.$slots,"default")])}const d=e(r,[["render",n],["__file","actiongroup.vue"]]);export{d as default};
diff --git a/assets/always-open-in-specific-browser.html-Cboldk4x.js b/assets/always-open-in-specific-browser.html-Cboldk4x.js
new file mode 100644
index 000000000..8ba25a75b
--- /dev/null
+++ b/assets/always-open-in-specific-browser.html-Cboldk4x.js
@@ -0,0 +1,31 @@
+import{_ as n,r as t,o as l,c as e,a as i,b as h,e as k}from"./app-CRZRGfEE.js";const p={};function r(o,s){const a=t("contribution-header");return l(),e("div",null,[s[0]||(s[0]=i("p",null,[i("a",{href:"/docs/community/contributions"},"Overview")],-1)),h(a,{url:"https://github.com/krisrok",author:"krisrok",page:"/docs/community/contributions/krisrok",profileImage:"https://avatars.githubusercontent.com/u/3404365?s=100&u=7025bf7e83b4a3cd72dc2cae9cec729080ee8970&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/178",title:"Always open in specific browser",gradient:"True"}),s[1]||(s[1]=k(`
Add this class to your project to always open with Chrome instead of your default browser (Firefox in my case) when you click "Play" or "Start Server". Note: This is an editor class and should either be put into an editor-only assembly or wrapped in #if UNITY_EDITOR and #endif.
Generated Projects can either be added to source control or kept dynamic. Adding them to source control unlocks being able to adjust HTML, CSS, etc very flexible. To generate dynamic projects, change their path to ../Library/MyScene. They will be regenerated if needed.
Please follow the instructions in the Authentication section if this is your first time accessing packages by needle on this machine.
Make sure you have a Needle Engine and Exporter license, otherwise the following steps will fail (you'll not be able to get authenticated package access).
Needs to be setup once per machine.
Clone this repository and open starter/Authenticate with Unity 2020.3.x
Return to packages.needle.tools โก and click the i icon in the top right corner opening the Registry Info window.
Copy the line containing _authToken (see the video below)
Focus Unity - a notification window should open that the information has been added successfully from your clipboard.
Click save and close Unity. You should now have access rights to the needle package registry.
',16)]))}const c=t(a,[["render",r],["__file","backlog.html.vue"]]),d=JSON.parse('{"path":"/backlog.html","title":"Documentation Backlog","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/backlog.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[{"level":2,"title":"Recommended Unity configuration","slug":"recommended-unity-configuration","link":"#recommended-unity-configuration","children":[]},{"level":2,"title":"Supported Unity configurations","slug":"supported-unity-configurations","link":"#supported-unity-configurations","children":[]},{"level":2,"title":"Source Control","slug":"source-control","link":"#source-control","children":[]},{"level":2,"title":"Authentication","slug":"authentication","link":"#authentication","children":[]}],"git":{"updatedTime":1669763662000},"filePathRelative":"_backlog.md"}');export{c as comp,d as data};
diff --git a/assets/buttons.esm-CUWjhgaJ.js b/assets/buttons.esm-CUWjhgaJ.js
new file mode 100644
index 000000000..52baca35a
--- /dev/null
+++ b/assets/buttons.esm-CUWjhgaJ.js
@@ -0,0 +1,5 @@
+/*!
+ * github-buttons v2.29.0
+ * (c) 2024 ใชใคใ
+ * @license BSD-2-Clause
+ */var C=window.document,p=window.Math,k=window.HTMLElement,b=window.XMLHttpRequest,F=function(e,t){for(var r=0,o=e.length;r'}}},download:{heights:{16:{width:16,path:''}}},eye:{heights:{16:{width:16,path:''}}},heart:{heights:{16:{width:16,path:''}}},"issue-opened":{heights:{16:{width:16,path:''}}},"mark-github":{heights:{16:{width:16,path:''}}},package:{heights:{16:{width:16,path:''}}},play:{heights:{16:{width:16,path:''}}},"repo-forked":{heights:{16:{width:16,path:''}}},"repo-template":{heights:{16:{width:16,path:''}}},star:{heights:{16:{width:16,path:''}}}},oe=function(e,t){e=Z(e).replace(/^octicon-/,""),m(x,e)||(e="mark-github");var r=t>=24&&24 in x[e].heights?24:16,o=x[e].heights[r];return'"},y={},re=function(e,t){var r=y[e]||(y[e]=[]);if(!(r.push(t)>1)){var o=V(function(){for(delete y[e];t=r.shift();)t.apply(null,arguments)});if(S){var a=new b;f(a,"abort",o),f(a,"error",o),f(a,"load",function(){var s;try{s=JSON.parse(this.responseText)}catch(d){o(d);return}o(this.status!==200,s)}),a.open("GET",e),a.send()}else{var n=this||window;n._=function(s){n._=null,o(s.meta.status!==200,s.data)};var c=A(n.document)("script",{async:!0,src:e+(e.indexOf("?")!==-1?"&":"?")+"callback=_"}),l=function(){n._&&n._({meta:{}})};f(c,"load",l),f(c,"error",l),$(c,/de|m/,l),n.document.getElementsByTagName("head")[0].appendChild(c)}}},T=function(e,t,r){var o=A(e.ownerDocument),a=e.appendChild(o("style",{type:"text/css"})),n=J+te(t["data-color-scheme"]);a.styleSheet?a.styleSheet.cssText=n:a.appendChild(e.ownerDocument.createTextNode(n));var c=Z(t["data-size"])==="large",l=o("a",{className:"btn",href:t.href,rel:"noopener",target:"_blank",title:t.title||void 0,"aria-label":t["aria-label"]||void 0,innerHTML:oe(t["data-icon"],c?16:14)+" "},[o("span",{},[t["data-text"]||""])]),s=e.appendChild(o("div",{className:"widget"+(c?" widget-lg":"")},[l])),d=l.hostname.replace(/\.$/,"");if(("."+d).substring(d.length-u.length)!=="."+u){l.removeAttribute("href"),r(s);return}var i=(" /"+l.pathname).split(/\/+/);if(((d===u||d==="gist."+u)&&i[3]==="archive"||d===u&&i[3]==="releases"&&(i[4]==="download"||i[4]==="latest"&&i[5]==="download")||d==="codeload."+u)&&(l.target="_top"),Z(t["data-show-count"])!=="true"||d!==u||i[1]==="marketplace"||i[1]==="sponsors"||i[1]==="orgs"||i[1]==="users"||i[1]==="-"){r(s);return}var v,h;if(!i[2]&&i[1])h="followers",v="?tab=followers";else if(!i[3]&&i[2])h="stargazers_count",v="/stargazers";else if(!i[4]&&i[3]==="subscription")h="subscribers_count",v="/watchers";else if(!i[4]&&i[3]==="fork")h="forks_count",v="/forks";else if(i[3]==="issues")h="open_issues_count",v="/issues";else{r(s);return}var D=i[2]?"/repos/"+i[1]+"/"+i[2]:"/users/"+i[1];re.call(this,P+D,function(B,L){if(!B){var w=L[h];s.appendChild(o("a",{className:"social-count",href:L.html_url+v,rel:"noopener",target:"_blank","aria-label":w+" "+h.replace(/_count$/,"").replace("_"," ").slice(0,w<2?-1:void 0)+" on GitHub"},[(""+w).replace(/\B(?=(\d{3})+(?!\d))/g,",")]))}r(s)})},M=window.devicePixelRatio||1,z=function(e){return(M>1?p.ceil(p.round(e*M)/M*2)/2:p.ceil(e))||0},ae=function(e){var t=e.offsetWidth,r=e.offsetHeight;if(e.getBoundingClientRect){var o=e.getBoundingClientRect();t=p.max(t,z(o.width)),r=p.max(r,z(o.height))}return[t,r]},H=function(e,t){e.style.width=t[0]+"px",e.style.height=t[1]+"px"},ne=function(e,t){if(!(e==null||t==null))if(e.getAttribute&&(e=q(e)),R){var r=_("span");T(r.attachShadow({mode:"closed"}),e,function(){t(r)})}else{var o=_("iframe",{src:"javascript:0",title:e.title||void 0,allowtransparency:!0,scrolling:"no",frameBorder:0});H(o,[0,0]),o.style.border="none";var a=function(){var n=o.contentWindow,c;try{c=n.document.body}catch{C.body.appendChild(o.parentNode.removeChild(o));return}E(o,"load",a),T.call(n,c,e,function(l){var s=ae(l);o.parentNode.removeChild(o),I(o,"load",function(){H(o,s)}),o.src=N+"#"+(o.name=W(e)),t(o)})};f(o,"load",a),C.body.appendChild(o)}};export{ne as render};
diff --git a/assets/calculate-pointer-world-position.html-BBtMcQ13.js b/assets/calculate-pointer-world-position.html-BBtMcQ13.js
new file mode 100644
index 000000000..cf0f85e56
--- /dev/null
+++ b/assets/calculate-pointer-world-position.html-BBtMcQ13.js
@@ -0,0 +1,37 @@
+import{_ as t,r as n,o as h,c as l,a as s,b as k,e}from"./app-CRZRGfEE.js";const p={};function r(d,i){const a=n("contribution-header");return h(),l("div",null,[i[0]||(i[0]=s("p",null,[s("a",{href:"/docs/community/contributions"},"Overview")],-1)),k(a,{url:"https://github.com/kipash",author:"kipash",page:"/docs/community/contributions/kipash",profileImage:"https://avatars.githubusercontent.com/u/30328735?s=100&u=f28398f4575da1835d1c710d14763c69418cd0fa&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/165",title:"Calculate pointer world position",gradient:"True"}),i[1]||(i[1]=e(`
import { Behaviour, serializable, setWorldPosition } from "@needle-tools/engine";
+import { Object3D, Vector3 } from "three";
+
+const vector1 = new Vector3();
+const vector2 = new Vector3();
+
+export class PointerFollower extends Behaviour {
+
+ @serializable(Object3D)
+ target?: Object3D;
+
+ @serializable()
+ offset: number = 10;
+
+ update(): void {
+ const cam = this.context.mainCamera;
+ const input = this.context.input;
+
+ if(!this.target || !cam)
+ return;
+
+ // get relative mouse position, in range -1 to 1
+ const mouse = input.mousePositionRC;
+
+ // get world position of mouse on the near plane
+ vector1.set(mouse.x, mouse.y, -1).unproject(cam!);
+
+ // caulculate direction from camera to world mouse
+ vector2.copy(vector1).sub(cam.position).normalize();
+
+ // offset it to the wanted distance
+ vector1.addScaledVector(vector2, this.offset);
+
+ // apply the result
+ setWorldPosition(this.target, vector1);
+ }
+}
`,2))])}const g=t(p,[["render",r],["__file","calculate-pointer-world-position.html.vue"]]),C=JSON.parse('{"path":"/community/contributions/kipash/calculate-pointer-world-position","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/kipash: calculate pointer world position.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{g as comp,C as data};
diff --git a/assets/camera-video-background.html-BLeFh9kF.js b/assets/camera-video-background.html-BLeFh9kF.js
new file mode 100644
index 000000000..699b1a2f8
--- /dev/null
+++ b/assets/camera-video-background.html-BLeFh9kF.js
@@ -0,0 +1,29 @@
+import{_ as n,r as t,o as h,c as l,a as s,b as e,e as k}from"./app-CRZRGfEE.js";const p={};function r(d,i){const a=t("contribution-header");return h(),l("div",null,[i[0]||(i[0]=s("p",null,[s("a",{href:"/docs/community/contributions"},"Overview")],-1)),e(a,{url:"https://github.com/marwie",author:"marwie",page:"/docs/community/contributions/marwie",profileImage:"https://avatars.githubusercontent.com/u/5083203?s=100&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/166",title:"Camera Video Background",gradient:"True"}),i[1]||(i[1]=k(`
Put it anywhere in your scene to render a camera video behind your 3D scene Live demo
import { Behaviour, ClearFlags, RGBAColor } from "@needle-tools/engine";
+
+export class VideoBackground extends Behaviour {
+
+ async awake() {
+ // create video element and put it inside the <needle-engine> component
+ const video = document.createElement("video");
+ video.style.cssText = \`
+ position: fixed;
+ min-width: 100%;
+ min-height: 100%;
+ z-index: -1;
+ \`
+ this.context.domElement.shadowRoot!.appendChild(video);
+
+ // get webcam input
+ const input = await navigator.mediaDevices.getUserMedia({ video: true })
+ if (!input) return;
+ video.srcObject = input;
+ video.play();
+
+ // make sure the camera background is transparent
+ const camera = this.context.mainCameraComponent;
+ if (camera) {
+ camera.clearFlags = ClearFlags.SolidColor;
+ camera.backgroundColor = new RGBAColor(125, 125, 125, 0);
+ }
+ }
+}
`,2))])}const g=n(p,[["render",r],["__file","camera-video-background.html.vue"]]),C=JSON.parse('{"path":"/community/contributions/marwie/camera-video-background","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/marwie: camera video background.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{g as comp,C as data};
diff --git a/assets/code-contribution-example.html-BnLNxDrY.js b/assets/code-contribution-example.html-BnLNxDrY.js
new file mode 100644
index 000000000..a622909f5
--- /dev/null
+++ b/assets/code-contribution-example.html-BnLNxDrY.js
@@ -0,0 +1,16 @@
+import{_ as a,r as e,o as n,c as l,a as s,b as h,e as p}from"./app-CRZRGfEE.js";const o={};function k(r,i){const t=e("contribution-header");return n(),l("div",null,[i[0]||(i[0]=s("p",null,[s("a",{href:"/docs/community/contributions"},"Overview")],-1)),h(t,{url:"https://github.com/marwie",author:"marwie",page:"/docs/community/contributions/marwie",profileImage:"https://avatars.githubusercontent.com/u/5083203?s=100&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/146",title:"Code Contribution Example",gradient:"True"}),i[1]||(i[1]=p(`
This is mostly a basic example on how to contribute. It will then be added on our documentation contributions page: https://engine.needle.tools/docs/community/contributions
Please include at least one code snippet, for example like this:
import { Behaviour, serializable } from "@needle-tools/engine"
+import { Object3D } from "three"
+
+export class MyComponent extends Behaviour {
+
+ @serializable(Object3D)
+ myObjectReference?: Object3D;
+
+ start() {
+ console.log("Hello world", this);
+ }
+
+ update() {
+ // called every frame
+ }
+}
`,3))])}const c=a(o,[["render",k],["__file","code-contribution-example.html.vue"]]),g=JSON.parse('{"path":"/community/contributions/marwie/code-contribution-example","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/marwie: code contribution example.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{c as comp,g as data};
diff --git a/assets/component-compiler.html-DQMaNo6k.js b/assets/component-compiler.html-DQMaNo6k.js
new file mode 100644
index 000000000..ac968b543
--- /dev/null
+++ b/assets/component-compiler.html-DQMaNo6k.js
@@ -0,0 +1,98 @@
+import{_ as r,r as n,o as p,c as o,e as h,b as a,w as l,a as i,d as s}from"./app-CRZRGfEE.js";const d={};function c(y,t){const e=n("CodeGroupItem"),k=n("CodeGroup");return p(),o("div",null,[t[3]||(t[3]=h(`
When working in Unity or Blender then you will notice that when you create a new Needle Engine component in Typescript or Javascript it will automatically generate a Unity C# stub component OR a Blender panel for you.
This is thanks to the magic of the Needle component compiler that runs behind the scenes in an editor environment and watches changes to your script files. When it notices that you created a new Needle Engine component it will then generate the correct Unity component or Blender panel including public variables or properties that you can then set or link from within the Editor.
If you want to add scripts inside the src/scripts folder in your project then you need to have a Component Generator on the GameObject with your ExportInfo component. Now when adding new components in your/threejs/project/src/scriptsit will automatically generate Unity scripts in Assets/Needle/Components.codegen. If you want to add scripts to any NpmDef file you can just create them - each NpmDef automatically watches script changes and handles component generation, so you don't need any additional component in your scene.
For C# fields to be correctly generated it is currently important that you explictly declare a Typescript type. For example myField : number = 5
You can switch between Typescript input and generated C# stub components using the tabs below
Component C# classes are generated with the partial flag so that it is easy to extend them with functionality. This is helpful to draw gizmos, add context menus or add additional fields or methods that are not part of a built-in component.
Member Casing
Exported members will start with a lowercase letter. For example if your C# member is named MyString it will be assigned to myString.
',3))])}const C=r(d,[["render",c],["__file","component-compiler.html.vue"]]),E=JSON.parse('{"path":"/component-compiler.html","title":"Automatic Component Generation","lang":"en-US","frontmatter":{"title":"Automatic Component Generation","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/automatic component generation.png"}],["meta",{"name":"og:description","content":"---\\nWhen working in Unity or Blender then you will notice that when you create a new Needle Engine component in Typescript or Javascript it will automatically generate a Unity Cstub component OR a Blender panel for you.\\nThis is thanks to the magic of the Needle component compiler that runs behind the scenes in an editor environment and watches changes to your script files. When it notices that you created a new Needle Engine component it will then generate the correct Unity component or Blender panel including public variables or properties that you can then set or link from within the Editor.\\nYou can use the following comments in your typescript code to control Ccode generation behavior:"}]],"description":"---\\nWhen working in Unity or Blender then you will notice that when you create a new Needle Engine component in Typescript or Javascript it will automatically generate a Unity Cstub component OR a Blender panel for you.\\nThis is thanks to the magic of the Needle component compiler that runs behind the scenes in an editor environment and watches changes to your script files. When it notices that you created a new Needle Engine component it will then generate the correct Unity component or Blender panel including public variables or properties that you can then set or link from within the Editor.\\nYou can use the following comments in your typescript code to control Ccode generation behavior:"},"headers":[{"level":3,"title":"Automatically generating Editor components","slug":"automatically-generating-editor-components","link":"#automatically-generating-editor-components","children":[]},{"level":3,"title":"Controlling component generation","slug":"controlling-component-generation","link":"#controlling-component-generation","children":[]},{"level":3,"title":"Component Compiler in Unity","slug":"component-compiler-in-unity","link":"#component-compiler-in-unity","children":[]},{"level":3,"title":"Extending generated components","slug":"extending-generated-components","link":"#extending-generated-components","children":[]}],"git":{"updatedTime":1726514637000},"filePathRelative":"component-compiler.md"}');export{C as comp,E as data};
diff --git a/assets/component-reference.html-zo-jLPyy.js b/assets/component-reference.html-zo-jLPyy.js
new file mode 100644
index 000000000..589c40cf3
--- /dev/null
+++ b/assets/component-reference.html-zo-jLPyy.js
@@ -0,0 +1 @@
+import{_ as i,r as l,o as s,c,a as t,d as o,b as r,w as n,e as a}from"./app-CRZRGfEE.js";const h={};function p(u,e){const d=l("RouteLink");return s(),c("div",null,[e[16]||(e[16]=t("h1",{id:"needle-core-components",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#needle-core-components"},[t("span",null,"Needle Core Components")])],-1)),e[17]||(e[17]=t("p",null,"Here is a overview of some of the components that we provide. Many of them map to components and functionality in Unity, Blender or other integrations.",-1)),e[18]||(e[18]=t("p",null,[o("For a complete list please have a look at our "),t("a",{href:"https://engine.needle.tools/docs/api/latest",target:"_blank",rel:"noopener noreferrer"},"API docs"),o(".")],-1)),e[19]||(e[19]=t("p",null,"You can always add your own components or add wrappers for Unity components we haven't provided yet.",-1)),t("p",null,[e[1]||(e[1]=o("Learn more in the ")),r(d,{to:"/scripting.html"},{default:n(()=>e[0]||(e[0]=[o("Scripting")])),_:1}),e[2]||(e[2]=o(" section of our docs."))]),e[20]||(e[20]=a('
DirectionalLight, PointLight, Spotlight. Note that you can use it to bake light (e.g. Rectangular Light shapes) as well
XRFlag
Control when objects will be visible. E.g. only enable object when in AR
DeviceFlag
Control on which device objects will be visible
LODGroup
ParticleSystem
Experimental and currently not fully supported
VideoPlayer
Playback videos from url or referenced video file (will be copied to output on export). The VideoPlayer also supports streaming from MediaStream objects or M3U8 livestream URLs
MeshRenderer
Used to handle rendering of objects including lightmapping and instancing
Postprocessing effects use the pmndrs postprocessing library under the hood. This means you can also easily add your own custom effects and get an automatically optimized postprocessing pass.
Unity only: Note that Postprocessing effects using a Volume in Unity is only supported with URP
',18)),t("p",null,[r(d,{to:"/xr.html"},{default:n(()=>e[3]||(e[3]=[o("Read the XR docs")])),_:1})]),t("table",null,[e[15]||(e[15]=t("thead",null,[t("tr",null,[t("th",null,"Name"),t("th",null,"Description")])],-1)),t("tbody",null,[e[6]||(e[6]=t("tr",null,[t("td",null,[t("code",null,"WebXR")]),t("td",null,"Add to scene for VR, AR and Passthrough support as well as rendering Avatar models")],-1)),t("tr",null,[t("td",null,[r(d,{to:"/everywhere-actions.html"},{default:n(()=>e[4]||(e[4]=[t("code",null,"USDZExporter",-1)])),_:1})]),e[5]||(e[5]=t("td",null,"Add to enable USD and Quicklook support",-1))]),e[7]||(e[7]=t("tr",null,[t("td",null,[t("code",null,"XRFlag")]),t("td",null,"Control when objects are visible, e.g. only in VR or AR or only in ThirdPerson")],-1)),e[8]||(e[8]=t("tr",null,[t("td",null,[t("code",null,"WebARSessionRoot")]),t("td",null,"Handles placement and scale of your scene in AR mode")],-1)),e[9]||(e[9]=t("tr",null,[t("td",null,[t("code",null,"WebARCameraBackground")]),t("td",null,"Add to access the AR camera image and apply effects or use it for rendering")],-1)),e[10]||(e[10]=t("tr",null,[t("td",null,[t("code",null,"WebXRImageTracking")]),t("td",null,"Assign images to be tracked and optionally instantiate an object at the image position")],-1)),e[11]||(e[11]=t("tr",null,[t("td",null,[t("code",null,"WebXRPlaneTracking")]),t("td",null,"Create plane meshes or colliders for tracked planes")],-1)),e[12]||(e[12]=t("tr",null,[t("td",null,[t("code",null,"XRControllerModel")]),t("td",null,"Can be added to render device controllers or hand models (will be created by default when enabled in the WebXR component)")],-1)),e[13]||(e[13]=t("tr",null,[t("td",null,[t("code",null,"XRControllerMovement")]),t("td",null,"Can be added to provide default movement and teleport controls")],-1)),e[14]||(e[14]=t("tr",null,[t("td",null,[t("code",null,"XRControllerFollow")]),t("td",null,"Can be added to any object in the scene and configured to follow either left or right hands or controllers")],-1))])]),e[21]||(e[21]=a('
Spatial UI components are mapped from Unity UI (Canvas, not UI Toolkit) to three-mesh-ui. UI can be animated.
Name
Description
Canvas
Unity's UI system. Needs to be in World Space mode right now.
Text (Legacy)
Render Text using Unity's UI Text component. Custom fonts are supported, a font atlas will be automatically generated on export. Use the font settings to control which characters are included in the atlas. Note: In Unity make sure to use the Legacy/Text component (TextMeshPro is not supported at the moment)
Button
Receives click events - use the onClick event to react to it. It can be added too 3D scene objects as well. Note: Make sure to use the Legacy/Text component in the Button (or create the Button via the UI/Legacy/Button Unity context menu since TextMeshPro is not supported at the moment)
Image
Renders a sprite image
RawImage
Renders a texture
InputField
Allows text input
Note: Depending on your project, often a mix of spatial and 2D UI makes sense for cross-platform projects where VR, AR, and screens are supported. Typically, you'd build the 2D parts with HTML for best accessibility, and the 3D parts with geometric UIs that also support depth offsets (e.g. button hover states and the like).
Handles loading and unloading of other scenes or prefabs / glTF files. Has features to preload, change scenes via swiping, keyboard events or URL navigation
Main component for managing the web project(s) to e.g. install or start the web app
EditorSync
Add to enable networking material or component value changes to the running three.js app directly from the Unity Editor without having to reload
',12))])}const g=i(h,[["render",p],["__file","component-reference.html.vue"]]),b=JSON.parse(`{"path":"/component-reference.html","title":"Needle Core Components","lang":"en-US","frontmatter":{"title":"Needle Core Components","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/needle core components.png"}],["meta",{"name":"og:description","content":"---\\nHere is a overview of some of the components that we provide. Many of them map to components and functionality in Unity, Blender or other integrations.\\nFor a complete list please have a look at our API docs.\\nYou can always add your own components or add wrappers for Unity components we haven't provided yet.\\nLearn more in the Scripting section of our docs."}]],"description":"---\\nHere is a overview of some of the components that we provide. Many of them map to components and functionality in Unity, Blender or other integrations.\\nFor a complete list please have a look at our API docs.\\nYou can always add your own components or add wrappers for Unity components we haven't provided yet.\\nLearn more in the Scripting section of our docs."},"headers":[{"level":2,"title":"Audio","slug":"audio","link":"#audio","children":[]},{"level":2,"title":"Animation","slug":"animation","link":"#animation","children":[]},{"level":2,"title":"Rendering","slug":"rendering","link":"#rendering","children":[{"level":3,"title":"Postprocessing","slug":"postprocessing","link":"#postprocessing","children":[]}]},{"level":2,"title":"Networking","slug":"networking","link":"#networking","children":[]},{"level":2,"title":"Interaction","slug":"interaction","link":"#interaction","children":[]},{"level":2,"title":"Physics","slug":"physics","link":"#physics","children":[]},{"level":2,"title":"XR / WebXR","slug":"xr-webxr","link":"#xr-webxr","children":[]},{"level":2,"title":"Debugging","slug":"debugging","link":"#debugging","children":[]},{"level":2,"title":"Runtime File Input/Output","slug":"runtime-file-input-output","link":"#runtime-file-input-output","children":[]},{"level":2,"title":"UI","slug":"ui","link":"#ui","children":[]},{"level":2,"title":"Other","slug":"other","link":"#other","children":[]},{"level":2,"title":"Editor Only","slug":"editor-only","link":"#editor-only","children":[]}],"git":{"updatedTime":1727182460000},"filePathRelative":"component-reference.md"}`);export{g as comp,b as data};
diff --git a/assets/contribution-header-B7v_pWCC.js b/assets/contribution-header-B7v_pWCC.js
new file mode 100644
index 000000000..61683eed1
--- /dev/null
+++ b/assets/contribution-header-B7v_pWCC.js
@@ -0,0 +1 @@
+import{_ as l,o as r,c as n,a as t,t as o,g as s,n as c,f as _}from"./app-CRZRGfEE.js";const d={__name:"contribution-header",props:{title:String,url:String,page:String,author:String,profileImage:String,githubUrl:String,gradient:Boolean},setup(a,{expose:i}){i();const e={};return Object.defineProperty(e,"__isScriptSetup",{enumerable:!1,value:!0}),e}},u={class:"contribution"},h={class:"profile"},f=["src"],g=["href"],m={class:"links"},b=["href"],p={key:0,class:"title"};function S(a,i,e,v,k,y){return r(),n("div",u,[t("div",{class:c(["header",e.gradient?"gradient":""])},[t("div",h,[t("img",{src:e.profileImage,alt:"profile image"},null,8,f),t("a",{class:"authorname",href:e.page},[t("span",null,o(e.author),1)],8,g)]),t("div",m,[e.githubUrl?(r(),n("a",{key:0,href:e.githubUrl,target:"_blank",rel:"noopener noreferrer"},"View on Github",8,b)):s("",!0)])],2),e.title?(r(),n("div",p,[t("h2",null,o(e.title),1)])):s("",!0),_(a.$slots,"default",{},void 0,!0)])}const x=l(d,[["render",S],["__scopeId","data-v-543822ed"],["__file","contribution-header.vue"]]);export{x as default};
diff --git a/assets/contribution-listentry-CYemCd1W.js b/assets/contribution-listentry-CYemCd1W.js
new file mode 100644
index 000000000..33680c564
--- /dev/null
+++ b/assets/contribution-listentry-CYemCd1W.js
@@ -0,0 +1 @@
+import{_ as n,o as s,c as o,a as c,t as i}from"./app-CRZRGfEE.js";const _={__name:"contribution-listentry",props:{title:String,url:String},setup(r,{expose:t}){t();const e={};return Object.defineProperty(e,"__isScriptSetup",{enumerable:!1,value:!0}),e}},a=["href"];function l(r,t,e,u,p,d){return s(),o("a",{class:"entry",href:e.url},[c("div",null,i(e.title),1)],8,a)}const b=n(_,[["render",l],["__scopeId","data-v-bcc3d6f6"],["__file","contribution-listentry.vue"]]);export{b as default};
diff --git a/assets/contribution-preview-BGt1xWap.js b/assets/contribution-preview-BGt1xWap.js
new file mode 100644
index 000000000..77a3dc586
--- /dev/null
+++ b/assets/contribution-preview-BGt1xWap.js
@@ -0,0 +1 @@
+import{_ as s,o as c,c as a,a as i,t as l,f as _}from"./app-CRZRGfEE.js";const p={__name:"contribution-preview",props:{title:String,pageUrl:String},setup(t,{expose:r}){r();const e=t;function o(){window.location.href=e.pageUrl}const n={props:e,onClick:o};return Object.defineProperty(n,"__isScriptSetup",{enumerable:!1,value:!0}),n}},d={class:"title"},u={class:"content"};function f(t,r,e,o,n,v){return c(),a("div",{class:"preview",onClick:o.onClick},[i("div",d,l(e.title),1),i("div",u,[_(t.$slots,"default",{},void 0,!0)])])}const m=s(p,[["render",f],["__scopeId","data-v-f801cb6e"],["__file","contribution-preview.vue"]]);export{m as default};
diff --git a/assets/contributions-author-BSwyEtB9.js b/assets/contributions-author-BSwyEtB9.js
new file mode 100644
index 000000000..20e8b645a
--- /dev/null
+++ b/assets/contributions-author-BSwyEtB9.js
@@ -0,0 +1 @@
+import{_ as a,r as u,o as n,c as i,g as l,b as c,a as r,f as _,F as d}from"./app-CRZRGfEE.js";const f={__name:"contributions-author",props:{name:String,url:String,githubUrl:String,profileImage:String,overviewLink:String},setup(o,{expose:t}){t();const e={};return Object.defineProperty(e,"__isScriptSetup",{enumerable:!1,value:!0}),e}},g=["href"],h={class:"previews"};function m(o,t,e,p,v,b){const s=u("contribution-header");return n(),i(d,null,[e.overviewLink?(n(),i("a",{key:0,href:e.overviewLink,class:"overview-link"},"โ Overview",8,g)):l("",!0),c(s,{profileImage:e.profileImage,author:e.name,githubUrl:e.githubUrl},null,8,["profileImage","author","githubUrl"]),r("div",h,[_(o.$slots,"default")]),t[0]||(t[0]=r("div",{class:"footer"},[r("a",{href:"https://github.com/needle-tools/needle-engine-support/discussions/new?category=share"},"Add your contribution")],-1))],64)}const k=a(f,[["render",m],["__file","contributions-author.vue"]]);export{k as default};
diff --git a/assets/contributions-overview-D_rfeOIs.js b/assets/contributions-overview-D_rfeOIs.js
new file mode 100644
index 000000000..322f5121b
--- /dev/null
+++ b/assets/contributions-overview-D_rfeOIs.js
@@ -0,0 +1 @@
+import{_ as o,o as t,c as s,f as r}from"./app-CRZRGfEE.js";const c={},n={class:"list"};function i(e,_){return t(),s("div",n,[r(e.$slots,"default")])}const l=o(c,[["render",i],["__file","contributions-overview.vue"]]);export{l as default};
diff --git a/assets/contributions.html-DddtFv1E.js b/assets/contributions.html-DddtFv1E.js
new file mode 100644
index 000000000..fd6d36591
--- /dev/null
+++ b/assets/contributions.html-DddtFv1E.js
@@ -0,0 +1 @@
+import{_ as c,r,o as l,c as u,a,d as m,b as t,w as o}from"./app-CRZRGfEE.js";const d={};function b(p,n){const e=r("contribution-listentry"),i=r("contribution-header"),s=r("contributions-overview");return l(),u("div",null,[n[0]||(n[0]=a("h1",null,"Community Scripts",-1)),n[1]||(n[1]=a("p",null,[m("To contribute a script, please create a new discussion in the "),a("a",{href:"https://github.com/needle-tools/needle-engine-support/discussions/categories/share",target:"_blank",rel:"noopener noreferrer"},"Share category")],-1)),t(s,null,{default:o(()=>[t(i,{url:"https://github.com/llllkatjallll",author:"llllkatjallll",page:"/docs/community/contributions/llllkatjallll",profileImage:"https://avatars.githubusercontent.com/u/38395689?s=100&u=7ce0fef973c4819c4f07823568d6f6061abfe410&v=4"},{default:o(()=>[t(e,{title:"Custom VR Button, that appears only on headsets and not on mobile phones",url:"/docs/community/contributions/llllkatjallll/custom-vr-button-that-appears-only-on-headsets-and-not-on-mobile-phones"}),t(e,{title:"Set fallback material for USDZ exporter",url:"/docs/community/contributions/llllkatjallll/set-fallback-material-for-usdz-exporter"})]),_:1}),t(i,{url:"https://github.com/ROBYER1",author:"ROBYER1",page:"/docs/community/contributions/robyer1",profileImage:"https://avatars.githubusercontent.com/u/10745594?s=100&u=daf2c8b5dad729e556ae2a01c721672b24bc108a&v=4"},{default:o(()=>[t(e,{title:"Microphone access in a browser window (and streamed playback)",url:"/docs/community/contributions/robyer1/microphone-access-in-a-browser-window-and-streamed-playback"}),t(e,{title:"AR Move/Scale/Rotate Controls for Needle on Mobile",url:"/docs/community/contributions/robyer1/ar-move-scale-rotate-controls-for-needle-on-mobile"})]),_:1}),t(i,{url:"https://github.com/ericcraft-mh",author:"ericcraft-mh",page:"/docs/community/contributions/ericcraft-mh",profileImage:"https://avatars.githubusercontent.com/u/99364056?s=100&v=4"},{default:o(()=>[t(e,{title:"QuickLook Vertical Image Tracker",url:"/docs/community/contributions/ericcraft-mh/quicklook-vertical-image-tracker"})]),_:1}),t(i,{url:"https://github.com/krisrok",author:"krisrok",page:"/docs/community/contributions/krisrok",profileImage:"https://avatars.githubusercontent.com/u/3404365?s=100&u=7025bf7e83b4a3cd72dc2cae9cec729080ee8970&v=4"},{default:o(()=>[t(e,{title:"Always open in specific browser",url:"/docs/community/contributions/krisrok/always-open-in-specific-browser"})]),_:1}),t(i,{url:"https://github.com/marwie",author:"marwie",page:"/docs/community/contributions/marwie",profileImage:"https://avatars.githubusercontent.com/u/5083203?s=100&v=4"},{default:o(()=>[t(e,{title:"Camera Video Background",url:"/docs/community/contributions/marwie/camera-video-background"}),t(e,{title:"USDZ: Hide Object on Start",url:"/docs/community/contributions/marwie/usdz-hide-object-on-start"}),t(e,{title:"Everywhere Action: Emphasize on Click",url:"/docs/community/contributions/marwie/everywhere-action-emphasize-on-click"}),t(e,{title:"Control a Timeline by scroll",url:"/docs/community/contributions/marwie/control-a-timeline-by-scroll"}),t(e,{title:"Code Contribution Example",url:"/docs/community/contributions/marwie/code-contribution-example"})]),_:1}),t(i,{url:"https://github.com/kipash",author:"kipash",page:"/docs/community/contributions/kipash",profileImage:"https://avatars.githubusercontent.com/u/30328735?s=100&u=f28398f4575da1835d1c710d14763c69418cd0fa&v=4"},{default:o(()=>[t(e,{title:"Calculate pointer world position",url:"/docs/community/contributions/kipash/calculate-pointer-world-position"})]),_:1}),t(i,{url:"https://github.com/Web3Kev",author:"Web3Kev",page:"/docs/community/contributions/web3kev",profileImage:"https://avatars.githubusercontent.com/u/106066970?s=100&v=4"},{default:o(()=>[t(e,{title:"Vertical Move in VR using the right joystick (Quest)",url:"/docs/community/contributions/web3kev/vertical-move-in-vr-using-the-right-joystick-quest"}),t(e,{title:"Squeeze to Scale (Object or World) in VR",url:"/docs/community/contributions/web3kev/squeeze-to-scale-object-or-world-in-vr"}),t(e,{title:"Network instantiation of multiple objects",url:"/docs/community/contributions/web3kev/network-instantiation-of-multiple-objects"})]),_:1})]),_:1})])}const f=c(d,[["render",b],["__file","contributions.html.vue"]]),y=JSON.parse('{"path":"/community/contributions","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/community: contributions.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{f as comp,y as data};
diff --git a/assets/control-a-timeline-by-scroll.html-GAfS3g38.js b/assets/control-a-timeline-by-scroll.html-GAfS3g38.js
new file mode 100644
index 000000000..3ff733dbe
--- /dev/null
+++ b/assets/control-a-timeline-by-scroll.html-GAfS3g38.js
@@ -0,0 +1,54 @@
+import{_ as t,r as n,o as h,c as l,a as s,b as k,e}from"./app-CRZRGfEE.js";const p={};function r(d,i){const a=n("contribution-header");return h(),l("div",null,[i[0]||(i[0]=s("p",null,[s("a",{href:"/docs/community/contributions"},"Overview")],-1)),k(a,{url:"https://github.com/marwie",author:"marwie",page:"/docs/community/contributions/marwie",profileImage:"https://avatars.githubusercontent.com/u/5083203?s=100&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/155",title:"Control a Timeline by scroll",gradient:"True"}),i[1]||(i[1]=e(`
Use the mouse wheel or touch delta to update a timeline's time.
import { Behaviour, PlayableDirector, serializeable } from "@needle-tools/engine";
+import { Mathf } from "@needle-tools/engine";
+
+// Example of setting a timeline's time
+// without relying on any HTML elements.
+// Here we directly use the mousewheel scroll and the touch delta
+
+export class ScrollTimeline_2 extends Behaviour {
+
+ @serializeable(PlayableDirector)
+ timeline?: PlayableDirector;
+
+ @serializeable()
+ scrollSpeed: number = 0.5;
+
+ @serializeable()
+ lerpSpeed: number = 2.5;
+
+ private targetTime: number = 0;
+
+ start() {
+
+ this.timeline?.pause();
+
+ // Grab the mousewheel event
+ window.addEventListener("wheel", (evt: WheelEvent) => this.updateTime(evt.deltaY));
+
+ // Touch events are a bit more complicated
+ // We need to keep track of the last touch position
+ // and calculate the delta between the current and the last position
+ let lastTouchPosition = -1;
+ window.addEventListener("touchmove", (evt: TouchEvent) => {
+ const delta = evt.touches[0].clientY - lastTouchPosition;
+ // We only want to apply the delta if it's not TOO big
+ // e.g. when the user is scrolling the page
+ if (delta < 10) this.updateTime(-delta);
+ // Update the last touch position
+ lastTouchPosition = evt.touches[0].clientY;
+ });
+ }
+
+ private updateTime(delta) {
+ if (!this.timeline) return;
+ this.targetTime += delta * 0.01 * this.scrollSpeed;
+ this.targetTime = Mathf.clamp(this.targetTime, 0, this.timeline.duration);
+ }
+
+ onBeforeRender(): void {
+ if (!this.timeline) return;
+ this.timeline.pause();
+ this.timeline.time = Mathf.lerp(this.timeline.time, this.targetTime, this.lerpSpeed * this.context.time.deltaTime);
+ this.timeline.evaluate();
+ }
+}
`,2))])}const C=t(p,[["render",r],["__file","control-a-timeline-by-scroll.html.vue"]]),g=JSON.parse('{"path":"/community/contributions/marwie/control-a-timeline-by-scroll","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/marwie: control a timeline by scroll.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{C as comp,g as data};
diff --git a/assets/copyright-ClhS8G3R.js b/assets/copyright-ClhS8G3R.js
new file mode 100644
index 000000000..da7abbb8b
--- /dev/null
+++ b/assets/copyright-ClhS8G3R.js
@@ -0,0 +1 @@
+import{_ as o,o as a,c as s,d as c,a as t}from"./app-CRZRGfEE.js";const n={},r={class:"footer"};function l(i,e,d,p,_,f){return a(),s("div",r,e[0]||(e[0]=[c(" ยฉ2024 Needle Tools GmbH ยท "),t("a",{class:"no-external-link-icon",target:"_blank",href:"https://needle.tools/contact"},"About ยท ",-1),t("a",{class:"no-external-link-icon",target:"_blank",href:"https://needle.tools/contact#privacy-policy"},"Privacy Policy",-1)]))}const h=o(n,[["render",l],["__scopeId","data-v-66df4b0b"],["__file","copyright.vue"]]);export{h as default};
diff --git a/assets/custom-loading-style-s1K1my2z.js b/assets/custom-loading-style-s1K1my2z.js
new file mode 100644
index 000000000..7aab91972
--- /dev/null
+++ b/assets/custom-loading-style-s1K1my2z.js
@@ -0,0 +1 @@
+const s="/docs/imgs/custom-loading-style.webp";export{s as _};
diff --git a/assets/custom-vr-button-that-appears-only-on-headsets-and-not-on-mobile-phones.html-Cyk2JJNy.js b/assets/custom-vr-button-that-appears-only-on-headsets-and-not-on-mobile-phones.html-Cyk2JJNy.js
new file mode 100644
index 000000000..017cfc892
--- /dev/null
+++ b/assets/custom-vr-button-that-appears-only-on-headsets-and-not-on-mobile-phones.html-Cyk2JJNy.js
@@ -0,0 +1,15 @@
+import{_ as t,r as n,o as e,c as l,a as i,b as h,e as k}from"./app-CRZRGfEE.js";const p={};function o(r,s){const a=n("contribution-header");return e(),l("div",null,[s[0]||(s[0]=i("p",null,[i("a",{href:"/docs/community/contributions"},"Overview")],-1)),h(a,{url:"https://github.com/llllkatjallll",author:"llllkatjallll",page:"/docs/community/contributions/llllkatjallll",profileImage:"https://avatars.githubusercontent.com/u/38395689?s=100&u=7ce0fef973c4819c4f07823568d6f6061abfe410&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/198",title:"Custom VR Button, that appears only on headsets and not on mobile phones",gradient:"True"}),s[1]||(s[1]=k(`
I combined two checks - Needle's check to see if it's a mobile device (this way, you can exclude desktops), and then a second check that uses user agent info to see if it's one of the most common mobile systems. Result: the button does not appear on mobile phones, but it is visible on Quest and Pico ๐
P.S: I am using Svelte for 2D UI handling.
import { isMobileDevice, NeedleXRSession } from "@needle-tools/engine";
+
+...
+
+// check if this is a mobile phone
+function isMobilePhone() {
+ return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
+}
+
+...
+
+// show the button, if the device is not a mobile phone and VR is supported
+{#if isMobileDevice() && !isMobilePhone() && $haveVR }
+ <VrButton buttonFunction={() => StartVR()} />
+{/if}
`,3))])}const g=t(p,[["render",o],["__file","custom-vr-button-that-appears-only-on-headsets-and-not-on-mobile-phones.html.vue"]]),c=JSON.parse('{"path":"/community/contributions/llllkatjallll/custom-vr-button-that-appears-only-on-headsets-and-not-on-mobile-phones","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/llllkatjallll: custom vr button that appears only on headsets and not on mobile phones.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{g as comp,c as data};
diff --git a/assets/debugging.html-x99TIVp3.js b/assets/debugging.html-x99TIVp3.js
new file mode 100644
index 000000000..ec54d6a96
--- /dev/null
+++ b/assets/debugging.html-x99TIVp3.js
@@ -0,0 +1,12 @@
+import{_ as n,r as l,o,c as r,e as s,a,d as i,b as h,w as d}from"./app-CRZRGfEE.js";const p="/docs/debugging/vscode-start-debugging.webp",g={};function u(c,e){const t=l("RouteLink");return o(),r("div",null,[e[5]||(e[5]=s('
',10)),a("p",null,[e[1]||(e[1]=i("Needle Engine also has some very powerful and useful debugging methods that are part of the static ")),e[2]||(e[2]=a("code",null,"Gizmos",-1)),e[3]||(e[3]=i(" class. See the ")),h(t,{to:"/scripting.html#gizmos"},{default:d(()=>e[0]||(e[0]=[i("scripting documentation")])),_:1}),e[4]||(e[4]=i(" for more information."))]),e[6]||(e[6]=s(`
If you have changed the port on which your server starts make sure to update the url field accordingly. You can then start your local server from within VSCode:
For Android debugging, you can attach Chrome Dev Tools to your device and see logs right from your PC. You have to switch your device into development mode and connect it via USB.
Quest is just an Android device - see the Android Debugging section for steps.
',19))])}const b=n(g,[["render",u],["__file","debugging.html.vue"]]),f=JSON.parse('{"path":"/debugging.html","title":"How To Debug","lang":"en-US","frontmatter":{"title":"How To Debug","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/how to debug.png"}],["meta",{"name":"og:description","content":"---\\nTo inspect glTF or glb files online:"}]],"description":"---\\nTo inspect glTF or glb files online:"},"headers":[{"level":2,"title":"Useful resources for working with glTF","slug":"useful-resources-for-working-with-gltf","link":"#useful-resources-for-working-with-gltf","children":[]},{"level":2,"title":"Built-in URL parameters","slug":"built-in-url-parameters","link":"#built-in-url-parameters","children":[]},{"level":2,"title":"Debug Methods","slug":"debug-methods","link":"#debug-methods","children":[]},{"level":2,"title":"Local Testing of release builds","slug":"local-testing-of-release-builds","link":"#local-testing-of-release-builds","children":[]},{"level":2,"title":"VSCode","slug":"vscode","link":"#vscode","children":[]},{"level":2,"title":"Mobile","slug":"mobile","link":"#mobile","children":[{"level":3,"title":"Android Debugging","slug":"android-debugging","link":"#android-debugging","children":[]},{"level":3,"title":"iOS Debugging","slug":"ios-debugging","link":"#ios-debugging","children":[]},{"level":3,"title":"Quest Debugging","slug":"quest-debugging","link":"#quest-debugging","children":[]}]}],"git":{"updatedTime":1700724672000},"filePathRelative":"debugging.md"}');export{b as comp,f as data};
diff --git a/assets/deployment.html-C3wlSpNe.js b/assets/deployment.html-C3wlSpNe.js
new file mode 100644
index 000000000..d4e8d7ada
--- /dev/null
+++ b/assets/deployment.html-C3wlSpNe.js
@@ -0,0 +1,4 @@
+import{_ as a}from"./texture-compression-BuEaeBZn.js";import{_ as s}from"./ktx-env-variable-DxwKzzNo.js";import{_ as r,r as l,o as d,c as p,e as o,a as t,d as c,b as h}from"./app-CRZRGfEE.js";const i="/docs/imgs/unity-texture-compression.jpg",m="/docs/imgs/unity-texture-compression-options.jpg",u="/docs/imgs/unity-mesh-compression-component.jpg",g="/docs/imgs/unity-mesh-simplification.jpg",y="/docs/imgs/unity-progressive-textures.jpg",b="/docs/imgs/unity-lods-settings-1.jpg",f="/docs/imgs/unity-lods-settings-2.jpg",w="/docs/deployment/deploytoglitch-1.jpg",v="/docs/deployment/deploytoglitch-2.jpg",k="/docs/blender/deploy_to_glitch.webp",x="/docs/deployment/deploytonetlify-2.jpg",T="/docs/deployment/deploytonetlify.jpg",_="/docs/deployment/deploytoftp.jpg",j="/docs/deployment/deploytoftp2.jpg",D="/docs/deployment/deploytoftp3.jpg",P="/docs/deployment/buildoptions_gzip.jpg",F="/docs/deployment/deploytogithubpages.jpg",G="/docs/deployment/deploytofacebookinstantgames.jpg",I="/docs/deployment/deploytofacebookinstantgames-hosting.jpg",U="/docs/deployment/deploytofacebookinstantgames-upload.jpg",N="/docs/deployment/facebookinstantgames-1.jpg",B="/docs/deployment/facebookinstantgames-2.jpg",C="/docs/deployment/facebookinstantgames-3.jpg",S="/docs/imgs/unity-build-window-menu.jpg",O="/docs/imgs/unity-build-window.jpg",A={},z={class:"hint-container details"};function E(M,e){const n=l("video-embed");return d(),p("div",null,[e[2]||(e[2]=o('
Deployment is the process of making your application available to the public on a website. Needle Engine ensures that your project is as small and fast as possible by using the latest compression techniques such as KTX2, Draco, and Meshopt.
See guides above on how to access the options from within your Editor (e.g. Unity or Blender).
The main difference to a production build is that it does not perform ktx2 and draco compression (for reduction of file size and loading speed) as well as the option to progressively load high-quality textures.
We generally recommend making production builds for optimized file size and loading speed (see more information below).
To make a production build, you need to have toktx installed, which provides texture compression using the KTX2 supercompression format. Please go to the toktx Releases Page and download and install the latest version (v4.1.0 at the time of writing). You may need to restart Unity after installing it. If you're sure that you have installed toktx and it's part of your PATH but still can't be found, please restart your machine and try build again.
Advanced: Custom glTF extensions
If you plan on adding your own custom glTF extensions, building for production requires handling those in gltf-transform. See @needle-tools/gltf-build-pipeline for reference.
Production builds will by default compress textures using KTX2 (either ETC1S or UASTC depending on their usage in the project) but you can also select WebP compression and select a quality level.
High-detail data textures: normal maps, roughness, metallic, etc.
Files where ETC1S quality is not sufficient but UASTC is too large
You have the option to select texture compression and progressive loading options per Texture by using the Needle Texture Importer in Unity or in the Material tab in Blender.
Unity: How can I set per-texture compression settings?
Blender: How can I set per-texture compression settings?
Select the material tab. You will see compression options for all textures that are being used by that material.
Toktx can not be found
Windows: Make sure you have added toktx to your system environment variables. You may need to restart your computer after adding it to refresh the environment variables. The default install location is C:\\Program Files\\KTX-Software\\bin
By default, a production build will compress meshes using Draco compression. Use the MeshCompression component to select between draco and mesh-opt per exported glTF. Additionally you can setup mesh simplification to reduce the polycount for production builds in the mesh import settings (Unity). When viewing your application in the browser, you can append ?wireframe to your URL to preview the meshes.
You can also add the Progressive Texture Settings component anywhere in your scene, to make all textures in your project be progressively loaded. Progressive loading is not applied to lightmaps or skybox textures at this point.
With progressive loading textures will first be loaded using a lower resolution version. A full quality version will be loaded dynamically when the texture becomes visible. This usually reduces initial loading of your scene significantly.
Since Needle Engine 3.36 we automatically generate LOD meshes and switch between them at runtime. LODs are loaded on demand and only when needed so so this feature both reduces your loading time as well as performance.
Key Beneftis
Faster initial loading time
Faster rendering time due to less vertices on screen on average
Faster raycasting due to the use of LOD meshes
You can either disable LOD generation for your whole project in the Progressive Loading Settings component or in the Mesh Importer settings.
Glitch provides a fast and free way for everyone to host small and large websites. We're providing an easy way to remix and deploy to a new Glitch page (based on our starter), and also to run a minimalistic networking server on the same Glitch page if needed.
You can deploy to glitch by adding the DeployToGlitch component to your scene and following the instructions.
Note that free projects hosted on glitch may not exceed ~100 MB. If you need to upload a larger project consider using a different deployment target.
How do I deploy to Glitch from Unity?
Add the DeployToGlitch component to the GameObject that also has the ExportInfo component.
Click the Create new Glitch Remix button on the component
Glitch will now create a remix of the template. Copy the URL from your browser
Open Unity again and paste the URL in the Project Name field of your Deploy To Glitch component
Wait a few seconds until Unity has received your deployment key from glitch (this key is safely stored in the .env file on glitch. Do not share it with others, everyone with this key will be able to upload to your glitch website)
Once the Deploy Key has been received you can click the Build & Deploy button to upload to glitch.
How do I deploy to Glitch from Blender?
Find the Deploy To Glitch panel in the Scene tab
Click the Remix on glitch button on the component
Your browser will open the glitch project template
Wait for Glitch to generate a new project
Copy paste the project URL in the Blender DeployToGlitch panel as the project name (you can paste the full URL, the panel will extract the necessary information)
On Glitch open the .env file and enter a password in the field Variable Value next to the DEPLOY_KEY
Enter the same password in Blender in the Key field
Click the DeployToGlitch button to build and upload your project to glitch. A browser will open when the upload has finished. Try to refresh the page if it shows black after having opened it.
If you click Create new Glitch Remix and the browser shows an error like there was an error starting the editor you can click OK. Then go to glitch.com and make sure you are signed in. After that you then try clicking the button again in Unity or Blender.
Just add the DeployToNetlify component to your scene and follow the instructions. You can create new projects with the click of a button or by deploying to existing projects.
If you see this error after uploading your project make sure you do not upload a gzipped index.html. You can disable gzip compression in vite.config.js in your Needle web project folder. Just remove the line with viteCompression({ deleteOriginFile: true }). The build your project again and upload to itch.
Add the DeployToFTP componentยน on a GameObject in your scene (it is good practice to add it to the same GameObject as ExportInfo - but it is not mandatory)
Assign an FTP server asset and fill out server, username, and password if you have not already ยฒ This asset contains the access information to your FTP server - you get them when you create a new FTP account at your hosting provider
Click the Build & Deploy button on the DeployToFTP component to build your project and uploading it to your FTP account
ยน Deploy to FTP component
ยฒ FTP Server asset containing the access information of your FTP user account
Deploy To FTP component after server asset is assigned. You can directly deploy to a subfolder on your server using the path field
How do I deploy to my FTP server manually?
Open File > Build Settings, select Needle Engine, and click on Build
Wait for the build to complete - the resulting dist folder will open automatically after all build and compression steps have run.
Copy the files from the dist folder to your FTP storage.
That's it! ๐
Note: If the result doesn't work when uploaded it might be that your web server does not support serving gzipped files. You have two options to fix the problem: Option 1: You can try enabling gzip compression on your server using a htaccess file! Option 2: You can turn gzip compression off in the build settings at File/Build Window and selecting the Needle Engine platform.
Note: If you're getting errors during compression, please let us know and report a bug! If your project works locally and only fails when doing production builds, you can get unstuck right away by doing a Development Build. For that, simply toggle Development Build on in the Build Settings.
To enable gzip compression on your FTP server you can create a file named .htaccess in the directory you want to upload to (or a parent directory). Insert the following code into your .htaccess file and save/upload it to your server:
`,62)),t("details",z,[e[0]||(e[0]=t("summary",null,"How do I deploy to Github Pages from Unity?",-1)),e[1]||(e[1]=t("p",null,[c("Add the DeployToGithubPages component to your scene and copy-paste the github repository (or github pages url) that you want to deploy to."),t("br"),t("img",{src:F,alt:"Deploy To github pages component"})],-1)),h(n,{src:"https://www.youtube.com/watch?v=Vyk3cWB6u-c"})]),e[3]||(e[3]=o('
With Needle Engine you can build to Facebook Instant Games automatically No manual adjustments to your web app or game are required.
How do I deploy to Facebook Instant Games from Unity?
Add the Deploy To Facebook Instant Games component to your scene:
Click the Build For Instant Games button
After the build has finished you will get a ZIP file that you can upload to your facebook app.
On Facebook add the Instant Games module and go to Instant Games/Web hosting
You can upload your zip using the Upload version button (1). After the upload has finished and the zip has been processed click the Stage for testing button to test your app (2, here the blue button) or Push to production (the button with the star icon)
That's it - you can then click the Play button next to each version to test your game on facebook.
How do I create a app on Facebook (with Instant Games capabilities)
After creating the app add the Instant Games product
Here you can find the official instant games documentation on facebook. Note that all you have to do is to create an app with instant games capabilities. We will take care of everything else and no manual adjustments to your Needle Engine website are required.
In Unity open File/Build Settings and select Needle Engine for options:
To build your web project for uploading to any web server you can click Build in the Unity Editor Build Settings Window. You can enable the Development Build checkbox to omit compression (see below) which requires toktx to be installed on your machine.
To locally preview your final build you can use the Preview Build button at the bottom of the window. This button will first perform a regular build and then start a local server in the directory with the final files so you can see what you get once you upload these files to your webserver.
Nodejs is only required during development. The distributed website (using our default vite template) is a static page that doesn't rely on Nodejs and can be put on any regular web server. Nodejs is required if you want to run our minimalistic networking server on the same web server (automatically contained in the Glitch deployment process).
It's possible to create regular Unity projects where you can build both to Needle Engine and to regular Unity platforms such as Desktop or even WebGL. Our "component mapping" approach means that no runtime logic is modified inside Unity - if you want you can regularily use Play Mode and build to other target platforms. In some cases this will mean that you have duplicate code (C# code and matching TypeScript logic). The amount of extra work through this depends on your project.
Enter Play Mode in Unity In Project Settings > Needle Engine, you can turn off Override Play Mode and Override Build settings to switch between Needle's build process and Unity's build process:
Needle Engine for Unity supports various commandline arguments to export single assets (Prefabs or Scenes) or to build a whole web project in batch mode (windowsless).
The following list gives a table over the available options:
-scene
path to a scene or a asset to be exported e.g. Assets/path/to/myObject.prefab or Assets/path/to/myScene.unity
-outputPath <path/to/output.glb>
set the output path for the build (only valid when building a scene)
-buildProduction
run a production build
-buildDevelopment
run a development build
-debug
open a console window for debugging
',19))])}const q=r(A,[["render",E],["__file","deployment.html.vue"]]),Y=JSON.parse('{"path":"/deployment.html","title":"Deployment and Optimization","lang":"en-US","frontmatter":{"title":"Deployment and Optimization","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/deployment and optimization.png"}],["meta",{"name":"og:description","content":"---\\nDeployment is the process of making your application available to the public on a website. Needle Engine ensures that your project is as small and fast as possible by using the latest compression techniques such as KTX2, Draco, and Meshopt."}]],"description":"---\\nDeployment is the process of making your application available to the public on a website. Needle Engine ensures that your project is as small and fast as possible by using the latest compression techniques such as KTX2, Draco, and Meshopt."},"headers":[{"level":2,"title":"What does deployment mean?","slug":"what-does-deployment-mean","link":"#what-does-deployment-mean","children":[]},{"level":2,"title":"Available Deployment Targets","slug":"available-deployment-targets","link":"#available-deployment-targets","children":[]},{"level":2,"title":"Development Builds","slug":"development-builds","link":"#development-builds","children":[]},{"level":2,"title":"Production Builds","slug":"production-builds","link":"#production-builds","children":[{"level":3,"title":"Optimization and Compression Options","slug":"optimization-and-compression-options","link":"#optimization-and-compression-options","children":[]},{"level":3,"title":"Texture compression","slug":"texture-compression","link":"#texture-compression","children":[]},{"level":3,"title":"Mesh compression","slug":"mesh-compression","link":"#mesh-compression","children":[]},{"level":3,"title":"Progressive Textures","slug":"progressive-textures","link":"#progressive-textures","children":[]},{"level":3,"title":"Automatic Mesh LODs (Level of Detail)","slug":"automatic-mesh-lods-level-of-detail","link":"#automatic-mesh-lods-level-of-detail","children":[]}]},{"level":2,"title":"Deployment Options","slug":"deployment-options","link":"#deployment-options","children":[{"level":3,"title":"Deploy to Glitch ๐","slug":"deploy-to-glitch","link":"#deploy-to-glitch","children":[]},{"level":3,"title":"Deploy to Netlify","slug":"deploy-to-netlify","link":"#deploy-to-netlify","children":[]},{"level":3,"title":"Deploy to Vercel","slug":"deploy-to-vercel","link":"#deploy-to-vercel","children":[]},{"level":3,"title":"Deploy to itch.io","slug":"deploy-to-itch.io","link":"#deploy-to-itch.io","children":[]},{"level":3,"title":"Deploy to FTP","slug":"deploy-to-ftp","link":"#deploy-to-ftp","children":[]},{"level":3,"title":"Deploy to Github Pages","slug":"deploy-to-github-pages","link":"#deploy-to-github-pages","children":[]},{"level":3,"title":"Deploy to Facebook Instant Games","slug":"deploy-to-facebook-instant-games","link":"#deploy-to-facebook-instant-games","children":[]}]},{"level":2,"title":"Build To Folder","slug":"build-to-folder","link":"#build-to-folder","children":[]},{"level":2,"title":"Cross-Platform Deployment Workflows","slug":"cross-platform-deployment-workflows","link":"#cross-platform-deployment-workflows","children":[]},{"level":2,"title":"Needle Engine Commandline Arguments for Unity","slug":"needle-engine-commandline-arguments-for-unity","link":"#needle-engine-commandline-arguments-for-unity","children":[]}],"git":{"updatedTime":1725399379000},"filePathRelative":"deployment.md"}');export{q as comp,Y as data};
diff --git a/assets/editor-sync.html-B1ATYa5k.js b/assets/editor-sync.html-B1ATYa5k.js
new file mode 100644
index 000000000..1f6319657
--- /dev/null
+++ b/assets/editor-sync.html-B1ATYa5k.js
@@ -0,0 +1 @@
+import{_ as o,r as n,o as a,c as r,e as i,b as s}from"./app-CRZRGfEE.js";const d={};function l(c,e){const t=n("video-embed");return a(),r("div",null,[e[0]||(e[0]=i('
Needle allows for a very fast, iterative workflow between Unity and the browser. Usually, exports take less than a few seconds. However, once scenes become more complex, for some types of changes (adjusting material properties, nudging objects around), we provide an even faster way to see your changes โ Editor Sync.
You can enable Editor Sync by adding the EditorSync component to your scene. This component will connect your Unity Editor with your browser project and automatically sync applicable changes between the two.
Tips
Editor Sync is currently an experimental feature. Please let us know about your experience with it! We're eager to hear your feedback.
This tutorial shows the Editor Sync workflow in action:
',7)),s(t,{src:"https://www.youtube.com/watch?v=gZX_sqrne8U",limit_height:""})])}const p=o(d,[["render",l],["__file","editor-sync.html.vue"]]),u=JSON.parse('{"path":"/unity/editor-sync.html","title":"Editor Sync","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/unity: editor sync.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[{"level":2,"title":"How to use Editor Sync","slug":"how-to-use-editor-sync","link":"#how-to-use-editor-sync","children":[]},{"level":2,"title":"Video Tutorial","slug":"video-tutorial","link":"#video-tutorial","children":[]}],"git":{"updatedTime":1727106484000},"filePathRelative":"unity/editor-sync.md"}');export{p as comp,u as data};
diff --git a/assets/embedding.html-Bwm82YNo.js b/assets/embedding.html-Bwm82YNo.js
new file mode 100644
index 000000000..080c44d32
--- /dev/null
+++ b/assets/embedding.html-Bwm82YNo.js
@@ -0,0 +1,16 @@
+import{_ as p,r as l,o as r,c,e as o,b as t,w as a,a as e,d as s}from"./app-CRZRGfEE.js";const k={};function u(g,i){const n=l("CodeGroupItem"),h=l("CodeGroup"),d=l("RouteLink");return r(),c("div",null,[i[6]||(i[6]=o('
Needle Engine can be used to create new web apps, and can also be integrated into existing websites. In both cases, you'll want to upload your project's distribution folder to a web hoster to make them accessible to the world.
There are several ways to integrate Needle Engine with your website. Which one is better depends on a number of factors, like complexity of your project, if you're using custom scripts or only core components, how much control you have over the target website, what the "trust level" is between you and the target website, and so on.
If you want to quickly try out how projects made with Needle will look on your website, just add these two lines anywhere on your page for testing:
',7)),t(h,null,{default:a(()=>[t(n,{title:"Embedding Needle"},{default:a(()=>i[0]||(i[0]=[e("div",{class:"language-html","data-highlighter":"shiki","data-ext":"html","data-title":"html",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[e("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),e("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE"}},"script"),e("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890"}}," type"),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),e("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"module"'),e("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890"}}," src"),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),e("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"https://unpkg.com/@needle-tools/engine/dist/needle-engine.min.js"'),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},">"),e("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"<"),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"/"),e("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE"}},"script"),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},">")]),s(`
+`),e("span",{class:"line"},[e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),e("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE"}},"needle-engine"),e("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890"}}," src"),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),e("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"https://cloud.needle.tools/api/v1/public/873a48a/10801b111/MusicalInstrument.glb"'),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},">"),e("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE"}},"needle-engine"),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},">")])])])],-1)])),_:1}),t(n,{title:"Using an iframe"},{default:a(()=>i[1]||(i[1]=[e("div",{class:"language-html","data-highlighter":"shiki","data-ext":"html","data-title":"html",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[e("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),e("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE"}},"iframe"),e("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890"}}," src"),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),e("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"https://engine.needle.tools/samples-uploads/musical-instrument/"')]),s(`
+`),e("span",{class:"line"},[e("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890"}}," allow"),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),e("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"xr; xr-spatial-tracking; fullscreen;"'),e("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890"}}," width"),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),e("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"100%"'),e("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890"}}," height"),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),e("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"500px"'),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},">")]),s(`
+`),e("span",{class:"line"},[e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},""),e("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE"}},"iframe"),e("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},">")])])])],-1)])),_:1}),t(n,{title:"Result"},{default:a(()=>i[2]||(i[2]=[e("iframe",{src:"https://engine.needle.tools/samples-uploads/musical-instrument/",allow:"xr; xr-spatial-tracking; fullscreen;",width:"100%",height:"500px",style:{border:"0",outline:"0"}},null,-1)])),_:1})]),_:1}),i[7]||(i[7]=e("h2",{id:"using-the-deploy-to-...-components",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#using-the-deploy-to-...-components"},[e("span",null,'Using the "Deploy to ..." components')])],-1)),i[8]||(i[8]=e("p",null,"Our Needle Engine integrations ship with built-in deployment options. You can deploy your project to Needle Cloud, FTP servers, Glitch, Itch.io, GitHub Pages, and more with just a few clicks.",-1)),e("p",null,[i[4]||(i[4]=s("See the ")),t(d,{to:"/deployment.html"},{default:a(()=>i[3]||(i[3]=[s("Deployment")])),_:1}),i[5]||(i[5]=s(" section for more information on each of these options."))]),i[9]||(i[9]=o(`
Add the "Deploy to ..." component you want to use to your scene in Unity or Blender.
Configure the necessary options and click on "Deploy".
That's it! Your project is now live.
Recommended Workflow
This is the easiest option, and recommended for most workflows โ it's very fast! You can iteratively work on your project on your computer, and then upload a new version to the web in seconds.
If you don't want to use our "Deploy to..." components, or there's no component for your particlar workflow, you can do the same process manually. The resulting web app will be identical to what you see in your local server while working on the project.
Make a production build of your web project. This will create a dist/ folder with all necessary files, ready for distribution. It contains all necessary files, including the JavaScript bundle, the HTML file, and any other assets like textures, audio, or video files.
Upload the content of the dist/ folder from your Web Project to your web hoster. You can do this via FTP, SFTP, or any other file transfer method your hoster provides. Look at the documentation of your web hoster for details.
That's it! Your web app is now live.
The folder location influences the URL of your web app.
Depending on your hoster's settings, the folder location and name determine what the URL of your web app is. Here's an example:
Your domain https://your-website.com/ points at the folder /var/www/html on your webspace.
You upload your files to /var/www/html/my-app so that the index.html file is at /var/www/html/my-app/index.html.
The URL of your web app is now https://your-website.com/my-app/.
In some cases, you want a Needle Engine project to be part of an existing web site, for example as a part of a blog post, a product page, or a portfolio. The process is very similar, but instead of uploading the files to the root of your web space, you embed the project into an existing website with a few lines of code.
Make a production build of your web project. This will create a dist/ folder with all necessary files, ready for distribution. It contains all necessary files, including the JavaScript bundle, the HTML file, and any other assets like textures, audio, or video files.
Upload the dist/ folder from your Web Project to your web hoster.
The folder can be hosted anywhere!
If you don't have access to your web hoster's file system, or no way to upload files there, you can upload the folder to any other webspace and use the public URL of that in the next step.
Inside your dist folder, you'll find an index.html file. We want to copy some lines from this folder, so open the file in a text editor. Typically, it looks like this:
On the target website, add the <script...> and <needle-engine...> tags as well. Make sure that the paths point at the location where you have uploaded the files to.
When you have limited access to a website, for example when you're using a CMS like WordPress, you can use an iframe to embed a Needle Engine scene into your website. You may know this workflow from embedding YouTube videos or Sketchfab models.
Make a production build of your web project. This will create a dist/ folder with all necessary files, ready for distribution.
Upload the dist/ folder from your Web Project to your web hoster.
The folder can be hosted anywhere!
If you don't have access to your web hoster's file system, or no way to upload files there, you can upload the folder to any other webspace and use the public URL of that in the next step.
Add an iframe to your website, pointing to the index.html file in the dist/ folder.
The list inside allow= depends on the features your web app uses. For example, XR applications require xr and xr-spatial-tracking to work inside iframes.
Upload the assets/ folder from your Web Project to your web hoster. Depending on your project settings, this folder contains one or more .glb files and any number of other files like audio, video, skybox and more.
Change the src= attribute of the needle-engine tag to the URL of the .glb file you want to display. Typically, this will be some path like https://your-website.com/assets/MyScene.glb.
That's it! The scene should now be displayed on your website.
Decide on the method you want to use to embed your Needle Engine project. You can either use the "Embedding a Needle project into an existing website" method, or the "Embedding a Needle project as iframe" method.
Upload the content of the dist/ folder from your Web Project to your web hoster. Usually, the Wordpress uploads directory is located at wp-content/uploads/.
Wordpress Backups
You can decide if your new project should be at wp-content/uploads/my-project/, or at a different location like my-projects/my-project. This affects if and how your project will be contained in Wordpress backups.
In the page you want to add Needle Engine to, add a HTML block and paste the code snippet as outlined above โ either as script embed, or as iframe.
`,27))])}const m=p(k,[["render",u],["__file","embedding.html.vue"]]),b=JSON.parse('{"path":"/embedding.html","title":"Needle Engine on your Website","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/embedding.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[{"level":2,"title":"Quickstart","slug":"quickstart","link":"#quickstart","children":[]},{"level":2,"title":"Using the \\"Deploy to ...\\" components","slug":"using-the-deploy-to-...-components","link":"#using-the-deploy-to-...-components","children":[]},{"level":2,"title":"Uploading a production build as web app","slug":"uploading-a-production-build-as-web-app","link":"#uploading-a-production-build-as-web-app","children":[]},{"level":2,"title":"Embedding a Needle project into an existing website","slug":"embedding-a-needle-project-into-an-existing-website","link":"#embedding-a-needle-project-into-an-existing-website","children":[]},{"level":2,"title":"Embedding a Needle project as iframe","slug":"embedding-a-needle-project-as-iframe","link":"#embedding-a-needle-project-as-iframe","children":[]},{"level":2,"title":"Embedding scenes that use no custom scripts","slug":"embedding-scenes-that-use-no-custom-scripts","link":"#embedding-scenes-that-use-no-custom-scripts","children":[]},{"level":2,"title":"Embedding a Needle Cloud web app as iframe","slug":"embedding-a-needle-cloud-web-app-as-iframe","link":"#embedding-a-needle-cloud-web-app-as-iframe","children":[]},{"level":2,"title":"Wordpress","slug":"wordpress","link":"#wordpress","children":[]},{"level":2,"title":"Shopify","slug":"shopify","link":"#shopify","children":[]},{"level":2,"title":"Wix","slug":"wix","link":"#wix","children":[]},{"level":2,"title":"Webflow","slug":"webflow","link":"#webflow","children":[]}],"git":{"updatedTime":1727778176000},"filePathRelative":"embedding.md"}');export{m as comp,b as data};
diff --git a/assets/ericcraft-mh.html-CjAn6AX_.js b/assets/ericcraft-mh.html-CjAn6AX_.js
new file mode 100644
index 000000000..eae1c1826
--- /dev/null
+++ b/assets/ericcraft-mh.html-CjAn6AX_.js
@@ -0,0 +1,43 @@
+import{_ as n,r as h,o as r,c as p,b as a,w as l,a as i,d as s}from"./app-CRZRGfEE.js";const d={};function o(g,t){const k=h("contribution-preview"),e=h("contributions-author");return r(),p("div",null,[a(e,{overviewLink:"/docs/community/contributions",name:"ericcraft-mh",url:"https://github.com/ericcraft-mh",profileImage:"https://avatars.githubusercontent.com/u/99364056?s=100&v=4",githubUrl:"https://github.com/ericcraft-mh"},{default:l(()=>[a(k,{title:"QuickLook Vertical Image Tracker",pageUrl:"/docs/community/contributions/ericcraft-mh/quicklook-vertical-image-tracker"},{default:l(()=>t[0]||(t[0]=[i("p",null,[s("In cases in which you are using QuickLook Image Tracker and Vertical Imagery you will need to correct the orientation of the model. As noted on the "),i("a",{href:"https://developer.apple.com/documentation/arkit/arkit_in_ios/content_anchors/detecting_images_in_an_ar_experience",target:"_blank",rel:"noopener noreferrer"},"Detecting Images in an AR Experience"),s(" page:")],-1),i("blockquote",null,[i("p",null,[i("code",null,"SCNPlane"),s(" is vertically oriented in its local coordinate space, but "),i("code",null,"ARImageAnchor"),s(" assumes the image is horizontal in its local space, so rotate the plane to match.")])],-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," GameObject"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," serializable"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," USDZExporter "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Euler "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "three"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," QuickLookObjectsToFix"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"("),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Object3D"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," objectToFix"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," usdzExporter"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," USDZExporter"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," startRot"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Euler "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Euler"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onEnable"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"usdzExporter "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," GameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"findObjectOfType"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(USDZExporter)"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"startRot "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"objectToFix"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"rotation"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"subscribeToBeforeExportEvent"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onDisable"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"unsubscribeFromBeforeExportEvent"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," subscribeToBeforeExportEvent"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"usdzExporter"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"addEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"before-export"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"onBeforeExport)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"usdzExporter"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"addEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"after-export"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"onAfterExport)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," unsubscribeFromBeforeExportEvent"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"usdzExporter"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"removeEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"before-export"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"onBeforeExport)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"usdzExporter"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"removeEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"after-export"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"onAfterExport)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onBeforeExport"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," ()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," =>"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"objectToFix"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"updateMatrixWorld"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"objectToFix"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"rotation"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"x "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," -"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Math"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"PI"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," /"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 2"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"objectToFix"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"updateMatrixWorld"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onAfterExport"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," ()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," =>"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"objectToFix"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"updateMatrixWorld"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"objectToFix"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"setRotationFromEuler"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"startRot)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"objectToFix"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"updateMatrixWorld"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1),i("p",null,[s("Thanks to "),i("a",{href:"https://github.com/llllkatjallll",target:"_blank",rel:"noopener noreferrer"},"llllkatjallll"),s(" as their "),i("a",{href:"https://github.com/needle-tools/needle-engine-support/discussions/184",target:"_blank",rel:"noopener noreferrer"},"Set fallback material for USDZ exporter"),s(" solution helped me come up with a working solution for this.")],-1),i("p",null,"EDIT: Code cleanup and fixes.",-1)])),_:1})]),_:1})])}const C=n(d,[["render",o],["__file","ericcraft-mh.html.vue"]]),E=JSON.parse('{"path":"/community/contributions/ericcraft-mh","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/contributions: ericcraft mh.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{C as comp,E as data};
diff --git a/assets/everywhere-action-emphasize-on-click.html-CVeSGZpb.js b/assets/everywhere-action-emphasize-on-click.html-CVeSGZpb.js
new file mode 100644
index 000000000..c65b2224f
--- /dev/null
+++ b/assets/everywhere-action-emphasize-on-click.html-CVeSGZpb.js
@@ -0,0 +1,27 @@
+import{_ as t,r as n,o as h,c as l,a as s,b as e,e as k}from"./app-CRZRGfEE.js";const p={};function r(d,i){const a=n("contribution-header");return h(),l("div",null,[i[0]||(i[0]=s("p",null,[s("a",{href:"/docs/community/contributions"},"Overview")],-1)),e(a,{url:"https://github.com/marwie",author:"marwie",page:"/docs/community/contributions/marwie",profileImage:"https://avatars.githubusercontent.com/u/5083203?s=100&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/157",title:"Everywhere Action: Emphasize on Click",gradient:"True"}),i[1]||(i[1]=k(`
Example for adding custom USDZ behaviours for iOS AR
`,3))])}const y=t(p,[["render",r],["__file","everywhere-action-emphasize-on-click.html.vue"]]),g=JSON.parse('{"path":"/community/contributions/marwie/everywhere-action-emphasize-on-click","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/marwie: everywhere action emphasize on click.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{y as comp,g as data};
diff --git a/assets/everywhere-actions.html-ddHbg6za.js b/assets/everywhere-actions.html-ddHbg6za.js
new file mode 100644
index 000000000..df0a78d90
--- /dev/null
+++ b/assets/everywhere-actions.html-ddHbg6za.js
@@ -0,0 +1,24 @@
+import{_ as n,r as l,o as r,c as o,e as a,b as s,a as e}from"./app-CRZRGfEE.js";const h="/docs/imgs/everywhere-actions-component-menu.gif",d={};function p(k,i){const t=l("sample");return r(),o("div",null,[i[0]||(i[0]=a('
Needle's Everywhere Actions are a set of carefully chosen components that allow you to create interactive experiences in Unity without writing a single line of code. They are designed to serve as building blocks for experiences across the web, mobile and XR, including Augmented Reality on iOS.
From low-level triggers and actions, higher-level complex interactive behaviours can be built.
For iOS support add the USDZExporter component to your scene. It is good practice to add it to the same object as the WebXR component (but not mandatory)
To add an action to any object in your scene select it and then click Add Component > Needle > Everywhere Actions > [Action].
Demonstrates spatial audio, animation, and interactions.
',14)),s(t,{src:"https://engine.needle.tools/samples-uploads/musical-instrument"}),i[1]||(i[1]=e("h3",{id:"simple-character-controllers",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#simple-character-controllers"},[e("span",null,"Simple Character Controllers")])],-1)),i[2]||(i[2]=e("p",null,"Demonstrates combining animations, look at, and movement.",-1)),s(t,{src:"https://engine.needle.tools/samples-uploads/usdz-characters"}),i[3]||(i[3]=a('
Demonstrates how to attach 3D content onto a custom image marker. Start the scene below in AR and point your phone's camera at the image marker on a screen, or print it out.
On Android: please turn on "WebXR Incubations" in the Chrome Flags. You can find those by pasting chrome://flags/#webxr-incubations into the Chrome browser address bar of your Android phone.
',5)),s(t,{src:"https://engine.needle.tools/samples-uploads/image-tracking"}),i[4]||(i[4]=e("h3",{id:"interactive-building-blocks-overview",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#interactive-building-blocks-overview"},[e("span",null,"Interactive Building Blocks Overview")])],-1)),s(t,{src:"https://engine.needle.tools/samples-uploads/usdz-interactivity"}),i[5]||(i[5]=a(`
Creating new Everywhere Actions involves writing code for your action in TypeScript, which will be used in the browser and for WebXR, and using our TriggerBuilder and ActionBuilder API to create a matching setup for Augmented Reality on iOS via QuickLook. When creating custom actions, keep in mind that QuickLook has a limited set of features available. You can still use any code you want for the browser and WebXR, but the behaviour for QuickLook may need to be an approximation built from the available triggers and actions.
Tips
Often constructing specific behaviours requires thinking outside the box and creatively applying the available low-level actions. An example would be a "Tap to Place" action โ there is no raycasting or hit testing available in QuickLook, but you could cover the expected placement area with a number of invisible objects and use a "Tap" trigger to move the object to be placed to the position of the tapped invisible object.
Here's the implementation for HideOnStart as an example for how to create an Everywhere Action with implementations for both the browser and QuickLook:
Often, getting the right behaviour will involve composing higher-level actions from the available lower-level actions. For example, our "Change Material on Click" action is composed of a number of fadeActions and internally duplicates objects with different sets of materials each. By carefully constructing these actions, complex behaviours can be achieved.
To see the implementation of our built-in Everywhere Actions, please take look at src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts.
`,16))])}const g=n(d,[["render",p],["__file","everywhere-actions.html.vue"]]),y=JSON.parse(`{"path":"/everywhere-actions.html","title":"Everywhere Actions","lang":"en-US","frontmatter":{"title":"Everywhere Actions","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/everywhere actions.png"}],["meta",{"name":"og:description","content":"---\\nNeedle's Everywhere Actions are a set of carefully chosen components that allow you to create interactive experiences in Unity without writing a single line of code.\\nThey are designed to serve as building blocks for experiences across the web, mobile and XR, including Augmented Reality on iOS.\\nFrom low-level triggers and actions, higher-level complex interactive behaviours can be built."}]],"description":"---\\nNeedle's Everywhere Actions are a set of carefully chosen components that allow you to create interactive experiences in Unity without writing a single line of code.\\nThey are designed to serve as building blocks for experiences across the web, mobile and XR, including Augmented Reality on iOS.\\nFrom low-level triggers and actions, higher-level complex interactive behaviours can be built."},"headers":[{"level":2,"title":"What are Everywhere Actions?","slug":"what-are-everywhere-actions","link":"#what-are-everywhere-actions","children":[{"level":3,"title":"Supported Platforms","slug":"supported-platforms","link":"#supported-platforms","children":[]}]},{"level":2,"title":"How do I use Everywhere Actions?","slug":"how-do-i-use-everywhere-actions","link":"#how-do-i-use-everywhere-actions","children":[]},{"level":2,"title":"List of Everywhere Actions","slug":"list-of-everywhere-actions","link":"#list-of-everywhere-actions","children":[]},{"level":2,"title":"Samples","slug":"samples","link":"#samples","children":[{"level":3,"title":"Musical Instrument","slug":"musical-instrument","link":"#musical-instrument","children":[]},{"level":3,"title":"Simple Character Controllers","slug":"simple-character-controllers","link":"#simple-character-controllers","children":[]},{"level":3,"title":"Image Tracking","slug":"image-tracking","link":"#image-tracking","children":[]},{"level":3,"title":"Interactive Building Blocks Overview","slug":"interactive-building-blocks-overview","link":"#interactive-building-blocks-overview","children":[]}]},{"level":2,"title":"Create your own Everywhere Actions","slug":"create-your-own-everywhere-actions","link":"#create-your-own-everywhere-actions","children":[{"level":3,"title":"Code Example","slug":"code-example","link":"#code-example","children":[]},{"level":3,"title":"Low level methods for building your own actions","slug":"low-level-methods-for-building-your-own-actions","link":"#low-level-methods-for-building-your-own-actions","children":[]}]},{"level":2,"title":"Further reading","slug":"further-reading","link":"#further-reading","children":[]}],"git":{"updatedTime":1726585195000},"filePathRelative":"everywhere-actions.md"}`);export{g as comp,y as data};
diff --git a/assets/examples.html-BMSngmVr.js b/assets/examples.html-BMSngmVr.js
new file mode 100644
index 000000000..16781c630
--- /dev/null
+++ b/assets/examples.html-BMSngmVr.js
@@ -0,0 +1 @@
+import{_ as o,r as s,o as l,c as i,e as d,b as r,a as e,d as a}from"./app-CRZRGfEE.js";const p={};function u(c,n){const t=s("video-embed");return l(),i("div",null,[n[0]||(n[0]=d('
',5)),r(t,{src:"https://user-images.githubusercontent.com/5083203/186126996-27b45c5f-f3b9-40f7-b8c7-6ecba1d25a6e.mp4"}),n[1]||(n[1]=e("h2",{id:"castle-builder",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#castle-builder"},[e("span",null,"Castle Builder")])],-1)),n[2]||(n[2]=e("p",null,[e("a",{href:"https://castle.needle.tools",target:"_blank",rel:"noopener noreferrer"},"Play Now"),a(" โ by Needle")],-1)),r(t,{src:"https://user-images.githubusercontent.com/5083203/186145731-705cfec2-1779-4a0b-97d9-95f3edaaf2d0.mp4"}),n[3]||(n[3]=e("h2",{id:"bike-configurator",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#bike-configurator"},[e("span",null,"Bike Configurator")])],-1)),n[4]||(n[4]=e("p",null,[e("a",{href:"https://bike.needle.tools",target:"_blank",rel:"noopener noreferrer"},"Bike Configurator"),a(" โ by Needle")],-1)),r(t,{src:"https://user-images.githubusercontent.com/5083203/186146814-52fb05c7-a073-4efa-a226-47a9c1835413.mp4"}),n[5]||(n[5]=e("h2",{id:"sandbox-template",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#sandbox-template"},[e("span",null,"Sandbox Template")])],-1)),n[6]||(n[6]=e("p",null,[e("a",{href:"https://fwd.needle.tools/needle-engine/glitch-starter",target:"_blank",rel:"noopener noreferrer"},"Sandbox Template"),a(" โ by Needle")],-1)),r(t,{src:"https://user-images.githubusercontent.com/5083203/186149117-ca7cf22f-dc7d-4c74-86d4-d78fe53a208c.mp4"}),n[7]||(n[7]=e("h2",{id:"songs-of-cultures",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#songs-of-cultures"},[e("span",null,"Songs of Cultures")])],-1)),n[8]||(n[8]=e("p",null,[e("a",{href:"https://fwd.needle.tools/needle-engine/projects/songs-of-cultures",target:"_blank",rel:"noopener noreferrer"},"Songs of Cultures"),a(" โ by A.MUSE")],-1)),r(t,{src:"https://user-images.githubusercontent.com/5083203/186147814-159a33f9-f1a6-47d4-804f-5f8f5a63125d.mp4"}),n[9]||(n[9]=e("h2",{id:"pok-mon-card",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pok-mon-card"},[e("span",null,"Pokรฉmon Card")])],-1)),n[10]||(n[10]=e("p",null,[e("a",{href:"https://fwd.needle.tools/needle-engine/projects/pokemon-card",target:"_blank",rel:"noopener noreferrer"},"Pokรฉmon Card"),a(" โ Scene from Alex Ameye โข "),e("a",{href:"https://alexanderameye.github.io/notes/holographic-card-shader/",target:"_blank",rel:"noopener noreferrer"},"Original Blog Post by Alex")],-1)),r(t,{src:"https://user-images.githubusercontent.com/5083203/186149736-49a697b3-4282-4b71-ab13-a6b176955c13.mp4"}),n[11]||(n[11]=e("h2",{id:"encryption-in-space",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#encryption-in-space"},[e("span",null,"Encryption in Space")])],-1)),n[12]||(n[12]=e("p",null,[e("a",{href:"https://fwd.needle.tools/needle-engine/projects/encryption",target:"_blank",rel:"noopener noreferrer"},"Encryption in Space"),a(" โ by Katja Rempel & Nick Jwu")],-1)),r(t,{src:"https://user-images.githubusercontent.com/5083203/186151157-0c0a7d05-ad42-44be-b553-8d4cd48cbb81.mp4"}),n[13]||(n[13]=e("h2",{id:"physics-playground",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#physics-playground"},[e("span",null,"Physics Playground")])],-1)),n[14]||(n[14]=e("p",null,[e("a",{href:"https://bruno-simon-20k-needle.glitch.me/",target:"_blank",rel:"noopener noreferrer"},"Physics Playground"),a(" โ Scene from Bruno Simon")],-1)),r(t,{src:"https://user-images.githubusercontent.com/5083203/186149536-987ee796-3fe0-42bc-bd80-4c25aaf174aa.mp4"})])}const m=o(p,[["render",u],["__file","examples.html.vue"]]),h=JSON.parse('{"path":"/examples.html","title":"Example Projects โจ","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/examples.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[{"level":2,"title":"Needle Website","slug":"needle-website","link":"#needle-website","children":[]},{"level":2,"title":"Castle Builder","slug":"castle-builder","link":"#castle-builder","children":[]},{"level":2,"title":"Bike Configurator","slug":"bike-configurator","link":"#bike-configurator","children":[]},{"level":2,"title":"Sandbox Template","slug":"sandbox-template","link":"#sandbox-template","children":[]},{"level":2,"title":"Songs of Cultures","slug":"songs-of-cultures","link":"#songs-of-cultures","children":[]},{"level":2,"title":"Pokรฉmon Card","slug":"pok-mon-card","link":"#pok-mon-card","children":[]},{"level":2,"title":"Encryption in Space","slug":"encryption-in-space","link":"#encryption-in-space","children":[]},{"level":2,"title":"Physics Playground","slug":"physics-playground","link":"#physics-playground","children":[]}],"git":{"updatedTime":1726606065000},"filePathRelative":"examples.md"}');export{m as comp,h as data};
diff --git a/assets/export.html-Cyapuf2Z.js b/assets/export.html-Cyapuf2Z.js
new file mode 100644
index 000000000..1a003d44e
--- /dev/null
+++ b/assets/export.html-Cyapuf2Z.js
@@ -0,0 +1,23 @@
+import{_ as s,r as n,o,c as l,e as a,a as r,d as t,b as p,w as h}from"./app-CRZRGfEE.js";const d={};function c(g,e){const i=n("RouteLink");return o(),l("div",null,[e[3]||(e[3]=a('
Add an ExportInfo component to your Unity scene to generate a new web project from a template, link to an existing web project that you want to export to, set up dependencies to other libraries and packages and to deploy your project.
By default, your scene is exported on save. This setting can be changed by disabling Auto Export in the ExportInfo component.
To export meshes, materials, animations, textures (...) create a new GameObject in your hierarchy and add a GltfObject component to it. This is the root of a new glTF file. It will be exported whenever you make a change to the scene and save.
Only scripts and data on and inside those root objects is exported. Scripts and data outside of them are not exported.
Add a cube as a child of your root object and save your scene. Note that the output assets/ folder (see project structure) now contains a new .glb file with the same name as your root GameObject.
You can enable the Smart Export setting (via Edit/Project Settings/Needle ) to only export when a change in this object's hierarchy is detected.
How to prevent specific objects from being exported
Objects with the EditorOnly tag will be ignored on export including their child hierarchy. Be aware that this is preferred over disabling objects as disabled will still get exported in case they're turned on later.
If you want to split up your application into multiple levels or scenes then you can simply use the SceneSwitcher component. You can then structure your application into multiple scenes or prefabs and add them to the SceneSwitcher array to be loaded and unloaded at runtime. This is a great way to avoid having to load all your content upfront and to keep loading times small (for example it is what we did on needle.tools by separating each section of your website into its own scene and only loading them when necessary)
Max. 500k vertices (less if you target mobile VR as well)
Max. 4x 2k lightmaps
',13)),r("p",null,[e[1]||(e[1]=t("You can split up scenes and prefabs into multiple glTF files, and then load those on demand (only when needed). This keeps loading performance fast and file size small. See the ")),p(i,{to:"/scripting.html#assetreference-and-addressables"},{default:h(()=>e[0]||(e[0]=[t("AssetReference section in the Scripting docs")])),_:1}),e[2]||(e[2]=t("."))]),e[4]||(e[4]=a(`
The scene complexity here is recommended to ensure good performance across a range of web-capable devices and bandwidths. There's no technical limitation to this beyond the capabilities of your device.
Prefabs can be exported as invidual glTF files and instantiated at runtime. To export a prefab as glTF just reference a prefab asset (from the project browser and not in the scene) from one of your scripts.
Exporting Prefabs works with nesting too: a component in a Prefab can reference another Prefab which will then also be exported. This mechanism allows for composing scenes to be as lightweight as possible and loading the most important content first and defer loading of additional content.
Similar to Prefab assets, you can reference other Scene assets. To get started, create a component in Unity with a UnityEditor.SceneAsset field and add it to one of your GameObjects inside a GltfObject. The referenced scene will now be exported as a separate glTF file and can be loaded/deserialized as a AssetReference from TypeScript.
You can keep working inside a referenced scene and still update your main exporter scene/website. On scene save or play mode change we will detect if the current scene is being used by your currently running server and then trigger a re-export for only that glb. (This check is done by name - if a glb inside your <web_project>/assets/ folder exists, it is exported again and the main scene reloads it.)
As an example on our website each section is setup as a separate scene and on export packed into multiple glb files that we load on demand:
If you want to reference and load a prefab from one of your scripts you can declare a AssetReference type. Here is a minimal example:
import { Behaviour, serializable, AssetReference } from "@needle-tools/engine";
+
+export class MyClass extends Behaviour {
+
+ // if you export a prefab or scene as a reference from Unity you'll get a path to that asset
+ // which you can de-serialize to AssetReference for convenient loading
+ @serializable(AssetReference)
+ myPrefab?: AssetReference;
+
+ async start() {
+ // directly instantiate
+ const myInstance = await this.myPrefab?.instantiate();
+
+ // you can also just load and instantiate later
+ // const myInstance = await this.myPrefab.loadAssetAsync();
+ // this.gameObject.add(myInstance)
+ // this is useful if you know that you want to load this asset only once because it will not create a copy
+ // since \`\`instantiate()\`\` does create a copy of the asset after loading it
+ }
+}
Procedural Animations can be created via scripting
Needle Engine is one of the first to support the new glTF extension KHR_ANIMATION_POINTER. This means that almost all properties, including script variables, are animatable.
One current limitation is that materials won't be duplicated on export โ if you want to animate the same material with different colors, for example, you currently need to split the material in two.
By default, materials are converted into glTF materials on export. glTF supports a physically based material model and has a number of extensions that help to represent complex materials.
For full control over what gets exported, it's highly recommended to use the glTF materials provided by UnityGltf:
PBRGraph
UnlitGraph
These materials are exported as-is, with no conversion necessary. They allow for using advanced material properties such as refractive transmission and iridescence, which can be exported as well.
Materials that can be converted out-of-the-box:
BiRP/Standard
BiRP/Autodesk Interactive
BiRP/Unlit
URP/Lit
URP/Unlit
Other materials are converted using a propery name heuristic. That means that depending on what property names your materials and shaders use, you might want to either refactor your custom shader's properties to use the property names of either URP/Lit or PBRGraph, or export the material as Custom Shader.
To export custom shaders (e.g. ShaderGraph shaders), add an ExportShader Asset Label (see bottom of the inspector) to the shader you want to export.
Warning
Please see limitations listed below
Note that Custom Shaders aren't part of the ratified glTF material model. The resulting GLB files will not display correctly in other viewers (the materials will most likely display white).
We currently only support custom Unlit shaders โ Lit shader conversion is not officially supported.
Custom Lit Shaders are currently experimental. Not all rendering modes are supported.
Shadow receiving on custom shaders is not supported
Skinned meshes with custom shaders are not supported
As there's multiple coordinate system changes when going from Unity to three.js and glTF, there might be some changes necessary to get advanced effects to work. We try to convert data on export but may not catch all cases where conversions are necessary. These coordinate changes are
UV coordinates in Unity start at the bottom left; in glTF they start at the top left.
X axis values are flipped in glTF compared to Unity (a variant of a left-handed to right-handed coordinate system change).
To export lightmaps simply generate lightmaps in Unity. Lightmaps will be automatically exported.
When working on multiple scenes, disable "Auto Generate" and bake lightmaps explicitly. Otherwise, Unity will discard temporary lightmaps on scene change.
There's no 100% mapping between how Unity handles lights and environment and how three.js handle that. For example, Unity has entirely separate code paths for lightmapped and non-lightmapped objects (lightmapped objects don't receive ambient light since that is already baked into their maps), and three.js doesn't distinguish in that way.
This means that to get best results, we currently recommend specific settings if you're mixing baked and non-baked objects in a scene:
Environment Lighting: Skybox
+Ambient Intensity: 1
+Ambient Color: black
2021.3+
2020.3+
If you have no baked objects in your scene, then the following settings should also yield correct results:
Environment Lighting: Color
+Ambient Color: any
`,54))])}const u=s(d,[["render",c],["__file","export.html.vue"]]),y=JSON.parse(`{"path":"/export.html","title":"Exporting Assets to glTF","lang":"en-US","frontmatter":{"title":"Exporting Assets to glTF","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/exporting assets to gltf.png"}],["meta",{"name":"og:description","content":"---\\nAdd an ExportInfo component to your Unity scene to generate a new web project from a template, link to an existing web project that you want to export to, set up dependencies to other libraries and packages and to deploy your project.\\nBy default, your scene is exported on save. This setting can be changed by disabling Auto Export in the ExportInfo component.\\nTo export meshes, materials, animations, textures (...) create a new GameObject in your hierarchy and add a GltfObject component to it. This is the root of a new glTF file. It will be exported whenever you make a change to the scene and save.\\nOnly scripts and data on and inside those root objects is exported. Scripts and data outside of them are not exported.\\nAdd a cube as a child of your root object and save your scene. Note that the output assets/ folder (see project structure) now contains a new .glb file with the same name as your root GameObject.\\nYou can enable the Smart Export setting (via Edit/Project Settings/Needle ) to only export when a change in this object's hierarchy is detected.\\n:::details How to prevent specific objects from being exported\\nObjects with the EditorOnly tag will be ignored on export including their child hierarchy.\\nBe aware that this is preferred over disabling objects as disabled will still get exported in case they're turned on later.\\n:::\\nIf you want to split up your application into multiple levels or scenes then you can simply use the SceneSwitcher component. You can then structure your application into multiple scenes or prefabs and add them to the SceneSwitcher array to be loaded and unloaded at runtime. This is a great way to avoid having to load all your content upfront and to keep loading times small (for example i"}]],"description":"---\\nAdd an ExportInfo component to your Unity scene to generate a new web project from a template, link to an existing web project that you want to export to, set up dependencies to other libraries and packages and to deploy your project.\\nBy default, your scene is exported on save. This setting can be changed by disabling Auto Export in the ExportInfo component.\\nTo export meshes, materials, animations, textures (...) create a new GameObject in your hierarchy and add a GltfObject component to it. This is the root of a new glTF file. It will be exported whenever you make a change to the scene and save.\\nOnly scripts and data on and inside those root objects is exported. Scripts and data outside of them are not exported.\\nAdd a cube as a child of your root object and save your scene. Note that the output assets/ folder (see project structure) now contains a new .glb file with the same name as your root GameObject.\\nYou can enable the Smart Export setting (via Edit/Project Settings/Needle ) to only export when a change in this object's hierarchy is detected.\\n:::details How to prevent specific objects from being exported\\nObjects with the EditorOnly tag will be ignored on export including their child hierarchy.\\nBe aware that this is preferred over disabling objects as disabled will still get exported in case they're turned on later.\\n:::\\nIf you want to split up your application into multiple levels or scenes then you can simply use the SceneSwitcher component. You can then structure your application into multiple scenes or prefabs and add them to the SceneSwitcher array to be loaded and unloaded at runtime. This is a great way to avoid having to load all your content upfront and to keep loading times small (for example i"},"headers":[{"level":2,"title":"๐ฆ Exporting glTF files","slug":"exporting-gltf-files","link":"#exporting-gltf-files","children":[{"level":3,"title":"Lazy loading and multiple levels / scenes","slug":"lazy-loading-and-multiple-levels-scenes","link":"#lazy-loading-and-multiple-levels-scenes","children":[]},{"level":3,"title":"Recommended Complexity per glTF","slug":"recommended-complexity-per-gltf","link":"#recommended-complexity-per-gltf","children":[]},{"level":3,"title":"Prefabs","slug":"prefabs","link":"#prefabs","children":[]},{"level":3,"title":"Scene Assets","slug":"scene-assets","link":"#scene-assets","children":[]}]},{"level":2,"title":"๐ Exporting Animations","slug":"exporting-animations","link":"#exporting-animations","children":[]},{"level":2,"title":"๐ Exporting the Skybox","slug":"exporting-the-skybox","link":"#exporting-the-skybox","children":[]},{"level":2,"title":"โจ Exporting Materials","slug":"exporting-materials","link":"#exporting-materials","children":[{"level":3,"title":"Physically Based Materials (PBR)","slug":"physically-based-materials-pbr","link":"#physically-based-materials-pbr","children":[]},{"level":3,"title":"Custom Shaders","slug":"custom-shaders","link":"#custom-shaders","children":[]}]},{"level":2,"title":"๐ก Exporting Lightmaps","slug":"exporting-lightmaps","link":"#exporting-lightmaps","children":[{"level":3,"title":"Recommended Lightmap Settings","slug":"recommended-lightmap-settings","link":"#recommended-lightmap-settings","children":[]},{"level":3,"title":"Mixing Baked and Non-Baked Objects","slug":"mixing-baked-and-non-baked-objects","link":"#mixing-baked-and-non-baked-objects","children":[]}]}],"git":{"updatedTime":1726585195000},"filePathRelative":"export.md"}`);export{u as comp,y as data};
diff --git a/assets/faq.html-B6pZlv9Y.js b/assets/faq.html-B6pZlv9Y.js
new file mode 100644
index 000000000..f81b3a1a1
--- /dev/null
+++ b/assets/faq.html-B6pZlv9Y.js
@@ -0,0 +1,11 @@
+import{_ as r}from"./ktx-env-variable-DxwKzzNo.js";import{_ as l,r as h,o as d,c,e as n,a as i,d as t,b as o,w as a}from"./app-CRZRGfEE.js";const p="/docs/imgs/unity-needle-engine-license.jpg",g="/docs/faq/lightmap_encoding.jpg",u={},k={class:"hint-container tip"};function m(f,e){const s=h("RouteLink");return d(),c("div",null,[e[14]||(e[14]=n('
Open Edit/Project Settings/Needle to get the Needle Engine plugin settings. At the top of the window you'll find fields for entering your license information.
Email - Enter the email you purchased the license with
Invoice ID - Enter one of the invoice ids that you received by email
This usually happens when you're using custom shaders or materials and their properties don't cleanly translate to known property names for glTF export. You can either make sure you're using glTF-compatible materials and shaders, or mark shaders as "custom" to export them directly.
You might see a warning in your browser about SSL Security depending on your local configuration.
This is because while the connection is encrypted, by default there's no SSL certificate that the browser can validate. If that happens: click Advanced and Proceed to Site. In Safari, you might need to refresh the page afterwards, because it does not automatically proceed. Now you should see your scene in the browser!
The dialogue should only show up once for the same local server.
',15)),i("div",k,[e[3]||(e[3]=i("p",{class:"hint-container-title"},"Tips",-1)),e[4]||(e[4]=i("p",null,"Connections are secured, because we're enforcing HTTPS to make sure that WebXR and other modern web APIs work out-of-the-box. Some browsers will still complain that the SSL connection (between your local development server and the local website) can't be automatically trusted, and that you need to manually verify you trust that page. Automatic Page Reload and Websocket connections may also be affected depending on the browser and system settings.",-1)),i("p",null,[e[1]||(e[1]=t("See ")),o(s,{to:"/testing.html"},{default:a(()=>e[0]||(e[0]=[t("the Testing docs")])),_:1}),e[2]||(e[2]=t(" for information on how to set up a self-signed certificate for a smoother development experience."))])]),e[15]||(e[15]=n(`
If that happens there's usually an exception either in engine code or your code. Open the dev tools (Ctrl + Shift + I or F12 in Chrome) and check the Console for errors. In some cases, especially when you just updated the Needle Engine package version, this can be fixed by stopping and restarting the local dev server. For that, click on the running progress bar in the bottom right corner of the Editor, and click the little X to cancel the running task. Then, simply press Play again.
This can have many reasons, but a few common ones are:
too many textures or textures are too large
meshes have too many vertices
meshes have vertex attributes you don't actually need (e.g. have normals and tangents but you're not using them)
objects are disabled and not ignored โ disabled objects get exported as well in case you want to turn them on at runtime! Set their Tag to EditorOnly to completely ignore them for export.
you have multiple GltfObject components in your scene and they all have EmbedSkybox enabled (you need to have the skybox only once per scene you export)
',15)),i("p",null,[e[6]||(e[6]=t("If loading time itself is an issue you can ")),e[7]||(e[7]=i("strong",null,"try to split up your content into multiple glb files",-1)),e[8]||(e[8]=t(" and load them on-demand (this is what we do on our website). For it to work you can put your content into Prefabs or Scenes and reference them from any of your scripts. Please have a look at ")),o(s,{to:"/scripting.html#assetreference-and-addressables"},{default:a(()=>e[5]||(e[5]=[t("Scripting/Addressables in the documentation")])),_:1}),e[9]||(e[9]=t("."))]),e[16]||(e[16]=n('
Make sure that your lights are set to "Baked" or "Realtime". "Mixed" is currently not supported.
Lights set to mixed (with lightmapping) do affect objects twice in three.js, since there is currently no way to exclude lightmapped objects from lighting
The Intensity Multiplier factor for Skybox in Lighting/Environment is currently not supported and has no effect in Needle Engine
Light shadow intensity can currently not be changed due to a three.js limitation.
Your light has shadows enabled (either Soft Shadow or Hard Shadow)
Your objects are set to "Cast Shadows: On" (see MeshRenderer component)
For directional lights the position of the light is currently important since the shadow camera will be placed where the light is located in the scene.
Deploying on Glitch is a fast way to prototype and might even work for some small productions. The little server there doesn't have the power and bandwidth to host many people in a persistent session.
We're working on other networking ideas, but in the meantime you can host the website somewhere else (with node.js support) or simply remix it to distribute load among multiple servers. You can also host the networking backend package itself somewhere else where it can scale e.g. Google Cloud.
Make sure to add the WebXR component somewhere inside your root GltfObject.
Optionally add a AR Session Root component on your root GltfObject or within the child hierarchy to specify placement, scale and orientation for WebXR.
Optionally add a XR Rig component to control where users start in VR
When creating new scripts in npmdefs in sub-scenes (that is a scene that is exported as a reference from a script in your root export scene) you currently have to re-export the root scene again. This is because the code-gen that is responsible for registering new scripts currently only runs for scenes with a ExportInfo component. This will be fixed in the future.
The most likely reason is an incorrect installation. Check the console and the ExportInfo component for errors or warnings.
If these warnings/errors didn't help, try the following steps in order. Give them some time to complete. Stop once your problem has been resolved. Check the console for warnings and errors.
',27)),i("ul",null,[i("li",null,[e[11]||(e[11]=t("Make sure you follow the ")),o(s,{to:"/getting-started/#prerequisites"},{default:a(()=>e[10]||(e[10]=[t("Prerequisites")])),_:1}),e[12]||(e[12]=t("."))]),e[13]||(e[13]=n("
Install your project by selecting your ExportInfo component and clicking Install
Run a clean installation by selecting your ExportInfo component, holding Alt and clicking Clean Install
Try opening your web project directory in a command line tool and follow these steps:
run npm install and then npm run dev-host
Make sure both the local runtime package (node_modules/@needle-tools/engine) as well as three.js (node_modules/three) did install.
You may run npm install in both of these directories as well.
While generating C# components does technically run with vanilla javascript too we don't recommend it and fully support it since it is more guesswork or simply impossible for the generator to know which C# type to create for your javascript class. Below you find a minimal example on how to generate a Unity Component from javascript if you really want to tho.
On Windows: Make sure you have added toktx to your system environment variables. You may need to restart your computer after adding it to refresh the environment variables. The default install location is C:\\Program Files\\KTX-Software\\bin
Make sure to not create a project on a drive formatted as exFAT because exFAT does not support symlinks, which is required for Needle Engine for Unity prior to version 3.x. You can check the formatting of your drives using the following steps:
Open "System Information" (either windows key and type that or enter "msinfo32" in cmd)
Select Components > Storage > Drives
Select all (Ctrl + A) on the right side of the screen and copy that (Ctrl + C) and paste here (Ctrl + V)
Make sure your project is on a disk that is known to work with node.js. Main reason for failures is that the disk doesn't support symlinks (symbolic links / softlinks), which is a requirement for proper functioning of node.js. NTFS formatting should always work. Known problematic file system formattings are exFAT and FAT32.
To check the format of your drives, you can:
Open "System Information" (either Windows key and type "System Information" or enter msinfo32 in cmd Windows + R)
Select "Components > Storage > Drives"
There, you can see all drives and their formatting listed. Put your projects on a drive that is NTFS formatted.
You're likely using an x86_64 version of Unity on an (ARM) Apple Silicon processor. Unity 2020.3 is only available for x86_64, later versions also have Apple Silicon versions. Our Unity integration calling npm will thus do so from an x86_64 process, resulting in the x86_64 version of node and vite/esbuild being used. When you afterwards try to run npm commands in the same project from an Apple Silicon app (e.g. VS Code), npm will complain about mismatching architectures with a long error message.
To fix this, use an Apple Silicon version of Unity (2021.1 or later).
You can also temporarily fix it on 2020.3 by deleting the node_modules folder and running npm install again from VS Code. You'll have to delete node_modules again when you switch back to Unity.
This can happen when you have e.g. a SceneSwitcher (or any other component that loads a scene or asset) and the referenced Asset in Unity contains a GltfObject that has the same name as your original scene with the SceneSwitcher. You can double check this in Unity if you get an error that says something like:
Failed to export โ YourSceneName.glb
+you seem to have objects with the same name referencing each other.
To fix this you can:
Remove the GltfObject in the referenced Prefab or Scene
Rename the GameObject with the component that loads the referenced scenes
If this doesn't fix the problem please ask in our discord.
Use a detector like this one to determine if your device supports WebGL 2, it also hints at what could be the cause of your problem, but generally make sure you have updated your browser and drivers. WebGL 1 is not supported.
`,35))])}const w=l(u,[["render",m],["__file","faq.html.vue"]]),v=JSON.parse(`{"path":"/faq.html","title":"Questions and Answers (FAQ) ๐ก","lang":"en-US","frontmatter":{"title":"Questions and Answers (FAQ) ๐ก","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/preview.jpeg"}],["meta",{"name":"og:description","content":"---"}]],"description":"---"},"headers":[{"level":2,"title":"I purchased a license - how can I activate my Needle Engine License?","slug":"i-purchased-a-license-how-can-i-activate-my-needle-engine-license","link":"#i-purchased-a-license-how-can-i-activate-my-needle-engine-license","children":[{"level":3,"title":"Activating the license in Unity","slug":"activating-the-license-in-unity","link":"#activating-the-license-in-unity","children":[]},{"level":3,"title":"Activating the license in Blender","slug":"activating-the-license-in-blender","link":"#activating-the-license-in-blender","children":[]}]},{"level":2,"title":"My objects are white after export","slug":"my-objects-are-white-after-export","link":"#my-objects-are-white-after-export","children":[]},{"level":2,"title":"There's a SSL error when opening the local website","slug":"there-s-a-ssl-error-when-opening-the-local-website","link":"#there-s-a-ssl-error-when-opening-the-local-website","children":[]},{"level":2,"title":"My local website stays black","slug":"my-local-website-stays-black","link":"#my-local-website-stays-black","children":[]},{"level":2,"title":"How to fix Uncaught ReferenceError: NEEDLE_ENGINE_META is not defined / NEEDLE_USE_RAPIER is not defined","slug":"how-to-fix-uncaught-referenceerror-needle-engine-meta-is-not-defined-needle-use-rapier-is-not-defined","link":"#how-to-fix-uncaught-referenceerror-needle-engine-meta-is-not-defined-needle-use-rapier-is-not-defined","children":[]},{"level":2,"title":"THREE.EXRLoader: provided file doesnt appear to be in OpenEXR format","slug":"three.exrloader-provided-file-doesnt-appear-to-be-in-openexr-format","link":"#three.exrloader-provided-file-doesnt-appear-to-be-in-openexr-format","children":[]},{"level":2,"title":"My website becomes too large / is loading slow (too many MB)","slug":"my-website-becomes-too-large-is-loading-slow-too-many-mb","link":"#my-website-becomes-too-large-is-loading-slow-too-many-mb","children":[]},{"level":2,"title":"My UI is not rendering Text","slug":"my-ui-is-not-rendering-text","link":"#my-ui-is-not-rendering-text","children":[]},{"level":2,"title":"My scripts don't work after export","slug":"my-scripts-don-t-work-after-export","link":"#my-scripts-don-t-work-after-export","children":[]},{"level":2,"title":"My lightmaps look different / too bright","slug":"my-lightmaps-look-different-too-bright","link":"#my-lightmaps-look-different-too-bright","children":[]},{"level":2,"title":"My scene is too bright / lighting looks different than in Unity","slug":"my-scene-is-too-bright-lighting-looks-different-than-in-unity","link":"#my-scene-is-too-bright-lighting-looks-different-than-in-unity","children":[]},{"level":2,"title":"My skybox resolution is low? How to change my skybox resolution","slug":"my-skybox-resolution-is-low-how-to-change-my-skybox-resolution","link":"#my-skybox-resolution-is-low-how-to-change-my-skybox-resolution","children":[]},{"level":2,"title":"My Shadows are not visible or cut off","slug":"my-shadows-are-not-visible-or-cut-off","link":"#my-shadows-are-not-visible-or-cut-off","children":[]},{"level":2,"title":"My colors look wrong","slug":"my-colors-look-wrong","link":"#my-colors-look-wrong","children":[]},{"level":2,"title":"I'm using networking and Glitch and it doesn't work if more than 30 people visit the Glitch page at the same time","slug":"i-m-using-networking-and-glitch-and-it-doesn-t-work-if-more-than-30-people-visit-the-glitch-page-at-the-same-time","link":"#i-m-using-networking-and-glitch-and-it-doesn-t-work-if-more-than-30-people-visit-the-glitch-page-at-the-same-time","children":[]},{"level":2,"title":"My website doesn't have AR/VR buttons","slug":"my-website-doesn-t-have-ar-vr-buttons","link":"#my-website-doesn-t-have-ar-vr-buttons","children":[]},{"level":2,"title":"I created a new script in a sub-scene but it does not work","slug":"i-created-a-new-script-in-a-sub-scene-but-it-does-not-work","link":"#i-created-a-new-script-in-a-sub-scene-but-it-does-not-work","children":[]},{"level":2,"title":"My local server does not start / I do not see a website","slug":"my-local-server-does-not-start-i-do-not-see-a-website","link":"#my-local-server-does-not-start-i-do-not-see-a-website","children":[]},{"level":2,"title":"Does C# component generation work with javascript only too?","slug":"does-c-component-generation-work-with-javascript-only-too","link":"#does-c-component-generation-work-with-javascript-only-too","children":[]},{"level":2,"title":"I don't have any buttons like \\"Generate Project\\" in my components/inspector","slug":"i-don-t-have-any-buttons-like-generate-project-in-my-components-inspector","link":"#i-don-t-have-any-buttons-like-generate-project-in-my-components-inspector","children":[]},{"level":2,"title":"Toktx can not be found / toktx is not installed","slug":"toktx-can-not-be-found-toktx-is-not-installed","link":"#toktx-can-not-be-found-toktx-is-not-installed","children":[]},{"level":2,"title":"Installing the web project takes forever / does never finish / EONET: no such file or directory","slug":"installing-the-web-project-takes-forever-does-never-finish-eonet-no-such-file-or-directory","link":"#installing-the-web-project-takes-forever-does-never-finish-eonet-no-such-file-or-directory","children":[]},{"level":2,"title":"NPM install fails and there are errors about hard drive / IO","slug":"npm-install-fails-and-there-are-errors-about-hard-drive-io","link":"#npm-install-fails-and-there-are-errors-about-hard-drive-io","children":[]},{"level":2,"title":"I'm getting errors with \\"Unexpected token @. Expected identifier, string literal, numeric literal or ...\\"","slug":"i-m-getting-errors-with-unexpected-token-.-expected-identifier-string-literal-numeric-literal-or-...","link":"#i-m-getting-errors-with-unexpected-token-.-expected-identifier-string-literal-numeric-literal-or-...","children":[]},{"level":2,"title":"I'm getting an error 'failed to load config ... vite.config.js' when running npm commands on Mac OS","slug":"i-m-getting-an-error-failed-to-load-config-...-vite.config.js-when-running-npm-commands-on-mac-os","link":"#i-m-getting-an-error-failed-to-load-config-...-vite.config.js-when-running-npm-commands-on-mac-os","children":[]},{"level":2,"title":"Circular reference error","slug":"circular-reference-error","link":"#circular-reference-error","children":[]},{"level":2,"title":"My scene is not loading and the console contains a warning with 'circular references' or 'failed to update active state'","slug":"my-scene-is-not-loading-and-the-console-contains-a-warning-with-circular-references-or-failed-to-update-active-state","link":"#my-scene-is-not-loading-and-the-console-contains-a-warning-with-circular-references-or-failed-to-update-active-state","children":[]},{"level":2,"title":"Does my machine support WebGL 2?","slug":"does-my-machine-support-webgl-2","link":"#does-my-machine-support-webgl-2","children":[]},{"level":2,"title":"Still have questions? ๐ฑ","slug":"still-have-questions","link":"#still-have-questions","children":[]}],"git":{"updatedTime":1727125836000},"filePathRelative":"faq.md"}`);export{w as comp,v as data};
diff --git a/assets/features-overview.html-CBlEyL-B.js b/assets/features-overview.html-CBlEyL-B.js
new file mode 100644
index 000000000..4f259ddfa
--- /dev/null
+++ b/assets/features-overview.html-CBlEyL-B.js
@@ -0,0 +1 @@
+import{_ as p,r as a,o as u,c as m,a as t,d as n,b as o,w as r,e as d}from"./app-CRZRGfEE.js";const g={},f={class:"table-of-contents"};function b(h,e){const i=a("router-link"),l=a("RouteLink"),s=a("sample");return u(),m("div",null,[e[59]||(e[59]=t("h1",{id:"feature-overview",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#feature-overview"},[t("span",null,"Feature Overview")])],-1)),e[60]||(e[60]=t("p",null,[n("Needle Engine is a fully fledged 3D engine that runs in the browser. It comes with all the features you'd expect from a modern 3D engine, and more. If you haven't yet, take a look at our "),t("a",{href:"https://needle.tools",target:"_blank",rel:"noopener noreferrer"},"Homepage"),n(" and our "),t("a",{href:"https://engine.needle.tools/samples",target:"_blank",rel:"noopener noreferrer"},"Samples and Showcase"),n(".")],-1)),t("nav",f,[t("ul",null,[t("li",null,[o(i,{to:"#shaders-and-materials"},{default:r(()=>e[0]||(e[0]=[n("Shaders and Materials")])),_:1})]),t("li",null,[o(i,{to:"#crossplatform-vr-ar-mobile-desktop"},{default:r(()=>e[1]||(e[1]=[n("Crossplatform: VR, AR, Mobile, Desktop")])),_:1})]),t("li",null,[o(i,{to:"#lightmaps"},{default:r(()=>e[2]||(e[2]=[n("Lightmaps")])),_:1})]),t("li",null,[o(i,{to:"#multiplayer-and-networking"},{default:r(()=>e[3]||(e[3]=[n("Multiplayer and Networking")])),_:1})]),t("li",null,[o(i,{to:"#animations-and-sequencing"},{default:r(()=>e[4]||(e[4]=[n("Animations and Sequencing")])),_:1}),t("ul",null,[t("li",null,[o(i,{to:"#animator"},{default:r(()=>e[5]||(e[5]=[n("Animator")])),_:1})]),t("li",null,[o(i,{to:"#timeline"},{default:r(()=>e[6]||(e[6]=[n("Timeline")])),_:1})])])]),t("li",null,[o(i,{to:"#physics"},{default:r(()=>e[7]||(e[7]=[n("Physics")])),_:1})]),t("li",null,[o(i,{to:"#ui"},{default:r(()=>e[8]||(e[8]=[n("UI")])),_:1})]),t("li",null,[o(i,{to:"#particles"},{default:r(()=>e[9]||(e[9]=[n("Particles")])),_:1})]),t("li",null,[o(i,{to:"#postprocessing"},{default:r(()=>e[10]||(e[10]=[n("PostProcessing")])),_:1})]),t("li",null,[o(i,{to:"#editor-integrations"},{default:r(()=>e[11]||(e[11]=[n("Editor Integrations")])),_:1})]),t("li",null,[o(i,{to:"#scripting"},{default:r(()=>e[12]||(e[12]=[n("Scripting")])),_:1})]),t("li",null,[o(i,{to:"#and-there-is-more"},{default:r(()=>e[13]||(e[13]=[n("And there is more")])),_:1})])])]),e[61]||(e[61]=t("h2",{id:"shaders-and-materials",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#shaders-and-materials"},[t("span",null,"Shaders and Materials")])],-1)),e[62]||(e[62]=t("p",null,"Both PBR Materials and Custom shaders created with Shader Graph or other systems can be exported.",-1)),e[63]||(e[63]=t("img",{src:"https://user-images.githubusercontent.com/5083203/186012027-9bbe3944-fa56-41fa-bfbb-c989fa87aebb.png"},null,-1)),e[64]||(e[64]=t("p",null,[n("Use the node based "),t("a",{href:"https://unity.com/features/shader-graph",target:"_blank",rel:"noopener noreferrer"},"ShaderGraph"),n(" to create shaders for the web. ShaderGraph makes it easy for artists to keep creating without having to worry about syntax.")],-1)),t("p",null,[e[16]||(e[16]=n("Read more about ")),o(l,{to:"/export.html#physically-based-materials-pbr"},{default:r(()=>e[14]||(e[14]=[n("PBR Materials")])),_:1}),e[17]||(e[17]=n(" โข ")),o(l,{to:"/export.html#custom-shaders"},{default:r(()=>e[15]||(e[15]=[n("Custom Shaders")])),_:1})]),e[65]||(e[65]=t("h2",{id:"crossplatform-vr-ar-mobile-desktop",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#crossplatform-vr-ar-mobile-desktop"},[t("span",null,"Crossplatform: VR, AR, Mobile, Desktop")])],-1)),t("p",null,[e[19]||(e[19]=n("Needle Engine runs everywhere web technology does: run the same application on desktop, mobile, AR or VR. We build Needle Engine ")),o(l,{to:"/xr.html"},{default:r(()=>e[18]||(e[18]=[n("with XR in mind")])),_:1}),e[20]||(e[20]=n(" and consider this as and integral part of responsive webdesign!"))]),t("p",null,[e[22]||(e[22]=n("Use ")),o(l,{to:"/everywhere-actions.html"},{default:r(()=>e[21]||(e[21]=[n("Everywhere Actions")])),_:1}),e[23]||(e[23]=n(" for ")),e[24]||(e[24]=t("strong",null,"Interactive AR on both Android and iOS",-1)),e[25]||(e[25]=n("."))]),e[66]||(e[66]=d('
Lightmaps can be baked in Unity or Blender to easily add beautiful static light to your 3d content. Lightbaking for the web was never as easy. Just mark objects that you want to lightmap as static in Unity, add one or many lights to your scene (or use emissive materials) and click bake. Needle Engine will export your lightmaps per scene and automatically load and display them just as you see it in the Editor!
Note: There is no technical limitation on which lightmapper to use, as long as they end up in Unity's lightmapping data structures. Third party lightmappers such as Bakery thus are also supported.
Networking is built into the core runtime. Needle Engine deployments to Glitch come with a tiny server that allows you to deploy a multiplayer 3D environment in seconds. The built-in networked components make it easy to get started, and you can create your own synchronized components. Synchronizing variables and state is super easy!
Needle Engine brings powerful animations, state control and sequencing to the web โ from just playing a single animation to orchestrating and blending complex animations and character controllers. The Exporter can translate Unity components like Animator and Timeline into a web-ready format. We even added this functionality to our Blender addon so you can craft compatible animation state machines and export nla tracks as timelines to the web from within Blender too.
',10)),t("ul",null,[t("li",null,[e[27]||(e[27]=n("Read more about ")),o(l,{to:"/component-reference.html#animation"},{default:r(()=>e[26]||(e[26]=[n("Animation Components")])),_:1})])]),e[67]||(e[67]=d('
The Animator and AnimatorController components in Unity let you setup animations and define conditions for when and how to blend between them. We support exporting state machines, StateMachineBehaviours, transitions and layers. StateMachineBehaviours are also supported with OnStateEnter, OnStateUpdate and OnStateExit events.
Note: Sub-states and Blend Trees are not supported.
We're also translating Unity's Timeline setup and tracks into a web-ready format. Supported tracks include: AnimationTrack, AudioTrack, ActivationTrack, ControlTrack, SignalTrack.
',9)),t("ul",null,[t("li",null,[e[29]||(e[29]=n("Read more about ")),o(l,{to:"/component-reference.html#animation"},{default:r(()=>e[28]||(e[28]=[n("Animation Components")])),_:1})])]),e[68]||(e[68]=t("h2",{id:"physics",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#physics"},[t("span",null,"Physics")])],-1)),e[69]||(e[69]=t("p",null,"Use Rigidbodies, Mesh Colliders, Box Colliders and SphereColliders to add some juicy physics to your world.",-1)),t("ul",null,[t("li",null,[e[31]||(e[31]=n("Read more about ")),o(l,{to:"/component-reference.html#physics"},{default:r(()=>e[30]||(e[30]=[n("Physics Components")])),_:1})])]),o(s,{src:"https://engine.needle.tools/samples-uploads/physics-animation/"}),e[70]||(e[70]=t("h2",{id:"ui",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#ui"},[t("span",null,"UI")])],-1)),e[71]||(e[71]=t("p",null,"Building UI using Unity's UI canvas system is in development. Features currently include exporting Text (including fonts), Images, Buttons.",-1)),t("p",null,[e[33]||(e[33]=n("See the ")),o(l,{to:"/component-reference.html#ui"},{default:r(()=>e[32]||(e[32]=[n("ui component reference")])),_:1}),e[34]||(e[34]=n(" for supported component."))]),o(s,{src:"https://engine.needle.tools/samples-uploads/screenspace-ui"}),e[72]||(e[72]=t("h2",{id:"particles",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#particles"},[t("span",null,"Particles")])],-1)),e[73]||(e[73]=t("p",null,[n("Export of Unity ParticleSystem (Shuriken) is in development. Features currently include world/local space simulation, box and sphere emitter shapes, emission over time as well as burst emission, velocity- and color over time, emission by velocity, texturesheet animation, basic trails."),t("br"),n(" See a "),t("a",{href:"https://engine.needle.tools/samples/particles",target:"_blank",rel:"noopener noreferrer"},"live sample"),n(" of supported features below:")],-1)),o(s,{src:"https://engine.needle.tools/samples-uploads/particles/"}),e[74]||(e[74]=t("h2",{id:"postprocessing",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#postprocessing"},[t("span",null,"PostProcessing")])],-1)),t("p",null,[e[36]||(e[36]=n("Builtin effects include Bloom, Screenspace Ambient Occlusion, Depth of Field, Color Correction. You can also create your own custom effects. See ")),o(l,{to:"/component-reference.html#postprocessing"},{default:r(()=>e[35]||(e[35]=[n("the component reference")])),_:1}),e[37]||(e[37]=n(" for a complete list."))]),o(s,{src:"https://engine.needle.tools/samples-uploads/postprocessing/"}),e[75]||(e[75]=t("h2",{id:"editor-integrations",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#editor-integrations"},[t("span",null,"Editor Integrations")])],-1)),e[76]||(e[76]=t("p",null,[n("Needle Engine comes with powerful integrations into the Unity Editor and Blender."),t("br"),n(" It allows you to setup and export complex scenes in a visual way providing easy and flexible collaboration between artists and developers.")],-1)),e[77]||(e[77]=t("h2",{id:"scripting",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#scripting"},[t("span",null,"Scripting")])],-1)),t("p",null,[e[39]||(e[39]=n("Needle Engine uses as ")),o(l,{to:"/scripting.html#component-architecture"},{default:r(()=>e[38]||(e[38]=[n("component based workflow")])),_:1}),e[40]||(e[40]=n(". Create custom scripts in typescript or javascript. Use our ")),e[41]||(e[41]=t("a",{href:"https://fwd.needle.tools/needle-engine/docs/npmdef",target:"_blank",rel:"noopener noreferrer"},"modular npm-based package workflow",-1)),e[42]||(e[42]=n(" integrated into Unity. A ")),e[43]||(e[43]=t("a",{href:"https://fwd.needle.tools/needle-engine/docs/component-compiler",target:"_blank",rel:"noopener noreferrer"},"typescript to C# component compiler",-1)),e[44]||(e[44]=n(" produces Unity components magically on the fly."))]),e[78]||(e[78]=t("ul",null,[t("li",null,[n("Read more about "),t("a",{href:"scripting"},"Scripting Reference"),n(" โข "),t("a",{href:"https://fwd.needle.tools/needle-engine/docs/npmdef",target:"_blank",rel:"noopener noreferrer"},"Npm Definition Files")])],-1)),e[79]||(e[79]=t("h2",{id:"and-there-is-more",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#and-there-is-more"},[t("span",null,"And there is more")])],-1)),t("ul",null,[e[48]||(e[48]=t("li",null,"PostProcessing โ Bloom, Screenspace Ambient Occlusion, Depth of Field, Color Correction...",-1)),e[49]||(e[49]=t("li",null,"EditorSync โ Live synchronize editing in Unity to the running three.js application for local development",-1)),t("li",null,[e[46]||(e[46]=n("Interactive AR on iOS and Android โ Use our ")),o(l,{to:"/everywhere-actions.html"},{default:r(()=>e[45]||(e[45]=[n("Everywhere Actions")])),_:1}),e[47]||(e[47]=n(" feature set or build your own"))])]),e[80]||(e[80]=t("hr",null,null,-1)),e[81]||(e[81]=t("h1",{id:"where-to-go-next",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#where-to-go-next"},[t("span",null,"Where to go next")])],-1)),t("p",null,[e[51]||(e[51]=n("See our ")),o(l,{to:"/getting-started/"},{default:r(()=>e[50]||(e[50]=[n("Getting Started Guide")])),_:1}),e[52]||(e[52]=n(" to learn about how to download and set up Needle Engine.")),e[53]||(e[53]=t("br",null,null,-1)),e[54]||(e[54]=n(" Learn about ")),e[55]||(e[55]=t("a",{href:"vision"},"our vision",-1)),e[56]||(e[56]=n(" or dive deeper into some of the ")),e[57]||(e[57]=t("a",{href:"technical-overview"},"technical background and glTF",-1)),e[58]||(e[58]=n(" powering it all."))])])}const k=p(g,[["render",b],["__file","features-overview.html.vue"]]),w=JSON.parse('{"path":"/features-overview.html","title":"Feature Overview","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/features overview.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[{"level":2,"title":"Shaders and Materials","slug":"shaders-and-materials","link":"#shaders-and-materials","children":[]},{"level":2,"title":"Crossplatform: VR, AR, Mobile, Desktop","slug":"crossplatform-vr-ar-mobile-desktop","link":"#crossplatform-vr-ar-mobile-desktop","children":[]},{"level":2,"title":"Lightmaps","slug":"lightmaps","link":"#lightmaps","children":[]},{"level":2,"title":"Multiplayer and Networking","slug":"multiplayer-and-networking","link":"#multiplayer-and-networking","children":[]},{"level":2,"title":"Animations and Sequencing","slug":"animations-and-sequencing","link":"#animations-and-sequencing","children":[{"level":3,"title":"Animator","slug":"animator","link":"#animator","children":[]},{"level":3,"title":"Timeline","slug":"timeline","link":"#timeline","children":[]}]},{"level":2,"title":"Physics","slug":"physics","link":"#physics","children":[]},{"level":2,"title":"UI","slug":"ui","link":"#ui","children":[]},{"level":2,"title":"Particles","slug":"particles","link":"#particles","children":[]},{"level":2,"title":"PostProcessing","slug":"postprocessing","link":"#postprocessing","children":[]},{"level":2,"title":"Editor Integrations","slug":"editor-integrations","link":"#editor-integrations","children":[]},{"level":2,"title":"Scripting","slug":"scripting","link":"#scripting","children":[]},{"level":2,"title":"And there is more","slug":"and-there-is-more","link":"#and-there-is-more","children":[]}],"git":{"updatedTime":1727125836000},"filePathRelative":"features-overview.md"}');export{k as comp,w as data};
diff --git a/assets/for-unity-developers.html-9HXvx0qv.js b/assets/for-unity-developers.html-9HXvx0qv.js
new file mode 100644
index 000000000..2a695bad9
--- /dev/null
+++ b/assets/for-unity-developers.html-9HXvx0qv.js
@@ -0,0 +1,122 @@
+import{_ as p,r as h,o as r,c as o,e as l,a as s,d as e,b as t,w as n}from"./app-CRZRGfEE.js";const d={},c={class:"hint-container details"};function g(y,i){const a=h("RouteLink"),k=h("video-embed");return r(),o("div",null,[i[41]||(i[41]=l('
Needle Engine provides a tight integration into the Unity Editor. This allows developers and designers alike to work together in a familiar environment and deliver fast, performant and lightweight web-experiences.
The following guide is mainly aimed at developers with a Unity3D background but it may also be useful for developers with a web or three.js background. It covers topics regarding how things are done in Unity vs in three.js or Needle Engine.
If you are all new to Typescript and Javascript and you want to dive into writing scripts for Needle Engine then we also recommend reading the Typescript Essentials Guide for a basic understanding between the differences between C# and Javascript/Typescript.
If you want to code-along you can open engine.needle.tools/new to create a small project that you can edit in the browser โก
Needle Engine is a 3d web engine running on-top of three.js. Three.js is one of the most popular 3D webgl based rendering libraries for the web. Whenever we refer to a gameObject in Needle Engine we are actually also talking about a three.js Object3D, the base type of any object in three.js. Both terms can be used interchangeably. Any gameObjectis a Object3D.
This also means that - if you are already familiar with three.js - you will have no problem at all using Needle Engine. Everything you can do with three.js can be done in Needle Engine as well. If you are already using certain libraries then you will be able to also use them in a Needle Engine based environment.
',7)),s("p",null,[i[1]||(i[1]=e("Note: ")),i[2]||(i[2]=s("strong",null,[e("Needle Engine's Exporter does "),s("em",null,"NOT"),e(" compile your existing C# code to Web Assembly")],-1)),i[3]||(i[3]=e(".")),i[4]||(i[4]=s("br",null,null,-1)),i[5]||(i[5]=e(" While using Web Assembly ")),i[6]||(i[6]=s("em",null,"may",-1)),i[7]||(i[7]=e(" result in better performance at runtime, it comes at a high cost for iteration speed and flexibility in building web experiences. Read more about our ")),t(a,{to:"/getting-started/vision.html"},{default:n(()=>i[0]||(i[0]=[e("vision")])),_:1}),i[8]||(i[8]=e(" and ")),i[9]||(i[9]=s("a",{href:"./technical-overview"},"technical overview",-1)),i[10]||(i[10]=e("."))]),s("details",c,[i[11]||(i[11]=s("summary",null,"How to create a new Unity project with Needle Engine? (Video)",-1)),t(k,{src:"https://www.youtube.com/watch?v=gZX_sqrne8U",limit_height:""})]),i[42]||(i[42]=l(`
If you have seen some Needle Engine scripts then you might have noticed that some variables are annotated with @serializable above their declaration. This is a Decorator in Typescript and can be used to modify or annotate code. In Needle Engine this is used for example to let the core serialization know which types we expect in our script when it converts from the raw component information stored in the glTF to a Component instance. Consider the following example:
This tells Needle Engine that myOtherComponent should be of type Behaviour. It will then automatically assign the correct reference to the field when your scene is loaded. The same is true for someOtherObject where we want to deserialize to an Object3D reference.
Note that in some cases the type can be ommitted. This can be done for all primitive types in Javascript. These are boolean, number, bigint, string, null and undefined.
import { Behaviour, serializable } from "@needle-tools/engine";
+class SomeClass {
+ @serializable() // < no type is needed here because the field type is a primitive
+ myString?: string;
+}
To access the current scene from a component you use this.scene which is equivalent to this.context.scene, this gives you the root three.js scene object.
To traverse the hierarchy from a component you can either iterate over the children of an object with a for loop:
for (let i = 0; i < this.gameObject.children; i++) {
+ console.log(this.gameObject.children[i]);
+}
or you can iterate using the foreach equivalent:
for (const child of this.gameObject.children) {
+ console.log(child);
+}
You can also use three.js specific methods to quickly iterate all objects recursively using the traverse method:
or to just traverse visible objects use traverseVisible instead.
Another option that is quite useful when you just want to iterate objects being renderable you can query all renderer components and iterate over them like so:
import { Renderer } from "@needle-tools/engine";
+
+for(const renderer of this.gameObject.getComponentsInChildren(Renderer))
+ console.log(renderer);
For more information about getting components see the next section.
`,29)),s("p",null,[i[13]||(i[13]=e("Needle Engine is making heavy use of a Component System that is similar to that of Unity. This means that you can add or remove components to any ")),i[14]||(i[14]=s("code",null,"Object3D",-1)),i[15]||(i[15]=e(" / ")),i[16]||(i[16]=s("code",null,"GameObject",-1)),i[17]||(i[17]=e(" in the scene. A component will be registered to the engine when using ")),i[18]||(i[18]=s("code",null,"addNewComponent(, )",-1)),i[19]||(i[19]=e(".")),i[20]||(i[20]=s("br",null,null,-1)),i[21]||(i[21]=e(" The event methods that the attached component will then automatically be called by the engine (e.g. ")),i[22]||(i[22]=s("code",null,"update",-1)),i[23]||(i[23]=e(" or ")),i[24]||(i[24]=s("code",null,"onBeforeRender",-1)),i[25]||(i[25]=e("). A full list of event methods can be found in the ")),t(a,{to:"/scripting.html#lifecycle-methods"},{default:n(()=>i[12]||(i[12]=[e("scripting documentation")])),_:1})]),i[43]||(i[43]=l('
For getting component you can use the familiar methods similar to Unity. Note that the following uses the Animator type as an example but you can as well use any component type that is either built-in or created by you.
Method name
Desciption
this.gameObject.getComponent(Animator)
Get the Animator component on a GameObject/Object3D. It will either return the Animator instance if it has an Animator component or null if the object has no such componnent.
this.gameObject.getComponentInChildren(Animator)
Get the first Animator component on a GameObject/Object3D or on any of its children
this.gameObject.getComponentsInParents(Animator)
Get all animator components in the parent hierarchy (including the current GameObject/Object3D)
These methods are also available on the static GameObject type. For example GameObject.getComponent(this.gameObject, Animator) to get the Animator component on a passed in GameObject/Object3D.
To search the whole scene for one or multiple components you can use GameObject.findObjectOfType(Animator) or GameObject.findObjectsOfType(Animator).
Some Unity-specific types are mapped to different type names in our engine. See the following list:
Type in Unity
Type in Needle Engine
UnityEvent
EventList
A UnityEvent will be exported as a EventList type (use serializable(EventList) to deserialize UnityEvents)
GameObject
Object3D
Transform
Object3D
In three.js and Needle Engine a GameObject and a Transform are the same (there is no Transform component). The only exception to that rule is when referencing a RectTransform which is a component in Needle Engine as well.
Color
RGBAColor
The three.js color type doesnt have a alpha property. Because of that all Color types exported from Unity will be exported as RGBAColor which is a custom Needle Engine type
this.gameObject.quaternion - is the quaternion in local space
this.gameObject.scale - is the scale in local space
The major difference here to keep in mind is that position in three.js is by default a localspace position whereas in Unity position would be worldspace. The next section will explain how to get the worldspace position in three.js.
In three.js (and thus also in Needle Engine) the object.position, object.rotation, object.scale are all local space coordinates. This is different to Unity where we are used to position being worldspace and using localPosition to deliberately use the local space position.
If you want to access the world coordinates in Needle Engine we have utility methods that you can use with your objects. Call getWorldPosition(yourObject) to calculate the world position. Similar methods exist for rotation/quaternion and scale. To get access to those methods just import them from Needle Engine like so import { getWorldPosition } from "@needle.tools/engine"
',15)),s("p",null,[i[27]||(i[27]=e("Note that these utility methods like ")),i[28]||(i[28]=s("code",null,"getWorldPosition",-1)),i[29]||(i[29]=e(", ")),i[30]||(i[30]=s("code",null,"getWorldRotation",-1)),i[31]||(i[31]=e(", ")),i[32]||(i[32]=s("code",null,"getWorldScale",-1)),i[33]||(i[33]=e(" internally have a buffer of Vector3 instances and are meant to be used locally only. This means that you should not cache them in your component, otherwise your cached value will eventually be overriden. But it is safe to call ")),i[34]||(i[34]=s("code",null,"getWorldPosition",-1)),i[35]||(i[35]=e(" multiple times in your function to make calculations without having to worry to re-use the same instance. If you are not sure what this means you should take a look at the ")),i[36]||(i[36]=s("strong",null,"Primitive Types",-1)),i[37]||(i[37]=e(" section in the ")),t(a,{to:"/getting-started/typescript-essentials.html#primitive-types"},{default:n(()=>i[26]||(i[26]=[e("Typescript Essentials Guide")])),_:1})]),i[44]||(i[44]=l(`
Use this.context.physics.raycast() to perform a raycast and get a list of intersections. If you dont pass in any options the raycast is performed from the mouse position (or first touch position) in screenspace using the currently active mainCamera. You can also pass in a RaycastOptions object that has various settings like maxDistance, the camera to be used or the layers to be tested against.
Use this.context.physics.raycastFromRay(your_ray) to perform a raycast using a three.js ray
Note that the calls above are by default raycasting against visible scene objects. That is different to Unity where you always need colliders to hit objects. The default three.js solution has both pros and cons where one major con is that it can perform quite slow depending on your scene geometry. It may be especially slow when raycasting against skinned meshes. It is therefor recommended to usually set objects with SkinnedMeshRenderers in Unity to the Ignore Raycast layer which will then be ignored by default by Needle Engine as well.
Another option is to use the physics raycast methods which will only return hits with colliders in the scene.
const hit = this.context.physics.engine?.raycast();
You can also subscribe to events in the InputEvents enum like so:
import { Behaviour, InputEvents, NEPointerEvent } from "@needle-tools/engine";
+
+export class MyScript extends Behaviour
+{
+ onEnable(){
+ this.context.input.addEventListener(InputEvents.PointerDown, this.inputPointerDown);
+ }
+ onDisable() {
+ // it is recommended to also unsubscribe from events when your component becomes inactive
+ this.context.input.removeEventListener(InputEvents.PointerDown, this.inputPointerDown);
+ }
+
+ inputPointerDown = (evt: NEPointerEvent) => { console.log(evt); }
+}
If you want to handle inputs yourself you can also subscribe to all events the browser provides (there are a ton). For example to subscribe to the browsers click event you can write:
Note that in this case you have to handle all cases yourself. For example you may need to use different events if your user is visiting your website on desktop vs mobile vs a VR device. These cases are automatically handled by the Needle Engine input events (e.g. PointerDown is raised both for mouse down, touch down and in case of VR on controller button down).
The Debug.Log() equivalent in javascript is console.log(). You can also use console.warn() or console.error().
import { GameObject, Renderer } from "@needle-tools/engine";
+const someVariable = 42;
+// ---cut-before---
+
+console.log("Hello web");
+// You can pass in as many arguments as you want like so:
+console.log("Hello", someVariable, GameObject.findObjectOfType(Renderer), this.context);
In Unity you normally have to use special methods to draw Gizmos like OnDrawGizmos or OnDrawGizmosSelected. In Needle Engine on the other hand such methods dont exist and you are free to draw gizmos from anywhere in your script. Note that it is also your responsibility then to not draw them in e.g. your deployed web application (you can just filter them by if(isDevEnvironment))).
Here is an example to draw a red wire sphere for one second for e.g. visualizing a point in worldspace
import { Vector3 } from "three";
+const hit = { point: new Vector3(0, 0, 0) };
+// ---cut-before---
+import { Gizmos } from "@needle-tools/engine";
+Gizmos.DrawWireSphere(hit.point, 0.05, 0xff0000, 1);
Import from @needle-tools/engine e.g. import { getParam } from "@needle-tools/engine"
Method name
Description
getParam()
Checks if a url parameter exists. Returns true if it exists but has no value (e.g. ?help), false if it is not found in the url or is set to 0 (e.g. ?help=0), otherwise it returns the value (e.g. ?message=test)
isMobileDevice()
Returns true if the app is accessed from a mobile device
isDevEnvironment()
Returns true if the current app is running on a local server
isMozillaXR()
isiOS
isSafari
import { isMobileDevice } from "@needle-tools/engine"
+if( isMobileDevice() )
In C# you usually work with a solution containing one or many projects. In Unity this solution is managed by Unity for you and when you open a C# script it opens the project and shows you the file. You usually install Packages using Unity's built-in package manager to add features provided by either Unity or other developers (either on your team or e.g. via Unity's AssetStore). Unity does a great job of making adding and managing packages easy with their PackageManager and you might never have had to manually edit a file like the manifest.json (this is what Unity uses to track which packages are installed) or run a command from the command line to install a package.
In a web environment you use npm - the Node Package Manager - to manage dependencies / packages for you. It does basically the same to what Unity's PackageManager does - it installs (downloads) packages from some server (you hear it usually called a registry in that context) and puts them inside a folder named node_modules.
When working with a web project most of you dependencies are installed from npmjs.com. It is the most popular package registry out there for web projects.
Here is an example of how a package.json might look like:
Our default template uses Vite as its bundler and has no frontend framework pre-installed. Needle Engine is unoppionated about which framework to use so you are free to work with whatever framework you like. We have samples for popular frameworks like Vue.js, Svelte, Next.js, React or React Three Fiber.
To install a dependency from npm you can open your web project in a commandline (or terminal) and run npm i <the/package_name> (shorthand for npm install) For example run npm i @needle-tools/engine to install Needle Engine. This will then add the package to your package.json to the dependencies array. To install a package as a devDependency only you can run npm i --save-dev <package_name>. More about the difference between dependencies and devDependencies below.
You may have noticed that there are two entries containing dependency - dependencies and devDependencies.
dependencies are always installed (or bundled) when either your web project is installed or in cases where you develop a library and your package is installed as a dependency of another project.
devDependencies are only installed when developing the project (meaning that when you directly run install in the specific directory) and they are otherwise not included in your project.
The Installing section taught us that you can install dependencies by running npm i <package_name> in your project directory where the package_name can be any package that you find on npm.js.
Let's assume you want to add a tweening library to your project. We will use @tweenjs/tween.js for this example. Here is the final project if you want to jump ahead and just see the result.
First run npm install @tweenjs/tween.js in the terminal and wait for the installation to finish. This will add a new entry to our package.json:
Then open one of your script files in which you want to use tweening and import at the top of the file:
import * as TWEEN from '@tweenjs/tween.js';
Note that we do here import all types in the library by writing * as TWEEN. We could also just import specific types like import { Tween } from @tweenjs/tween.js.
Now we can use it in our script. It is always recommended to refer to the documentation of the library that you want to use. In the case of tween.js they provide a user guide that we can follow. Usually the Readme page of the package on npm contains information on how to install and use the package.
To rotate a cube we create a new component type called TweenRotation, we then go ahead and create our tween instance for the object rotation, how often it should repeat, which easing to use, the tween we want to perform and then we start it. We then only have to call update every frame to update the tween animation. The final script looks like this:
import { Behaviour } from "@needle-tools/engine";
+import * as TWEEN from '@tweenjs/tween.js';
+
+export class TweenRotation extends Behaviour {
+
+ // save the instance of our tweener
+ private _tween?: TWEEN.Tween<any>;
+
+ start() {
+ const rotation = this.gameObject.rotation;
+ // create the tween instance
+ this._tween = new TWEEN.Tween(rotation);
+ // set it to repeat forever
+ this._tween.repeat(Infinity);
+ // set the easing to use
+ this._tween.easing(TWEEN.Easing.Quintic.InOut);
+ // set the values to tween
+ this._tween.to({ y: Math.PI * 0.5 }, 1000);
+ // start it
+ this._tween.start();
+ }
+
+ update() {
+ // update the tweening every frame
+ // the '?' is a shorthand for checking if _tween has been created
+ this._tween?.update();
+ }
+}
Now we only have to add it to any of the objects in our scene to rotate them forever. You can see the final script in action here.
`,66)),s("ul",null,[i[40]||(i[40]=s("li",null,[s("a",{href:"../scripting"},"Scripting in Needle Engine")],-1)),s("li",null,[t(a,{to:"/getting-started/typescript-essentials.html"},{default:n(()=>i[38]||(i[38]=[e("Typescript Essentials")])),_:1})]),s("li",null,[t(a,{to:"/component-reference.html"},{default:n(()=>i[39]||(i[39]=[e("Component Reference")])),_:1})])])])}const F=p(d,[["render",g],["__file","for-unity-developers.html.vue"]]),u=JSON.parse(`{"path":"/getting-started/for-unity-developers.html","title":"Scripting Introduction for Unity Developers","lang":"en-US","frontmatter":{"title":"Scripting Introduction for Unity Developers","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/scripting introduction for unity developers.png"}],["meta",{"name":"og:description","content":"---\\nNeedle Engine provides a tight integration into the Unity Editor. This allows developers and designers alike to work together in a familiar environment and deliver fast, performant and lightweight web-experiences.\\nThe following guide is mainly aimed at developers with a Unity3D background but it may also be useful for developers with a web or three.js background. It covers topics regarding how things are done in Unity vs in three.js or Needle Engine.\\nIf you are all new to Typescript and Javascript and you want to dive into writing scripts for Needle Engine then we also recommend reading the Typescript Essentials Guide for a basic understanding between the differences between Cand Javascript/Typescript.\\nIf you want to code-along you can open engine.needle.tools/new to create a small project that you can edit in the browser โก\\nNeedle Engine is a 3d web engine running on-top of three.js. Three.js is one of the most popular 3D webgl based rendering libraries for the web. Whenever we refer to a gameObject in Needle Engine we are actually also talking about a three.js Object3D, the base type of any object in three.js. Both terms can be used interchangeably. Any gameObject is a Object3D.\\nThis also means that"}]],"description":"---\\nNeedle Engine provides a tight integration into the Unity Editor. This allows developers and designers alike to work together in a familiar environment and deliver fast, performant and lightweight web-experiences.\\nThe following guide is mainly aimed at developers with a Unity3D background but it may also be useful for developers with a web or three.js background. It covers topics regarding how things are done in Unity vs in three.js or Needle Engine.\\nIf you are all new to Typescript and Javascript and you want to dive into writing scripts for Needle Engine then we also recommend reading the Typescript Essentials Guide for a basic understanding between the differences between Cand Javascript/Typescript.\\nIf you want to code-along you can open engine.needle.tools/new to create a small project that you can edit in the browser โก\\nNeedle Engine is a 3d web engine running on-top of three.js. Three.js is one of the most popular 3D webgl based rendering libraries for the web. Whenever we refer to a gameObject in Needle Engine we are actually also talking about a three.js Object3D, the base type of any object in three.js. Both terms can be used interchangeably. Any gameObject is a Object3D.\\nThis also means that"},"headers":[{"level":2,"title":"The Basics","slug":"the-basics","link":"#the-basics","children":[]},{"level":2,"title":"Creating a Component","slug":"creating-a-component","link":"#creating-a-component","children":[]},{"level":2,"title":"Script Fields","slug":"script-fields","link":"#script-fields","children":[{"level":3,"title":"serializable","slug":"serializable","link":"#serializable","children":[]},{"level":3,"title":"public vs private","slug":"public-vs-private","link":"#public-vs-private","children":[]}]},{"level":2,"title":"GameObjects and the Scene","slug":"gameobjects-and-the-scene","link":"#gameobjects-and-the-scene","children":[]},{"level":2,"title":"Components","slug":"components","link":"#components","children":[]},{"level":2,"title":"Renamed Unity Types","slug":"renamed-unity-types","link":"#renamed-unity-types","children":[]},{"level":2,"title":"Transform","slug":"transform","link":"#transform","children":[{"level":3,"title":"WORLD- Position, Rotation, Scale...","slug":"world-position-rotation-scale...","link":"#world-position-rotation-scale...","children":[]}]},{"level":2,"title":"Time","slug":"time","link":"#time","children":[]},{"level":2,"title":"Raycasting","slug":"raycasting","link":"#raycasting","children":[]},{"level":2,"title":"Input","slug":"input","link":"#input","children":[]},{"level":2,"title":"InputSystem Callbacks","slug":"inputsystem-callbacks","link":"#inputsystem-callbacks","children":[]},{"level":2,"title":"Debug.Log","slug":"debug.log","link":"#debug.log","children":[]},{"level":2,"title":"Gizmos","slug":"gizmos","link":"#gizmos","children":[]},{"level":2,"title":"Useful Utility Methods","slug":"useful-utility-methods","link":"#useful-utility-methods","children":[]},{"level":2,"title":"The Web project","slug":"the-web-project","link":"#the-web-project","children":[]},{"level":2,"title":"Installing packages & dependencies","slug":"installing-packages-dependencies","link":"#installing-packages-dependencies","children":[{"level":3,"title":"What's the difference between 'dependencies' and 'devDependencies'","slug":"what-s-the-difference-between-dependencies-and-devdependencies","link":"#what-s-the-difference-between-dependencies-and-devdependencies","children":[]},{"level":3,"title":"How do I install another package or dependency and how to use it?","slug":"how-do-i-install-another-package-or-dependency-and-how-to-use-it","link":"#how-do-i-install-another-package-or-dependency-and-how-to-use-it","children":[]}]}],"git":{"updatedTime":1726585195000},"filePathRelative":"getting-started/for-unity-developers.md"}`);export{F as comp,u as data};
diff --git a/assets/for-unity-developers.html-Cn4ckO1w.js b/assets/for-unity-developers.html-Cn4ckO1w.js
new file mode 100644
index 000000000..258bc5ca4
--- /dev/null
+++ b/assets/for-unity-developers.html-Cn4ckO1w.js
@@ -0,0 +1 @@
+import{_ as n,o as a,c as o,a as e,d as r}from"./app-CRZRGfEE.js";const s={};function i(d,t){return a(),o("div",null,t[0]||(t[0]=[e("h1",{id:"this-page-has-been-moved-continue-here",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#this-page-has-been-moved-continue-here"},[e("span",null,[r("This page has been moved: "),e("a",{href:"./getting-started/for-unity-developers"},"continue here")])])],-1)]))}const c=n(s,[["render",i],["__file","for-unity-developers.html.vue"]]),p=JSON.parse('{"path":"/for-unity-developers.html","title":"Scripting basics For Unity Developers","lang":"en-US","frontmatter":{"title":"Scripting basics For Unity Developers","editLinks":false,"sidebar":false,"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/preview.jpeg"}],["meta",{"name":"og:description","content":"---"}]],"description":"---"},"headers":[],"git":{"updatedTime":1725399379000},"filePathRelative":"for-unity-developers.md"}');export{c as comp,p as data};
diff --git a/assets/getting-started.html-CpSyGMSg.js b/assets/getting-started.html-CpSyGMSg.js
new file mode 100644
index 000000000..45cfc47b7
--- /dev/null
+++ b/assets/getting-started.html-CpSyGMSg.js
@@ -0,0 +1 @@
+import{_ as a,r as o,o as r,c as s,a as i,d as e,b as d,w as g}from"./app-CRZRGfEE.js";const l={};function m(c,t){const n=o("RouteLink");return r(),s("div",null,[i("p",null,[t[1]||(t[1]=e("Moved to ")),d(n,{to:"/getting-started/"},{default:g(()=>t[0]||(t[0]=[e("Getting Started")])),_:1})])])}const _=a(l,[["render",m],["__file","getting-started.html.vue"]]),f=JSON.parse('{"path":"/getting-started.html","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/getting started.png"}],["meta",{"name":"og:description","content":")"}]],"description":")"},"headers":[],"git":{"updatedTime":1727125836000},"filePathRelative":"getting-started.md"}');export{_ as comp,f as data};
diff --git a/assets/github-star-DNJWumWy.js b/assets/github-star-DNJWumWy.js
new file mode 100644
index 000000000..bbe11bd3c
--- /dev/null
+++ b/assets/github-star-DNJWumWy.js
@@ -0,0 +1 @@
+import{h as r,i as a,j as n,k as l,l as h,_ as u,r as d,o as p,m as c,w as f,d as _}from"./app-CRZRGfEE.js";const $=r({name:"github-button",props:{href:String,ariaLabel:String,title:String,dataIcon:String,dataColorScheme:String,dataSize:String,dataShowCount:String,dataText:String},render:function(){const t={ref:"_"};for(const e in this.$props)t[a(e)]=this.$props[e];return n("span",[l(this.$slots,"default")?n("a",t,this.$slots.default()):n("a",t)])},mounted:function(){this.paint()},beforeUpdate:function(){this.reset()},updated:function(){this.paint()},beforeUnmount:function(){this.reset()},methods:{paint:function(){if(this.$el.lastChild!==this.$refs._)return;const t=this.$el.appendChild(document.createElement("span")),e=this;h(()=>import("./buttons.esm-CUWjhgaJ.js"),[]).then(function(i){e.$el.lastChild===t&&i.render(t.appendChild(e.$refs._),function(o){e.$el.lastChild===t&&t.parentNode.replaceChild(o,t)})})},reset:function(){this.$refs._!=null&&this.$el.replaceChild(this.$refs._,this.$el.lastChild)}}}),g={components:{GithubButton:$}};function m(t,e,i,o,b,S){const s=d("github-button");return p(),c(s,{href:"https://github.com/needle-tools/needle-engine-support","data-icon":"octicon-star","data-size":"large","data-show-count":"true","aria-label":"Star needle-tools/needle-engine-support on GitHub"},{default:f(()=>e[0]||(e[0]=[_("Star")])),_:1})}const x=u(g,[["render",m],["__file","github-star.vue"]]);export{x as default};
diff --git a/assets/html.html-DP7Dz9Ne.js b/assets/html.html-DP7Dz9Ne.js
new file mode 100644
index 000000000..16133abd7
--- /dev/null
+++ b/assets/html.html-DP7Dz9Ne.js
@@ -0,0 +1,35 @@
+import{_ as o}from"./custom-loading-style-s1K1my2z.js";import{_ as p,r,o as h,c as d,e as l,a as i,d as s,b as t,w as n}from"./app-CRZRGfEE.js";const k="/docs/imgs/unity-needle-engine-modules-physics.jpg",c={},g={class:"hint-container tip"};function u(y,e){const a=r("RouteLink");return h(),d("div",null,[e[24]||(e[24]=l('
Needle Engine is build as a web component. This means just install @needle-tools/engine in your project and include <needle-engine src="path/to/your.glb"> anywhere in your web-project.
Install using npm: npm i @needle-tools/engine
With our default Vite based project template Needle Engine gets bundled into a web app on deployment. This ensures smaller files, tree-shaking (similar to code stripping in Unity) and optimizes load times. Instead of downloading numerous small scripts and components, only one or a few are downloaded that contain the minimal code needed.
Vite (our default bundler) has a good explanation why web apps should be bundled: Why Bundle for Production
Needle Engine is unoponiated about the choice of framework. Our default template uses the popular vite as bundler. From there, you can add vue, svelte, nuxt, react, react-three-fiber or other frameworks, and we have samples for a lot of them. You can also integrate other bundlers, or use none at all โ just plain HTML and Javascript.
Here's some example tech stacks that are possible and that we use Needle Engine with:
',8)),i("ul",null,[e[3]||(e[3]=l('
Vite + HTML โ This is what our default template uses!
Vite + Vue โ This is what the Needle Tools website uses!. Find a sample to download here.
Vite + Svelte
Vite + SvelteKit
Vite + React โ There's an experimental template shipped with the Unity integration for this that you can pick when generating a project!
react-three-fiber โ There's an experimental template shipped with the Unity integration for this that you can pick when generating a project!
',7)),i("li",null,[i("p",null,[e[1]||(e[1]=i("strong",null,"CDN without any bundler",-1)),e[2]||(e[2]=s(" โ Find a code example ")),t(a,{to:"/vanilla-js.html"},{default:n(()=>e[0]||(e[0]=[s("here")])),_:1})])])]),e[25]||(e[25]=i("p",null,[s("In short: we're currently providing a minimal vite template, but you can extend it or switch to other frameworks โ"),i("br"),s(" Let us know what and how you build, and how we can improve the experience for your usecase or provide an example!")],-1)),i("div",g,[e[11]||(e[11]=i("p",{class:"hint-container-title"},"Tips",-1)),i("p",null,[e[5]||(e[5]=s("Some frameworks require custom settings in ")),e[6]||(e[6]=i("code",null,"needle.config.json",-1)),e[7]||(e[7]=s(". Learn more ")),t(a,{to:"/reference/needle-config-json.html"},{default:n(()=>e[4]||(e[4]=[s("here")])),_:1}),e[8]||(e[8]=s(". Typically, the ")),e[9]||(e[9]=i("code",null,"baseUrl",-1)),e[10]||(e[10]=s(" needs to be set."))])]),e[26]||(e[26]=l(`How do I create a custom project template in Unity?
You can create and share your own web project templates to use other bundlers, build systems, or none at all.
Create a new Template
Select Create/Needle Engine/Project Template to add a ProjectTemplate into the folder you want to use as a template
Done! It's that simple.
The dependencies come from unity when there is a NpmDef in the project (so when your project uses local references). You could also publish your packages to npm and reference them via version number.
Tree shaking refers to a common practice when it comes to bundling of web applications (see MSDN docs). It means that code paths and features that are not used in your code will be removed from the final bundled javascript file(s) to reduce filesize. See below about features that Needle Engine includes and remove them:
How to remove Rapier physics engine? (Reduce the overall bundle size removing ~2MB (~600KB when gzipping))
Option 1: via needlePlugins config: Set useRapier to false in your vite.config: needlePlugins(command, needleConfig, { useRapier: false }),
Option 2: via vite.define config: Declare the NEEDLE_USE_RAPIER define with false
define: {
+ NEEDLE_USE_RAPIER: false
+},
Option 3: via .env Create a .env file in your web project and add VITE_NEEDLE_USE_RAPIER=false
Option 4: via Unity component Add the Needle Engine Modules component to your scene and set Physics Engine to None
We support easily creating a Progressive Web App (PWA) directly from our vite template. PWAs are web applications that load like regular web pages or websites but can offer user functionality such as working offline, push notifications, and device hardware access traditionally available only to native mobile applications.
By default, PWAs created with Needle have offline support, and can optionally refresh automatically when you publish a new version of your app.
Install the Vite PWA plugin in your web project: npm install vite-plugin-pwa --save-dev
Modify vite.config.js as seen below. Make sure to pass the same pwaOptions object to both needlePlugins and VitePWA.
import { VitePWA } from 'vite-plugin-pwa';
+
+export default defineConfig(async ({ command }) => {
+
+ // Create the pwaOptions object.
+ // You can edit or enter PWA settings here (e.g. change the PWA name or add icons).
+ /** @type {import("vite-plugin-pwa").VitePWAOptions} */
+ const pwaOptions = {};
+
+ const { needlePlugins } = await import("@needle-tools/engine/plugins/vite/index.js");
+
+ return {
+ plugins: [
+ // pass the pwaOptions object to the needlePlugins and the VitePWA function
+ needlePlugins(command, needleConfig, { pwa: pwaOptions }),
+ VitePWA(pwaOptions),
+ ],
+ // the rest of your vite config...
All assets are cached by default
Note that by default, all assets in your build folder are added the PWA precache โ for large applications with many dynamic assets, this may not be what you want (imagine the YouTube PWA caching all videos once a user opens the app!). See More PWA Options for how to customize this behavior.
To test your PWA, deploy the page, for example using the DeployToFTP component. Then, open the deployed page in a browser and check if the PWA features work as expected:
the app shows up as installable
the app works offline
the app is detected as offline-capable PWA by pwabuilder.com
PWAs use Service Workers to cache resources and provide offline support. Service Workers are somewhat harder to use during development, and typically are only enabled for builds (e.g. when you use a DeployTo... component).
You can enable PWA support for development by adding the following to the options object in your vite.config.js.
const pwaOptions = {
+ // Note: PWAs behave different in dev mode.
+ // Make sure to verify the behaviour in production builds!
+ devOptions: {
+ enabled: true,
+ }
+};
Please note that PWAs in development mode do not support offline usage โ trying it may result in unexpected behavior.
Websites typically show new or updated content on page refresh.
In some situations, you may want the page to refresh and reload automatically when a new version has been published โ such as in a museum, trade show, public display, or other long-running scenarios.
To enable automatic updates, set the updateInterval property in the pwaOptions object to a duration (in milliseconds) in which the app should check for updates. If an update is detected, the page will reload automatically.
It's not recommended to use automatic reloads in applications where users are interacting with forms or other data that could be lost on a reload. For these applications, showing a reload prompt is recommended. See the Vite PWA plugin documentation for more information on how to implement a reload prompt instead of automatic reloading.
Since Needle uses the Vite PWA plugin under the hood, you can use all options and hooks provided by that. For example, you can provide a partial manifest with a custom app title or theme color:
const pwaOptions = {
+ // manifest options provided here will override the defaults
+ manifest: {
+ name: "My App",
+ short_name: "My App",
+ theme_color: "#B2D464",
+ }
+};
For complex requirements like partial caching, custom service workers or different update strategies, you can remove the { pwa: pwaOptions } option from needlePlugins and add PWA functionality directly through the Vite PWA plugin.
`,28)),i("p",null,[e[13]||(e[13]=s("Code that you expose can be accessed from JavaScript after bundling. This allows to build viewers and other applications where there's a split between data known at edit time and data only known at runtime (e.g. dynamically loaded files, user generated content).")),e[14]||(e[14]=i("br",null,null,-1)),e[15]||(e[15]=s(" For accessing components from regular javascript outside of the engine please refer to the ")),t(a,{to:"/scripting.html#accessing-needle-engine-and-components-from-anywhere"},{default:n(()=>e[12]||(e[12]=[s("interop with regular javascript section")])),_:1})]),e[27]||(e[27]=i("h2",{id:"customizing-how-loading-looks",tabindex:"-1"},[i("a",{class:"header-anchor",href:"#customizing-how-loading-looks"},[i("span",null,"Customizing how loading looks")])],-1)),i("p",null,[e[17]||(e[17]=s("See the ")),e[18]||(e[18]=i("em",null,"Loading Display",-1)),e[19]||(e[19]=s(" section in ")),t(a,{to:"/reference/needle-engine-attributes.html"},{default:n(()=>e[16]||(e[16]=[s("needle engine component reference")])),_:1})]),e[28]||(e[28]=l('
The needle-engine loading appearance can use a light or dark skin. To change the appearance use the loading-style attribute on the <needle-engine> web component. Options are light and dark (default):
',4)),t(p,null,{default:r(()=>[e("p",null,[i[1]||(i[1]=s("Make sure you have installed ")),i[2]||(i[2]=e("a",{target:"_blank",href:"https://www.blender.org/download/"},[e("strong",null,"Blender"),s(" 4.0, 4.1 or 4.2")],-1)),i[3]||(i[3]=s(" and ")),t(h,{windows_url:"https://nodejs.org/dist/v20.9.0/node-v20.9.0-x64.msi",osx_url:"https://nodejs.org/dist/v20.9.0/node-v20.9.0.pkg"},{default:r(()=>i[0]||(i[0]=[e("strong",null,"node.js",-1)])),_:1}),i[4]||(i[4]=s("."))])]),_:1}),t(k,null,{default:r(()=>[t(d,{event_goal:"download_blender",event_position:"getting_started",large:"",href:"https://engine.needle.tools/downloads/blender?utm_source=needle_docs&utm_content=getting_started",same_tab:"",next_url:"/docs/blender/"},{default:r(()=>i[5]||(i[5]=[e("strong",null,"Download Needle Engine for Blender",-1)])),_:1})]),_:1}),i[19]||(i[19]=a('
In Blender, go to File > Settings > Add-ons and click the Install button.
Select the downloaded zip file (named needle-blender-plugin-*.zip) to install it.
Search for "Needle" in the Add-ons search bar and make sure `Needle Engine.
With this add-on you can create highly interactive and optimized WebGL and WebXR experiences inside Blender, that run using Needle Engine and three.js.
You'll be able to sequence animations, easily lightmap your scenes, add interactivity or create your own scripts written in Typescript or Javascript that run on the web.
Matching lighting and environment settings between Blender and Needle Engine. HDRI environment lights are automatically exported, directly from Blender. Once you save, the page is automatically reloaded.
Providing Feedback
Your feedback is invaluable when it comes to deciding which features and workflows we should prioritize. If you have feedback for us (good or bad), please let us know in the forum!
First create or open a new blend file that you want to be exported to the web. Open the Properties window open the scene category. Select a Project Path in the Needle Engine panel. Then click Generate Project. It will automatically install and start the server - once it has finished your browser should open and the threejs scene will load.
By default your scene will automatically re-exported when you save the blend file. If the local server is running (e.g. by clicking Run Project) the website will automatically refresh with your changed model.
When your web project already exists and you just want to continue working on the website click the blue Run Project button to start the local server:
The path to your web project. You can use the little folder button on the right to select a different path.
The Run Project button shows up when the Project path shows to a valid web project. A web project is valid when it contains a package.json
Directory open the directory of your web project (the Project Path)
This button re-exports the current scene as a glb to your local web project. This also happens by default when saving your blend file.
Code Editor tries to open the vscode workspace in your web project
If you work with multiple scenes in one blend file, you can configure which scene is your Main scene and should be exported to the web. If any of your components references another scene they will also be exported as separate glb files. When clicking the "Export" button, your Main scene will be the one that's loaded in the browser.
Use the Build: Development or Build: Production buttons when you want to upload your web project to a server. This will bundle your web project and produce the files that you can upload. When clicking Build: Production it will also apply optimization to your textures (they will be compressed for the web)
By default the blender viewport is set to Filmic - with this setting your colors in Blender and in three.js will not match. To fix this go to the Blender Render category and in the ColorManagement panel select View Transform: Standard
You can change the environment lighting and skybox using the Viewport shading options. Assign a cubemap to use for lighting or the background skybox. You can adjust the strength or blur to modify the appearance to your liking.
Note: To also see the skybox cubemap in the browser increase the World Opacity to 1.
Note: Alternatively you can enable the Scene World setting in the Viewport Shading tab to use the environment texture assigned in the blender world settings.
',20)),t(n,{limit_height:"",max_height:"300px",src:"/docs/blender/environment.mp4"}),i[21]||(i[21]=e("p",null,[s("Alternatively if you don't want to see the cubemap as a background add a Camera component to your Blender Camera and change "),e("code",null,"clearFlags: SolidColor"),s(" - note that the Camera "),e("code",null,"backgroundBlurriness"),s(" and "),e("code",null,"backgroundIntensity"),s(" settings override the Viewport shading settings.")],-1)),i[22]||(i[22]=e("p",null,[e("img",{src:w,alt:"Environment Camera"})],-1)),i[23]||(i[23]=e("h3",{id:"add-your-custom-hdri-exr-environment-lighting-and-skybox",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#add-your-custom-hdri-exr-environment-lighting-and-skybox"},[e("span",null,"Add your custom HDRI / EXR environment lighting and skybox")])],-1)),t(n,{limit_height:"",src:"/docs/blender/custom_hdri.mp4"}),i[24]||(i[24]=a('
For simple usecases you can use the Animation component for playback of one or multiple animationclips. Just select your object, add an Animation component and assign the clip (you can add additional clips to be exported to the clips array. By default it will only playback the first clip assigned when playAutomatically is enabled. You can trigger the other clips using a simple custom typescript component)
',6)),t(n,{limit_height:"",src:"/docs/blender/animation.mp4"}),i[25]||(i[25]=s()),i[26]||(i[26]=e("h3",{id:"animatorcontroller",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#animatorcontroller"},[e("span",null,"AnimatorController")])],-1)),i[27]||(i[27]=e("p",null,"The animator controller can be created for more complex scenarios. It works as a statemachine which allows you to create multiple animation states in a graph and configure conditions and interpolation settings for transitioning between those.",-1)),t(n,{src:"/docs/blender/animatorcontroller-web.mp4"}),i[28]||(i[28]=e("p",null,[e("em",null,[s("Create and export "),e("a",{href:"#animatorcontroller"},"animator statemachines"),s(" for controlling complex character animations")])],-1)),i[29]||(i[29]=e("h4",{id:"creating-an-animatorcontroller",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#creating-an-animatorcontroller"},[e("span",null,"Creating an AnimatorController")])],-1)),i[30]||(i[30]=e("p",null,"The AnimatorController editor can be opened using the EditorType dropdown in the topleft corner of each panel:",-1)),i[31]||(i[31]=e("p",null,[e("img",{src:x,alt:"AnimatorController open window"})],-1)),t(n,{limit_height:"",max_height:"188px",src:"/docs/blender/animatorcontroller-create.mp4"}),i[32]||(i[32]=a('
Creating a new animator-controller asset โ or select one from your previously created assets
The Parameters node will be created once you add a first node. Select it to setup parameters to be used in transitions (via the Node panel on the right border)
This is an AnimatorState. the orange state is the start state (it can be changed using the Set default state button in the Node/Properties panel)
The Properties for an AnimatorState can be used to setup one or multiple transitions to other states. Use the Conditions array to select parameters that must match the condition for doing the transition.
To use an AnimatorController add an Animator component to the root object of your animations and select the AnimatorController asset that you want to use for this object.
You can set the Animator parameters from typescript or by e.g. using the event of a Button component
You can export Blender NLA tracks directly to the web. Add a PlayableDirector component (via Add Component) to a any blender object. Assign the objects in the animation tracks list in the component for which you want the NLA tracks to be exported.
Code example for interactive timeline playback
Add this script to src/scripts (see custom components section) and add it to any object in Blender to make a timeline's time be controlled by scrolling in the browsers
You can add or remove components to objects in your hierarchy using the Needle Components panel:
For example by adding an OrbitControls component to the camera object you get basic camera controls for mobile and desktop devicesAdjust settings for each component in their respective panels
Components can be removed using the X button in the lower right:
Custom components can also be easily added by simply writing Typescript classes. They will automatically compile and show up in Blender when saved.
To create custom components open the workspace via the Needle Project panel and add a .ts script file in src/scripts inside your web project. Please refer to the scripting documentation to learn how to write custom components for Needle Engine.
Note
Make sure @needle-tools/needle-component-compiler 2.x is installed in your web project (package.json devDependencies)
Needle includes a lightmapping plugin that makes it very easy to bake beautiful lights to textures and bring them to the web. The plugin will automatically generate lightmap UVs for all models marked to be lightmapped, there is no need to make a manual texture atlas. It also supports lightmapping of multiple instances with their own lightmap data. For lightmapping to work, you need at least one light and one object with Lightmapped turned on in the Needle Object panel.
You can download the .blend file from the video here.
Use the Needle Object panel to enable lightmapping for a mesh object or light:
For quick access to lightmap settings and baking options you can use the scene view panel in the Needle tab:
Alternatively you can also use the Lightmapping panel in the Render Properties tab:
Experimental Feature
The lightmapping plugin is experimental. We recommend creating a backup of your .blend file when using it. Please report problems or errors you encounter in our forum ๐
',10)),e("p",null,[i[9]||(i[9]=s("The Needle Engine Build Pipeline automatically compresses textures using ECT1S and UASTC (depending on their usage in materials) when making a production build (")),e("strong",null,[i[7]||(i[7]=s("requires ")),t(o,{to:"/getting-started/#install-these-tools-for-production-builds"},{default:r(()=>i[6]||(i[6]=[s("toktx")])),_:1}),i[8]||(i[8]=s(" being installed"))]),i[10]||(i[10]=s("). But you can override or change the compression type per texture in the Material panel."))]),e("p",null,[i[12]||(i[12]=s("You can modify the compression that is being applied per texture. To override the default compression settings go to the ")),i[13]||(i[13]=e("code",null,"Material",-1)),i[14]||(i[14]=s(" tab and open the ")),i[15]||(i[15]=e("code",null,"Needle Material Settings",-1)),i[16]||(i[16]=s(". There you will find a toggle to override the texture settings per texture used in your material. See the ")),t(o,{to:"/deployment.html#how-do-i-choose-between-etc1s-uastc-and-webp-compression"},{default:r(()=>i[11]||(i[11]=[s("texture compression table")])),_:1}),i[17]||(i[17]=s(" for a brief overview over the differences between each compression algorithm."))]),i[34]||(i[34]=a('
You can also automatically create and upload a bugreport directly from Blender. Uploaded bugreports will solely be used for debugging. They are encrypted on our backend and will be deleted after 30 days.
If needed, in certain cases we're also able to set up custom NDAs for your projects. Please contact us for more information.
Using the Bug Reporter requires a web project
Make sure you've set up a web project before sending a bug report โ it will allow us to understand more about your system and setup and make it easier to reproduce the issue.
',10))])}const V=g(M,[["render",U],["__file","index.html.vue"]]),z=JSON.parse('{"path":"/blender/","title":"Needle Engine for Blender","lang":"en-US","frontmatter":{"title":"Needle Engine for Blender","editLink":true,"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/preview.jpeg"}],["meta",{"name":"og:description","content":"---"}]],"description":"---"},"headers":[{"level":2,"title":"Install the Blender Add-on","slug":"install-the-blender-add-on","link":"#install-the-blender-add-on","children":[]},{"level":2,"title":"Getting Started","slug":"getting-started","link":"#getting-started","children":[]},{"level":2,"title":"Samples for Blender","slug":"samples-for-blender","link":"#samples-for-blender","children":[{"level":3,"title":"Project Panel overview","slug":"project-panel-overview","link":"#project-panel-overview","children":[]}]},{"level":2,"title":"Blender Settings","slug":"blender-settings","link":"#blender-settings","children":[{"level":3,"title":"Color Management","slug":"color-management","link":"#color-management","children":[]}]},{"level":2,"title":"Environment Lighting","slug":"environment-lighting","link":"#environment-lighting","children":[{"level":3,"title":"Add your custom HDRI / EXR environment lighting and skybox","slug":"add-your-custom-hdri-exr-environment-lighting-and-skybox","link":"#add-your-custom-hdri-exr-environment-lighting-and-skybox","children":[]}]},{"level":2,"title":"Export","slug":"export","link":"#export","children":[]},{"level":2,"title":"Animation ๐","slug":"animation","link":"#animation","children":[{"level":3,"title":"AnimatorController","slug":"animatorcontroller","link":"#animatorcontroller","children":[]},{"level":3,"title":"Timeline โ NLA Tracks export ๐ฌ","slug":"timeline-nla-tracks-export","link":"#timeline-nla-tracks-export","children":[]}]},{"level":2,"title":"Interactivity ๐","slug":"interactivity","link":"#interactivity","children":[{"level":3,"title":"Custom Components","slug":"custom-components","link":"#custom-components","children":[]}]},{"level":2,"title":"Lightmapping ๐ก","slug":"lightmapping","link":"#lightmapping","children":[]},{"level":2,"title":"Texture Compression","slug":"texture-compression","link":"#texture-compression","children":[]},{"level":2,"title":"Updating","slug":"updating","link":"#updating","children":[]},{"level":2,"title":"Reporting an issue","slug":"reporting-an-issue","link":"#reporting-an-issue","children":[{"level":3,"title":"Integrated Bug Reporter","slug":"integrated-bug-reporter","link":"#integrated-bug-reporter","children":[]}]}],"git":{"updatedTime":1727182460000},"filePathRelative":"blender/index.md"}');export{V as comp,z as data};
diff --git a/assets/index.html-CxWg-amX.js b/assets/index.html-CxWg-amX.js
new file mode 100644
index 000000000..09003bf5b
--- /dev/null
+++ b/assets/index.html-CxWg-amX.js
@@ -0,0 +1 @@
+import{_ as g,r as a,o as m,c as f,e as c,b as n,w as i,a as e,d as o}from"./app-CRZRGfEE.js";const h="/docs/imgs/banner.webp",b={};function w(y,t){const r=a("quoteslides"),s=a("action"),l=a("actiongroup"),d=a("copyright"),u=a("removeserviceworker"),p=a("ClientOnly");return m(),f("div",null,[t[6]||(t[6]=c('
Needle Engine is a web engine for complex and simple 3D applications alike. Work on your machine and deploy anywhere. Needle Engine is flexible, extensible and has built-in support for collaboration and XR. It is built around the glTF standard for 3D assets.
Powerful integrations for Unity and Blender allow artists and developers to collaborate and manage web applications inside battle-tested 3d editors. These Integrations allow you to use editor features for creating models, authoring materials, animating and sequencing animations, baking lightmaps and more with ease.
Our powerful compression and optimization pipeline for the web make sure your files are ready, small and load fast.
',4)),n(r,null,{default:i(()=>t[0]||(t[0]=[e("div",null,[o("Unbelievable Unity editor integration by an order of magnitude,"),e("br"),o("and as straightforward as the docs claim. Wow. โ Chris Mahoney")],-1),e("div",null,"This is the best thing I have seen after cinemachine in Unity โ Rinesh Thomas",-1),e("div",null,[o("Spent the last 2.5 months building this game, never built a game or used Unity before"),e("br"),o("but absolutely loving the whole process. So rapid! โ Matthew Pieri")],-1),e("div",null,[e("a",{href:"https://needle.tools?utm_source=needle_docs&utm_content=quote"},"needle.tools"),o(" is a wonderful showcase of what Needle contributes to 3D via the web. I just love it. โ Kevin Curry")],-1),e("div",null,"Played with this a bit this morning ๐คฏ๐คฏ pretty magical โ Brit Gardner",-1),e("div",null,"This is huge for WebXR and shared, immersive 3D experiences! The AR part worked flawlessly on my Samsung S21. โ Marc Wakefield",-1),e("div",null,"This is amazing and if you are curious about WebXR with Unity this will help us get there โ Dilmer Valecillos",-1),e("div",null,"We just gotta say WOW ๐คฉ โ Unity for Digital Twins",-1)])),_:1}),n(l,null,{default:i(()=>[n(s,{href:"getting-started/"},{default:i(()=>t[1]||(t[1]=[o(" Get started โญ ")])),_:1}),n(s,{href:"features-overview"},{default:i(()=>t[2]||(t[2]=[o(" Features ๐จ ")])),_:1}),n(s,{href:"https://engine.needle.tools/samples?utm_source=needle_docs&utm_content=actionbutton"},{default:i(()=>t[3]||(t[3]=[o(" Samples ๐ ")])),_:1}),n(s,{subtitle:"with AI support",href:"https://forum.needle.tools?utm_source=needle_docs&utm_content=actionbutton"},{default:i(()=>t[4]||(t[4]=[o(" Get Help ๐ฌ ")])),_:1})]),_:1}),t[7]||(t[7]=e("br",null,null,-1)),t[8]||(t[8]=e("br",null,null,-1)),n(l,null,{default:i(()=>t[5]||(t[5]=[e("p",null,[e("a",{class:"no-external-link-icon",href:"https://www.npmjs.com/package/@needle-tools/engine"},[e("img",{src:"https://img.shields.io/npm/v/@needle-tools/engine?style=flat&colorA=ddd&colorB=ddd"})])],-1),e("p",null,[e("a",{class:"no-external-link-icon",href:"https://engine.needle.tools/docs/getting-started/"},[e("img",{src:"https://img.shields.io/npm/dt/@needle-tools/engine.svg?style=flat&colorA=ddd&colorB=ddd"})])],-1),e("p",null,[e("a",{class:"no-external-link-icon",href:"https://discord.needle.tools"},[e("img",{src:"https://img.shields.io/discord/717429793926283276?style=flat&colorA=ddd&colorB=ddd&label=discord&logo=discord&logoColor=ffffff"})])],-1)])),_:1}),t[9]||(t[9]=e("p",null,null,-1)),n(d),n(p,null,{default:i(()=>[n(u)]),_:1})])}const k=g(b,[["render",w],["__file","index.html.vue"]]),x=JSON.parse('{"path":"/","title":"","lang":"en-US","frontmatter":{"next":"./getting-started/index.md","sidebar":false,"editLink":false,"lastUpdated":false,"footer":"Copyright ยฉ 2024 Needle Tools GmbH","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/needle engine.png"}],["meta",{"name":"og:description","content":"---\\nNeedle Engine is a web engine for complex and simple 3D applications alike. Work on your machine and deploy anywhere. Needle Engine is flexible, extensible and has built-in support for collaboration and XR. It is built around the glTF standard for 3D assets.\\nPowerful integrations for Unity and Blender allow artists and developers to collaborate and manage web applications inside battle-tested 3d editors. These Integrations allow you to use editor features for creating models, authoring materials, animating and sequencing animations, baking lightmaps and more with ease.\\nOur powerful compression and optimization pipeline for the web make sure your files are ready, small and load fast."}]],"description":"---\\nNeedle Engine is a web engine for complex and simple 3D applications alike. Work on your machine and deploy anywhere. Needle Engine is flexible, extensible and has built-in support for collaboration and XR. It is built around the glTF standard for 3D assets.\\nPowerful integrations for Unity and Blender allow artists and developers to collaborate and manage web applications inside battle-tested 3d editors. These Integrations allow you to use editor features for creating models, authoring materials, animating and sequencing animations, baking lightmaps and more with ease.\\nOur powerful compression and optimization pipeline for the web make sure your files are ready, small and load fast."},"headers":[],"git":{"updatedTime":1727191885000},"filePathRelative":"index.md"}');export{k as comp,x as data};
diff --git a/assets/index.html-DyXM_9kx.js b/assets/index.html-DyXM_9kx.js
new file mode 100644
index 000000000..e426b9b21
--- /dev/null
+++ b/assets/index.html-DyXM_9kx.js
@@ -0,0 +1 @@
+import{_ as u,r as s,o as p,c as g,a as t,d as o,b as n,e as m,w as l}from"./app-CRZRGfEE.js";const f={};function y(b,e){const d=s("tool-tiles"),r=s("os-link"),i=s("RouteLink"),a=s("ClientOnly");return p(),g("div",null,[e[30]||(e[30]=t("br",null,null,-1)),e[31]||(e[31]=t("h1",{id:"downloads",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#downloads"},[t("span",null,"Downloads")])],-1)),e[32]||(e[32]=t("p",null,[o("With "),t("strong",null,"Needle Engine"),o(", you can create fully interactive 3D websites. They can be deployed anywhere on the web and get optimized automatically by the "),t("strong",null,"Needle Engine Build Pipeline"),o(" reducing asset size by up x100 without compromising quality.")],-1)),e[33]||(e[33]=t("p",null,"Needle Engine is available as a package for Unity, add-on for Blender, as a ready-to-go Web Component, and as npm package for projects without an editor integration. Each of these comes with the same components and building blocks โ the choice is yours.",-1)),e[34]||(e[34]=t("h2",{id:"choose-your-workflow",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#choose-your-workflow"},[t("span",null,"Choose your Workflow")])],-1)),n(d),e[35]||(e[35]=m('
Needle Engine makes it easy to build web apps. That often, but not always, includes coding with JavaScript/TypeScript or writing HTML and CSS to describe user interfacces. We recommend Visual Studio Code for creating and editing these files. It's a free, open-source code editor that runs on Windows, macOS, and Linux.
',5)),n(a,null,{default:l(()=>[n(r,{windows_url:"https://code.visualstudio.com/sha/download?build=stable&os=win32-x64-user",osx_url:"https://code.visualstudio.com/sha/download?build=stable&os=darwin-universal"},{default:l(()=>e[0]||(e[0]=[o("Download Visual Studio Code")])),_:1}),e[7]||(e[7]=t("br",null,null,-1)),e[8]||(e[8]=t("br",null,null,-1)),e[9]||(e[9]=t("h3",{id:"other-useful-tools",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#other-useful-tools"},[t("span",null,"Other useful tools")])],-1)),e[10]||(e[10]=t("div",{class:"hint-container tip"},[t("p",{class:"hint-container-title"},"Tips"),t("p",null,"Needle Engine uses the following tools to create your web app, but you don't need to manually install them when using the Unity or Blender integration. We'll guide you through the installation process after you've installed the Needle integration.")],-1)),e[11]||(e[11]=t("br",null,null,-1)),n(r,{windows_url:"https://nodejs.org/dist/v20.9.0/node-v20.9.0-x64.msi",osx_url:"https://nodejs.org/dist/v20.9.0/node-v20.9.0.pkg"},{default:l(()=>e[1]||(e[1]=[o("Node.js 18 LTS or 20 LTS.")])),_:1}),e[12]||(e[12]=o(" Needle Engine uses Node.js to manage, preview and build the web app that you are creating locally on your computer. It is also used for uploading (deploying) your website to the internet. ")),t("p",null,[e[4]||(e[4]=t("br",null,null,-1)),n(r,{windows_url:"https://fwd.needle.tools/needle-engine/toktx/win",osx_url:"https://fwd.needle.tools/needle-engine/toktx/osx",osx_silicon_url:"https://fwd.needle.tools/needle-engine/toktx/osx-silicon"},{default:l(()=>e[2]||(e[2]=[o("KTX Software โ toktx texture tools.")])),_:1}),e[5]||(e[5]=o(" We use toktx by the Khronos Group to locally optimize and compress your 3D files. Learn more about production builds ")),n(i,{to:"/deployment.html#production-builds"},{default:l(()=>e[3]||(e[3]=[o("in the docs")])),_:1}),e[6]||(e[6]=o("."))]),e[13]||(e[13]=t("br",null,null,-1))]),_:1}),e[36]||(e[36]=t("h2",{id:"next-steps",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#next-steps"},[t("span",null,"Next Steps")])],-1)),e[37]||(e[37]=t("p",null,"Now that you've installed Needle Engine, you're ready to dive deeper into project creation, component workflows, scripting, deployment and more.",-1)),t("ul",null,[e[20]||(e[20]=t("li",null,[t("a",{href:"https://engine.needle.tools/samples",target:"_blank",rel:"noopener noreferrer"},"Needle Engine Samples")],-1)),t("li",null,[n(i,{to:"/export.html"},{default:l(()=>e[14]||(e[14]=[o("Exporting 3D objects and content")])),_:1})]),t("li",null,[n(i,{to:"/project-structure.html"},{default:l(()=>e[15]||(e[15]=[o("Project Structure")])),_:1})]),t("li",null,[n(i,{to:"/deployment.html"},{default:l(()=>e[16]||(e[16]=[o("Deploy your website to the web")])),_:1})]),t("li",null,[n(i,{to:"/getting-started/typescript-essentials.html"},{default:l(()=>e[17]||(e[17]=[o("Typescript Essentials")])),_:1})]),t("li",null,[n(i,{to:"/getting-started/for-unity-developers.html"},{default:l(()=>e[18]||(e[18]=[o("Needle Engine for Unity Developers")])),_:1})]),t("li",null,[n(i,{to:"/scripting.html"},{default:l(()=>e[19]||(e[19]=[o("Scripting Reference")])),_:1})])]),t("p",null,[e[22]||(e[22]=o("In case you need troubleshooting help, please see the ")),n(i,{to:"/faq.html"},{default:l(()=>e[21]||(e[21]=[o("Questions and Answers โ FAQ")])),_:1}),e[23]||(e[23]=o(" section.")),e[24]||(e[24]=t("br",null,null,-1)),e[25]||(e[25]=o(" We welcome you to join our ")),e[26]||(e[26]=t("a",{href:"https://forum.needle.tools",target:"_blank",rel:"noopener noreferrer"},"Forum",-1)),e[27]||(e[27]=o(" and ")),e[28]||(e[28]=t("a",{href:"https://discord.needle.tools",target:"_blank",rel:"noopener noreferrer"},"Discord Community",-1)),e[29]||(e[29]=o("."))])])}const h=u(f,[["render",y],["__file","index.html.vue"]]),x=JSON.parse('{"path":"/getting-started/","title":"Getting Started & Installation","lang":"en-US","frontmatter":{"lang":"en-US","title":"Getting Started & Installation","next":"../project-structure.md","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/getting started & installation.png"}],["meta",{"name":"og:description","content":"---\\nWith Needle Engine, you can create fully interactive 3D websites.\\nThey can be deployed anywhere on the web and get optimized automatically by the Needle Engine Build Pipeline reducing asset size by up x100 without compromising quality.\\nNeedle Engine is available as a package for Unity, add-on for Blender, as a ready-to-go Web Component, and as npm package for projects without an editor integration. Each of these comes with the same components and building blocks โ the choice is yours."}]],"description":"---\\nWith Needle Engine, you can create fully interactive 3D websites.\\nThey can be deployed anywhere on the web and get optimized automatically by the Needle Engine Build Pipeline reducing asset size by up x100 without compromising quality.\\nNeedle Engine is available as a package for Unity, add-on for Blender, as a ready-to-go Web Component, and as npm package for projects without an editor integration. Each of these comes with the same components and building blocks โ the choice is yours."},"headers":[{"level":2,"title":"Choose your Workflow","slug":"choose-your-workflow","link":"#choose-your-workflow","children":[]},{"level":2,"title":"Code Editor and Tools","slug":"code-editor-and-tools","link":"#code-editor-and-tools","children":[{"level":3,"title":"Install a code editor","slug":"install-a-code-editor","link":"#install-a-code-editor","children":[]},{"level":3,"title":"Other useful tools","slug":"other-useful-tools","link":"#other-useful-tools","children":[]}]},{"level":2,"title":"Next Steps","slug":"next-steps","link":"#next-steps","children":[]}],"git":{"updatedTime":1727189596000},"filePathRelative":"getting-started/index.md"}');export{h as comp,x as data};
diff --git a/assets/index.html-Msujlet7.js b/assets/index.html-Msujlet7.js
new file mode 100644
index 000000000..2e162fd40
--- /dev/null
+++ b/assets/index.html-Msujlet7.js
@@ -0,0 +1 @@
+import{_ as n,o,c as a,a as t}from"./app-CRZRGfEE.js";const i={};function s(r,e){return o(),a("div",null,e[0]||(e[0]=[t("h1",{id:"integrate-with-other-tools",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#integrate-with-other-tools"},[t("span",null,"Integrate with other tools")])],-1),t("div",{class:"hint-container warning"},[t("p",{class:"hint-container-title"},"This page is under construction.")],-1)]))}const l=n(i,[["render",s],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/custom-integrations/","title":"Integrate with other tools","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/custom integrations.png"}],["meta",{"name":"og:description","content":":"}]],"description":":"},"headers":[],"git":{"updatedTime":1727182460000},"filePathRelative":"custom-integrations/index.md"}');export{l as comp,d as data};
diff --git a/assets/index.html-_acHVPU1.js b/assets/index.html-_acHVPU1.js
new file mode 100644
index 000000000..af7d05e72
--- /dev/null
+++ b/assets/index.html-_acHVPU1.js
@@ -0,0 +1,73 @@
+import{_ as k,r as l,o as r,c as d,e as h,b as e,w as n,a as i,d as s}from"./app-CRZRGfEE.js";const o="/docs/logo.png",g="/docs/imgs/logo-webcomponents.png",c="/docs/imgs/threejs-logo.webp",y={};function E(C,a){const t=l("CodeGroupItem"),p=l("CodeGroup");return r(),d("div",null,[a[2]||(a[2]=h('
Needle Engine provides an easy-to-use web component that can be used to display rich, interactive 3D scenes directly in HTML with just a few lines of code. It's the same web component that powers our integrations.
Once you outgrow the configuration options of the web component, you can extend it with custom scripts and components, and full programmatic scene graph access.
Use the integrations!
For complex 3D scenes and fast iteration, we recommend using Needle Engine with one of our integrations. They provide a powerful creation workflow, including a live preview, hot reloading, and an advanced build pipeline with 3D optimizations.
You can work directly with Needle Engine without using any Integration. Needle Engine uses three.js as scene graph and rendering library, so all functionality from three.js is available in Needle as well.
You can install Needle Engine from npm by running:
While our default template uses vite, Needle Engine can also be used directly with vanilla Javascript โ without using any bundler.
You can add a complete, prebundled version of Needle Engine to your website with just a line of code. This includes our core components, physics, particles, networking, XR, and more. Use this if you're not sure!
If you know your project doesn't require physics features, you can also use a smaller version of Needle Engine, without the physics engine. This will reduce the total downloaded size.
If you want to work with Needle Engine without any integration, then you'll likely want to run a local server for your website. Here's how you can do that with Visual Studio Code:
Open the folder with your HTML file in Visual Studio Code.
Since Needle Engine uses three.js as scene graph and rendering library, all functionality from three.js is available in Needle as well and can be used from component scripts. We're using a fork of three.js that includes additional features and improvements, especially in relation to WebXR, Animation, and USDZ export.
Tips
Make sure to update the <needle-engine src="myScene.glb"> path to an existing glb file or download this sample glb and put it in the same folder as the index.html, name it myScene.glb or update the src path.
<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8" />
+ <link rel="icon" href="favicon.ico">
+ <meta name="viewport" content="width=device-width, user-scalable=no">
+ <title>Made with Needle</title>
+
+ <!-- importmap -->
+ <script type="importmap">
+ {
+ "imports": {
+ "three": "https://unpkg.com/three/build/three.module.js",
+ "three/": "https://unpkg.com/three/"
+ }
+ }
+ </script>
+ <!-- parcel require must currently defined somewhere for peerjs -->
+ <script> var parcelRequire; </script>
+
+ <!-- the .light version does not include dependencies like Rapier.js (so no physics) to reduce the bundle size, if your project needs physics then just change it to needle-engine.min.js -->
+ <script type="module" src="https://unpkg.com/@needle-tools/engine@3.5.6-alpha/dist/needle-engine.light.min.js"></script>
+
+ <style>
+ body { margin: 0; }
+ needle-engine {
+ position: absolute;
+ display: block;
+ width: 100%;
+ height: 100%;
+ background: grey;
+ }
+ </style>
+
+</head>
+
+<body>
+
+ <!-- load a gltf or glb here as your scene and listen to the finished event to start interacting with it -->
+ <needle-engine src="myScene.glb" loadfinished="onLoaded"></needle-engine>
+
+</body>
+
+<script>
+ function onLoaded(ctx) {
+ console.log("Loading a glb file finished ๐");
+ console.log("This is the scene: ", ctx.scene);
+ }
+</script>
+
+</html>
`,23))])}const m=k(y,[["render",E],["__file","index.html.vue"]]),B=JSON.parse('{"path":"/three/","title":"Needle Engine as Web Component","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/three.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[{"level":3,"title":"Quick Start","slug":"quick-start","link":"#quick-start","children":[]},{"level":2,"title":"Install from npm","slug":"install-from-npm","link":"#install-from-npm","children":[]},{"level":2,"title":"Install needle-engine from a CDN","slug":"install-needle-engine-from-a-cdn","link":"#install-needle-engine-from-a-cdn","children":[]},{"level":2,"title":"Rapid Prototyping on StackBlitz","slug":"rapid-prototyping-on-stackblitz","link":"#rapid-prototyping-on-stackblitz","children":[]},{"level":2,"title":"Rapid Prototyping on Glitch","slug":"rapid-prototyping-on-glitch","link":"#rapid-prototyping-on-glitch","children":[]},{"level":2,"title":"Local Development with VS Code","slug":"local-development-with-vs-code","link":"#local-development-with-vs-code","children":[]},{"level":2,"title":"three.js and Needle Engine","slug":"three.js-and-needle-engine","link":"#three.js-and-needle-engine","children":[]}],"git":{"updatedTime":1727707190000},"filePathRelative":"three/index.md"}');export{m as comp,B as data};
diff --git a/assets/index.html-gQ6khto9.js b/assets/index.html-gQ6khto9.js
new file mode 100644
index 000000000..9bae3b700
--- /dev/null
+++ b/assets/index.html-gQ6khto9.js
@@ -0,0 +1 @@
+import{_ as c,r as n,o as m,c as h,e as a,b as r,w as i,a as t,d as o}from"./app-CRZRGfEE.js";const u="/docs/imgs/unity-logo.webp",g="/docs/imgs/unity-project-local-template.jpg",f="/docs/imgs/unity-project-remote-template.jpg",y={};function b(w,e){const l=n("needle-button"),s=n("NoDownloadYet"),p=n("video-embed"),d=n("RouteLink");return m(),h("div",null,[e[11]||(e[11]=a('
',4)),r(s,null,{default:i(()=>[e[1]||(e[1]=t("br",null,null,-1)),r(l,{event_goal:"download_unity",event_position:"getting_started",large:"",href:"https://engine.needle.tools/downloads/unity?utm_source=needle_docs&utm_content=getting_started",same_tab:"",next_url:"/docs/unity/"},{default:i(()=>e[0]||(e[0]=[t("strong",null,"Download Needle Engine for Unity",-1)])),_:1})]),_:1}),e[12]||(e[12]=a('
Drop the downloaded .unitypackage file into a Unity project and confirm that you want to import it.
Wait a moment for the installation and import to finish. A window may open stating that "A new scoped registry is now available in the Package Manager.". This is our Needle Package registry. You can safely close that window.
Explore Samples. Select the menu option Needle Engine > Explore Samples to view, open and modify all available sample scenes.
There are 100+ samples that cover a wide range of topics, use cases, and industries. For a quick overview, take a look at our Samples page.
All of these samples are available directly in Unity:
Go to Needle Engine > Explore Samples to browse for samples
Click "Install Samples" to install the samples package right inside your editor.
Choose any sample and click on Open Scene.
The Samples are read-only โ that makes them easy to update.
Our samples scenes are part of a UPM package in Unity. This means that you can't edit the assets and scripts in them directly โ they are read-only. To edit an asset from the samples package, copy it into your project's Assets folder. To edit a script from the samples package, copy it into your web project's src folder.
We provide a number of Scene Templates for quickly starting new projects. These allow you to go from idea to prototype in a few clicks.
Click on File > New Scene
Select one of the templates with (needle) in their name and click Create. We recommend the Collaborative Sandbox template which is a great way to get started with interactivity, multiplayer, and adding assets.
Click Play to install and startup your new web project.
If you don't want to start from a scene template, you can follow these steps. Effectively, we're going to recreate the "Minimal (Needle)" template that's shipping with the package.
Create a new empty scene
Set up your scene for exporting Add an empty GameObject, name it "Exporter" and add an ExportInfo component to it. In this component you create and quickly access your exported runtime project. It also warns you if any of our packages and modules are outdated or not locally installed in your web project.
Project Name and Scene Name
By default, the project name matches the name of your scene. If you want to change that, you can pick or enter a Directory Name where you want to create your new web project. The path is relative to your Unity project.
Choose a web project template Now, select a web project template for your project. The default template is based on Vite, a fast web app bundler.
Click Play to install and start your new web project
Define your own templates
If you find yourself creating many similar projects, you can create your own local or remote templates using the Project View context menu under Create/Needle Engine/Project Template. Templates can either be local on disk (a folder being copied) or remote repositories (a git repository being cloned).
',14)),t("table",null,[e[10]||(e[10]=t("thead",null,[t("tr",null,[t("th",null,"Folder"),t("th")])],-1)),t("tbody",null,[e[6]||(e[6]=t("tr",null,[t("td",null,[t("strong",null,"Unity")]),t("td")],-1)),e[7]||(e[7]=t("tr",null,[t("td",null,[t("code",null,"Assets")]),t("td",null,"This is where project specific/exclusive assets live.")],-1)),e[8]||(e[8]=t("tr",null,[t("td",null,[t("code",null,"Packages")]),t("td",null,[o("This is where packages installed for this project live. A package can contain any asset type. The main difference is that it can be added to multiple Unity projects. It therefor is a great method to share code or assets. To learn more about packages see "),t("a",{href:"https://docs.unity3d.com/Manual/PackagesList.html",target:"_blank",rel:"noopener noreferrer"},"the Unity documentation about packages"),o(".")])],-1)),e[9]||(e[9]=t("tr",null,[t("td",null,[t("strong",null,"Needle Engine Unity Package")]),t("td")],-1)),t("tr",null,[e[5]||(e[5]=t("td",null,[t("code",null,"Core/Runtime/Components")],-1)),t("td",null,[e[3]||(e[3]=o("Contains all Needle Engine built-in components. Learn more about them in the ")),r(d,{to:"/component-reference.html"},{default:i(()=>e[2]||(e[2]=[o("Components Reference")])),_:1}),e[4]||(e[4]=o("."))])])])]),e[14]||(e[14]=a('
When creating a new web project in Unity, you can choose to create it from a local template (by default we ship a vite based web template).
You can also reference remote templates by entering a repository URL in the ExportInfo project path (this can be saved with your scene for example). When creating a new web project the repository will be either cloned or downloaded (depending on if you have git installed) and searched for a needle.config.json file. If none can be found in the cloned repository the root directory will be used. Examples of remote template projects can be found on github.com/needle-engine
If you're planning to only add custom files via NpmDefs and not change the project config (e.g. for a quick fullscreen test), you can prefix the project path with Library. The project will be generated in the Unity Project Library and does not need to be added to source control (the Library folder should be excluded from source control). We call these projects temporary projects. They're great for quickly testing out ideas!
NPM Definition are npm packages tightly integrated into the Unity Editor which makes it easily possible to share scripts with multiple web- or even Unity projects.
C# component stubs for typescript files will also be automatically generated for scripts inside npmdef packages.
To create a NPM Definition right click in the Unity Project browser and select Create/NPM Definition. You can install a NPM Definition package to your runtime project by e.g. selecting your Export Info component and adding it to the dependencies list (internally this will just add the underlying npm package to your package.json).
Don't forget to install the newly added package by e.g. clicking Install on the ExportInfo component and also restart the server if it is already running
To edit the code inside a NPM Definition package just double click the asset NPM Definition asset in your project browser and it will open the vscode workspace that comes with each npmdef.
',14))])}const j=c(y,[["render",b],["__file","index.html.vue"]]),v=JSON.parse('{"path":"/unity/","title":"Needle Engine for Unity","lang":"en-US","frontmatter":{"title":"Needle Engine for Unity","editLink":true,"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/preview.jpeg"}],["meta",{"name":"og:description","content":"---"}]],"description":"---"},"headers":[{"level":2,"title":"Install the Unity Package","slug":"install-the-unity-package","link":"#install-the-unity-package","children":[]},{"level":2,"title":"Quickstart Video Tutorial","slug":"quickstart-video-tutorial","link":"#quickstart-video-tutorial","children":[]},{"level":2,"title":"Start from a Sample","slug":"start-from-a-sample","link":"#start-from-a-sample","children":[]},{"level":2,"title":"Start from a template","slug":"start-from-a-template","link":"#start-from-a-template","children":[]},{"level":2,"title":"Start from scratch","slug":"start-from-scratch","link":"#start-from-scratch","children":[]},{"level":2,"title":"Project Folders and Files","slug":"project-folders-and-files","link":"#project-folders-and-files","children":[{"level":3,"title":"Temporary Projects","slug":"temporary-projects","link":"#temporary-projects","children":[]}]},{"level":2,"title":"Typescript in Unity","slug":"typescript-in-unity","link":"#typescript-in-unity","children":[]}],"git":{"updatedTime":1727182460000},"filePathRelative":"unity/index.md"}');export{j as comp,v as data};
diff --git a/assets/kipash.html-BN1wH6PC.js b/assets/kipash.html-BN1wH6PC.js
new file mode 100644
index 000000000..36170fe5b
--- /dev/null
+++ b/assets/kipash.html-BN1wH6PC.js
@@ -0,0 +1,37 @@
+import{_ as n,r as h,o as r,c as p,b as a,w as l,a as i,d as s}from"./app-CRZRGfEE.js";const d={};function o(C,t){const k=h("contribution-preview"),e=h("contributions-author");return r(),p("div",null,[a(e,{overviewLink:"/docs/community/contributions",name:"kipash",url:"https://github.com/kipash",profileImage:"https://avatars.githubusercontent.com/u/30328735?s=100&u=f28398f4575da1835d1c710d14763c69418cd0fa&v=4",githubUrl:"https://github.com/kipash"},{default:l(()=>[a(k,{title:"Calculate pointer world position",pageUrl:"/docs/community/contributions/kipash/calculate-pointer-world-position"},{default:l(()=>t[0]||(t[0]=[i("p",null,"https://github.com/needle-tools/needle-engine-support/assets/30328735/92404e43-9d45-4ee2-a018-fb00412b6bd5",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," serializable"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," setWorldPosition "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Vector3 "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "three"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," vector1 "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Vector3"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," vector2 "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Vector3"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," PointerFollower"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"("),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Object3D"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," target"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"()")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," offset"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," number"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 10"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," update"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," void"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," cam "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"mainCamera"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," input "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"input"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"target "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"||"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," !"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"cam)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," return"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // get relative mouse position, in range -1 to 1")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," mouse "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," input"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"mousePositionRC"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // get world position of mouse on the near plane")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," vector1"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"set"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(mouse"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"x"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," mouse"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"y"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," -"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"1"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"unproject"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(cam"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // caulculate direction from camera to world mouse")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," vector2"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"copy"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(vector1)"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"sub"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(cam"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"position)"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"normalize"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // offset it to the wanted distance")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," vector1"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"addScaledVector"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(vector2"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"offset)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // apply the result")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," setWorldPosition"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"target"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," vector1)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1)])),_:1})]),_:1})])}const y=n(d,[["render",o],["__file","kipash.html.vue"]]),c=JSON.parse('{"path":"/community/contributions/kipash","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/contributions: kipash.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{y as comp,c as data};
diff --git a/assets/krisrok.html-6_QA53zB.js b/assets/krisrok.html-6_QA53zB.js
new file mode 100644
index 000000000..36b48dc0f
--- /dev/null
+++ b/assets/krisrok.html-6_QA53zB.js
@@ -0,0 +1,31 @@
+import{_ as k,r as a,o as r,c as o,b as l,w as e,a as i,d as s}from"./app-CRZRGfEE.js";const p={};function d(c,t){const h=a("contribution-preview"),n=a("contributions-author");return r(),o("div",null,[l(n,{overviewLink:"/docs/community/contributions",name:"krisrok",url:"https://github.com/krisrok",profileImage:"https://avatars.githubusercontent.com/u/3404365?s=100&u=7025bf7e83b4a3cd72dc2cae9cec729080ee8970&v=4",githubUrl:"https://github.com/krisrok"},{default:e(()=>[l(h,{title:"Always open in specific browser",pageUrl:"/docs/community/contributions/krisrok/always-open-in-specific-browser"},{default:e(()=>t[0]||(t[0]=[i("p",null,[s('Add this class to your project to always open with Chrome instead of your default browser (Firefox in my case) when you click "Play" or "Start Server". Note: This is an editor class and should either be put into an editor-only assembly or wrapped in '),i("code",null,"#if UNITY_EDITOR"),s(" and "),i("code",null,"#endif"),s(".")],-1),i("div",{class:"language-csharp","data-highlighter":"shiki","data-ext":"csharp","data-title":"csharp",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"using"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," System"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"Diagnostics"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"using"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," UnityEditor"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"using"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," UnityEngine"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"using"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Needle"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"Engine"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"["),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"InitializeOnLoad"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"]")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"public"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," static"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," CustomBrowserOpen")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," static"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," CustomBrowserOpen"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Init"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"();")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," ["),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"RuntimeInitializeOnLoadMethod"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"]")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," static"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," void"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Init"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ActionsBrowser"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"BeforeOpen "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"+="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ActionsBrowser_BeforeOpen"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," static"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," void"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," ActionsBrowser_BeforeOpen"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"("),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"ActionsBrowser"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"OpenBrowserArguments"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," args"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," args"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"PreventDefault "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," string"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," processArgs "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," args"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Url"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," var"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," psi "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," new"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," ProcessStartInfo")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," FileName "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "chrome.exe"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Arguments "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," processArgs")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," };")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Process"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"Start"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"("),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"psi"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},");")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1)])),_:1})]),_:1})])}const g=k(p,[["render",d],["__file","krisrok.html.vue"]]),C=JSON.parse('{"path":"/community/contributions/krisrok","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/contributions: krisrok.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{g as comp,C as data};
diff --git a/assets/ktx-env-variable-DxwKzzNo.js b/assets/ktx-env-variable-DxwKzzNo.js
new file mode 100644
index 000000000..c6b04a827
--- /dev/null
+++ b/assets/ktx-env-variable-DxwKzzNo.js
@@ -0,0 +1 @@
+const s="/docs/imgs/ktx-env-variable.webp";export{s as _};
diff --git a/assets/llllkatjallll.html-CUXH6RfS.js b/assets/llllkatjallll.html-CUXH6RfS.js
new file mode 100644
index 000000000..200328373
--- /dev/null
+++ b/assets/llllkatjallll.html-CUXH6RfS.js
@@ -0,0 +1,59 @@
+import{_ as n,r as k,o as r,c as p,b as l,w as h,a as i,d as s}from"./app-CRZRGfEE.js";const d={};function C(y,t){const a=k("contribution-preview"),e=k("contributions-author");return r(),p("div",null,[l(e,{overviewLink:"/docs/community/contributions",name:"llllkatjallll",url:"https://github.com/llllkatjallll",profileImage:"https://avatars.githubusercontent.com/u/38395689?s=100&u=7ce0fef973c4819c4f07823568d6f6061abfe410&v=4",githubUrl:"https://github.com/llllkatjallll"},{default:h(()=>[l(a,{title:"Custom VR Button, that appears only on headsets and not on mobile phones",pageUrl:"/docs/community/contributions/llllkatjallll/custom-vr-button-that-appears-only-on-headsets-and-not-on-mobile-phones"},{default:h(()=>t[0]||(t[0]=[i("p",null,"I combined two checks - Needle's check to see if it's a mobile device (this way, you can exclude desktops), and then a second check that uses user agent info to see if it's one of the most common mobile systems. Result: the button does not appear on mobile phones, but it is visible on Quest and Pico ๐",-1),i("p",null,"P.S: I am using Svelte for 2D UI handling.",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," isMobileDevice"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," NeedleXRSession "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"...")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"// check if this is a mobile phone")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"function"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," isMobilePhone"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," return"),i("span",{style:{"--shiki-light":"#EA76CB","--shiki-dark":"#F4B8E4"}}," /"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Mobi"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"|"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Android"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"|"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"iPhone"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"|"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"iPad"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"|"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"iPod"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"|"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"BlackBerry"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"|"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"IEMobile"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"|"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Opera Mini"),i("span",{style:{"--shiki-light":"#EA76CB","--shiki-dark":"#F4B8E4"}},"/"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"i"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"test"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(navigator"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"userAgent)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"...")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"// show the button, if the device is not a mobile phone and VR is supported")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"#"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"if"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," isMobileDevice"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"() "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"&&"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," !"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"isMobilePhone"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"() "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"&&"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," $haveVR "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," <"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"VrButton buttonFunction"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"() => "),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"StartVR"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()}"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," />")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"/"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"if"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1)])),_:1}),l(a,{title:"Set fallback material for USDZ exporter",pageUrl:"/docs/community/contributions/llllkatjallll/set-fallback-material-for-usdz-exporter"},{default:h(()=>t[1]||(t[1]=[i("p",null,"If you want to set a fallback material for an object that will be exported as USDZ (for AR-mode on iOS), you can add this script to the object, which material should be replaced.",-1),i("p",null,"This is especially useful if you use custom shaders in your scene (they are visible on Desktop+WebXR, but not in AR on iOS).",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," GameObject"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Renderer"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," USDZExporter"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," serializable "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Material"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Object3D "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "three"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," FallbackMaterial"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"("),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Material"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," fallbackMaterial"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Material"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," originalMaterial"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Material"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," usdzExporter"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," USDZExporter"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onEnable"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"usdzExporter "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," GameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"findObjectOfType"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(USDZExporter)"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"subscribeToBeforeExportEvent"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onDisable"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"unsubscribeFromBeforeExportEvent"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," subscribeToBeforeExportEvent"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"usdzExporter"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"addEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"before-export"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"onBeforeExport)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"usdzExporter"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"addEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"after-export"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"onAfterExport)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," unsubscribeFromBeforeExportEvent"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"usdzExporter"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"removeEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"before-export"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"onBeforeExport)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"usdzExporter"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"removeEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"after-export"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"onAfterExport)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onBeforeExport"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," ()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," =>"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," console"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"log"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"onBeforeExport"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," renderer "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"getComponent"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(Renderer)"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"originalMaterial "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," renderer"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"sharedMaterial"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," renderer"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"sharedMaterial "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"fallbackMaterial"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onAfterExport"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," ()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," =>"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," console"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"log"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"onAfterExport"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," renderer "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"getComponent"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(Renderer)"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," renderer"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"sharedMaterial "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"originalMaterial"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1)])),_:1})]),_:1})])}const o=n(d,[["render",C],["__file","llllkatjallll.html.vue"]]),F=JSON.parse('{"path":"/community/contributions/llllkatjallll","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/contributions: llllkatjallll.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{o as comp,F as data};
diff --git a/assets/marwie.html-DsbxB-fW.js b/assets/marwie.html-DsbxB-fW.js
new file mode 100644
index 000000000..244bd6d33
--- /dev/null
+++ b/assets/marwie.html-DsbxB-fW.js
@@ -0,0 +1,144 @@
+import{_ as n,r as a,o as r,c as d,b as h,w as l,a as i,d as s}from"./app-CRZRGfEE.js";const p={};function C(y,t){const k=a("contribution-preview"),e=a("contributions-author");return r(),d("div",null,[h(e,{overviewLink:"/docs/community/contributions",name:"marwie",url:"https://github.com/marwie",profileImage:"https://avatars.githubusercontent.com/u/5083203?s=100&v=4",githubUrl:"https://github.com/marwie"},{default:l(()=>[h(k,{title:"Camera Video Background",pageUrl:"/docs/community/contributions/marwie/camera-video-background"},{default:l(()=>t[0]||(t[0]=[i("p",null,[s("Put it anywhere in your scene to render a camera video behind your 3D scene "),i("a",{href:"https://linen-upbeat-sailboat.glitch.me/",target:"_blank",rel:"noopener noreferrer"},"Live demo")],-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ClearFlags"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," RGBAColor "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," VideoBackground"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," async"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," awake"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // create video element and put it inside the component")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," video "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," document"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"createElement"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"video"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," video"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"style"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"cssText "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}}," `")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}}," position: fixed;")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}}," min-width: 100%;")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}}," min-height: 100%;")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}}," z-index: -1;")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}}," `")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"domElement"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"shadowRoot"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"appendChild"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(video)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // get webcam input")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," input "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," await"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," navigator"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"mediaDevices"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"getUserMedia"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," video"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ("),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"input) "),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"return"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," video"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"srcObject "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," input"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," video"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"play"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // make sure the camera background is transparent")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," camera "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"mainCameraComponent"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," (camera) "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," camera"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"clearFlags "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ClearFlags"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"SolidColor"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," camera"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"backgroundColor "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," RGBAColor"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"125"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 125"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 125"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1)])),_:1}),h(k,{title:"USDZ: Hide Object on Start",pageUrl:"/docs/community/contributions/marwie/usdz-hide-object-on-start"},{default:l(()=>t[1]||(t[1]=[i("p",null,[s("This is an example from our "),i("em",null,"Everywhere Actions"),s(". The following script hides an object on start on Android and on iOS AR")],-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," HideOnStart"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," implements"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," UsdzBehaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," start"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"visible "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," createBehaviours"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"ext"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," model"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," _context"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," (model"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"uuid "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"==="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"uuid)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ext"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"addBehavior"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}},"new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," BehaviorModel"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"HideOnStart_"'),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," +"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"name"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," TriggerBuilder"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"sceneStartTrigger"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ActionBuilder"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"fadeAction"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(model"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," false"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ))"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," beforeCreateDocument"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"visible "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," afterCreateDocument"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"visible "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1)])),_:1}),h(k,{title:"Everywhere Action: Emphasize on Click",pageUrl:"/docs/community/contributions/marwie/everywhere-action-emphasize-on-click"},{default:l(()=>t[2]||(t[2]=[i("p",null,"Example for adding custom USDZ behaviours for iOS AR",-1),i("p",null,"This is an USDZ / iOS AR only example",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," EmphasizeOnClick"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," implements"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," UsdzBehaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"()")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," target"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"()")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," duration"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," number"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0.5"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"()")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," motionType"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," MotionType "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," MotionType"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"bounce"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," beforeCreateDocument"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," createBehaviours"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"ext"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," model"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," _context"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ("),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"target) "),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"return"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," (model"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"uuid "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"==="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"uuid) "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," emphasize "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," BehaviorModel"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"emphasize "'),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," +"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"name"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," TriggerBuilder"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"tapTrigger"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gameObject)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ActionBuilder"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"emphasize"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"target"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"duration"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"motionType"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," undefined"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "basic"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," )"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ext"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"addBehavior"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(emphasize)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," afterCreateDocument"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"_ext"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," _context"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1)])),_:1}),h(k,{title:"Control a Timeline by scroll",pageUrl:"/docs/community/contributions/marwie/control-a-timeline-by-scroll"},{default:l(()=>t[3]||(t[3]=[i("p",null,"Use the mouse wheel or touch delta to update a timeline's time.",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," PlayableDirector"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," serializeable "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Mathf "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"// Example of setting a timeline's time ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"// without relying on any HTML elements.")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"// Here we directly use the mousewheel scroll and the touch delta")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," ScrollTimeline_2"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializeable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"("),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"PlayableDirector"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," timeline"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," PlayableDirector"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializeable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"()")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," scrollSpeed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," number"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0.5"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializeable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"()")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," lerpSpeed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," number"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 2.5"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," targetTime"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," number"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," start"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"timeline"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"pause"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // Grab the mousewheel event")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," window"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"addEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"wheel"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," ("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"evt"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," WheelEvent"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," =>"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"updateTime"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(evt"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"deltaY))"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // Touch events are a bit more complicated")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // We need to keep track of the last touch position")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // and calculate the delta between the current and the last position")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," let"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," lastTouchPosition "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," -"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"1"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," window"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"addEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"touchmove"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," ("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"evt"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," TouchEvent"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," =>"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," delta "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," evt"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"touches["),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"]"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"clientY "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"-"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," lastTouchPosition"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // We only want to apply the delta if it's not TOO big")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // e.g. when the user is scrolling the page")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," (delta "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 10"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},") "),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"updateTime"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"-"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"delta)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // Update the last touch position")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," lastTouchPosition "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," evt"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"touches["),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"]"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"clientY"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," updateTime"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"delta"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ("),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"timeline) "),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"return"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"targetTime "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"+="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," delta "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"*"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0.01"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," *"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scrollSpeed"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"targetTime "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Mathf"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"clamp"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"targetTime"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"timeline"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"duration)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onBeforeRender"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," void"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ("),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"timeline) "),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"return"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"timeline"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"pause"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"timeline"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"time "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Mathf"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"lerp"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"timeline"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"time"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"targetTime"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"lerpSpeed "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"*"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"time"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"deltaTime)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"timeline"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"evaluate"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1)])),_:1}),h(k,{title:"Code Contribution Example",pageUrl:"/docs/community/contributions/marwie/code-contribution-example"},{default:l(()=>t[4]||(t[4]=[i("p",null,"This is mostly a basic example on how to contribute. It will then be added on our documentation contributions page: https://engine.needle.tools/docs/community/contributions",-1),i("p",null,"Please include at least one code snippet, for example like this:",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," serializable "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"')]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Object3D "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "three"')]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," MyComponent"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"("),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Object3D"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," myObjectReference"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," start"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," console"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"log"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"Hello world"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," update"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // called every frame")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1)])),_:1})]),_:1})])}const F=n(p,[["render",C],["__file","marwie.html.vue"]]),E=JSON.parse('{"path":"/community/contributions/marwie","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/contributions: marwie.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{F as comp,E as data};
diff --git a/assets/meta-test.html-Bv0TrQjv.js b/assets/meta-test.html-Bv0TrQjv.js
new file mode 100644
index 000000000..675df420d
--- /dev/null
+++ b/assets/meta-test.html-Bv0TrQjv.js
@@ -0,0 +1 @@
+import{_ as o,r as n,o as r,c as a,a as s,d as i,b as h,e as d}from"./app-CRZRGfEE.js";const m={};function u(l,e){const t=n("metalink");return r(),a("div",null,[s("p",null,[e[0]||(e[0]=i("Hello world ")),h(t)]),e[1]||(e[1]=d('
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sy but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looIt uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo
There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage
of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo
but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo
',8))])}const c=o(m,[["render",u],["__file","meta-test.html.vue"]]),g=JSON.parse('{"path":"/meta-test.html","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/meta test.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[{"level":3,"title":"my_test_header","slug":"my-test-header","link":"#my-test-header","children":[]},{"level":3,"title":"UI Canvas","slug":"ui-canvas","link":"#ui-canvas","children":[]},{"level":3,"title":"How physics works","slug":"how-physics-works","link":"#how-physics-works","children":[]}],"git":{"updatedTime":1671370582000},"filePathRelative":"_meta-test.md"}');export{c as comp,g as data};
diff --git a/assets/metalink-RADuWAM2.js b/assets/metalink-RADuWAM2.js
new file mode 100644
index 000000000..f697d3cc0
--- /dev/null
+++ b/assets/metalink-RADuWAM2.js
@@ -0,0 +1 @@
+import{_}from"./app-CRZRGfEE.js";const n={__name:"metalink",setup(r,{expose:t}){t();const e={};return Object.defineProperty(e,"__isScriptSetup",{enumerable:!1,value:!0}),e}};function s(r,t,e,o,a,p){return" ``` hello ``` "}const u=_(n,[["render",s],["__file","metalink.vue"]]);export{u as default};
diff --git a/assets/microphone-access-in-a-browser-window-and-streamed-playback.html-DC969LS3.js b/assets/microphone-access-in-a-browser-window-and-streamed-playback.html-DC969LS3.js
new file mode 100644
index 000000000..54e27034d
--- /dev/null
+++ b/assets/microphone-access-in-a-browser-window-and-streamed-playback.html-DC969LS3.js
@@ -0,0 +1,65 @@
+import{_ as n,r as h,o as t,c as k,a as i,b as l,e as p}from"./app-CRZRGfEE.js";const e={};function r(d,s){const a=h("contribution-header");return t(),k("div",null,[s[0]||(s[0]=i("p",null,[i("a",{href:"/docs/community/contributions"},"Overview")],-1)),l(a,{url:"https://github.com/ROBYER1",author:"ROBYER1",page:"/docs/community/contributions/robyer1",profileImage:"https://avatars.githubusercontent.com/u/10745594?s=100&u=daf2c8b5dad729e556ae2a01c721672b24bc108a&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/192",title:"Microphone access in a browser window (and streamed playback)",gradient:"True"}),s[1]||(s[1]=p(`
A simple script to show how to request access to, then access a microphone device and also play back the audio stream to debug it. A useful starting point for making an experience revolving around microphone access.
`,2))])}const y=n(e,[["render",r],["__file","microphone-access-in-a-browser-window-and-streamed-playback.html.vue"]]),g=JSON.parse('{"path":"/community/contributions/robyer1/microphone-access-in-a-browser-window-and-streamed-playback","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/robyer1: microphone access in a browser window and streamed playback.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{y as comp,g as data};
diff --git a/assets/modules.html-1xnFAmG8.js b/assets/modules.html-1xnFAmG8.js
new file mode 100644
index 000000000..448f8027e
--- /dev/null
+++ b/assets/modules.html-1xnFAmG8.js
@@ -0,0 +1 @@
+import{_ as a,r as i,o as s,c as r,a as t,d as o,b as l,w as p,e as c}from"./app-CRZRGfEE.js";const d={};function u(m,e){const n=i("RouteLink");return s(),r("div",null,[t("p",null,[e[1]||(e[1]=o("Projects can be composed of re-usable pieces that we call ")),l(n,{to:"/project-structure.html#npm-definition-files"},{default:p(()=>e[0]||(e[0]=[t("strong",null,"NpmDef",-1)])),_:1}),e[2]||(e[2]=o(" (which stands for Npm Defintion File)."))]),e[3]||(e[3]=c('
Below you can find links to other repositories that contain Unity packages. These packages can be installed like any Unity package and used in your own projects. They usually contain eihter examples or modules that we use ourselves, but that are not ready to be part of the core Needle Engine.
Custom Timeline Tracks Video Track (sync video playback to a Timeline) CssTrack (control css properties from the Timeline)
Splines (for Unity 2022.1+) Export splines to three.js SplineWalker for controlling camera motion based on a spline Timeline track to control SplineWalker
Google Drive Integration Sketches around drag-drop integration of Google Drive, file picking, app integration
',3))])}const g=a(d,[["render",u],["__file","modules.html.vue"]]),f=JSON.parse('{"path":"/modules.html","title":"Additional Modules","lang":"en-US","frontmatter":{"title":"Additional Modules","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/additional modules.png"}],["meta",{"name":"og:description","content":"---\\nProjects can be composed of re-usable pieces that we call NpmDef (which stands for Npm Defintion File).\\nBelow you can find links to other repositories that contain Unity packages. These packages can be installed like any Unity package and used in your own projects. They usually contain eihter examples or modules that we use ourselves, but that are not ready to be part of the core Needle Engine."}]],"description":"---\\nProjects can be composed of re-usable pieces that we call NpmDef (which stands for Npm Defintion File).\\nBelow you can find links to other repositories that contain Unity packages. These packages can be installed like any Unity package and used in your own projects. They usually contain eihter examples or modules that we use ourselves, but that are not ready to be part of the core Needle Engine."},"headers":[],"git":{"updatedTime":1725399379000},"filePathRelative":"modules.md"}');export{g as comp,f as data};
diff --git a/assets/needle-button-CkzDHAYk.js b/assets/needle-button-CkzDHAYk.js
new file mode 100644
index 000000000..3d475bd61
--- /dev/null
+++ b/assets/needle-button-CkzDHAYk.js
@@ -0,0 +1 @@
+import{_ as l,o as _,c as u,a as d,f as c,g as f,n as o,u as h}from"./app-CRZRGfEE.js";const a={props:{href:String,secondary:Boolean,same_tab:Boolean,large:Boolean,event_goal:String,event_position:String,next_url:String||void 0},methods:{get_next_url:g}};function g(){if(typeof window>"u")return this.href;if(!this.next_url)return this.href;let e=window.location.origin+window.location.pathname,n=new URL(this.href);return this.next_url&&(console.log(this.next_url,e),e=new URL(this.next_url,e).href,e=encodeURIComponent(e),e+="?t="+Date.now(),n=new URL(n),n.searchParams.append("next",e),n.searchParams.append("t",Date.now().toString())),n.href}const s=()=>{h(e=>({"5058ae9e":e.secondary?"#aaa":"#826bed",b1e81df0:e.large?"1em":".8em","03a7f81f":e.secondary?"#bbb":"#6248be"}))},r=a.setup;a.setup=r?(e,n)=>(s(),r(e,n)):s;const m=["href","target"];function b(e,n,t,v,p,i){return _(),u("a",{href:i.get_next_url(),target:t.same_tab?"_self":"_blank",class:o(["no-external-link-icon",t.event_goal?"plausible-event-name="+t.event_goal+(t.event_position?" plausible-event-position="+t.event_position:""):""])},[d("button",{class:o(t.event_goal?"plausible-event-name="+t.event_goal+(t.event_position?" plausible-event-position="+t.event_position:""):"")},[c(e.$slots,"default",{},void 0,!0),f("",!0)],2)],10,m)}const x=l(a,[["render",b],["__scopeId","data-v-9b3341da"],["__file","needle-button.vue"]]);export{x as default};
diff --git a/assets/needle-config-json.html-CGJ-KXV-.js b/assets/needle-config-json.html-CGJ-KXV-.js
new file mode 100644
index 000000000..3e56519e6
--- /dev/null
+++ b/assets/needle-config-json.html-CGJ-KXV-.js
@@ -0,0 +1,22 @@
+import{_ as e,r as a,o as n,c as h,e as l,a as i,b as p,w as r,d as k}from"./app-CRZRGfEE.js";const d={};function o(c,s){const t=a("RouteLink");return n(),h("div",null,[s[1]||(s[1]=l(`
The needle.config.json is used to provide configuration for the Needle Editor integrations and for the Needle Engine build pipeline plugins.
Paths
buildDirectory
This is where the built project files are being copied to
assetsDirectory
This is where the Editor integration assets will be copied to or created at (e.g. the .glb files exported from Unity or Blender)
scriptsDirectory
This is the directory the Editor integration is watching for code changes to re-generate components
codegenDirectory
This is where the Editor integration is outputting generated files to.
baseUrl
Required for e.g. next.js or SvelteKit integration. When baseUrl is set, relative paths for codegen and inside files are using baseUrl, not assetsDirectory. This is useful in cases where the assetDirectory does not match the server url. For example, the path on disk could be "assetsDirectory": "public/assets", but the framework serves files from "baseUrl": "assets".
Tools
build : { copy: ["myFileOrDirectory"] }
Array of string paths for copying additional files or folders to the buildDirectory. These can either be absolute or relative.
Files are exported to static/assets but the framework serves them from /assets. In this case, the baseUrl needs to be set to assets so that relative paths in files are correct.
`,10)),i("ul",null,[i("li",null,[p(t,{to:"/project-structure.html"},{default:r(()=>s[0]||(s[0]=[k("Project Structure")])),_:1})])])])}const B=e(d,[["render",o],["__file","needle-config-json.html.vue"]]),C=JSON.parse('{"path":"/reference/needle-config-json.html","title":"needle.config.json","lang":"en-US","frontmatter":{"title":"needle.config.json","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/needle.png"}],["meta",{"name":"og:description","content":"---\\nThe needle.config.json is used to provide configuration for the Needle Editor integrations and for the Needle Engine build pipeline plugins."}]],"description":"---\\nThe needle.config.json is used to provide configuration for the Needle Editor integrations and for the Needle Engine build pipeline plugins."},"headers":[],"git":{"updatedTime":1725399379000},"filePathRelative":"reference/needle-config-json.md"}');export{B as comp,C as data};
diff --git a/assets/needle-engine-attributes.html-Dizdapts.js b/assets/needle-engine-attributes.html-Dizdapts.js
new file mode 100644
index 000000000..d32bd8dd5
--- /dev/null
+++ b/assets/needle-engine-attributes.html-Dizdapts.js
@@ -0,0 +1,16 @@
+import{_ as e}from"./custom-loading-style-s1K1my2z.js";import{_ as i,o as s,c as a,e as n}from"./app-CRZRGfEE.js";const d={};function o(l,t){return s(),a("div",null,t[0]||(t[0]=[n(`
The <needle-engine> web-component comes with a nice collection of built-in attributes that can be used to modify the look and feel of the loaded scene without the need to add or edit the three.js scene directly. The table below shows a list of the most important ones:
Attribute
Description
Loading
src
Path to one or multiple glTF or glb files. Supported types are string, string[] or a stringified array (, separated)
optional, hex color to be used as a background color. Examples: rgb(255, 200, 100), #dddd00
background-image
optional, URL to a skybox image (background image) or a preset string: studio, blurred-skybox, quicklook, quicklook-ar
background-blurriness
optional, bluriness value between 0 (no blur) and 1 (max blur) for the background-image. Example: background-blurriness="0.5"
environment-image
optional, URL to a environment image (environment light) or a preset string: studio, blurred-skybox, quicklook, quicklook-ar
contactshadows
optional, render contact shadows
tone-mapping
optional, supported values are none, linear, neutral, agx
tone-mapping-exposure
optional number e.g. increase exposure with tone-mapping-exposure="1.5", requires tone-mapping to be set
Interaction
autoplay
add or set to true to auto play animations e.g. <needle-engine autoplay
camera-controls
add or set to true to automatically add OrbitControls if no camera controls are found in the scene
auto-rotate
add to enable auto-rotate (only used with camera-controls)
Events
loadstart
Name of the function to call when loading starts. Note that the arguments are (ctx:Context, evt:Event). You can call evt.preventDefault() to hide the default loading overlay
progress
Name of the function to call when loading updates. onProgress(ctx:Context, evt: {detail: {context:Context, name:string, index:number, count:number, totalProgress01:number}) { ... }
loadfinished
Name of the function to call when loading finishes
Loading Display
Available options to change how the Needle Engine loading display looks. Use ?debugloadingrendering for easier editing
loading-style
Options are light or dark
loading-background-color
PRO โ Change the loading background color (e.g. =#dd5500)
loading-text-color
PRO โ Change the loading text color
loading-logo-src
PRO โ Change the loading logo image
primary-color
PRO โ Change the primary loading color
secondary-color
PRO โ Change the secondary loading color
hide-loading-overlay
PRO โ Do not show the loading overlay, added in Needle Engine > 3.17.1
Internal
hash
Used internally, is appended to the files being loaded to force an update (e.g. when the browser has cached a glb file). Should not be edited manually.
You can easily modify how Needle Engine looks by setting the appropriate attributes on the <needle-engine> web component. Please see the table above for details.
',12)]))}const c=i(d,[["render",o],["__file","needle-engine-attributes.html.vue"]]),p=JSON.parse('{"path":"/reference/needle-engine-attributes.html","title":" Configuration","lang":"en-US","frontmatter":{"title":" Configuration","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/ configuration.png"}],["meta",{"name":"og:description","content":"---\\nThe"}]],"description":"---\\nThe"},"headers":[{"level":3,"title":"Custom Loading Style (PRO)","slug":"custom-loading-style-pro","link":"#custom-loading-style-pro","children":[]}],"git":{"updatedTime":1727172155000},"filePathRelative":"reference/needle-engine-attributes.md"}');export{c as comp,p as data};
diff --git a/assets/network-instantiation-of-multiple-objects.html-BiATyjwH.js b/assets/network-instantiation-of-multiple-objects.html-BiATyjwH.js
new file mode 100644
index 000000000..4e294cdd0
--- /dev/null
+++ b/assets/network-instantiation-of-multiple-objects.html-BiATyjwH.js
@@ -0,0 +1,160 @@
+import{_ as t,r as n,o as h,c as k,a as i,b as l,e as p}from"./app-CRZRGfEE.js";const e={};function r(d,s){const a=n("contribution-header");return h(),k("div",null,[s[0]||(s[0]=i("p",null,[i("a",{href:"/docs/community/contributions"},"Overview")],-1)),l(a,{url:"https://github.com/Web3Kev",author:"Web3Kev",page:"/docs/community/contributions/web3kev",profileImage:"https://avatars.githubusercontent.com/u/106066970?s=100&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/153",title:"Network instantiation of multiple objects",gradient:"True"}),s[1]||(s[1]=p(`
In a multiuser session, typically objects are instantiated using instantiateSynced as such:
import { Behaviour,GameObject,serializable,InstantiateOptions} from "@needle-tools/engine";
+import { Vector3, Object3D, } from "three";
+
+export class InstantiateObjectForAll extends Behaviour
+{
+ @serializable(Object3D)
+ myPrefab?: GameObject;
+
+ public makeObject():void{
+ const options = new InstantiateOptions();
+ options.context = this.context;
+ options.position = new Vector3(0,0,0);
+ GameObject.instantiateSynced(this.myPrefab, options) as GameObject;
+ }
+}
My particular use-case was for generating programmatically a random scene made of cubes, and that scene had to be the same for all users of the same room. I had used the example above but for some unknown reasons sometimes the scenes were partially rendered when instantiating simultaneously >400 objects. @Marcel of Needle suggested to generate a seed (position of all objects in the scene) and send that seed instead using :
this.context.connection.send()
All users using :
this.context.connection.beginListen()
would receive any seed previously sent, upon joining the same room, allowing them to instantiate cubes according to that seed (array of Vector3).
Here is a script illustrating the use of the send method and the beginListen counterpart:
+//This is an example of sending the seed of a randomly generated scene made of cubes, for all other instances logging into the same room to create the same scene.
+
+//This script requires a prefab (e.g. a 1x1x1 Cube)
+//This script will generate and build randomly positioned cubes (random walk) as a child of the object it is attached to.
+//The generateSeed() method is in this script called via a button. The button is deactivated once the seed has been transmitted.
+//Any users joining the same room will receive the seed and build the exact same scene
+
+
+import { Behaviour,GameObject,serializable,InstantiateOptions} from "@needle-tools/engine";
+import { Vector3, Object3D } from "three";
+
+
+export class NetworkedSeed extends Behaviour
+{
+ @serializable(Object3D)
+ prefab?: GameObject;
+
+ @serializable(Object3D)
+ generateButton?: Object3D;
+
+ public seedSize: number = 30;
+
+ seed: Vector3[] = [];
+
+ onEnable(): void {
+ this.context.connection.beginListen("mySeed", this.onDataReceived);
+ if(this.generateButton)
+ {
+ this.generateButton.visible=true;
+ }
+ }
+ onDisable(): void {
+ this.context.connection.stopListen("mySeed", this.onDataReceived);
+ }
+
+ onDataReceived = (data: any) => {
+
+ console.log("Received data:", data.mySeed);
+ if(this.seed.length===0)
+ {
+ //prevent other generations of the seed
+ if(this.generateButton)
+ {
+ this.generateButton.visible=false;
+ }
+
+ this.seed=data.mySeed;
+ //build scene
+ this.buildScene();
+ }
+ };
+
+
+ //generate and send seed to all from the button generateButton
+ public generateSeed():void{
+
+ if(this.seed.length==0) //no seed found => generate one
+ {
+ this.seed = [];
+ const uniquePositions = new Set<string>();
+
+ //start at origin
+ const startPosition = new Vector3(0, 0, 0);
+ this.seed.push(startPosition.clone());
+ uniquePositions.add(startPosition.toArray().toString());
+
+ //go for a random walk of length : seedSize
+ while (this.seed.length < this.seedSize) {
+ const lastPosition = this.seed[this.seed.length - 1];
+ let newPosition: Vector3;
+
+ //walk and add position, making sure they are unique
+ do {
+ const direction = this.getRandomDirection();
+ newPosition = lastPosition.clone().add(direction);
+ } while (uniquePositions.has(newPosition.toArray().toString()));
+
+ this.seed.push(newPosition.clone());
+ uniquePositions.add(newPosition.toArray().toString());
+ }
+
+ //send the seed to all on the server
+ this.sendSeed();
+
+ //prevent other generations of the seed
+ if(this.generateButton)
+ {
+ this.generateButton.visible=false;
+ }
+ }
+
+ //build scene locally
+ this.buildScene();
+ }
+
+ private sendSeed():void{
+ if(this.seed.length!=0)
+ {
+ this.context.connection.send("mySeed",{guid:this.guid, mySeed: this.seed});
+ console.log("------ SEED SENT -------");
+ }
+ }
+
+ public buildScene():void{
+
+ //check if the seed is not empty
+ if(this.seed.length==0)
+ {
+ console.log("array was empty");
+ return;
+ }
+
+ //check if the scene has already been built
+ if(this.gameObject.children.length>0)
+ {
+ console.log("Scene already present");
+ return;
+ }
+
+ // Create cubes at each position of the random walk
+ for(let i=0; i<this.seed.length; i++)
+ {
+ const option = new InstantiateOptions();
+ option.context = this.context;
+ option.parent=this.gameObject;
+ option.position = this.seed[i];
+
+ if(this.prefab!=null)
+ {
+ const cube = GameObject.instantiate(this.prefab, option) as GameObject;
+ }
+ }
+
+ console.log("----------- Scene Built ---------");
+
+ }
+
+ private getRandomDirection(): Vector3 {
+ const x = Math.random() < 0.5 ? -1 : 1;
+ const y = Math.random() < 0.5 ? -1 : 1;
+ const z = Math.random() < 0.5 ? -1 : 1;
+ return new Vector3(x, y, z);
+ }
+
+}
The above script is placed on an object (any Transform) and will generate an array of unique Vector3 positions for a specified length (seedSize) after generateSeed() is called (In this case it is called from a button: generateButton).
Once generated it will send the array to the server and build the scene. The building process consist of instantiating the prefab at each Vector3 position of the seed (this.seed) array.
Any user joining the same room after a seed has been generated and sent, will receive the seed from the server and trigger the callback onDataReceived() which will cache the seed array, disable the button, and build the scene with the prefab, according to the seed.
This gives a way to generate a scene and communicate the seed of that scene, for each user to build locally.
This was the solution I chose which worked better than instantiating a complex scene (>400 objects) with instantiateSynced which would occasionally cause bugs.
`,14))])}const g=t(e,[["render",r],["__file","network-instantiation-of-multiple-objects.html.vue"]]),y=JSON.parse('{"path":"/community/contributions/web3kev/network-instantiation-of-multiple-objects","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/web3kev: network instantiation of multiple objects.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{g as comp,y as data};
diff --git a/assets/networking.html-Ccq0lod9.js b/assets/networking.html-Ccq0lod9.js
new file mode 100644
index 000000000..c5ba50976
--- /dev/null
+++ b/assets/networking.html-Ccq0lod9.js
@@ -0,0 +1,62 @@
+import{_ as s,o as a,c as e,e as n}from"./app-CRZRGfEE.js";const t="/docs/imgs/networking_absolute.webp",l={};function h(k,i){return a(),e("div",null,i[0]||(i[0]=[n(`
Access to core networking functionality can be obtained by using this.context.connection from a component. The default backend server connects users to rooms. Users in the same room will share state and receive messages from each other.
Networking is currently based on websockets and sending either json strings (for infrequent updates) or flatbuffers (for frequent updates). Continue reading below for more details:
SyncedRoom โ handles networking connection and connection to a room. This can also be done by code using the networking api accessible from this.context.connection
When sending an object containing a guid field it will be saved in the persistant storage and automatically sent to users that connect later or come back later to the site (e.g. to restore state). To delete state for a specific guid from the backend storage you can use delete-state as the key and provide an object with { guid: "guid_to_delete" }
Subscribe to json events / listen to events in the room using a specific key this.context.connection.beginListen(key:string, callback:(data) => void) Unsubscribe with stopListening
Subscribe to flatbuffer binary events this.context.connection.beginListenBinrary(identifier:string, callback:(data : ByteBuffer) => void) Unsubscribe with stopListenBinary
To automatically network fields in a component you can just decorate a field with a @syncField() decorator (note: you need to have experimentalDecorators: true in your tsconfig.json file for it to work)
Example Code
Automatically network a color field. The following script also changes the color randomly on click
import { Behaviour, IPointerClickHandler, PointerEventData, Renderer, RoomEvents, delay, serializable, showBalloonMessage, syncField } from "@needle-tools/engine";
+import { Color } from "three"
+
+export class Networking_ClickToChangeColor extends Behaviour implements IPointerClickHandler {
+
+ // START MARKER network color change syncField
+ /** syncField does automatically send a property value when it changes */
+ @syncField(Networking_ClickToChangeColor.prototype.onColorChanged)
+ @serializable(Color)
+ color!: Color;
+
+ private onColorChanged() {
+ // syncField will network the color as a number, so we need to convert it back to a Color when we receive it
+ if (typeof this.color === "number")
+ this.color = new Color(this.color);
+ this.setColorToMaterials();
+ }
+ // END MARKER network color change syncField
+
+ /** called when the object is clicked and does generate a random color */
+ onPointerClick(_: PointerEventData) {
+ const randomColor = new Color(Math.random(), Math.random(), Math.random());
+ this.color = randomColor;
+ }
+
+ onEnable() {
+ this.setColorToMaterials();
+ }
+
+ private setColorToMaterials() {
+ const renderer = this.gameObject.getComponent(Renderer);
+ if (renderer) {
+ for (let i = 0; i < renderer.sharedMaterials.length; i++) {
+ // we clone the material so that we don't change the original material
+ // just for demonstration purposes, you can also change the original material
+ const mat = renderer.sharedMaterials[i]?.clone();
+ renderer.sharedMaterials[i] = mat;
+ if (mat && "color" in mat)
+ mat.color = this.color;
+ }
+ }
+ else console.warn("No renderer found", this.gameObject)
+ }
+
+}
Simple networking of a number
import { Behaviour, syncField, IPointerClickHandler } from "@needle-tools/engine"
+
+export class AutoFieldSync extends Behaviour implements IPointerClickHandler {
+
+ // Use \`@syncField\` to automatically network a field.
+ // You can optionally assign a method or method name to be called when the value changes
+ @syncField("myValueChanged")
+ mySyncedValue?: number = 1;
+
+ private myValueChanged() {
+ console.log("My value changed", this.mySyncedValue);
+ }
+
+ onPointerClick() {
+ this.mySyncedValue = Math.random();
+ }
+}
Needle Engine currently uses its own networking package hosted on npm. By default if not configured differently using the Networking component Needle Engine will connect to a server running on Glitch.
It can be added to your own fastiy or express server running on any server for example by adding the following code on your server after installing the package:
import networking from "@needle-tools/needle-tiny-networking-ws";
+networking.startServerFastify(fastifyApp, { endpoint: "/socket" });
The following options are available:
endpointstring
relative path to the websocket endpoint (e.g. /socket)
maxUsersnumber
Amount of users allowed per room
defaultUserTimeoutnumber
Timeout length in seconds until a user is kicked from a room (if no ping is received). Defaults to 30 seconds
When deploying your app to Glitch, we include a simple networking backend that is great for prototyping and small deployments (~15-20 people at the same time). You can later update to a bigger/better/stronger networking solution if required.
For testing and development purposes it can be desired to run the needle engine networking package on a local server. We have prepared a repository that is setup to host the websocket package and to make that easy for you. Please follow the instructions in the linked repository:
You can also deploy your own networking server on e.g. google cloud. For further instructions please refer to the description found here: Local Networking Repository
If you want to use a different server for your local development and your hosted development (and the hosted server is not the same as your needle engine deployed website) then you can also enter a absolute URL in the Networking component URL field as well:
If you want to modify the default peerjs options you can call setPeerOptions(opts: PeerjsOptions) with your custom options. This can be used to modify the hosting provider in case where you host your own peerjs server.
',44)]))}const o=s(l,[["render",h],["__file","networking.html.vue"]]),p=JSON.parse('{"path":"/networking.html","title":"Networking","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/networking.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[{"level":2,"title":"Using Multiplayer","slug":"using-multiplayer","link":"#using-multiplayer","children":[]},{"level":2,"title":"Core Components","slug":"core-components","link":"#core-components","children":[]},{"level":2,"title":"Manual Networking","slug":"manual-networking","link":"#manual-networking","children":[{"level":3,"title":"Sending","slug":"sending","link":"#sending","children":[]},{"level":3,"title":"Receiving","slug":"receiving","link":"#receiving","children":[]}]},{"level":2,"title":"Auto Networking (experimental)","slug":"auto-networking-experimental","link":"#auto-networking-experimental","children":[]},{"level":2,"title":"Flatbuffers for your own components","slug":"flatbuffers-for-your-own-components","link":"#flatbuffers-for-your-own-components","children":[]},{"level":2,"title":"Networking Package","slug":"networking-package","link":"#networking-package","children":[]},{"level":2,"title":"Networking on Glitch","slug":"networking-on-glitch","link":"#networking-on-glitch","children":[{"level":3,"title":"Limitations","slug":"limitations","link":"#limitations","children":[]}]},{"level":2,"title":"Local Networking","slug":"local-networking","link":"#local-networking","children":[]},{"level":2,"title":"Hosting your own Networking Server","slug":"hosting-your-own-networking-server","link":"#hosting-your-own-networking-server","children":[]},{"level":2,"title":"peerjs","slug":"peerjs","link":"#peerjs","children":[{"level":3,"title":"Customizing peerjs options","slug":"customizing-peerjs-options","link":"#customizing-peerjs-options","children":[]}]}],"git":{"updatedTime":1726585195000},"filePathRelative":"networking.md"}');export{o as comp,p as data};
diff --git a/assets/os-link-CAGZ994T.js b/assets/os-link-CAGZ994T.js
new file mode 100644
index 000000000..ff7d52dec
--- /dev/null
+++ b/assets/os-link-CAGZ994T.js
@@ -0,0 +1 @@
+import{_ as s,o as e,c as n,f as o,d as l,t as u}from"./app-CRZRGfEE.js";const _={props:{text:String,generic_url:String,windows_url:String,osx_url:String,osx_silicon_url:String,linux_url:String},methods:{getUrl:function(){const r=navigator.userAgent;if(r.indexOf("Windows")!==-1){if(this.windows_url)return this.windows_url}else if(r.indexOf("Mac")!==-1){if(this.osx_silicon_url&&r.indexOf("Intel")===-1)return this.osx_silicon_url;if(this.osx_url)return this.osx_url}else if(r.indexOf("Linux")!==-1){if(this.linux_url)return this.linux_url;if(this.osx_url)return this.osx_url}return this.generic_url??this.windows_url}}},f=["href"];function c(r,x,i,a,d,t){return e(),n("a",{href:t.getUrl()},[o(r.$slots,"default",{},()=>[l(u(i.text),1)])],8,f)}const g=s(_,[["render",c],["__file","os-link.vue"]]);export{g as default};
diff --git a/assets/project-structure.html-DAYDA0w3.js b/assets/project-structure.html-DAYDA0w3.js
new file mode 100644
index 000000000..681af4e3d
--- /dev/null
+++ b/assets/project-structure.html-DAYDA0w3.js
@@ -0,0 +1 @@
+import{_ as u,r as i,o as d,c as s,a as t,b as r,w as o,d as l}from"./app-CRZRGfEE.js";const p={};function a(f,e){const n=i("RouteLink");return d(),s("div",null,[e[33]||(e[33]=t("h1",{id:"needle-engine-project-structure",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#needle-engine-project-structure"},[t("span",null,"Needle Engine Project Structure")])],-1)),e[34]||(e[34]=t("h3",{id:"web-project-files",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#web-project-files"},[t("span",null,"Web Project Files")])],-1)),t("table",null,[e[8]||(e[8]=t("thead",null,[t("tr",null,[t("th"),t("th")])],-1)),t("tbody",null,[e[2]||(e[2]=t("tr",null,[t("td",null,[t("strong",null,"Needle Engine")]),t("td")],-1)),t("tr",null,[t("td",null,[r(n,{to:"/reference/needle-config-json.html"},{default:o(()=>e[0]||(e[0]=[t("code",null,"needle.config.json",-1)])),_:1})]),e[1]||(e[1]=t("td",null,"Configuration for Needle Engine builds and integrations",-1))]),e[3]||(e[3]=t("tr",null,[t("td",null,[t("strong",null,"Ecosystem")]),t("td")],-1)),e[4]||(e[4]=t("tr",null,[t("td",null,[t("code",null,"package.json")]),t("td",null,"Project configuration containing name, version, dependencies and scripts")],-1)),e[5]||(e[5]=t("tr",null,[t("td",null,[t("code",null,"tsconfig.json")]),t("td",null,"Typescript compiler configuration")],-1)),e[6]||(e[6]=t("tr",null,[t("td",null,[t("code",null,".gitignore")]),t("td",null,"Files and folders to be ignored in git")],-1)),e[7]||(e[7]=t("tr",null,[t("td",null,[t("code",null,"vite.config.js")]),t("td",null,[l("Contains vite specific configuration."),t("br"),l("It also adds the Needle Engine vite plugins.")])],-1))])]),e[35]||(e[35]=t("h3",{id:"default-vite-project-structure",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#default-vite-project-structure"},[t("span",null,"Default Vite project structure")])],-1)),e[36]||(e[36]=t("p",null,[l("Our main project template uses the superfast "),t("a",{href:"https://vitejs.dev/",target:"_blank",rel:"noopener noreferrer"},"vite"),l(" bundler. The following shows the structure of the Vite template that we created and ship (altough it is possible to adapt it to your own needs).")],-1)),t("table",null,[e[27]||(e[27]=t("thead",null,[t("tr",null,[t("th"),t("th")])],-1)),t("tbody",null,[e[17]||(e[17]=t("tr",null,[t("td",null,[t("strong",null,"Folders")]),t("td")],-1)),e[18]||(e[18]=t("tr",null,[t("td",null,[t("code",null,"assets/")]),t("td",null,[l("The asset folder contains exported assets from Unity. E.g. generated "),t("code",null,"gltf"),l(" files, audio or video files. It is not recommended to manually add files to "),t("code",null,"assets"),l(" as it will get cleared on building the distribution for the project.")])],-1)),e[19]||(e[19]=t("tr",null,[t("td",null,[t("code",null,"include/")]),t("td",null,"(optional) - If you have custom assets that you need to reference/load add them to the include directory. On build this directory will be copied to the output folder.")],-1)),e[20]||(e[20]=t("tr",null,[t("td",null,[t("code",null,"src/generated/")]),t("td",null,"The generated javascript code. Do not edit manually!")],-1)),e[21]||(e[21]=t("tr",null,[t("td",null,[t("code",null,"src/scripts/")]),t("td",null,"Your project specific scripts / components")],-1)),e[22]||(e[22]=t("tr",null,[t("td",null,[t("code",null,"src/styles/")]),t("td",null,"Stylesheets")],-1)),t("tr",null,[e[12]||(e[12]=t("td",null,[t("code",null,"*")],-1)),t("td",null,[e[10]||(e[10]=l("You can add any new folders here as you please. Make sure to ")),r(n,{to:"/reference/needle-config-json.html"},{default:o(()=>e[9]||(e[9]=[l("copy")])),_:1}),e[11]||(e[11]=l(" them to the output directory when building"))])]),e[23]||(e[23]=t("tr",null,[t("td",null,[t("strong",null,"Files")]),t("td")],-1)),e[24]||(e[24]=t("tr",null,[t("td",null,[t("code",null,"index.html")]),t("td",null,"The landing- or homepage of your website")],-1)),e[25]||(e[25]=t("tr",null,[t("td",null,[t("code",null,"vite.config")]),t("td",null,[l("The "),t("a",{href:"https://vitejs.dev/config/",target:"_blank",rel:"noopener noreferrer"},"vite config"),l(". Settings for building the distribution and hosting the development server are made here. It is usually not necessary to edit these settings.")])],-1)),e[26]||(e[26]=t("tr",null,[t("td",null,[t("code",null,"src/main.ts")]),t("td",null,[l("Included from "),t("code",null,"index.html"),l(" and importing "),t("code",null,"needle-engine")])],-1)),t("tr",null,[e[16]||(e[16]=t("td",null,[t("code",null,"*")],-1)),t("td",null,[e[14]||(e[14]=l("You can add any new files here as you please. Make sure to ")),r(n,{to:"/reference/needle-config-json.html"},{default:o(()=>e[13]||(e[13]=[l("copy")])),_:1}),e[15]||(e[15]=l(" them to the output directory when building (unless they are just being used during development)"))])])])]),e[37]||(e[37]=t("p",null,"Our exporter can be used with other project structures as well, vite is just our go-to frontend bundling tool because of its speed. Feel free to set up your JavaScript project as you like.",-1)),t("p",null,[r(n,{to:"/html.html"},{default:o(()=>e[28]||(e[28]=[l("Learn more in the docs about bundling and usage with other frameworks")])),_:1})]),e[38]||(e[38]=t("hr",null,null,-1)),e[39]||(e[39]=t("h4",{id:"continue-reading",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#continue-reading"},[t("span",null,"Continue Reading")])],-1)),t("ul",null,[t("li",null,[r(n,{to:"/getting-started/for-unity-developers.html"},{default:o(()=>e[29]||(e[29]=[l("Typescript Guide for Unity Developers")])),_:1})]),t("li",null,[r(n,{to:"/getting-started/typescript-essentials.html"},{default:o(()=>e[30]||(e[30]=[l("Typescript Essentials")])),_:1})]),t("li",null,[r(n,{to:"/scripting.html"},{default:o(()=>e[31]||(e[31]=[l("Writing custom scripts")])),_:1})]),t("li",null,[r(n,{to:"/everywhere-actions.html"},{default:o(()=>e[32]||(e[32]=[l("Everywhere Actions")])),_:1})])])])}const m=u(p,[["render",a],["__file","project-structure.html.vue"]]),j=JSON.parse('{"path":"/project-structure.html","title":"Web Project Structure","lang":"en-US","frontmatter":{"title":"Web Project Structure","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/preview.jpeg"}],["meta",{"name":"og:description","content":"---"}]],"description":"---"},"headers":[{"level":3,"title":"Web Project Files","slug":"web-project-files","link":"#web-project-files","children":[]},{"level":3,"title":"Default Vite project structure","slug":"default-vite-project-structure","link":"#default-vite-project-structure","children":[]}],"git":{"updatedTime":1727723055000},"filePathRelative":"project-structure.md"}');export{m as comp,j as data};
diff --git a/assets/quicklook-vertical-image-tracker.html-C2obMcDm.js b/assets/quicklook-vertical-image-tracker.html-C2obMcDm.js
new file mode 100644
index 000000000..371976a97
--- /dev/null
+++ b/assets/quicklook-vertical-image-tracker.html-C2obMcDm.js
@@ -0,0 +1,43 @@
+import{_ as t,r as n,o as h,c as l,a as s,b as k,e}from"./app-CRZRGfEE.js";const p={};function r(d,i){const a=n("contribution-header");return h(),l("div",null,[i[0]||(i[0]=s("p",null,[s("a",{href:"/docs/community/contributions"},"Overview")],-1)),k(a,{url:"https://github.com/ericcraft-mh",author:"ericcraft-mh",page:"/docs/community/contributions/ericcraft-mh",profileImage:"https://avatars.githubusercontent.com/u/99364056?s=100&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/191",title:"QuickLook Vertical Image Tracker",gradient:"True"}),i[1]||(i[1]=e(`
In cases in which you are using QuickLook Image Tracker and Vertical Imagery you will need to correct the orientation of the model. As noted on the Detecting Images in an AR Experience page:
SCNPlane is vertically oriented in its local coordinate space, but ARImageAnchor assumes the image is horizontal in its local space, so rotate the plane to match.
`,5))])}const g=t(p,[["render",r],["__file","quicklook-vertical-image-tracker.html.vue"]]),y=JSON.parse('{"path":"/community/contributions/ericcraft-mh/quicklook-vertical-image-tracker","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/ericcraft mh: quicklook vertical image tracker.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{g as comp,y as data};
diff --git a/assets/quoteslides-Jbcw9khk.js b/assets/quoteslides-Jbcw9khk.js
new file mode 100644
index 000000000..125f38471
--- /dev/null
+++ b/assets/quoteslides-Jbcw9khk.js
@@ -0,0 +1 @@
+import{_ as n,p as l,q as a,o as d,c,f as i}from"./app-CRZRGfEE.js";const r=l({setup(){a(()=>{const o=document.querySelector(".quotes");o.style.display="flex";const e=document.querySelectorAll(".quotes > div");for(let s=0;s{e[t].style.display="none",t=(t+1)%e.length,e[t].style.display="block"},5e3)})}}),u={class:"quotes"};function f(o,e,t,s,p,_){return d(),c("div",u,[i(o.$slots,"default",{},void 0,!0)])}const y=n(r,[["render",f],["__scopeId","data-v-96af3692"],["__file","quoteslides.vue"]]);export{y as default};
diff --git a/assets/removeserviceworker-CWQpv6MD.js b/assets/removeserviceworker-CWQpv6MD.js
new file mode 100644
index 000000000..a708778c2
--- /dev/null
+++ b/assets/removeserviceworker-CWQpv6MD.js
@@ -0,0 +1 @@
+import{_ as t}from"./app-CRZRGfEE.js";console.log("ServiceWorker:",navigator.serviceWorker);var o;(o=navigator.serviceWorker)==null||o.getRegistrations().then(r=>{for(const e of r)console.log("ServiceWorker unregistered:",e),e.unregister()});const s={};function n(r,e,i,c,a,v){return null}const f=t(s,[["render",n],["__file","removeserviceworker.vue"]]);export{f as default};
diff --git a/assets/robyer1.html-DKUQa3GZ.js b/assets/robyer1.html-DKUQa3GZ.js
new file mode 100644
index 000000000..caf412450
--- /dev/null
+++ b/assets/robyer1.html-DKUQa3GZ.js
@@ -0,0 +1,65 @@
+import{_ as n,r as k,o as r,c as p,b as h,w as a,a as i,d as s}from"./app-CRZRGfEE.js";const d={};function C(y,t){const l=k("contribution-preview"),e=k("contributions-author");return r(),p("div",null,[h(e,{overviewLink:"/docs/community/contributions",name:"ROBYER1",url:"https://github.com/ROBYER1",profileImage:"https://avatars.githubusercontent.com/u/10745594?s=100&u=daf2c8b5dad729e556ae2a01c721672b24bc108a&v=4",githubUrl:"https://github.com/ROBYER1"},{default:a(()=>[h(l,{title:"Microphone access in a browser window (and streamed playback)",pageUrl:"/docs/community/contributions/robyer1/microphone-access-in-a-browser-window-and-streamed-playback"},{default:a(()=>t[0]||(t[0]=[i("p",null,"A simple script to show how to request access to, then access a microphone device and also play back the audio stream to debug it. A useful starting point for making an experience revolving around microphone access.",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Behaviour "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Microphone"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," start"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"getLocalStream"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," public"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," el"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Element"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," attachStream"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"stream"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," el"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," options"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," var"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," item"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," var"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," URL "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," window"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"URL"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," var"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," element "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," el"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," var"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," opts "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," autoplay"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," mirror"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," muted"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," audio"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," disableContextMenu"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," };")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," (options) "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," for"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," (item "),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"in"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," options) "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," opts[item] "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," options[item]"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ("),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"element) "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," element "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," document"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"createElement"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(opts"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"audio "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "audio"'),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," :"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "video"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," else"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," (element"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"tagName"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"toLowerCase"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"() "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"==="),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "audio"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},") "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," opts"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"audio "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," (opts"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"autoplay) element"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"autoplay "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "autoplay"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," (opts"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"muted) element"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"muted "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ("),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"opts"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"audio "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"&&"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," opts"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"mirror) "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ["),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'""'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "moz"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "webkit"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "o"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "ms"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"]"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"forEach"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"function"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," ("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"prefix"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," var"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," styleName "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," prefix "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," prefix "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"+"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "Transform"'),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," :"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "transform"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," element"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"style[styleName] "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "scaleX(-1)"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," element"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"srcObject "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," stream"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," return"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," element"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," getLocalStream"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," navigator"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"mediaDevices")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"getUserMedia"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," video"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," audio"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"then"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"stream"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," =>"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," var"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," doesnotexist "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," !"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"el"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"el "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"attachStream"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(stream"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"el"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," audio"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," autoplay"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," (doesnotexist) document"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"body"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"appendChild"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"el)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"catch"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"err"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," =>"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," console"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"log"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"u got an error:"'),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," +"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," err)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1)])),_:1}),h(l,{title:"AR Move/Scale/Rotate Controls for Needle on Mobile",pageUrl:"/docs/community/contributions/robyer1/ar-move-scale-rotate-controls-for-needle-on-mobile"},{default:a(()=>t[1]||(t[1]=[i("p",null,"This is live for preview over at https://needle-ar.glitch.me/",-1),i("p",null,"I will share a github repository if others are interested in collaborating on this, so far I have just sorted functionality for spawning a product model and a floor plane that is large to move it around on. Scale and Rotate work with two finger touches.",-1),i("p",null,"I am hoping to figure out how to show and raycast against AR detected planes over here which will remove the need for a large floor plane to raycast against โ Unknown",-1),i("p",null,"Public Github Repository: https://github.com/ROBYER1/Needle-AR-Demo",-1)])),_:1})]),_:1})])}const F=n(d,[["render",C],["__file","robyer1.html.vue"]]),o=JSON.parse('{"path":"/community/contributions/robyer1","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/contributions: robyer1.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{F as comp,o as data};
diff --git a/assets/sample-rKQKOG12.js b/assets/sample-rKQKOG12.js
new file mode 100644
index 000000000..2fd87573a
--- /dev/null
+++ b/assets/sample-rKQKOG12.js
@@ -0,0 +1 @@
+import{_ as c,o as r,c as o,a as l,g as i}from"./app-CRZRGfEE.js";const d={props:{src:String,split:{type:Boolean,default:!1},noRoom:{type:Boolean,default:!1}},data(){return{sanitizedUrl:""}},watch:{src:{immediate:!0,handler(n){const e=new URL(n),t=Math.random().toString(36).substring(2,6);e.searchParams.delete("room"),this.noRoom||e.searchParams.append("room",`needle_docs_${t}`),typeof window<"u"&&new URL(window.location.href),e.searchParams.append("hideClose",""),e.searchParams.append("utm_source","needle_docs"),e.searchParams.append("utm_content","sample_embed"),this.sanitizedUrl=e.toString()}}},mounted(){const n=e=>{if(!e||!e.contentWindow)return;const t=e.contentWindow.document.getElementsByTagName("iframe")[0];if(t){const a=t.contentWindow.document.querySelector("needle-engine");a&&(a.style.touchAction="pan-y")}};n(this.$refs.frame1),n(this.$refs.frame2)}},m=["src"],p=["src"];function u(n,e,t,s,a,f){return r(),o("div",null,[l("iframe",{src:a.sanitizedUrl,ref:"frame1",allow:"xr; xr-spatial-tracking; camera; microphone; fullscreen; display-capture"},null,8,m),t.split===!0?(r(),o("iframe",{key:0,src:a.sanitizedUrl,ref:"frame2",allow:"xr; xr-spatial-tracking; camera; microphone; fullscreen; display-capture"},null,8,p)):i("",!0)])}const h=c(d,[["render",u],["__scopeId","data-v-b3cc3f9a"],["__file","sample.vue"]]);export{h as default};
diff --git a/assets/samples-and-modules.html-BqywFF0Q.js b/assets/samples-and-modules.html-BqywFF0Q.js
new file mode 100644
index 000000000..13aa51f59
--- /dev/null
+++ b/assets/samples-and-modules.html-BqywFF0Q.js
@@ -0,0 +1 @@
+import{_ as s,r as o,o as t,c as d,b as p,a as e,d as l}from"./app-CRZRGfEE.js";const i={};function r(m,a){const n=o("sample");return t(),d("div",null,[p(n,{src:"https://engine.needle.tools/samples-uploads/physics-cannon/"}),a[0]||(a[0]=e("h2",{id:"samples-to-download-and-play",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#samples-to-download-and-play"},[e("span",null,"Samples to download and play")])],-1)),a[1]||(a[1]=e("p",null,[l("View all samples at "),e("a",{href:"https://engine.needle.tools/samples",target:"_blank",rel:"noopener noreferrer"},"engine.needle.tools/samples"),l(" with a live preview and links for download and installation.")],-1))])}const w=s(i,[["render",r],["__file","samples-and-modules.html.vue"]]),h=JSON.parse('{"path":"/samples-and-modules.html","title":"Samples Projects","lang":"en-US","frontmatter":{"title":"Samples Projects","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/samples projects.png"}],["meta",{"name":"og:description","content":"---\\nView all samples at engine.needle.tools/samples with a live preview and links for download and installation.\\nScripting Examples
import { Behaviour, serializable } from "@needle-tools/engine";
+import { Object3D } from "three"
+
+export class MyClass extends Behaviour {
+ // this will be a "Transform" field in Unity
+ @serializable(Object3D)
+ myObjectReference: Object3D | null = null;
+
+ // this will be a "Transform" array field in Unity
+ // Note that the @serializable decorator contains the array content type! (Object3D and not Object3D[])
+ @serializable(Object3D)
+ myObjectReferenceList: Object3D[] | null = null;
+}
import { Behaviour, serializable, AssetReference } from "@needle-tools/engine";
+
+export class MyClass extends Behaviour {
+
+ // if you export a prefab or scene as a reference from Unity you'll get a path to that asset
+ // which you can de-serialize to AssetReference for convenient loading
+ @serializable(AssetReference)
+ myPrefab?: AssetReference;
+
+ async start() {
+ // directly instantiate
+ const myInstance = await this.myPrefab?.instantiate();
+
+ // you can also just load and instantiate later
+ // const myInstance = await this.myPrefab.loadAssetAsync();
+ // this.gameObject.add(myInstance)
+ // this is useful if you know that you want to load this asset only once because it will not create a copy
+ // since \`\`instantiate()\`\` does create a copy of the asset after loading it
+ }
+}
import { Behaviour, serializable, AssetReference } from "@needle-tools/engine";
+
+export class LoadingScenes extends Behaviour {
+ // tell the component compiler that we want to reference an array of SceneAssets
+ // @type UnityEditor.SceneAsset[]
+ @serializable(AssetReference)
+ myScenes?: AssetReference[];
+
+ async awake() {
+ if (!this.myScenes) {
+ return;
+ }
+ for (const scene of this.myScenes) {
+ // check if it is assigned in unity
+ if(!scene) continue;
+ // load the scene once
+ const myScene = await scene.loadAssetAsync();
+ // add it to the threejs scene
+ this.gameObject.add(myScene);
+
+ // of course you can always just load one at a time
+ // and remove it from the scene when you want
+ // myScene.removeFromParent();
+ // this is the same as scene.asset.removeFromParent()
+ }
+ }
+
+ onDestroy(): void {
+ if (!this.myScenes) return;
+ for (const scene of this.myScenes) {
+ scene?.unload();
+ }
+ }
+}
Add this script to any object in your scene that you want to be clickable. Make sure to also have an ObjectRaycaster component in the parent hierarchy of that object.
`,12)),a(t,{file:"@code/component-click.ts"},{default:e(()=>i[0]||(i[0]=[r(" test ")])),_:1}),i[3]||(i[3]=s(`
import { Behaviour, IPointerClickHandler, PointerEventData, showBalloonMessage } from "@needle-tools/engine";
+
+export class ClickExample extends Behaviour implements IPointerClickHandler {
+
+ // Make sure to have an ObjectRaycaster component in the parent hierarchy
+ onPointerClick(_args: PointerEventData) {
+ showBalloonMessage("Clicked " + this.name);
+ }
+}
Add this script to any object in your scene that you want to be clickable. Make sure to also have an ObjectRaycaster component in the parent hierarchy of that object. The component will send the received click to all connected clients and will raise an event that you can then react to in your app. If you are using Unity or Blender you can simply assign functions to call to the onClick event to e.g. play an animation or hide objects.
EventList events are also invoked on the component level. This means you can also subscribe to the event declared above using myComponent.addEventListener("my-event", evt => {...}) as well. This is an experimental feature. Please provide feedback in our forum
This is useful for when you want to expose an event to Unity or Blender with some custom arguments (like a string)
import { Behaviour, serializable, EventList } from "@needle-tools/engine";
+import { Object3D } from "three";
+
+/*
+Make sure to have a c# file in your project with the following content:
+
+using UnityEngine;
+using UnityEngine.Events;
+
+[System.Serializable]
+public class MyCustomUnityEvent : UnityEvent<string>
+{
+}
+
+Unity documentation about custom events:
+https://docs.unity3d.com/ScriptReference/Events.UnityEvent_2.html
+
+*/
+
+// Documentation โ https://docs.needle.tools/scripting
+
+export class CustomEventCaller extends Behaviour {
+
+ // The next line is not just a comment, it defines
+ // a specific type for the component generator to use.
+
+ //@type MyCustomUnityEvent
+ @serializable(EventList)
+ myEvent!: EventList;
+
+ // just for testing - could be when a button is clicked, etc.
+ start() {
+ this.myEvent.invoke("Hello");
+ }
+}
+
+export class CustomEventReceiver extends Behaviour {
+
+ logStringAndObject(str: string) {
+ console.log("From Event: ", str);
+ }
+}
You can nest objects and their data. With properly matching @serializable(SomeType) decorators, the data will be serialized and deserialized into the correct types automatically.
using System;
+
+[Serializable]
+public class CustomSubData
+{
+ public string subString;
+ public float subNumber;
+}
+
+[Serializable]
+public class CustomData
+{
+ public string myStringField;
+ public float myNumberField;
+ public bool myBooleanField;
+ public CustomSubData subData;
+}
Tips
Without the correct type decorators, you will still get the data, but just as a plain object. This is useful when you're porting components, as you'll have access to all data and can add types as required.
Assuming you have a custom shader with a property name _Speed that is a float value this is how you would change it from a script. You can find a live example to download in our samples
Make sure to install npm i postprocessing in your web project. Then you can add new effects by deriving from PostProcessingEffect.
To use the effect add it to the same object as your Volume component.
Here is an example that wraps the Outline postprocessing effect. You can expose variables and settings as usual as any effect is also just a component in your three.js scene.
import { EffectProviderResult, PostProcessingEffect, registerCustomEffectType, serializable } from "@needle-tools/engine";
+import { OutlineEffect } from "postprocessing";
+import { Object3D } from "three";
+
+export class OutlinePostEffect extends PostProcessingEffect {
+
+ // the outline effect takes a list of objects to outline
+ @serializable(Object3D)
+ selection!: Object3D[];
+
+ // this is just an example method that you could call to update the outline effect selection
+ updateSelection() {
+ if (this._outlineEffect) {
+ this._outlineEffect.selection.clear();
+ for (const obj of this.selection) {
+ this._outlineEffect.selection.add(obj);
+ }
+ }
+ }
+
+
+ // a unique name is required for custom effects
+ get typeName(): string {
+ return "Outline";
+ }
+
+ private _outlineEffect: void | undefined | OutlineEffect;
+
+ // method that creates the effect once
+ onCreateEffect(): EffectProviderResult | undefined {
+
+ const outlineEffect = new OutlineEffect(this.context.scene, this.context.mainCamera!);
+ this._outlineEffect = outlineEffect;
+ outlineEffect.edgeStrength = 10;
+ outlineEffect.visibleEdgeColor.set(0xff0000);
+ for (const obj of this.selection) {
+ outlineEffect.selection.add(obj);
+ }
+
+ return outlineEffect;
+ }
+}
+// You need to register your effect type with the engine
+registerCustomEffectType("Outline", OutlinePostEffect);
This is an example how you could create your own audio component. For most usecases however you can use the core AudioSource component and don't have to write code.
import { AudioSource, Behaviour, serializable } from "@needle-tools/engine";
+
+// declaring AudioClip type is for codegen to produce the correct input field (for e.g. Unity or Blender)
+declare type AudioClip = string;
+
+export class My2DAudio extends Behaviour {
+
+ // The clip contains a string pointing to the audio file - by default it's relative to the GLB that contains the component
+ // by adding the URL decorator the clip string will be resolved relative to your project root and can be loaded
+ @serializable(URL)
+ clip?: AudioClip;
+
+ awake() {
+ // creating a new audio element and playing it
+ const audioElement = new Audio(this.clip);
+ audioElement.loop = true;
+ // on the web we have to wait for the user to interact with the page before we can play audio
+ AudioSource.registerWaitForAllowAudio(() => {
+ audioElement.play();
+ })
+ }
+}
Use the FileReference type to load external files (e.g. a json file)
import { Behaviour, FileReference, ImageReference, serializable } from "@needle-tools/engine";
+
+export class FileReferenceExample extends Behaviour {
+
+ // A FileReference can be used to load and assign arbitrary data in the editor. You can use it to load images, audio, text files... FileReference types will not be saved inside as part of the GLB (the GLB will only contain a relative URL to the file)
+ @serializable(FileReference)
+ myFile?: FileReference;
+ // Tip: if you want to export and load an image (that is not part of your GLB) if you intent to add it to your HTML content for example you can use the ImageReference type instead of FileReference. It will be loaded as an image and you can use it as a source for an <img> tag.
+
+ async start() {
+ console.log("This is my file: ", this.myFile);
+ // load the file
+ const data = await this.myFile?.loadRaw();
+ if (!data) {
+ console.error("Failed loading my file...");
+ return;
+ }
+ console.log("Loaded my file. These are the bytes:", await data.arrayBuffer());
+ }
+}
import { Behaviour, EventList, serializable, serializeable } from "@needle-tools/engine";
+
+export class HTMLButtonClick extends Behaviour {
+
+ /** Enter a button query (e.g. button.some-button if you're interested in a button with the class 'some-button')
+ * Or you can also use an id (e.g. #some-button if you're interested in a button with the id 'some-button')
+ * Or you can also use a tag (e.g. button if you're interested in any button
+ */
+ @serializeable()
+ htmlSelector: string = "button.some-button";
+
+ /** This is the event to be invoked when the html element is clicked. In Unity or Blender you can assign methods to be called in the Editor */
+ @serializable(EventList)
+ onClick: EventList = new EventList();
+
+ private element? : HTMLButtonElement;
+
+ onEnable() {
+ // Get the element from the DOM
+ this.element = document.querySelector(this.htmlSelector) as HTMLButtonElement;
+ if (this.element) {
+ this.element.addEventListener('click', this.onClicked);
+ }
+ else console.warn(\`Could not find element with selector \\"\${this.htmlSelector}\\"\`);
+ }
+
+ onDisable() {
+ if (this.element) {
+ this.element.removeEventListener('click', this.onClicked);
+ }
+ }
+
+ private onClicked = () => {
+ this.onClick.invoke();
+ }
+}
import { Behaviour, GameObject, LogType, serializeable, showBalloonMessage, WaitForSeconds } from "@needle-tools/engine";
+
+export class TimedSpawn extends Behaviour {
+ @serializeable(GameObject)
+ object?: GameObject;
+
+ interval: number = 1000;
+ max: number = 100;
+
+ private spawned: number = 0;
+
+ awake() {
+ if (!this.object) {
+ console.warn("TimedSpawn: no object to spawn");
+ showBalloonMessage("TimedSpawn: no object to spawn", LogType.Warn);
+ return;
+ }
+ GameObject.setActive(this.object, false);
+ this.startCoroutine(this.spawn())
+ }
+
+ *spawn() {
+ if (!this.object) return;
+ while (this.spawned < this.max) {
+ const instance = GameObject.instantiate(this.object);
+ GameObject.setActive(instance!, true);
+ this.spawned += 1;
+ yield WaitForSeconds(this.interval / 1000);
+ }
+ }
+}
`,50))])}const F=l(d,[["render",C],["__file","scripting-examples.html.vue"]]),c=JSON.parse('{"path":"/scripting-examples.html","title":"Scripting Examples","lang":"en-US","frontmatter":{"title":"Scripting Examples","description":"A collection of useful script snippets and examples.","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/scripting examples.png"}],["meta",{"name":"og:description","content":"A collection of useful script snippets and examples."}]]},"headers":[{"level":2,"title":"Basic component","slug":"basic-component","link":"#basic-component","children":[]},{"level":2,"title":"Reference an Object from Unity","slug":"reference-an-object-from-unity","link":"#reference-an-object-from-unity","children":[]},{"level":2,"title":"Reference and load an asset from Unity (Prefab or SceneAsset)","slug":"reference-and-load-an-asset-from-unity-prefab-or-sceneasset","link":"#reference-and-load-an-asset-from-unity-prefab-or-sceneasset","children":[]},{"level":2,"title":"Reference and load scenes from Unity","slug":"reference-and-load-scenes-from-unity","link":"#reference-and-load-scenes-from-unity","children":[]},{"level":2,"title":"Receive Clicks on Objects","slug":"receive-clicks-on-objects","link":"#receive-clicks-on-objects","children":[]},{"level":2,"title":"Networking Clicks on Objects","slug":"networking-clicks-on-objects","link":"#networking-clicks-on-objects","children":[{"level":3,"title":"Play Animation on click","slug":"play-animation-on-click","link":"#play-animation-on-click","children":[]}]},{"level":2,"title":"Reference an Animation Clip","slug":"reference-an-animation-clip","link":"#reference-an-animation-clip","children":[]},{"level":2,"title":"Create and invoke a UnityEvent","slug":"create-and-invoke-a-unityevent","link":"#create-and-invoke-a-unityevent","children":[{"level":3,"title":"Declare a custom event type","slug":"declare-a-custom-event-type","link":"#declare-a-custom-event-type","children":[]}]},{"level":2,"title":"Use nested objects and serialization","slug":"use-nested-objects-and-serialization","link":"#use-nested-objects-and-serialization","children":[]},{"level":2,"title":"Use Web APIs","slug":"use-web-apis","link":"#use-web-apis","children":[{"level":3,"title":"Display current location","slug":"display-current-location","link":"#display-current-location","children":[]},{"level":3,"title":"Display current time using a Coroutine","slug":"display-current-time-using-a-coroutine","link":"#display-current-time-using-a-coroutine","children":[]}]},{"level":2,"title":"Change custom shader property","slug":"change-custom-shader-property","link":"#change-custom-shader-property","children":[]},{"level":2,"title":"Switching src attribute","slug":"switching-src-attribute","link":"#switching-src-attribute","children":[]},{"level":2,"title":"Adding new postprocessing effects","slug":"adding-new-postprocessing-effects","link":"#adding-new-postprocessing-effects","children":[]},{"level":2,"title":"Custom ParticleSystem Behaviour","slug":"custom-particlesystem-behaviour","link":"#custom-particlesystem-behaviour","children":[]},{"level":2,"title":"Custom 2D Audio Component","slug":"custom-2d-audio-component","link":"#custom-2d-audio-component","children":[]},{"level":2,"title":"Arbitrary external files","slug":"arbitrary-external-files","link":"#arbitrary-external-files","children":[]},{"level":2,"title":"Receiving html element click in component","slug":"receiving-html-element-click-in-component","link":"#receiving-html-element-click-in-component","children":[]},{"level":2,"title":"Disable environment light","slug":"disable-environment-light","link":"#disable-environment-light","children":[]},{"level":2,"title":"Use mediapipe package to control the 3D scene with hands","slug":"use-mediapipe-package-to-control-the-3d-scene-with-hands","link":"#use-mediapipe-package-to-control-the-3d-scene-with-hands","children":[]},{"level":2,"title":"Change Color On Collision","slug":"change-color-on-collision","link":"#change-color-on-collision","children":[]},{"level":2,"title":"Physics Trigger Relay","slug":"physics-trigger-relay","link":"#physics-trigger-relay","children":[]},{"level":2,"title":"Auto Reset","slug":"auto-reset","link":"#auto-reset","children":[]},{"level":2,"title":"Play Audio On Collision","slug":"play-audio-on-collision","link":"#play-audio-on-collision","children":[]},{"level":2,"title":"Set Random Color","slug":"set-random-color","link":"#set-random-color","children":[]},{"level":2,"title":"Spawn Objects Over Time","slug":"spawn-objects-over-time","link":"#spawn-objects-over-time","children":[]}],"git":{"updatedTime":1726606065000},"filePathRelative":"scripting-examples.md"}');export{F as comp,c as data};
diff --git a/assets/scripting.html-UnvL8K8f.js b/assets/scripting.html-UnvL8K8f.js
new file mode 100644
index 000000000..efacc0eb7
--- /dev/null
+++ b/assets/scripting.html-UnvL8K8f.js
@@ -0,0 +1,166 @@
+import{_ as h,r as k,o as p,c as o,a as t,b as a,w as n,d as s,e as l}from"./app-CRZRGfEE.js";const r={},d={class:"hint-container details"};function c(g,i){const e=k("RouteLink");return p(),o("div",null,[i[35]||(i[35]=t("p",null,"If you are new to scripting we recommend reading the following guides first:",-1)),t("ul",null,[t("li",null,[a(e,{to:"/getting-started/typescript-essentials.html"},{default:n(()=>i[0]||(i[0]=[s("Typescript Essentials")])),_:1})]),t("li",null,[a(e,{to:"/getting-started/for-unity-developers.html"},{default:n(()=>i[1]||(i[1]=[s("Needle Engine for Unity Developers")])),_:1})])]),i[36]||(i[36]=t("p",null,[s("If you know what you're doing, feel free to jump right into the "),t("a",{href:"https://engine.needle.tools/docs/api/latest",target:"_blank",rel:"noopener noreferrer"},"Needle Engine API documentation"),s(".")],-1)),i[37]||(i[37]=t("hr",null,null,-1)),i[38]||(i[38]=t("p",null,[s("Runtime code for Needle Engine is written in "),t("a",{href:"https://typescriptlang.org",target:"_blank",rel:"noopener noreferrer"},"TypeScript"),s(" (recommended) or "),t("a",{href:"https://javascript.info/",target:"_blank",rel:"noopener noreferrer"},"JavaScript"),s(". We automatically generate C# stub components out of that, which you can add to GameObjects in the editor. The C# components and their data are recreated by the runtime as JavaScript components with the same data and attached to three.js objects.")],-1)),t("p",null,[i[3]||(i[3]=s("Both custom components as well as built-in Unity components can be mapped to JavaScript components in this way. For example, mappings for many built-in components related to animation, rendering or physics are already ")),a(e,{to:"/component-reference.html#unity-components"},{default:n(()=>i[2]||(i[2]=[s("included in Needle Engine")])),_:1}),i[4]||(i[4]=s("."))]),i[39]||(i[39]=l('
If you want to code-along with the following examples without having to install anything you just click the following link:
Our web runtime engine adopts a component model similar to Unity and thus provides a lot of functionality that will feel familiar. Components attached to three's Object3D objects have lifecycle methods like awake, start, onEnable, onDisable, update and lateUpdate that you can implement. You can also use Coroutines.
Often, interactive scenes can be realized using Events in Unity and calling methods on built-in components. A typical example is playing an animation on button click - you create a button, add a Click event in the inspector, and have that call Animator.SetTrigger or similar to play a specific animation.
Needle Engine translates Unity Events into JavaScript method calls, which makes this a very fast and flexible workflow - set up your events as usual and when they're called they'll work the same as in Unity.
An example of a Button Click Event that is working out-of-the-box in Needle Engine โ no code needed.
Scripts are written in TypeScript (recommended) or JavaScript. There are two ways to add custom scripts to your project:
',11)),t("ul",null,[i[16]||(i[16]=t("li",null,[t("p",null,[s("Simply add a file with an "),t("code",null,".ts"),s(" or "),t("code",null,".js"),s(" extension inside "),t("code",null,"src/scripts/"),s(" in your generated project directory, for example "),t("code",null,"src/scripts/MyFirstScript.ts")])],-1)),t("li",null,[t("p",null,[i[6]||(i[6]=s("Unity specific:")),i[7]||(i[7]=t("br",null,null,-1)),i[8]||(i[8]=s(" Organize your code into NPM Definition Files (npm packages). These help you to modularize and re-use code between projects and if you are familiar with web development they are in fact regular npm packages that are installed locally.")),i[9]||(i[9]=t("br",null,null,-1)),i[10]||(i[10]=s(" In Unity you can create NpmDef files via ")),i[11]||(i[11]=t("code",null,"Create > NPM Definition",-1)),i[12]||(i[12]=s(" and then add TypeScript files by right-clicking an NpmDef file and selecting ")),i[13]||(i[13]=t("code",null,"Create > TypeScript",-1)),i[14]||(i[14]=s(". Please see ")),a(e,{to:"/project-structure.html#npm-definition-files"},{default:n(()=>i[5]||(i[5]=[s("this chapter")])),_:1}),i[15]||(i[15]=s(" for more information."))])])]),i[40]||(i[40]=t("p",null,[s("In both approaches, source directories are watched for changes and C# stub components or Blender panels are regenerated whenever a change is detected."),t("br"),s(" Changes to the source files also result in a hot reload of the running website โ you don't have to wait for Unity to recompile the C# components. This makes iterating on code pretty much instant.")],-1)),i[41]||(i[41]=t("p",null,[s("You can even have multiple component types inside one file (e.g. you can declare "),t("code",null,"export class MyComponent1"),s(" and "),t("code",null,"export class MyOtherComponent"),s(" in the same Typescript file).")],-1)),t("p",null,[i[18]||(i[18]=s("If you are new to writing Javascript or Typescript we recommend reading the ")),a(e,{to:"/getting-started/typescript-essentials.html"},{default:n(()=>i[17]||(i[17]=[s("Typescript Essentials Guide")])),_:1}),i[19]||(i[19]=s(" guide first before continuing with this guide."))]),i[42]||(i[42]=l(`Example: Creating a Component that rotates an object
Create a component that rotates an object Create src/scripts/Rotate.ts and add the following code:
import { Behaviour, serializable } from "@needle-tools/engine";
+
+export class Rotate extends Behaviour
+{
+ @serializable()
+ speed : number = 1;
+
+ start(){
+ // logging this is useful for debugging in the browser.
+ // You can open the developer console (F12) to see what data your component contains
+ console.log(this);
+ }
+
+ // update will be called every frame
+ update(){
+ this.gameObject.rotateY(this.context.time.deltaTime * this.speed);
+ }
+}
Now inside Unity a new script called Rotate.cs will be automatically generated. Add the new Unity component to a Cube and save the scene. The cube is now rotating inside the browser. Open the chrome developer console by F12 to inspect the log from the Rotate.start method. This is a helpful practice to learn and debug what fields are exported and currently assigned. In general all public and serializable fields and all public properties are exported.
Now add a new field public float speed = 5 to your Unity component and save it. The Rotate component inspector now shows a speed field that you can edit. Save the scene (or click the Build button) and note that the javascript component now has the exported speed value assigned.
`,1)),t("details",d,[i[23]||(i[23]=t("summary",null,"Create component with a custom function",-1)),t("p",null,[i[21]||(i[21]=s("Refer to the ")),a(e,{to:"/getting-started/typescript-essentials.html"},{default:n(()=>i[20]||(i[20]=[s("Typescript Essentials Guide")])),_:1}),i[22]||(i[22]=s(" to learn more about the syntax and language."))]),i[24]||(i[24]=l(`
import { Behaviour } from "@needle-tools/engine";
+
+export class PrintNumberComponent extends Behaviour
+{
+ start(){
+ this.printNumber(42);
+ }
+
+ private printNumber(myNumber : number){
+ console.log("My Number is: " + myNumber);
+ }
+}
`,1))]),i[43]||(i[43]=l(`Version Control & Unity
While generated C# components use the type name to produce stable GUIDs, we recommend checking in generated components in version control as a good practice.
Components are added to three.js Object3Ds. This is similar to how Components in Unity are added to GameObjects. Therefore when we want to access a three.js Object3D, we can access it as this.gameObject which returns the Object3D that the component is attached to.
Note: Setting visible to false on a Object3D will act like SetActive(false) in Unity - meaning it will also disable all the current components on this object and its children. Update events for inactive components are not being called until visible is set to true again. If you want to hide an object without affecting components you can just disable the Needle Engine Renderer component.
Note that lifecycle methods are only being called when they are declared. So only declare update lifecycle methods when they are actually necessary, otherwise it may hurt performance if you have many components with update loops that do nothing.
Method name
Description
awake()
First method being called when a new component is created
onEnable()
Called when a component is enabled (e.g. when enabled changes from false to true)
onDisable()
Called when a component is disabled (e.g. when enabled changes from true to false)
onDestroy()
called when the Object3D or component is being destroyed
start()
Called on the start of the first frame after the component was created
Optionally implement if you only want to receive XR callbacks for specific XR modes like immersive-vr or immersive-ar. Return true to notify the system that you want callbacks for the passed in mode
Callback when a controller is connected/added while in a XR session OR when the component joins a running XR session that has already connected controllers OR when the component becomes active during a running XR session that has already connected controllers
Coroutines can be declared using the JavaScript Generator Syntax. To start a coroutine, call this.startCoroutine(this.myRoutineName());
Example
import { Behaviour, FrameEvent } from "@needle-tools/engine";
+
+export class Rotate extends Behaviour {
+
+ start() {
+ // the second argument is optional and allows you to specifiy
+ // when it should be called in the current frame loop
+ // coroutine events are called after regular component events of the same name
+ // for example: Update coroutine events are called after component.update() functions
+ this.startCoroutine(this.rotate(), FrameEvent.Update);
+ }
+
+ // this method is called every frame until the component is disabled
+ *rotate() {
+ // keep looping forever
+ while (true) {
+ yield;
+ }
+ }
+}
To stop a coroutine, either exit the routine by returning from it, or cache the return value of startCoroutine and call this.stopCoroutine(<...>). All Coroutines are stopped at onDisable / when disabling a component.
Needle Engine also exposes a few lifecycle hooks that you can use to hook into the update loop without having to write a full component. Those hooks can be inserted at any point in your web application (for example in toplevel scope or in a svelte component)
Method name
Description
onInitialized(cb, options)
Called when a new context is initialized (before the first frame)
onClear(cb, options)
Register a callback before the engine context is cleared
onDestroy(cb, options)
Register a callback in the engine before the context is destroyed
onStart(cb, options)
Called directly after components start at the beginning of a frame
// this can be put into e.g. main.ts or a svelte component (similar to onMount)
+import { Context, onUpdate, onBeforeRender, onAfterRender } from "@needle-tools/engine"
+onUpdate((ctx: Context) => {
+ // do something... e.g. access the scene via ctx.scene
+ console.log("UPDATE", ctx.time.frame);
+});
+
+onBeforeRender((ctx: Context) => {
+ // this event is only called once because of the { once: true } argument
+ console.log("ON BEFORE RENDER", ctx.time.frame);
+}, { once: true } );
+
+// Every event hook returns a method to unsubscribe from the event
+const unsubscribe = onAfterRender((ctx: Context) => {
+ console.log("ON AFTER RENDER", ctx.time.frame);
+});
+// Unsubscribe from the event at any time
+setTimeout(()=> unsubscribe(), 1000);
To access other components, use the static methods on GameObject or this.gameObject methods. For example, to access a Renderer component in the parent use GameObject.getComponentInParent(this.gameObject, Renderer) or this.gameObject.getComponentInParent(Renderer).
creates a new instance of this object including new instances of all its components
GameObject.destroy(Object3D | Component)
destroy a component or Object3D (and its components)
GameObject.addNewComponent(Object3D, Type)
adds (and creates) a new component for a type to the provided object. Note that awake and onEnable is already called when the component is returned
GameObject.addComponent(Object3D, Component)
moves a component instance to the provided object. It is useful if you already have an instance e.g. when you create a component with e.g. new MyComponent() and then attach it to a object
GameObject.removeComponent(Component)
removes a component from a gameObject
GameObject.getComponent(Object3D, Type)
returns the first component matching a type on the provided object.
GameObject.getComponents(Object3D, Type)
returns all components matching a type on the provided object.
GameObject.getComponentInChildren
same as getComponent but also searches in child objects.
GameObject.getComponentsInChildren
same as getComponents but also searches in child objects.
GameObject.getComponentInParent
same as getComponent but also searches in parent objects.
GameObject.getComponentsInParent
same as getComponents but also searches in parent objects.
The context refers to the runtime inside a web component. The three.js scene lives inside a custom HTML component called <needle-engine> (see the index.html in your project). You can access the <needle-engine> web component using this.context.domElement.
This architecture allows for potentially having multiple needle WebGL scenes on the same webpage, that can either run on their own or communicate between each other as parts of your webpage.
To access the current scene from a component you use this.scene which is equivalent to this.context.scene, this gives you the root three.js scene object.
To traverse the hierarchy from a component you can either iterate over the children of an object with a for loop:
for(let i = 0; i < this.gameObject.children; i++)
+ console.log(this.gameObject.children[i]);
or you can iterate using the foreach equivalent:
for(const child of this.gameObject.children) {
+ console.log(child);
+}
You can also use three.js specific methods to quickly iterate all objects recursively using the traverse method:
import { Object3D } from "three";
+this.gameObject.traverse((obj: Object3D) => console.log(obj));
or to just traverse visible objects use traverseVisible instead.
Another option that is quite useful when you just want to iterate objects being renderable you can query all renderer components and iterate over them like so:
import { Renderer } from "@needle-tools/engine";
+for(const renderer of this.gameObject.getComponentsInChildren(Renderer))
+ console.log(renderer);
For more information about getting components see the next section.
Receive input data for the object the component is on:
import { Behaviour } from "@needle-tools/engine";
+export class MyScript extends Behaviour
+{
+ onPointerDown() {
+ console.log("POINTER DOWN on " + this.gameObject.name);
+ }
+}
You can also subscribe to global events in the InputEvents enum like so:
import { Behaviour, InputEvents, NEPointerEvent } from "@needle-tools/engine";
+
+export class MyScript extends Behaviour
+{
+ onEnable() {
+ this.context.input.addEventListener(InputEvents.PointerDown, this.inputPointerDown);
+ }
+
+ onDisable() {
+ // it is recommended to also unsubscribe from events when your component becomes inactive
+ this.context.input.removeEventListener(InputEvents.PointerDown, this.inputPointerDown);
+ }
+
+ // @nonSerialized
+ inputPointerDown = (evt: NEPointerEvent) => { console.log("POINTER DOWN anywhere on the <needle-engine> element"); }
+}
Or use this.context.input if you want to poll input state every frame:
import { Behaviour } from "@needle-tools/engine";
+export class MyScript extends Behaviour
+{
+ update() {
+ if(this.context.input.getPointerDown(0)){
+ console.log("POINTER DOWN anywhere")
+ }
+ }
+}
If you want to handle inputs yourself you can also subscribe to all events the browser provides (there are a ton). For example to subscribe to the browsers click event you can write:
import { Behaviour } from "@needle-tools/engine";
+export class MyScript extends Behaviour
+{
+ onEnable() {
+ window.addEventListener("click", this.windowClick);
+ }
+
+ onDisable() {
+ // unsubscribe again when the component is disabled
+ window.removeEventListener("click", this.windowClick);
+ }
+
+ windowClick = () => { console.log("CLICK anywhere on the page, not just on <needle-engine>"); }
+}
Note that in this case you have to handle all cases yourself. For example you may need to use different events if your user is visiting your website on desktop vs mobile vs a VR device. These cases are automatically handled by the Needle Engine input events (e.g. PointerDown is raised both for mouse down, touch down and in case of VR on controller button down).
Use this.context.physics.raycast() to perform a raycast and get a list of intersections. If you dont pass in any options the raycast is performed from the mouse position (or first touch position) in screenspace using the currently active mainCamera. You can also pass in a RaycastOptions object that has various settings like maxDistance, the camera to be used or the layers to be tested against.
Use this.context.physics.raycastFromRay(your_ray) to perform a raycast using a three.js ray
Note that the calls above are by default raycasting against visible scene objects. That is different to Unity where you always need colliders to hit objects. The default three.js solution has both pros and cons where one major con is that it can perform quite slow depending on your scene geometry. It may be especially slow when raycasting against skinned meshes. It is therefor recommended to usually set objects with SkinnedMeshRenderers in Unity to the Ignore Raycast layer which will then be ignored by default by Needle Engine as well.
Another option is to use the physics raycast methods which will only return hits with colliders in the scene.
const hit = this.context.physics.engine?.raycast();
`,69)),t("p",null,[i[26]||(i[26]=s("Networking methods can be accessed via ")),i[27]||(i[27]=t("code",null,"this.context.connection",-1)),i[28]||(i[28]=s(". Please refer to the ")),a(e,{to:"/networking.html"},{default:n(()=>i[25]||(i[25]=[s("networking docs")])),_:1}),i[29]||(i[29]=s(" for further information."))]),i[44]||(i[44]=l(`
It is possible to access all the functionality described above using regular JavaScript code that is not inside components and lives somewhere else. All the components and functionality of the needle runtime is accessible via the global Needle namespace (you can write console.log(Needle) to get an overview)
You can find components using Needle.findObjectOfType(Needle.AudioSource) for example. It is recommended to cache those references, as searching the whole scene repeatedly is expensive. See the list for finding adding and removing components above.
For getting callbacks for the initial scene load see the following example:
You can also subscribe to the globale NeedleEngine (sometimes also referred to as ContextRegistry) to receive a callback when a Needle Engine context has been created or to access all available contexts:
Another option is using the onInitialized(ctx => {})lifecycle hook
You can also access all available contexts via NeedleEngine.Registered which returns the internal array. (Note that this array should not be modified but can be used to iterate all active contexts to modify settings, e.g. set all contexts to context.isPaused = true)
Below you find a list of available events on the static NeedleEngine type. You can subscribe to those events via NeedleEngine.registerCallback(ContextEvent.ContextCreated, (args) => {})
ContextEvent options
ContextEvent.ContextRegistered
Called when the context is registered to the registry.
ContextEvent.ContextCreationStart
Called before the first glb is loaded and can be used to initialize the physics engine. Can return a promise
ContextEvent.ContextCreated
Called when the context has been created before the first frame
ContextEvent.ContextDestroyed
Called when the context has been destroyed
ContextEvent.MissingCamera
Called when the context could not find a camera, currently only called during creation
ContextEvent.ContextClearing
Called when the context is being cleared: all objects in the scene are being destroyed and internal state is reset
The static Gizmos class can be used to draw lines, shapes and text which is mostly useful for debugging. All gizmos function have multiple options for e.g. colors or for how long they should be displayed in the scene. Internally they are cached and re-used.
Gizmos
Gizmos.DrawLabel
Draws a label with a background optionally. It can be attached to an object. Returns a Label handle which can be used to update the text.
Gizmos.DrawRay
Takes an origin and direction in worldspace to draw an infinite ray line
Gizmos.DrawDirection
Takes a origin and direction to draw a direction in worldspace
To embed components and recreate components with their correct types in glTF, we also need to save non-primitive types (everything that is not a Number, Boolean or String). You can do so is adding a @serializable(<type>) decorator above your field or property.
Example:
import { Behaviour, serializable } from "@needle-tools/engine";
+import { Object3D } from "three"
+
+export class MyClass extends Behaviour {
+ // this will be a "Transform" field in Unity
+ @serializable(Object3D)
+ myObjectReference: Object3D | null = null;
+
+ // this will be a "Transform" array field in Unity
+ // Note that the @serializable decorator contains the array content type! (Object3D and not Object3D[])
+ @serializable(Object3D)
+ myObjectReferenceList: Object3D[] | null = null;
+}
To serialize from and to custom formats, it is possible to extend from the TypeSerializer class and create an instance. Use super() in the constructor to register supported types.
Note: In addition to matching fields, matching properties will also be exported when they match to fields in the typescript file.
`,21)),t("p",null,[i[31]||(i[31]=s("Referenced Prefabs, SceneAssets and ")),i[32]||(i[32]=t("a",{href:"https://docs.unity3d.com/Packages/com.unity.addressables@latest/manual/AddressableAssetsGettingStarted.html",target:"_blank",rel:"noopener noreferrer"},[t("code",null,"AssetReferences")],-1)),i[33]||(i[33]=s(" in Unity will automatically be exported as glTF files (please refer to the ")),a(e,{to:"/export.html"},{default:n(()=>i[30]||(i[30]=[s("Export Prefabs")])),_:1}),i[34]||(i[34]=s(" documentation)."))]),i[45]||(i[45]=l(`
These exported gltf files will be serialized as plain string URIs. To simplify loading these from TypeScript components, we added the concept of AssetReference types. They can be loaded at runtime and thus allow to defer loading parts of your app or loading external content.
Example:
import { Behaviour, serializable, AssetReference } from "@needle-tools/engine";
+
+export class MyClass extends Behaviour {
+
+ // if you export a prefab or scene as a reference from Unity you'll get a path to that asset
+ // which you can de-serialize to AssetReference for convenient loading
+ @serializable(AssetReference)
+ myPrefab?: AssetReference;
+
+ async start() {
+ // directly instantiate
+ const myInstance = await this.myPrefab?.instantiate();
+
+ // you can also just load and instantiate later
+ // const myInstance = await this.myPrefab.loadAssetAsync();
+ // this.gameObject.add(myInstance)
+ // this is useful if you know that you want to load this asset only once because it will not create a copy
+ // since \`\`instantiate()\`\` does create a copy of the asset after loading it
+ }
+}
AssetReferences are cached by URI, so if you reference the same exported glTF/Prefab in multiple components/scripts it will only be loaded once and then re-used.
`,4))])}const C=h(r,[["render",c],["__file","scripting.html.vue"]]),F=JSON.parse(`{"path":"/scripting.html","title":"Creating and using Components","lang":"en-US","frontmatter":{"title":"Creating and using Components","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/creating and using components.png"}],["meta",{"name":"og:description","content":"---\\nIf you are new to scripting we recommend reading the following guides first:"}]],"description":"---\\nIf you are new to scripting we recommend reading the following guides first:"},"headers":[{"level":2,"title":"When you don't need to write code","slug":"when-you-don-t-need-to-write-code","link":"#when-you-don-t-need-to-write-code","children":[]},{"level":2,"title":"Creating a new component","slug":"creating-a-new-component","link":"#creating-a-new-component","children":[]},{"level":2,"title":"Component architecture","slug":"component-architecture","link":"#component-architecture","children":[{"level":3,"title":"Lifecycle methods","slug":"lifecycle-methods","link":"#lifecycle-methods","children":[]},{"level":3,"title":"Physic event methods","slug":"physic-event-methods","link":"#physic-event-methods","children":[]},{"level":3,"title":"Input event methods","slug":"input-event-methods","link":"#input-event-methods","children":[]},{"level":3,"title":"XR event methods","slug":"xr-event-methods","link":"#xr-event-methods","children":[]},{"level":3,"title":"Coroutines","slug":"coroutines","link":"#coroutines","children":[]}]},{"level":2,"title":"Special Lifecycle hooks","slug":"special-lifecycle-hooks","link":"#special-lifecycle-hooks","children":[]},{"level":2,"title":"Finding, adding and removing components","slug":"finding-adding-and-removing-components","link":"#finding-adding-and-removing-components","children":[{"level":3,"title":"Some of the available methods:","slug":"some-of-the-available-methods","link":"#some-of-the-available-methods","children":[]}]},{"level":2,"title":"Three.js and the HTML DOM","slug":"three.js-and-the-html-dom","link":"#three.js-and-the-html-dom","children":[{"level":3,"title":"Access the scene","slug":"access-the-scene","link":"#access-the-scene","children":[]},{"level":3,"title":"Time","slug":"time","link":"#time","children":[]},{"level":3,"title":"Input","slug":"input","link":"#input","children":[]},{"level":3,"title":"Physics","slug":"physics","link":"#physics","children":[]},{"level":3,"title":"Networking","slug":"networking","link":"#networking","children":[]}]},{"level":2,"title":"Accessing Needle Engine and components from anywhere","slug":"accessing-needle-engine-and-components-from-anywhere","link":"#accessing-needle-engine-and-components-from-anywhere","children":[]},{"level":2,"title":"Gizmos","slug":"gizmos","link":"#gizmos","children":[]},{"level":2,"title":"Serialization / Components in glTF files","slug":"serialization-components-in-gltf-files","link":"#serialization-components-in-gltf-files","children":[]},{"level":2,"title":"Loading Scenes","slug":"loading-scenes","link":"#loading-scenes","children":[]}],"git":{"updatedTime":1726585195000},"filePathRelative":"scripting.md"}`);export{C as comp,F as data};
diff --git a/assets/set-fallback-material-for-usdz-exporter.html-BEiyoRdx.js b/assets/set-fallback-material-for-usdz-exporter.html-BEiyoRdx.js
new file mode 100644
index 000000000..f58161232
--- /dev/null
+++ b/assets/set-fallback-material-for-usdz-exporter.html-BEiyoRdx.js
@@ -0,0 +1,45 @@
+import{_ as t,r as n,o as l,c as h,a as i,b as k,e}from"./app-CRZRGfEE.js";const p={};function r(d,s){const a=n("contribution-header");return l(),h("div",null,[s[0]||(s[0]=i("p",null,[i("a",{href:"/docs/community/contributions"},"Overview")],-1)),k(a,{url:"https://github.com/llllkatjallll",author:"llllkatjallll",page:"/docs/community/contributions/llllkatjallll",profileImage:"https://avatars.githubusercontent.com/u/38395689?s=100&u=7ce0fef973c4819c4f07823568d6f6061abfe410&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/184",title:"Set fallback material for USDZ exporter",gradient:"True"}),s[1]||(s[1]=e(`
If you want to set a fallback material for an object that will be exported as USDZ (for AR-mode on iOS), you can add this script to the object, which material should be replaced.
This is especially useful if you use custom shaders in your scene (they are visible on Desktop+WebXR, but not in AR on iOS).
`,3))])}const y=t(p,[["render",r],["__file","set-fallback-material-for-usdz-exporter.html.vue"]]),g=JSON.parse('{"path":"/community/contributions/llllkatjallll/set-fallback-material-for-usdz-exporter","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/llllkatjallll: set fallback material for usdz exporter.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{y as comp,g as data};
diff --git a/assets/showcase-bike.html-C8g9IaUw.js b/assets/showcase-bike.html-C8g9IaUw.js
new file mode 100644
index 000000000..233dfe0cd
--- /dev/null
+++ b/assets/showcase-bike.html-C8g9IaUw.js
@@ -0,0 +1 @@
+import{_ as n,r as s,o,c as a,a as e,b as l}from"./app-CRZRGfEE.js";const r={};function c(d,t){const i=s("sample");return o(),a("div",null,[t[0]||(t[0]=e("h3",{id:"live",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#live"},[e("span",null,"Live")])],-1)),l(i,{src:"https://bike.needle.tools"}),t[1]||(t[1]=e("p",null,[e("a",{href:"https://bike.needle.tools",target:"_blank",rel:"noopener noreferrer"},"Visit website")],-1))])}const m=n(r,[["render",c],["__file","showcase-bike.html.vue"]]),h=JSON.parse('{"path":"/showcase-bike.html","title":"Bike Configurator ๐ฒ","lang":"en-US","frontmatter":{"lang":"en-US","title":"Bike Configurator ๐ฒ","sidebar":false,"editLink":false,"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/bike configurator.png"}],["meta",{"name":"og:description","content":"---\\nVisit website"}]],"description":"---\\nVisit website"},"headers":[{"level":3,"title":"Live","slug":"live","link":"#live","children":[]}],"git":{"updatedTime":1669763662000},"filePathRelative":"showcase-bike.md"}');export{m as comp,h as data};
diff --git a/assets/showcase-castle.html-2qaErZDl.js b/assets/showcase-castle.html-2qaErZDl.js
new file mode 100644
index 000000000..315fd5364
--- /dev/null
+++ b/assets/showcase-castle.html-2qaErZDl.js
@@ -0,0 +1 @@
+import{_ as a,r as n,o as r,c as i,a as t,b as s,e as l}from"./app-CRZRGfEE.js";const c={};function d(u,e){const o=n("sample");return r(),i("div",null,[e[0]||(e[0]=t("h3",{id:"live",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#live"},[t("span",null,"Live")])],-1)),s(o,{src:"https://castle.needle.tools"}),e[1]||(e[1]=l('
Build your own castle! Drag 3D models from the various palettes onto the stage, and create your very own world. Works on Desktop, Mobile, VR, AR, all right in your browser. Interactions are currently optimized for VR; placement on screens is a bit harder but possible. Have fun, no matter which device you're on!
Invite your friends! Click Create Room to be put into a live, multi-user space โ just copy the URL, send it to a friend and they join you automatically. There's currently a max limit for "users online at the same time" - if you don't get into a room, please try later.
This page was authored in Unity and exported to three.js using tools and technologies by ๐ต needle.
There are a lot of open technologies involved: 3D models are in glTF format, the render engine is three.js, VR and AR are using WebXR. The networking server runs on Glitch, and audio is sent over WebRTC using PeerJS.
',7))])}const p=a(c,[["render",d],["__file","showcase-castle.html.vue"]]),m=JSON.parse(`{"path":"/showcase-castle.html","title":"Castle Builder ๐ฐ","lang":"en-US","frontmatter":{"lang":"en-US","title":"Castle Builder ๐ฐ","sidebar":false,"editLink":false,"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/castle builder.png"}],["meta",{"name":"og:description","content":"---\\nVisit website\\nBuild your own castle! Drag 3D models from the various palettes onto the stage, and create your very own world.\\nWorks on Desktop, Mobile, VR, AR, all right in your browser. Interactions are currently optimized for VR; placement on screens is a bit harder but possible. Have fun, no matter which device you're on!\\nInvite your friends! Click Create Room to be put into a live, multi-user space โ just copy the URL, send it to a friend and they join you automatically.\\nThere's currently a max limit for 'users online at the same time'"}]],"description":"---\\nVisit website\\nBuild your own castle! Drag 3D models from the various palettes onto the stage, and create your very own world.\\nWorks on Desktop, Mobile, VR, AR, all right in your browser. Interactions are currently optimized for VR; placement on screens is a bit harder but possible. Have fun, no matter which device you're on!\\nInvite your friends! Click Create Room to be put into a live, multi-user space โ just copy the URL, send it to a friend and they join you automatically.\\nThere's currently a max limit for 'users online at the same time'"},"headers":[{"level":3,"title":"Live","slug":"live","link":"#live","children":[]},{"level":3,"title":"How To Play","slug":"how-to-play","link":"#how-to-play","children":[]},{"level":3,"title":"Info","slug":"info","link":"#info","children":[]}],"git":{"updatedTime":1669763662000},"filePathRelative":"showcase-castle.md"}`);export{p as comp,m as data};
diff --git a/assets/showcase-mercedes-benz.html-CYMbPrd8.js b/assets/showcase-mercedes-benz.html-CYMbPrd8.js
new file mode 100644
index 000000000..d6dbcc4ed
--- /dev/null
+++ b/assets/showcase-mercedes-benz.html-CYMbPrd8.js
@@ -0,0 +1,29 @@
+import{_ as t,r as n,o as h,c as l,a as s,d as e,b as p,e as r}from"./app-CRZRGfEE.js";const o="/docs/showcase-mercedes/1_skybox.png",d="/docs/showcase-mercedes/2_paintjob_simple.jpg",k="/docs/showcase-mercedes/3_SpecularHighlights_off.jpg",c="/docs/showcase-mercedes/4_SpecularHighlights_on.jpg",g="/docs/showcase-mercedes/5_NoBackground.jpg",y="/docs/showcase-mercedes/6_MapBackground.png",m="/docs/showcase-mercedes/7_EnvShaderGraph.jpg",u="/docs/showcase-mercedes/8_Gradiant.png",w="/docs/showcase-mercedes/9_Rotator.png",C="/docs/showcase-mercedes/10_WheelsAndGrid.png",b="/docs/showcase-mercedes/11_GridShader.jpg",f="/docs/showcase-mercedes/12_WheelWithText.png",F="/docs/showcase-mercedes/13_WheelShader.jpg",E="/docs/showcase-mercedes/14_RearUI.jpg",B={};function v(x,i){const a=n("sample");return h(),l("div",null,[i[0]||(i[0]=s("h2",{id:"about",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#about"},[s("span",null,"About")])],-1)),i[1]||(i[1]=s("p",null,[e("Hello, my name is Kryลกtof and i did a research project about Needle. At "),s("a",{href:"https://www.ishowroom.cz/home/",target:"_blank",rel:"noopener noreferrer"},"our company"),e(", we wanted to determine how Needle can help us in our workflow. We have one local client which focuses on reselling luxury cars. We already delivered a mobile app and VR experience using Unity. We have around 30 unique cars ready in the engine. We plan to expand the client's website with visually pleasing digital clones with more configuration options. Needle could achieve a perfect 1:1 conversion between unity and web visuals. It would be a massive benefit to our workflow. So that's what sparked our research.")],-1)),p(a,{src:"https://engine.needle.tools/demos/mercedes-benz-demo/"}),i[2]||(i[2]=r('
I'm not very well experienced with javascript, typescript or three.js, so my point of view is as a semi-experienced Unity developer trying out the simplest way how to create a web experience. For those who would suggest Unity WebGL, that sadly doesn't work and isn't flexible on mobile browsers. Needle is ๐
Our lighting model is based on reflection probes in unity. We do not need any directional or point lights, only ambient lighting.
We're using this skybox:
Which looks like this on the paint job:
Then to add a slight detail, i've added 2 directional lights with an insignificant intensity (0.04) to create specular highlights. So before it looked like this:
But with the added directional lights it added a better dynamic. The effect could be deepened with higher intensity:
The black background isn't very pretty. So to differentiate between visual and lighting skyboxes i've added an inverse sphere which wraps the whole map.
Regarding the gradient goes from a slight gray to a white color..
This effect could be easily made with just a proper UV mapping and a single pixel high texture which would define the gradient.
I've made an unlit shader in the shader graph:
I've noticed a color banding issue, so i've tried to implement dithering. Frankly, it didn't help the artefacts but i bet there's a simple solution to that issue. So the upper part of the shader does sample the gradient based on the Y axis in object space. And the lower part tries to negate the color banding.
By using shaders it's simpler to use and iterate the gradiant. By using Needle's Shadergraph markdown asset, it's even simpler! ๐ต
The user now sees a car driving in deep nothingness, the color doesn't resemble anything and the experience is dull. We want to ground the model and that's done by adding a grid and then shifting it so it seems the car is moving. This is what we want to achieve:
The shader for the grid was comprised of two parts. A simple tiled texture of the grid that's being multipled by a circular gradient to make the edges fade off.
This tech demo takes it's goal to showcase the car's capabilities.
Let's start by highlighting the wheels.
Adding this shader to a plane will result in a dashed circle which is rotating by a defined speed. Combined with world space UI with a normal Text component this can highlight some interesting capabilities or parameters of the given product.
After showcasing the wheels we want to finish with a broad information about the product. In this case, that would be the car's full name and perhaps some available configurations.
Needle Engine seems to be a very good candidate for us!
There are a few features which we miss.
That would be for example proper support for the Lit Shader Graphs. But nothing stops us to create shaders the three.js way and create simmilar shaders in Unity for our content team to tweak the materials.
Using Needle was a blast! ๐ต
',48))])}const A=t(B,[["render",v],["__file","showcase-mercedes-benz.html.vue"]]),D=JSON.parse(`{"path":"/showcase-mercedes-benz.html","title":"Mercedes-Benz Showcase","lang":"en-US","frontmatter":{"lang":"en-US","title":"Mercedes-Benz Showcase","editLink":false,"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/mercedes benz showcase.png"}],["meta",{"name":"og:description","content":"---\\nHello, my name is Kryลกtof and i did a research project about Needle. At our company, we wanted to determine how Needle can help us in our workflow. We have one local client which focuses on reselling luxury cars. We already delivered a mobile app and VR experience using Unity. We have around 30 unique cars ready in the engine. We plan to expand the client's website with visually pleasing digital clones with more configuration options. Needle could achieve a perfect 1:1 conversion between unity and web visuals. It would be a massive benefit to our workflow. So that's what sparked our research.\\nI'm not very well experienced with javascript, typescript or three.js, so my point of view is as a semi-experienced Unity developer trying out the simplest way how to create a web experience. For those who would suggest Unity WebGL, that sadly doesn't work and isn't flexible on mobile browsers. Needle is ๐\\nOur lighting model is based on reflection probes in unity. We do not need any directional or point lights, only ambient lighting.\\nWe're using this skybox:\\nWhich looks like this on the paint job:\\nThen to add a slight detail, i've added 2 directional lights with an insignificant intensity (0.04) to create specular highlights. So before it looked like this:\\nBut with the added directional lights it added a better dynamic. The effect could be deepened with higher intensity:\\nThe scene now looks like this:\\nThe black background isn't very pretty. So to differentiate b"}]],"description":"---\\nHello, my name is Kryลกtof and i did a research project about Needle. At our company, we wanted to determine how Needle can help us in our workflow. We have one local client which focuses on reselling luxury cars. We already delivered a mobile app and VR experience using Unity. We have around 30 unique cars ready in the engine. We plan to expand the client's website with visually pleasing digital clones with more configuration options. Needle could achieve a perfect 1:1 conversion between unity and web visuals. It would be a massive benefit to our workflow. So that's what sparked our research.\\nI'm not very well experienced with javascript, typescript or three.js, so my point of view is as a semi-experienced Unity developer trying out the simplest way how to create a web experience. For those who would suggest Unity WebGL, that sadly doesn't work and isn't flexible on mobile browsers. Needle is ๐\\nOur lighting model is based on reflection probes in unity. We do not need any directional or point lights, only ambient lighting.\\nWe're using this skybox:\\nWhich looks like this on the paint job:\\nThen to add a slight detail, i've added 2 directional lights with an insignificant intensity (0.04) to create specular highlights. So before it looked like this:\\nBut with the added directional lights it added a better dynamic. The effect could be deepened with higher intensity:\\nThe scene now looks like this:\\nThe black background isn't very pretty. So to differentiate b"},"headers":[{"level":2,"title":"About","slug":"about","link":"#about","children":[]},{"level":2,"title":"Context","slug":"context","link":"#context","children":[]},{"level":2,"title":"Lighting","slug":"lighting","link":"#lighting","children":[]},{"level":2,"title":"Background","slug":"background","link":"#background","children":[]},{"level":2,"title":"Car fake movement","slug":"car-fake-movement","link":"#car-fake-movement","children":[]},{"level":2,"title":"Extra elements","slug":"extra-elements","link":"#extra-elements","children":[]},{"level":2,"title":"Wrap up","slug":"wrap-up","link":"#wrap-up","children":[]},{"level":2,"title":"Conclusion","slug":"conclusion","link":"#conclusion","children":[]}],"git":{"updatedTime":1726585195000},"filePathRelative":"showcase-mercedes-benz.md"}`);export{A as comp,D as data};
diff --git a/assets/showcase-monsterhands.html-Cjm5xxr6.js b/assets/showcase-monsterhands.html-Cjm5xxr6.js
new file mode 100644
index 000000000..50575c601
--- /dev/null
+++ b/assets/showcase-monsterhands.html-Cjm5xxr6.js
@@ -0,0 +1 @@
+import{_ as n,r as a,o,c as r,a as e,b as l}from"./app-CRZRGfEE.js";const i={};function d(m,t){const s=a("sample");return o(),r("div",null,[t[0]||(t[0]=e("h3",{id:"live",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#live"},[e("span",null,"Live")])],-1)),l(s,{src:"https://monster-hands.needle.tools/"}),t[1]||(t[1]=e("p",null,[e("a",{href:"https://monster-hands.needle.tools/",target:"_blank",rel:"noopener noreferrer"},"Visit website")],-1))])}const h=n(i,[["render",d],["__file","showcase-monsterhands.html.vue"]]),p=JSON.parse('{"path":"/showcase-monsterhands.html","title":"Monster Hands ๐","lang":"en-US","frontmatter":{"lang":"en-US","title":"Monster Hands ๐","sidebar":false,"editLink":false,"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/monster hands.png"}],["meta",{"name":"og:description","content":"---\\nVisit website"}]],"description":"---\\nVisit website"},"headers":[{"level":3,"title":"Live","slug":"live","link":"#live","children":[]}],"git":{"updatedTime":1682497626000},"filePathRelative":"showcase-monsterhands.md"}');export{h as comp,p as data};
diff --git a/assets/showcase-towerdefence.html-B-3p76-5.js b/assets/showcase-towerdefence.html-B-3p76-5.js
new file mode 100644
index 000000000..33bdfecae
--- /dev/null
+++ b/assets/showcase-towerdefence.html-B-3p76-5.js
@@ -0,0 +1 @@
+import{_ as s,r as o,o as a,c as i,a as e,b as l}from"./app-CRZRGfEE.js";const r={};function d(c,t){const n=o("sample");return a(),i("div",null,[t[0]||(t[0]=e("h3",{id:"live",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#live"},[e("span",null,"Live")])],-1)),l(n,{src:"https://v6p9d9t4.ssl.hwcdn.net/html/7746989/index.html"}),t[1]||(t[1]=e("p",null,[e("a",{href:"https://willitaugment.itch.io/tumbleweed-defender",target:"_blank",rel:"noopener noreferrer"},"Visit website")],-1))])}const p=s(r,[["render",d],["__file","showcase-towerdefence.html.vue"]]),f=JSON.parse('{"path":"/showcase-towerdefence.html","title":"Tower Defense","lang":"en-US","frontmatter":{"lang":"en-US","title":"Tower Defense","sidebar":false,"editLink":false,"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/tower defense.png"}],["meta",{"name":"og:description","content":"---\\nVisit website"}]],"description":"---\\nVisit website"},"headers":[{"level":3,"title":"Live","slug":"live","link":"#live","children":[]}],"git":{"updatedTime":1684592760000},"filePathRelative":"showcase-towerdefence.md"}');export{p as comp,f as data};
diff --git a/assets/showcase-website.html-DsWtTfPo.js b/assets/showcase-website.html-DsWtTfPo.js
new file mode 100644
index 000000000..1e69d4afd
--- /dev/null
+++ b/assets/showcase-website.html-DsWtTfPo.js
@@ -0,0 +1 @@
+import{_ as n,r as l,o as a,c as i,a as e,b as o}from"./app-CRZRGfEE.js";const r={};function d(c,t){const s=l("sample");return a(),i("div",null,[t[0]||(t[0]=e("h3",{id:"live",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#live"},[e("span",null,"Live")])],-1)),o(s,{src:"https://needle.tools"}),t[1]||(t[1]=e("p",null,[e("a",{href:"https://needle.tools",target:"_blank",rel:"noopener noreferrer"},"Visit website")],-1))])}const m=n(r,[["render",d],["__file","showcase-website.html.vue"]]),h=JSON.parse('{"path":"/showcase-website.html","title":"Castle Builder ๐ฐ","lang":"en-US","frontmatter":{"lang":"en-US","title":"Castle Builder ๐ฐ","sidebar":false,"editLink":false,"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/castle builder.png"}],["meta",{"name":"og:description","content":"---\\nVisit website"}]],"description":"---\\nVisit website"},"headers":[{"level":3,"title":"Live","slug":"live","link":"#live","children":[]}],"git":{"updatedTime":1669763662000},"filePathRelative":"showcase-website.md"}');export{m as comp,h as data};
diff --git a/assets/showcase-zenrepublic.html-C8fRcD0r.js b/assets/showcase-zenrepublic.html-C8fRcD0r.js
new file mode 100644
index 000000000..61d1d5832
--- /dev/null
+++ b/assets/showcase-zenrepublic.html-C8fRcD0r.js
@@ -0,0 +1 @@
+import{_ as s,r as a,o as i,c as l,a as e,b as r}from"./app-CRZRGfEE.js";const o={};function c(p,t){const n=a("sample");return i(),l("div",null,[t[0]||(t[0]=e("h3",{id:"live",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#live"},[e("span",null,"Live")])],-1)),r(n,{src:"https://zenrepublic.space/?realm=3"}),t[1]||(t[1]=e("p",null,[e("a",{href:"https://zenrepublic.space/?realm=3",target:"_blank",rel:"noopener noreferrer"},"Visit website")],-1))])}const m=s(o,[["render",c],["__file","showcase-zenrepublic.html.vue"]]),h=JSON.parse('{"path":"/showcase-zenrepublic.html","title":"Monster Hands ๐","lang":"en-US","frontmatter":{"lang":"en-US","title":"Monster Hands ๐","sidebar":false,"editLink":false,"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/monster hands.png"}],["meta",{"name":"og:description","content":"---\\nVisit website"}]],"description":"---\\nVisit website"},"headers":[{"level":3,"title":"Live","slug":"live","link":"#live","children":[]}],"git":{"updatedTime":1682497626000},"filePathRelative":"showcase-zenrepublic.md"}');export{m as comp,h as data};
diff --git a/assets/squeeze-to-scale-object-or-world-in-vr.html-C2ATGHYY.js b/assets/squeeze-to-scale-object-or-world-in-vr.html-C2ATGHYY.js
new file mode 100644
index 000000000..1232c19fa
--- /dev/null
+++ b/assets/squeeze-to-scale-object-or-world-in-vr.html-C2ATGHYY.js
@@ -0,0 +1,249 @@
+import{_ as n,r as h,o as k,c as l,a as i,b as t,e as p}from"./app-CRZRGfEE.js";const e={};function r(C,s){const a=h("contribution-header");return k(),l("div",null,[s[0]||(s[0]=i("p",null,[i("a",{href:"/docs/community/contributions"},"Overview")],-1)),t(a,{url:"https://github.com/Web3Kev",author:"Web3Kev",page:"/docs/community/contributions/web3kev",profileImage:"https://avatars.githubusercontent.com/u/106066970?s=100&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/159",title:"Squeeze to Scale (Object or World) in VR",gradient:"True"}),s[1]||(s[1]=p(`
The following code enables you to use both controllers in VR (tested on Quest) and scale the player's perspective (XRRig) by squeezing the grab triggers and moving the controllers closer (pinch out) or further apart (pinch in). The boolean allowWorldScaling has to be ticked in unity for that to work.
Upon selecting a draggable object (Drag controls script), the player can scale up or down that object, while keeping the finger on the trigger and squeezing both grab buttons and moving the hands closer or apart.
The current script enables you to visually see the scale. Create a world canvas with a text component as a child. Assign the world canvas to scaleTextObject and the text to scaleText. scaleTextObject will then spawn in front of the player and follow the head movement whenever scaling.
At the moment the position of the hands (controllers) is done by finding the avatar's hands. I couldn't make it work otherwise. If you find a better way please share.
import { Behaviour, WebXR,serializeable, WebXREvent,WebXRAvatar,GameObject, AvatarMarker,Text} from "@needle-tools/engine";
+import { Object3D, Vector3,Quaternion,PerspectiveCamera} from "three";
+
+export class SqueezeScale extends Behaviour {
+
+
+ private webXR?: WebXR;
+
+ private selectedObj: Object3D| null = null;
+
+ @serializeable(Object3D)
+ scaleTextObject: Object3D| null = null;
+
+ @serializeable(Text)
+ scaleText?: Text;
+
+ public allowWorldScaling: boolean =false;
+
+ private leftSqueeze:boolean=false;
+ private rightSqueeze:boolean=false;
+
+ private bothSqueezeStarted=false;
+
+ private rigScaleUpdated=false;
+
+ private initialDistance:number=1;
+ private initialScale:number=1;
+ private newScale:number | null=null;
+
+ private leftHand?:Object3D;
+ private rightHand?:Object3D;
+ private head?:Object3D;
+
+ start(): void {
+
+ let _webxr=GameObject.findObjectOfType(WebXR);
+ if(_webxr)
+ {
+ this.webXR=_webxr;
+ console.log("webxr found");
+ }
+
+ //Wait for XR Session
+ WebXR.addEventListener(WebXREvent.XRStarted, () => {
+ //listen to squeeze events
+ this.context.xrSession?.addEventListener("squeezestart", (event) => { this.onSqueezeEvent(event,true); });
+ this.context.xrSession?.addEventListener("squeezeend", (event) => { this.onSqueezeEvent(event,false); });
+ });
+ }
+
+
+ onSqueezeEvent(event: XRInputSourceEvent, status:boolean) {
+
+ if(event.inputSource.handedness==="right")
+ {
+ this.rightSqueeze=status;
+ }
+
+ if(event.inputSource.handedness==="left")
+ {
+ this.leftSqueeze=status;
+ }
+ }
+
+ update(){
+
+ if(this.context.isInVR)
+ {
+ //cache object selected if any
+ this.objectGrab();
+
+ //if both grips are squeezed
+ if(this.leftSqueeze && this.rightSqueeze)
+ {
+ //if object is selected either in the left or right controller (only one)
+ if(this.selectedObj!=null)
+ {
+ //after initial distance value has been set
+ if(this.bothSqueezeStarted)
+ {
+ //get current distance between controllers
+ const scaleValue=this.calculateDistance();
+
+ //get distance change since beginning of squeeze to get a "pinch in/out" effect
+ this.newScale=this.initialScale+scaleValue-this.initialDistance;
+
+ //avoid 0 and negative scales
+ if(this.newScale<0.001){this.newScale=0.001;}
+
+ // scale object according to new distance since initial distance
+ this.selectedObj.scale.x=this.newScale;
+ this.selectedObj.scale.y=this.newScale;
+ this.selectedObj.scale.z=this.newScale;
+
+ this.showVisual(this.newScale,"Object :");
+ }
+ else
+ {
+ //get initial distance value (only once at a new squeeze both hands event)
+ this.bothSqueezeStarted=true;
+
+ this.initialDistance=this.calculateDistance();
+
+ //cache object's initial scale
+ this.initialScale=this.selectedObj.scale.x;
+ }
+ }
+ else
+ {
+ //scale world ?
+ if(this.webXR?.Rig && this.allowWorldScaling)
+ {
+ //after initial distance value has been set
+ if(this.bothSqueezeStarted)
+ {
+ //get current distance between controllers
+ const scaleValue=this.calculateDistance();
+
+ //get distance change since beginning of squeeze to get a "pinch in/out" effect
+ this.newScale=this.initialScale+scaleValue-this.initialDistance;
+
+ //avoid 0 and negative scales
+ if(this.newScale<0.001){this.newScale=0.001;}
+
+ this.showVisual(this.newScale, "World :");
+
+ this.rigScaleUpdated=true;
+ }
+ else
+ {
+ //get initial distance value (only once at a new squeeze both hands event)
+ this.bothSqueezeStarted=true;
+
+ this.initialDistance=this.calculateDistance();
+
+ //cache object's initial scale
+ this.initialScale=this.webXR.Rig.scale.x;
+ }
+ }
+ }
+
+ }
+ else
+ {
+ //reset values
+ this.bothSqueezeStarted=false;
+
+ //if world has been scaled, scale rig accordingly at the end of squeezing and once only
+ if(this.webXR?.Rig && this.rigScaleUpdated && this.newScale)
+ {
+ //change rig scale
+ this.webXR.Rig.scale.set(this.newScale, this.newScale, this.newScale);
+ this.webXR.Rig.updateMatrixWorld();
+
+ const cam = this.context.mainCamera as PerspectiveCamera;
+ cam.near=this.newScale>2?0.0001:0.2;
+ cam.updateProjectionMatrix();
+
+ //reset
+ this.rigScaleUpdated=false;
+ }
+
+ if(this.scaleTextObject)
+ {
+ this.scaleTextObject.visible=false;
+ }
+
+ this.newScale=null;
+ }
+ }
+ }
+
+ private calculateDistance():number
+ {
+ let distance=1;
+
+ if(this.leftHand && this.rightHand)
+ {
+
+ const left=this.leftHand.position;
+ const right=this.rightHand.position;
+
+ // Calculate the difference between the positions
+ const dx = left.x - right.x;
+ const dy = left.y - right.y;
+ const dz = left.z - right.z;
+
+ // Calculate the distance using the Euclidean distance formula
+ distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
+ }
+ else
+ {
+ //set positions of controllers from your avatar (only once)
+ let allAvatars = AvatarMarker.instances;
+ if(allAvatars.length>0)
+ {
+ for (let i=0;i<allAvatars.length;i++)
+ {
+ if(allAvatars[i].isLocalAvatar())
+ {
+ const av=allAvatars[i].avatar as WebXRAvatar;
+ if(av!=null)
+ {
+ this.leftHand=av.handLeft as Object3D;
+ this.rightHand=av.handRight as Object3D;
+ this.head = av.head as Object3D;
+ }
+ }
+ }
+ }
+ }
+
+ return distance;
+ }
+
+ showVisual(scale:number, mesg:string):void
+ {
+ if(this.scaleTextObject && this.head && this.scaleText)
+ {
+ this.scaleTextObject.visible=true;
+
+ const offset = new Vector3(0,0,7);
+ offset.applyQuaternion(this.head.quaternion);
+
+ this.scaleTextObject.position.copy(this.head.position.add(offset));
+
+ const roundedNum= +scale.toFixed(3);
+ this.scaleText.text=mesg+" "+roundedNum;
+ }
+
+ }
+
+
+ objectGrab():void
+ {
+ if(this.webXR?.RightController?.grabbed?.selected)
+ {
+ this.selectedObj = this.webXR.RightController.grabbed.selected;
+ }
+ else if(this.webXR?.LeftController?.grabbed?.selected)
+ {
+ this.selectedObj = this.webXR.LeftController.grabbed.selected;
+ }
+ else
+ {
+ this.selectedObj=null;
+ }
+ }
+}
Ask questions and provide feedback in Needle Forum
Get instant help with Needle AI
Ask any question about Needle Engine, web development or our editor integrations and get instant help from the Needle AI that has access to the latest code, documentation and our integrations.
',8)),o(n,{src:"https://www.youtube.com/watch?v=gZuC40Alr88"}),o(n,{src:"https://www.youtube.com/watch?v=F6_buCHZhWk"}),e[1]||(e[1]=t("div",{class:"hint-container info"},[t("p",{class:"hint-container-title"},"You made a cool project?"),t("p",null,[a("Don't hestitate to "),t("a",{href:"mailto:hi@needle.tools",target:"_blank",rel:"noopener noreferrer"},"reach out"),a(" โ we always love seeing what you built ๐")])],-1))])}const m=r(p,[["render",h],["__file","support.html.vue"]]),b=JSON.parse('{"path":"/support.html","title":"Support and Community","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/support.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[{"level":2,"title":"Forum","slug":"forum","link":"#forum","children":[]},{"level":2,"title":"Discord","slug":"discord","link":"#discord","children":[]},{"level":2,"title":"Get Inspired","slug":"get-inspired","link":"#get-inspired","children":[]}],"git":{"updatedTime":1727681911000},"filePathRelative":"support.md"}');export{m as comp,b as data};
diff --git a/assets/technical-overview.html-BeSwd6Mh.js b/assets/technical-overview.html-BeSwd6Mh.js
new file mode 100644
index 000000000..b58ecdca9
--- /dev/null
+++ b/assets/technical-overview.html-BeSwd6Mh.js
@@ -0,0 +1,254 @@
+import{_ as t,r as h,o as e,c as l,e as a,a as k,d as i,b as p,w as r}from"./app-CRZRGfEE.js";const d={};function C(B,s){const n=h("RouteLink");return e(),l("div",null,[s[3]||(s[3]=a(`
a number of components and tools that allow you to set up scenes for Needle Engine from e.g. the Unity Editor.
an exporter that turns scene and component data into glTF.
a web runtime that loads and runs the produced glTF files and their extensions.
The web runtime uses three.js for rendering, adds a component system on top of the three scene graph and hooks up extension loaders for our custom glTF extensions.
Effectively, this turns tools like Unity or Blender into spatial web development powerhouses โ adding glTF assets to the typical HTML, CSS, JavaScript and bundling workflow.
Models, textures, animations, lights, cameras and more are stored as glTF 2.0 files in Needle Engine. Custom data is stored in vendor extensions. These cover everything from interactive components to physics, sequencing and lightmaps.
More extensions and custom extensions can be added using the export callbacks of UnityGLTF (not documented yet) and the glTF import extensions of three.js.
Note: Materials using these extensions can be exported from Unity via UnityGLTF's PBRGraph material.
Note: Audio and variants are already supported in Needle Engine through NEEDLE_components and NEEDLE_persistent_assets, but there are some options for more alignment to existing proposals such as KHR_audio and KHR_materials_variants.
For production, we compress glTF assets with glTF-transform. Textures use either etc1s, uastc, webp or no compression, depending on texture type. Meshes use draco by default but can be configured to use meshtopt (per glTF file). Custom extensions are passed through in an opaque way.
`,21)),k("p",null,[s[1]||(s[1]=i("See the ")),p(n,{to:"/deployment.html#optimization-and-compression-options"},{default:r(()=>s[0]||(s[0]=[i("deployment & compression")])),_:1}),s[2]||(s[2]=i(" page for more information"))]),s[4]||(s[4]=a(`
Needle Engine stores custom data in glTF files through our vendor extensions. These extensions are designed to be flexible and allow relatively arbitrary data to put into them. Notably, no code is stored in these files. Interactive components is restored from the data at runtime. This has some similarities to how AssetBundles function in Unity โ the receiving side of an asset needs to have matching code for components stored in the file.
We're currently not prodiving schemas for these extensions as they are still in development. The JSON snippets below demonstrates extension usage by example and includes notes on architectural choices and what we may change in future releases.
References between pieces of data are currently constructed through a mix of indices into other parts of the glTF file and JSON pointers. We may consolidate these approaches in a future release. We're also storing string-based GUIDs for cases where ordering is otherwise hard to resolve (e.g. two components referencing each other) that we may remove in the future.
This extension contains per-node component data. The component names map to type names on both the JavaScript and C# side. Multiple components with the same name can be added to the same node.
Data in NEEDLE_components can be animated via the currently not ratified KHR_animation_pointer extension.
Note: Storing only the component type name means that type names currently need to be unique per project. We're planning to include package names in a future release to loosen this constraint to unique component type names per package instead of globally.
Note: Currently there's no versioning information in the extension (which npm packaage does a component belong to, which version of that package was it exported against). We're planning to include versioning information in a future release.
Note: Currently all components are in the builtin_components array. We might rename this to just components in a future release.
This extension contains additional per-node data related to state, layers, and tags. Layers are used for both rendering and physics, similar to how three.js and Unity treat them.
Components in NEEDLE_components can reference data via JSON Pointers. The data in NEEDLE_persistent_assets is often referenced multiple times by different components and is thus stored separately in a root extension. By design, they are always referenced by something else (or have references within each other), and thus do not store type information at all: they're simply pieces of JSON data and components referencing them currently need to know what they expect.
Examples for assets/data stored in here are:
AnimatorControllers, their layers and states
PlayableAssets (timelines), their tracks and embedded clips
SignalAssets
...
Data in persistent_assets can reference other persistent_assets via JSON Pointer, but by design can't reference NEEDLE_components. This is similar to the separation beween "Scene data" and "AssetDatabase content" in Unity.
This extension builds upon the archived KHR_techniques_webgl extension and extends it in a few crucial places. While the original extension was specified against WebGL 1.0, we're using it with WebGL 2.0 here and have added a number of uniform types.
While Unity's compilation process from C# to IL to C++ (via IL2CPP) to WASM (via emscripten) is ingenious, it's also relatively slow. Building even a simple project to WASM takes many minutes, and that process is pretty much repeated on every code change. Some of it can be avoided through clever caching and ensuring that dev builds don't try to strip as much code, but it still stays slow.
We do have a prototype for some WASM translation, but it's far from complete and the iteration speed stays slow, so we are not actively investigating this path right now.
When looking into modern web workflows, we found that code reload times during development are neglectible, usually in sub-second ranges. This of course trades some performance (interpretation of JavaScript on the fly instead of compiler optimization at build time) for flexibility, but browsers got really good at getting the most out of JavaScript.
We believe that for iteration and tight testing workflows, it's beneficial to be able to test on device and on the target platform (the browser, in this case) as quickly and as often as possible - which is why we're skipping Unity's entire play mode, effectively always running in the browser.
Note: A really nice side effect is avoiding the entire slow "domain reload" step that usually costs 15-60 seconds each time you enter Play Mode. You're just "live" in the browser the moment you press Play.
`,49))])}const g=t(d,[["render",C],["__file","technical-overview.html.vue"]]),y=JSON.parse(`{"path":"/technical-overview.html","title":"Technical Overview","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/technical overview.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[{"level":2,"title":"How it works","slug":"how-it-works","link":"#how-it-works","children":[]},{"level":2,"title":"glTF Assets","slug":"gltf-assets","link":"#gltf-assets","children":[{"level":3,"title":"Supported glTF extensions","slug":"supported-gltf-extensions","link":"#supported-gltf-extensions","children":[]},{"level":3,"title":"Compression","slug":"compression","link":"#compression","children":[]}]},{"level":2,"title":"Vendor-specific glTF Extensions (NEEDLE_*)","slug":"vendor-specific-gltf-extensions-needle","link":"#vendor-specific-gltf-extensions-needle","children":[{"level":3,"title":"NEEDLE_components","slug":"needle-components","link":"#needle-components","children":[]},{"level":3,"title":"NEEDLE_gameobject_data","slug":"needle-gameobject-data","link":"#needle-gameobject-data","children":[]},{"level":3,"title":"NEEDLE_lighting_settings","slug":"needle-lighting-settings","link":"#needle-lighting-settings","children":[]},{"level":3,"title":"NEEDLE_lightmaps","slug":"needle-lightmaps","link":"#needle-lightmaps","children":[]},{"level":3,"title":"NEEDLE_persistent_assets","slug":"needle-persistent-assets","link":"#needle-persistent-assets","children":[]},{"level":3,"title":"NEEDLE_techniques_webgl","slug":"needle-techniques-webgl","link":"#needle-techniques-webgl","children":[]}]},{"level":2,"title":"TypeScript and Data Mapping","slug":"typescript-and-data-mapping","link":"#typescript-and-data-mapping","children":[]},{"level":2,"title":"Rendering with three.js","slug":"rendering-with-three.js","link":"#rendering-with-three.js","children":[]},{"level":2,"title":"Why aren't you compiling to WebAssembly?","slug":"why-aren-t-you-compiling-to-webassembly","link":"#why-aren-t-you-compiling-to-webassembly","children":[]}],"git":{"updatedTime":1725399379000},"filePathRelative":"technical-overview.md"}`);export{g as comp,y as data};
diff --git a/assets/testimonial-yd4jmUbJ.js b/assets/testimonial-yd4jmUbJ.js
new file mode 100644
index 000000000..de5c8924c
--- /dev/null
+++ b/assets/testimonial-yd4jmUbJ.js
@@ -0,0 +1 @@
+import{_ as o,o as n,c as r,a as e,f as i,d as c,t as _}from"./app-CRZRGfEE.js";const d={props:{name:String,src:String}},l={class:"root"},f={class:"testimonial"},m={class:"quote"},p={class:"name"},u=["href"];function h(a,t,s,g,v,S){return n(),r("div",l,[e("div",f,[e("span",m,[i(a.$slots,"default",{},void 0,!0)]),e("span",p,[t[0]||(t[0]=c(" โ ")),e("a",{href:s.src,target:"_blank"},_(s.name),9,u)])])])}const x=o(d,[["render",h],["__scopeId","data-v-47fef520"],["__file","testimonial.vue"]]);export{x as default};
diff --git a/assets/testimonials.html-B3wqsnkd.js b/assets/testimonials.html-B3wqsnkd.js
new file mode 100644
index 000000000..488f6c4cb
--- /dev/null
+++ b/assets/testimonials.html-B3wqsnkd.js
@@ -0,0 +1 @@
+import{_ as o,r,o as l,c as m,a,b as s,w as i,d as n}from"./app-CRZRGfEE.js";const d={};function u(h,t){const e=r("testimonial");return l(),m("div",null,[t[12]||(t[12]=a("h1",{id:"testimonials",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#testimonials"},[a("span",null,"Testimonials")])],-1)),t[13]||(t[13]=a("p",null,null,-1)),s(e,{name:"Rinesh Thomas",src:"https://twitter.com/rineshthomas/status/1566342798063947777?t=z6sG3Z7mol-NfIRfTTKqCQ&s=19"},{default:i(()=>t[0]||(t[0]=[n(" This is the best thing I have seen after cinemachine in unity. Unity should acquire this ")])),_:1}),s(e,{name:"Chris Mahoney",src:"https://twitter.com/mahoneymatic/status/1562981022932684800?t=qNqojoZkk2CZrJa7dGzqng&s=19"},{default:i(()=>t[1]||(t[1]=[n(" Unbelievable Unity editor integration by an order of magnitude, and as straightforward as the docs claim. Wow. ")])),_:1}),s(e,{name:"Kevin Curry",src:"https://twitter.com/kmcurry/status/1574333302022062080"},{default:i(()=>t[2]||(t[2]=[n(" needle.tools is a wonderful showcase of what @NeedleTools contributes to 3D via the web. I just love it. ")])),_:1}),s(e,{name:"Stella Cannefax",src:"https://twitter.com/0xstella/status/1574853012585172993"},{default:i(()=>t[3]||(t[3]=[n(" Thanks to @NeedleTools, seeing quite a bit of this solution for web-based real time 3d tools - export scenes from Unity, where you can leverage the extensive 3d editor ecosystem & content, and then render them in your own web-based engine ")])),_:1}),s(e,{name:"Brit Gardner",src:"https://twitter.com/britg/status/1562443905580163072"},{default:i(()=>t[4]||(t[4]=[n(" Played with this a bit this morning ๐คฏ๐คฏ pretty magical ")])),_:1}),s(e,{name:"Marc Wakefield",src:"https://twitter.com/mrm_design/status/1567391880169545729"},{default:i(()=>t[5]||(t[5]=[n(" This is huge for WebXR and shared, immersive 3D experiences! Thank you so much for putting in the work on this @NeedleTools crew! Hoping @Apple sort out their WebXR situation sooner rather than later. The AR part worked flawlessly on my @SamsungMobile S21. ")])),_:1}),s(e,{name:"Pete Patterson",src:"https://twitter.com/VRSpatialist/status/1572300394285383680"},{default:i(()=>t[6]||(t[6]=[n(" Finally checking out @NeedleTools with Unity. Super easy to get something up and running in the cloud using their glitch integration ")])),_:1}),s(e,{name:"Dilmer Valecillos",src:"https://twitter.com/Dilmerv/status/1562209049856188420"},{default:i(()=>t[7]||(t[7]=[n(" This is amazing and if you are curious about #WebXR with Unity this will help us get there ")])),_:1}),s(e,{name:"VRSpatialist",src:"https://discord.com/channels/717429793926283276/722046635525537842/1030201907513405530"},{default:i(()=>t[8]||(t[8]=[n(" I am a long time Unity dev and recently started playing with Needle Tools and I love it! It's a great on ramp for Unity devs who want to learn WebXR and three.js. The runtime engine is awesome and it was pretty easy to create my own custom component ")])),_:1}),s(e,{name:"Unity for Digital Twins",src:"https://twitter.com/DigitalTwin/status/1576934958681055233"},{default:i(()=>t[9]||(t[9]=[n(" We just gotta say WOW ๐คฉ ")])),_:1}),s(e,{name:"Matthew Pieri",src:"https://discord.com/channels/717429793926283276/1097572505738301571/1097572505738301571"},{default:i(()=>t[10]||(t[10]=[n(" Spent the last 2.5 months building this game, never built a game/never used unity before, but absolutely loving the whole process with needle tools. So rapid! Would love to make a career building AR experiences! ")])),_:1}),s(e,{name:"Yuzu",src:"https://discord.com/channels/717429793926283276/1264966222467043433/1265268833485066240"},{default:i(()=>t[11]||(t[11]=[n(" My workflow has been optimized 10X ever since i started using needle ")])),_:1})])}const g=o(d,[["render",u],["__file","testimonials.html.vue"]]),f=JSON.parse('{"path":"/testimonials.html","title":"Testimonials","lang":"en-US","frontmatter":{"next":"getting-started/","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/testimonials.png"}],["meta",{"name":"og:description","content":"---\\nThis is the best thing I have seen after cinemachine in unity. Unity should acquire this"}]],"description":"---\\nThis is the best thing I have seen after cinemachine in unity. Unity should acquire this"},"headers":[],"git":{"updatedTime":1727125836000},"filePathRelative":"testimonials.md"}');export{g as comp,f as data};
diff --git a/assets/testing.html-DcypmV_S.js b/assets/testing.html-DcypmV_S.js
new file mode 100644
index 000000000..b939ec727
--- /dev/null
+++ b/assets/testing.html-DcypmV_S.js
@@ -0,0 +1 @@
+import{_ as t,o as i,c as n,e as o}from"./app-CRZRGfEE.js";const a="/docs/testing/switch-to-mkcert.webp",r={};function s(l,e){return i(),n("div",null,e[0]||(e[0]=[o('
When using our templates, Needle Engine runs a local development server for you. Simply press play, and a website will open in your default browser, ready for testing on your local device. For testing on other devices in the same network, you may have to install a self-signed certificate (see below).
When using a custom setup / framework, please refer to your framework's documentation on how to run a secure local development server.
Tips
Our local server uses the IP address in your local network (e.g. https://192.168.0.123:3000) instead of localhost:3000. This allows you to quickly view and test your project from mobile devices, VR glasses, and other machines in the same network.
We're using HTTPS instead of the older HTTP, because modern powerful web APIs like WebXR require a secure connection โ the S stands for "secure".
Different operating systems have different security requirements for local development. Typically, displaying a website will work even with a auto-generated untrusted certificate, but browsers may warn about the missing trust and some features are not available. Here's a summary:
Tips
Installing trusted self-signed certificates on your development devices is recommended for a smooth development experience. Find the steps at the bottom of this page.
Default โ with auto-generated untrusted certificate Displays a certificate warning upon opening the project in a browser.Uses the vite-plugin-basic-ssl npm package.
We're using websocket connections to communicate between the browser and the local development server. Websockets require a secure connection (HTTPS) in order to work. Depending on the platform, you might need to set up a custom certificate for local development. Android and Windows don't need a custom certificate, but iOS and MacOS do.
OS
Viewing in the browser (with a security warning)
Automatic reloads
Windows
(โ)
โ
Linux
(โ)
โ
Android
(โ)
โ
macOS
(โ)
โ
iOS
(โ Safari and Chrome, after page reload) โ Mozilla XR Viewer
โ
Xcode Simulators
(โ after page reload)
โ
With a self-signed, trusted root certificate No security warning is displayed. You need to install the generated certificate on your device(s). Uses the vite-plugin-mkcert npm package.
in Unity/Blender, click on "Open Workspace" to open VS Code
check that you're using vite-plugin-mkcert instead of vite-plugin-basic-ssl (the latter doesn't generate a trusted root certificate, which we need) in your vite.config.ts file:
open package.json
remove the line with "@vitejs/plugin-basic-ssl" from devDependencies
open a Terminal in VS Code and run npm install vite-plugin-mkcert --save-dev which will add the latest version
open vite.config.ts and replace import basicSsl from '@vitejs/plugin-basic-ssl' with import mkcert from'vite-plugin-mkcert'
in plugins: [, replace basicSsl(), with mkcert(),
package.json looks like this now:
run npm run start once from VS Code's terminal
on Windows, this will open a new window and guide you through the creation and installation of the certificate
on MacOS, the terminal prompts for your password and then generates and installs the certificate.
if you're getting an error Error: Port 3000 is already in use, please close the server that may still be running from Unity.
the generated certificate will automatically be installed on the machine you generated it on.
you can stop the terminal process again.
from now on, pressing Play in Unity/Blender will use the generated certificate for the local server, and no "security warning" will be shown anymore, since your browser now trusts the local connection.
On your development devices, you need to install the generated certificate and allow the OS to trust it. This is different for each OS. For each of them, you'll need the rootCA.pem file that was generated, and send it to the device you want to authenticate.
On Windows: find the certificate in Users/<your-user>/.vite-plugin-mkcert/rootCA.pem On MacOS: find the certificate in Users/<your-user>/.vite-plugin-mkcert/rootCA.pem
Send the device to yourself (e.g. via E-Mail, AirDrop, iCloud, USB, Slack, ...) so that you can access it on your development devices.
You'll be prompted to add the profile to your device. Confirm.
Go to Settings
There will be a new entry "Profile". Select it and allow the profile to be active for this device.
On iOS / iPadOS, you also need to allow "Root Certificate Trust". For this, search for Trust or go to Settings > General > About > Info > Certificate Trust Settings and enable full trust for the root certificate.
Tips
The certificate is automatically installed on the machine you generated it on. For other machines in the local network, follow the steps below to also establish a trusted connection.
Open certmgr ("Manage user certificates") by typing Windows key + certmgr.
In the left sidebar, select "Trusted Root Certification Authorities".
Right-click on "Certificates" and select "All Tasks > Import".
Select the rootCA.pem file (you may have to change the file type to "all") and follow the instructions.
',27)]))}const d=t(r,[["render",s],["__file","testing.html.vue"]]),h=JSON.parse(`{"path":"/testing.html","title":"Testing on local devices","lang":"en-US","frontmatter":{"title":"Testing on local devices","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/testing on local devices.png"}],["meta",{"name":"og:description","content":"---\\nWhen using our templates, Needle Engine runs a local development server for you. Simply press play, and a website will open in your default browser, ready for testing on your local device. For testing on other devices in the same network, you may have to install a self-signed certificate (see below).\\nWhen using a custom setup / framework, please refer to your framework's documentation on how to run a secure local development server.\\n::: tip\\nOur local server uses the IP address in your local network (e.g. https://192.168.0.123:3000) instead of localhost:3000. This allows you to quickly view and test your project from mobile devices, VR glasses, and other machines in the same network.\\nWe're using HTTPS instead of the older HTTP, because modern powerful web APIs like WebXR require a secure connection โ the S stands for 'secure'.\\n:::\\nDifferent operating systems have different security requirements for local development. Typically, displaying a website will work even with a auto-generated untrusted certificate, but browsers may warn about the missing trust and some features are not available. Here's a summary:\\n::: tip\\nInstalling trusted self-signed certificates on your development devices is recommended for a smooth development experience. Find the steps at the bottom of this page.\\n:::\\nDefault โ with auto-generated untrusted certificate\\n_Displays a certificate warning upon opening the project in a browser._\\n_Uses the vite-plugin-basic-ssl npm package._\\nWe're using websocket connections to communicate between the browser and the local development server. Websockets require a secure connection (HTTPS) in order to work. Depending on the platform, you might need to set up a custom certificate for local development. Android and Windows don't need a custom certificate, but iOS an"}]],"description":"---\\nWhen using our templates, Needle Engine runs a local development server for you. Simply press play, and a website will open in your default browser, ready for testing on your local device. For testing on other devices in the same network, you may have to install a self-signed certificate (see below).\\nWhen using a custom setup / framework, please refer to your framework's documentation on how to run a secure local development server.\\n::: tip\\nOur local server uses the IP address in your local network (e.g. https://192.168.0.123:3000) instead of localhost:3000. This allows you to quickly view and test your project from mobile devices, VR glasses, and other machines in the same network.\\nWe're using HTTPS instead of the older HTTP, because modern powerful web APIs like WebXR require a secure connection โ the S stands for 'secure'.\\n:::\\nDifferent operating systems have different security requirements for local development. Typically, displaying a website will work even with a auto-generated untrusted certificate, but browsers may warn about the missing trust and some features are not available. Here's a summary:\\n::: tip\\nInstalling trusted self-signed certificates on your development devices is recommended for a smooth development experience. Find the steps at the bottom of this page.\\n:::\\nDefault โ with auto-generated untrusted certificate\\n_Displays a certificate warning upon opening the project in a browser._\\n_Uses the vite-plugin-basic-ssl npm package._\\nWe're using websocket connections to communicate between the browser and the local development server. Websockets require a secure connection (HTTPS) in order to work. Depending on the platform, you might need to set up a custom certificate for local development. Android and Windows don't need a custom certificate, but iOS an"},"headers":[{"level":2,"title":"Testing on local devices","slug":"testing-on-local-devices","link":"#testing-on-local-devices","children":[]},{"level":2,"title":"Setting up a self-signed certificate for development","slug":"setting-up-a-self-signed-certificate-for-development","link":"#setting-up-a-self-signed-certificate-for-development","children":[{"level":3,"title":"Generating a self-signed development certificate","slug":"generating-a-self-signed-development-certificate","link":"#generating-a-self-signed-development-certificate","children":[]}]},{"level":2,"title":"Installing the certificate on your development devices","slug":"installing-the-certificate-on-your-development-devices","link":"#installing-the-certificate-on-your-development-devices","children":[{"level":3,"title":"Installing the certificate on Android","slug":"installing-the-certificate-on-android","link":"#installing-the-certificate-on-android","children":[]},{"level":3,"title":"Installing the certificate on iOS / iPadOS / VisionOS","slug":"installing-the-certificate-on-ios-ipados-visionos","link":"#installing-the-certificate-on-ios-ipados-visionos","children":[]},{"level":3,"title":"Installing the certificate on another MacOS machine","slug":"installing-the-certificate-on-another-macos-machine","link":"#installing-the-certificate-on-another-macos-machine","children":[]},{"level":3,"title":"Installing the certificate on another Windows machine","slug":"installing-the-certificate-on-another-windows-machine","link":"#installing-the-certificate-on-another-windows-machine","children":[]}]}],"git":{"updatedTime":1689176384000},"filePathRelative":"testing.md"}`);export{d as comp,h as data};
diff --git a/assets/texture-compression-BuEaeBZn.js b/assets/texture-compression-BuEaeBZn.js
new file mode 100644
index 000000000..e374e6f24
--- /dev/null
+++ b/assets/texture-compression-BuEaeBZn.js
@@ -0,0 +1 @@
+const e="/docs/blender/texture-compression.webp";export{e as _};
diff --git a/assets/tool-tile-DaqpZjM0.js b/assets/tool-tile-DaqpZjM0.js
new file mode 100644
index 000000000..ca4bda8a2
--- /dev/null
+++ b/assets/tool-tile-DaqpZjM0.js
@@ -0,0 +1 @@
+import{_ as n,o as l,c as a,a as o,n as r,f as s,g as d}from"./app-CRZRGfEE.js";const c={props:{image:String,docs_url:String},methods:{getClass:u}};function u(){return this.$slots["video-tutorial"]?"":"small"}const _=["src"],m={class:"description"},f=["href"];function g(e,h,t,v,p,i){return l(),a("div",{class:r(["tile",i.getClass()])},[o("img",{src:t.image,alt:"Tool Image",class:r(t.image?"":"hidden")},null,10,_),o("h3",null,[s(e.$slots,"tool-name",{},void 0,!0)]),o("p",m,[s(e.$slots,"tool-description",{},void 0,!0)]),s(e.$slots,"download-button",{},void 0,!0),o("p",null,[s(e.$slots,"video-tutorial",{},void 0,!0)]),t.docs_url?(l(),a("a",{key:0,href:t.docs_url},"Next Steps",8,f)):d("",!0)],2)}const S=n(c,[["render",g],["__scopeId","data-v-c9f16638"],["__file","tool-tile.vue"]]);export{S as default};
diff --git a/assets/tool-tiles-BfEF3OW4.js b/assets/tool-tiles-BfEF3OW4.js
new file mode 100644
index 000000000..65f6a97c3
--- /dev/null
+++ b/assets/tool-tiles-BfEF3OW4.js
@@ -0,0 +1 @@
+import{_ as a,r as s,o as u,c as g,a as n,b as l,w as e,d as o,F as m}from"./app-CRZRGfEE.js";const _={};function p(w,t){const d=s("needle-button"),i=s("video-embed"),r=s("tool-tile");return u(),g(m,null,[n("div",null,[l(r,{image:"/docs/imgs/unity-logo.webp",docs_url:"/docs/unity/"},{"tool-name":e(()=>t[0]||(t[0]=[o(" Needle ร Unity ")])),"tool-description":e(()=>t[1]||(t[1]=[o(" Unity 2021.3 LTS"),n("br",null,null,-1),o(" Unity 2022.3 LTS"),n("br",null,null,-1),o(" Unity 6 ")])),"download-button":e(()=>[l(d,{event_goal:"download_unity",event_position:"getting_started",large:"",href:"https://engine.needle.tools/downloads/unity?utm_source=needle_docs&utm_content=getting_started",next_url:"./../unity/"},{default:e(()=>t[2]||(t[2]=[n("strong",null,"Download",-1)])),_:1})]),"video-tutorial":e(()=>[l(i,{src:"https://www.youtube.com/watch?v=gfslrxhkH3E",limit_height:"",max_height:"70xp"})]),_:1}),l(r,{image:"/docs/blender/logo.png",docs_url:"/docs/blender/"},{"tool-name":e(()=>t[3]||(t[3]=[o(" Needle ร Blender ")])),"tool-description":e(()=>t[4]||(t[4]=[o(" Blender 4.0"),n("br",null,null,-1),o(" Blender 4.1"),n("br",null,null,-1),o(" Blender 4.2+ ")])),"download-button":e(()=>[l(d,{event_goal:"download_blender",event_position:"getting_started",large:"",href:"https://engine.needle.tools/downloads/blender?utm_source=needle_docs&utm_content=getting_started",next_url:"./../blender/"},{default:e(()=>t[5]||(t[5]=[n("strong",null,"Download",-1)])),_:1})]),"video-tutorial":e(()=>[l(i,{src:"https://www.youtube.com/watch?v=TXEHR4Cq7iU",limit_height:"",max_height:"70xp"})]),_:1})]),n("div",null,[l(r,{image:"/docs/imgs/logo-webcomponents.png"},{"tool-name":e(()=>t[6]||(t[6]=[o(" "),n("br",null,null,-1),o("Web Component ")])),"tool-description":e(()=>t[7]||(t[7]=[o(" Rich, interactive 3D content made easy ")])),"download-button":e(()=>[l(d,{event_goal:"download_webcomponent",event_position:"getting_started",large:"",same_tab:"",href:"/docs/three/"},{default:e(()=>t[8]||(t[8]=[n("strong",null,"Get Started",-1)])),_:1})]),_:1}),l(r,{image:"/docs/imgs/threejs-logo.webp"},{"tool-name":e(()=>t[9]||(t[9]=[o(" Needle ร three.js ")])),"tool-description":e(()=>t[10]||(t[10]=[o(" three.js r162+ ")])),"download-button":e(()=>[l(d,{event_goal:"download_threejs",event_position:"getting_started",large:"",same_tab:"",href:"/docs/three/"},{default:e(()=>t[11]||(t[11]=[n("strong",null,"Get Started",-1)])),_:1})]),_:1}),l(r,null,{"tool-name":e(()=>t[12]||(t[12]=[o(" Other Workflows ")])),"tool-description":e(()=>t[13]||(t[13]=[o(" Learn how to integrate Needle Engine into your tool or workflow ")])),"download-button":e(()=>[l(d,{event_goal:"download_custom",event_position:"getting_started",large:"",same_tab:"",href:"/docs/custom-integrations/"},{default:e(()=>t[14]||(t[14]=[n("strong",null,"Learn More",-1)])),_:1})]),_:1})])],64)}const v=a(_,[["render",p],["__scopeId","data-v-90f1465a"],["__file","tool-tiles.vue"]]);export{v as default};
diff --git a/assets/typescript-decorators.html-DQAWNuRE.js b/assets/typescript-decorators.html-DQAWNuRE.js
new file mode 100644
index 000000000..2f08e4ad4
--- /dev/null
+++ b/assets/typescript-decorators.html-DQAWNuRE.js
@@ -0,0 +1,56 @@
+import{_ as s,o as a,c as t,e as l}from"./app-CRZRGfEE.js";const n={};function e(h,i){return a(),t("div",null,i[0]||(i[0]=[l(`
The following table contains available Typescript decorators that Needle Engine provides.
You can think of them as Attributes on steroids (if you are familiar with C#) - they can be added to classes, fields or methods in Typescript to provide additional functionality.
Field & Property Decorators
@serializable()
Add to exposed / serialized fields. Is used when loading glTF files that have been exported with components from Unity or Blender.
@syncField()
Add to a field to network the value when it changes. You can pass in a method to be called when the field changes
@validate()
Add to receive callbacks in the component event method onValidate whenever the value changes. This behaves similar to Unity's onValidate.
Method Decorators
@prefix(<type>) (experimental)
Can be used to easily inject custom code into other components. Optionally return false to prevent the original method from being executed. See the example below
Class Decorators
@registerType
No argument. Can be added to a custom component class to be registered to the Needle Engine types and to enable hot reloading support.
import { Behaviour, serializable, EventList } from "@needle-tools/engine";
+import { Object3D } from "three";
+
+export class SomeComponentType extends Behaviour {}
+
+export class ButtonObject extends Behaviour {
+ // you can omit the type if it's a primitive
+ // e.g. Number, String or Bool
+ @serializable()
+ myNumber: number = 42;
+
+ // otherwise add the concrete type that you want to serialize to
+ @serializable(EventList)
+ onClick?: EventList;
+
+ @serializable(SomeComponentType)
+ myComponent?: SomeComponentType;
+
+ // Note that for arrays you still add the concrete type (not the array)
+ @serializable(Object3D)
+ myObjects?: Object3D[];
+}
The @syncField decorator can be used to automatically network properties of your components for all users (visitors of your website) connected to the same networking room. It can optionally take a callback function that will be invoked whenever the value changes.
To notify the system that a reference value (like an object or an array) has changed you need to re-assign the field. E.g. like this: myField = myField
The callback function can not be an arrow function (e.g. MyScript.prototype.onNumberChanged works for onNumberChanged() { ... } but it does not for myNumberChanged = () => { ... })
import { Camera, prefix } from "@needle-tools/engine";
+class YourClass {
+ @prefix(Camera) // < this is type that has the method you want to change
+ awake() { // < this is the method name you want to change
+
+ // this is now called before the Camera.awake method runs
+ // NOTE: \`this\` does now refer to the Camera instance and NOT \`YourClass\` anymore. This allows you to access internal state of the component as well
+ console.log("Hello camera:", this)
+ // optionally return false if you want to prevent the default behaviour
+ }
+}
`,15)]))}const p=s(n,[["render",e],["__file","typescript-decorators.html.vue"]]),r=JSON.parse('{"path":"/reference/typescript-decorators.html","title":"@serializable and other decorators","lang":"en-US","frontmatter":{"title":"@serializable and other decorators","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/@serializable and other decorators.png"}],["meta",{"name":"og:description","content":"---\\nThe following table contains available Typescript decorators that Needle Engine provides.\\nYou can think of them as Attributes on steroids (if you are familiar with C#)"}]],"description":"---\\nThe following table contains available Typescript decorators that Needle Engine provides.\\nYou can think of them as Attributes on steroids (if you are familiar with C#)"},"headers":[{"level":2,"title":"Examples","slug":"examples","link":"#examples","children":[{"level":3,"title":"Serializable","slug":"serializable","link":"#serializable","children":[]},{"level":3,"title":"SyncField","slug":"syncfield","link":"#syncfield","children":[]},{"level":3,"title":"Validate","slug":"validate","link":"#validate","children":[]},{"level":3,"title":"Prefix","slug":"prefix","link":"#prefix","children":[]}]}],"git":{"updatedTime":1726585195000},"filePathRelative":"reference/typescript-decorators.md"}');export{p as comp,r as data};
diff --git a/assets/typescript-essentials.html-B7bTUCZ9.js b/assets/typescript-essentials.html-B7bTUCZ9.js
new file mode 100644
index 000000000..1c6ea6675
--- /dev/null
+++ b/assets/typescript-essentials.html-B7bTUCZ9.js
@@ -0,0 +1,99 @@
+import{_ as t,r as n,o as h,c as e,e as l,a as s,b as k,w as p,d as r}from"./app-CRZRGfEE.js";const d={};function o(y,i){const a=n("RouteLink");return h(),e("div",null,[i[1]||(i[1]=l(`
The following guide tries to highlight some of the key differences between C#, Javascript and Typescript. This is most useful for developers new to the web ecosystem.
Here are also some useful resources for learning how to write Typescript:
CSharp or C# is a statically typed & compiled language. It means that before your code can run (or be executed) it has to be compiled - translated - into IL or CIL, an intermediate language that is a little closer to machine code. The important bit to understand here is that your code is analyzed and has to pass certain checks and rules that are enforced by the compiler. You will get compiler errors in Unity and your application not even start running if you write code that violates any of the rules of the C# language. You will not be able to enter Play-Mode with compiler errors.
Javascript on the other hand is interpreted at runtime. That means you can write code that is not valid and cause errors - but you will not see those errors until your program runs or tries to execute exactly that line that has the error. For example you can write var points = 100; points += "hello world"; and nobody will complain until you run the code in a browser.
Typescript is a language designed by Microsoft that compiles to javascript It adds a lot of features like for example type-safety. That means when you write code in Typescript you can declare types and hence get errors at compile-time when you try to e.g. make invalid assignments or call methods with unexpected types. Read more about types in Javascript and Typescript below.
Vanilla Javascript does (as of today) not have any concept of types: there is no guarantuee that a variable that you declared as let points = 100 will still be a number later in your application. That means that in Javascript it is perfectly valid code to assign points = new Vector3(100, 0, 0); later in your code. Or even points = null or points = myRandomObject - you get the idea. This is all OK while you write the code but it may crash horrible when your code is executed because later you write points -= 1 and now you get errors in the browser when your application is already running.
As mentioned above Typescript was created to help fix that problem by adding syntax for defining types.
It is important to understand that you basically still write Javascript when you write Typescript and while it is possible to circumvent all type checking and safety checks by e.g. adding //@ts-ignore above a erroneous line or defining all types as any this is definitely not recommneded. Types are here to help you find errors before they actually happen. You really dont want to deploy your website to your server only to later get reports from users or visitors telling you your app crashed while it was running.
While vanilla Javascript does not offer types you can still add type-annotations to your javascript variables, classes and methods by using JSDoc.
In C# you write variables either by using the type or the var keyword. For example you can either write int points = 100; or alternatively use var and let the compiler figure out the correct type for you: var points = 100
In Javascript or Typescript you have two modern options to declaring a variable. For a variable that you plan to re-assign use let, for example let points = 100; For a variable that you do not want to be able to re-assign use const, for example const points = 100;
Be aware of var You might come across the var keyword in javascript as well but it is not recommended to use it and the modern replacement for it is let. Learn more about var vs let.
Please note that you can still assign values to variables declared with const if they are (for example) a custom type. Consider the following example:
import { Vector3 } from "three";
+// ---cut-before---
+const myPosition : Vector3 = new Vector3(0, 0, 0);
+myPosition.x = 100; // Assigning x is perfectly fine
The above is perfectly fine Typescript code because you don't re-assign myPosition but only the x member of myPosition. On the other hand the following example would not be allowed and cause a runtime or typescript error:
// @errors: 2588
+import { Vector3 } from "three";
+// ---cut-before---
+const myPosition : Vector3 = new Vector3(0, 0, 0);
+myPosition = new Vector3(100, 0, 0); // โ ASSIGNING TO CONST IS NOT ALLOWED
In Unity you usually add using statements at the top of you code to import specific namespaces from Assemblies that are references in your project or - in certain cases - you migth find yourself importing a specific type with a name from a namespace. See the following example:
using UnityEngine;
+// importing just a specific type and giving it a name
+using MonoBehaviour = UnityEngine.MonoBehaviour;
This is how you do the same in Typescript to import specific types from a package:
import { Vector3 } from 'three';
+import { Behaviour } from '@needle-tools/engine';
You can also import all the types from a specific package by giving it a name which you might see here and there:
import * as THREE from 'three';
+const myVector : THREE.Vector3 = new THREE.Vector3(1, 2, 3);
Vector2, Vector3, Vector4... If you have a C# background you might be familiar with the difference between a class and a struct. While a class is a reference type a struct is a custom value type. Meaning it is, depending on the context, allocated on the stack and when being passed to a method by default a copy is created. Consider the following example in C#:
void MyCallerMethod(){
+ var position = new Vector3(0,0,0);
+ MyExampleVectorMethod(position);
+ UnityEngine.Debug.Log("Position.x is " + position.x); // Here x will be 0
+}
+void MyExampleVectorMethod(Vector3 position){
+ position.x = 42;
+}
A method is called with a Vector3 named position. Inside the method the passed in vector position is modified: x is set to 42. But in C# the original vector that is being passed into this method (see line 2) is not changed and x will still be 0 (line 4).
The same is not true for Javascript/Typescript. Here we don't have custom value types, meaning if you come across a Vector in Needle Engine or three.js you will always have a reference type. Consider the following example in typescript:
import { Vector3 } from 'three'
+
+function myCallerMethod() : void {
+ const position = new Vector3(0,0,0);
+ myExampleVectorMethod(position);
+ console.log("Position.x is " + position.x); // Here x will be 42
+}
+function myExampleVectorMethod(position: Vector3) : void {
+ position.x = 42;
+}
Do you see the difference? Because vectors and all custom objects are in fact reference types we will have modified the original position variable (line 3) and x is now 42.
This is not only important to understand for methods but also when working with variables. In C# the following code will produce two instances of Vector3 and changing one will not affect the other:
var myVector = new Vector3(1,1,1);
+var myOtherVector = myVector;
+myOtherVector.x = 42;
+// will log: 1, 42
+UnityEngine.Debug.Log(myVector.x + ", " + myOtherVector.x);
If you do the same in Typescript you will not create a copy but get a reference to the same myVector instance instead:
import { Vector3 } from 'three'
+
+const myVector = new Vector3(1,1,1);
+const myOtherVector = myVector;
+myOtherVector.x = 42;
+// will log: 42, 42
+console.log(myVector.x, myOtherVector.x);
While in C# you can use operator overloading this is not available in Javascript unfortunately. This means that while you can multiply a Vector3 in C# like this:
var myFirstVector = new Vector3(1,1,1);
+var myFactor = 100f;
+myFirstVector *= myFactor;
+// โ myFirstVector is now 100, 100, 100
you have to use a method on the Vector3 type to archieve the same result (just with a little more boilerplate code)
import { Vector3 } from "three"
+
+const myFirstVector : Vector3 = new Vector3(1, 1, 1)
+const myFactor = 100;
+myFirstVector.multiplyScalar(myFactor);
+// โ myFirstVector is now 100, 100, 100
You notice that the second variable playerIsNullOrUndefined is using == which does a loose equality check in which case null and undefined will both result in truehere. You can read more about that here
When you subscribe to an Event in C# you do it like this:
// this is how an event is declared
+event Action MyEvent;
+// you subscribe by adding to (or removing from)
+void OnEnable() {
+ MyEvent += OnMyEvent;
+}
+void OnDisable() {
+ MyEvent -= OnMyEvent;
+}
+void OnMyEvent() {}
In Typescript and Javascript when you add a method to a list you have to "bind this". That essentially means you create a method where you explictly set this to (usually) your current class instance. There are two way to archieve this.
Please note that we are using the type EventList here which is a Needle Engine type to declare events (the EventList will also automatically be converted to a UnityEvent and or a event list in Blender when you use them with our Editor integrations)
The short and recommended syntax for doing this is to use Arrow Functions.
import { EventList, Behaviour, serializable } from "@needle-tools/engine";
+
+export class MyComponent extends Behaviour {
+
+ @serializable(EventList)
+ myEvent!: EventList;
+
+ onEnable() {
+ this.myEvent.addEventListener(this.onMyEvent);
+ }
+
+ onDisable() {
+ this.myEvent.removeEventListener(this.onMyEvent);
+ }
+
+ // Declaring the function as an arrow function to automatically bind \`this\`
+ private onMyEvent = () => {
+ console.log(this !== undefined, this)
+ }
+}
There is also the more verbose "classical" way to archieve the same thing by manually binding this (and saving the method in a variable to later remove it again from the event list):
import { EventList, Behaviour, serializable } from "@needle-tools/engine";
+
+export class MyComponent extends Behaviour {
+
+ @serializable(EventList)
+ myEvent?: EventList;
+
+ private _onMyEventFn?: Function;
+
+ onEnable() {
+ // bind this
+ this._onMyEventFn = this.onMyEvent.bind(this);
+ // add the bound method to the event
+ this.myEvent?.addEventListener(this._onMyEventFn);
+ }
+
+ onDisable() {
+ this.myEvent?.removeEventListener(this._onMyEventFn);
+ }
+
+ // Declaring the function as an arrow function to automatically bind \`this\`
+ private onMyEvent = () => { }
+}
`,60)),s("ul",null,[s("li",null,[k(a,{to:"/scripting.html"},{default:p(()=>i[0]||(i[0]=[r("Needle Engine Scripting")])),_:1})])])])}const c=t(d,[["render",o],["__file","typescript-essentials.html.vue"]]),C=JSON.parse(`{"path":"/getting-started/typescript-essentials.html","title":"Scripting in Needle Engine","lang":"en-US","frontmatter":{"title":"Scripting in Needle Engine","description":"Differences, similarities and key concepts of Typescript, Javascript and C#.","sidebarDepth":2,"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/scripting in needle engine.png"}],["meta",{"name":"og:description","content":"Differences, similarities and key concepts of Typescript, Javascript and C#."}]]},"headers":[{"level":3,"title":"Key differences between C#, Javascript or Typescript","slug":"key-differences-between-c-javascript-or-typescript","link":"#key-differences-between-c-javascript-or-typescript","children":[]},{"level":3,"title":"Types โ or the lack thereof","slug":"types-or-the-lack-thereof","link":"#types-or-the-lack-thereof","children":[]},{"level":3,"title":"Variables","slug":"variables","link":"#variables","children":[]},{"level":3,"title":"Using or Importing Types","slug":"using-or-importing-types","link":"#using-or-importing-types","children":[]},{"level":3,"title":"Primitive Types","slug":"primitive-types","link":"#primitive-types","children":[]},{"level":3,"title":"Vector Maths and Operators","slug":"vector-maths-and-operators","link":"#vector-maths-and-operators","children":[]},{"level":3,"title":"Equality Checks","slug":"equality-checks","link":"#equality-checks","children":[]},{"level":3,"title":"Events, Binding and this","slug":"events-binding-and-this","link":"#events-binding-and-this","children":[]},{"level":2,"title":"What's next?","slug":"what-s-next","link":"#what-s-next","children":[]}],"git":{"updatedTime":1726585195000},"filePathRelative":"getting-started/typescript-essentials.md"}`);export{c as comp,C as data};
diff --git a/assets/usdz-hide-object-on-start.html-Bd-eN8WF.js b/assets/usdz-hide-object-on-start.html-Bd-eN8WF.js
new file mode 100644
index 000000000..92334301c
--- /dev/null
+++ b/assets/usdz-hide-object-on-start.html-Bd-eN8WF.js
@@ -0,0 +1,22 @@
+import{_ as t,r as n,o as h,c as e,a as s,b as l,e as k}from"./app-CRZRGfEE.js";const p={};function r(d,i){const a=n("contribution-header");return h(),e("div",null,[i[0]||(i[0]=s("p",null,[s("a",{href:"/docs/community/contributions"},"Overview")],-1)),l(a,{url:"https://github.com/marwie",author:"marwie",page:"/docs/community/contributions/marwie",profileImage:"https://avatars.githubusercontent.com/u/5083203?s=100&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/156",title:"USDZ: Hide Object on Start",gradient:"True"}),i[1]||(i[1]=k(`
This is an example from our Everywhere Actions. The following script hides an object on start on Android and on iOS AR
`,2))])}const g=t(p,[["render",r],["__file","usdz-hide-object-on-start.html.vue"]]),y=JSON.parse('{"path":"/community/contributions/marwie/usdz-hide-object-on-start","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/marwie: usdz hide object on start.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{g as comp,y as data};
diff --git a/assets/vanilla-js.html-DtAi2Rtr.js b/assets/vanilla-js.html-DtAi2Rtr.js
new file mode 100644
index 000000000..2830dc7a8
--- /dev/null
+++ b/assets/vanilla-js.html-DtAi2Rtr.js
@@ -0,0 +1 @@
+import{_ as n,r as o,o as a,c as i,a as l,d as s,b as r}from"./app-CRZRGfEE.js";const d={};function c(m,e){const t=o("RouteLink");return a(),i("div",null,[l("p",null,[e[0]||(e[0]=s("Moved to ")),r(t,{to:"/getting-started/"})])])}const p=n(d,[["render",c],["__file","vanilla-js.html.vue"]]),v=JSON.parse('{"path":"/vanilla-js.html","title":"Using Needle Engine directly from HTML","lang":"en-US","frontmatter":{"title":"Using Needle Engine directly from HTML","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/using needle engine directly from html.png"}],["meta",{"name":"og:description","content":"---\\nMoved to [](./getting-started/)"}]],"description":"---\\nMoved to [](./getting-started/)"},"headers":[],"git":{"updatedTime":1727182460000},"filePathRelative":"vanilla-js.md"}');export{p as comp,v as data};
diff --git a/assets/vertical-move-in-vr-using-the-right-joystick-quest.html-4yb7LuOj.js b/assets/vertical-move-in-vr-using-the-right-joystick-quest.html-4yb7LuOj.js
new file mode 100644
index 000000000..0dc9bfec6
--- /dev/null
+++ b/assets/vertical-move-in-vr-using-the-right-joystick-quest.html-4yb7LuOj.js
@@ -0,0 +1,59 @@
+import{_ as t,r as n,o as h,c as k,a as i,b as l,e as p}from"./app-CRZRGfEE.js";const e={};function r(d,s){const a=n("contribution-header");return h(),k("div",null,[s[0]||(s[0]=i("p",null,[i("a",{href:"/docs/community/contributions"},"Overview")],-1)),l(a,{url:"https://github.com/Web3Kev",author:"Web3Kev",page:"/docs/community/contributions/web3kev",profileImage:"https://avatars.githubusercontent.com/u/106066970?s=100&v=4",githubUrl:"https://github.com/needle-tools/needle-engine-support/discussions/158",title:"Vertical Move in VR using the right joystick (Quest)",gradient:"True"}),s[1]||(s[1]=p(`
The following code will enable Quest users (haven't tested with other devices) to move up and down with the right-joystick\`s y axis. (the x axis being used for snap-turns).
This code will interfere with the teleport script when accidentally pointing towards an object and trying to move up. It is recommended to remove the teleport script for that matter.
`,4))])}const g=t(e,[["render",r],["__file","vertical-move-in-vr-using-the-right-joystick-quest.html.vue"]]),y=JSON.parse('{"path":"/community/contributions/web3kev/vertical-move-in-vr-using-the-right-joystick-quest","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/web3kev: vertical move in vr using the right joystick quest.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{g as comp,y as data};
diff --git a/assets/video-embed-Dnh8jbMt.js b/assets/video-embed-Dnh8jbMt.js
new file mode 100644
index 000000000..40286580a
--- /dev/null
+++ b/assets/video-embed-Dnh8jbMt.js
@@ -0,0 +1 @@
+import{_ as n,o as t,c as r,a,u as l}from"./app-CRZRGfEE.js";const _={props:{src:String,controls:Boolean,limit_height:Boolean,max_height:String}},o=_,i=()=>{l(e=>({"50c06be4":e.limit_height?"auto":"100%",22327078:e.limit_height?"100%":"auto","6af71086":e.limit_height?e.max_height:"100%"}))},c=o.setup;o.setup=c?(e,s)=>(i(),c(e,s)):i;const d={key:0,class:"container"},p=["src"],h={key:1,class:"container"},u=["src"];function m(e,s,f,g,v,b){return e.src.includes("youtube.com")?(t(),r("div",d,[a("iframe",{id:"ytplayer",class:"video",src:e.src.replace("watch?v=","embed/")+"?autoplay=0&origin=http://docs.needle.tools&controls=1&loop=1&modestbranding=1&showinfo=0&color=white&rel=0",frameborder:"0",allowfullscreen:""},null,8,p)])):(t(),r("div",h,[a("video",{loop:"",autoplay:"",controls:"",src:e.src},null,8,u)]))}const B=n(o,[["render",m],["__scopeId","data-v-977b38fe"],["__file","video-embed.vue"]]);export{B as default};
diff --git a/assets/vision.html-zI-oAntf.js b/assets/vision.html-zI-oAntf.js
new file mode 100644
index 000000000..b060d40e6
--- /dev/null
+++ b/assets/vision.html-zI-oAntf.js
@@ -0,0 +1 @@
+import{_ as o,r as a,o as r,c as s,e as t,a as i,b as l,w as d,d as h}from"./app-CRZRGfEE.js";const p={};function c(g,e){const n=a("RouteLink");return r(),s("div",null,[e[1]||(e[1]=t('
We believe the use of 3D on the web will expand considerably in the next years. While today native apps are the norm, more and more content is made available as a web app or PWA. New VR and AR devices will extend into the web, creating an interesting problem: responsive suddenly not only means "small screen" or "large screen", you're also dealing with spaces, 3D, spatial placement and potentially glasses and controllers!
Add to that a push towards more interactivity and collaboration, and you have an interesting mix of challenges.
At Needle, we believe ideating and creating in this space should be easy. We've set out to speed things up โ creating our own runtime to reach these goals. That's why we're baking the ability to deploy to AR and VR right into our core components, and continually test that new ideas work across platforms.
There's numerous options, that's true! We found that current systems1 can be roughly sorted into two categories: some have great asset handling, tools, and artist-friendly workflows but output some sort of binary blob, and others are more code-focussed, developer-friendly and allow for great integration into modern web workflows2.
We want to bridge these worlds and combine the best of both worlds: artist-friendly workflows and modern web technologies. Combined with modern formats and a snappy workflow, we believe this will allow many more creators to bring their content to the web. We also saw an opportunity to get AR, VR and collaboration right from the start.
1: Examples include Unity, PlayCanvas, three.js, react-three-fiber, Babylon, A-Frame, Godot, and many more.2: There's more nuance to this than fits into an introductory paragraph! All engines and frameworks have their strengths and weaknesses, and are constantly evolving.
We think the next wave of 3D apps on the web will come with better workflows: everyone should be able to put together a 3D scene, an art gallery, present a product or 3D scan on the web or make simple games. Reaching this goal will require more than just supporting one particular system and exporting to the web from there.
Our goal is to allow people to bring data to the web from their creative tools: be it Unity, Blender, Photoshop, or something else. We're aware that this is a big goal โ but instead of doing everything at once, we want to iterate and get closer to it together.
At the core of Needle Engine stands the glTF format and its ability to be extended with custom extensions. The goal is: a single .glb file can contain your entire application's data.
It's worth noting that it's not a goal to ship actual code inside glTF; shipping and running code is the job of modern web runtimes and bundling. We certainly can imagine that abstract representations of logic (e.g. graphs, state machines, and so on) can be standardized to a certain degree and allow for interoperable worlds, but we're not there yet.
',15)),i("p",null,[l(n,{to:"/technical-overview.html"},{default:d(()=>e[0]||(e[0]=[h("Read more about our use of glTF and extensions")])),_:1})]),e[2]||(e[2]=t('
From working with Unity for many years we've found that while the engine and editor progress at a great pace, WebGL output has somewhat lacked behind. Integration of Unity players into web-based systems is rather hard, "talking" to the surrounding website requires a number of workarounds, and most of all, iteration times are very slow due to the way that Unity packs all code into WebAssembly via IL2CPP. These technologies are awesome, and result in great runtime performance and a lot of flexibility. But they're so much slower and walled off compared to modern web development workflows that we decided to take matters into our own hands.
Needle Engine builds on three.js. All rendering goes through it, glTF files are loaded via three's extension interfaces, and our component system revolves around three's Object3D and scene graph. We're committed to upstreaming some of our changes and improvements, creating pull requests and reporting issues along the way.
',10))])}const w=o(p,[["render",c],["__file","vision.html.vue"]]),m=JSON.parse(`{"path":"/vision.html","title":"Our Vision ๐ฎ","lang":"en-US","frontmatter":{"next":"features-overview","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/vision.png"}],["meta",{"name":"og:description","content":"---\\nWe believe the use of 3D on the web will expand considerably in the next years. While today native apps are the norm, more and more content is made available as a web app or PWA. New VR and AR devices will extend into the web, creating an interesting problem: responsive suddenly not only means 'small screen' or 'large screen', you're also dealing with spaces, 3D, spatial placement and potentially glasses and controllers!\\nAdd to that a push towards more interactivity and collaboration, and you have an interesting mix of challenges.\\nAt Needle, we believe ideating and creating in this space should be easy. We've set out to speed things up โ creating our own runtime to reach these goals. That's why we're baking the ability to deploy to AR and VR right into our core components, and continually test that new ideas work across platforms.\\nThere's numerous options, that's true! We found that current systems1 can be roughly sorted into two categories: some have great asset handling, tools, and artist-friendly workflows but output some sort of binary blob, and others are more code-focussed, developer-friendly and allow for great integration into modern web workflows2.\\nWe want to bridge these worlds and combine the best of both worlds: artist-friendly workflows and modern web technologies. Combined with modern formats and a snappy workflow, we believe this will allow many more creators to bring their content to the web. We also saw an opportunity to get AR, VR and collaboration right from the start.\\n1: _Examples include Unity, PlayCanvas, three.js, react-three-fiber, Babylon, A-Frame, Godot, and many more._\\n2: _There's more nuance to this than fits into an introductory p"}]],"description":"---\\nWe believe the use of 3D on the web will expand considerably in the next years. While today native apps are the norm, more and more content is made available as a web app or PWA. New VR and AR devices will extend into the web, creating an interesting problem: responsive suddenly not only means 'small screen' or 'large screen', you're also dealing with spaces, 3D, spatial placement and potentially glasses and controllers!\\nAdd to that a push towards more interactivity and collaboration, and you have an interesting mix of challenges.\\nAt Needle, we believe ideating and creating in this space should be easy. We've set out to speed things up โ creating our own runtime to reach these goals. That's why we're baking the ability to deploy to AR and VR right into our core components, and continually test that new ideas work across platforms.\\nThere's numerous options, that's true! We found that current systems1 can be roughly sorted into two categories: some have great asset handling, tools, and artist-friendly workflows but output some sort of binary blob, and others are more code-focussed, developer-friendly and allow for great integration into modern web workflows2.\\nWe want to bridge these worlds and combine the best of both worlds: artist-friendly workflows and modern web technologies. Combined with modern formats and a snappy workflow, we believe this will allow many more creators to bring their content to the web. We also saw an opportunity to get AR, VR and collaboration right from the start.\\n1: _Examples include Unity, PlayCanvas, three.js, react-three-fiber, Babylon, A-Frame, Godot, and many more._\\n2: _There's more nuance to this than fits into an introductory p"},"headers":[{"level":2,"title":"The Future of the 3D Web","slug":"the-future-of-the-3d-web","link":"#the-future-of-the-3d-web","children":[]},{"level":2,"title":"Why another platform for 3D on the web? Aren't there enough options already?","slug":"why-another-platform-for-3d-on-the-web-aren-t-there-enough-options-already","link":"#why-another-platform-for-3d-on-the-web-aren-t-there-enough-options-already","children":[]},{"level":2,"title":"Creating a Workflow, not an Editor","slug":"creating-a-workflow-not-an-editor","link":"#creating-a-workflow-not-an-editor","children":[]},{"level":2,"title":"Open Standards instead of Proprietary Containers","slug":"open-standards-instead-of-proprietary-containers","link":"#open-standards-instead-of-proprietary-containers","children":[]},{"level":2,"title":"Goals","slug":"goals","link":"#goals","children":[]},{"level":2,"title":"Non-Goals","slug":"non-goals","link":"#non-goals","children":[]},{"level":2,"title":"Needle Engine and Unity WebGL","slug":"needle-engine-and-unity-webgl","link":"#needle-engine-and-unity-webgl","children":[]},{"level":2,"title":"Needle Engine and three.js","slug":"needle-engine-and-three.js","link":"#needle-engine-and-three.js","children":[]}],"git":{"updatedTime":1667075212000},"filePathRelative":"vision.md"}`);export{w as comp,m as data};
diff --git a/assets/web3kev.html-BYOJlV9Z.js b/assets/web3kev.html-BYOJlV9Z.js
new file mode 100644
index 000000000..5229fa0e6
--- /dev/null
+++ b/assets/web3kev.html-BYOJlV9Z.js
@@ -0,0 +1,466 @@
+import{_ as e,r as a,o as r,c as p,b as k,w as t,a as i,d as s}from"./app-CRZRGfEE.js";const C={};function d(y,h){const l=a("contribution-preview"),n=a("contributions-author");return r(),p("div",null,[k(n,{overviewLink:"/docs/community/contributions",name:"Web3Kev",url:"https://github.com/Web3Kev",profileImage:"https://avatars.githubusercontent.com/u/106066970?s=100&v=4",githubUrl:"https://github.com/Web3Kev"},{default:t(()=>[k(l,{title:"Vertical Move in VR using the right joystick (Quest)",pageUrl:"/docs/community/contributions/web3kev/vertical-move-in-vr-using-the-right-joystick-quest"},{default:t(()=>h[0]||(h[0]=[i("p",null,"The following code will enable Quest users (haven't tested with other devices) to move up and down with the right-joystick`s y axis. (the x axis being used for snap-turns).",-1),i("p",null,"This code will interfere with the teleport script when accidentally pointing towards an object and trying to move up. It is recommended to remove the teleport script for that matter.",-1),i("p",null,"You can place this script anywhere.",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," WebXR"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," GameObject"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Vector3"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Quaternion"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "three"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Mathf "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," VerticalMove"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," WebXR"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," joystickY"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"number"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," worldRot"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Quaternion "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Quaternion"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," start"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," void"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," let"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," _webxr"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"GameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"findObjectOfType"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(WebXR)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(_webxr)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"_webxr"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," console"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"log"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"webxr found"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," update"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"isInVR)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //get y value from right joystick")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"verticalMove"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," verticalMove"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"void")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"RightController"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"input"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gamepad"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"axes["),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"3"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"]) ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"joystickY"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"RightController"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"input"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gamepad"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"axes["),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"3"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"]"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," speedFactor "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 3"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," powFactor "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 2"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," speed "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Mathf"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"clamp01"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"2"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," *"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 2"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," verticalDir "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"joystickY "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ?"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 1"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," :"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," -"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"1"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," let"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," vertical "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Math"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"pow"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"joystickY"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," powFactor)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," vertical "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"*="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," verticalDir"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," vertical "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"*="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," speed"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Rig"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"getWorldQuaternion"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"worldRot)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," let"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," movementVector"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}},"new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Vector3"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," movementVector"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"set"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," vertical"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," movementVector"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"applyQuaternion"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"TransformOrientation)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," movementVector"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"x "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," movementVector"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"applyQuaternion"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"worldRot)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," movementVector"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"multiplyScalar"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(speedFactor "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"*"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"time"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"deltaTime)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Rig"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"position"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"add"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(movementVector)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1)])),_:1}),k(l,{title:"Squeeze to Scale (Object or World) in VR",pageUrl:"/docs/community/contributions/web3kev/squeeze-to-scale-object-or-world-in-vr"},{default:t(()=>h[1]||(h[1]=[i("p",null,"The following code enables you to use both controllers in VR (tested on Quest) and scale the player's perspective (XRRig) by squeezing the grab triggers and moving the controllers closer (pinch out) or further apart (pinch in). The boolean allowWorldScaling has to be ticked in unity for that to work.",-1),i("p",null,"Upon selecting a draggable object (Drag controls script), the player can scale up or down that object, while keeping the finger on the trigger and squeezing both grab buttons and moving the hands closer or apart.",-1),i("p",null,"The current script enables you to visually see the scale. Create a world canvas with a text component as a child. Assign the world canvas to scaleTextObject and the text to scaleText. scaleTextObject will then spawn in front of the player and follow the head movement whenever scaling.",-1),i("p",null,"At the moment the position of the hands (controllers) is done by finding the avatar's hands. I couldn't make it work otherwise. If you find a better way please share.",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," WebXR"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"serializeable"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," WebXREvent"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"WebXRAvatar"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"GameObject"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," AvatarMarker"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Text"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Vector3"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Quaternion"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"PerspectiveCamera"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "three"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," SqueezeScale"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," WebXR"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," selectedObj"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Object3D"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"|"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," null"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," null"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializeable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"("),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Object3D"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," scaleTextObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Object3D"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"|"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," null"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," null"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializeable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"("),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Text"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," scaleText"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Text"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," public"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," allowWorldScaling"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," boolean"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," leftSqueeze"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"boolean"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," rightSqueeze"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"boolean"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," bothSqueezeStarted"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," rigScaleUpdated"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," initialDistance"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"number"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"1"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," initialScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"number"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"1"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," newScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"number"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," |"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," null"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"null"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," leftHand"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," rightHand"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," head"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," start"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," void"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," let"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," _webxr"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"GameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"findObjectOfType"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(WebXR)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(_webxr)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"_webxr"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," console"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"log"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"webxr found"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //Wait for XR Session")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," WebXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"addEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(WebXREvent"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"XRStarted"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," ()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," =>"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //listen to squeeze events")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"xrSession"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"addEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"squeezestart"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," ("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"event"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," =>"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"onSqueezeEvent"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(event"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"true"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"xrSession"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"addEventListener"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"squeezeend"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," ("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"event"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," =>"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"onSqueezeEvent"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(event"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"false"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onSqueezeEvent"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"event"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," XRInputSourceEvent"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," status"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"boolean"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(event"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"inputSource"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"handedness"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"==="),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"right"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"rightSqueeze"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"status"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(event"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"inputSource"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"handedness"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"==="),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"left"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"leftSqueeze"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"status"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," update"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"(){")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"isInVR)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //cache object selected if any")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"objectGrab"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //if both grips are squeezed ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"leftSqueeze "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"&&"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"rightSqueeze)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //if object is selected either in the left or right controller (only one)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"selectedObj"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"null"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //after initial distance value has been set")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"bothSqueezeStarted)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //get current distance between controllers")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," scaleValue"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"calculateDistance"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},' //get distance change since beginning of squeeze to get a "pinch in/out" effect')]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"initialScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"+"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scaleValue"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"-"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"initialDistance"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //avoid 0 and negative scales")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0.001"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0.001"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";}")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // scale object according to new distance since initial distance")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"selectedObj"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"x"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"selectedObj"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"y"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"selectedObj"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"z"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"showVisual"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"Object :"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," else")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //get initial distance value (only once at a new squeeze both hands event)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"bothSqueezeStarted"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"initialDistance"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"calculateDistance"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //cache object's initial scale")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"initialScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"selectedObj"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"x"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," else")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //scale world ?")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Rig "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"&&"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"allowWorldScaling)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //after initial distance value has been set")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"bothSqueezeStarted)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //get current distance between controllers")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," scaleValue"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"calculateDistance"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},' //get distance change since beginning of squeeze to get a "pinch in/out" effect')]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"initialScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"+"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scaleValue"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"-"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"initialDistance"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //avoid 0 and negative scales")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0.001"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0.001"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";}")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"showVisual"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "World :"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"rigScaleUpdated"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," else")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //get initial distance value (only once at a new squeeze both hands event)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"bothSqueezeStarted"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"initialDistance"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"calculateDistance"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //cache object's initial scale")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"initialScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Rig"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"x"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," else")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //reset values")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"bothSqueezeStarted"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //if world has been scaled, scale rig accordingly at the end of squeezing and once only")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Rig "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"&&"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"rigScaleUpdated "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"&&"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //change rig scale")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Rig"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"set"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Rig"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"updateMatrixWorld"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," cam "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"mainCamera "),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"as"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," PerspectiveCamera"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," cam"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"near"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},">"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"2"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0.0001"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0.2"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," cam"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"updateProjectionMatrix"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //reset")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"rigScaleUpdated"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scaleTextObject)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scaleTextObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"visible"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"newScale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"null"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," calculateDistance"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"number")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," let"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," distance"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"1"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"leftHand "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"&&"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"rightHand)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," left"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"leftHand"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"position"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," right"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"rightHand"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"position"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // Calculate the difference between the positions")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," dx "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," left"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"x "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"-"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," right"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"x"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," dy "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," left"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"y "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"-"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," right"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"y"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," dz "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," left"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"z "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"-"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," right"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"z"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // Calculate the distance using the Euclidean distance formula")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," distance "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Math"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"sqrt"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(dx "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"*"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," dx "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"+"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," dy "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"*"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," dy "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"+"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," dz "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"*"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," dz)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," else")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //set positions of controllers from your avatar (only once)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," let"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," allAvatars "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," AvatarMarker"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"instances"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(allAvatars"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"length"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},">"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," for"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ("),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"let"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," i"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"i"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"allAvatars"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"length"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"i"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"++"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(allAvatars[i]"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"isLocalAvatar"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"())")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," av"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"allAvatars[i]"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"avatar "),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"as"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," WebXRAvatar"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(av"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"null"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"leftHand"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"av"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"handLeft "),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"as"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"rightHand"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"av"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"handRight "),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"as"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"head "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," av"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"head "),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"as"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," return"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," distance"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," showVisual"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"scale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"number"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," mesg"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"string"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"void")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scaleTextObject "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"&&"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"head "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"&&"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scaleText)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scaleTextObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"visible"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," offset "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Vector3"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"7"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," offset"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"applyQuaternion"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"head"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"quaternion)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scaleTextObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"position"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"copy"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"head"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"position"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"add"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(offset))"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," roundedNum"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," +"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scale"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"toFixed"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"3"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"scaleText"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"text"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"mesg"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"+"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'" "'),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"+"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"roundedNum"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," objectGrab"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"void")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"RightController"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"grabbed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"selected) ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"selectedObj "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"RightController"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"grabbed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"selected"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," else"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"LeftController"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"grabbed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"selected)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"selectedObj "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"webXR"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"LeftController"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"grabbed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"selected"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," else")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"selectedObj"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"null"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1)])),_:1}),k(l,{title:"Network instantiation of multiple objects",pageUrl:"/docs/community/contributions/web3kev/network-instantiation-of-multiple-objects"},{default:t(()=>h[2]||(h[2]=[i("p",null,"In a multiuser session, typically objects are instantiated using instantiateSynced as such:",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"GameObject"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"serializable"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"InstantiateOptions"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Vector3"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "three"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," InstantiateObjectForAll"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"("),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Object3D"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," myPrefab"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," GameObject"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," public"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," makeObject"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"void"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," options "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," InstantiateOptions"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," options"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," options"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"position "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Vector3"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," GameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"instantiateSynced"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"myPrefab"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," options) "),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"as"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," GameObject"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1),i("p",null,"My particular use-case was for generating programmatically a random scene made of cubes, and that scene had to be the same for all users of the same room. I had used the example above but for some unknown reasons sometimes the scenes were partially rendered when instantiating simultaneously >400 objects. @Marcel of Needle suggested to generate a seed (position of all objects in the scene) and send that seed instead using :",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"connection"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"send"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()")])])])],-1),i("p",null,"All users using :",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"connection"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"beginListen"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()")])])])],-1),i("p",null,"would receive any seed previously sent, upon joining the same room, allowing them to instantiate cubes according to that seed (array of Vector3).",-1),i("p",null,"Here is a script illustrating the use of the send method and the beginListen counterpart:",-1),i("div",{class:"language-ts","data-highlighter":"shiki","data-ext":"ts","data-title":"ts",style:{"--shiki-light":"#4c4f69","--shiki-dark":"#c6d0f5","--shiki-light-bg":"#eff1f5","--shiki-dark-bg":"#303446"}},[i("pre",{class:"shiki shiki-themes catppuccin-latte catppuccin-frappe vp-code"},[i("code",null,[i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"//This is an example of sending the seed of a randomly generated scene made of cubes, for all other instances logging into the same room to create the same scene.")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"//This script requires a prefab (e.g. a 1x1x1 Cube)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"//This script will generate and build randomly positioned cubes (random walk) as a child of the object it is attached to. ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"//The generateSeed() method is in this script called via a button. The button is deactivated once the seed has been transmitted.")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"//Any users joining the same room will receive the seed and build the exact same scene")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Behaviour"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"GameObject"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"serializable"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"InstantiateOptions"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "@needle-tools/engine"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"import"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Vector3"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Object3D "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," from"),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},' "three"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"export"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," class"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," NetworkedSeed"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," extends"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Behaviour")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"("),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Object3D"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," prefab"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," GameObject"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," @serializable"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"("),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"Object3D"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," generateButton"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"?:"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Object3D"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," public"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," seedSize"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," number"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 30"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," seed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Vector3[] "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," []"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onEnable"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," void"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"connection"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"beginListen"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"mySeed"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"onDataReceived)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"generateButton)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"generateButton"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"visible"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"true"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onDisable"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," void"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"connection"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"stopListen"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"mySeed"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"onDataReceived)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," onDataReceived"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ="),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," ("),i("span",{style:{"--shiki-light":"#E64553","--shiki-dark":"#EA999C","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"data"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," any"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},")"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," =>"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," console"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"log"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"Received data:"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," data"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"mySeed)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"length"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"==="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //prevent other generations of the seed")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"generateButton)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"generateButton"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"visible"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"data"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"mySeed"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //build scene")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"buildScene"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," };")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //generate and send seed to all from the button generateButton")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," public"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," generateSeed"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"void"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"length"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"=="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},") "),i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"//no seed found => generate one")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," []"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," uniquePositions "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Set"),i("span",{style:{"--shiki-light":"#04A5E5","--shiki-dark":"#99D1DB"}},"<"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"string"),i("span",{style:{"--shiki-light":"#04A5E5","--shiki-dark":"#99D1DB"}},">"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //start at origin")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," startPosition "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Vector3"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"push"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(startPosition"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"clone"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"())"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," uniquePositions"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"add"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(startPosition"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"toArray"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"toString"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"())"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //go for a random walk of length : seedSize")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," while"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"length "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seedSize) "),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," lastPosition "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed["),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"length "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"-"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 1"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"]"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," let"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," newPosition"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Vector3"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //walk and add position, making sure they are unique")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," do"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," direction "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"getRandomDirection"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," newPosition "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," lastPosition"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"clone"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"add"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(direction)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," while"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," (uniquePositions"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"has"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(newPosition"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"toArray"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"toString"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()))"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"push"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(newPosition"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"clone"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"())"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," uniquePositions"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"add"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(newPosition"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"toArray"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"toString"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"())"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //send the seed to all on the server")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"sendSeed"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //prevent other generations of the seed")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"generateButton)")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"generateButton"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"visible"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"false"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //build scene locally")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"buildScene"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," sendSeed"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"void"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"length"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"connection"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"send"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"mySeed"'),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},",{"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"guid"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"guid"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," mySeed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," console"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"log"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"------ SEED SENT -------"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," public"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," buildScene"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"void"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"{")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //check if the seed is not empty")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"length"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"=="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," console"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"log"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"array was empty"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," return"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," //check if the scene has already been built")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"children"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"length"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},">"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},") ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," console"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"log"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"Scene already present"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," return"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#9CA0B0","--shiki-dark":"#737994","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," // Create cubes at each position of the random walk ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," for"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"let"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," i"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"0"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," i"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"length"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," i"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"++"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," option "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," InstantiateOptions"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"()"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," option"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"context"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," option"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"parent"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"gameObject"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," option"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"position "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}}," this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"seed[i]"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," if"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"prefab"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"!="),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"null"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," cube "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," GameObject"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"instantiate"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#D20F39","--shiki-dark":"#E78284"}},"this"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"prefab"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," option) "),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}},"as"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," GameObject"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," console"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"log"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"("),i("span",{style:{"--shiki-light":"#40A02B","--shiki-dark":"#A6D189"}},'"----------- Scene Built ---------"'),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},")"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," ")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," private"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," getRandomDirection"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"()"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},":"),i("span",{style:{"--shiki-light":"#DF8E1D","--shiki-dark":"#E5C890","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Vector3"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," {")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," x "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Math"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"random"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"() "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0.5"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ?"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," -"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"1"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," :"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 1"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," y "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Math"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"random"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"() "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0.5"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ?"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," -"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"1"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," :"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 1"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," const"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," z "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"="),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," Math"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"."),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}},"random"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"() "),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}},"<"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 0.5"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," ?"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," -"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}},"1"),i("span",{style:{"--shiki-light":"#179299","--shiki-dark":"#81C8BE"}}," :"),i("span",{style:{"--shiki-light":"#FE640B","--shiki-dark":"#EF9F76"}}," 1"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6"}}," return"),i("span",{style:{"--shiki-light":"#8839EF","--shiki-dark":"#CA9EE6","--shiki-light-font-weight":"bold","--shiki-dark-font-weight":"bold"}}," new"),i("span",{style:{"--shiki-light":"#1E66F5","--shiki-dark":"#8CAAEE","--shiki-light-font-style":"italic","--shiki-dark-font-style":"italic"}}," Vector3"),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}},"(x"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," y"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},","),i("span",{style:{"--shiki-light":"#4C4F69","--shiki-dark":"#C6D0F5"}}," z)"),i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},";")]),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}}," }")]),s(`
+`),i("span",{class:"line"}),s(`
+`),i("span",{class:"line"},[i("span",{style:{"--shiki-light":"#7C7F93","--shiki-dark":"#949CBB"}},"}")])])])],-1),i("p",null,"The above script is placed on an object (any Transform) and will generate an array of unique Vector3 positions for a specified length (seedSize) after generateSeed() is called (In this case it is called from a button: generateButton).",-1),i("p",null,"Once generated it will send the array to the server and build the scene. The building process consist of instantiating the prefab at each Vector3 position of the seed (this.seed) array.",-1),i("p",null,"Any user joining the same room after a seed has been generated and sent, will receive the seed from the server and trigger the callback onDataReceived() which will cache the seed array, disable the button, and build the scene with the prefab, according to the seed.",-1),i("p",null,"This gives a way to generate a scene and communicate the seed of that scene, for each user to build locally.",-1),i("p",null,"This was the solution I chose which worked better than instantiating a complex scene (>400 objects) with instantiateSynced which would occasionally cause bugs.",-1)])),_:1})]),_:1})])}const F=e(C,[["render",d],["__file","web3kev.html.vue"]]),E=JSON.parse('{"path":"/community/contributions/web3kev","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/contributions: web3kev.png"}],["meta",{"name":"og:description","content":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."}]],"description":"Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally. Needle Exporter for Unity bridges the Unity Editor and the web runtime. It helps you to export your assets, animations, lightmaps and so on to the web. It is built around the glTF standard for 3D assets."},"headers":[],"git":{},"filePathRelative":null}');export{F as comp,E as data};
diff --git a/assets/xr.html-BFJdLi8t.js b/assets/xr.html-BFJdLi8t.js
new file mode 100644
index 000000000..214fe85b5
--- /dev/null
+++ b/assets/xr.html-BFJdLi8t.js
@@ -0,0 +1,14 @@
+import{_ as d,r as o,o as p,c as h,e as a,a as t,d as s,b as i,w as r}from"./app-CRZRGfEE.js";const c={};function u(g,e){const n=o("RouteLink"),l=o("sample");return p(),h("div",null,[e[34]||(e[34]=a('
Theoretically all WebXR-capable devices and browsers should be supported. That being said, we've tested the following configurations:
Tested VR Device
Browser
Notes
Apple Vision Pro
โ๏ธ Safari Browser
hand tracking, support for transient pointer
Meta Quest 1
โ๏ธ Meta Browser
hand tracking, support for sessiongranted1
Meta Quest 2
โ๏ธ Meta Browser
hand tracking, support for sessiongranted1, passthrough (black and white)
Meta Quest 3
โ๏ธ Meta Browser
hand tracking, support for sessiongranted1, passthrough, depth sensing, mesh tracking
Meta Quest Pro
โ๏ธ Meta Browser
hand tracking, support for sessiongranted1, passthrough
Pico Neo 3
โ๏ธ Pico Browser
no hand tracking, inverted controller thumbsticks
Pico Neo 4
โ๏ธ Pico Browser
passthrough, hand tracking2
Oculus Rift 1/2
โ๏ธ Chrome
Hololens 2
โ๏ธ Edge
hand tracking, support for AR and VR (in VR mode, background is rendered as well)
Looking Glass Portrait
โ๏ธ Chrome
requires shim, see samples
',3)),t("table",null,[e[11]||(e[11]=t("thead",null,[t("tr",null,[t("th",null,"Tested AR Device"),t("th",null,"Browser"),t("th",null,"Notes")])],-1)),t("tbody",null,[e[5]||(e[5]=t("tr",null,[t("td",null,"Android 10+"),t("td",null,"โ๏ธ Chrome"),t("td")],-1)),e[6]||(e[6]=t("tr",null,[t("td",null,"Android 10+"),t("td",null,"โ๏ธ Firefox"),t("td")],-1)),e[7]||(e[7]=t("tr",null,[t("td",null,"iOS 15+"),t("td",null,"โ๏ธ WebXR Viewer"),t("td",null,"does not fully implement standards, but supported")],-1)),t("tr",null,[e[3]||(e[3]=t("td",null,"iOS 15+",-1)),e[4]||(e[4]=t("td",null,[s("(โ๏ธ)"),t("sup",null,"3"),s(" Safari")],-1)),t("td",null,[e[1]||(e[1]=s("No full code support, but Needle ")),i(n,{to:"/everywhere-actions.html"},{default:r(()=>e[0]||(e[0]=[s("Everywhere Actions")])),_:1}),e[2]||(e[2]=s(" are supported for creating dynamic, interactive USDZ files."))])]),e[8]||(e[8]=t("tr",null,[t("td",null,"Hololens 2"),t("td",null,"โ๏ธ Edge"),t("td")],-1)),e[9]||(e[9]=t("tr",null,[t("td",null,"Hololens 1"),t("td",null,"โ"),t("td",null,"no WebXR support")],-1)),e[10]||(e[10]=t("tr",null,[t("td",null,"Magic Leap 2"),t("td",null,"โ๏ธ"),t("td")],-1))])]),e[35]||(e[35]=t("table",null,[t("thead",null,[t("tr",null,[t("th",null,"Not Tested but Should Workโข๏ธ"),t("th",null,"Browser"),t("th",null,"Notes")])]),t("tbody",null,[t("tr",null,[t("td",null,"Magic Leap 1"),t("td"),t("td",null,"please let us know if you tried!")])])],-1)),t("p",null,[e[13]||(e[13]=t("sup",null,"1",-1)),e[14]||(e[14]=s(": Requires enabling a browser flag: ")),e[15]||(e[15]=t("code",null,"chrome://flags/#webxr-navigation-permission",-1)),e[16]||(e[16]=t("br",null,null,-1)),e[17]||(e[17]=t("sup",null,"2",-1)),e[18]||(e[18]=s(": Requires enabling a toggle in the Developer settings")),e[19]||(e[19]=t("br",null,null,-1)),e[20]||(e[20]=t("sup",null,"3",-1)),e[21]||(e[21]=s(": Uses ")),i(n,{to:"/everywhere-actions.html"},{default:r(()=>e[12]||(e[12]=[s("Everywhere Actions")])),_:1}),e[22]||(e[22]=s(" or ")),e[23]||(e[23]=t("a",{href:"#augmented-reality-and-webxr-on-ios"},"other approaches",-1))]),e[36]||(e[36]=a('
AR, VR and networking capabilites in Needle Engine are designed to be modular. You can choose to not support any of them, or add only specific features.
Enable AR and VR Add a WebXR component. Optional: you can set a custom avatar by referencing an Avatar Prefab. By default a very basic DefaultAvatar is assigned.
Enable Teleportation Add a TeleportTarget component to object hierarchies that can be teleported on. To exclude specific objects, set their layer to IgnoreRaycasting.
Define the AR Session Root and Scale Add a WebARSessionRoot component to your root object. Here you can define the user scale to shrink (< 1) or enlarge (> 1) the user in relation to the scene when entering AR.
Define whether an object is visible in Browser, AR, VR, First Person, Third Person Add a XR Flag component to the object you want to control. Change options on the dropdown as needed.
Common usecases are
hiding floors when entering AR
hiding Avatar parts in First or Third Person views (e.g. first-person head shouldn't be visible).
Needle Engine supports the sessiongranted state. This allows users to seamlessly traverse between WebXR applications without leaving an immersive session โ they stay in VR or AR.
Currently, this is only supported on Oculus Quest 1, 2 and 3 in the Oculus Browser. On other platforms, users will be kicked out of their current immersive session and have to enter VR again on the new page. Requires enabling a browser flag: chrome://flags/#webxr-navigation-permission
Click on objects to open links Add the OpenURL component that makes it very easy to build connected worlds.
',19)),t("p",null,[e[25]||(e[25]=s("Read more about scripting for XR at the ")),i(n,{to:"/scripting.html#xr-event-methods"},{default:r(()=>e[24]||(e[24]=[s("scripting XR documentation")])),_:1})]),e[37]||(e[37]=a(`
There's a number of experimental components to build more expressive Avatars. At this point we recommended duplicating them to make your own variants, since they might be changed or removed at a later point.
Example Avatar Rig with basic neck model and limb constraints
Random Player Colors As an example for avatar customization, you can add a PlayerColor component to your renderers. This randomized color is synchronized between players.
Eye Rotation AvatarEyeLook_Rotation rotates GameObjects (eyes) to follow other avatars and a random target. This component is synchronized between players.
Eye Blinking AvatarBlink_Simple randomly hides GameObjects (eyes) every few seconds, emulating a blink.
Example Avatar Prefab hierarchy
Offset Constraint OffsetConstraint allows to shift an object in relation to another one in Avatar space. This allows, for example, to have a Body follow the Head but keep rotation levelled. It also allows to construct simple neck models.
Limb Constraint BasicIKConstraint is a very minimalistic constraint that takes two transforms and a hint. This is useful to construct simple arm or leg chains. As rotation is currently not properly implemented, arms and legs may need to be rotationally symmetric to "look right". It's called "Basic" for a reason!
If you want to display different html content whether the client is using a regular browser or using AR or VR, you can just use a set of html classes. This is controlled via HTML element classes. For example, to make content appear on desktop and in AR add a <div class="desktop ar"> ... </div> inside the <needle-engine> tag:
<needle-engine>
+ <div class="desktop ar" style="pointer-events:none;">
+ <div class="positioning-container">
+ <p>your content for AR and desktop goes here</p>
+ <p class="only-in-ar">This will only be visible in AR</p>
+ <div>
+ </div>
+</needle-engine>
Content Overlays are implemented using the optional dom-overlay feature which is usually supported on screen-based AR devices (phones, tablets).
Use the .ar-session-active class to show/hide specific content while in AR. The :xr-overlay pseudo class shouldn't be used at this point because using it breaks Mozilla's WebXR Viewer.
It's worth noting that the overlay element will be always displayed fullscreen while in XR, independent of styling that has been applied. If you want to align items differently, you should make a container inside the class="ar" element.
Needle Engine supports WebXR ImageTracking (Live Demo) Note: While WebXR ImageTracking is still in "draft" phase (Marker Tracking Explainer) you need to follow these steps to enable WebXR ImageTracking on Android devices:
Enable WebXR Incubations in chrome
Add the WebXRImageTracking component
',3)),t("p",null,[e[27]||(e[27]=s("You can find additional documentation in the ")),i(n,{to:"/everywhere-actions.html#image-tracking"},{default:r(()=>e[26]||(e[26]=[s("Everywhere Actions")])),_:1}),e[28]||(e[28]=s(" section"))]),e[39]||(e[39]=a('
Without that spec, one can still request camera image access and run custom algorithms to determine device pose. Libraries to add image tracking:
Augmented Reality experiences on iOS are somewhat limited, due to Apple currently not supporting WebXR on iOS devices.
',4)),t("p",null,[e[31]||(e[31]=s("Needle Engine's ")),i(n,{to:"/everywhere-actions.html"},{default:r(()=>e[29]||(e[29]=[s("Everywhere Actions")])),_:1}),e[32]||(e[32]=s(" are designed to fill that gap, bringing automatic interactive capabilities to iOS devices for scenes composed of specific components. They support a subset of the functionality that's available in WebXR, for example spatial audio, image tracking, animations, and more. See ")),i(n,{to:"/everywhere-actions.html"},{default:r(()=>e[30]||(e[30]=[s("the docs")])),_:1}),e[33]||(e[33]=s(" for more information."))]),e[40]||(e[40]=t("h3",{id:"musical-instrument-webxr-and-quicklook-support",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#musical-instrument-webxr-and-quicklook-support"},[t("span",null,"Musical Instrument โ WebXR and QuickLook support")])],-1)),e[41]||(e[41]=t("p",null,"Here's an example for a musical instrument that uses Everywhere Actions and thus works in browsers and in AR on iOS devices. It uses spatial audio, animation, and tap interactions.",-1)),i(l,{src:"https://engine.needle.tools/samples-uploads/musical-instrument"}),e[42]||(e[42]=a('
There's also other options for guiding iOS users to even more capable interactive AR experiences:
Exporting content on-the-fly as USDZ files. These files can be displayed on iOS devices in AR. When exported from scenes with Everywhere Actions the interactivity is the same, more than sufficient for product configurators, narrative experiences and similar. An example is Castle Builder where creations (not the live session) can be viewed in AR.
Encryption in Space uses this approach. Players can collaboratively place text into the scene on their screens and then view the results in AR on iOS. On Android, they can also interact right in WebXR. โ #madewithneedle by Katja Rempel ๐
Guiding users towards WebXR-compatible browsers on iOS. Depending on your target audience, you can guide users on iOS towards for example Mozilla's WebXR Viewer to experience AR on iOS.
Using camera access and custom algorithms on iOS devices. One can request camera image access and run custom algorithms to determine device pose. While we currently don't provide built-in components for this, here's a few references to libraries and frameworks that we want to try in the future:
',7))])}const m=d(c,[["render",u],["__file","xr.html.vue"]]),b=JSON.parse(`{"path":"/xr.html","title":"VR & AR (WebXR)","lang":"en-US","frontmatter":{"title":"VR & AR (WebXR)","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/vr & ar.png"}],["meta",{"name":"og:description","content":"---\\nTheoretically all WebXR-capable devices and browsers should be supported. That being said, we've tested the following configurations:"}]],"description":"---\\nTheoretically all WebXR-capable devices and browsers should be supported. That being said, we've tested the following configurations:"},"headers":[{"level":2,"title":"Supported Devices","slug":"supported-devices","link":"#supported-devices","children":[]},{"level":2,"title":"Examples","slug":"examples","link":"#examples","children":[]},{"level":2,"title":"Adding VR and AR capabilities to a scene","slug":"adding-vr-and-ar-capabilities-to-a-scene","link":"#adding-vr-and-ar-capabilities-to-a-scene","children":[{"level":3,"title":"Basic capabilities","slug":"basic-capabilities","link":"#basic-capabilities","children":[]},{"level":3,"title":"Multiplayer","slug":"multiplayer","link":"#multiplayer","children":[]},{"level":3,"title":"Special AR Components","slug":"special-ar-components","link":"#special-ar-components","children":[]},{"level":3,"title":"Controlling object display for XR","slug":"controlling-object-display-for-xr","link":"#controlling-object-display-for-xr","children":[]},{"level":3,"title":"Travelling between VR worlds","slug":"travelling-between-vr-worlds","link":"#travelling-between-vr-worlds","children":[]}]},{"level":2,"title":"Scripting","slug":"scripting","link":"#scripting","children":[]},{"level":2,"title":"Avatars","slug":"avatars","link":"#avatars","children":[{"level":3,"title":"Experimental Avatar Components","slug":"experimental-avatar-components","link":"#experimental-avatar-components","children":[]}]},{"level":2,"title":"HTML Content Overlays in AR","slug":"html-content-overlays-in-ar","link":"#html-content-overlays-in-ar","children":[]},{"level":2,"title":"Image Tracking","slug":"image-tracking","link":"#image-tracking","children":[]},{"level":2,"title":"Augmented Reality and WebXR on iOS","slug":"augmented-reality-and-webxr-on-ios","link":"#augmented-reality-and-webxr-on-ios","children":[{"level":3,"title":"Musical Instrument โ WebXR and QuickLook support","slug":"musical-instrument-webxr-and-quicklook-support","link":"#musical-instrument-webxr-and-quicklook-support","children":[]},{"level":3,"title":"Everywhere Actions and other options for iOS AR","slug":"everywhere-actions-and-other-options-for-ios-ar","link":"#everywhere-actions-and-other-options-for-ios-ar","children":[]}]},{"level":2,"title":"References","slug":"references","link":"#references","children":[]}],"git":{"updatedTime":1726086775000},"filePathRelative":"xr.md"}`);export{m as comp,b as data};
diff --git a/backlog-mermaid.html b/backlog-mermaid.html
new file mode 100644
index 000000000..8b6c8e487
--- /dev/null
+++ b/backlog-mermaid.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+ Needle Engine Documentation
+
+
+
+
+
+
Generated Projects can either be added to source control or kept dynamic. Adding them to source control unlocks being able to adjust HTML, CSS, etc very flexible. To generate dynamic projects, change their path to ../Library/MyScene. They will be regenerated if needed.
Please follow the instructions in the Authentication section if this is your first time accessing packages by needle on this machine.
Make sure you have a Needle Engine and Exporter license, otherwise the following steps will fail (you'll not be able to get authenticated package access).
Needs to be setup once per machine.
Clone this repository and open starter/Authenticate with Unity 2020.3.x
With this add-on you can create highly interactive and optimized WebGL and WebXR experiences inside Blender, that run using Needle Engine and three.js.
You'll be able to sequence animations, easily lightmap your scenes, add interactivity or create your own scripts written in Typescript or Javascript that run on the web.
Matching lighting and environment settings between Blender and Needle Engine. HDRI environment lights are automatically exported, directly from Blender. Once you save, the page is automatically reloaded.
Providing Feedback
Your feedback is invaluable when it comes to deciding which features and workflows we should prioritize. If you have feedback for us (good or bad), please let us know in the forum!
First create or open a new blend file that you want to be exported to the web. Open the Properties window open the scene category. Select a Project Path in the Needle Engine panel. Then click Generate Project. It will automatically install and start the server - once it has finished your browser should open and the threejs scene will load.
By default your scene will automatically re-exported when you save the blend file. If the local server is running (e.g. by clicking Run Project) the website will automatically refresh with your changed model.
When your web project already exists and you just want to continue working on the website click the blue Run Project button to start the local server:
The path to your web project. You can use the little folder button on the right to select a different path.
The Run Project button shows up when the Project path shows to a valid web project. A web project is valid when it contains a package.json
Directory open the directory of your web project (the Project Path)
This button re-exports the current scene as a glb to your local web project. This also happens by default when saving your blend file.
Code Editor tries to open the vscode workspace in your web project
If you work with multiple scenes in one blend file, you can configure which scene is your Main scene and should be exported to the web. If any of your components references another scene they will also be exported as separate glb files. When clicking the "Export" button, your Main scene will be the one that's loaded in the browser.
Use the Build: Development or Build: Production buttons when you want to upload your web project to a server. This will bundle your web project and produce the files that you can upload. When clicking Build: Production it will also apply optimization to your textures (they will be compressed for the web)
By default the blender viewport is set to Filmic - with this setting your colors in Blender and in three.js will not match. To fix this go to the Blender Render category and in the ColorManagement panel select View Transform: Standard
You can change the environment lighting and skybox using the Viewport shading options. Assign a cubemap to use for lighting or the background skybox. You can adjust the strength or blur to modify the appearance to your liking.
Note: To also see the skybox cubemap in the browser increase the World Opacity to 1.
Note: Alternatively you can enable the Scene World setting in the Viewport Shading tab to use the environment texture assigned in the blender world settings.
Alternatively if you don't want to see the cubemap as a background add a Camera component to your Blender Camera and change clearFlags: SolidColor - note that the Camera backgroundBlurriness and backgroundIntensity settings override the Viewport shading settings.
For simple usecases you can use the Animation component for playback of one or multiple animationclips. Just select your object, add an Animation component and assign the clip (you can add additional clips to be exported to the clips array. By default it will only playback the first clip assigned when playAutomatically is enabled. You can trigger the other clips using a simple custom typescript component)
The animator controller can be created for more complex scenarios. It works as a statemachine which allows you to create multiple animation states in a graph and configure conditions and interpolation settings for transitioning between those.
The Parameters node will be created once you add a first node. Select it to setup parameters to be used in transitions (via the Node panel on the right border)
This is an AnimatorState. the orange state is the start state (it can be changed using the Set default state button in the Node/Properties panel)
The Properties for an AnimatorState can be used to setup one or multiple transitions to other states. Use the Conditions array to select parameters that must match the condition for doing the transition.
To use an AnimatorController add an Animator component to the root object of your animations and select the AnimatorController asset that you want to use for this object.
You can set the Animator parameters from typescript or by e.g. using the event of a Button component
You can export Blender NLA tracks directly to the web. Add a PlayableDirector component (via Add Component) to a any blender object. Assign the objects in the animation tracks list in the component for which you want the NLA tracks to be exported.
Code example for interactive timeline playback
Add this script to src/scripts (see custom components section) and add it to any object in Blender to make a timeline's time be controlled by scrolling in the browsers
You can add or remove components to objects in your hierarchy using the Needle Components panel:
For example by adding an OrbitControls component to the camera object you get basic camera controls for mobile and desktop devicesAdjust settings for each component in their respective panels
Components can be removed using the X button in the lower right:
Custom components can also be easily added by simply writing Typescript classes. They will automatically compile and show up in Blender when saved.
To create custom components open the workspace via the Needle Project panel and add a .ts script file in src/scripts inside your web project. Please refer to the scripting documentation to learn how to write custom components for Needle Engine.
Note
Make sure @needle-tools/needle-component-compiler 2.x is installed in your web project (package.json devDependencies)
Needle includes a lightmapping plugin that makes it very easy to bake beautiful lights to textures and bring them to the web. The plugin will automatically generate lightmap UVs for all models marked to be lightmapped, there is no need to make a manual texture atlas. It also supports lightmapping of multiple instances with their own lightmap data. For lightmapping to work, you need at least one light and one object with Lightmapped turned on in the Needle Object panel.
Tips
You can download the .blend file from the video here.
Use the Needle Object panel to enable lightmapping for a mesh object or light:
For quick access to lightmap settings and baking options you can use the scene view panel in the Needle tab:
Alternatively you can also use the Lightmapping panel in the Render Properties tab:
Experimental Feature
The lightmapping plugin is experimental. We recommend creating a backup of your .blend file when using it. Please report problems or errors you encounter in our forum ๐
The Needle Engine Build Pipeline automatically compresses textures using ECT1S and UASTC (depending on their usage in materials) when making a production build (requires toktx being installed). But you can override or change the compression type per texture in the Material panel.
You can modify the compression that is being applied per texture. To override the default compression settings go to the Material tab and open the Needle Material Settings. There you will find a toggle to override the texture settings per texture used in your material. See the texture compression table for a brief overview over the differences between each compression algorithm.
You can also automatically create and upload a bugreport directly from Blender. Uploaded bugreports will solely be used for debugging. They are encrypted on our backend and will be deleted after 30 days.
If needed, in certain cases we're also able to set up custom NDAs for your projects. Please contact us for more information.
Using the Bug Reporter requires a web project
Make sure you've set up a web project before sending a bug report โ it will allow us to understand more about your system and setup and make it easier to reproduce the issue.
+
+
+
diff --git a/blender/lightmapping-object.webp b/blender/lightmapping-object.webp
new file mode 100644
index 000000000..822c765c4
Binary files /dev/null and b/blender/lightmapping-object.webp differ
diff --git a/blender/lightmapping-panel.webp b/blender/lightmapping-panel.webp
new file mode 100644
index 000000000..cc265138c
Binary files /dev/null and b/blender/lightmapping-panel.webp differ
diff --git a/blender/lightmapping-scene-panel.webp b/blender/lightmapping-scene-panel.webp
new file mode 100644
index 000000000..a6f145989
Binary files /dev/null and b/blender/lightmapping-scene-panel.webp differ
diff --git a/blender/lightmapping.mp4 b/blender/lightmapping.mp4
new file mode 100644
index 000000000..a25642d63
Binary files /dev/null and b/blender/lightmapping.mp4 differ
diff --git a/blender/logo.png b/blender/logo.png
new file mode 100644
index 000000000..8f04e2756
Binary files /dev/null and b/blender/logo.png differ
diff --git a/blender/object-panels.webp b/blender/object-panels.webp
new file mode 100644
index 000000000..63a533f26
Binary files /dev/null and b/blender/object-panels.webp differ
diff --git a/blender/project-panel-2.webp b/blender/project-panel-2.webp
new file mode 100644
index 000000000..8a8745c48
Binary files /dev/null and b/blender/project-panel-2.webp differ
diff --git a/blender/project-panel-3.webp b/blender/project-panel-3.webp
new file mode 100644
index 000000000..e1fe1c80f
Binary files /dev/null and b/blender/project-panel-3.webp differ
diff --git a/blender/project-panel.webp b/blender/project-panel.webp
new file mode 100644
index 000000000..768f411d0
Binary files /dev/null and b/blender/project-panel.webp differ
diff --git a/blender/remove-component.webp b/blender/remove-component.webp
new file mode 100644
index 000000000..58191b706
Binary files /dev/null and b/blender/remove-component.webp differ
diff --git a/blender/settings-color-management.webp b/blender/settings-color-management.webp
new file mode 100644
index 000000000..3cb381249
Binary files /dev/null and b/blender/settings-color-management.webp differ
diff --git a/blender/settings.webp b/blender/settings.webp
new file mode 100644
index 000000000..3e7166b68
Binary files /dev/null and b/blender/settings.webp differ
diff --git a/blender/texture-compression.webp b/blender/texture-compression.webp
new file mode 100644
index 000000000..7a08a6ed6
Binary files /dev/null and b/blender/texture-compression.webp differ
diff --git a/blender/timeline.webp b/blender/timeline.webp
new file mode 100644
index 000000000..1362d3808
Binary files /dev/null and b/blender/timeline.webp differ
diff --git a/blender/timeline_setup.webp b/blender/timeline_setup.webp
new file mode 100644
index 000000000..6771cd7e8
Binary files /dev/null and b/blender/timeline_setup.webp differ
diff --git a/blender/updates.webp b/blender/updates.webp
new file mode 100644
index 000000000..8dc20e608
Binary files /dev/null and b/blender/updates.webp differ
diff --git a/castle.png b/castle.png
new file mode 100644
index 000000000..8e0f89573
Binary files /dev/null and b/castle.png differ
diff --git a/code-samples/basic-component.ts b/code-samples/basic-component.ts
new file mode 100644
index 000000000..ff1cb9931
--- /dev/null
+++ b/code-samples/basic-component.ts
@@ -0,0 +1,16 @@
+import { Behaviour, serializable } from "@needle-tools/engine"
+import { Object3D } from "three"
+
+export class MyComponent extends Behaviour {
+
+ @serializable(Object3D)
+ myObjectReference?: Object3D;
+
+ start() {
+ console.log("Hello world", this);
+ }
+
+ update() {
+ this.gameObject.rotateY(this.context.time.deltaTime);
+ }
+}
\ No newline at end of file
diff --git a/code-samples/basic-html.html b/code-samples/basic-html.html
new file mode 100644
index 000000000..ff5096f90
--- /dev/null
+++ b/code-samples/basic-html.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+ Made with Needle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code-samples/basic-webcomponent.html b/code-samples/basic-webcomponent.html
new file mode 100644
index 000000000..b6f34df92
--- /dev/null
+++ b/code-samples/basic-webcomponent.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code-samples/component-2d-audio.ts b/code-samples/component-2d-audio.ts
new file mode 100644
index 000000000..bea8ddd81
--- /dev/null
+++ b/code-samples/component-2d-audio.ts
@@ -0,0 +1,22 @@
+import { AudioSource, Behaviour, serializable } from "@needle-tools/engine";
+
+// declaring AudioClip type is for codegen to produce the correct input field (for e.g. Unity or Blender)
+declare type AudioClip = string;
+
+export class My2DAudio extends Behaviour {
+
+ // The clip contains a string pointing to the audio file - by default it's relative to the GLB that contains the component
+ // by adding the URL decorator the clip string will be resolved relative to your project root and can be loaded
+ @serializable(URL)
+ clip?: AudioClip;
+
+ awake() {
+ // creating a new audio element and playing it
+ const audioElement = new Audio(this.clip);
+ audioElement.loop = true;
+ // on the web we have to wait for the user to interact with the page before we can play audio
+ AudioSource.registerWaitForAllowAudio(() => {
+ audioElement.play();
+ })
+ }
+}
\ No newline at end of file
diff --git a/code-samples/component-animation-onclick.ts b/code-samples/component-animation-onclick.ts
new file mode 100644
index 000000000..365d684fe
--- /dev/null
+++ b/code-samples/component-animation-onclick.ts
@@ -0,0 +1,20 @@
+import { Behaviour, serializable, Animation, IPointerClickHandler, PointerEventData } from "@needle-tools/engine";
+
+export class PlayAnimationOnClick extends Behaviour implements IPointerClickHandler {
+
+ @serializable(Animation)
+ animation?: Animation;
+
+ awake() {
+ if (this.animation) {
+ this.animation.playAutomatically = false;
+ this.animation.loop = false;
+ }
+ }
+
+ onPointerClick(_args: PointerEventData) {
+ if (this.animation) {
+ this.animation.play();
+ }
+ }
+}
\ No newline at end of file
diff --git a/code-samples/component-animationclip.ts b/code-samples/component-animationclip.ts
new file mode 100644
index 000000000..54be3cb12
--- /dev/null
+++ b/code-samples/component-animationclip.ts
@@ -0,0 +1,12 @@
+import { Behaviour, serializable } from "@needle-tools/engine";
+import { AnimationClip } from "three"
+
+export class ExportAnimationClip extends Behaviour {
+
+ @serializable(AnimationClip)
+ animation?: AnimationClip;
+
+ awake() {
+ console.log("My referenced animation clip", this.animation);
+ }
+}
\ No newline at end of file
diff --git a/code-samples/component-click-networking.ts b/code-samples/component-click-networking.ts
new file mode 100644
index 000000000..5ca54dd62
--- /dev/null
+++ b/code-samples/component-click-networking.ts
@@ -0,0 +1,27 @@
+import { Behaviour, EventList, IPointerClickHandler, PointerEventData, serializable } from "@needle-tools/engine";
+
+export class SyncedClick extends Behaviour implements IPointerClickHandler {
+
+ @serializable(EventList)
+ onClick!: EventList;
+
+ onPointerClick(_args: PointerEventData) {
+ console.log("SEND CLICK");
+ this.context.connection.send("clicked/" + this.guid);
+ this.onClick?.invoke();
+ }
+
+ onEnable(): void {
+ this.context.connection.beginListen("clicked/" + this.guid, this.onRemoteClick);
+ }
+ onDisable(): void {
+ this.context.connection.stopListen("clicked/" + this.guid, this.onRemoteClick);
+ }
+
+
+ onRemoteClick = () => {
+ console.log("RECEIVED CLICK");
+ this.onClick?.invoke();
+ }
+
+}
\ No newline at end of file
diff --git a/code-samples/component-click.ts b/code-samples/component-click.ts
new file mode 100644
index 000000000..d90e185cd
--- /dev/null
+++ b/code-samples/component-click.ts
@@ -0,0 +1,9 @@
+import { Behaviour, IPointerClickHandler, PointerEventData, showBalloonMessage } from "@needle-tools/engine";
+
+export class ClickExample extends Behaviour implements IPointerClickHandler {
+
+ // Make sure to have an ObjectRaycaster component in the parent hierarchy
+ onPointerClick(_args: PointerEventData) {
+ showBalloonMessage("Clicked " + this.name);
+ }
+}
diff --git a/code-samples/component-customevent.ts b/code-samples/component-customevent.ts
new file mode 100644
index 000000000..b6bb933bf
--- /dev/null
+++ b/code-samples/component-customevent.ts
@@ -0,0 +1,42 @@
+import { Behaviour, serializable, EventList } from "@needle-tools/engine";
+import { Object3D } from "three";
+
+/*
+Make sure to have a c# file in your project with the following content:
+
+using UnityEngine;
+using UnityEngine.Events;
+
+[System.Serializable]
+public class MyCustomUnityEvent : UnityEvent
+{
+}
+
+Unity documentation about custom events:
+https://docs.unity3d.com/ScriptReference/Events.UnityEvent_2.html
+
+*/
+
+// Documentation โ https://docs.needle.tools/scripting
+
+export class CustomEventCaller extends Behaviour {
+
+ // The next line is not just a comment, it defines
+ // a specific type for the component generator to use.
+
+ //@type MyCustomUnityEvent
+ @serializable(EventList)
+ myEvent!: EventList;
+
+ // just for testing - could be when a button is clicked, etc.
+ start() {
+ this.myEvent.invoke("Hello");
+ }
+}
+
+export class CustomEventReceiver extends Behaviour {
+
+ logStringAndObject(str: string) {
+ console.log("From Event: ", str);
+ }
+}
\ No newline at end of file
diff --git a/code-samples/component-customshaderproperty.ts b/code-samples/component-customshaderproperty.ts
new file mode 100644
index 000000000..949e304d8
--- /dev/null
+++ b/code-samples/component-customshaderproperty.ts
@@ -0,0 +1,20 @@
+import { Behaviour, serializable } from "@needle-tools/engine";
+import { Material } from "three";
+
+declare type MyCustomShaderMaterial = Material & {
+ _Speed: number;
+};
+
+export class IncreaseShaderSpeedOverTime extends Behaviour {
+
+ //@type UnityEngine.Material
+ @serializable(Material)
+ myMaterial?: MyCustomShaderMaterial;
+
+ update() {
+ if (this.myMaterial) {
+ this.myMaterial._Speed *= 1 + this.context.time.deltaTime;
+ if(this.myMaterial._Speed > 1) this.myMaterial._Speed = .0005;
+ }
+ }
+}
\ No newline at end of file
diff --git a/code-samples/component-everywhere-action-hideonstart.ts b/code-samples/component-everywhere-action-hideonstart.ts
new file mode 100644
index 000000000..9a90a2076
--- /dev/null
+++ b/code-samples/component-everywhere-action-hideonstart.ts
@@ -0,0 +1,24 @@
+import { Behaviour, UsdzBehaviour, BehaviorModel, TriggerBuilder, ActionBuilder, BehaviorExtension, USDObject, USDZExporterContext } from "@needle-tools/engine";
+
+export class HideOnStart extends Behaviour implements UsdzBehaviour {
+
+ start() {
+ this.gameObject.visible = false;
+ }
+
+ createBehaviours(ext: BehaviorExtension, model: USDObject, _context: USDZExporterContext) {
+ if (model.uuid === this.gameObject.uuid)
+ ext.addBehavior(new BehaviorModel("HideOnStart_" + this.gameObject.name,
+ TriggerBuilder.sceneStartTrigger(),
+ ActionBuilder.fadeAction(model, 0, false)
+ ));
+ }
+
+ beforeCreateDocument() {
+ this.gameObject.visible = true;
+ }
+
+ afterCreateDocument() {
+ this.gameObject.visible = false;
+ }
+}
\ No newline at end of file
diff --git a/code-samples/component-filereference.ts b/code-samples/component-filereference.ts
new file mode 100644
index 000000000..387ba54eb
--- /dev/null
+++ b/code-samples/component-filereference.ts
@@ -0,0 +1,20 @@
+import { Behaviour, FileReference, ImageReference, serializable } from "@needle-tools/engine";
+
+export class FileReferenceExample extends Behaviour {
+
+ // A FileReference can be used to load and assign arbitrary data in the editor. You can use it to load images, audio, text files... FileReference types will not be saved inside as part of the GLB (the GLB will only contain a relative URL to the file)
+ @serializable(FileReference)
+ myFile?: FileReference;
+ // Tip: if you want to export and load an image (that is not part of your GLB) if you intent to add it to your HTML content for example you can use the ImageReference type instead of FileReference. It will be loaded as an image and you can use it as a source for an tag.
+
+ async start() {
+ console.log("This is my file: ", this.myFile);
+ // load the file
+ const data = await this.myFile?.loadRaw();
+ if (!data) {
+ console.error("Failed loading my file...");
+ return;
+ }
+ console.log("Loaded my file. These are the bytes:", await data.arrayBuffer());
+ }
+}
\ No newline at end of file
diff --git a/code-samples/component-location.ts b/code-samples/component-location.ts
new file mode 100644
index 000000000..bb95f392b
--- /dev/null
+++ b/code-samples/component-location.ts
@@ -0,0 +1,11 @@
+import { Behaviour, showBalloonMessage } from "@needle-tools/engine";
+
+export class WhereAmI extends Behaviour {
+ start() {
+ navigator.geolocation.getCurrentPosition((position) => {
+ console.log("Navigator response:", position);
+ const latlong = position.coords.latitude + ", " + position.coords.longitude;
+ showBalloonMessage("You are at\nLatLong " + latlong);
+ });
+ }
+}
\ No newline at end of file
diff --git a/code-samples/component-nested-serialization-cs.cs b/code-samples/component-nested-serialization-cs.cs
new file mode 100644
index 000000000..445a11183
--- /dev/null
+++ b/code-samples/component-nested-serialization-cs.cs
@@ -0,0 +1,17 @@
+using System;
+
+[Serializable]
+public class CustomSubData
+{
+ public string subString;
+ public float subNumber;
+}
+
+[Serializable]
+public class CustomData
+{
+ public string myStringField;
+ public float myNumberField;
+ public bool myBooleanField;
+ public CustomSubData subData;
+}
\ No newline at end of file
diff --git a/code-samples/component-nested-serialization.ts b/code-samples/component-nested-serialization.ts
new file mode 100644
index 000000000..2a3a4473c
--- /dev/null
+++ b/code-samples/component-nested-serialization.ts
@@ -0,0 +1,40 @@
+import { Behaviour, serializable } from "@needle-tools/engine";
+
+// Documentation โ https://docs.needle.tools/scripting
+
+class CustomSubData {
+ @serializable()
+ subString: string = "";
+
+ @serializable()
+ subNumber: number = 0;
+}
+
+class CustomData {
+ @serializable()
+ myStringField: string = "";
+
+ @serializable()
+ myNumberField: number = 0;
+
+ @serializable()
+ myBooleanField: boolean = false;
+
+ @serializable(CustomSubData)
+ subData: CustomSubData | undefined = undefined;
+
+ someMethod() {
+ console.log("My string is " + this.myStringField, "my sub data", this.subData)
+ }
+}
+
+export class SerializedDataSample extends Behaviour {
+
+ @serializable(CustomData)
+ myData: CustomData | undefined;
+
+ onEnable() {
+ console.log(this.myData);
+ this.myData?.someMethod();
+ }
+}
\ No newline at end of file
diff --git a/code-samples/component-object-reference.ts b/code-samples/component-object-reference.ts
new file mode 100644
index 000000000..f3a94e49c
--- /dev/null
+++ b/code-samples/component-object-reference.ts
@@ -0,0 +1,13 @@
+import { Behaviour, serializable } from "@needle-tools/engine";
+import { Object3D } from "three"
+
+export class MyClass extends Behaviour {
+ // this will be a "Transform" field in Unity
+ @serializable(Object3D)
+ myObjectReference: Object3D | null = null;
+
+ // this will be a "Transform" array field in Unity
+ // Note that the @serializable decorator contains the array content type! (Object3D and not Object3D[])
+ @serializable(Object3D)
+ myObjectReferenceList: Object3D[] | null = null;
+}
\ No newline at end of file
diff --git a/code-samples/component-prefab.ts b/code-samples/component-prefab.ts
new file mode 100644
index 000000000..73f5551ee
--- /dev/null
+++ b/code-samples/component-prefab.ts
@@ -0,0 +1,20 @@
+import { Behaviour, serializable, AssetReference } from "@needle-tools/engine";
+
+export class MyClass extends Behaviour {
+
+ // if you export a prefab or scene as a reference from Unity you'll get a path to that asset
+ // which you can de-serialize to AssetReference for convenient loading
+ @serializable(AssetReference)
+ myPrefab?: AssetReference;
+
+ async start() {
+ // directly instantiate
+ const myInstance = await this.myPrefab?.instantiate();
+
+ // you can also just load and instantiate later
+ // const myInstance = await this.myPrefab.loadAssetAsync();
+ // this.gameObject.add(myInstance)
+ // this is useful if you know that you want to load this asset only once because it will not create a copy
+ // since ``instantiate()`` does create a copy of the asset after loading it
+ }
+}
\ No newline at end of file
diff --git a/code-samples/component-scene.ts b/code-samples/component-scene.ts
new file mode 100644
index 000000000..38fdfa048
--- /dev/null
+++ b/code-samples/component-scene.ts
@@ -0,0 +1,34 @@
+import { Behaviour, serializable, AssetReference } from "@needle-tools/engine";
+
+export class LoadingScenes extends Behaviour {
+ // tell the component compiler that we want to reference an array of SceneAssets
+ // @type UnityEditor.SceneAsset[]
+ @serializable(AssetReference)
+ myScenes?: AssetReference[];
+
+ async awake() {
+ if (!this.myScenes) {
+ return;
+ }
+ for (const scene of this.myScenes) {
+ // check if it is assigned in unity
+ if(!scene) continue;
+ // load the scene once
+ const myScene = await scene.loadAssetAsync();
+ // add it to the threejs scene
+ this.gameObject.add(myScene);
+
+ // of course you can always just load one at a time
+ // and remove it from the scene when you want
+ // myScene.removeFromParent();
+ // this is the same as scene.asset.removeFromParent()
+ }
+ }
+
+ onDestroy(): void {
+ if (!this.myScenes) return;
+ for (const scene of this.myScenes) {
+ scene?.unload();
+ }
+ }
+}
\ No newline at end of file
diff --git a/code-samples/component-time.ts b/code-samples/component-time.ts
new file mode 100644
index 000000000..9c7e8431a
--- /dev/null
+++ b/code-samples/component-time.ts
@@ -0,0 +1,21 @@
+import { Behaviour, Text, serializable, WaitForSeconds } from "@needle-tools/engine";
+
+export class DisplayTime extends Behaviour {
+
+ @serializable(Text)
+ text?: Text;
+
+ onEnable(): void {
+ this.startCoroutine(this.updateTime())
+ }
+
+ private *updateTime() {
+ while (true) {
+ if (this.text) {
+ this.text.text = new Date().toLocaleTimeString();
+ console.log(this.text.text)
+ }
+ yield WaitForSeconds(1)
+ }
+ };
+}
\ No newline at end of file
diff --git a/code-samples/component-unityevent.ts b/code-samples/component-unityevent.ts
new file mode 100644
index 000000000..3f326a790
--- /dev/null
+++ b/code-samples/component-unityevent.ts
@@ -0,0 +1,11 @@
+import { Behaviour, serializable, EventList } from "@needle-tools/engine"
+
+export class MyComponent extends Behaviour {
+
+ @serializable(EventList)
+ myEvent? : EventList;
+
+ start() {
+ this.myEvent?.invoke();
+ }
+}
\ No newline at end of file
diff --git a/code-samples/custom-particle-system-behaviour.ts b/code-samples/custom-particle-system-behaviour.ts
new file mode 100644
index 000000000..f12b58eb0
--- /dev/null
+++ b/code-samples/custom-particle-system-behaviour.ts
@@ -0,0 +1,17 @@
+import { Behaviour, ParticleSystem } from "@needle-tools/engine";
+import { ParticleSystemBaseBehaviour, QParticle } from "@needle-tools/engine";
+
+// Derive your custom behaviour from the ParticleSystemBaseBehaviour class (or use QParticleBehaviour)
+class MyParticlesBehaviour extends ParticleSystemBaseBehaviour {
+
+ // callback invoked per particle
+ update(particle: QParticle): void {
+ particle.position.y += 5 * this.context.time.deltaTime;
+ }
+}
+export class TestCustomParticleSystemBehaviour extends Behaviour {
+ start() {
+ // add your custom behaviour to the particle system
+ this.gameObject.getComponent(ParticleSystem)!.addBehaviour(new MyParticlesBehaviour())
+ }
+}
diff --git a/code-samples/custom-post-effect.ts b/code-samples/custom-post-effect.ts
new file mode 100644
index 000000000..3d6c6cfc5
--- /dev/null
+++ b/code-samples/custom-post-effect.ts
@@ -0,0 +1,44 @@
+import { EffectProviderResult, PostProcessingEffect, registerCustomEffectType, serializable } from "@needle-tools/engine";
+import { OutlineEffect } from "postprocessing";
+import { Object3D } from "three";
+
+export class OutlinePostEffect extends PostProcessingEffect {
+
+ // the outline effect takes a list of objects to outline
+ @serializable(Object3D)
+ selection!: Object3D[];
+
+ // this is just an example method that you could call to update the outline effect selection
+ updateSelection() {
+ if (this._outlineEffect) {
+ this._outlineEffect.selection.clear();
+ for (const obj of this.selection) {
+ this._outlineEffect.selection.add(obj);
+ }
+ }
+ }
+
+
+ // a unique name is required for custom effects
+ get typeName(): string {
+ return "Outline";
+ }
+
+ private _outlineEffect: void | undefined | OutlineEffect;
+
+ // method that creates the effect once
+ onCreateEffect(): EffectProviderResult | undefined {
+
+ const outlineEffect = new OutlineEffect(this.context.scene, this.context.mainCamera!);
+ this._outlineEffect = outlineEffect;
+ outlineEffect.edgeStrength = 10;
+ outlineEffect.visibleEdgeColor.set(0xff0000);
+ for (const obj of this.selection) {
+ outlineEffect.selection.add(obj);
+ }
+
+ return outlineEffect;
+ }
+}
+// You need to register your effect type with the engine
+registerCustomEffectType("Outline", OutlinePostEffect);
\ No newline at end of file
diff --git a/community/contributions.html b/community/contributions.html
new file mode 100644
index 000000000..2e1728985
--- /dev/null
+++ b/community/contributions.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+ Needle Engine Documentation
+
+
+
+
+
+
When working in Unity or Blender then you will notice that when you create a new Needle Engine component in Typescript or Javascript it will automatically generate a Unity C# stub component OR a Blender panel for you.
This is thanks to the magic of the Needle component compiler that runs behind the scenes in an editor environment and watches changes to your script files. When it notices that you created a new Needle Engine component it will then generate the correct Unity component or Blender panel including public variables or properties that you can then set or link from within the Editor.
If you want to add scripts inside the src/scripts folder in your project then you need to have a Component Generator on the GameObject with your ExportInfo component. Now when adding new components in your/threejs/project/src/scriptsit will automatically generate Unity scripts in Assets/Needle/Components.codegen. If you want to add scripts to any NpmDef file you can just create them - each NpmDef automatically watches script changes and handles component generation, so you don't need any additional component in your scene.
For C# fields to be correctly generated it is currently important that you explictly declare a Typescript type. For example myField : number = 5
You can switch between Typescript input and generated C# stub components using the tabs below
// NEEDLE_CODEGEN_START
+// auto generated code - do not edit directly
+
+#pragma warning disable
+
+namespace Needle.Typescript.GeneratedComponents
+{
+ public partial class MyCustomComponent : UnityEngine.MonoBehaviour
+ {
+ public float @myFloatValue = 42f;
+ public UnityEngine.Transform @myOtherObject;
+ public UnityEngine.Transform[] @prefabs = new UnityEngine.Transform[]{ };
+ public void start(){}
+ public void update(){}
+ }
+}
+
+// NEEDLE_CODEGEN_END
using UnityEditor;
+
+// you can add code above or below the NEEDLE_CODEGEN_ blocks
+
+// NEEDLE_CODEGEN_START
+// auto generated code - do not edit directly
+
+#pragma warning disable
+
+namespace Needle.Typescript.GeneratedComponents
+{
+ public partial class MyCustomComponent : UnityEngine.MonoBehaviour
+ {
+ public float @myFloatValue = 42f;
+ public UnityEngine.Transform @myOtherObject;
+ public UnityEngine.Transform[] @prefabs = new UnityEngine.Transform[]{ };
+ public void start(){}
+ public void update(){}
+ }
+}
+
+// NEEDLE_CODEGEN_END
+
+namespace Needle.Typescript.GeneratedComponents
+{
+ // This is how you extend the generated component (namespace and class name must match!)
+ public partial class MyCustomComponent : UnityEngine.MonoBehaviour
+ {
+
+ public void MyAdditionalMethod()
+ {
+ }
+
+ private void OnValidate()
+ {
+ myFloatValue = 42;
+ }
+ }
+
+ // of course you can also add custom editors
+ [CustomEditor(typeof(MyCustomComponent))]
+ public class MyCustomComponentEditor : Editor
+ {
+ public override void OnInspectorGUI()
+ {
+ EditorGUILayout.HelpBox("This is my sample component", MessageType.None);
+ base.OnInspectorGUI();
+ }
+ }
+}
Component C# classes are generated with the partial flag so that it is easy to extend them with functionality. This is helpful to draw gizmos, add context menus or add additional fields or methods that are not part of a built-in component.
Member Casing
Exported members will start with a lowercase letter. For example if your C# member is named MyString it will be assigned to myString.
Here is a overview of some of the components that we provide. Many of them map to components and functionality in Unity, Blender or other integrations.
For a complete list please have a look at our API docs.
You can always add your own components or add wrappers for Unity components we haven't provided yet.
DirectionalLight, PointLight, Spotlight. Note that you can use it to bake light (e.g. Rectangular Light shapes) as well
XRFlag
Control when objects will be visible. E.g. only enable object when in AR
DeviceFlag
Control on which device objects will be visible
LODGroup
ParticleSystem
Experimental and currently not fully supported
VideoPlayer
Playback videos from url or referenced video file (will be copied to output on export). The VideoPlayer also supports streaming from MediaStream objects or M3U8 livestream URLs
MeshRenderer
Used to handle rendering of objects including lightmapping and instancing
Postprocessing effects use the pmndrs postprocessing library under the hood. This means you can also easily add your own custom effects and get an automatically optimized postprocessing pass.
Unity only: Note that Postprocessing effects using a Volume in Unity is only supported with URP
Spatial UI components are mapped from Unity UI (Canvas, not UI Toolkit) to three-mesh-ui. UI can be animated.
Name
Description
Canvas
Unity's UI system. Needs to be in World Space mode right now.
Text (Legacy)
Render Text using Unity's UI Text component. Custom fonts are supported, a font atlas will be automatically generated on export. Use the font settings to control which characters are included in the atlas. Note: In Unity make sure to use the Legacy/Text component (TextMeshPro is not supported at the moment)
Button
Receives click events - use the onClick event to react to it. It can be added too 3D scene objects as well. Note: Make sure to use the Legacy/Text component in the Button (or create the Button via the UI/Legacy/Button Unity context menu since TextMeshPro is not supported at the moment)
Image
Renders a sprite image
RawImage
Renders a texture
InputField
Allows text input
Note: Depending on your project, often a mix of spatial and 2D UI makes sense for cross-platform projects where VR, AR, and screens are supported. Typically, you'd build the 2D parts with HTML for best accessibility, and the 3D parts with geometric UIs that also support depth offsets (e.g. button hover states and the like).
Handles loading and unloading of other scenes or prefabs / glTF files. Has features to preload, change scenes via swiping, keyboard events or URL navigation
Needle Engine also has some very powerful and useful debugging methods that are part of the static Gizmos class. See the scripting documentation for more information.
If you have changed the port on which your server starts make sure to update the url field accordingly. You can then start your local server from within VSCode:
For Android debugging, you can attach Chrome Dev Tools to your device and see logs right from your PC. You have to switch your device into development mode and connect it via USB.
Deployment is the process of making your application available to the public on a website. Needle Engine ensures that your project is as small and fast as possible by using the latest compression techniques such as KTX2, Draco, and Meshopt.
See guides above on how to access the options from within your Editor (e.g. Unity or Blender).
The main difference to a production build is that it does not perform ktx2 and draco compression (for reduction of file size and loading speed) as well as the option to progressively load high-quality textures.
We generally recommend making production builds for optimized file size and loading speed (see more information below).
To make a production build, you need to have toktx installed, which provides texture compression using the KTX2 supercompression format. Please go to the toktx Releases Page and download and install the latest version (v4.1.0 at the time of writing). You may need to restart Unity after installing it. If you're sure that you have installed toktx and it's part of your PATH but still can't be found, please restart your machine and try build again.
Advanced: Custom glTF extensions
If you plan on adding your own custom glTF extensions, building for production requires handling those in gltf-transform. See @needle-tools/gltf-build-pipeline for reference.
Production builds will by default compress textures using KTX2 (either ETC1S or UASTC depending on their usage in the project) but you can also select WebP compression and select a quality level.
High-detail data textures: normal maps, roughness, metallic, etc.
Files where ETC1S quality is not sufficient but UASTC is too large
You have the option to select texture compression and progressive loading options per Texture by using the Needle Texture Importer in Unity or in the Material tab in Blender.
Unity: How can I set per-texture compression settings?
Blender: How can I set per-texture compression settings?
Select the material tab. You will see compression options for all textures that are being used by that material.
Toktx can not be found
Windows: Make sure you have added toktx to your system environment variables. You may need to restart your computer after adding it to refresh the environment variables. The default install location is C:\Program Files\KTX-Software\bin
By default, a production build will compress meshes using Draco compression. Use the MeshCompression component to select between draco and mesh-opt per exported glTF. Additionally you can setup mesh simplification to reduce the polycount for production builds in the mesh import settings (Unity). When viewing your application in the browser, you can append ?wireframe to your URL to preview the meshes.
You can also add the Progressive Texture Settings component anywhere in your scene, to make all textures in your project be progressively loaded. Progressive loading is not applied to lightmaps or skybox textures at this point.
With progressive loading textures will first be loaded using a lower resolution version. A full quality version will be loaded dynamically when the texture becomes visible. This usually reduces initial loading of your scene significantly.
Since Needle Engine 3.36 we automatically generate LOD meshes and switch between them at runtime. LODs are loaded on demand and only when needed so so this feature both reduces your loading time as well as performance.
Key Beneftis
Faster initial loading time
Faster rendering time due to less vertices on screen on average
Faster raycasting due to the use of LOD meshes
You can either disable LOD generation for your whole project in the Progressive Loading Settings component or in the Mesh Importer settings.
Glitch provides a fast and free way for everyone to host small and large websites. We're providing an easy way to remix and deploy to a new Glitch page (based on our starter), and also to run a minimalistic networking server on the same Glitch page if needed.
You can deploy to glitch by adding the DeployToGlitch component to your scene and following the instructions.
Note that free projects hosted on glitch may not exceed ~100 MB. If you need to upload a larger project consider using a different deployment target.
How do I deploy to Glitch from Unity?
Add the DeployToGlitch component to the GameObject that also has the ExportInfo component.
Click the Create new Glitch Remix button on the component
Glitch will now create a remix of the template. Copy the URL from your browser
Open Unity again and paste the URL in the Project Name field of your Deploy To Glitch component
Wait a few seconds until Unity has received your deployment key from glitch (this key is safely stored in the .env file on glitch. Do not share it with others, everyone with this key will be able to upload to your glitch website)
Once the Deploy Key has been received you can click the Build & Deploy button to upload to glitch.
How do I deploy to Glitch from Blender?
Find the Deploy To Glitch panel in the Scene tab
Click the Remix on glitch button on the component
Your browser will open the glitch project template
Wait for Glitch to generate a new project
Copy paste the project URL in the Blender DeployToGlitch panel as the project name (you can paste the full URL, the panel will extract the necessary information)
On Glitch open the .env file and enter a password in the field Variable Value next to the DEPLOY_KEY
Enter the same password in Blender in the Key field
Click the DeployToGlitch button to build and upload your project to glitch. A browser will open when the upload has finished. Try to refresh the page if it shows black after having opened it.
If you click Create new Glitch Remix and the browser shows an error like there was an error starting the editor you can click OK. Then go to glitch.com and make sure you are signed in. After that you then try clicking the button again in Unity or Blender.
Just add the DeployToNetlify component to your scene and follow the instructions. You can create new projects with the click of a button or by deploying to existing projects.
If you see this error after uploading your project make sure you do not upload a gzipped index.html. You can disable gzip compression in vite.config.js in your Needle web project folder. Just remove the line with viteCompression({ deleteOriginFile: true }). The build your project again and upload to itch.
Add the DeployToFTP componentยน on a GameObject in your scene (it is good practice to add it to the same GameObject as ExportInfo - but it is not mandatory)
Assign an FTP server asset and fill out server, username, and password if you have not already ยฒ This asset contains the access information to your FTP server - you get them when you create a new FTP account at your hosting provider
Click the Build & Deploy button on the DeployToFTP component to build your project and uploading it to your FTP account
ยน Deploy to FTP component
ยฒ FTP Server asset containing the access information of your FTP user account
Deploy To FTP component after server asset is assigned. You can directly deploy to a subfolder on your server using the path field
How do I deploy to my FTP server manually?
Open File > Build Settings, select Needle Engine, and click on Build
Wait for the build to complete - the resulting dist folder will open automatically after all build and compression steps have run.
Copy the files from the dist folder to your FTP storage.
That's it! ๐
Note: If the result doesn't work when uploaded it might be that your web server does not support serving gzipped files. You have two options to fix the problem: Option 1: You can try enabling gzip compression on your server using a htaccess file! Option 2: You can turn gzip compression off in the build settings at File/Build Window and selecting the Needle Engine platform.
Note: If you're getting errors during compression, please let us know and report a bug! If your project works locally and only fails when doing production builds, you can get unstuck right away by doing a Development Build. For that, simply toggle Development Build on in the Build Settings.
To enable gzip compression on your FTP server you can create a file named .htaccess in the directory you want to upload to (or a parent directory). Insert the following code into your .htaccess file and save/upload it to your server:
With Needle Engine you can build to Facebook Instant Games automatically No manual adjustments to your web app or game are required.
How do I deploy to Facebook Instant Games from Unity?
Add the Deploy To Facebook Instant Games component to your scene:
Click the Build For Instant Games button
After the build has finished you will get a ZIP file that you can upload to your facebook app.
On Facebook add the Instant Games module and go to Instant Games/Web hosting
You can upload your zip using the Upload version button (1). After the upload has finished and the zip has been processed click the Stage for testing button to test your app (2, here the blue button) or Push to production (the button with the star icon)
That's it - you can then click the Play button next to each version to test your game on facebook.
How do I create a app on Facebook (with Instant Games capabilities)
After creating the app add the Instant Games product
Here you can find the official instant games documentation on facebook. Note that all you have to do is to create an app with instant games capabilities. We will take care of everything else and no manual adjustments to your Needle Engine website are required.
In Unity open File/Build Settings and select Needle Engine for options:
To build your web project for uploading to any web server you can click Build in the Unity Editor Build Settings Window. You can enable the Development Build checkbox to omit compression (see below) which requires toktx to be installed on your machine.
To locally preview your final build you can use the Preview Build button at the bottom of the window. This button will first perform a regular build and then start a local server in the directory with the final files so you can see what you get once you upload these files to your webserver.
Nodejs is only required during development. The distributed website (using our default vite template) is a static page that doesn't rely on Nodejs and can be put on any regular web server. Nodejs is required if you want to run our minimalistic networking server on the same web server (automatically contained in the Glitch deployment process).
It's possible to create regular Unity projects where you can build both to Needle Engine and to regular Unity platforms such as Desktop or even WebGL. Our "component mapping" approach means that no runtime logic is modified inside Unity - if you want you can regularily use Play Mode and build to other target platforms. In some cases this will mean that you have duplicate code (C# code and matching TypeScript logic). The amount of extra work through this depends on your project.
Enter Play Mode in Unity In Project Settings > Needle Engine, you can turn off Override Play Mode and Override Build settings to switch between Needle's build process and Unity's build process:
Needle Engine for Unity supports various commandline arguments to export single assets (Prefabs or Scenes) or to build a whole web project in batch mode (windowsless).
The following list gives a table over the available options:
-scene
path to a scene or a asset to be exported e.g. Assets/path/to/myObject.prefab or Assets/path/to/myScene.unity
-outputPath <path/to/output.glb>
set the output path for the build (only valid when building a scene)
-buildProduction
run a production build
-buildDevelopment
run a development build
-debug
open a console window for debugging
+
+
+
diff --git a/deployment/buildoptions_gzip.jpg b/deployment/buildoptions_gzip.jpg
new file mode 100644
index 000000000..0472b9701
Binary files /dev/null and b/deployment/buildoptions_gzip.jpg differ
diff --git a/deployment/deploytofacebookinstantgames-hosting.jpg b/deployment/deploytofacebookinstantgames-hosting.jpg
new file mode 100644
index 000000000..63ef163c7
Binary files /dev/null and b/deployment/deploytofacebookinstantgames-hosting.jpg differ
diff --git a/deployment/deploytofacebookinstantgames-upload.jpg b/deployment/deploytofacebookinstantgames-upload.jpg
new file mode 100644
index 000000000..826fd6e5c
Binary files /dev/null and b/deployment/deploytofacebookinstantgames-upload.jpg differ
diff --git a/deployment/deploytofacebookinstantgames.jpg b/deployment/deploytofacebookinstantgames.jpg
new file mode 100644
index 000000000..d0dfb4107
Binary files /dev/null and b/deployment/deploytofacebookinstantgames.jpg differ
diff --git a/deployment/deploytoftp.jpg b/deployment/deploytoftp.jpg
new file mode 100644
index 000000000..85ae4b87f
Binary files /dev/null and b/deployment/deploytoftp.jpg differ
diff --git a/deployment/deploytoftp2.jpg b/deployment/deploytoftp2.jpg
new file mode 100644
index 000000000..16d1173de
Binary files /dev/null and b/deployment/deploytoftp2.jpg differ
diff --git a/deployment/deploytoftp3.jpg b/deployment/deploytoftp3.jpg
new file mode 100644
index 000000000..cb7a1f242
Binary files /dev/null and b/deployment/deploytoftp3.jpg differ
diff --git a/deployment/deploytogithubpages.jpg b/deployment/deploytogithubpages.jpg
new file mode 100644
index 000000000..19978c16b
Binary files /dev/null and b/deployment/deploytogithubpages.jpg differ
diff --git a/deployment/deploytoglitch-1.jpg b/deployment/deploytoglitch-1.jpg
new file mode 100644
index 000000000..c45bcd0ee
Binary files /dev/null and b/deployment/deploytoglitch-1.jpg differ
diff --git a/deployment/deploytoglitch-2.jpg b/deployment/deploytoglitch-2.jpg
new file mode 100644
index 000000000..35c3e40d6
Binary files /dev/null and b/deployment/deploytoglitch-2.jpg differ
diff --git a/deployment/deploytonetlify-2.jpg b/deployment/deploytonetlify-2.jpg
new file mode 100644
index 000000000..30f80f9ab
Binary files /dev/null and b/deployment/deploytonetlify-2.jpg differ
diff --git a/deployment/deploytonetlify.jpg b/deployment/deploytonetlify.jpg
new file mode 100644
index 000000000..e56f2ed5c
Binary files /dev/null and b/deployment/deploytonetlify.jpg differ
diff --git a/deployment/facebookinstantgames-1.jpg b/deployment/facebookinstantgames-1.jpg
new file mode 100644
index 000000000..7e7ebdc6e
Binary files /dev/null and b/deployment/facebookinstantgames-1.jpg differ
diff --git a/deployment/facebookinstantgames-2.jpg b/deployment/facebookinstantgames-2.jpg
new file mode 100644
index 000000000..122a3dbb0
Binary files /dev/null and b/deployment/facebookinstantgames-2.jpg differ
diff --git a/deployment/facebookinstantgames-3.jpg b/deployment/facebookinstantgames-3.jpg
new file mode 100644
index 000000000..4157da4d6
Binary files /dev/null and b/deployment/facebookinstantgames-3.jpg differ
diff --git a/embedding.html b/embedding.html
new file mode 100644
index 000000000..1cd5a5745
--- /dev/null
+++ b/embedding.html
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+ Needle Engine on your Website | Needle Engine Documentation
+
+
+
+
+
+
Needle Engine can be used to create new web apps, and can also be integrated into existing websites. In both cases, you'll want to upload your project's distribution folder to a web hoster to make them accessible to the world.
There are several ways to integrate Needle Engine with your website. Which one is better depends on a number of factors, like complexity of your project, if you're using custom scripts or only core components, how much control you have over the target website, what the "trust level" is between you and the target website, and so on.
Our Needle Engine integrations ship with built-in deployment options. You can deploy your project to Needle Cloud, FTP servers, Glitch, Itch.io, GitHub Pages, and more with just a few clicks.
See the Deployment section for more information on each of these options.
Add the "Deploy to ..." component you want to use to your scene in Unity or Blender.
Configure the necessary options and click on "Deploy".
That's it! Your project is now live.
Recommended Workflow
This is the easiest option, and recommended for most workflows โ it's very fast! You can iteratively work on your project on your computer, and then upload a new version to the web in seconds.
If you don't want to use our "Deploy to..." components, or there's no component for your particlar workflow, you can do the same process manually. The resulting web app will be identical to what you see in your local server while working on the project.
Make a production build of your web project. This will create a dist/ folder with all necessary files, ready for distribution. It contains all necessary files, including the JavaScript bundle, the HTML file, and any other assets like textures, audio, or video files.
Upload the content of the dist/ folder from your Web Project to your web hoster. You can do this via FTP, SFTP, or any other file transfer method your hoster provides. Look at the documentation of your web hoster for details.
That's it! Your web app is now live.
The folder location influences the URL of your web app.
Depending on your hoster's settings, the folder location and name determine what the URL of your web app is. Here's an example:
Your domain https://your-website.com/ points at the folder /var/www/html on your webspace.
You upload your files to /var/www/html/my-app so that the index.html file is at /var/www/html/my-app/index.html.
The URL of your web app is now https://your-website.com/my-app/.
In some cases, you want a Needle Engine project to be part of an existing web site, for example as a part of a blog post, a product page, or a portfolio. The process is very similar, but instead of uploading the files to the root of your web space, you embed the project into an existing website with a few lines of code.
Make a production build of your web project. This will create a dist/ folder with all necessary files, ready for distribution. It contains all necessary files, including the JavaScript bundle, the HTML file, and any other assets like textures, audio, or video files.
Upload the dist/ folder from your Web Project to your web hoster.
The folder can be hosted anywhere!
If you don't have access to your web hoster's file system, or no way to upload files there, you can upload the folder to any other webspace and use the public URL of that in the next step.
Inside your dist folder, you'll find an index.html file. We want to copy some lines from this folder, so open the file in a text editor. Typically, it looks like this:
On the target website, add the <script...> and <needle-engine...> tags as well. Make sure that the paths point at the location where you have uploaded the files to.
When you have limited access to a website, for example when you're using a CMS like WordPress, you can use an iframe to embed a Needle Engine scene into your website. You may know this workflow from embedding YouTube videos or Sketchfab models.
Make a production build of your web project. This will create a dist/ folder with all necessary files, ready for distribution.
Upload the dist/ folder from your Web Project to your web hoster.
The folder can be hosted anywhere!
If you don't have access to your web hoster's file system, or no way to upload files there, you can upload the folder to any other webspace and use the public URL of that in the next step.
Add an iframe to your website, pointing to the index.html file in the dist/ folder.
The list inside allow= depends on the features your web app uses. For example, XR applications require xr and xr-spatial-tracking to work inside iframes.
Upload the assets/ folder from your Web Project to your web hoster. Depending on your project settings, this folder contains one or more .glb files and any number of other files like audio, video, skybox and more.
Change the src= attribute of the needle-engine tag to the URL of the .glb file you want to display. Typically, this will be some path like https://your-website.com/assets/MyScene.glb.
That's it! The scene should now be displayed on your website.
Decide on the method you want to use to embed your Needle Engine project. You can either use the "Embedding a Needle project into an existing website" method, or the "Embedding a Needle project as iframe" method.
Upload the content of the dist/ folder from your Web Project to your web hoster. Usually, the Wordpress uploads directory is located at wp-content/uploads/.
Wordpress Backups
You can decide if your new project should be at wp-content/uploads/my-project/, or at a different location like my-projects/my-project. This affects if and how your project will be contained in Wordpress backups.
In the page you want to add Needle Engine to, add a HTML block and paste the code snippet as outlined above โ either as script embed, or as iframe.
Needle's Everywhere Actions are a set of carefully chosen components that allow you to create interactive experiences in Unity without writing a single line of code. They are designed to serve as building blocks for experiences across the web, mobile and XR, including Augmented Reality on iOS.
From low-level triggers and actions, higher-level complex interactive behaviours can be built.
For iOS support add the USDZExporter component to your scene. It is good practice to add it to the same object as the WebXR component (but not mandatory)
To add an action to any object in your scene select it and then click Add Component > Needle > Everywhere Actions > [Action].
Demonstrates how to attach 3D content onto a custom image marker. Start the scene below in AR and point your phone's camera at the image marker on a screen, or print it out.
On Android: please turn on "WebXR Incubations" in the Chrome Flags. You can find those by pasting chrome://flags/#webxr-incubations into the Chrome browser address bar of your Android phone.
Creating new Everywhere Actions involves writing code for your action in TypeScript, which will be used in the browser and for WebXR, and using our TriggerBuilder and ActionBuilder API to create a matching setup for Augmented Reality on iOS via QuickLook. When creating custom actions, keep in mind that QuickLook has a limited set of features available. You can still use any code you want for the browser and WebXR, but the behaviour for QuickLook may need to be an approximation built from the available triggers and actions.
Tips
Often constructing specific behaviours requires thinking outside the box and creatively applying the available low-level actions. An example would be a "Tap to Place" action โ there is no raycasting or hit testing available in QuickLook, but you could cover the expected placement area with a number of invisible objects and use a "Tap" trigger to move the object to be placed to the position of the tapped invisible object.
Here's the implementation for HideOnStart as an example for how to create an Everywhere Action with implementations for both the browser and QuickLook:
Often, getting the right behaviour will involve composing higher-level actions from the available lower-level actions. For example, our "Change Material on Click" action is composed of a number of fadeActions and internally duplicates objects with different sets of materials each. By carefully constructing these actions, complex behaviours can be achieved.
To see the implementation of our built-in Everywhere Actions, please take look at src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts.
Add an ExportInfo component to your Unity scene to generate a new web project from a template, link to an existing web project that you want to export to, set up dependencies to other libraries and packages and to deploy your project.
By default, your scene is exported on save. This setting can be changed by disabling Auto Export in the ExportInfo component.
To export meshes, materials, animations, textures (...) create a new GameObject in your hierarchy and add a GltfObject component to it. This is the root of a new glTF file. It will be exported whenever you make a change to the scene and save.
Only scripts and data on and inside those root objects is exported. Scripts and data outside of them are not exported.
Add a cube as a child of your root object and save your scene. Note that the output assets/ folder (see project structure) now contains a new .glb file with the same name as your root GameObject.
You can enable the Smart Export setting (via Edit/Project Settings/Needle ) to only export when a change in this object's hierarchy is detected.
How to prevent specific objects from being exported
Objects with the EditorOnly tag will be ignored on export including their child hierarchy. Be aware that this is preferred over disabling objects as disabled will still get exported in case they're turned on later.
If you want to split up your application into multiple levels or scenes then you can simply use the SceneSwitcher component. You can then structure your application into multiple scenes or prefabs and add them to the SceneSwitcher array to be loaded and unloaded at runtime. This is a great way to avoid having to load all your content upfront and to keep loading times small (for example it is what we did on needle.tools by separating each section of your website into its own scene and only loading them when necessary)
Max. 500k vertices (less if you target mobile VR as well)
Max. 4x 2k lightmaps
You can split up scenes and prefabs into multiple glTF files, and then load those on demand (only when needed). This keeps loading performance fast and file size small. See the AssetReference section in the Scripting docs.
The scene complexity here is recommended to ensure good performance across a range of web-capable devices and bandwidths. There's no technical limitation to this beyond the capabilities of your device.
Prefabs can be exported as invidual glTF files and instantiated at runtime. To export a prefab as glTF just reference a prefab asset (from the project browser and not in the scene) from one of your scripts.
Exporting Prefabs works with nesting too: a component in a Prefab can reference another Prefab which will then also be exported. This mechanism allows for composing scenes to be as lightweight as possible and loading the most important content first and defer loading of additional content.
Similar to Prefab assets, you can reference other Scene assets. To get started, create a component in Unity with a UnityEditor.SceneAsset field and add it to one of your GameObjects inside a GltfObject. The referenced scene will now be exported as a separate glTF file and can be loaded/deserialized as a AssetReference from TypeScript.
You can keep working inside a referenced scene and still update your main exporter scene/website. On scene save or play mode change we will detect if the current scene is being used by your currently running server and then trigger a re-export for only that glb. (This check is done by name - if a glb inside your <web_project>/assets/ folder exists, it is exported again and the main scene reloads it.)
As an example on our website each section is setup as a separate scene and on export packed into multiple glb files that we load on demand:
If you want to reference and load a prefab from one of your scripts you can declare a AssetReference type. Here is a minimal example:
import { Behaviour, serializable, AssetReference } from "@needle-tools/engine";
+
+export class MyClass extends Behaviour {
+
+ // if you export a prefab or scene as a reference from Unity you'll get a path to that asset
+ // which you can de-serialize to AssetReference for convenient loading
+ @serializable(AssetReference)
+ myPrefab?: AssetReference;
+
+ async start() {
+ // directly instantiate
+ const myInstance = await this.myPrefab?.instantiate();
+
+ // you can also just load and instantiate later
+ // const myInstance = await this.myPrefab.loadAssetAsync();
+ // this.gameObject.add(myInstance)
+ // this is useful if you know that you want to load this asset only once because it will not create a copy
+ // since ``instantiate()`` does create a copy of the asset after loading it
+ }
+}
Procedural Animations can be created via scripting
Needle Engine is one of the first to support the new glTF extension KHR_ANIMATION_POINTER. This means that almost all properties, including script variables, are animatable.
One current limitation is that materials won't be duplicated on export โ if you want to animate the same material with different colors, for example, you currently need to split the material in two.
By default, materials are converted into glTF materials on export. glTF supports a physically based material model and has a number of extensions that help to represent complex materials.
For full control over what gets exported, it's highly recommended to use the glTF materials provided by UnityGltf:
PBRGraph
UnlitGraph
These materials are exported as-is, with no conversion necessary. They allow for using advanced material properties such as refractive transmission and iridescence, which can be exported as well.
Materials that can be converted out-of-the-box:
BiRP/Standard
BiRP/Autodesk Interactive
BiRP/Unlit
URP/Lit
URP/Unlit
Other materials are converted using a propery name heuristic. That means that depending on what property names your materials and shaders use, you might want to either refactor your custom shader's properties to use the property names of either URP/Lit or PBRGraph, or export the material as Custom Shader.
To export custom shaders (e.g. ShaderGraph shaders), add an ExportShader Asset Label (see bottom of the inspector) to the shader you want to export.
Warning
Please see limitations listed below
Note that Custom Shaders aren't part of the ratified glTF material model. The resulting GLB files will not display correctly in other viewers (the materials will most likely display white).
We currently only support custom Unlit shaders โ Lit shader conversion is not officially supported.
Custom Lit Shaders are currently experimental. Not all rendering modes are supported.
Shadow receiving on custom shaders is not supported
Skinned meshes with custom shaders are not supported
As there's multiple coordinate system changes when going from Unity to three.js and glTF, there might be some changes necessary to get advanced effects to work. We try to convert data on export but may not catch all cases where conversions are necessary. These coordinate changes are
UV coordinates in Unity start at the bottom left; in glTF they start at the top left.
X axis values are flipped in glTF compared to Unity (a variant of a left-handed to right-handed coordinate system change).
To export lightmaps simply generate lightmaps in Unity. Lightmaps will be automatically exported.
When working on multiple scenes, disable "Auto Generate" and bake lightmaps explicitly. Otherwise, Unity will discard temporary lightmaps on scene change.
There's no 100% mapping between how Unity handles lights and environment and how three.js handle that. For example, Unity has entirely separate code paths for lightmapped and non-lightmapped objects (lightmapped objects don't receive ambient light since that is already baked into their maps), and three.js doesn't distinguish in that way.
This means that to get best results, we currently recommend specific settings if you're mixing baked and non-baked objects in a scene:
Environment Lighting: Skybox
+Ambient Intensity: 1
+Ambient Color: black
2021.3+
2020.3+
If you have no baked objects in your scene, then the following settings should also yield correct results:
Open Edit/Project Settings/Needle to get the Needle Engine plugin settings. At the top of the window you'll find fields for entering your license information.
Email - Enter the email you purchased the license with
Invoice ID - Enter one of the invoice ids that you received by email
This usually happens when you're using custom shaders or materials and their properties don't cleanly translate to known property names for glTF export. You can either make sure you're using glTF-compatible materials and shaders, or mark shaders as "custom" to export them directly.
You might see a warning in your browser about SSL Security depending on your local configuration.
This is because while the connection is encrypted, by default there's no SSL certificate that the browser can validate. If that happens: click Advanced and Proceed to Site. In Safari, you might need to refresh the page afterwards, because it does not automatically proceed. Now you should see your scene in the browser!
The dialogue should only show up once for the same local server.
Tips
Connections are secured, because we're enforcing HTTPS to make sure that WebXR and other modern web APIs work out-of-the-box. Some browsers will still complain that the SSL connection (between your local development server and the local website) can't be automatically trusted, and that you need to manually verify you trust that page. Automatic Page Reload and Websocket connections may also be affected depending on the browser and system settings.
See the Testing docs for information on how to set up a self-signed certificate for a smoother development experience.
If that happens there's usually an exception either in engine code or your code. Open the dev tools (Ctrl + Shift + I or F12 in Chrome) and check the Console for errors. In some cases, especially when you just updated the Needle Engine package version, this can be fixed by stopping and restarting the local dev server. For that, click on the running progress bar in the bottom right corner of the Editor, and click the little X to cancel the running task. Then, simply press Play again.
This can have many reasons, but a few common ones are:
too many textures or textures are too large
meshes have too many vertices
meshes have vertex attributes you don't actually need (e.g. have normals and tangents but you're not using them)
objects are disabled and not ignored โ disabled objects get exported as well in case you want to turn them on at runtime! Set their Tag to EditorOnly to completely ignore them for export.
you have multiple GltfObject components in your scene and they all have EmbedSkybox enabled (you need to have the skybox only once per scene you export)
If loading time itself is an issue you can try to split up your content into multiple glb files and load them on-demand (this is what we do on our website). For it to work you can put your content into Prefabs or Scenes and reference them from any of your scripts. Please have a look at Scripting/Addressables in the documentation.
Make sure that your lights are set to "Baked" or "Realtime". "Mixed" is currently not supported.
Lights set to mixed (with lightmapping) do affect objects twice in three.js, since there is currently no way to exclude lightmapped objects from lighting
The Intensity Multiplier factor for Skybox in Lighting/Environment is currently not supported and has no effect in Needle Engine
Light shadow intensity can currently not be changed due to a three.js limitation.
Your light has shadows enabled (either Soft Shadow or Hard Shadow)
Your objects are set to "Cast Shadows: On" (see MeshRenderer component)
For directional lights the position of the light is currently important since the shadow camera will be placed where the light is located in the scene.
Deploying on Glitch is a fast way to prototype and might even work for some small productions. The little server there doesn't have the power and bandwidth to host many people in a persistent session.
We're working on other networking ideas, but in the meantime you can host the website somewhere else (with node.js support) or simply remix it to distribute load among multiple servers. You can also host the networking backend package itself somewhere else where it can scale e.g. Google Cloud.
Make sure to add the WebXR component somewhere inside your root GltfObject.
Optionally add a AR Session Root component on your root GltfObject or within the child hierarchy to specify placement, scale and orientation for WebXR.
Optionally add a XR Rig component to control where users start in VR
When creating new scripts in npmdefs in sub-scenes (that is a scene that is exported as a reference from a script in your root export scene) you currently have to re-export the root scene again. This is because the code-gen that is responsible for registering new scripts currently only runs for scenes with a ExportInfo component. This will be fixed in the future.
The most likely reason is an incorrect installation. Check the console and the ExportInfo component for errors or warnings.
If these warnings/errors didn't help, try the following steps in order. Give them some time to complete. Stop once your problem has been resolved. Check the console for warnings and errors.
While generating C# components does technically run with vanilla javascript too we don't recommend it and fully support it since it is more guesswork or simply impossible for the generator to know which C# type to create for your javascript class. Below you find a minimal example on how to generate a Unity Component from javascript if you really want to tho.
On Windows: Make sure you have added toktx to your system environment variables. You may need to restart your computer after adding it to refresh the environment variables. The default install location is C:\Program Files\KTX-Software\bin
Make sure to not create a project on a drive formatted as exFAT because exFAT does not support symlinks, which is required for Needle Engine for Unity prior to version 3.x. You can check the formatting of your drives using the following steps:
Open "System Information" (either windows key and type that or enter "msinfo32" in cmd)
Select Components > Storage > Drives
Select all (Ctrl + A) on the right side of the screen and copy that (Ctrl + C) and paste here (Ctrl + V)
Make sure your project is on a disk that is known to work with node.js. Main reason for failures is that the disk doesn't support symlinks (symbolic links / softlinks), which is a requirement for proper functioning of node.js. NTFS formatting should always work. Known problematic file system formattings are exFAT and FAT32.
To check the format of your drives, you can:
Open "System Information" (either Windows key and type "System Information" or enter msinfo32 in cmd Windows + R)
Select "Components > Storage > Drives"
There, you can see all drives and their formatting listed. Put your projects on a drive that is NTFS formatted.
You're likely using an x86_64 version of Unity on an (ARM) Apple Silicon processor. Unity 2020.3 is only available for x86_64, later versions also have Apple Silicon versions. Our Unity integration calling npm will thus do so from an x86_64 process, resulting in the x86_64 version of node and vite/esbuild being used. When you afterwards try to run npm commands in the same project from an Apple Silicon app (e.g. VS Code), npm will complain about mismatching architectures with a long error message.
To fix this, use an Apple Silicon version of Unity (2021.1 or later).
You can also temporarily fix it on 2020.3 by deleting the node_modules folder and running npm install again from VS Code. You'll have to delete node_modules again when you switch back to Unity.
This can happen when you have e.g. a SceneSwitcher (or any other component that loads a scene or asset) and the referenced Asset in Unity contains a GltfObject that has the same name as your original scene with the SceneSwitcher. You can double check this in Unity if you get an error that says something like:
Failed to export โ YourSceneName.glb
+you seem to have objects with the same name referencing each other.
To fix this you can:
Remove the GltfObject in the referenced Prefab or Scene
Rename the GameObject with the component that loads the referenced scenes
If this doesn't fix the problem please ask in our discord.
Use a detector like this one to determine if your device supports WebGL 2, it also hints at what could be the cause of your problem, but generally make sure you have updated your browser and drivers. WebGL 1 is not supported.
Needle Engine is a fully fledged 3D engine that runs in the browser. It comes with all the features you'd expect from a modern 3D engine, and more. If you haven't yet, take a look at our Homepage and our Samples and Showcase.
Both PBR Materials and Custom shaders created with Shader Graph or other systems can be exported.
Use the node based ShaderGraph to create shaders for the web. ShaderGraph makes it easy for artists to keep creating without having to worry about syntax.
Needle Engine runs everywhere web technology does: run the same application on desktop, mobile, AR or VR. We build Needle Engine with XR in mind and consider this as and integral part of responsive webdesign!
Lightmaps can be baked in Unity or Blender to easily add beautiful static light to your 3d content. Lightbaking for the web was never as easy. Just mark objects that you want to lightmap as static in Unity, add one or many lights to your scene (or use emissive materials) and click bake. Needle Engine will export your lightmaps per scene and automatically load and display them just as you see it in the Editor!
Note: There is no technical limitation on which lightmapper to use, as long as they end up in Unity's lightmapping data structures. Third party lightmappers such as Bakery thus are also supported.
Networking is built into the core runtime. Needle Engine deployments to Glitch come with a tiny server that allows you to deploy a multiplayer 3D environment in seconds. The built-in networked components make it easy to get started, and you can create your own synchronized components. Synchronizing variables and state is super easy!
Needle Engine brings powerful animations, state control and sequencing to the web โ from just playing a single animation to orchestrating and blending complex animations and character controllers. The Exporter can translate Unity components like Animator and Timeline into a web-ready format. We even added this functionality to our Blender addon so you can craft compatible animation state machines and export nla tracks as timelines to the web from within Blender too.
The Animator and AnimatorController components in Unity let you setup animations and define conditions for when and how to blend between them. We support exporting state machines, StateMachineBehaviours, transitions and layers. StateMachineBehaviours are also supported with OnStateEnter, OnStateUpdate and OnStateExit events.
Note: Sub-states and Blend Trees are not supported.
We're also translating Unity's Timeline setup and tracks into a web-ready format. Supported tracks include: AnimationTrack, AudioTrack, ActivationTrack, ControlTrack, SignalTrack.
Export of Unity ParticleSystem (Shuriken) is in development. Features currently include world/local space simulation, box and sphere emitter shapes, emission over time as well as burst emission, velocity- and color over time, emission by velocity, texturesheet animation, basic trails. See a live sample of supported features below:
Builtin effects include Bloom, Screenspace Ambient Occlusion, Depth of Field, Color Correction. You can also create your own custom effects. See the component reference for a complete list.
Needle Engine comes with powerful integrations into the Unity Editor and Blender. It allows you to setup and export complex scenes in a visual way providing easy and flexible collaboration between artists and developers.
Needle Engine provides a tight integration into the Unity Editor. This allows developers and designers alike to work together in a familiar environment and deliver fast, performant and lightweight web-experiences.
The following guide is mainly aimed at developers with a Unity3D background but it may also be useful for developers with a web or three.js background. It covers topics regarding how things are done in Unity vs in three.js or Needle Engine.
If you are all new to Typescript and Javascript and you want to dive into writing scripts for Needle Engine then we also recommend reading the Typescript Essentials Guide for a basic understanding between the differences between C# and Javascript/Typescript.
If you want to code-along you can open engine.needle.tools/new to create a small project that you can edit in the browser โก
Needle Engine is a 3d web engine running on-top of three.js. Three.js is one of the most popular 3D webgl based rendering libraries for the web. Whenever we refer to a gameObject in Needle Engine we are actually also talking about a three.js Object3D, the base type of any object in three.js. Both terms can be used interchangeably. Any gameObjectis a Object3D.
This also means that - if you are already familiar with three.js - you will have no problem at all using Needle Engine. Everything you can do with three.js can be done in Needle Engine as well. If you are already using certain libraries then you will be able to also use them in a Needle Engine based environment.
Note: Needle Engine's Exporter does NOT compile your existing C# code to Web Assembly. While using Web Assembly may result in better performance at runtime, it comes at a high cost for iteration speed and flexibility in building web experiences. Read more about our vision and technical overview.
How to create a new Unity project with Needle Engine? (Video)
If you have seen some Needle Engine scripts then you might have noticed that some variables are annotated with @serializable above their declaration. This is a Decorator in Typescript and can be used to modify or annotate code. In Needle Engine this is used for example to let the core serialization know which types we expect in our script when it converts from the raw component information stored in the glTF to a Component instance. Consider the following example:
This tells Needle Engine that myOtherComponent should be of type Behaviour. It will then automatically assign the correct reference to the field when your scene is loaded. The same is true for someOtherObject where we want to deserialize to an Object3D reference.
Note that in some cases the type can be ommitted. This can be done for all primitive types in Javascript. These are boolean, number, bigint, string, null and undefined.
import { Behaviour, serializable } from "@needle-tools/engine";
+class SomeClass {
+ @serializable() // < no type is needed here because the field type is a primitive
+ myString?: string;
+}
To access the current scene from a component you use this.scene which is equivalent to this.context.scene, this gives you the root three.js scene object.
To traverse the hierarchy from a component you can either iterate over the children of an object with a for loop:
for (let i = 0; i < this.gameObject.children; i++) {
+ console.log(this.gameObject.children[i]);
+}
or you can iterate using the foreach equivalent:
for (const child of this.gameObject.children) {
+ console.log(child);
+}
You can also use three.js specific methods to quickly iterate all objects recursively using the traverse method:
or to just traverse visible objects use traverseVisible instead.
Another option that is quite useful when you just want to iterate objects being renderable you can query all renderer components and iterate over them like so:
import { Renderer } from "@needle-tools/engine";
+
+for(const renderer of this.gameObject.getComponentsInChildren(Renderer))
+ console.log(renderer);
For more information about getting components see the next section.
Needle Engine is making heavy use of a Component System that is similar to that of Unity. This means that you can add or remove components to any Object3D / GameObject in the scene. A component will be registered to the engine when using addNewComponent(<Object3D>, <ComponentType>). The event methods that the attached component will then automatically be called by the engine (e.g. update or onBeforeRender). A full list of event methods can be found in the scripting documentation
For getting component you can use the familiar methods similar to Unity. Note that the following uses the Animator type as an example but you can as well use any component type that is either built-in or created by you.
Method name
Desciption
this.gameObject.getComponent(Animator)
Get the Animator component on a GameObject/Object3D. It will either return the Animator instance if it has an Animator component or null if the object has no such componnent.
this.gameObject.getComponentInChildren(Animator)
Get the first Animator component on a GameObject/Object3D or on any of its children
this.gameObject.getComponentsInParents(Animator)
Get all animator components in the parent hierarchy (including the current GameObject/Object3D)
These methods are also available on the static GameObject type. For example GameObject.getComponent(this.gameObject, Animator) to get the Animator component on a passed in GameObject/Object3D.
To search the whole scene for one or multiple components you can use GameObject.findObjectOfType(Animator) or GameObject.findObjectsOfType(Animator).
Some Unity-specific types are mapped to different type names in our engine. See the following list:
Type in Unity
Type in Needle Engine
UnityEvent
EventList
A UnityEvent will be exported as a EventList type (use serializable(EventList) to deserialize UnityEvents)
GameObject
Object3D
Transform
Object3D
In three.js and Needle Engine a GameObject and a Transform are the same (there is no Transform component). The only exception to that rule is when referencing a RectTransform which is a component in Needle Engine as well.
Color
RGBAColor
The three.js color type doesnt have a alpha property. Because of that all Color types exported from Unity will be exported as RGBAColor which is a custom Needle Engine type
this.gameObject.quaternion - is the quaternion in local space
this.gameObject.scale - is the scale in local space
The major difference here to keep in mind is that position in three.js is by default a localspace position whereas in Unity position would be worldspace. The next section will explain how to get the worldspace position in three.js.
In three.js (and thus also in Needle Engine) the object.position, object.rotation, object.scale are all local space coordinates. This is different to Unity where we are used to position being worldspace and using localPosition to deliberately use the local space position.
If you want to access the world coordinates in Needle Engine we have utility methods that you can use with your objects. Call getWorldPosition(yourObject) to calculate the world position. Similar methods exist for rotation/quaternion and scale. To get access to those methods just import them from Needle Engine like so import { getWorldPosition } from "@needle.tools/engine"
Note that these utility methods like getWorldPosition, getWorldRotation, getWorldScale internally have a buffer of Vector3 instances and are meant to be used locally only. This means that you should not cache them in your component, otherwise your cached value will eventually be overriden. But it is safe to call getWorldPosition multiple times in your function to make calculations without having to worry to re-use the same instance. If you are not sure what this means you should take a look at the Primitive Types section in the Typescript Essentials Guide
Use this.context.physics.raycast() to perform a raycast and get a list of intersections. If you dont pass in any options the raycast is performed from the mouse position (or first touch position) in screenspace using the currently active mainCamera. You can also pass in a RaycastOptions object that has various settings like maxDistance, the camera to be used or the layers to be tested against.
Use this.context.physics.raycastFromRay(your_ray) to perform a raycast using a three.js ray
Note that the calls above are by default raycasting against visible scene objects. That is different to Unity where you always need colliders to hit objects. The default three.js solution has both pros and cons where one major con is that it can perform quite slow depending on your scene geometry. It may be especially slow when raycasting against skinned meshes. It is therefor recommended to usually set objects with SkinnedMeshRenderers in Unity to the Ignore Raycast layer which will then be ignored by default by Needle Engine as well.
Another option is to use the physics raycast methods which will only return hits with colliders in the scene.
const hit = this.context.physics.engine?.raycast();
You can also subscribe to events in the InputEvents enum like so:
import { Behaviour, InputEvents, NEPointerEvent } from "@needle-tools/engine";
+
+export class MyScript extends Behaviour
+{
+ onEnable(){
+ this.context.input.addEventListener(InputEvents.PointerDown, this.inputPointerDown);
+ }
+ onDisable() {
+ // it is recommended to also unsubscribe from events when your component becomes inactive
+ this.context.input.removeEventListener(InputEvents.PointerDown, this.inputPointerDown);
+ }
+
+ inputPointerDown = (evt: NEPointerEvent) => { console.log(evt); }
+}
If you want to handle inputs yourself you can also subscribe to all events the browser provides (there are a ton). For example to subscribe to the browsers click event you can write:
Note that in this case you have to handle all cases yourself. For example you may need to use different events if your user is visiting your website on desktop vs mobile vs a VR device. These cases are automatically handled by the Needle Engine input events (e.g. PointerDown is raised both for mouse down, touch down and in case of VR on controller button down).
The Debug.Log() equivalent in javascript is console.log(). You can also use console.warn() or console.error().
import { GameObject, Renderer } from "@needle-tools/engine";
+const someVariable = 42;
+// ---cut-before---
+
+console.log("Hello web");
+// You can pass in as many arguments as you want like so:
+console.log("Hello", someVariable, GameObject.findObjectOfType(Renderer), this.context);
In Unity you normally have to use special methods to draw Gizmos like OnDrawGizmos or OnDrawGizmosSelected. In Needle Engine on the other hand such methods dont exist and you are free to draw gizmos from anywhere in your script. Note that it is also your responsibility then to not draw them in e.g. your deployed web application (you can just filter them by if(isDevEnvironment))).
Here is an example to draw a red wire sphere for one second for e.g. visualizing a point in worldspace
import { Vector3 } from "three";
+const hit = { point: new Vector3(0, 0, 0) };
+// ---cut-before---
+import { Gizmos } from "@needle-tools/engine";
+Gizmos.DrawWireSphere(hit.point, 0.05, 0xff0000, 1);
Import from @needle-tools/engine e.g. import { getParam } from "@needle-tools/engine"
Method name
Description
getParam()
Checks if a url parameter exists. Returns true if it exists but has no value (e.g. ?help), false if it is not found in the url or is set to 0 (e.g. ?help=0), otherwise it returns the value (e.g. ?message=test)
isMobileDevice()
Returns true if the app is accessed from a mobile device
isDevEnvironment()
Returns true if the current app is running on a local server
isMozillaXR()
isiOS
isSafari
import { isMobileDevice } from "@needle-tools/engine"
+if( isMobileDevice() )
In C# you usually work with a solution containing one or many projects. In Unity this solution is managed by Unity for you and when you open a C# script it opens the project and shows you the file. You usually install Packages using Unity's built-in package manager to add features provided by either Unity or other developers (either on your team or e.g. via Unity's AssetStore). Unity does a great job of making adding and managing packages easy with their PackageManager and you might never have had to manually edit a file like the manifest.json (this is what Unity uses to track which packages are installed) or run a command from the command line to install a package.
In a web environment you use npm - the Node Package Manager - to manage dependencies / packages for you. It does basically the same to what Unity's PackageManager does - it installs (downloads) packages from some server (you hear it usually called a registry in that context) and puts them inside a folder named node_modules.
When working with a web project most of you dependencies are installed from npmjs.com. It is the most popular package registry out there for web projects.
Here is an example of how a package.json might look like:
Our default template uses Vite as its bundler and has no frontend framework pre-installed. Needle Engine is unoppionated about which framework to use so you are free to work with whatever framework you like. We have samples for popular frameworks like Vue.js, Svelte, Next.js, React or React Three Fiber.
To install a dependency from npm you can open your web project in a commandline (or terminal) and run npm i <the/package_name> (shorthand for npm install) For example run npm i @needle-tools/engine to install Needle Engine. This will then add the package to your package.json to the dependencies array. To install a package as a devDependency only you can run npm i --save-dev <package_name>. More about the difference between dependencies and devDependencies below.
You may have noticed that there are two entries containing dependency - dependencies and devDependencies.
dependencies are always installed (or bundled) when either your web project is installed or in cases where you develop a library and your package is installed as a dependency of another project.
devDependencies are only installed when developing the project (meaning that when you directly run install in the specific directory) and they are otherwise not included in your project.
The Installing section taught us that you can install dependencies by running npm i <package_name> in your project directory where the package_name can be any package that you find on npm.js.
Let's assume you want to add a tweening library to your project. We will use @tweenjs/tween.js for this example. Here is the final project if you want to jump ahead and just see the result.
First run npm install @tweenjs/tween.js in the terminal and wait for the installation to finish. This will add a new entry to our package.json:
Then open one of your script files in which you want to use tweening and import at the top of the file:
import * as TWEEN from '@tweenjs/tween.js';
Note that we do here import all types in the library by writing * as TWEEN. We could also just import specific types like import { Tween } from @tweenjs/tween.js.
Now we can use it in our script. It is always recommended to refer to the documentation of the library that you want to use. In the case of tween.js they provide a user guide that we can follow. Usually the Readme page of the package on npm contains information on how to install and use the package.
To rotate a cube we create a new component type called TweenRotation, we then go ahead and create our tween instance for the object rotation, how often it should repeat, which easing to use, the tween we want to perform and then we start it. We then only have to call update every frame to update the tween animation. The final script looks like this:
import { Behaviour } from "@needle-tools/engine";
+import * as TWEEN from '@tweenjs/tween.js';
+
+export class TweenRotation extends Behaviour {
+
+ // save the instance of our tweener
+ private _tween?: TWEEN.Tween<any>;
+
+ start() {
+ const rotation = this.gameObject.rotation;
+ // create the tween instance
+ this._tween = new TWEEN.Tween(rotation);
+ // set it to repeat forever
+ this._tween.repeat(Infinity);
+ // set the easing to use
+ this._tween.easing(TWEEN.Easing.Quintic.InOut);
+ // set the values to tween
+ this._tween.to({ y: Math.PI * 0.5 }, 1000);
+ // start it
+ this._tween.start();
+ }
+
+ update() {
+ // update the tweening every frame
+ // the '?' is a shorthand for checking if _tween has been created
+ this._tween?.update();
+ }
+}
Now we only have to add it to any of the objects in our scene to rotate them forever. You can see the final script in action here.
With Needle Engine, you can create fully interactive 3D websites. They can be deployed anywhere on the web and get optimized automatically by the Needle Engine Build Pipeline reducing asset size by up x100 without compromising quality.
Needle Engine is available as a package for Unity, add-on for Blender, as a ready-to-go Web Component, and as npm package for projects without an editor integration. Each of these comes with the same components and building blocks โ the choice is yours.
Needle Engine makes it easy to build web apps. That often, but not always, includes coding with JavaScript/TypeScript or writing HTML and CSS to describe user interfacces. We recommend Visual Studio Code for creating and editing these files. It's a free, open-source code editor that runs on Windows, macOS, and Linux.
The following guide tries to highlight some of the key differences between C#, Javascript and Typescript. This is most useful for developers new to the web ecosystem.
Here are also some useful resources for learning how to write Typescript:
CSharp or C# is a statically typed & compiled language. It means that before your code can run (or be executed) it has to be compiled - translated - into IL or CIL, an intermediate language that is a little closer to machine code. The important bit to understand here is that your code is analyzed and has to pass certain checks and rules that are enforced by the compiler. You will get compiler errors in Unity and your application not even start running if you write code that violates any of the rules of the C# language. You will not be able to enter Play-Mode with compiler errors.
Javascript on the other hand is interpreted at runtime. That means you can write code that is not valid and cause errors - but you will not see those errors until your program runs or tries to execute exactly that line that has the error. For example you can write var points = 100; points += "hello world"; and nobody will complain until you run the code in a browser.
Typescript is a language designed by Microsoft that compiles to javascript It adds a lot of features like for example type-safety. That means when you write code in Typescript you can declare types and hence get errors at compile-time when you try to e.g. make invalid assignments or call methods with unexpected types. Read more about types in Javascript and Typescript below.
Vanilla Javascript does (as of today) not have any concept of types: there is no guarantuee that a variable that you declared as let points = 100 will still be a number later in your application. That means that in Javascript it is perfectly valid code to assign points = new Vector3(100, 0, 0); later in your code. Or even points = null or points = myRandomObject - you get the idea. This is all OK while you write the code but it may crash horrible when your code is executed because later you write points -= 1 and now you get errors in the browser when your application is already running.
As mentioned above Typescript was created to help fix that problem by adding syntax for defining types.
It is important to understand that you basically still write Javascript when you write Typescript and while it is possible to circumvent all type checking and safety checks by e.g. adding //@ts-ignore above a erroneous line or defining all types as any this is definitely not recommneded. Types are here to help you find errors before they actually happen. You really dont want to deploy your website to your server only to later get reports from users or visitors telling you your app crashed while it was running.
While vanilla Javascript does not offer types you can still add type-annotations to your javascript variables, classes and methods by using JSDoc.
In C# you write variables either by using the type or the var keyword. For example you can either write int points = 100; or alternatively use var and let the compiler figure out the correct type for you: var points = 100
In Javascript or Typescript you have two modern options to declaring a variable. For a variable that you plan to re-assign use let, for example let points = 100; For a variable that you do not want to be able to re-assign use const, for example const points = 100;
Be aware of var You might come across the var keyword in javascript as well but it is not recommended to use it and the modern replacement for it is let. Learn more about var vs let.
Please note that you can still assign values to variables declared with const if they are (for example) a custom type. Consider the following example:
import { Vector3 } from "three";
+// ---cut-before---
+const myPosition : Vector3 = new Vector3(0, 0, 0);
+myPosition.x = 100; // Assigning x is perfectly fine
The above is perfectly fine Typescript code because you don't re-assign myPosition but only the x member of myPosition. On the other hand the following example would not be allowed and cause a runtime or typescript error:
// @errors: 2588
+import { Vector3 } from "three";
+// ---cut-before---
+const myPosition : Vector3 = new Vector3(0, 0, 0);
+myPosition = new Vector3(100, 0, 0); // โ ASSIGNING TO CONST IS NOT ALLOWED
In Unity you usually add using statements at the top of you code to import specific namespaces from Assemblies that are references in your project or - in certain cases - you migth find yourself importing a specific type with a name from a namespace. See the following example:
using UnityEngine;
+// importing just a specific type and giving it a name
+using MonoBehaviour = UnityEngine.MonoBehaviour;
This is how you do the same in Typescript to import specific types from a package:
import { Vector3 } from 'three';
+import { Behaviour } from '@needle-tools/engine';
You can also import all the types from a specific package by giving it a name which you might see here and there:
import * as THREE from 'three';
+const myVector : THREE.Vector3 = new THREE.Vector3(1, 2, 3);
Vector2, Vector3, Vector4... If you have a C# background you might be familiar with the difference between a class and a struct. While a class is a reference type a struct is a custom value type. Meaning it is, depending on the context, allocated on the stack and when being passed to a method by default a copy is created. Consider the following example in C#:
void MyCallerMethod(){
+ var position = new Vector3(0,0,0);
+ MyExampleVectorMethod(position);
+ UnityEngine.Debug.Log("Position.x is " + position.x); // Here x will be 0
+}
+void MyExampleVectorMethod(Vector3 position){
+ position.x = 42;
+}
A method is called with a Vector3 named position. Inside the method the passed in vector position is modified: x is set to 42. But in C# the original vector that is being passed into this method (see line 2) is not changed and x will still be 0 (line 4).
The same is not true for Javascript/Typescript. Here we don't have custom value types, meaning if you come across a Vector in Needle Engine or three.js you will always have a reference type. Consider the following example in typescript:
import { Vector3 } from 'three'
+
+function myCallerMethod() : void {
+ const position = new Vector3(0,0,0);
+ myExampleVectorMethod(position);
+ console.log("Position.x is " + position.x); // Here x will be 42
+}
+function myExampleVectorMethod(position: Vector3) : void {
+ position.x = 42;
+}
Do you see the difference? Because vectors and all custom objects are in fact reference types we will have modified the original position variable (line 3) and x is now 42.
This is not only important to understand for methods but also when working with variables. In C# the following code will produce two instances of Vector3 and changing one will not affect the other:
var myVector = new Vector3(1,1,1);
+var myOtherVector = myVector;
+myOtherVector.x = 42;
+// will log: 1, 42
+UnityEngine.Debug.Log(myVector.x + ", " + myOtherVector.x);
If you do the same in Typescript you will not create a copy but get a reference to the same myVector instance instead:
import { Vector3 } from 'three'
+
+const myVector = new Vector3(1,1,1);
+const myOtherVector = myVector;
+myOtherVector.x = 42;
+// will log: 42, 42
+console.log(myVector.x, myOtherVector.x);
While in C# you can use operator overloading this is not available in Javascript unfortunately. This means that while you can multiply a Vector3 in C# like this:
var myFirstVector = new Vector3(1,1,1);
+var myFactor = 100f;
+myFirstVector *= myFactor;
+// โ myFirstVector is now 100, 100, 100
you have to use a method on the Vector3 type to archieve the same result (just with a little more boilerplate code)
import { Vector3 } from "three"
+
+const myFirstVector : Vector3 = new Vector3(1, 1, 1)
+const myFactor = 100;
+myFirstVector.multiplyScalar(myFactor);
+// โ myFirstVector is now 100, 100, 100
You notice that the second variable playerIsNullOrUndefined is using == which does a loose equality check in which case null and undefined will both result in truehere. You can read more about that here
When you subscribe to an Event in C# you do it like this:
// this is how an event is declared
+event Action MyEvent;
+// you subscribe by adding to (or removing from)
+void OnEnable() {
+ MyEvent += OnMyEvent;
+}
+void OnDisable() {
+ MyEvent -= OnMyEvent;
+}
+void OnMyEvent() {}
In Typescript and Javascript when you add a method to a list you have to "bind this". That essentially means you create a method where you explictly set this to (usually) your current class instance. There are two way to archieve this.
Please note that we are using the type EventList here which is a Needle Engine type to declare events (the EventList will also automatically be converted to a UnityEvent and or a event list in Blender when you use them with our Editor integrations)
The short and recommended syntax for doing this is to use Arrow Functions.
import { EventList, Behaviour, serializable } from "@needle-tools/engine";
+
+export class MyComponent extends Behaviour {
+
+ @serializable(EventList)
+ myEvent!: EventList;
+
+ onEnable() {
+ this.myEvent.addEventListener(this.onMyEvent);
+ }
+
+ onDisable() {
+ this.myEvent.removeEventListener(this.onMyEvent);
+ }
+
+ // Declaring the function as an arrow function to automatically bind `this`
+ private onMyEvent = () => {
+ console.log(this !== undefined, this)
+ }
+}
There is also the more verbose "classical" way to archieve the same thing by manually binding this (and saving the method in a variable to later remove it again from the event list):
import { EventList, Behaviour, serializable } from "@needle-tools/engine";
+
+export class MyComponent extends Behaviour {
+
+ @serializable(EventList)
+ myEvent?: EventList;
+
+ private _onMyEventFn?: Function;
+
+ onEnable() {
+ // bind this
+ this._onMyEventFn = this.onMyEvent.bind(this);
+ // add the bound method to the event
+ this.myEvent?.addEventListener(this._onMyEventFn);
+ }
+
+ onDisable() {
+ this.myEvent?.removeEventListener(this._onMyEventFn);
+ }
+
+ // Declaring the function as an arrow function to automatically bind `this`
+ private onMyEvent = () => { }
+}
Needle Engine is build as a web component. This means just install @needle-tools/engine in your project and include <needle-engine src="path/to/your.glb"> anywhere in your web-project.
Install using npm: npm i @needle-tools/engine
With our default Vite based project template Needle Engine gets bundled into a web app on deployment. This ensures smaller files, tree-shaking (similar to code stripping in Unity) and optimizes load times. Instead of downloading numerous small scripts and components, only one or a few are downloaded that contain the minimal code needed.
Vite (our default bundler) has a good explanation why web apps should be bundled: Why Bundle for Production
Needle Engine is unoponiated about the choice of framework. Our default template uses the popular vite as bundler. From there, you can add vue, svelte, nuxt, react, react-three-fiber or other frameworks, and we have samples for a lot of them. You can also integrate other bundlers, or use none at all โ just plain HTML and Javascript.
Here's some example tech stacks that are possible and that we use Needle Engine with:
Vite + HTML โ This is what our default template uses!
Vite + Vue โ This is what the Needle Tools website uses!. Find a sample to download here.
Vite + Svelte
Vite + SvelteKit
Vite + React โ There's an experimental template shipped with the Unity integration for this that you can pick when generating a project!
react-three-fiber โ There's an experimental template shipped with the Unity integration for this that you can pick when generating a project!
CDN without any bundler โ Find a code example here
In short: we're currently providing a minimal vite template, but you can extend it or switch to other frameworks โ Let us know what and how you build, and how we can improve the experience for your usecase or provide an example!
Tips
Some frameworks require custom settings in needle.config.json. Learn more here. Typically, the baseUrl needs to be set.
How do I create a custom project template in Unity?
You can create and share your own web project templates to use other bundlers, build systems, or none at all.
Create a new Template
Select Create/Needle Engine/Project Template to add a ProjectTemplate into the folder you want to use as a template
Done! It's that simple.
The dependencies come from unity when there is a NpmDef in the project (so when your project uses local references). You could also publish your packages to npm and reference them via version number.
Tree shaking refers to a common practice when it comes to bundling of web applications (see MSDN docs). It means that code paths and features that are not used in your code will be removed from the final bundled javascript file(s) to reduce filesize. See below about features that Needle Engine includes and remove them:
How to remove Rapier physics engine? (Reduce the overall bundle size removing ~2MB (~600KB when gzipping))
Option 1: via needlePlugins config: Set useRapier to false in your vite.config: needlePlugins(command, needleConfig, { useRapier: false }),
Option 2: via vite.define config: Declare the NEEDLE_USE_RAPIER define with false
define: {
+ NEEDLE_USE_RAPIER: false
+},
Option 3: via .env Create a .env file in your web project and add VITE_NEEDLE_USE_RAPIER=false
Option 4: via Unity component Add the Needle Engine Modules component to your scene and set Physics Engine to None
We support easily creating a Progressive Web App (PWA) directly from our vite template. PWAs are web applications that load like regular web pages or websites but can offer user functionality such as working offline, push notifications, and device hardware access traditionally available only to native mobile applications.
By default, PWAs created with Needle have offline support, and can optionally refresh automatically when you publish a new version of your app.
Install the Vite PWA plugin in your web project: npm install vite-plugin-pwa --save-dev
Modify vite.config.js as seen below. Make sure to pass the same pwaOptions object to both needlePlugins and VitePWA.
import { VitePWA } from 'vite-plugin-pwa';
+
+export default defineConfig(async ({ command }) => {
+
+ // Create the pwaOptions object.
+ // You can edit or enter PWA settings here (e.g. change the PWA name or add icons).
+ /** @type {import("vite-plugin-pwa").VitePWAOptions} */
+ const pwaOptions = {};
+
+ const { needlePlugins } = await import("@needle-tools/engine/plugins/vite/index.js");
+
+ return {
+ plugins: [
+ // pass the pwaOptions object to the needlePlugins and the VitePWA function
+ needlePlugins(command, needleConfig, { pwa: pwaOptions }),
+ VitePWA(pwaOptions),
+ ],
+ // the rest of your vite config...
All assets are cached by default
Note that by default, all assets in your build folder are added the PWA precache โ for large applications with many dynamic assets, this may not be what you want (imagine the YouTube PWA caching all videos once a user opens the app!). See More PWA Options for how to customize this behavior.
To test your PWA, deploy the page, for example using the DeployToFTP component. Then, open the deployed page in a browser and check if the PWA features work as expected:
the app shows up as installable
the app works offline
the app is detected as offline-capable PWA by pwabuilder.com
PWAs use Service Workers to cache resources and provide offline support. Service Workers are somewhat harder to use during development, and typically are only enabled for builds (e.g. when you use a DeployTo... component).
You can enable PWA support for development by adding the following to the options object in your vite.config.js.
const pwaOptions = {
+ // Note: PWAs behave different in dev mode.
+ // Make sure to verify the behaviour in production builds!
+ devOptions: {
+ enabled: true,
+ }
+};
Please note that PWAs in development mode do not support offline usage โ trying it may result in unexpected behavior.
Websites typically show new or updated content on page refresh.
In some situations, you may want the page to refresh and reload automatically when a new version has been published โ such as in a museum, trade show, public display, or other long-running scenarios.
To enable automatic updates, set the updateInterval property in the pwaOptions object to a duration (in milliseconds) in which the app should check for updates. If an update is detected, the page will reload automatically.
It's not recommended to use automatic reloads in applications where users are interacting with forms or other data that could be lost on a reload. For these applications, showing a reload prompt is recommended. See the Vite PWA plugin documentation for more information on how to implement a reload prompt instead of automatic reloading.
Since Needle uses the Vite PWA plugin under the hood, you can use all options and hooks provided by that. For example, you can provide a partial manifest with a custom app title or theme color:
const pwaOptions = {
+ // manifest options provided here will override the defaults
+ manifest: {
+ name: "My App",
+ short_name: "My App",
+ theme_color: "#B2D464",
+ }
+};
For complex requirements like partial caching, custom service workers or different update strategies, you can remove the { pwa: pwaOptions } option from needlePlugins and add PWA functionality directly through the Vite PWA plugin.
Code that you expose can be accessed from JavaScript after bundling. This allows to build viewers and other applications where there's a split between data known at edit time and data only known at runtime (e.g. dynamically loaded files, user generated content). For accessing components from regular javascript outside of the engine please refer to the interop with regular javascript section
The needle-engine loading appearance can use a light or dark skin. To change the appearance use the loading-style attribute on the <needle-engine> web component. Options are light and dark (default):
Needle Engine is a web engine for complex and simple 3D applications alike. Work on your machine and deploy anywhere. Needle Engine is flexible, extensible and has built-in support for collaboration and XR. It is built around the glTF standard for 3D assets.
Powerful integrations for Unity and Blender allow artists and developers to collaborate and manage web applications inside battle-tested 3d editors. These Integrations allow you to use editor features for creating models, authoring materials, animating and sequencing animations, baking lightmaps and more with ease.
Our powerful compression and optimization pipeline for the web make sure your files are ready, small and load fast.
Unbelievable Unity editor integration by an order of magnitude, and as straightforward as the docs claim. Wow. โ Chris Mahoney
This is the best thing I have seen after cinemachine in Unity โ Rinesh Thomas
Spent the last 2.5 months building this game, never built a game or used Unity before but absolutely loving the whole process. So rapid! โ Matthew Pieri
needle.tools is a wonderful showcase of what Needle contributes to 3D via the web. I just love it. โ Kevin Curry
Played with this a bit this morning ๐คฏ๐คฏ pretty magical โ Brit Gardner
This is huge for WebXR and shared, immersive 3D experiences! The AR part worked flawlessly on my Samsung S21. โ Marc Wakefield
This is amazing and if you are curious about WebXR with Unity this will help us get there โ Dilmer Valecillos
We just gotta say WOW ๐คฉ โ Unity for Digital Twins
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sy but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looIt uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo
There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage
of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo
but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which loo
+
+
+
diff --git a/meta/samples.json b/meta/samples.json
new file mode 100644
index 000000000..1fbe65c2f
--- /dev/null
+++ b/meta/samples.json
@@ -0,0 +1 @@
+{"webxr":[{"name":"webxr","page":"_meta-test","anchor":"my-test-header","absolute-url":"https://engine.needle.tools/docs/_meta-test#my-test-header","description":"here is a description","tags":["webxr","webgl","augmented reality"]}],"ui canvas":[{"name":"ui canvas","page":"_meta-test","anchor":"ui-canvas","absolute-url":"https://engine.needle.tools/docs/_meta-test#ui-canvas","description":"this is a description about UI","tags":["hello world"]}],"physics playground":[{"name":"physics playground","page":"_meta-test","anchor":"how-physics-works","absolute-url":"https://engine.needle.tools/docs/_meta-test#how-physics-works","tags":["physics"]}],"another sample":[{"name":"another sample","page":"_meta-test","anchor":"how-physics-works","absolute-url":"https://engine.needle.tools/docs/_meta-test#how-physics-works","tags":["physics"]}]}
\ No newline at end of file
diff --git a/meta/tags.json b/meta/tags.json
new file mode 100644
index 000000000..b261d6ce5
--- /dev/null
+++ b/meta/tags.json
@@ -0,0 +1 @@
+{"codegen":[{"page":"component-compiler","anchor":"automatically-generating-editor-components","absolute-url":"https://engine.needle.tools/docs/component-compiler#automatically-generating-editor-components"},{"page":"component-compiler","anchor":"controlling-component-generation","absolute-url":"https://engine.needle.tools/docs/component-compiler#controlling-component-generation"}],"webxr":[{"page":"_meta-test","anchor":"my-test-header","absolute-url":"https://engine.needle.tools/docs/_meta-test#my-test-header"}],"webgl":[{"page":"_meta-test","anchor":"my-test-header","absolute-url":"https://engine.needle.tools/docs/_meta-test#my-test-header"}],"augmented reality":[{"page":"_meta-test","anchor":"my-test-header","absolute-url":"https://engine.needle.tools/docs/_meta-test#my-test-header"}],"hello world":[{"page":"_meta-test","anchor":"ui-canvas","absolute-url":"https://engine.needle.tools/docs/_meta-test#ui-canvas"}],"physics":[{"page":"_meta-test","anchor":"how-physics-works","absolute-url":"https://engine.needle.tools/docs/_meta-test#how-physics-works"}],"serialization":[{"page":"scripting","anchor":"serialization-components-in-gltf-files","absolute-url":"https://engine.needle.tools/docs/scripting#serialization-components-in-gltf-files"}]}
\ No newline at end of file
diff --git a/modules.html b/modules.html
new file mode 100644
index 000000000..d32bb71cb
--- /dev/null
+++ b/modules.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+ Additional Modules | Needle Engine Documentation
+
+
+
+
+
+
Projects can be composed of re-usable pieces that we call NpmDef (which stands for Npm Defintion File).
Below you can find links to other repositories that contain Unity packages. These packages can be installed like any Unity package and used in your own projects. They usually contain eihter examples or modules that we use ourselves, but that are not ready to be part of the core Needle Engine.
Custom Timeline Tracks Video Track (sync video playback to a Timeline) CssTrack (control css properties from the Timeline)
Splines (for Unity 2022.1+) Export splines to three.js SplineWalker for controlling camera motion based on a spline Timeline track to control SplineWalker
Google Drive Integration Sketches around drag-drop integration of Google Drive, file picking, app integration
Access to core networking functionality can be obtained by using this.context.connection from a component. The default backend server connects users to rooms. Users in the same room will share state and receive messages from each other.
Networking is currently based on websockets and sending either json strings (for infrequent updates) or flatbuffers (for frequent updates). Continue reading below for more details:
SyncedRoom โ handles networking connection and connection to a room. This can also be done by code using the networking api accessible from this.context.connection
When sending an object containing a guid field it will be saved in the persistant storage and automatically sent to users that connect later or come back later to the site (e.g. to restore state). To delete state for a specific guid from the backend storage you can use delete-state as the key and provide an object with { guid: "guid_to_delete" }
Subscribe to json events / listen to events in the room using a specific key this.context.connection.beginListen(key:string, callback:(data) => void) Unsubscribe with stopListening
Subscribe to flatbuffer binary events this.context.connection.beginListenBinrary(identifier:string, callback:(data : ByteBuffer) => void) Unsubscribe with stopListenBinary
To automatically network fields in a component you can just decorate a field with a @syncField() decorator (note: you need to have experimentalDecorators: true in your tsconfig.json file for it to work)
Example Code
Automatically network a color field. The following script also changes the color randomly on click
import { Behaviour, IPointerClickHandler, PointerEventData, Renderer, RoomEvents, delay, serializable, showBalloonMessage, syncField } from "@needle-tools/engine";
+import { Color } from "three"
+
+export class Networking_ClickToChangeColor extends Behaviour implements IPointerClickHandler {
+
+ // START MARKER network color change syncField
+ /** syncField does automatically send a property value when it changes */
+ @syncField(Networking_ClickToChangeColor.prototype.onColorChanged)
+ @serializable(Color)
+ color!: Color;
+
+ private onColorChanged() {
+ // syncField will network the color as a number, so we need to convert it back to a Color when we receive it
+ if (typeof this.color === "number")
+ this.color = new Color(this.color);
+ this.setColorToMaterials();
+ }
+ // END MARKER network color change syncField
+
+ /** called when the object is clicked and does generate a random color */
+ onPointerClick(_: PointerEventData) {
+ const randomColor = new Color(Math.random(), Math.random(), Math.random());
+ this.color = randomColor;
+ }
+
+ onEnable() {
+ this.setColorToMaterials();
+ }
+
+ private setColorToMaterials() {
+ const renderer = this.gameObject.getComponent(Renderer);
+ if (renderer) {
+ for (let i = 0; i < renderer.sharedMaterials.length; i++) {
+ // we clone the material so that we don't change the original material
+ // just for demonstration purposes, you can also change the original material
+ const mat = renderer.sharedMaterials[i]?.clone();
+ renderer.sharedMaterials[i] = mat;
+ if (mat && "color" in mat)
+ mat.color = this.color;
+ }
+ }
+ else console.warn("No renderer found", this.gameObject)
+ }
+
+}
Simple networking of a number
import { Behaviour, syncField, IPointerClickHandler } from "@needle-tools/engine"
+
+export class AutoFieldSync extends Behaviour implements IPointerClickHandler {
+
+ // Use `@syncField` to automatically network a field.
+ // You can optionally assign a method or method name to be called when the value changes
+ @syncField("myValueChanged")
+ mySyncedValue?: number = 1;
+
+ private myValueChanged() {
+ console.log("My value changed", this.mySyncedValue);
+ }
+
+ onPointerClick() {
+ this.mySyncedValue = Math.random();
+ }
+}
Needle Engine currently uses its own networking package hosted on npm. By default if not configured differently using the Networking component Needle Engine will connect to a server running on Glitch.
It can be added to your own fastiy or express server running on any server for example by adding the following code on your server after installing the package:
import networking from "@needle-tools/needle-tiny-networking-ws";
+networking.startServerFastify(fastifyApp, { endpoint: "/socket" });
The following options are available:
endpointstring
relative path to the websocket endpoint (e.g. /socket)
maxUsersnumber
Amount of users allowed per room
defaultUserTimeoutnumber
Timeout length in seconds until a user is kicked from a room (if no ping is received). Defaults to 30 seconds
When deploying your app to Glitch, we include a simple networking backend that is great for prototyping and small deployments (~15-20 people at the same time). You can later update to a bigger/better/stronger networking solution if required.
For testing and development purposes it can be desired to run the needle engine networking package on a local server. We have prepared a repository that is setup to host the websocket package and to make that easy for you. Please follow the instructions in the linked repository:
You can also deploy your own networking server on e.g. google cloud. For further instructions please refer to the description found here: Local Networking Repository
If you want to use a different server for your local development and your hosted development (and the hosted server is not the same as your needle engine deployed website) then you can also enter a absolute URL in the Networking component URL field as well:
If you want to modify the default peerjs options you can call setPeerOptions(opts: PeerjsOptions) with your custom options. This can be used to modify the hosting provider in case where you host your own peerjs server.
+
+
+
diff --git a/preview.jpeg b/preview.jpeg
new file mode 100644
index 000000000..814a087c3
Binary files /dev/null and b/preview.jpeg differ
diff --git a/project-structure.html b/project-structure.html
new file mode 100644
index 000000000..539c0cdff
--- /dev/null
+++ b/project-structure.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+ Web Project Structure | Needle Engine Documentation
+
+
+
+
+
+
Our main project template uses the superfast vite bundler. The following shows the structure of the Vite template that we created and ship (altough it is possible to adapt it to your own needs).
Folders
assets/
The asset folder contains exported assets from Unity. E.g. generated gltf files, audio or video files. It is not recommended to manually add files to assets as it will get cleared on building the distribution for the project.
include/
(optional) - If you have custom assets that you need to reference/load add them to the include directory. On build this directory will be copied to the output folder.
src/generated/
The generated javascript code. Do not edit manually!
src/scripts/
Your project specific scripts / components
src/styles/
Stylesheets
*
You can add any new folders here as you please. Make sure to copy them to the output directory when building
Files
index.html
The landing- or homepage of your website
vite.config
The vite config. Settings for building the distribution and hosting the development server are made here. It is usually not necessary to edit these settings.
src/main.ts
Included from index.html and importing needle-engine
*
You can add any new files here as you please. Make sure to copy them to the output directory when building (unless they are just being used during development)
Our exporter can be used with other project structures as well, vite is just our go-to frontend bundling tool because of its speed. Feel free to set up your JavaScript project as you like.
The needle.config.json is used to provide configuration for the Needle Editor integrations and for the Needle Engine build pipeline plugins.
Paths
buildDirectory
This is where the built project files are being copied to
assetsDirectory
This is where the Editor integration assets will be copied to or created at (e.g. the .glb files exported from Unity or Blender)
scriptsDirectory
This is the directory the Editor integration is watching for code changes to re-generate components
codegenDirectory
This is where the Editor integration is outputting generated files to.
baseUrl
Required for e.g. next.js or SvelteKit integration. When baseUrl is set, relative paths for codegen and inside files are using baseUrl, not assetsDirectory. This is useful in cases where the assetDirectory does not match the server url. For example, the path on disk could be "assetsDirectory": "public/assets", but the framework serves files from "baseUrl": "assets".
Tools
build : { copy: ["myFileOrDirectory"] }
Array of string paths for copying additional files or folders to the buildDirectory. These can either be absolute or relative.
Files are exported to static/assets but the framework serves them from /assets. In this case, the baseUrl needs to be set to assets so that relative paths in files are correct.
The <needle-engine> web-component comes with a nice collection of built-in attributes that can be used to modify the look and feel of the loaded scene without the need to add or edit the three.js scene directly. The table below shows a list of the most important ones:
Attribute
Description
Loading
src
Path to one or multiple glTF or glb files. Supported types are string, string[] or a stringified array (, separated)
optional, hex color to be used as a background color. Examples: rgb(255, 200, 100), #dddd00
background-image
optional, URL to a skybox image (background image) or a preset string: studio, blurred-skybox, quicklook, quicklook-ar
background-blurriness
optional, bluriness value between 0 (no blur) and 1 (max blur) for the background-image. Example: background-blurriness="0.5"
environment-image
optional, URL to a environment image (environment light) or a preset string: studio, blurred-skybox, quicklook, quicklook-ar
contactshadows
optional, render contact shadows
tone-mapping
optional, supported values are none, linear, neutral, agx
tone-mapping-exposure
optional number e.g. increase exposure with tone-mapping-exposure="1.5", requires tone-mapping to be set
Interaction
autoplay
add or set to true to auto play animations e.g. <needle-engine autoplay
camera-controls
add or set to true to automatically add OrbitControls if no camera controls are found in the scene
auto-rotate
add to enable auto-rotate (only used with camera-controls)
Events
loadstart
Name of the function to call when loading starts. Note that the arguments are (ctx:Context, evt:Event). You can call evt.preventDefault() to hide the default loading overlay
progress
Name of the function to call when loading updates. onProgress(ctx:Context, evt: {detail: {context:Context, name:string, index:number, count:number, totalProgress01:number}) { ... }
loadfinished
Name of the function to call when loading finishes
Loading Display
Available options to change how the Needle Engine loading display looks. Use ?debugloadingrendering for easier editing
loading-style
Options are light or dark
loading-background-color
PRO โ Change the loading background color (e.g. =#dd5500)
loading-text-color
PRO โ Change the loading text color
loading-logo-src
PRO โ Change the loading logo image
primary-color
PRO โ Change the primary loading color
secondary-color
PRO โ Change the secondary loading color
hide-loading-overlay
PRO โ Do not show the loading overlay, added in Needle Engine > 3.17.1
Internal
hash
Used internally, is appended to the files being loaded to force an update (e.g. when the browser has cached a glb file). Should not be edited manually.
You can easily modify how Needle Engine looks by setting the appropriate attributes on the <needle-engine> web component. Please see the table above for details.
The following table contains available Typescript decorators that Needle Engine provides.
You can think of them as Attributes on steroids (if you are familiar with C#) - they can be added to classes, fields or methods in Typescript to provide additional functionality.
Field & Property Decorators
@serializable()
Add to exposed / serialized fields. Is used when loading glTF files that have been exported with components from Unity or Blender.
@syncField()
Add to a field to network the value when it changes. You can pass in a method to be called when the field changes
@validate()
Add to receive callbacks in the component event method onValidate whenever the value changes. This behaves similar to Unity's onValidate.
Method Decorators
@prefix(<type>) (experimental)
Can be used to easily inject custom code into other components. Optionally return false to prevent the original method from being executed. See the example below
Class Decorators
@registerType
No argument. Can be added to a custom component class to be registered to the Needle Engine types and to enable hot reloading support.
import { Behaviour, serializable, EventList } from "@needle-tools/engine";
+import { Object3D } from "three";
+
+export class SomeComponentType extends Behaviour {}
+
+export class ButtonObject extends Behaviour {
+ // you can omit the type if it's a primitive
+ // e.g. Number, String or Bool
+ @serializable()
+ myNumber: number = 42;
+
+ // otherwise add the concrete type that you want to serialize to
+ @serializable(EventList)
+ onClick?: EventList;
+
+ @serializable(SomeComponentType)
+ myComponent?: SomeComponentType;
+
+ // Note that for arrays you still add the concrete type (not the array)
+ @serializable(Object3D)
+ myObjects?: Object3D[];
+}
The @syncField decorator can be used to automatically network properties of your components for all users (visitors of your website) connected to the same networking room. It can optionally take a callback function that will be invoked whenever the value changes.
To notify the system that a reference value (like an object or an array) has changed you need to re-assign the field. E.g. like this: myField = myField
The callback function can not be an arrow function (e.g. MyScript.prototype.onNumberChanged works for onNumberChanged() { ... } but it does not for myNumberChanged = () => { ... })
import { Camera, prefix } from "@needle-tools/engine";
+class YourClass {
+ @prefix(Camera) // < this is type that has the method you want to change
+ awake() { // < this is the method name you want to change
+
+ // this is now called before the Camera.awake method runs
+ // NOTE: `this` does now refer to the Camera instance and NOT `YourClass` anymore. This allows you to access internal state of the component as well
+ console.log("Hello camera:", this)
+ // optionally return false if you want to prevent the default behaviour
+ }
+}
import { Behaviour, serializable } from "@needle-tools/engine";
+import { Object3D } from "three"
+
+export class MyClass extends Behaviour {
+ // this will be a "Transform" field in Unity
+ @serializable(Object3D)
+ myObjectReference: Object3D | null = null;
+
+ // this will be a "Transform" array field in Unity
+ // Note that the @serializable decorator contains the array content type! (Object3D and not Object3D[])
+ @serializable(Object3D)
+ myObjectReferenceList: Object3D[] | null = null;
+}
import { Behaviour, serializable, AssetReference } from "@needle-tools/engine";
+
+export class MyClass extends Behaviour {
+
+ // if you export a prefab or scene as a reference from Unity you'll get a path to that asset
+ // which you can de-serialize to AssetReference for convenient loading
+ @serializable(AssetReference)
+ myPrefab?: AssetReference;
+
+ async start() {
+ // directly instantiate
+ const myInstance = await this.myPrefab?.instantiate();
+
+ // you can also just load and instantiate later
+ // const myInstance = await this.myPrefab.loadAssetAsync();
+ // this.gameObject.add(myInstance)
+ // this is useful if you know that you want to load this asset only once because it will not create a copy
+ // since ``instantiate()`` does create a copy of the asset after loading it
+ }
+}
import { Behaviour, serializable, AssetReference } from "@needle-tools/engine";
+
+export class LoadingScenes extends Behaviour {
+ // tell the component compiler that we want to reference an array of SceneAssets
+ // @type UnityEditor.SceneAsset[]
+ @serializable(AssetReference)
+ myScenes?: AssetReference[];
+
+ async awake() {
+ if (!this.myScenes) {
+ return;
+ }
+ for (const scene of this.myScenes) {
+ // check if it is assigned in unity
+ if(!scene) continue;
+ // load the scene once
+ const myScene = await scene.loadAssetAsync();
+ // add it to the threejs scene
+ this.gameObject.add(myScene);
+
+ // of course you can always just load one at a time
+ // and remove it from the scene when you want
+ // myScene.removeFromParent();
+ // this is the same as scene.asset.removeFromParent()
+ }
+ }
+
+ onDestroy(): void {
+ if (!this.myScenes) return;
+ for (const scene of this.myScenes) {
+ scene?.unload();
+ }
+ }
+}
Add this script to any object in your scene that you want to be clickable. Make sure to also have an ObjectRaycaster component in the parent hierarchy of that object.
test
import { Behaviour, IPointerClickHandler, PointerEventData, showBalloonMessage } from "@needle-tools/engine";
+
+export class ClickExample extends Behaviour implements IPointerClickHandler {
+
+ // Make sure to have an ObjectRaycaster component in the parent hierarchy
+ onPointerClick(_args: PointerEventData) {
+ showBalloonMessage("Clicked " + this.name);
+ }
+}
Add this script to any object in your scene that you want to be clickable. Make sure to also have an ObjectRaycaster component in the parent hierarchy of that object. The component will send the received click to all connected clients and will raise an event that you can then react to in your app. If you are using Unity or Blender you can simply assign functions to call to the onClick event to e.g. play an animation or hide objects.
EventList events are also invoked on the component level. This means you can also subscribe to the event declared above using myComponent.addEventListener("my-event", evt => {...}) as well. This is an experimental feature. Please provide feedback in our forum
This is useful for when you want to expose an event to Unity or Blender with some custom arguments (like a string)
import { Behaviour, serializable, EventList } from "@needle-tools/engine";
+import { Object3D } from "three";
+
+/*
+Make sure to have a c# file in your project with the following content:
+
+using UnityEngine;
+using UnityEngine.Events;
+
+[System.Serializable]
+public class MyCustomUnityEvent : UnityEvent<string>
+{
+}
+
+Unity documentation about custom events:
+https://docs.unity3d.com/ScriptReference/Events.UnityEvent_2.html
+
+*/
+
+// Documentation โ https://docs.needle.tools/scripting
+
+export class CustomEventCaller extends Behaviour {
+
+ // The next line is not just a comment, it defines
+ // a specific type for the component generator to use.
+
+ //@type MyCustomUnityEvent
+ @serializable(EventList)
+ myEvent!: EventList;
+
+ // just for testing - could be when a button is clicked, etc.
+ start() {
+ this.myEvent.invoke("Hello");
+ }
+}
+
+export class CustomEventReceiver extends Behaviour {
+
+ logStringAndObject(str: string) {
+ console.log("From Event: ", str);
+ }
+}
You can nest objects and their data. With properly matching @serializable(SomeType) decorators, the data will be serialized and deserialized into the correct types automatically.
using System;
+
+[Serializable]
+public class CustomSubData
+{
+ public string subString;
+ public float subNumber;
+}
+
+[Serializable]
+public class CustomData
+{
+ public string myStringField;
+ public float myNumberField;
+ public bool myBooleanField;
+ public CustomSubData subData;
+}
Tips
Without the correct type decorators, you will still get the data, but just as a plain object. This is useful when you're porting components, as you'll have access to all data and can add types as required.
Assuming you have a custom shader with a property name _Speed that is a float value this is how you would change it from a script. You can find a live example to download in our samples
Make sure to install npm i postprocessing in your web project. Then you can add new effects by deriving from PostProcessingEffect.
To use the effect add it to the same object as your Volume component.
Here is an example that wraps the Outline postprocessing effect. You can expose variables and settings as usual as any effect is also just a component in your three.js scene.
import { EffectProviderResult, PostProcessingEffect, registerCustomEffectType, serializable } from "@needle-tools/engine";
+import { OutlineEffect } from "postprocessing";
+import { Object3D } from "three";
+
+export class OutlinePostEffect extends PostProcessingEffect {
+
+ // the outline effect takes a list of objects to outline
+ @serializable(Object3D)
+ selection!: Object3D[];
+
+ // this is just an example method that you could call to update the outline effect selection
+ updateSelection() {
+ if (this._outlineEffect) {
+ this._outlineEffect.selection.clear();
+ for (const obj of this.selection) {
+ this._outlineEffect.selection.add(obj);
+ }
+ }
+ }
+
+
+ // a unique name is required for custom effects
+ get typeName(): string {
+ return "Outline";
+ }
+
+ private _outlineEffect: void | undefined | OutlineEffect;
+
+ // method that creates the effect once
+ onCreateEffect(): EffectProviderResult | undefined {
+
+ const outlineEffect = new OutlineEffect(this.context.scene, this.context.mainCamera!);
+ this._outlineEffect = outlineEffect;
+ outlineEffect.edgeStrength = 10;
+ outlineEffect.visibleEdgeColor.set(0xff0000);
+ for (const obj of this.selection) {
+ outlineEffect.selection.add(obj);
+ }
+
+ return outlineEffect;
+ }
+}
+// You need to register your effect type with the engine
+registerCustomEffectType("Outline", OutlinePostEffect);
This is an example how you could create your own audio component. For most usecases however you can use the core AudioSource component and don't have to write code.
import { AudioSource, Behaviour, serializable } from "@needle-tools/engine";
+
+// declaring AudioClip type is for codegen to produce the correct input field (for e.g. Unity or Blender)
+declare type AudioClip = string;
+
+export class My2DAudio extends Behaviour {
+
+ // The clip contains a string pointing to the audio file - by default it's relative to the GLB that contains the component
+ // by adding the URL decorator the clip string will be resolved relative to your project root and can be loaded
+ @serializable(URL)
+ clip?: AudioClip;
+
+ awake() {
+ // creating a new audio element and playing it
+ const audioElement = new Audio(this.clip);
+ audioElement.loop = true;
+ // on the web we have to wait for the user to interact with the page before we can play audio
+ AudioSource.registerWaitForAllowAudio(() => {
+ audioElement.play();
+ })
+ }
+}
Use the FileReference type to load external files (e.g. a json file)
import { Behaviour, FileReference, ImageReference, serializable } from "@needle-tools/engine";
+
+export class FileReferenceExample extends Behaviour {
+
+ // A FileReference can be used to load and assign arbitrary data in the editor. You can use it to load images, audio, text files... FileReference types will not be saved inside as part of the GLB (the GLB will only contain a relative URL to the file)
+ @serializable(FileReference)
+ myFile?: FileReference;
+ // Tip: if you want to export and load an image (that is not part of your GLB) if you intent to add it to your HTML content for example you can use the ImageReference type instead of FileReference. It will be loaded as an image and you can use it as a source for an <img> tag.
+
+ async start() {
+ console.log("This is my file: ", this.myFile);
+ // load the file
+ const data = await this.myFile?.loadRaw();
+ if (!data) {
+ console.error("Failed loading my file...");
+ return;
+ }
+ console.log("Loaded my file. These are the bytes:", await data.arrayBuffer());
+ }
+}
import { Behaviour, EventList, serializable, serializeable } from "@needle-tools/engine";
+
+export class HTMLButtonClick extends Behaviour {
+
+ /** Enter a button query (e.g. button.some-button if you're interested in a button with the class 'some-button')
+ * Or you can also use an id (e.g. #some-button if you're interested in a button with the id 'some-button')
+ * Or you can also use a tag (e.g. button if you're interested in any button
+ */
+ @serializeable()
+ htmlSelector: string = "button.some-button";
+
+ /** This is the event to be invoked when the html element is clicked. In Unity or Blender you can assign methods to be called in the Editor */
+ @serializable(EventList)
+ onClick: EventList = new EventList();
+
+ private element? : HTMLButtonElement;
+
+ onEnable() {
+ // Get the element from the DOM
+ this.element = document.querySelector(this.htmlSelector) as HTMLButtonElement;
+ if (this.element) {
+ this.element.addEventListener('click', this.onClicked);
+ }
+ else console.warn(`Could not find element with selector \"${this.htmlSelector}\"`);
+ }
+
+ onDisable() {
+ if (this.element) {
+ this.element.removeEventListener('click', this.onClicked);
+ }
+ }
+
+ private onClicked = () => {
+ this.onClick.invoke();
+ }
+}
Runtime code for Needle Engine is written in TypeScript (recommended) or JavaScript. We automatically generate C# stub components out of that, which you can add to GameObjects in the editor. The C# components and their data are recreated by the runtime as JavaScript components with the same data and attached to three.js objects.
Both custom components as well as built-in Unity components can be mapped to JavaScript components in this way. For example, mappings for many built-in components related to animation, rendering or physics are already included in Needle Engine.
If you want to code-along with the following examples without having to install anything you just click the following link:
Our web runtime engine adopts a component model similar to Unity and thus provides a lot of functionality that will feel familiar. Components attached to three's Object3D objects have lifecycle methods like awake, start, onEnable, onDisable, update and lateUpdate that you can implement. You can also use Coroutines.
Often, interactive scenes can be realized using Events in Unity and calling methods on built-in components. A typical example is playing an animation on button click - you create a button, add a Click event in the inspector, and have that call Animator.SetTrigger or similar to play a specific animation.
Needle Engine translates Unity Events into JavaScript method calls, which makes this a very fast and flexible workflow - set up your events as usual and when they're called they'll work the same as in Unity.
An example of a Button Click Event that is working out-of-the-box in Needle Engine โ no code needed.
Scripts are written in TypeScript (recommended) or JavaScript. There are two ways to add custom scripts to your project:
Simply add a file with an .ts or .js extension inside src/scripts/ in your generated project directory, for example src/scripts/MyFirstScript.ts
Unity specific: Organize your code into NPM Definition Files (npm packages). These help you to modularize and re-use code between projects and if you are familiar with web development they are in fact regular npm packages that are installed locally. In Unity you can create NpmDef files via Create > NPM Definition and then add TypeScript files by right-clicking an NpmDef file and selecting Create > TypeScript. Please see this chapter for more information.
In both approaches, source directories are watched for changes and C# stub components or Blender panels are regenerated whenever a change is detected. Changes to the source files also result in a hot reload of the running website โ you don't have to wait for Unity to recompile the C# components. This makes iterating on code pretty much instant.
You can even have multiple component types inside one file (e.g. you can declare export class MyComponent1 and export class MyOtherComponent in the same Typescript file).
If you are new to writing Javascript or Typescript we recommend reading the Typescript Essentials Guide guide first before continuing with this guide.
Example: Creating a Component that rotates an object
Create a component that rotates an object Create src/scripts/Rotate.ts and add the following code:
import { Behaviour, serializable } from "@needle-tools/engine";
+
+export class Rotate extends Behaviour
+{
+ @serializable()
+ speed : number = 1;
+
+ start(){
+ // logging this is useful for debugging in the browser.
+ // You can open the developer console (F12) to see what data your component contains
+ console.log(this);
+ }
+
+ // update will be called every frame
+ update(){
+ this.gameObject.rotateY(this.context.time.deltaTime * this.speed);
+ }
+}
Now inside Unity a new script called Rotate.cs will be automatically generated. Add the new Unity component to a Cube and save the scene. The cube is now rotating inside the browser. Open the chrome developer console by F12 to inspect the log from the Rotate.start method. This is a helpful practice to learn and debug what fields are exported and currently assigned. In general all public and serializable fields and all public properties are exported.
Now add a new field public float speed = 5 to your Unity component and save it. The Rotate component inspector now shows a speed field that you can edit. Save the scene (or click the Build button) and note that the javascript component now has the exported speed value assigned.
import { Behaviour } from "@needle-tools/engine";
+
+export class PrintNumberComponent extends Behaviour
+{
+ start(){
+ this.printNumber(42);
+ }
+
+ private printNumber(myNumber : number){
+ console.log("My Number is: " + myNumber);
+ }
+}
Version Control & Unity
While generated C# components use the type name to produce stable GUIDs, we recommend checking in generated components in version control as a good practice.
Components are added to three.js Object3Ds. This is similar to how Components in Unity are added to GameObjects. Therefore when we want to access a three.js Object3D, we can access it as this.gameObject which returns the Object3D that the component is attached to.
Note: Setting visible to false on a Object3D will act like SetActive(false) in Unity - meaning it will also disable all the current components on this object and its children. Update events for inactive components are not being called until visible is set to true again. If you want to hide an object without affecting components you can just disable the Needle Engine Renderer component.
Note that lifecycle methods are only being called when they are declared. So only declare update lifecycle methods when they are actually necessary, otherwise it may hurt performance if you have many components with update loops that do nothing.
Method name
Description
awake()
First method being called when a new component is created
onEnable()
Called when a component is enabled (e.g. when enabled changes from false to true)
onDisable()
Called when a component is disabled (e.g. when enabled changes from true to false)
onDestroy()
called when the Object3D or component is being destroyed
start()
Called on the start of the first frame after the component was created
Optionally implement if you only want to receive XR callbacks for specific XR modes like immersive-vr or immersive-ar. Return true to notify the system that you want callbacks for the passed in mode
Callback when a controller is connected/added while in a XR session OR when the component joins a running XR session that has already connected controllers OR when the component becomes active during a running XR session that has already connected controllers
Coroutines can be declared using the JavaScript Generator Syntax. To start a coroutine, call this.startCoroutine(this.myRoutineName());
Example
import { Behaviour, FrameEvent } from "@needle-tools/engine";
+
+export class Rotate extends Behaviour {
+
+ start() {
+ // the second argument is optional and allows you to specifiy
+ // when it should be called in the current frame loop
+ // coroutine events are called after regular component events of the same name
+ // for example: Update coroutine events are called after component.update() functions
+ this.startCoroutine(this.rotate(), FrameEvent.Update);
+ }
+
+ // this method is called every frame until the component is disabled
+ *rotate() {
+ // keep looping forever
+ while (true) {
+ yield;
+ }
+ }
+}
To stop a coroutine, either exit the routine by returning from it, or cache the return value of startCoroutine and call this.stopCoroutine(<...>). All Coroutines are stopped at onDisable / when disabling a component.
Needle Engine also exposes a few lifecycle hooks that you can use to hook into the update loop without having to write a full component. Those hooks can be inserted at any point in your web application (for example in toplevel scope or in a svelte component)
Method name
Description
onInitialized(cb, options)
Called when a new context is initialized (before the first frame)
onClear(cb, options)
Register a callback before the engine context is cleared
onDestroy(cb, options)
Register a callback in the engine before the context is destroyed
onStart(cb, options)
Called directly after components start at the beginning of a frame
// this can be put into e.g. main.ts or a svelte component (similar to onMount)
+import { Context, onUpdate, onBeforeRender, onAfterRender } from "@needle-tools/engine"
+onUpdate((ctx: Context) => {
+ // do something... e.g. access the scene via ctx.scene
+ console.log("UPDATE", ctx.time.frame);
+});
+
+onBeforeRender((ctx: Context) => {
+ // this event is only called once because of the { once: true } argument
+ console.log("ON BEFORE RENDER", ctx.time.frame);
+}, { once: true } );
+
+// Every event hook returns a method to unsubscribe from the event
+const unsubscribe = onAfterRender((ctx: Context) => {
+ console.log("ON AFTER RENDER", ctx.time.frame);
+});
+// Unsubscribe from the event at any time
+setTimeout(()=> unsubscribe(), 1000);
To access other components, use the static methods on GameObject or this.gameObject methods. For example, to access a Renderer component in the parent use GameObject.getComponentInParent(this.gameObject, Renderer) or this.gameObject.getComponentInParent(Renderer).
creates a new instance of this object including new instances of all its components
GameObject.destroy(Object3D | Component)
destroy a component or Object3D (and its components)
GameObject.addNewComponent(Object3D, Type)
adds (and creates) a new component for a type to the provided object. Note that awake and onEnable is already called when the component is returned
GameObject.addComponent(Object3D, Component)
moves a component instance to the provided object. It is useful if you already have an instance e.g. when you create a component with e.g. new MyComponent() and then attach it to a object
GameObject.removeComponent(Component)
removes a component from a gameObject
GameObject.getComponent(Object3D, Type)
returns the first component matching a type on the provided object.
GameObject.getComponents(Object3D, Type)
returns all components matching a type on the provided object.
GameObject.getComponentInChildren
same as getComponent but also searches in child objects.
GameObject.getComponentsInChildren
same as getComponents but also searches in child objects.
GameObject.getComponentInParent
same as getComponent but also searches in parent objects.
GameObject.getComponentsInParent
same as getComponents but also searches in parent objects.
The context refers to the runtime inside a web component. The three.js scene lives inside a custom HTML component called <needle-engine> (see the index.html in your project). You can access the <needle-engine> web component using this.context.domElement.
This architecture allows for potentially having multiple needle WebGL scenes on the same webpage, that can either run on their own or communicate between each other as parts of your webpage.
To access the current scene from a component you use this.scene which is equivalent to this.context.scene, this gives you the root three.js scene object.
To traverse the hierarchy from a component you can either iterate over the children of an object with a for loop:
for(let i = 0; i < this.gameObject.children; i++)
+ console.log(this.gameObject.children[i]);
or you can iterate using the foreach equivalent:
for(const child of this.gameObject.children) {
+ console.log(child);
+}
You can also use three.js specific methods to quickly iterate all objects recursively using the traverse method:
import { Object3D } from "three";
+this.gameObject.traverse((obj: Object3D) => console.log(obj));
or to just traverse visible objects use traverseVisible instead.
Another option that is quite useful when you just want to iterate objects being renderable you can query all renderer components and iterate over them like so:
import { Renderer } from "@needle-tools/engine";
+for(const renderer of this.gameObject.getComponentsInChildren(Renderer))
+ console.log(renderer);
For more information about getting components see the next section.
Receive input data for the object the component is on:
import { Behaviour } from "@needle-tools/engine";
+export class MyScript extends Behaviour
+{
+ onPointerDown() {
+ console.log("POINTER DOWN on " + this.gameObject.name);
+ }
+}
You can also subscribe to global events in the InputEvents enum like so:
import { Behaviour, InputEvents, NEPointerEvent } from "@needle-tools/engine";
+
+export class MyScript extends Behaviour
+{
+ onEnable() {
+ this.context.input.addEventListener(InputEvents.PointerDown, this.inputPointerDown);
+ }
+
+ onDisable() {
+ // it is recommended to also unsubscribe from events when your component becomes inactive
+ this.context.input.removeEventListener(InputEvents.PointerDown, this.inputPointerDown);
+ }
+
+ // @nonSerialized
+ inputPointerDown = (evt: NEPointerEvent) => { console.log("POINTER DOWN anywhere on the <needle-engine> element"); }
+}
Or use this.context.input if you want to poll input state every frame:
import { Behaviour } from "@needle-tools/engine";
+export class MyScript extends Behaviour
+{
+ update() {
+ if(this.context.input.getPointerDown(0)){
+ console.log("POINTER DOWN anywhere")
+ }
+ }
+}
If you want to handle inputs yourself you can also subscribe to all events the browser provides (there are a ton). For example to subscribe to the browsers click event you can write:
import { Behaviour } from "@needle-tools/engine";
+export class MyScript extends Behaviour
+{
+ onEnable() {
+ window.addEventListener("click", this.windowClick);
+ }
+
+ onDisable() {
+ // unsubscribe again when the component is disabled
+ window.removeEventListener("click", this.windowClick);
+ }
+
+ windowClick = () => { console.log("CLICK anywhere on the page, not just on <needle-engine>"); }
+}
Note that in this case you have to handle all cases yourself. For example you may need to use different events if your user is visiting your website on desktop vs mobile vs a VR device. These cases are automatically handled by the Needle Engine input events (e.g. PointerDown is raised both for mouse down, touch down and in case of VR on controller button down).
Use this.context.physics.raycast() to perform a raycast and get a list of intersections. If you dont pass in any options the raycast is performed from the mouse position (or first touch position) in screenspace using the currently active mainCamera. You can also pass in a RaycastOptions object that has various settings like maxDistance, the camera to be used or the layers to be tested against.
Use this.context.physics.raycastFromRay(your_ray) to perform a raycast using a three.js ray
Note that the calls above are by default raycasting against visible scene objects. That is different to Unity where you always need colliders to hit objects. The default three.js solution has both pros and cons where one major con is that it can perform quite slow depending on your scene geometry. It may be especially slow when raycasting against skinned meshes. It is therefor recommended to usually set objects with SkinnedMeshRenderers in Unity to the Ignore Raycast layer which will then be ignored by default by Needle Engine as well.
Another option is to use the physics raycast methods which will only return hits with colliders in the scene.
const hit = this.context.physics.engine?.raycast();
It is possible to access all the functionality described above using regular JavaScript code that is not inside components and lives somewhere else. All the components and functionality of the needle runtime is accessible via the global Needle namespace (you can write console.log(Needle) to get an overview)
You can find components using Needle.findObjectOfType(Needle.AudioSource) for example. It is recommended to cache those references, as searching the whole scene repeatedly is expensive. See the list for finding adding and removing components above.
For getting callbacks for the initial scene load see the following example:
You can also subscribe to the globale NeedleEngine (sometimes also referred to as ContextRegistry) to receive a callback when a Needle Engine context has been created or to access all available contexts:
Another option is using the onInitialized(ctx => {})lifecycle hook
You can also access all available contexts via NeedleEngine.Registered which returns the internal array. (Note that this array should not be modified but can be used to iterate all active contexts to modify settings, e.g. set all contexts to context.isPaused = true)
Below you find a list of available events on the static NeedleEngine type. You can subscribe to those events via NeedleEngine.registerCallback(ContextEvent.ContextCreated, (args) => {})
ContextEvent options
ContextEvent.ContextRegistered
Called when the context is registered to the registry.
ContextEvent.ContextCreationStart
Called before the first glb is loaded and can be used to initialize the physics engine. Can return a promise
ContextEvent.ContextCreated
Called when the context has been created before the first frame
ContextEvent.ContextDestroyed
Called when the context has been destroyed
ContextEvent.MissingCamera
Called when the context could not find a camera, currently only called during creation
ContextEvent.ContextClearing
Called when the context is being cleared: all objects in the scene are being destroyed and internal state is reset
The static Gizmos class can be used to draw lines, shapes and text which is mostly useful for debugging. All gizmos function have multiple options for e.g. colors or for how long they should be displayed in the scene. Internally they are cached and re-used.
Gizmos
Gizmos.DrawLabel
Draws a label with a background optionally. It can be attached to an object. Returns a Label handle which can be used to update the text.
Gizmos.DrawRay
Takes an origin and direction in worldspace to draw an infinite ray line
Gizmos.DrawDirection
Takes a origin and direction to draw a direction in worldspace
To embed components and recreate components with their correct types in glTF, we also need to save non-primitive types (everything that is not a Number, Boolean or String). You can do so is adding a @serializable(<type>) decorator above your field or property.
Example:
import { Behaviour, serializable } from "@needle-tools/engine";
+import { Object3D } from "three"
+
+export class MyClass extends Behaviour {
+ // this will be a "Transform" field in Unity
+ @serializable(Object3D)
+ myObjectReference: Object3D | null = null;
+
+ // this will be a "Transform" array field in Unity
+ // Note that the @serializable decorator contains the array content type! (Object3D and not Object3D[])
+ @serializable(Object3D)
+ myObjectReferenceList: Object3D[] | null = null;
+}
To serialize from and to custom formats, it is possible to extend from the TypeSerializer class and create an instance. Use super() in the constructor to register supported types.
Note: In addition to matching fields, matching properties will also be exported when they match to fields in the typescript file.
Referenced Prefabs, SceneAssets and AssetReferences in Unity will automatically be exported as glTF files (please refer to the Export Prefabs documentation).
These exported gltf files will be serialized as plain string URIs. To simplify loading these from TypeScript components, we added the concept of AssetReference types. They can be loaded at runtime and thus allow to defer loading parts of your app or loading external content.
Example:
import { Behaviour, serializable, AssetReference } from "@needle-tools/engine";
+
+export class MyClass extends Behaviour {
+
+ // if you export a prefab or scene as a reference from Unity you'll get a path to that asset
+ // which you can de-serialize to AssetReference for convenient loading
+ @serializable(AssetReference)
+ myPrefab?: AssetReference;
+
+ async start() {
+ // directly instantiate
+ const myInstance = await this.myPrefab?.instantiate();
+
+ // you can also just load and instantiate later
+ // const myInstance = await this.myPrefab.loadAssetAsync();
+ // this.gameObject.add(myInstance)
+ // this is useful if you know that you want to load this asset only once because it will not create a copy
+ // since ``instantiate()`` does create a copy of the asset after loading it
+ }
+}
AssetReferences are cached by URI, so if you reference the same exported glTF/Prefab in multiple components/scripts it will only be loaded once and then re-used.
Build your own castle! Drag 3D models from the various palettes onto the stage, and create your very own world. Works on Desktop, Mobile, VR, AR, all right in your browser. Interactions are currently optimized for VR; placement on screens is a bit harder but possible. Have fun, no matter which device you're on!
Invite your friends! Click Create Room to be put into a live, multi-user space โ just copy the URL, send it to a friend and they join you automatically. There's currently a max limit for "users online at the same time" - if you don't get into a room, please try later.
This page was authored in Unity and exported to three.js using tools and technologies by ๐ต needle.
There are a lot of open technologies involved: 3D models are in glTF format, the render engine is three.js, VR and AR are using WebXR. The networking server runs on Glitch, and audio is sent over WebRTC using PeerJS.
Hello, my name is Kryลกtof and i did a research project about Needle. At our company, we wanted to determine how Needle can help us in our workflow. We have one local client which focuses on reselling luxury cars. We already delivered a mobile app and VR experience using Unity. We have around 30 unique cars ready in the engine. We plan to expand the client's website with visually pleasing digital clones with more configuration options. Needle could achieve a perfect 1:1 conversion between unity and web visuals. It would be a massive benefit to our workflow. So that's what sparked our research.
I'm not very well experienced with javascript, typescript or three.js, so my point of view is as a semi-experienced Unity developer trying out the simplest way how to create a web experience. For those who would suggest Unity WebGL, that sadly doesn't work and isn't flexible on mobile browsers. Needle is ๐
Our lighting model is based on reflection probes in unity. We do not need any directional or point lights, only ambient lighting.
We're using this skybox:
Which looks like this on the paint job:
Then to add a slight detail, i've added 2 directional lights with an insignificant intensity (0.04) to create specular highlights. So before it looked like this:
But with the added directional lights it added a better dynamic. The effect could be deepened with higher intensity:
The black background isn't very pretty. So to differentiate between visual and lighting skyboxes i've added an inverse sphere which wraps the whole map.
Regarding the gradient goes from a slight gray to a white color..
This effect could be easily made with just a proper UV mapping and a single pixel high texture which would define the gradient.
I've made an unlit shader in the shader graph:
I've noticed a color banding issue, so i've tried to implement dithering. Frankly, it didn't help the artefacts but i bet there's a simple solution to that issue. So the upper part of the shader does sample the gradient based on the Y axis in object space. And the lower part tries to negate the color banding.
By using shaders it's simpler to use and iterate the gradiant. By using Needle's Shadergraph markdown asset, it's even simpler! ๐ต
The user now sees a car driving in deep nothingness, the color doesn't resemble anything and the experience is dull. We want to ground the model and that's done by adding a grid and then shifting it so it seems the car is moving. This is what we want to achieve:
The shader for the grid was comprised of two parts. A simple tiled texture of the grid that's being multipled by a circular gradient to make the edges fade off.
This tech demo takes it's goal to showcase the car's capabilities.
Let's start by highlighting the wheels.
Adding this shader to a plane will result in a dashed circle which is rotating by a defined speed. Combined with world space UI with a normal Text component this can highlight some interesting capabilities or parameters of the given product.
After showcasing the wheels we want to finish with a broad information about the product. In this case, that would be the car's full name and perhaps some available configurations.
Needle Engine seems to be a very good candidate for us!
There are a few features which we miss.
That would be for example proper support for the Lit Shader Graphs. But nothing stops us to create shaders the three.js way and create simmilar shaders in Unity for our content team to tweak the materials.
Using Needle was a blast! ๐ต
+
+
+
diff --git a/showcase-mercedes/10_WheelsAndGrid.png b/showcase-mercedes/10_WheelsAndGrid.png
new file mode 100644
index 000000000..869772b0b
Binary files /dev/null and b/showcase-mercedes/10_WheelsAndGrid.png differ
diff --git a/showcase-mercedes/11_GridShader.jpg b/showcase-mercedes/11_GridShader.jpg
new file mode 100644
index 000000000..8aa5c8dd7
Binary files /dev/null and b/showcase-mercedes/11_GridShader.jpg differ
diff --git a/showcase-mercedes/12_WheelWithText.png b/showcase-mercedes/12_WheelWithText.png
new file mode 100644
index 000000000..a2973ab3d
Binary files /dev/null and b/showcase-mercedes/12_WheelWithText.png differ
diff --git a/showcase-mercedes/13_WheelShader.jpg b/showcase-mercedes/13_WheelShader.jpg
new file mode 100644
index 000000000..fcf52ad50
Binary files /dev/null and b/showcase-mercedes/13_WheelShader.jpg differ
diff --git a/showcase-mercedes/14_RearUI.jpg b/showcase-mercedes/14_RearUI.jpg
new file mode 100644
index 000000000..35949e338
Binary files /dev/null and b/showcase-mercedes/14_RearUI.jpg differ
diff --git a/showcase-mercedes/1_skybox.png b/showcase-mercedes/1_skybox.png
new file mode 100644
index 000000000..4611373a0
Binary files /dev/null and b/showcase-mercedes/1_skybox.png differ
diff --git a/showcase-mercedes/2_paintjob_simple.jpg b/showcase-mercedes/2_paintjob_simple.jpg
new file mode 100644
index 000000000..e5be417b3
Binary files /dev/null and b/showcase-mercedes/2_paintjob_simple.jpg differ
diff --git a/showcase-mercedes/3_SpecularHighlights_off.jpg b/showcase-mercedes/3_SpecularHighlights_off.jpg
new file mode 100644
index 000000000..bdc2aa805
Binary files /dev/null and b/showcase-mercedes/3_SpecularHighlights_off.jpg differ
diff --git a/showcase-mercedes/4_SpecularHighlights_on.jpg b/showcase-mercedes/4_SpecularHighlights_on.jpg
new file mode 100644
index 000000000..fad273a28
Binary files /dev/null and b/showcase-mercedes/4_SpecularHighlights_on.jpg differ
diff --git a/showcase-mercedes/5_NoBackground.jpg b/showcase-mercedes/5_NoBackground.jpg
new file mode 100644
index 000000000..7f607b9de
Binary files /dev/null and b/showcase-mercedes/5_NoBackground.jpg differ
diff --git a/showcase-mercedes/6_MapBackground.png b/showcase-mercedes/6_MapBackground.png
new file mode 100644
index 000000000..e17bd6117
Binary files /dev/null and b/showcase-mercedes/6_MapBackground.png differ
diff --git a/showcase-mercedes/7_EnvShaderGraph.jpg b/showcase-mercedes/7_EnvShaderGraph.jpg
new file mode 100644
index 000000000..3d91713dd
Binary files /dev/null and b/showcase-mercedes/7_EnvShaderGraph.jpg differ
diff --git a/showcase-mercedes/8_Gradiant.png b/showcase-mercedes/8_Gradiant.png
new file mode 100644
index 000000000..e68c7984e
Binary files /dev/null and b/showcase-mercedes/8_Gradiant.png differ
diff --git a/showcase-mercedes/9_Rotator.png b/showcase-mercedes/9_Rotator.png
new file mode 100644
index 000000000..8ca66499d
Binary files /dev/null and b/showcase-mercedes/9_Rotator.png differ
diff --git a/showcase-monsterhands.html b/showcase-monsterhands.html
new file mode 100644
index 000000000..a573a9284
--- /dev/null
+++ b/showcase-monsterhands.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+ Monster Hands ๐ | Needle Engine Documentation
+
+
+
+
+
+
Ask questions and provide feedback in Needle Forum
Get instant help with Needle AI
Ask any question about Needle Engine, web development or our editor integrations and get instant help from the Needle AI that has access to the latest code, documentation and our integrations.
a number of components and tools that allow you to set up scenes for Needle Engine from e.g. the Unity Editor.
an exporter that turns scene and component data into glTF.
a web runtime that loads and runs the produced glTF files and their extensions.
The web runtime uses three.js for rendering, adds a component system on top of the three scene graph and hooks up extension loaders for our custom glTF extensions.
Effectively, this turns tools like Unity or Blender into spatial web development powerhouses โ adding glTF assets to the typical HTML, CSS, JavaScript and bundling workflow.
Models, textures, animations, lights, cameras and more are stored as glTF 2.0 files in Needle Engine. Custom data is stored in vendor extensions. These cover everything from interactive components to physics, sequencing and lightmaps.
More extensions and custom extensions can be added using the export callbacks of UnityGLTF (not documented yet) and the glTF import extensions of three.js.
Note: Materials using these extensions can be exported from Unity via UnityGLTF's PBRGraph material.
Note: Audio and variants are already supported in Needle Engine through NEEDLE_components and NEEDLE_persistent_assets, but there are some options for more alignment to existing proposals such as KHR_audio and KHR_materials_variants.
For production, we compress glTF assets with glTF-transform. Textures use either etc1s, uastc, webp or no compression, depending on texture type. Meshes use draco by default but can be configured to use meshtopt (per glTF file). Custom extensions are passed through in an opaque way.
Needle Engine stores custom data in glTF files through our vendor extensions. These extensions are designed to be flexible and allow relatively arbitrary data to put into them. Notably, no code is stored in these files. Interactive components is restored from the data at runtime. This has some similarities to how AssetBundles function in Unity โ the receiving side of an asset needs to have matching code for components stored in the file.
We're currently not prodiving schemas for these extensions as they are still in development. The JSON snippets below demonstrates extension usage by example and includes notes on architectural choices and what we may change in future releases.
References between pieces of data are currently constructed through a mix of indices into other parts of the glTF file and JSON pointers. We may consolidate these approaches in a future release. We're also storing string-based GUIDs for cases where ordering is otherwise hard to resolve (e.g. two components referencing each other) that we may remove in the future.
This extension contains per-node component data. The component names map to type names on both the JavaScript and C# side. Multiple components with the same name can be added to the same node.
Data in NEEDLE_components can be animated via the currently not ratified KHR_animation_pointer extension.
Note: Storing only the component type name means that type names currently need to be unique per project. We're planning to include package names in a future release to loosen this constraint to unique component type names per package instead of globally.
Note: Currently there's no versioning information in the extension (which npm packaage does a component belong to, which version of that package was it exported against). We're planning to include versioning information in a future release.
Note: Currently all components are in the builtin_components array. We might rename this to just components in a future release.
This extension contains additional per-node data related to state, layers, and tags. Layers are used for both rendering and physics, similar to how three.js and Unity treat them.
Components in NEEDLE_components can reference data via JSON Pointers. The data in NEEDLE_persistent_assets is often referenced multiple times by different components and is thus stored separately in a root extension. By design, they are always referenced by something else (or have references within each other), and thus do not store type information at all: they're simply pieces of JSON data and components referencing them currently need to know what they expect.
Examples for assets/data stored in here are:
AnimatorControllers, their layers and states
PlayableAssets (timelines), their tracks and embedded clips
SignalAssets
...
Data in persistent_assets can reference other persistent_assets via JSON Pointer, but by design can't reference NEEDLE_components. This is similar to the separation beween "Scene data" and "AssetDatabase content" in Unity.
This extension builds upon the archived KHR_techniques_webgl extension and extends it in a few crucial places. While the original extension was specified against WebGL 1.0, we're using it with WebGL 2.0 here and have added a number of uniform types.
While Unity's compilation process from C# to IL to C++ (via IL2CPP) to WASM (via emscripten) is ingenious, it's also relatively slow. Building even a simple project to WASM takes many minutes, and that process is pretty much repeated on every code change. Some of it can be avoided through clever caching and ensuring that dev builds don't try to strip as much code, but it still stays slow.
We do have a prototype for some WASM translation, but it's far from complete and the iteration speed stays slow, so we are not actively investigating this path right now.
When looking into modern web workflows, we found that code reload times during development are neglectible, usually in sub-second ranges. This of course trades some performance (interpretation of JavaScript on the fly instead of compiler optimization at build time) for flexibility, but browsers got really good at getting the most out of JavaScript.
We believe that for iteration and tight testing workflows, it's beneficial to be able to test on device and on the target platform (the browser, in this case) as quickly and as often as possible - which is why we're skipping Unity's entire play mode, effectively always running in the browser.
Note: A really nice side effect is avoiding the entire slow "domain reload" step that usually costs 15-60 seconds each time you enter Play Mode. You're just "live" in the browser the moment you press Play.
This is the best thing I have seen after cinemachine in unity. Unity should acquire this โ Rinesh Thomas
Unbelievable Unity editor integration by an order of magnitude, and as straightforward as the docs claim. Wow. โ Chris Mahoney
needle.tools is a wonderful showcase of what @NeedleTools contributes to 3D via the web. I just love it. โ Kevin Curry
Thanks to @NeedleTools, seeing quite a bit of this solution for web-based real time 3d tools - export scenes from Unity, where you can leverage the extensive 3d editor ecosystem & content, and then render them in your own web-based engine โ Stella Cannefax
Played with this a bit this morning ๐คฏ๐คฏ pretty magical โ Brit Gardner
This is huge for WebXR and shared, immersive 3D experiences! Thank you so much for putting in the work on this @NeedleTools crew! Hoping @Apple sort out their WebXR situation sooner rather than later. The AR part worked flawlessly on my @SamsungMobile S21. โ Marc Wakefield
Finally checking out @NeedleTools with Unity. Super easy to get something up and running in the cloud using their glitch integration โ Pete Patterson
This is amazing and if you are curious about #WebXR with Unity this will help us get there โ Dilmer Valecillos
I am a long time Unity dev and recently started playing with Needle Tools and I love it! It's a great on ramp for Unity devs who want to learn WebXR and three.js. The runtime engine is awesome and it was pretty easy to create my own custom component โ VRSpatialist
Spent the last 2.5 months building this game, never built a game/never used unity before, but absolutely loving the whole process with needle tools. So rapid! Would love to make a career building AR experiences! โ Matthew Pieri
My workflow has been optimized 10X ever since i started using needle โ Yuzu
When using our templates, Needle Engine runs a local development server for you. Simply press play, and a website will open in your default browser, ready for testing on your local device. For testing on other devices in the same network, you may have to install a self-signed certificate (see below).
When using a custom setup / framework, please refer to your framework's documentation on how to run a secure local development server.
Tips
Our local server uses the IP address in your local network (e.g. https://192.168.0.123:3000) instead of localhost:3000. This allows you to quickly view and test your project from mobile devices, VR glasses, and other machines in the same network.
We're using HTTPS instead of the older HTTP, because modern powerful web APIs like WebXR require a secure connection โ the S stands for "secure".
Different operating systems have different security requirements for local development. Typically, displaying a website will work even with a auto-generated untrusted certificate, but browsers may warn about the missing trust and some features are not available. Here's a summary:
Tips
Installing trusted self-signed certificates on your development devices is recommended for a smooth development experience. Find the steps at the bottom of this page.
Default โ with auto-generated untrusted certificate Displays a certificate warning upon opening the project in a browser.Uses the vite-plugin-basic-ssl npm package.
We're using websocket connections to communicate between the browser and the local development server. Websockets require a secure connection (HTTPS) in order to work. Depending on the platform, you might need to set up a custom certificate for local development. Android and Windows don't need a custom certificate, but iOS and MacOS do.
OS
Viewing in the browser (with a security warning)
Automatic reloads
Windows
(โ)
โ
Linux
(โ)
โ
Android
(โ)
โ
macOS
(โ)
โ
iOS
(โ Safari and Chrome, after page reload) โ Mozilla XR Viewer
โ
Xcode Simulators
(โ after page reload)
โ
With a self-signed, trusted root certificate No security warning is displayed. You need to install the generated certificate on your device(s). Uses the vite-plugin-mkcert npm package.
in Unity/Blender, click on "Open Workspace" to open VS Code
check that you're using vite-plugin-mkcert instead of vite-plugin-basic-ssl (the latter doesn't generate a trusted root certificate, which we need) in your vite.config.ts file:
open package.json
remove the line with "@vitejs/plugin-basic-ssl" from devDependencies
open a Terminal in VS Code and run npm install vite-plugin-mkcert --save-dev which will add the latest version
open vite.config.ts and replace import basicSsl from '@vitejs/plugin-basic-ssl' with import mkcert from'vite-plugin-mkcert'
in plugins: [, replace basicSsl(), with mkcert(),
package.json looks like this now:
run npm run start once from VS Code's terminal
on Windows, this will open a new window and guide you through the creation and installation of the certificate
on MacOS, the terminal prompts for your password and then generates and installs the certificate.
if you're getting an error Error: Port 3000 is already in use, please close the server that may still be running from Unity.
the generated certificate will automatically be installed on the machine you generated it on.
you can stop the terminal process again.
from now on, pressing Play in Unity/Blender will use the generated certificate for the local server, and no "security warning" will be shown anymore, since your browser now trusts the local connection.
On your development devices, you need to install the generated certificate and allow the OS to trust it. This is different for each OS. For each of them, you'll need the rootCA.pem file that was generated, and send it to the device you want to authenticate.
On Windows: find the certificate in Users/<your-user>/.vite-plugin-mkcert/rootCA.pem On MacOS: find the certificate in Users/<your-user>/.vite-plugin-mkcert/rootCA.pem
Send the device to yourself (e.g. via E-Mail, AirDrop, iCloud, USB, Slack, ...) so that you can access it on your development devices.
You'll be prompted to add the profile to your device. Confirm.
Go to Settings
There will be a new entry "Profile". Select it and allow the profile to be active for this device.
On iOS / iPadOS, you also need to allow "Root Certificate Trust". For this, search for Trust or go to Settings > General > About > Info > Certificate Trust Settings and enable full trust for the root certificate.
Tips
The certificate is automatically installed on the machine you generated it on. For other machines in the local network, follow the steps below to also establish a trusted connection.
Needle Engine provides an easy-to-use web component that can be used to display rich, interactive 3D scenes directly in HTML with just a few lines of code. It's the same web component that powers our integrations.
Once you outgrow the configuration options of the web component, you can extend it with custom scripts and components, and full programmatic scene graph access.
Use the integrations!
For complex 3D scenes and fast iteration, we recommend using Needle Engine with one of our integrations. They provide a powerful creation workflow, including a live preview, hot reloading, and an advanced build pipeline with 3D optimizations.
<!DOCTYPE html>
+<html>
+<head>
+ <!-- Import the Needle Engine module -->
+ <script
+ type="module"
+ src="https://unpkg.com/@needle-tools/engine/dist/needle-engine.min.js">
+ </script>
+</head>
+<body style="margin:0; padding:0;">
+ <!--
+ Add the <needle-engine> HTML component to your page, and specify a source file.
+ This .glb file contains interactions, sounds, a skybox, and animations,
+ because it was exported from our Unity integration.
+ -->
+ <needle-engine src="https://cloud.needle.tools/api/v1/public/873a48a/10801b111/MusicalInstrument.glb" background-blurriness="0.8"></needle-engine>
+</body>
+</html>
You can work directly with Needle Engine without using any Integration. Needle Engine uses three.js as scene graph and rendering library, so all functionality from three.js is available in Needle as well.
You can install Needle Engine from npm by running:
While our default template uses vite, Needle Engine can also be used directly with vanilla Javascript โ without using any bundler.
You can add a complete, prebundled version of Needle Engine to your website with just a line of code. This includes our core components, physics, particles, networking, XR, and more. Use this if you're not sure!
If you know your project doesn't require physics features, you can also use a smaller version of Needle Engine, without the physics engine. This will reduce the total downloaded size.
If you want to work with Needle Engine without any integration, then you'll likely want to run a local server for your website. Here's how you can do that with Visual Studio Code:
Open the folder with your HTML file in Visual Studio Code.
Since Needle Engine uses three.js as scene graph and rendering library, all functionality from three.js is available in Needle as well and can be used from component scripts. We're using a fork of three.js that includes additional features and improvements, especially in relation to WebXR, Animation, and USDZ export.
Tips
Make sure to update the <needle-engine src="myScene.glb"> path to an existing glb file or download this sample glb and put it in the same folder as the index.html, name it myScene.glb or update the src path.
<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8" />
+ <link rel="icon" href="favicon.ico">
+ <meta name="viewport" content="width=device-width, user-scalable=no">
+ <title>Made with Needle</title>
+
+ <!-- importmap -->
+ <script type="importmap">
+ {
+ "imports": {
+ "three": "https://unpkg.com/three/build/three.module.js",
+ "three/": "https://unpkg.com/three/"
+ }
+ }
+ </script>
+ <!-- parcel require must currently defined somewhere for peerjs -->
+ <script> var parcelRequire; </script>
+
+ <!-- the .light version does not include dependencies like Rapier.js (so no physics) to reduce the bundle size, if your project needs physics then just change it to needle-engine.min.js -->
+ <script type="module" src="https://unpkg.com/@needle-tools/engine@3.5.6-alpha/dist/needle-engine.light.min.js"></script>
+
+ <style>
+ body { margin: 0; }
+ needle-engine {
+ position: absolute;
+ display: block;
+ width: 100%;
+ height: 100%;
+ background: grey;
+ }
+ </style>
+
+</head>
+
+<body>
+
+ <!-- load a gltf or glb here as your scene and listen to the finished event to start interacting with it -->
+ <needle-engine src="myScene.glb" loadfinished="onLoaded"></needle-engine>
+
+</body>
+
+<script>
+ function onLoaded(ctx) {
+ console.log("Loading a glb file finished ๐");
+ console.log("This is the scene: ", ctx.scene);
+ }
+</script>
+
+</html>
Needle allows for a very fast, iterative workflow between Unity and the browser. Usually, exports take less than a few seconds. However, once scenes become more complex, for some types of changes (adjusting material properties, nudging objects around), we provide an even faster way to see your changes โ Editor Sync.
You can enable Editor Sync by adding the EditorSync component to your scene. This component will connect your Unity Editor with your browser project and automatically sync applicable changes between the two.
Tips
Editor Sync is currently an experimental feature. Please let us know about your experience with it! We're eager to hear your feedback.
Drop the downloaded .unitypackage file into a Unity project and confirm that you want to import it.
Wait a moment for the installation and import to finish. A window may open stating that "A new scoped registry is now available in the Package Manager.". This is our Needle Package registry. You can safely close that window.
Explore Samples. Select the menu option Needle Engine > Explore Samples to view, open and modify all available sample scenes.
There are 100+ samples that cover a wide range of topics, use cases, and industries. For a quick overview, take a look at our Samples page.
All of these samples are available directly in Unity:
Go to Needle Engine > Explore Samples to browse for samples
Click "Install Samples" to install the samples package right inside your editor.
Choose any sample and click on Open Scene.
The Samples are read-only โ that makes them easy to update.
Our samples scenes are part of a UPM package in Unity. This means that you can't edit the assets and scripts in them directly โ they are read-only. To edit an asset from the samples package, copy it into your project's Assets folder. To edit a script from the samples package, copy it into your web project's src folder.
We provide a number of Scene Templates for quickly starting new projects. These allow you to go from idea to prototype in a few clicks.
Click on File > New Scene
Select one of the templates with (needle) in their name and click Create. We recommend the Collaborative Sandbox template which is a great way to get started with interactivity, multiplayer, and adding assets.
Click Play to install and startup your new web project.
If you don't want to start from a scene template, you can follow these steps. Effectively, we're going to recreate the "Minimal (Needle)" template that's shipping with the package.
Create a new empty scene
Set up your scene for exporting Add an empty GameObject, name it "Exporter" and add an ExportInfo component to it. In this component you create and quickly access your exported runtime project. It also warns you if any of our packages and modules are outdated or not locally installed in your web project.
Project Name and Scene Name
By default, the project name matches the name of your scene. If you want to change that, you can pick or enter a Directory Name where you want to create your new web project. The path is relative to your Unity project.
Choose a web project template Now, select a web project template for your project. The default template is based on Vite, a fast web app bundler.
Click Play to install and start your new web project
Define your own templates
If you find yourself creating many similar projects, you can create your own local or remote templates using the Project View context menu under Create/Needle Engine/Project Template. Templates can either be local on disk (a folder being copied) or remote repositories (a git repository being cloned).
This is where project specific/exclusive assets live.
Packages
This is where packages installed for this project live. A package can contain any asset type. The main difference is that it can be added to multiple Unity projects. It therefor is a great method to share code or assets. To learn more about packages see the Unity documentation about packages.
Needle Engine Unity Package
Core/Runtime/Components
Contains all Needle Engine built-in components. Learn more about them in the Components Reference.
When creating a new web project in Unity, you can choose to create it from a local template (by default we ship a vite based web template).
You can also reference remote templates by entering a repository URL in the ExportInfo project path (this can be saved with your scene for example). When creating a new web project the repository will be either cloned or downloaded (depending on if you have git installed) and searched for a needle.config.json file. If none can be found in the cloned repository the root directory will be used. Examples of remote template projects can be found on github.com/needle-engine
If you're planning to only add custom files via NpmDefs and not change the project config (e.g. for a quick fullscreen test), you can prefix the project path with Library. The project will be generated in the Unity Project Library and does not need to be added to source control (the Library folder should be excluded from source control). We call these projects temporary projects. They're great for quickly testing out ideas!
NPM Definition are npm packages tightly integrated into the Unity Editor which makes it easily possible to share scripts with multiple web- or even Unity projects.
C# component stubs for typescript files will also be automatically generated for scripts inside npmdef packages.
To create a NPM Definition right click in the Unity Project browser and select Create/NPM Definition. You can install a NPM Definition package to your runtime project by e.g. selecting your Export Info component and adding it to the dependencies list (internally this will just add the underlying npm package to your package.json).
Don't forget to install the newly added package by e.g. clicking Install on the ExportInfo component and also restart the server if it is already running
To edit the code inside a NPM Definition package just double click the asset NPM Definition asset in your project browser and it will open the vscode workspace that comes with each npmdef.
+
+
+
diff --git a/vanilla-js.html b/vanilla-js.html
new file mode 100644
index 000000000..c7a65358f
--- /dev/null
+++ b/vanilla-js.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+ Using Needle Engine directly from HTML | Needle Engine Documentation
+
+
+
+
+
+
We believe the use of 3D on the web will expand considerably in the next years. While today native apps are the norm, more and more content is made available as a web app or PWA. New VR and AR devices will extend into the web, creating an interesting problem: responsive suddenly not only means "small screen" or "large screen", you're also dealing with spaces, 3D, spatial placement and potentially glasses and controllers!
Add to that a push towards more interactivity and collaboration, and you have an interesting mix of challenges.
At Needle, we believe ideating and creating in this space should be easy. We've set out to speed things up โ creating our own runtime to reach these goals. That's why we're baking the ability to deploy to AR and VR right into our core components, and continually test that new ideas work across platforms.
There's numerous options, that's true! We found that current systems1 can be roughly sorted into two categories: some have great asset handling, tools, and artist-friendly workflows but output some sort of binary blob, and others are more code-focussed, developer-friendly and allow for great integration into modern web workflows2.
We want to bridge these worlds and combine the best of both worlds: artist-friendly workflows and modern web technologies. Combined with modern formats and a snappy workflow, we believe this will allow many more creators to bring their content to the web. We also saw an opportunity to get AR, VR and collaboration right from the start.
1: Examples include Unity, PlayCanvas, three.js, react-three-fiber, Babylon, A-Frame, Godot, and many more.2: There's more nuance to this than fits into an introductory paragraph! All engines and frameworks have their strengths and weaknesses, and are constantly evolving.
We think the next wave of 3D apps on the web will come with better workflows: everyone should be able to put together a 3D scene, an art gallery, present a product or 3D scan on the web or make simple games. Reaching this goal will require more than just supporting one particular system and exporting to the web from there.
Our goal is to allow people to bring data to the web from their creative tools: be it Unity, Blender, Photoshop, or something else. We're aware that this is a big goal โ but instead of doing everything at once, we want to iterate and get closer to it together.
At the core of Needle Engine stands the glTF format and its ability to be extended with custom extensions. The goal is: a single .glb file can contain your entire application's data.
It's worth noting that it's not a goal to ship actual code inside glTF; shipping and running code is the job of modern web runtimes and bundling. We certainly can imagine that abstract representations of logic (e.g. graphs, state machines, and so on) can be standardized to a certain degree and allow for interoperable worlds, but we're not there yet.
From working with Unity for many years we've found that while the engine and editor progress at a great pace, WebGL output has somewhat lacked behind. Integration of Unity players into web-based systems is rather hard, "talking" to the surrounding website requires a number of workarounds, and most of all, iteration times are very slow due to the way that Unity packs all code into WebAssembly via IL2CPP. These technologies are awesome, and result in great runtime performance and a lot of flexibility. But they're so much slower and walled off compared to modern web development workflows that we decided to take matters into our own hands.
Needle Engine builds on three.js. All rendering goes through it, glTF files are loaded via three's extension interfaces, and our component system revolves around three's Object3D and scene graph. We're committed to upstreaming some of our changes and improvements, creating pull requests and reporting issues along the way.
Theoretically all WebXR-capable devices and browsers should be supported. That being said, we've tested the following configurations:
Tested VR Device
Browser
Notes
Apple Vision Pro
โ๏ธ Safari Browser
hand tracking, support for transient pointer
Meta Quest 1
โ๏ธ Meta Browser
hand tracking, support for sessiongranted1
Meta Quest 2
โ๏ธ Meta Browser
hand tracking, support for sessiongranted1, passthrough (black and white)
Meta Quest 3
โ๏ธ Meta Browser
hand tracking, support for sessiongranted1, passthrough, depth sensing, mesh tracking
Meta Quest Pro
โ๏ธ Meta Browser
hand tracking, support for sessiongranted1, passthrough
Pico Neo 3
โ๏ธ Pico Browser
no hand tracking, inverted controller thumbsticks
Pico Neo 4
โ๏ธ Pico Browser
passthrough, hand tracking2
Oculus Rift 1/2
โ๏ธ Chrome
Hololens 2
โ๏ธ Edge
hand tracking, support for AR and VR (in VR mode, background is rendered as well)
Looking Glass Portrait
โ๏ธ Chrome
requires shim, see samples
Tested AR Device
Browser
Notes
Android 10+
โ๏ธ Chrome
Android 10+
โ๏ธ Firefox
iOS 15+
โ๏ธ WebXR Viewer
does not fully implement standards, but supported
iOS 15+
(โ๏ธ)3 Safari
No full code support, but Needle Everywhere Actions are supported for creating dynamic, interactive USDZ files.
Hololens 2
โ๏ธ Edge
Hololens 1
โ
no WebXR support
Magic Leap 2
โ๏ธ
Not Tested but Should Workโข๏ธ
Browser
Notes
Magic Leap 1
please let us know if you tried!
1: Requires enabling a browser flag: chrome://flags/#webxr-navigation-permission 2: Requires enabling a toggle in the Developer settings 3: Uses Everywhere Actions or other approaches
AR, VR and networking capabilites in Needle Engine are designed to be modular. You can choose to not support any of them, or add only specific features.
Enable AR and VR Add a WebXR component. Optional: you can set a custom avatar by referencing an Avatar Prefab. By default a very basic DefaultAvatar is assigned.
Enable Teleportation Add a TeleportTarget component to object hierarchies that can be teleported on. To exclude specific objects, set their layer to IgnoreRaycasting.
Define the AR Session Root and Scale Add a WebARSessionRoot component to your root object. Here you can define the user scale to shrink (< 1) or enlarge (> 1) the user in relation to the scene when entering AR.
Define whether an object is visible in Browser, AR, VR, First Person, Third Person Add a XR Flag component to the object you want to control. Change options on the dropdown as needed.
Common usecases are
hiding floors when entering AR
hiding Avatar parts in First or Third Person views (e.g. first-person head shouldn't be visible).
Needle Engine supports the sessiongranted state. This allows users to seamlessly traverse between WebXR applications without leaving an immersive session โ they stay in VR or AR.
Currently, this is only supported on Oculus Quest 1, 2 and 3 in the Oculus Browser. On other platforms, users will be kicked out of their current immersive session and have to enter VR again on the new page. Requires enabling a browser flag: chrome://flags/#webxr-navigation-permission
Click on objects to open links Add the OpenURL component that makes it very easy to build connected worlds.
There's a number of experimental components to build more expressive Avatars. At this point we recommended duplicating them to make your own variants, since they might be changed or removed at a later point.
Example Avatar Rig with basic neck model and limb constraints
Random Player Colors As an example for avatar customization, you can add a PlayerColor component to your renderers. This randomized color is synchronized between players.
Eye Rotation AvatarEyeLook_Rotation rotates GameObjects (eyes) to follow other avatars and a random target. This component is synchronized between players.
Eye Blinking AvatarBlink_Simple randomly hides GameObjects (eyes) every few seconds, emulating a blink.
Example Avatar Prefab hierarchy
Offset Constraint OffsetConstraint allows to shift an object in relation to another one in Avatar space. This allows, for example, to have a Body follow the Head but keep rotation levelled. It also allows to construct simple neck models.
Limb Constraint BasicIKConstraint is a very minimalistic constraint that takes two transforms and a hint. This is useful to construct simple arm or leg chains. As rotation is currently not properly implemented, arms and legs may need to be rotationally symmetric to "look right". It's called "Basic" for a reason!
If you want to display different html content whether the client is using a regular browser or using AR or VR, you can just use a set of html classes. This is controlled via HTML element classes. For example, to make content appear on desktop and in AR add a <div class="desktop ar"> ... </div> inside the <needle-engine> tag:
<needle-engine>
+ <div class="desktop ar" style="pointer-events:none;">
+ <div class="positioning-container">
+ <p>your content for AR and desktop goes here</p>
+ <p class="only-in-ar">This will only be visible in AR</p>
+ <div>
+ </div>
+</needle-engine>
Content Overlays are implemented using the optional dom-overlay feature which is usually supported on screen-based AR devices (phones, tablets).
Use the .ar-session-active class to show/hide specific content while in AR. The :xr-overlay pseudo class shouldn't be used at this point because using it breaks Mozilla's WebXR Viewer.
It's worth noting that the overlay element will be always displayed fullscreen while in XR, independent of styling that has been applied. If you want to align items differently, you should make a container inside the class="ar" element.
Needle Engine supports WebXR ImageTracking (Live Demo) Note: While WebXR ImageTracking is still in "draft" phase (Marker Tracking Explainer) you need to follow these steps to enable WebXR ImageTracking on Android devices:
Augmented Reality experiences on iOS are somewhat limited, due to Apple currently not supporting WebXR on iOS devices.
Needle Engine's Everywhere Actions are designed to fill that gap, bringing automatic interactive capabilities to iOS devices for scenes composed of specific components. They support a subset of the functionality that's available in WebXR, for example spatial audio, image tracking, animations, and more. See the docs for more information.
Here's an example for a musical instrument that uses Everywhere Actions and thus works in browsers and in AR on iOS devices. It uses spatial audio, animation, and tap interactions.
There's also other options for guiding iOS users to even more capable interactive AR experiences:
Exporting content on-the-fly as USDZ files. These files can be displayed on iOS devices in AR. When exported from scenes with Everywhere Actions the interactivity is the same, more than sufficient for product configurators, narrative experiences and similar. An example is Castle Builder where creations (not the live session) can be viewed in AR.
Encryption in Space uses this approach. Players can collaboratively place text into the scene on their screens and then view the results in AR on iOS. On Android, they can also interact right in WebXR. โ #madewithneedle by Katja Rempel ๐
Guiding users towards WebXR-compatible browsers on iOS. Depending on your target audience, you can guide users on iOS towards for example Mozilla's WebXR Viewer to experience AR on iOS.
Using camera access and custom algorithms on iOS devices. One can request camera image access and run custom algorithms to determine device pose. While we currently don't provide built-in components for this, here's a few references to libraries and frameworks that we want to try in the future: