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..2a4bd5eb0 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..2927c5e7a 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..9a6c94df7 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..6fd280ff8 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..655dc2678 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..f9cc5e503 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..449ef0126 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..613aa1f8c Binary files /dev/null and b/.preview/bike configurator.png differ diff --git a/.preview/built in components.png b/.preview/built in components.png new file mode 100644 index 000000000..eecf323ed Binary files /dev/null and b/.preview/built in components.png differ diff --git a/.preview/castle builder.png b/.preview/castle builder.png new file mode 100644 index 000000000..d4b159438 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..9dac3ca56 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..3380cd3b2 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..75aa59046 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..6fb1eee2e 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..465f693df 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..c66f8136e 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..8beb7e09d 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..9c5744e9c 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..df29ba06b Binary files /dev/null and b/.preview/creating and using components.png differ diff --git a/.preview/deployment and optimization.png b/.preview/deployment and optimization.png new file mode 100644 index 000000000..6667bdd36 Binary files /dev/null and b/.preview/deployment and optimization.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..daf3619e5 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..fc652d244 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..66e5a0e54 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..8b438a773 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..67d405033 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..ed36fec13 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..4f9c427ce 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..c14a4e134 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..dc115e3cb 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..051eff815 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..4a2e7ee8d 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..a63d90a5d 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..bb9dbfc87 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..f915a0a81 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..e2bc9d667 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..5979a09f7 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..35bc9519e 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..5705f7d27 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..426060718 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..dff6fd160 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..84746fd5b Binary files /dev/null and b/.preview/monster hands.png differ diff --git a/.preview/needle engine for blender.png b/.preview/needle engine for blender.png new file mode 100644 index 000000000..8ae37d31d Binary files /dev/null and b/.preview/needle engine for blender.png differ diff --git a/.preview/needle engine.png b/.preview/needle engine.png new file mode 100644 index 000000000..c97f572a5 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..69b6bb15c 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..b9a14da06 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..0633a549d 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..3fe0a2733 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..5c97d2ebc 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..2d4233f08 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..a3a1a8535 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..69ca9d938 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..e72bfe407 Binary files /dev/null and b/.preview/summary.png differ diff --git a/.preview/technical overview.png b/.preview/technical overview.png new file mode 100644 index 000000000..d4827a3a4 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..1d1d3662e 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..b922006c9 Binary files /dev/null and b/.preview/testing on local devices.png differ diff --git a/.preview/tower defense.png b/.preview/tower defense.png new file mode 100644 index 000000000..f68125071 Binary files /dev/null and b/.preview/tower defense.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..a3c202165 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..668d664a8 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..d2e4ecf38 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..e18a5f413 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..597e29cab 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..fddf9dfc2 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..b9dd744a4 --- /dev/null +++ b/404.html @@ -0,0 +1,33 @@ + + + + + + + + + Documentation + + + + +

404

Gosh! You found a ๐ŸŒต glitch
Take me home
+ + + diff --git a/SUMMARY.html b/SUMMARY.html new file mode 100644 index 000000000..040b2cdc1 --- /dev/null +++ b/SUMMARY.html @@ -0,0 +1,33 @@ + + + + + + + + + Get Started | 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-959b6c1d.js b/assets/404.html-959b6c1d.js new file mode 100644 index 000000000..ec13434b5 --- /dev/null +++ b/assets/404.html-959b6c1d.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3706649a","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":"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{e as data}; diff --git a/assets/404.html-dca3a92c.js b/assets/404.html-dca3a92c.js new file mode 100644 index 000000000..3419c4990 --- /dev/null +++ b/assets/404.html-dca3a92c.js @@ -0,0 +1 @@ +import{_ as e,p as t,q as _}from"./framework-f6820c83.js";const c={};function r(n,o){return t(),_("div")}const a=e(c,[["render",r],["__file","404.html.vue"]]);export{a as default}; diff --git a/assets/SUMMARY.html-293245e7.js b/assets/SUMMARY.html-293245e7.js new file mode 100644 index 000000000..587389cef --- /dev/null +++ b/assets/SUMMARY.html-293245e7.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-1179968c","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":1684748013000},"filePathRelative":"SUMMARY.md"}');export{e as data}; diff --git a/assets/SUMMARY.html-4cbcb4ca.js b/assets/SUMMARY.html-4cbcb4ca.js new file mode 100644 index 000000000..67a179347 --- /dev/null +++ b/assets/SUMMARY.html-4cbcb4ca.js @@ -0,0 +1 @@ +import{_ as o,M as u,p as r,q as i,R as e,N as a,V as n,t as l}from"./framework-f6820c83.js";const d={},s=e("h1",{id:"get-started",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#get-started","aria-hidden":"true"},"#"),l(" Get Started")],-1),h=e("h1",{id:"samples",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#samples","aria-hidden":"true"},"#"),l(" Samples")],-1),m=e("h1",{id:"dive-deeper",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#dive-deeper","aria-hidden":"true"},"#"),l(" Dive Deeper ๐Ÿ ")],-1);function c(_,f){const t=u("RouterLink");return r(),i("div",null,[s,e("ul",null,[e("li",null,[a(t,{to:"/getting-started.html"},{default:n(()=>[l("Getting Started โญ")]),_:1}),e("ul",null,[e("li",null,[a(t,{to:"/features-overview.html"},{default:n(()=>[l("Feature Overview")]),_:1})]),e("li",null,[a(t,{to:"/export.html"},{default:n(()=>[l("Export")]),_:1})]),e("li",null,[a(t,{to:"/project-structure.html"},{default:n(()=>[l("Project Structure")]),_:1})]),e("li",null,[a(t,{to:"/xr.html"},{default:n(()=>[l("VR and AR")]),_:1})]),e("li",null,[a(t,{to:"/scripting.html"},{default:n(()=>[l("Scripting")]),_:1})]),e("li",null,[a(t,{to:"/html.html"},{default:n(()=>[l("HTML")]),_:1})]),e("li",null,[a(t,{to:"/deployment.html"},{default:n(()=>[l("Deployment")]),_:1})])])])]),h,e("ul",null,[e("li",null,[a(t,{to:"/samples-and-modules.html"},{default:n(()=>[l("Samples and Examples")]),_:1}),e("ul",null,[e("li",null,[a(t,{to:"/for-unity-developers.html"},{default:n(()=>[l("Tips for Unity Developers")]),_:1})]),e("li",null,[a(t,{to:"/examples.html"},{default:n(()=>[l("Showcase")]),_:1})]),e("li",null,[a(t,{to:"/samples-and-modules.html"},{default:n(()=>[l("Project Samples, Examples and Modules")]),_:1})]),e("li",null,[a(t,{to:"/faq.html"},{default:n(()=>[l("FAQ")]),_:1})])])])]),m,e("ul",null,[e("li",null,[a(t,{to:"/vision.html"},{default:n(()=>[l("Our Vision")]),_:1}),e("ul",null,[e("li",null,[a(t,{to:"/technical-overview.html"},{default:n(()=>[l("Technical Overview")]),_:1})]),e("li",null,[a(t,{to:"/component-reference.html"},{default:n(()=>[l("Component Reference")]),_:1})]),e("li",null,[a(t,{to:"/debugging.html"},{default:n(()=>[l("Debugging")]),_:1})])])])])])}const v=o(d,[["render",c],["__file","SUMMARY.html.vue"]]);export{v as default}; diff --git a/assets/action-ca178bd9.js b/assets/action-ca178bd9.js new file mode 100644 index 000000000..0b5f45532 --- /dev/null +++ b/assets/action-ca178bd9.js @@ -0,0 +1 @@ +import{_ as o,p as s,q as n,s as _}from"./framework-f6820c83.js";const a={props:{href:String}},c=["href"];function r(e,f,t,d,i,p){return s(),n("a",{class:"action",href:t.href},[_(e.$slots,"default",{},void 0,!0)],8,c)}const u=o(a,[["render",r],["__scopeId","data-v-1f05b024"],["__file","action.vue"]]);export{u as default}; diff --git a/assets/actiongroup-1feb3d4e.js b/assets/actiongroup-1feb3d4e.js new file mode 100644 index 000000000..e07f2cef4 --- /dev/null +++ b/assets/actiongroup-1feb3d4e.js @@ -0,0 +1 @@ +import{_ as o,p as t,q as s,s as n}from"./framework-f6820c83.js";const _={},a={class:"actiongroup"};function c(e,r,p,i,u,l){return t(),s("div",a,[n(e.$slots,"default")])}const f=o(_,[["render",c],["__file","actiongroup.vue"]]);export{f as default}; diff --git a/assets/app-1ecc50a8.js b/assets/app-1ecc50a8.js new file mode 100644 index 000000000..d9f5d6a29 --- /dev/null +++ b/assets/app-1ecc50a8.js @@ -0,0 +1,19 @@ +import{d as I,r as ie,a as Zo,b as fi,i as je,c as Jt,e as pi,f as Yo,g as W,o as we,h as C,j as Z,k as Qt,l as Xo,m as qe,u as at,n as ea,T as tr,p as A,q as R,s as Q,t as _t,v as ce,w as Ie,x as ta,_ as te,y as vi,z as H,A as Ge,B as hi,C as nr,D as _i,E as gi,F as yi,G as bi,H as rr,I as na,J as Ei,K as or,L as Ze,M as ze,N as Y,O as Oe,P as Je,Q as ae,R as U,S as ar,U as le,V as me,W as Sr,X as Oi,Y as wi,Z as En,$ as On,a0 as ra,a1 as Si,a2 as Pi,a3 as Pr,a4 as Ii,a5 as ki,a6 as Di,a7 as Li,a8 as Ai}from"./framework-f6820c83.js";const Ti="modulepreload",ji=function(e){return"/docs/"+e},Ir={},E=function(t,n,r){if(!n||n.length===0)return t();const o=document.getElementsByTagName("link");return Promise.all(n.map(a=>{if(a=ji(a),a in Ir)return;Ir[a]=!0;const l=a.endsWith(".css"),i=l?'[rel="stylesheet"]':"";if(!!r)for(let c=o.length-1;c>=0;c--){const d=o[c];if(d.href===a&&(!l||d.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${a}"]${i}`))return;const u=document.createElement("link");if(u.rel=l?"stylesheet":Ti,l||(u.as="script",u.crossOrigin=""),u.href=a,document.head.appendChild(u),l)return new Promise((c,d)=>{u.addEventListener("load",c),u.addEventListener("error",()=>d(new Error(`Unable to preload CSS for ${a}`)))})})).then(()=>t())},Ci={"v-1179968c":()=>E(()=>import("./SUMMARY.html-293245e7.js"),[]).then(({data:e})=>e),"v-45b6c339":()=>E(()=>import("./backlog-mermaid.html-43151d51.js"),[]).then(({data:e})=>e),"v-66ba7b75":()=>E(()=>import("./backlog.html-5f538ac1.js"),[]).then(({data:e})=>e),"v-0ca6c8f8":()=>E(()=>import("./meta-test.html-32ee71cc.js"),[]).then(({data:e})=>e),"v-5572989e":()=>E(()=>import("./component-compiler.html-a9955c85.js"),[]).then(({data:e})=>e),"v-d8eff192":()=>E(()=>import("./component-reference.html-47d337de.js"),[]).then(({data:e})=>e),"v-f588dc38":()=>E(()=>import("./debugging.html-0370ea20.js"),[]).then(({data:e})=>e),"v-4d21151b":()=>E(()=>import("./deployment.html-a77d8c0c.js"),[]).then(({data:e})=>e),"v-f2e4a738":()=>E(()=>import("./everywhere-actions.html-440cc1d9.js"),[]).then(({data:e})=>e),"v-63f25852":()=>E(()=>import("./examples.html-b9380b47.js"),[]).then(({data:e})=>e),"v-99bff5e8":()=>E(()=>import("./export.html-4f710d49.js"),[]).then(({data:e})=>e),"v-092a1d7c":()=>E(()=>import("./faq.html-e7435103.js"),[]).then(({data:e})=>e),"v-bcfe00ae":()=>E(()=>import("./features-overview.html-04daa736.js"),[]).then(({data:e})=>e),"v-7f349b5b":()=>E(()=>import("./for-unity-developers.html-916accf3.js"),[]).then(({data:e})=>e),"v-5dc4b15a":()=>E(()=>import("./getting-started.html-3c0ed39c.js"),[]).then(({data:e})=>e),"v-e554bd96":()=>E(()=>import("./html.html-c6d742ba.js"),[]).then(({data:e})=>e),"v-8daa1a0e":()=>E(()=>import("./index.html-7de73919.js"),[]).then(({data:e})=>e),"v-bb6009aa":()=>E(()=>import("./modules.html-ffce8241.js"),[]).then(({data:e})=>e),"v-dda7a368":()=>E(()=>import("./networking.html-91b3a83a.js"),[]).then(({data:e})=>e),"v-5c95b2b3":()=>E(()=>import("./project-structure.html-a65539aa.js"),[]).then(({data:e})=>e),"v-761831b6":()=>E(()=>import("./samples-and-modules.html-a3fd2522.js"),[]).then(({data:e})=>e),"v-ecef79fe":()=>E(()=>import("./scripting-examples.html-1310b8ce.js"),[]).then(({data:e})=>e),"v-fab6318a":()=>E(()=>import("./scripting.html-2ce40752.js"),[]).then(({data:e})=>e),"v-48ca6631":()=>E(()=>import("./showcase-bike.html-baf689ea.js"),[]).then(({data:e})=>e),"v-9d01ad8c":()=>E(()=>import("./showcase-castle.html-86fe547b.js"),[]).then(({data:e})=>e),"v-aa56fb8c":()=>E(()=>import("./showcase-mercedes-benz.html-555f99b8.js"),[]).then(({data:e})=>e),"v-da75c730":()=>E(()=>import("./showcase-monsterhands.html-bac5de01.js"),[]).then(({data:e})=>e),"v-3d5fe3ab":()=>E(()=>import("./showcase-towerdefence.html-c76eabe0.js"),[]).then(({data:e})=>e),"v-499adc36":()=>E(()=>import("./showcase-website.html-fad31bcf.js"),[]).then(({data:e})=>e),"v-7c306381":()=>E(()=>import("./showcase-zenrepublic.html-7fc38408.js"),[]).then(({data:e})=>e),"v-3b00a577":()=>E(()=>import("./technical-overview.html-f395cd1d.js"),[]).then(({data:e})=>e),"v-7e348068":()=>E(()=>import("./testimonials.html-e5440bb3.js"),[]).then(({data:e})=>e),"v-53401e42":()=>E(()=>import("./testing.html-4f37a182.js"),[]).then(({data:e})=>e),"v-7acec8c5":()=>E(()=>import("./vanilla-js.html-7228f453.js"),[]).then(({data:e})=>e),"v-2ace8550":()=>E(()=>import("./vision.html-9d98bfd6.js"),[]).then(({data:e})=>e),"v-099a1df4":()=>E(()=>import("./xr.html-4b92f962.js"),[]).then(({data:e})=>e),"v-c2d8b5ac":()=>E(()=>import("./index.html-ee5a34cc.js"),[]).then(({data:e})=>e),"v-63ed5104":()=>E(()=>import("./for-unity-developers.html-d7f50ac7.js"),[]).then(({data:e})=>e),"v-ccdc4da0":()=>E(()=>import("./index.html-fd183cb5.js"),[]).then(({data:e})=>e),"v-2e0e70fc":()=>E(()=>import("./typescript-essentials.html-67bd72a7.js"),[]).then(({data:e})=>e),"v-f7b8f1f2":()=>E(()=>import("./needle-config-json.html-aac10705.js"),[]).then(({data:e})=>e),"v-caed7310":()=>E(()=>import("./needle-engine-attributes.html-2b93dca4.js"),[]).then(({data:e})=>e),"v-6d28ca94":()=>E(()=>import("./typescript-decorators.html-d58d6f08.js"),[]).then(({data:e})=>e),"v-3706649a":()=>E(()=>import("./404.html-959b6c1d.js"),[]).then(({data:e})=>e),"v-165956bc":()=>E(()=>import("./index.html-0f3c5ccd.js"),[]).then(({data:e})=>e),"v-e6fbcab8":()=>E(()=>import("./index.html-615ba1f2.js"),[]).then(({data:e})=>e),"v-289be385":()=>E(()=>import("./index.html-76f967a7.js"),[]).then(({data:e})=>e),"v-44471890":()=>E(()=>import("./index.html-75e422fa.js"),[]).then(({data:e})=>e),"v-ea622532":()=>E(()=>import("./index.html-dda76fbd.js"),[]).then(({data:e})=>e),"v-28e23eea":()=>E(()=>import("./index.html-9d6db80a.js"),[]).then(({data:e})=>e),"v-1d2bf24a":()=>E(()=>import("./index.html-64bc0c3c.js"),[]).then(({data:e})=>e),"v-b5125a16":()=>E(()=>import("./index.html-45bf7509.js"),[]).then(({data:e})=>e),"v-fd5a4508":()=>E(()=>import("./index.html-8eb2326b.js"),[]).then(({data:e})=>e),"v-07e00b06":()=>E(()=>import("./index.html-0784c019.js"),[]).then(({data:e})=>e),"v-10c99314":()=>E(()=>import("./index.html-72dcd24e.js"),[]).then(({data:e})=>e),"v-acde0924":()=>E(()=>import("./index.html-d8d34b98.js"),[]).then(({data:e})=>e),"v-93aa7836":()=>E(()=>import("./index.html-3fc0407e.js"),[]).then(({data:e})=>e),"v-e9b14526":()=>E(()=>import("./index.html-6af3f72b.js"),[]).then(({data:e})=>e),"v-0c347631":()=>E(()=>import("./index.html-f975bb82.js"),[]).then(({data:e})=>e),"v-5d69cca2":()=>E(()=>import("./index.html-f2afcbd3.js"),[]).then(({data:e})=>e),"v-4fe6a858":()=>E(()=>import("./index.html-0a16bdd2.js"),[]).then(({data:e})=>e),"v-74ff5ee0":()=>E(()=>import("./index.html-1a7264fe.js"),[]).then(({data:e})=>e),"v-3721011d":()=>E(()=>import("./index.html-45b10041.js"),[]).then(({data:e})=>e),"v-a305d722":()=>E(()=>import("./index.html-9603f594.js"),[]).then(({data:e})=>e),"v-51c63c84":()=>E(()=>import("./index.html-1ac4d74c.js"),[]).then(({data:e})=>e),"v-1b71f33d":()=>E(()=>import("./index.html-0991b7d8.js"),[]).then(({data:e})=>e),"v-40fd290b":()=>E(()=>import("./index.html-7104cbee.js"),[]).then(({data:e})=>e)},xi=JSON.parse('{"base":"/docs/","lang":"en-US","title":"Documentation","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.","head":[["link",{"rel":"icon","href":"icons/favicon.ico"}],["link",{"rel":"manifest","href":"manifest.webmanifest"}],["meta",{"name":"theme-color","content":"#3eaf7c"}],["meta",{"property":"og:title","content":"Documentation"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:url","content":"https://engine.needle.tools/docs"}],["meta",{"property":"twitter:card","content":"summary_large_image"}],["script",{"src":"https://unpkg.com/@stackblitz/sdk/bundles/sdk.umd.js"}],["script",{"src":"https://analytics.needle.tools/js/script.tagged-events.outbound-links.js","defer":"","data-domain":"docs.needle.tools"}]],"locales":{}}'),oa={"v-1179968c":I(()=>E(()=>import("./SUMMARY.html-4cbcb4ca.js"),["assets/SUMMARY.html-4cbcb4ca.js","assets/framework-f6820c83.js"])),"v-45b6c339":I(()=>E(()=>import("./backlog-mermaid.html-af84429f.js"),["assets/backlog-mermaid.html-af84429f.js","assets/framework-f6820c83.js"])),"v-66ba7b75":I(()=>E(()=>import("./backlog.html-0991873b.js"),["assets/backlog.html-0991873b.js","assets/framework-f6820c83.js"])),"v-0ca6c8f8":I(()=>E(()=>import("./meta-test.html-ba3f3381.js"),["assets/meta-test.html-ba3f3381.js","assets/framework-f6820c83.js"])),"v-5572989e":I(()=>E(()=>import("./component-compiler.html-7fadcdfe.js"),["assets/component-compiler.html-7fadcdfe.js","assets/framework-f6820c83.js"])),"v-d8eff192":I(()=>E(()=>import("./component-reference.html-7433ba9c.js"),["assets/component-reference.html-7433ba9c.js","assets/framework-f6820c83.js"])),"v-f588dc38":I(()=>E(()=>import("./debugging.html-b90d9481.js"),["assets/debugging.html-b90d9481.js","assets/framework-f6820c83.js"])),"v-4d21151b":I(()=>E(()=>import("./deployment.html-679a59a7.js"),["assets/deployment.html-679a59a7.js","assets/texture-compression-8cd31165.js","assets/ktx-env-variable-d006aea1.js","assets/framework-f6820c83.js"])),"v-f2e4a738":I(()=>E(()=>import("./everywhere-actions.html-7590cd35.js"),["assets/everywhere-actions.html-7590cd35.js","assets/framework-f6820c83.js"])),"v-63f25852":I(()=>E(()=>import("./examples.html-1d035fb3.js"),["assets/examples.html-1d035fb3.js","assets/framework-f6820c83.js"])),"v-99bff5e8":I(()=>E(()=>import("./export.html-a783fce9.js"),["assets/export.html-a783fce9.js","assets/framework-f6820c83.js"])),"v-092a1d7c":I(()=>E(()=>import("./faq.html-f846be88.js"),["assets/faq.html-f846be88.js","assets/ktx-env-variable-d006aea1.js","assets/framework-f6820c83.js"])),"v-bcfe00ae":I(()=>E(()=>import("./features-overview.html-103369e6.js"),["assets/features-overview.html-103369e6.js","assets/framework-f6820c83.js"])),"v-7f349b5b":I(()=>E(()=>import("./for-unity-developers.html-b653e255.js"),["assets/for-unity-developers.html-b653e255.js","assets/framework-f6820c83.js"])),"v-5dc4b15a":I(()=>E(()=>import("./getting-started.html-9de2d56f.js"),["assets/getting-started.html-9de2d56f.js","assets/framework-f6820c83.js"])),"v-e554bd96":I(()=>E(()=>import("./html.html-42217360.js"),["assets/html.html-42217360.js","assets/custom-loading-style-65a23c0d.js","assets/framework-f6820c83.js"])),"v-8daa1a0e":I(()=>E(()=>import("./index.html-dda688da.js"),["assets/index.html-dda688da.js","assets/framework-f6820c83.js"])),"v-bb6009aa":I(()=>E(()=>import("./modules.html-d4ea4cf8.js"),["assets/modules.html-d4ea4cf8.js","assets/framework-f6820c83.js"])),"v-dda7a368":I(()=>E(()=>import("./networking.html-e1e616de.js"),["assets/networking.html-e1e616de.js","assets/framework-f6820c83.js"])),"v-5c95b2b3":I(()=>E(()=>import("./project-structure.html-ff62d7b3.js"),["assets/project-structure.html-ff62d7b3.js","assets/framework-f6820c83.js"])),"v-761831b6":I(()=>E(()=>import("./samples-and-modules.html-57e9f6ca.js"),["assets/samples-and-modules.html-57e9f6ca.js","assets/framework-f6820c83.js"])),"v-ecef79fe":I(()=>E(()=>import("./scripting-examples.html-40c60427.js"),["assets/scripting-examples.html-40c60427.js","assets/framework-f6820c83.js"])),"v-fab6318a":I(()=>E(()=>import("./scripting.html-0734bdfb.js"),["assets/scripting.html-0734bdfb.js","assets/framework-f6820c83.js"])),"v-48ca6631":I(()=>E(()=>import("./showcase-bike.html-aed7c46a.js"),["assets/showcase-bike.html-aed7c46a.js","assets/framework-f6820c83.js"])),"v-9d01ad8c":I(()=>E(()=>import("./showcase-castle.html-cdb5db57.js"),["assets/showcase-castle.html-cdb5db57.js","assets/framework-f6820c83.js"])),"v-aa56fb8c":I(()=>E(()=>import("./showcase-mercedes-benz.html-7eabd1f0.js"),["assets/showcase-mercedes-benz.html-7eabd1f0.js","assets/framework-f6820c83.js"])),"v-da75c730":I(()=>E(()=>import("./showcase-monsterhands.html-5723a356.js"),["assets/showcase-monsterhands.html-5723a356.js","assets/framework-f6820c83.js"])),"v-3d5fe3ab":I(()=>E(()=>import("./showcase-towerdefence.html-5bc7dd74.js"),["assets/showcase-towerdefence.html-5bc7dd74.js","assets/framework-f6820c83.js"])),"v-499adc36":I(()=>E(()=>import("./showcase-website.html-b68487d3.js"),["assets/showcase-website.html-b68487d3.js","assets/framework-f6820c83.js"])),"v-7c306381":I(()=>E(()=>import("./showcase-zenrepublic.html-df85526d.js"),["assets/showcase-zenrepublic.html-df85526d.js","assets/framework-f6820c83.js"])),"v-3b00a577":I(()=>E(()=>import("./technical-overview.html-3daf2543.js"),["assets/technical-overview.html-3daf2543.js","assets/framework-f6820c83.js"])),"v-7e348068":I(()=>E(()=>import("./testimonials.html-ee19aec9.js"),["assets/testimonials.html-ee19aec9.js","assets/framework-f6820c83.js"])),"v-53401e42":I(()=>E(()=>import("./testing.html-09ed932c.js"),["assets/testing.html-09ed932c.js","assets/framework-f6820c83.js"])),"v-7acec8c5":I(()=>E(()=>import("./vanilla-js.html-f7e2599d.js"),["assets/vanilla-js.html-f7e2599d.js","assets/framework-f6820c83.js"])),"v-2ace8550":I(()=>E(()=>import("./vision.html-c80404e1.js"),["assets/vision.html-c80404e1.js","assets/framework-f6820c83.js"])),"v-099a1df4":I(()=>E(()=>import("./xr.html-704d5e10.js"),["assets/xr.html-704d5e10.js","assets/framework-f6820c83.js"])),"v-c2d8b5ac":I(()=>E(()=>import("./index.html-ac7d998f.js"),["assets/index.html-ac7d998f.js","assets/logo-b695892f.js","assets/texture-compression-8cd31165.js","assets/framework-f6820c83.js"])),"v-63ed5104":I(()=>E(()=>import("./for-unity-developers.html-36af0499.js"),["assets/for-unity-developers.html-36af0499.js","assets/framework-f6820c83.js"])),"v-ccdc4da0":I(()=>E(()=>import("./index.html-4556c585.js"),["assets/index.html-4556c585.js","assets/logo-b695892f.js","assets/framework-f6820c83.js"])),"v-2e0e70fc":I(()=>E(()=>import("./typescript-essentials.html-534c1795.js"),["assets/typescript-essentials.html-534c1795.js","assets/framework-f6820c83.js"])),"v-f7b8f1f2":I(()=>E(()=>import("./needle-config-json.html-f91034da.js"),["assets/needle-config-json.html-f91034da.js","assets/framework-f6820c83.js"])),"v-caed7310":I(()=>E(()=>import("./needle-engine-attributes.html-b6293290.js"),["assets/needle-engine-attributes.html-b6293290.js","assets/custom-loading-style-65a23c0d.js","assets/framework-f6820c83.js"])),"v-6d28ca94":I(()=>E(()=>import("./typescript-decorators.html-64c32bd4.js"),["assets/typescript-decorators.html-64c32bd4.js","assets/framework-f6820c83.js"])),"v-3706649a":I(()=>E(()=>import("./404.html-dca3a92c.js"),["assets/404.html-dca3a92c.js","assets/framework-f6820c83.js"])),"v-165956bc":I(()=>E(()=>import("./index.html-81a91362.js"),["assets/index.html-81a91362.js","assets/framework-f6820c83.js"])),"v-e6fbcab8":I(()=>E(()=>import("./index.html-2c69ad93.js"),["assets/index.html-2c69ad93.js","assets/framework-f6820c83.js"])),"v-289be385":I(()=>E(()=>import("./index.html-013f9a89.js"),["assets/index.html-013f9a89.js","assets/framework-f6820c83.js"])),"v-44471890":I(()=>E(()=>import("./index.html-7afee307.js"),["assets/index.html-7afee307.js","assets/framework-f6820c83.js"])),"v-ea622532":I(()=>E(()=>import("./index.html-915af730.js"),["assets/index.html-915af730.js","assets/framework-f6820c83.js"])),"v-28e23eea":I(()=>E(()=>import("./index.html-32447045.js"),["assets/index.html-32447045.js","assets/framework-f6820c83.js"])),"v-1d2bf24a":I(()=>E(()=>import("./index.html-57d757de.js"),["assets/index.html-57d757de.js","assets/framework-f6820c83.js"])),"v-b5125a16":I(()=>E(()=>import("./index.html-4f00c2ff.js"),["assets/index.html-4f00c2ff.js","assets/framework-f6820c83.js"])),"v-fd5a4508":I(()=>E(()=>import("./index.html-3b58543e.js"),["assets/index.html-3b58543e.js","assets/framework-f6820c83.js"])),"v-07e00b06":I(()=>E(()=>import("./index.html-3604b913.js"),["assets/index.html-3604b913.js","assets/framework-f6820c83.js"])),"v-10c99314":I(()=>E(()=>import("./index.html-932f723a.js"),["assets/index.html-932f723a.js","assets/framework-f6820c83.js"])),"v-acde0924":I(()=>E(()=>import("./index.html-1c180a2f.js"),["assets/index.html-1c180a2f.js","assets/framework-f6820c83.js"])),"v-93aa7836":I(()=>E(()=>import("./index.html-00a074af.js"),["assets/index.html-00a074af.js","assets/framework-f6820c83.js"])),"v-e9b14526":I(()=>E(()=>import("./index.html-de7540da.js"),["assets/index.html-de7540da.js","assets/framework-f6820c83.js"])),"v-0c347631":I(()=>E(()=>import("./index.html-cb603ab8.js"),["assets/index.html-cb603ab8.js","assets/framework-f6820c83.js"])),"v-5d69cca2":I(()=>E(()=>import("./index.html-cfe2bf15.js"),["assets/index.html-cfe2bf15.js","assets/framework-f6820c83.js"])),"v-4fe6a858":I(()=>E(()=>import("./index.html-4d11b3c8.js"),["assets/index.html-4d11b3c8.js","assets/framework-f6820c83.js"])),"v-74ff5ee0":I(()=>E(()=>import("./index.html-7dfa6204.js"),["assets/index.html-7dfa6204.js","assets/framework-f6820c83.js"])),"v-3721011d":I(()=>E(()=>import("./index.html-ee156bf2.js"),["assets/index.html-ee156bf2.js","assets/framework-f6820c83.js"])),"v-a305d722":I(()=>E(()=>import("./index.html-de8c7aa7.js"),["assets/index.html-de8c7aa7.js","assets/framework-f6820c83.js"])),"v-51c63c84":I(()=>E(()=>import("./index.html-7517d60a.js"),["assets/index.html-7517d60a.js","assets/framework-f6820c83.js"])),"v-1b71f33d":I(()=>E(()=>import("./index.html-dccb5a4a.js"),["assets/index.html-dccb5a4a.js","assets/framework-f6820c83.js"])),"v-40fd290b":I(()=>E(()=>import("./index.html-238978c8.js"),["assets/index.html-238978c8.js","assets/framework-f6820c83.js"]))};var Ri=Symbol(""),Ni=ie(Ci),aa=Zo({key:"",path:"",title:"",lang:"",frontmatter:{},headers:[]}),Ke=ie(aa),rt=()=>Ke,ia=Symbol(""),He=()=>{const e=qe(ia);if(!e)throw new Error("usePageFrontmatter() is called without provider.");return e},ca=Symbol(""),Vi=()=>{const e=qe(ca);if(!e)throw new Error("usePageHead() is called without provider.");return e},Hi=Symbol(""),la=Symbol(""),sa=()=>{const e=qe(la);if(!e)throw new Error("usePageLang() is called without provider.");return e},ua=Symbol(""),Mi=()=>{const e=qe(ua);if(!e)throw new Error("usePageLayout() is called without provider.");return e},ir=Symbol(""),Zt=()=>{const e=qe(ir);if(!e)throw new Error("useRouteLocale() is called without provider.");return e},mt=ie(xi),$i=()=>mt,da=Symbol(""),cr=()=>{const e=qe(da);if(!e)throw new Error("useSiteLocaleData() is called without provider.");return e},zi=Symbol(""),qi="Layout",Bi="NotFound",Ne=fi({resolveLayouts:e=>e.reduce((t,n)=>({...t,...n.layouts}),{}),resolvePageData:async e=>{const t=Ni.value[e];return await(t==null?void 0:t())??aa},resolvePageFrontmatter:e=>e.frontmatter,resolvePageHead:(e,t,n)=>{const r=je(t.description)?t.description:n.description,o=[...Jt(t.head)?t.head:[],...n.head,["title",{},e],["meta",{name:"description",content:r}]];return pi(o)},resolvePageHeadTitle:(e,t)=>[e.title,t.title].filter(n=>!!n).join(" | "),resolvePageLang:e=>e.lang||"en",resolvePageLayout:(e,t)=>{let n;if(e.path){const r=e.frontmatter.layout;je(r)?n=r:n=qi}else n=Bi;return t[n]},resolveRouteLocale:(e,t)=>Yo(e,t),resolveSiteLocaleData:(e,t)=>({...e,...e.locales[t]})}),lr=W({name:"ClientOnly",setup(e,t){const n=ie(!1);return we(()=>{n.value=!0}),()=>{var r,o;return n.value?(o=(r=t.slots).default)==null?void 0:o.call(r):null}}}),Fi=W({name:"Content",props:{pageKey:{type:String,required:!1,default:""}},setup(e){const t=rt(),n=C(()=>oa[e.pageKey||t.value.key]);return()=>n.value?Z(n.value):Z("div","404 Not Found")}}),xe=(e={})=>e,sr=e=>Qt(e)?e:`/docs/${Xo(e)}`;function ur(e,t,n){var r,o,a;t===void 0&&(t=50),n===void 0&&(n={});var l=(r=n.isImmediate)!=null&&r,i=(o=n.callback)!=null&&o,s=n.maxWait,u=Date.now(),c=[];function d(){if(s!==void 0){var p=Date.now()-u;if(p+t>=s)return s-p}return t}var m=function(){var p=[].slice.call(arguments),h=this;return new Promise(function(_,v){var g=l&&a===void 0;if(a!==void 0&&clearTimeout(a),a=setTimeout(function(){if(a=void 0,u=Date.now(),!l){var O=e.apply(h,p);i&&i(O),c.forEach(function(w){return(0,w.resolve)(O)}),c=[]}},d()),g){var y=e.apply(h,p);return i&&i(y),_(y)}c.push({resolve:_,reject:v})})};return m.cancel=function(p){a!==void 0&&clearTimeout(a),c.forEach(function(h){return(0,h.reject)(p)}),c=[]},m}const Ui=({headerLinkSelector:e,headerAnchorSelector:t,delay:n,offset:r=5})=>{const o=at(),l=ur(()=>{var _,v;const i=Math.max(window.scrollY,document.documentElement.scrollTop,document.body.scrollTop);if(Math.abs(i-0)m.some(y=>y.hash===g.hash));for(let g=0;g=(((_=y.parentElement)==null?void 0:_.offsetTop)??0)-r,S=!O||i<(((v=O.parentElement)==null?void 0:v.offsetTop)??0)-r;if(!(w&&S))continue;const k=decodeURIComponent(o.currentRoute.value.hash),b=decodeURIComponent(y.hash);if(k===b)return;if(d){for(let L=g+1;L{window.addEventListener("scroll",l)}),ea(()=>{window.removeEventListener("scroll",l)})},kr=async(e,t)=>{const{scrollBehavior:n}=e.options;e.options.scrollBehavior=void 0,await e.replace({query:e.currentRoute.value.query,hash:t,force:!0}).finally(()=>e.options.scrollBehavior=n)},Wi="a.sidebar-item",Ki=".header-anchor",Gi=300,Ji=5,Qi=xe({setup(){Ui({headerLinkSelector:Wi,headerAnchorSelector:Ki,delay:Gi,offset:Ji})}}),Dr=()=>window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,Zi=()=>window.scrollTo({top:0,behavior:"smooth"});const Yi=W({name:"BackToTop",setup(){const e=ie(0),t=C(()=>e.value>300),n=ur(()=>{e.value=Dr()},100);we(()=>{e.value=Dr(),window.addEventListener("scroll",()=>n())});const r=Z("div",{class:"back-to-top",onClick:Zi});return()=>Z(tr,{name:"back-to-top"},()=>t.value?r:null)}}),Xi=xe({rootComponents:[Yi]});const ec=Z("svg",{class:"external-link-icon",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"},[Z("path",{fill:"currentColor",d:"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"}),Z("polygon",{fill:"currentColor",points:"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"})]),tc=W({name:"ExternalLinkIcon",props:{locales:{type:Object,required:!1,default:()=>({})}},setup(e){const t=Zt(),n=C(()=>e.locales[t.value]??{openInNewWindow:"open in new window"});return()=>Z("span",[ec,Z("span",{class:"external-link-icon-sr-only"},n.value.openInNewWindow)])}}),nc={"/":{openInNewWindow:"open in new window"}},rc=xe({enhance({app:e}){e.component("ExternalLinkIcon",Z(tc,{locales:nc}))}});/*! medium-zoom 1.1.0 | MIT License | https://github.com/francoischalifour/medium-zoom */var nt=Object.assign||function(e){for(var t=1;t1&&arguments[1]!==void 0?arguments[1]:{},r=window.Promise||function(j){function D(){}j(D,D)},o=function(j){var D=j.target;if(D===L){h();return}O.indexOf(D)!==-1&&_({target:D})},a=function(){if(!(S||!b.original)){var j=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;Math.abs(T-j)>k.scrollOffset&&setTimeout(h,150)}},l=function(j){var D=j.key||j.keyCode;(D==="Escape"||D==="Esc"||D===27)&&h()},i=function(){var j=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},D=j;if(j.background&&(L.style.background=j.background),j.container&&j.container instanceof Object&&(D.container=nt({},k.container,j.container)),j.template){var z=vn(j.template)?j.template:document.querySelector(j.template);D.template=z}return k=nt({},k,D),O.forEach(function(G){G.dispatchEvent(it("medium-zoom:update",{detail:{zoom:P}}))}),P},s=function(){var j=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};return e(nt({},k,j))},u=function(){for(var j=arguments.length,D=Array(j),z=0;z0?D.reduce(function($,X){return[].concat($,Ar(X))},[]):O;return G.forEach(function($){$.classList.remove("medium-zoom-image"),$.dispatchEvent(it("medium-zoom:detach",{detail:{zoom:P}}))}),O=O.filter(function($){return G.indexOf($)===-1}),P},d=function(j,D){var z=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return O.forEach(function(G){G.addEventListener("medium-zoom:"+j,D,z)}),w.push({type:"medium-zoom:"+j,listener:D,options:z}),P},m=function(j,D){var z=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return O.forEach(function(G){G.removeEventListener("medium-zoom:"+j,D,z)}),w=w.filter(function(G){return!(G.type==="medium-zoom:"+j&&G.listener.toString()===D.toString())}),P},p=function(){var j=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},D=j.target,z=function(){var $={width:document.documentElement.clientWidth,height:document.documentElement.clientHeight,left:0,top:0,right:0,bottom:0},X=void 0,J=void 0;if(k.container)if(k.container instanceof Object)$=nt({},$,k.container),X=$.width-$.left-$.right-k.margin*2,J=$.height-$.top-$.bottom-k.margin*2;else{var _e=vn(k.container)?k.container:document.querySelector(k.container),ne=_e.getBoundingClientRect(),se=ne.width,ee=ne.height,Se=ne.left,pe=ne.top;$=nt({},$,{width:se,height:ee,left:Se,top:pe})}X=X||$.width-k.margin*2,J=J||$.height-k.margin*2;var oe=b.zoomedHd||b.original,ke=Lr(oe)?X:oe.naturalWidth||X,be=Lr(oe)?J:oe.naturalHeight||J,Pe=oe.getBoundingClientRect(),De=Pe.top,Ee=Pe.left,ge=Pe.width,Be=Pe.height,Le=Math.min(Math.max(ge,ke),X)/ge,Ln=Math.min(Math.max(Be,be),J)/Be,bt=Math.min(Le,Ln),Xt=(-Ee+(X-ge)/2+k.margin+$.left)/bt,M=(-De+(J-Be)/2+k.margin+$.top)/bt,F="scale("+bt+") translate3d("+Xt+"px, "+M+"px, 0)";b.zoomed.style.transform=F,b.zoomedHd&&(b.zoomedHd.style.transform=F)};return new r(function(G){if(D&&O.indexOf(D)===-1){G(P);return}var $=function se(){S=!1,b.zoomed.removeEventListener("transitionend",se),b.original.dispatchEvent(it("medium-zoom:opened",{detail:{zoom:P}})),G(P)};if(b.zoomed){G(P);return}if(D)b.original=D;else if(O.length>0){var X=O;b.original=X[0]}else{G(P);return}if(b.original.dispatchEvent(it("medium-zoom:open",{detail:{zoom:P}})),T=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,S=!0,b.zoomed=ic(b.original),document.body.appendChild(L),k.template){var J=vn(k.template)?k.template:document.querySelector(k.template);b.template=document.createElement("div"),b.template.appendChild(J.content.cloneNode(!0)),document.body.appendChild(b.template)}if(b.original.parentElement&&b.original.parentElement.tagName==="PICTURE"&&b.original.currentSrc&&(b.zoomed.src=b.original.currentSrc),document.body.appendChild(b.zoomed),window.requestAnimationFrame(function(){document.body.classList.add("medium-zoom--opened")}),b.original.classList.add("medium-zoom-image--hidden"),b.zoomed.classList.add("medium-zoom-image--opened"),b.zoomed.addEventListener("click",h),b.zoomed.addEventListener("transitionend",$),b.original.getAttribute("data-zoom-src")){b.zoomedHd=b.zoomed.cloneNode(),b.zoomedHd.removeAttribute("srcset"),b.zoomedHd.removeAttribute("sizes"),b.zoomedHd.removeAttribute("loading"),b.zoomedHd.src=b.zoomed.getAttribute("data-zoom-src"),b.zoomedHd.onerror=function(){clearInterval(_e),console.warn("Unable to reach the zoom image target "+b.zoomedHd.src),b.zoomedHd=null,z()};var _e=setInterval(function(){b.zoomedHd.complete&&(clearInterval(_e),b.zoomedHd.classList.add("medium-zoom-image--opened"),b.zoomedHd.addEventListener("click",h),document.body.appendChild(b.zoomedHd),z())},10)}else if(b.original.hasAttribute("srcset")){b.zoomedHd=b.zoomed.cloneNode(),b.zoomedHd.removeAttribute("sizes"),b.zoomedHd.removeAttribute("loading");var ne=b.zoomedHd.addEventListener("load",function(){b.zoomedHd.removeEventListener("load",ne),b.zoomedHd.classList.add("medium-zoom-image--opened"),b.zoomedHd.addEventListener("click",h),document.body.appendChild(b.zoomedHd),z()})}else z()})},h=function(){return new r(function(j){if(S||!b.original){j(P);return}var D=function z(){b.original.classList.remove("medium-zoom-image--hidden"),document.body.removeChild(b.zoomed),b.zoomedHd&&document.body.removeChild(b.zoomedHd),document.body.removeChild(L),b.zoomed.classList.remove("medium-zoom-image--opened"),b.template&&document.body.removeChild(b.template),S=!1,b.zoomed.removeEventListener("transitionend",z),b.original.dispatchEvent(it("medium-zoom:closed",{detail:{zoom:P}})),b.original=null,b.zoomed=null,b.zoomedHd=null,b.template=null,j(P)};S=!0,document.body.classList.remove("medium-zoom--opened"),b.zoomed.style.transform="",b.zoomedHd&&(b.zoomedHd.style.transform=""),b.template&&(b.template.style.transition="opacity 150ms",b.template.style.opacity=0),b.original.dispatchEvent(it("medium-zoom:close",{detail:{zoom:P}})),b.zoomed.addEventListener("transitionend",D)})},_=function(){var j=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},D=j.target;return b.original?h():p({target:D})},v=function(){return k},g=function(){return O},y=function(){return b.original},O=[],w=[],S=!1,T=0,k=n,b={original:null,zoomed:null,zoomedHd:null,template:null};Object.prototype.toString.call(t)==="[object Object]"?k=t:(t||typeof t=="string")&&u(t),k=nt({margin:0,background:"#fff",scrollOffset:40,container:null,template:null},k);var L=ac(k.background);document.addEventListener("click",o),document.addEventListener("keyup",l),document.addEventListener("scroll",a),window.addEventListener("resize",h);var P={open:p,close:h,toggle:_,update:i,clone:s,attach:u,detach:c,on:d,off:m,getOptions:v,getImages:g,getZoomedImage:y};return P};function lc(e,t){t===void 0&&(t={});var n=t.insertAt;if(!(!e||typeof document>"u")){var r=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.type="text/css",n==="top"&&r.firstChild?r.insertBefore(o,r.firstChild):r.appendChild(o),o.styleSheet?o.styleSheet.cssText=e:o.appendChild(document.createTextNode(e))}}var sc=".medium-zoom-overlay{position:fixed;top:0;right:0;bottom:0;left:0;opacity:0;transition:opacity .3s;will-change:opacity}.medium-zoom--opened .medium-zoom-overlay{cursor:pointer;cursor:zoom-out;opacity:1}.medium-zoom-image{cursor:pointer;cursor:zoom-in;transition:transform .3s cubic-bezier(.2,0,.2,1)!important}.medium-zoom-image--hidden{visibility:hidden}.medium-zoom-image--opened{position:relative;cursor:pointer;cursor:zoom-out;will-change:transform}";lc(sc);const uc=cc,dc=Symbol("mediumZoom");const mc=".theme-default-content > img, .theme-default-content :not(a) > img",fc={},pc=300,vc=xe({enhance({app:e,router:t}){const n=uc(fc);n.refresh=(r=mc)=>{n.detach(),n.attach(r)},e.provide(dc,n),t.afterEach(()=>{setTimeout(()=>n.refresh(),pc)})}});/** + * NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress + * @license MIT + */const B={settings:{minimum:.08,easing:"ease",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,barSelector:'[role="bar"]',parent:"body",template:'
'},status:null,set:e=>{const t=B.isStarted();e=jn(e,B.settings.minimum,1),B.status=e===1?null:e;const n=B.render(!t),r=n.querySelector(B.settings.barSelector),o=B.settings.speed,a=B.settings.easing;return n.offsetWidth,hc(l=>{nn(r,{transform:"translate3d("+Tr(e)+"%,0,0)",transition:"all "+o+"ms "+a}),e===1?(nn(n,{transition:"none",opacity:"1"}),n.offsetWidth,setTimeout(function(){nn(n,{transition:"all "+o+"ms linear",opacity:"0"}),setTimeout(function(){B.remove(),l()},o)},o)):setTimeout(()=>l(),o)}),B},isStarted:()=>typeof B.status=="number",start:()=>{B.status||B.set(0);const e=()=>{setTimeout(()=>{B.status&&(B.trickle(),e())},B.settings.trickleSpeed)};return B.settings.trickle&&e(),B},done:e=>!e&&!B.status?B:B.inc(.3+.5*Math.random()).set(1),inc:e=>{let t=B.status;return t?(typeof e!="number"&&(e=(1-t)*jn(Math.random()*t,.1,.95)),t=jn(t+e,0,.994),B.set(t)):B.start()},trickle:()=>B.inc(Math.random()*B.settings.trickleRate),render:e=>{if(B.isRendered())return document.getElementById("nprogress");jr(document.documentElement,"nprogress-busy");const t=document.createElement("div");t.id="nprogress",t.innerHTML=B.settings.template;const n=t.querySelector(B.settings.barSelector),r=e?"-100":Tr(B.status||0),o=document.querySelector(B.settings.parent);return nn(n,{transition:"all 0 linear",transform:"translate3d("+r+"%,0,0)"}),o!==document.body&&jr(o,"nprogress-custom-parent"),o==null||o.appendChild(t),t},remove:()=>{Cr(document.documentElement,"nprogress-busy"),Cr(document.querySelector(B.settings.parent),"nprogress-custom-parent");const e=document.getElementById("nprogress");e&&_c(e)},isRendered:()=>!!document.getElementById("nprogress")},jn=(e,t,n)=>en?n:e,Tr=e=>(-1+e)*100,hc=function(){const e=[];function t(){const n=e.shift();n&&n(t)}return function(n){e.push(n),e.length===1&&t()}}(),nn=function(){const e=["Webkit","O","Moz","ms"],t={};function n(l){return l.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(i,s){return s.toUpperCase()})}function r(l){const i=document.body.style;if(l in i)return l;let s=e.length;const u=l.charAt(0).toUpperCase()+l.slice(1);let c;for(;s--;)if(c=e[s]+u,c in i)return c;return l}function o(l){return l=n(l),t[l]||(t[l]=r(l))}function a(l,i,s){i=o(i),l.style[i]=s}return function(l,i){for(const s in i){const u=i[s];u!==void 0&&Object.prototype.hasOwnProperty.call(i,s)&&a(l,s,u)}}}(),ma=(e,t)=>(typeof e=="string"?e:dr(e)).indexOf(" "+t+" ")>=0,jr=(e,t)=>{const n=dr(e),r=n+t;ma(n,t)||(e.className=r.substring(1))},Cr=(e,t)=>{const n=dr(e);if(!ma(e,t))return;const r=n.replace(" "+t+" "," ");e.className=r.substring(1,r.length-1)},dr=e=>(" "+(e.className||"")+" ").replace(/\s+/gi," "),_c=e=>{e&&e.parentNode&&e.parentNode.removeChild(e)};const gc=()=>{we(()=>{const e=at(),t=new Set;t.add(e.currentRoute.value.path),e.beforeEach(n=>{t.has(n.path)||B.start()}),e.afterEach(n=>{t.add(n.path),B.done()})})},yc=xe({setup(){gc()}}),bc=JSON.parse('{"docsRepo":"needle-tools/needle-engine-support","docsBranch":"main","docsDir":"documentation","logo":"/needle-logo-black.svg","colorModeSwitch":false,"lastUpdated":true,"contributors":false,"editLink":true,"editLinkText":"Suggest changes","externalLinkIcon":false,"notFound":["Oh no โ€” this page does not exist!","Gosh! You found a ๐ŸŒต glitch"],"navbar":[{"text":"Overview","link":"/","children":[{"text":"What is Needle Engine?","link":"/"},"/testimonials",{"text":"Get an overview","children":[{"text":"Samples and Showcase","link":"https://engine.needle.tools/samples?utm_source=needle_docs&utm_content=headernav"},"/vision","/features-overview","/technical-overview"]},{"text":"Resources","children":[{"text":"Pricing and Plans","link":"https://needle.tools/pricing/?utm_source=needle_docs&utm_content=headersubnav"},{"text":"Changelog","link":"https://github.com/needle-tools/needle-engine-support/releases"},{"text":"API Documentation","link":"https://engine.needle.tools/docs/api/latest"}]}]},{"text":"Downloads","link":"/getting-started"},{"text":"Guides","children":["/project-structure","/everywhere-actions","/export","/html","/testing","/deployment",{"text":"Advanced","children":["/networking","/xr","/vanilla-js"]},{"text":"Blender","children":["/blender"]},{"text":"Tutorials","children":[{"text":"Tutorials on Youtube","link":"https://www.youtube.com/playlist?list=PLJ4BaFFEGP1GVTmPhKDC6QzL8Am9700Wo"}]},{"text":"Troubleshooting","children":["/debugging","/faq"]}]},{"text":"Reference","children":[{"text":"Scripting Overview","children":["/getting-started/typescript-essentials","/getting-started/for-unity-developers","/component-reference","/everywhere-actions"]},{"text":"Components and Lifecycle","children":["/scripting","/reference/typescript-decorators","/component-compiler","/scripting-examples",{"text":"Community Contributions","link":"/community/contributions"},"/modules"]},{"text":"Settings and APIs","children":["/reference/needle-engine-attributes","/reference/needle-config-json",{"text":"Needle Engine API","link":"https://engine.needle.tools/docs/api/latest"},{"text":"three.js API","link":"https://threejs.org/docs/index.html"}]}]},{"text":"Samples","link":"https://engine.needle.tools/samples?utm_source=needle_docs&utm_content=headernav"},{"text":"Pricing","link":"https://needle.tools/pricing/?utm_source=needle_docs&utm_content=headernav"},{"text":"Contact","children":[{"text":"Needle Website","link":"https://needle.tools?utm_source=needle_docs&utm_content=headernav"},{"text":"Github","link":"https://github.com/needle-tools/needle-engine-support"},{"text":"Support Community","link":"https://forum.needle.tools?utm_source=needle_docs&utm_content=headernav"},{"text":"Discord Server","link":"https://discord.needle.tools"},{"text":"Twitter","link":"https://twitter.com/needletools"},{"text":"Newsletter","link":"https://fwd.needle.tools/needle-engine/newsletter"},{"text":"Email","link":"mailto:hi+docs@needle.tools"},{"text":"Feedback","link":"https://fwd.needle.tools/needle-engine/feedback"}]}],"sidebarDepth":1,"sidebar":{"/":[{"text":"Getting Started","children":[{"text":"Installation","link":"/getting-started"}]},{"text":"Creating your Project","children":["/project-structure","/everywhere-actions","/export","/html","/testing","/deployment","/debugging","/faq"]},{"text":"Scripting","children":["/getting-started/typescript-essentials","/getting-started/for-unity-developers","/scripting","/component-compiler","/scripting-examples",{"text":"Community Contributions","link":"/community/contributions"}]},{"text":"Advanced","children":["/xr","/networking","/vanilla-js"]},{"text":"Reference","children":["/features-overview","/technical-overview","/component-reference","/reference/needle-config-json","/reference/needle-engine-attributes","/reference/typescript-decorators"]},{"text":"Blender","children":["/blender"]}]},"locales":{"/":{"selectLanguageName":"English"}},"colorMode":"auto","repo":null,"selectLanguageText":"Languages","selectLanguageAriaLabel":"Select language","lastUpdatedText":"Last Updated","contributorsText":"Contributors","backToHome":"Take me home","openInNewWindow":"open in new window","toggleColorMode":"toggle color mode","toggleSidebar":"toggle sidebar"}'),Ec=ie(bc),Oc=()=>Ec,fa=Symbol(""),wc=()=>{const e=qe(fa);if(!e)throw new Error("useThemeLocaleData() is called without provider.");return e},Sc=(e,t)=>{var n;return{...e,...(n=e.locales)==null?void 0:n[t]}},Pc=xe({enhance({app:e}){const t=Oc(),n=e._context.provides[ir],r=C(()=>Sc(t.value,n.value));e.provide(fa,r),Object.defineProperties(e.config.globalProperties,{$theme:{get(){return t.value}},$themeLocale:{get(){return r.value}}})}}),Ic=W({__name:"Badge",props:{type:{type:String,required:!1,default:"tip"},text:{type:String,required:!1,default:""},vertical:{type:String,required:!1,default:void 0}},setup(e){return(t,n)=>(A(),R("span",{class:Ie(["badge",e.type]),style:ta({verticalAlign:e.vertical})},[Q(t.$slots,"default",{},()=>[_t(ce(e.text),1)])],6))}}),kc=te(Ic,[["__file","Badge.vue"]]),Dc=W({name:"CodeGroup",setup(e,{slots:t}){const n=ie(-1),r=ie([]),o=(i=n.value)=>{i{i>0?n.value=i-1:n.value=r.value.length-1,r.value[n.value].focus()},l=(i,s)=>{i.key===" "||i.key==="Enter"?(i.preventDefault(),n.value=s):i.key==="ArrowRight"?(i.preventDefault(),o(s)):i.key==="ArrowLeft"&&(i.preventDefault(),a(s))};return()=>{var s;const i=(((s=t.default)==null?void 0:s.call(t))||[]).filter(u=>u.type.name==="CodeGroupItem").map(u=>(u.props===null&&(u.props={}),u));return i.length===0?null:(n.value<0||n.value>i.length-1?(n.value=i.findIndex(u=>u.props.active===""||u.props.active===!0),n.value===-1&&(n.value=0)):i.forEach((u,c)=>{u.props.active=c===n.value}),Z("div",{class:"code-group"},[Z("div",{class:"code-group__nav"},Z("ul",{class:"code-group__ul"},i.map((u,c)=>{const d=c===n.value;return Z("li",{class:"code-group__li"},Z("button",{ref:m=>{m&&(r.value[c]=m)},class:{"code-group__nav-tab":!0,"code-group__nav-tab-active":d},ariaPressed:d,ariaExpanded:d,onClick:()=>n.value=c,onKeydown:m=>l(m,c)},u.props.title))}))),i]))}}}),Lc=["aria-selected"],Ac=W({name:"CodeGroupItem"}),Tc=W({...Ac,props:{title:{type:String,required:!0},active:{type:Boolean,required:!1,default:!1}},setup(e){return(t,n)=>(A(),R("div",{class:Ie(["code-group-item",{"code-group-item__active":e.active}]),"aria-selected":e.active},[Q(t.$slots,"default")],10,Lc))}}),jc=te(Tc,[["__file","CodeGroupItem.vue"]]);var xr;const pa=typeof window<"u",Cc=e=>typeof e=="function",xc=e=>typeof e=="string",Rc=()=>{};pa&&((xr=window==null?void 0:window.navigator)!=null&&xr.userAgent)&&/iP(ad|hone|od)/.test(window.navigator.userAgent);function Ut(e){return typeof e=="function"?e():H(e)}function Nc(e,t){function n(...r){return new Promise((o,a)=>{Promise.resolve(e(()=>t.apply(this,r),{fn:t,thisArg:this,args:r})).then(o).catch(a)})}return n}const va=e=>e();function Vc(e=va){const t=ie(!0);function n(){t.value=!1}function r(){t.value=!0}const o=(...a)=>{t.value&&e(...a)};return{isActive:Zo(t),pause:n,resume:r,eventFilter:o}}function Hc(e){return e}function ha(e){return _i()?(gi(e),!0):!1}function Mc(e){return typeof e=="function"?C(e):ie(e)}function $c(e,t=!0){hi()?we(e):t?e():nr(e)}function zc(e=!1,t={}){const{truthyValue:n=!0,falsyValue:r=!1}=t,o=vi(e),a=ie(e);function l(i){if(arguments.length)return a.value=i,a.value;{const s=Ut(n);return a.value=a.value===s?Ut(r):s,a.value}}return o?l:[a,l]}var Rr=Object.getOwnPropertySymbols,qc=Object.prototype.hasOwnProperty,Bc=Object.prototype.propertyIsEnumerable,Fc=(e,t)=>{var n={};for(var r in e)qc.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&Rr)for(var r of Rr(e))t.indexOf(r)<0&&Bc.call(e,r)&&(n[r]=e[r]);return n};function Uc(e,t,n={}){const r=n,{eventFilter:o=va}=r,a=Fc(r,["eventFilter"]);return Ge(e,Nc(o,t),a)}var Wc=Object.defineProperty,Kc=Object.defineProperties,Gc=Object.getOwnPropertyDescriptors,wn=Object.getOwnPropertySymbols,_a=Object.prototype.hasOwnProperty,ga=Object.prototype.propertyIsEnumerable,Nr=(e,t,n)=>t in e?Wc(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Jc=(e,t)=>{for(var n in t||(t={}))_a.call(t,n)&&Nr(e,n,t[n]);if(wn)for(var n of wn(t))ga.call(t,n)&&Nr(e,n,t[n]);return e},Qc=(e,t)=>Kc(e,Gc(t)),Zc=(e,t)=>{var n={};for(var r in e)_a.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&wn)for(var r of wn(e))t.indexOf(r)<0&&ga.call(e,r)&&(n[r]=e[r]);return n};function Yc(e,t,n={}){const r=n,{eventFilter:o}=r,a=Zc(r,["eventFilter"]),{eventFilter:l,pause:i,resume:s,isActive:u}=Vc(o);return{stop:Uc(e,t,Qc(Jc({},a),{eventFilter:l})),pause:i,resume:s,isActive:u}}function Xc(e){var t;const n=Ut(e);return(t=n==null?void 0:n.$el)!=null?t:n}const Sn=pa?window:void 0;function Vr(...e){let t,n,r,o;if(xc(e[0])||Array.isArray(e[0])?([n,r,o]=e,t=Sn):[t,n,r,o]=e,!t)return Rc;Array.isArray(n)||(n=[n]),Array.isArray(r)||(r=[r]);const a=[],l=()=>{a.forEach(c=>c()),a.length=0},i=(c,d,m,p)=>(c.addEventListener(d,m,p),()=>c.removeEventListener(d,m,p)),s=Ge(()=>[Xc(t),Ut(o)],([c,d])=>{l(),c&&a.push(...n.flatMap(m=>r.map(p=>i(c,m,p,d))))},{immediate:!0,flush:"post"}),u=()=>{s(),l()};return ha(u),u}function el(e,t=!1){const n=ie(),r=()=>n.value=Boolean(e());return r(),$c(r,t),n}function tl(e,t={}){const{window:n=Sn}=t,r=el(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let o;const a=ie(!1),l=()=>{o&&("removeEventListener"in o?o.removeEventListener("change",i):o.removeListener(i))},i=()=>{r.value&&(l(),o=n.matchMedia(Mc(e).value),a.value=o.matches,"addEventListener"in o?o.addEventListener("change",i):o.addListener(i))};return bi(i),ha(()=>l()),a}const qn=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},Bn="__vueuse_ssr_handlers__";qn[Bn]=qn[Bn]||{};const nl=qn[Bn];function rl(e,t){return nl[e]||t}function ol(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}var al=Object.defineProperty,Hr=Object.getOwnPropertySymbols,il=Object.prototype.hasOwnProperty,cl=Object.prototype.propertyIsEnumerable,Mr=(e,t,n)=>t in e?al(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,$r=(e,t)=>{for(var n in t||(t={}))il.call(t,n)&&Mr(e,n,t[n]);if(Hr)for(var n of Hr(t))cl.call(t,n)&&Mr(e,n,t[n]);return e};const ll={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},zr="vueuse-storage";function sl(e,t,n,r={}){var o;const{flush:a="pre",deep:l=!0,listenToStorageChanges:i=!0,writeDefaults:s=!0,mergeDefaults:u=!1,shallow:c,window:d=Sn,eventFilter:m,onError:p=b=>{console.error(b)}}=r,h=(c?yi:ie)(t);if(!n)try{n=rl("getDefaultStorage",()=>{var b;return(b=Sn)==null?void 0:b.localStorage})()}catch(b){p(b)}if(!n)return h;const _=Ut(t),v=ol(_),g=(o=r.serializer)!=null?o:ll[v],{pause:y,resume:O}=Yc(h,()=>w(h.value),{flush:a,deep:l,eventFilter:m});return d&&i&&(Vr(d,"storage",k),Vr(d,zr,T)),k(),h;function w(b){try{if(b==null)n.removeItem(e);else{const L=g.write(b),P=n.getItem(e);P!==L&&(n.setItem(e,L),d&&d.dispatchEvent(new CustomEvent(zr,{detail:{key:e,oldValue:P,newValue:L,storageArea:n}})))}}catch(L){p(L)}}function S(b){const L=b?b.newValue:n.getItem(e);if(L==null)return s&&_!==null&&n.setItem(e,g.write(_)),_;if(!b&&u){const P=g.read(L);return Cc(u)?u(P,_):v==="object"&&!Array.isArray(P)?$r($r({},_),P):P}else return typeof L!="string"?L:g.read(L)}function T(b){k(b.detail)}function k(b){if(!(b&&b.storageArea!==n)){if(b&&b.key==null){h.value=_;return}if(!(b&&b.key!==e)){y();try{h.value=S(b)}catch(L){p(L)}finally{b?nr(O):O()}}}}}function ul(e){return tl("(prefers-color-scheme: dark)",e)}var qr;(function(e){e.UP="UP",e.RIGHT="RIGHT",e.DOWN="DOWN",e.LEFT="LEFT",e.NONE="NONE"})(qr||(qr={}));var dl=Object.defineProperty,Br=Object.getOwnPropertySymbols,ml=Object.prototype.hasOwnProperty,fl=Object.prototype.propertyIsEnumerable,Fr=(e,t,n)=>t in e?dl(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,pl=(e,t)=>{for(var n in t||(t={}))ml.call(t,n)&&Fr(e,n,t[n]);if(Br)for(var n of Br(t))fl.call(t,n)&&Fr(e,n,t[n]);return e};const vl={easeInSine:[.12,0,.39,0],easeOutSine:[.61,1,.88,1],easeInOutSine:[.37,0,.63,1],easeInQuad:[.11,0,.5,0],easeOutQuad:[.5,1,.89,1],easeInOutQuad:[.45,0,.55,1],easeInCubic:[.32,0,.67,0],easeOutCubic:[.33,1,.68,1],easeInOutCubic:[.65,0,.35,1],easeInQuart:[.5,0,.75,0],easeOutQuart:[.25,1,.5,1],easeInOutQuart:[.76,0,.24,1],easeInQuint:[.64,0,.78,0],easeOutQuint:[.22,1,.36,1],easeInOutQuint:[.83,0,.17,1],easeInExpo:[.7,0,.84,0],easeOutExpo:[.16,1,.3,1],easeInOutExpo:[.87,0,.13,1],easeInCirc:[.55,0,1,.45],easeOutCirc:[0,.55,.45,1],easeInOutCirc:[.85,0,.15,1],easeInBack:[.36,0,.66,-.56],easeOutBack:[.34,1.56,.64,1],easeInOutBack:[.68,-.6,.32,1.6]};pl({linear:Hc},vl);const ve=()=>wc(),ya=Symbol(""),mr=()=>{const e=qe(ya);if(!e)throw new Error("useDarkMode() is called without provider.");return e},hl=()=>{const e=ve(),t=ul(),n=sl("vuepress-color-scheme",e.value.colorMode),r=C({get(){return e.value.colorModeSwitch?n.value==="auto"?t.value:n.value==="dark":e.value.colorMode==="dark"},set(o){o===t.value?n.value="auto":n.value=o?"dark":"light"}});rr(ya,r),_l(r)},_l=e=>{const t=(n=e.value)=>{const r=window==null?void 0:window.document.querySelector("html");r==null||r.classList.toggle("dark",n)};we(()=>{Ge(e,t,{immediate:!0})}),na(()=>t())},ba=(...e)=>{const n=at().resolve(...e),r=n.matched[n.matched.length-1];if(!(r!=null&&r.redirect))return n;const{redirect:o}=r,a=Ei(o)?o(n):o,l=je(a)?{path:a}:a;return ba({hash:n.hash,query:n.query,params:n.params,...l})},fr=e=>{const t=ba(encodeURI(e));return{text:t.meta.title||e,link:t.name==="404"?e:t.fullPath}};let Cn=null,wt=null;const gl={wait:()=>Cn,pending:()=>{Cn=new Promise(e=>wt=e)},resolve:()=>{wt==null||wt(),Cn=null,wt=null}},Ea=()=>gl,Oa=Symbol("sidebarItems"),pr=()=>{const e=qe(Oa);if(!e)throw new Error("useSidebarItems() is called without provider.");return e},yl=()=>{const e=ve(),t=He(),n=C(()=>bl(t.value,e.value));rr(Oa,n)},bl=(e,t)=>{const n=e.sidebar??t.sidebar??"auto",r=e.sidebarDepth??t.sidebarDepth??2;return e.home||n===!1?[]:n==="auto"?Ol(r):Jt(n)?wa(n,r):or(n)?wl(n,r):[]},El=(e,t)=>({text:e.title,link:e.link,children:vr(e.children,t)}),vr=(e,t)=>t>0?e.map(n=>El(n,t-1)):[],Ol=e=>{const t=rt();return[{text:t.value.title,children:vr(t.value.headers,e)}]},wa=(e,t)=>{const n=Ze(),r=rt(),o=a=>{var i;let l;if(je(a)?l=fr(a):l=a,l.children)return{...l,children:l.children.map(s=>o(s))};if(l.link===n.path){const s=((i=r.value.headers[0])==null?void 0:i.level)===1?r.value.headers[0].children:r.value.headers;return{...l,children:vr(s,t)}}return l};return e.map(a=>o(a))},wl=(e,t)=>{const n=Ze(),r=Yo(e,n.path),o=e[r]??[];return wa(o,t)},Sl={},Pl={class:"theme-default-content"};function Il(e,t){const n=ze("Content");return A(),R("div",Pl,[Y(n)])}const kl=te(Sl,[["render",Il],["__file","HomeContent.vue"]]),Dl={key:0,class:"features"},Ll=W({__name:"HomeFeatures",setup(e){const t=He(),n=C(()=>Jt(t.value.features)?t.value.features:[]);return(r,o)=>n.value.length?(A(),R("div",Dl,[(A(!0),R(Oe,null,Je(n.value,a=>(A(),R("div",{key:a.title,class:"feature"},[U("h2",null,ce(a.title),1),U("p",null,ce(a.details),1)]))),128))])):ae("v-if",!0)}}),Al=te(Ll,[["__file","HomeFeatures.vue"]]),Tl=["innerHTML"],jl=["textContent"],Cl=W({__name:"HomeFooter",setup(e){const t=He(),n=C(()=>t.value.footer),r=C(()=>t.value.footerHtml);return(o,a)=>n.value?(A(),R(Oe,{key:0},[ae(" eslint-disable-next-line vue/no-v-html "),r.value?(A(),R("div",{key:0,class:"footer",innerHTML:n.value},null,8,Tl)):(A(),R("div",{key:1,class:"footer",textContent:ce(n.value)},null,8,jl))],64)):ae("v-if",!0)}}),xl=te(Cl,[["__file","HomeFooter.vue"]]),Rl=["href","rel","target","aria-label"],Nl=W({inheritAttrs:!1}),Vl=W({...Nl,__name:"AutoLink",props:{item:{type:Object,required:!0}},setup(e){const t=e,n=Ze(),r=$i(),{item:o}=ar(t),a=C(()=>Qt(o.value.link)),l=C(()=>Oi(o.value.link)||wi(o.value.link)),i=C(()=>{if(!l.value){if(o.value.target)return o.value.target;if(a.value)return"_blank"}}),s=C(()=>i.value==="_blank"),u=C(()=>!a.value&&!l.value&&!s.value),c=C(()=>{if(!l.value){if(o.value.rel)return o.value.rel;if(s.value)return"noopener noreferrer"}}),d=C(()=>o.value.ariaLabel||o.value.text),m=C(()=>{const _=Object.keys(r.value.locales);return _.length?!_.some(v=>v===o.value.link):o.value.link!=="/"}),p=C(()=>m.value?n.path.startsWith(o.value.link):!1),h=C(()=>u.value?o.value.activeMatch?new RegExp(o.value.activeMatch).test(n.path):p.value:!1);return(_,v)=>{const g=ze("RouterLink"),y=ze("AutoLinkExternalIcon");return u.value?(A(),le(g,Sr({key:0,class:{"router-link-active":h.value},to:H(o).link,"aria-label":d.value},_.$attrs),{default:me(()=>[Q(_.$slots,"before"),_t(" "+ce(H(o).text)+" ",1),Q(_.$slots,"after")]),_:3},16,["class","to","aria-label"])):(A(),R("a",Sr({key:1,class:"external-link",href:H(o).link,rel:c.value,target:i.value,"aria-label":d.value},_.$attrs),[Q(_.$slots,"before"),_t(" "+ce(H(o).text)+" ",1),s.value?(A(),le(y,{key:0})):ae("v-if",!0),Q(_.$slots,"after")],16,Rl))}}}),Me=te(Vl,[["__file","AutoLink.vue"]]),Hl={class:"hero"},Ml={key:0,id:"main-title"},$l={key:1,class:"description"},zl={key:2,class:"actions"},ql=W({__name:"HomeHero",setup(e){const t=He(),n=cr(),r=mr(),o=C(()=>r.value&&t.value.heroImageDark!==void 0?t.value.heroImageDark:t.value.heroImage),a=C(()=>t.value.heroAlt||i.value||"hero"),l=C(()=>t.value.heroHeight||280),i=C(()=>t.value.heroText===null?null:t.value.heroText||n.value.title||"Hello"),s=C(()=>t.value.tagline===null?null:t.value.tagline||n.value.description||"Welcome to your VuePress site"),u=C(()=>Jt(t.value.actions)?t.value.actions.map(({text:d,link:m,type:p="primary"})=>({text:d,link:m,type:p})):[]),c=()=>{if(!o.value)return null;const d=Z("img",{src:sr(o.value),alt:a.value,height:l.value});return t.value.heroImageDark===void 0?d:Z(lr,()=>d)};return(d,m)=>(A(),R("header",Hl,[Y(c),i.value?(A(),R("h1",Ml,ce(i.value),1)):ae("v-if",!0),s.value?(A(),R("p",$l,ce(s.value),1)):ae("v-if",!0),u.value.length?(A(),R("p",zl,[(A(!0),R(Oe,null,Je(u.value,p=>(A(),le(Me,{key:p.text,class:Ie(["action-button",[p.type]]),item:p},null,8,["class","item"]))),128))])):ae("v-if",!0)]))}}),Bl=te(ql,[["__file","HomeHero.vue"]]),Fl={class:"home"},Ul=W({__name:"Home",setup(e){return(t,n)=>(A(),R("main",Fl,[Y(Bl),Y(Al),Y(kl),Y(xl)]))}}),Wl=te(Ul,[["__file","Home.vue"]]),Kl=W({__name:"NavbarBrand",setup(e){const t=Zt(),n=cr(),r=ve(),o=mr(),a=C(()=>r.value.home||t.value),l=C(()=>n.value.title),i=C(()=>o.value&&r.value.logoDark!==void 0?r.value.logoDark:r.value.logo),s=()=>{if(!i.value)return null;const u=Z("img",{class:"logo",src:sr(i.value),alt:l.value});return r.value.logoDark===void 0?u:Z(lr,()=>u)};return(u,c)=>{const d=ze("RouterLink");return A(),le(d,{to:a.value},{default:me(()=>[Y(s),l.value?(A(),R("span",{key:0,class:Ie(["site-name",{"can-hide":i.value}])},ce(l.value),3)):ae("v-if",!0)]),_:1},8,["to"])}}}),Gl=te(Kl,[["__file","NavbarBrand.vue"]]),Jl=W({__name:"DropdownTransition",setup(e){const t=r=>{r.style.height=r.scrollHeight+"px"},n=r=>{r.style.height=""};return(r,o)=>(A(),le(tr,{name:"dropdown",onEnter:t,onAfterEnter:n,onBeforeLeave:t},{default:me(()=>[Q(r.$slots,"default")]),_:3}))}}),Sa=te(Jl,[["__file","DropdownTransition.vue"]]),Ql=["aria-label"],Zl={class:"title"},Yl=U("span",{class:"arrow down"},null,-1),Xl=["aria-label"],es={class:"title"},ts={class:"navbar-dropdown"},ns={class:"navbar-dropdown-subtitle"},rs={key:1},os={class:"navbar-dropdown-subitem-wrapper"},as=W({__name:"NavbarDropdown",props:{item:{type:Object,required:!0}},setup(e){const t=e,{item:n}=ar(t),r=C(()=>n.value.ariaLabel||n.value.text),o=ie(!1),a=Ze();Ge(()=>a.path,()=>{o.value=!1});const l=s=>{s.detail===0?o.value=!o.value:o.value=!1},i=(s,u)=>u[u.length-1]===s;return(s,u)=>(A(),R("div",{class:Ie(["navbar-dropdown-wrapper",{open:o.value}])},[U("button",{class:"navbar-dropdown-title",type:"button","aria-label":r.value,onClick:l},[U("span",Zl,ce(H(n).text),1),Yl],8,Ql),U("button",{class:"navbar-dropdown-title-mobile",type:"button","aria-label":r.value,onClick:u[0]||(u[0]=c=>o.value=!o.value)},[U("span",es,ce(H(n).text),1),U("span",{class:Ie(["arrow",o.value?"down":"right"])},null,2)],8,Xl),Y(Sa,null,{default:me(()=>[En(U("ul",ts,[(A(!0),R(Oe,null,Je(H(n).children,c=>(A(),R("li",{key:c.text,class:"navbar-dropdown-item"},[c.children?(A(),R(Oe,{key:0},[U("h4",ns,[c.link?(A(),le(Me,{key:0,item:c,onFocusout:d=>i(c,H(n).children)&&c.children.length===0&&(o.value=!1)},null,8,["item","onFocusout"])):(A(),R("span",rs,ce(c.text),1))]),U("ul",os,[(A(!0),R(Oe,null,Je(c.children,d=>(A(),R("li",{key:d.link,class:"navbar-dropdown-subitem"},[Y(Me,{item:d,onFocusout:m=>i(d,c.children)&&i(c,H(n).children)&&(o.value=!1)},null,8,["item","onFocusout"])]))),128))])],64)):(A(),le(Me,{key:1,item:c,onFocusout:d=>i(c,H(n).children)&&(o.value=!1)},null,8,["item","onFocusout"]))]))),128))],512),[[On,o.value]])]),_:1})],2))}}),is=te(as,[["__file","NavbarDropdown.vue"]]),Ur=e=>decodeURI(e).replace(/#.*$/,"").replace(/(index)?\.(md|html)$/,""),cs=(e,t)=>{if(t.hash===e)return!0;const n=Ur(t.path),r=Ur(e);return n===r},Pa=(e,t)=>e.link&&cs(e.link,t)?!0:e.children?e.children.some(n=>Pa(n,t)):!1,Ia=e=>!Qt(e)||/github\.com/.test(e)?"GitHub":/bitbucket\.org/.test(e)?"Bitbucket":/gitlab\.com/.test(e)?"GitLab":/gitee\.com/.test(e)?"Gitee":null,ls={GitHub:":repo/edit/:branch/:path",GitLab:":repo/-/edit/:branch/:path",Gitee:":repo/edit/:branch/:path",Bitbucket:":repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default"},ss=({docsRepo:e,editLinkPattern:t})=>{if(t)return t;const n=Ia(e);return n!==null?ls[n]:null},us=({docsRepo:e,docsBranch:t,docsDir:n,filePathRelative:r,editLinkPattern:o})=>{if(!r)return null;const a=ss({docsRepo:e,editLinkPattern:o});return a?a.replace(/:repo/,Qt(e)?e:`https://github.com/${e}`).replace(/:branch/,t).replace(/:path/,Xo(`${ra(n)}/${r}`)):null},ds={key:0,class:"navbar-items"},ms=W({__name:"NavbarItems",setup(e){const t=()=>{const c=at(),d=Zt(),m=cr(),p=ve();return C(()=>{const h=Object.keys(m.value.locales);if(h.length<2)return[];const _=c.currentRoute.value.path,v=c.currentRoute.value.fullPath;return[{text:p.value.selectLanguageText??"unknown language",ariaLabel:p.value.selectLanguageAriaLabel??p.value.selectLanguageText??"unknown language",children:h.map(y=>{var b,L;const O=((b=m.value.locales)==null?void 0:b[y])??{},w=((L=p.value.locales)==null?void 0:L[y])??{},S=`${O.lang}`,T=w.selectLanguageName??S;let k;if(S===m.value.lang)k=v;else{const P=_.replace(d.value,y);c.getRoutes().some(N=>N.path===P)?k=v.replace(_,P):k=w.home??y}return{text:T,link:k}})}]})},n=()=>{const c=ve(),d=C(()=>c.value.repo),m=C(()=>d.value?Ia(d.value):null),p=C(()=>d.value&&!Qt(d.value)?`https://github.com/${d.value}`:d.value),h=C(()=>p.value?c.value.repoLabel?c.value.repoLabel:m.value===null?"Source":m.value:null);return C(()=>!p.value||!h.value?[]:[{text:h.value,link:p.value}])},r=c=>je(c)?fr(c):c.children?{...c,children:c.children.map(r)}:c,o=()=>{const c=ve();return C(()=>(c.value.navbar||[]).map(r))},a=ie(!1),l=o(),i=t(),s=n(),u=C(()=>[...l.value,...i.value,...s.value]);return we(()=>{const d=()=>{window.innerWidth<719?a.value=!0:a.value=!1};d(),window.addEventListener("resize",d,!1),window.addEventListener("orientationchange",d,!1)}),(c,d)=>u.value.length?(A(),R("nav",ds,[(A(!0),R(Oe,null,Je(u.value,m=>(A(),R("div",{key:m.text,class:"navbar-item"},[m.children?(A(),le(is,{key:0,item:m,class:Ie(a.value?"mobile":"")},null,8,["item","class"])):(A(),le(Me,{key:1,item:m},null,8,["item"]))]))),128))])):ae("v-if",!0)}}),ka=te(ms,[["__file","NavbarItems.vue"]]),fs=["title"],ps={class:"icon",focusable:"false",viewBox:"0 0 32 32"},vs=Si('',9),hs=[vs],_s={class:"icon",focusable:"false",viewBox:"0 0 32 32"},gs=U("path",{d:"M13.502 5.414a15.075 15.075 0 0 0 11.594 18.194a11.113 11.113 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3z",fill:"currentColor"},null,-1),ys=[gs],bs=W({__name:"ToggleColorModeButton",setup(e){const t=ve(),n=mr(),r=()=>{n.value=!n.value};return(o,a)=>(A(),R("button",{class:"toggle-color-mode-button",title:H(t).toggleColorMode,onClick:r},[En((A(),R("svg",ps,hs,512)),[[On,!H(n)]]),En((A(),R("svg",_s,ys,512)),[[On,H(n)]])],8,fs))}}),Es=te(bs,[["__file","ToggleColorModeButton.vue"]]),Os=["title"],ws=U("div",{class:"icon","aria-hidden":"true"},[U("span"),U("span"),U("span")],-1),Ss=[ws],Ps=W({__name:"ToggleSidebarButton",emits:["toggle"],setup(e){const t=ve();return(n,r)=>(A(),R("div",{class:"toggle-sidebar-button",title:H(t).toggleSidebar,"aria-expanded":"false",role:"button",tabindex:"0",onClick:r[0]||(r[0]=o=>n.$emit("toggle"))},Ss,8,Os))}}),Is=te(Ps,[["__file","ToggleSidebarButton.vue"]]),ks=W({__name:"Navbar",emits:["toggle-sidebar"],setup(e){const t=ve(),n=ie(null),r=ie(null),o=ie(0),a=C(()=>o.value?{maxWidth:o.value+"px"}:{});we(()=>{const s=l(n.value,"paddingLeft")+l(n.value,"paddingRight"),u=()=>{var c;window.innerWidth<719?o.value=0:o.value=n.value.offsetWidth-s-(((c=r.value)==null?void 0:c.offsetWidth)||0)};u(),window.addEventListener("resize",u,!1),window.addEventListener("orientationchange",u,!1)});function l(i,s){var d,m,p;const u=(p=(m=(d=i==null?void 0:i.ownerDocument)==null?void 0:d.defaultView)==null?void 0:m.getComputedStyle(i,null))==null?void 0:p[s],c=Number.parseInt(u,10);return Number.isNaN(c)?0:c}return(i,s)=>{const u=ze("NavbarSearch");return A(),R("header",{ref_key:"navbar",ref:n,class:"navbar"},[Y(Is,{onToggle:s[0]||(s[0]=c=>i.$emit("toggle-sidebar"))}),U("span",{ref_key:"navbarBrand",ref:r},[Y(Gl)],512),U("div",{class:"navbar-items-wrapper",style:ta(a.value)},[Q(i.$slots,"before"),Y(ka,{class:"can-hide"}),Q(i.$slots,"after"),H(t).colorModeSwitch?(A(),le(Es,{key:0})):ae("v-if",!0),Y(u)],4)],512)}}}),Ds=te(ks,[["__file","Navbar.vue"]]),Ls={class:"page-meta"},As={key:0,class:"meta-item edit-link"},Ts={key:1,class:"meta-item last-updated"},js={class:"meta-item-label"},Cs={class:"meta-item-info"},xs={key:2,class:"meta-item contributors"},Rs={class:"meta-item-label"},Ns={class:"meta-item-info"},Vs=["title"],Hs=W({__name:"PageMeta",setup(e){const t=()=>{const s=ve(),u=rt(),c=He();return C(()=>{if(!(c.value.editLink??s.value.editLink??!0))return null;const{repo:m,docsRepo:p=m,docsBranch:h="main",docsDir:_="",editLinkText:v}=s.value;if(!p)return null;const g=us({docsRepo:p,docsBranch:h,docsDir:_,filePathRelative:u.value.filePathRelative,editLinkPattern:c.value.editLinkPattern??s.value.editLinkPattern});return g?{text:v??"Edit this page",link:g}:null})},n=()=>{const s=ve(),u=rt(),c=He();return C(()=>{var p,h;return!(c.value.lastUpdated??s.value.lastUpdated??!0)||!((p=u.value.git)!=null&&p.updatedTime)?null:new Date((h=u.value.git)==null?void 0:h.updatedTime).toLocaleString()})},r=()=>{const s=ve(),u=rt(),c=He();return C(()=>{var m;return c.value.contributors??s.value.contributors??!0?((m=u.value.git)==null?void 0:m.contributors)??null:null})},o=ve(),a=t(),l=n(),i=r();return(s,u)=>{const c=ze("ClientOnly");return A(),R("footer",Ls,[H(a)?(A(),R("div",As,[Y(Me,{class:"meta-item-label",item:H(a)},null,8,["item"])])):ae("v-if",!0),H(l)?(A(),R("div",Ts,[U("span",js,ce(H(o).lastUpdatedText)+": ",1),Y(c,null,{default:me(()=>[U("span",Cs,ce(H(l)),1)]),_:1})])):ae("v-if",!0),H(i)&&H(i).length?(A(),R("div",xs,[U("span",Rs,ce(H(o).contributorsText)+": ",1),U("span",Ns,[(A(!0),R(Oe,null,Je(H(i),(d,m)=>(A(),R(Oe,{key:m},[U("span",{class:"contributor",title:`email: ${d.email}`},ce(d.name),9,Vs),m!==H(i).length-1?(A(),R(Oe,{key:0},[_t(", ")],64)):ae("v-if",!0)],64))),128))])])):ae("v-if",!0)])}}}),Ms=te(Hs,[["__file","PageMeta.vue"]]),$s={key:0,class:"page-nav"},zs={class:"inner"},qs={key:0,class:"prev"},Bs={key:1,class:"next"},Fs=W({__name:"PageNav",setup(e){const t=s=>s===!1?null:je(s)?fr(s):or(s)?s:!1,n=(s,u,c)=>{const d=s.findIndex(m=>m.link===u);if(d!==-1){const m=s[d+c];return m!=null&&m.link?m:null}for(const m of s)if(m.children){const p=n(m.children,u,c);if(p)return p}return null},r=He(),o=pr(),a=Ze(),l=C(()=>{const s=t(r.value.prev);return s!==!1?s:n(o.value,a.path,-1)}),i=C(()=>{const s=t(r.value.next);return s!==!1?s:n(o.value,a.path,1)});return(s,u)=>l.value||i.value?(A(),R("nav",$s,[U("p",zs,[l.value?(A(),R("span",qs,[Y(Me,{item:l.value},null,8,["item"])])):ae("v-if",!0),i.value?(A(),R("span",Bs,[Y(Me,{item:i.value},null,8,["item"])])):ae("v-if",!0)])])):ae("v-if",!0)}}),Us=te(Fs,[["__file","PageNav.vue"]]),Ws={class:"page"},Ks={class:"theme-default-content"},Gs=W({__name:"Page",setup(e){return(t,n)=>{const r=ze("Content");return A(),R("main",Ws,[Q(t.$slots,"top"),U("div",Ks,[Q(t.$slots,"content-top"),Y(r),Q(t.$slots,"content-bottom")]),Y(Ms),Y(Us),Q(t.$slots,"bottom")])}}}),Js=te(Gs,[["__file","Page.vue"]]),Qs={class:"sidebar-item-children"},Zs=W({__name:"SidebarItem",props:{item:{type:Object,required:!0},depth:{type:Number,required:!1,default:0}},setup(e){const t=e,{item:n,depth:r}=ar(t),o=Ze(),a=at(),l=C(()=>Pa(n.value,o)),i=C(()=>({"sidebar-item":!0,"sidebar-heading":r.value===0,active:l.value,collapsible:n.value.collapsible})),s=C(()=>n.value.collapsible?l.value:!0),[u,c]=zc(s.value),d=p=>{n.value.collapsible&&(p.preventDefault(),c())},m=a.afterEach(p=>{nr(()=>{u.value=s.value})});return ea(()=>{m()}),(p,h)=>{var v;const _=ze("SidebarItem",!0);return A(),R("li",null,[H(n).link?(A(),le(Me,{key:0,class:Ie(i.value),item:H(n)},null,8,["class","item"])):(A(),R("p",{key:1,tabindex:"0",class:Ie(i.value),onClick:d,onKeydown:Pi(d,["enter"])},[_t(ce(H(n).text)+" ",1),H(n).collapsible?(A(),R("span",{key:0,class:Ie(["arrow",H(u)?"down":"right"])},null,2)):ae("v-if",!0)],34)),(v=H(n).children)!=null&&v.length?(A(),le(Sa,{key:2},{default:me(()=>[En(U("ul",Qs,[(A(!0),R(Oe,null,Je(H(n).children,g=>(A(),le(_,{key:`${H(r)}${g.text}${g.link}`,item:g,depth:H(r)+1},null,8,["item","depth"]))),128))],512),[[On,H(u)]])]),_:1})):ae("v-if",!0)])}}}),Ys=te(Zs,[["__file","SidebarItem.vue"]]),Xs={key:0,class:"sidebar-items"},eu=W({__name:"SidebarItems",setup(e){const t=Ze(),n=pr();return we(()=>{Ge(()=>t.hash,r=>{const o=document.querySelector(".sidebar");if(!o)return;const a=document.querySelector(`.sidebar a.sidebar-item[href="${t.path}${r}"]`);if(!a)return;const{top:l,height:i}=o.getBoundingClientRect(),{top:s,height:u}=a.getBoundingClientRect();sl+i&&a.scrollIntoView(!1)})}),(r,o)=>H(n).length?(A(),R("ul",Xs,[(A(!0),R(Oe,null,Je(H(n),a=>(A(),le(Ys,{key:`${a.text}${a.link}`,item:a},null,8,["item"]))),128))])):ae("v-if",!0)}}),tu=te(eu,[["__file","SidebarItems.vue"]]),nu={class:"sidebar"},ru=W({__name:"Sidebar",setup(e){return(t,n)=>(A(),R("aside",nu,[Y(ka),Q(t.$slots,"top"),Y(tu),Q(t.$slots,"bottom")]))}}),ou=te(ru,[["__file","Sidebar.vue"]]),au=W({__name:"Layout",setup(e){const t=rt(),n=He(),r=ve(),o=C(()=>n.value.navbar!==!1&&r.value.navbar!==!1),a=pr(),l=ie(!1),i=v=>{l.value=typeof v=="boolean"?v:!l.value},s={x:0,y:0},u=v=>{s.x=v.changedTouches[0].clientX,s.y=v.changedTouches[0].clientY},c=v=>{const g=v.changedTouches[0].clientX-s.x,y=v.changedTouches[0].clientY-s.y;Math.abs(g)>Math.abs(y)&&Math.abs(g)>40&&(g>0&&s.x<=80?i(!0):i(!1))},d=C(()=>[{"no-navbar":!o.value,"no-sidebar":!a.value.length,"sidebar-open":l.value},n.value.pageClass]);let m;we(()=>{m=at().afterEach(()=>{i(!1)})}),na(()=>{m()});const p=Ea(),h=p.resolve,_=p.pending;return(v,g)=>(A(),R("div",{class:Ie(["theme-container",d.value]),onTouchstart:u,onTouchend:c},[Q(v.$slots,"navbar",{},()=>[o.value?(A(),le(Ds,{key:0,onToggleSidebar:i},{before:me(()=>[Q(v.$slots,"navbar-before")]),after:me(()=>[Q(v.$slots,"navbar-after")]),_:3})):ae("v-if",!0)]),U("div",{class:"sidebar-mask",onClick:g[0]||(g[0]=y=>i(!1))}),Q(v.$slots,"sidebar",{},()=>[Y(ou,null,{top:me(()=>[Q(v.$slots,"sidebar-top")]),bottom:me(()=>[Q(v.$slots,"sidebar-bottom")]),_:3})]),Q(v.$slots,"page",{},()=>[H(n).home?(A(),le(Wl,{key:0})):(A(),le(tr,{key:1,name:"fade-slide-y",mode:"out-in",onBeforeEnter:H(h),onBeforeLeave:H(_)},{default:me(()=>[(A(),le(Js,{key:H(t).path},{top:me(()=>[Q(v.$slots,"page-top")]),"content-top":me(()=>[Q(v.$slots,"page-content-top")]),"content-bottom":me(()=>[Q(v.$slots,"page-content-bottom")]),bottom:me(()=>[Q(v.$slots,"page-bottom")]),_:3}))]),_:3},8,["onBeforeEnter","onBeforeLeave"]))])],34))}}),iu=te(au,[["__file","Layout.vue"]]),cu={class:"theme-container"},lu={class:"page"},su={class:"theme-default-content"},uu=U("h1",null,"404",-1),du=W({__name:"NotFound",setup(e){const t=Zt(),n=ve(),r=n.value.notFound??["Not Found"],o=()=>r[Math.floor(Math.random()*r.length)],a=n.value.home??t.value,l=n.value.backToHome??"Back to home";return(i,s)=>{const u=ze("RouterLink");return A(),R("div",cu,[U("main",lu,[U("div",su,[uu,U("blockquote",null,ce(o()),1),Y(u,{to:H(a)},{default:me(()=>[_t(ce(H(l)),1)]),_:1},8,["to"])])])])}}}),mu=te(du,[["__file","NotFound.vue"]]);const fu=xe({enhance({app:e,router:t}){e.component("Badge",kc),e.component("CodeGroup",Dc),e.component("CodeGroupItem",jc),e.component("AutoLinkExternalIcon",()=>{const r=e.component("ExternalLinkIcon");return r?Z(r):null}),e.component("NavbarSearch",()=>{const r=e.component("Docsearch")||e.component("SearchBox");return r?Z(r):null});const n=t.options.scrollBehavior;t.options.scrollBehavior=async(...r)=>(await Ea().wait(),n(...r))},setup(){hl(),yl()},layouts:{Layout:iu,NotFound:mu}});/*! @docsearch/js 3.6.1 | MIT License | ยฉ Algolia, Inc. and contributors | https://docsearch.algolia.com */function Wr(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function K(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n3)for(n=[n],a=3;a0?Lt(p.type,p.props,p.key,null,p.__v):p)!=null){if(p.__=n,p.__b=n.__b+1,(m=g[c])===null||m&&p.key==m.key&&p.type===m.type)g[c]=void 0;else for(d=0;d3)for(n=[n],a=3;a=n.__.length&&n.__.push({}),n.__[e]}function gr(e){return ht=1,$a(za,e)}function $a(e,t,n){var r=Yt(yt++,2);return r.t=e,r.__c||(r.__=[n?n(t):za(void 0,t),function(o){var a=r.t(r.__[0],o);r.__[0]!==a&&(r.__=[a,r.__[1]],r.__c.setState({}))}],r.__c=he),r.__}function yr(e,t){var n=Yt(yt++,3);!x.__s&&br(n.__H,t)&&(n.__=e,n.__H=t,he.__H.__h.push(n))}function ro(e,t){var n=Yt(yt++,4);!x.__s&&br(n.__H,t)&&(n.__=e,n.__H=t,he.__h.push(n))}function xn(e,t){var n=Yt(yt++,7);return br(n.__H,t)&&(n.__=e(),n.__H=t,n.__h=e),n.__}function yu(){Kn.forEach(function(e){if(e.__P)try{e.__H.__h.forEach(_n),e.__H.__h.forEach(Gn),e.__H.__h=[]}catch(t){e.__H.__h=[],x.__e(t,e.__v)}}),Kn=[]}x.__b=function(e){he=null,Yr&&Yr(e)},x.__r=function(e){Xr&&Xr(e),yt=0;var t=(he=e.__c).__H;t&&(t.__h.forEach(_n),t.__h.forEach(Gn),t.__h=[])},x.diffed=function(e){eo&&eo(e);var t=e.__c;t&&t.__H&&t.__H.__h.length&&(Kn.push(t)!==1&&Zr===x.requestAnimationFrame||((Zr=x.requestAnimationFrame)||function(n){var r,o=function(){clearTimeout(a),oo&&cancelAnimationFrame(r),setTimeout(n)},a=setTimeout(o,100);oo&&(r=requestAnimationFrame(o))})(yu)),he=void 0},x.__c=function(e,t){t.some(function(n){try{n.__h.forEach(_n),n.__h=n.__h.filter(function(r){return!r.__||Gn(r)})}catch(r){t.some(function(o){o.__h&&(o.__h=[])}),t=[],x.__e(r,n.__v)}}),to&&to(e,t)},x.unmount=function(e){no&&no(e);var t=e.__c;if(t&&t.__H)try{t.__H.__.forEach(_n)}catch(n){x.__e(n,t.__v)}};var oo=typeof requestAnimationFrame=="function";function _n(e){var t=he;typeof e.__c=="function"&&e.__c(),he=t}function Gn(e){var t=he;e.__c=e.__(),he=t}function br(e,t){return!e||e.length!==t.length||t.some(function(n,r){return n!==e[r]})}function za(e,t){return typeof t=="function"?t(e):t}function qa(e,t){for(var n in t)e[n]=t[n];return e}function Jn(e,t){for(var n in e)if(n!=="__source"&&!(n in t))return!0;for(var r in t)if(r!=="__source"&&e[r]!==t[r])return!0;return!1}function Qn(e){this.props=e}(Qn.prototype=new Ce).isPureReactComponent=!0,Qn.prototype.shouldComponentUpdate=function(e,t){return Jn(this.props,e)||Jn(this.state,t)};var ao=x.__b;x.__b=function(e){e.type&&e.type.__f&&e.ref&&(e.props.ref=e.ref,e.ref=null),ao&&ao(e)};var bu=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.forward_ref")||3911,io=function(e,t){return e==null?null:$e($e(e).map(t))},Eu={map:io,forEach:io,count:function(e){return e?$e(e).length:0},only:function(e){var t=$e(e);if(t.length!==1)throw"Children.only";return t[0]},toArray:$e},Ou=x.__e;function gn(){this.__u=0,this.t=null,this.__b=null}function Ba(e){var t=e.__.__c;return t&&t.__e&&t.__e(e)}function It(){this.u=null,this.o=null}x.__e=function(e,t,n){if(e.then){for(var r,o=t;o=o.__;)if((r=o.__c)&&r.__c)return t.__e==null&&(t.__e=n.__e,t.__k=n.__k),r.__c(e,t)}Ou(e,t,n)},(gn.prototype=new Ce).__c=function(e,t){var n=t.__c,r=this;r.t==null&&(r.t=[]),r.t.push(n);var o=Ba(r.__v),a=!1,l=function(){a||(a=!0,n.componentWillUnmount=n.__c,o?o(i):i())};n.__c=n.componentWillUnmount,n.componentWillUnmount=function(){l(),n.__c&&n.__c()};var i=function(){if(!--r.__u){if(r.state.__e){var u=r.state.__e;r.__v.__k[0]=function d(m,p,h){return m&&(m.__v=null,m.__k=m.__k&&m.__k.map(function(_){return d(_,p,h)}),m.__c&&m.__c.__P===p&&(m.__e&&h.insertBefore(m.__e,m.__d),m.__c.__e=!0,m.__c.__P=h)),m}(u,u.__c.__P,u.__c.__O)}var c;for(r.setState({__e:r.__b=null});c=r.t.pop();)c.forceUpdate()}},s=t.__h===!0;r.__u++||s||r.setState({__e:r.__b=r.__v.__k[0]}),e.then(l,l)},gn.prototype.componentWillUnmount=function(){this.t=[]},gn.prototype.render=function(e,t){if(this.__b){if(this.__v.__k){var n=document.createElement("div"),r=this.__v.__k[0].__c;this.__v.__k[0]=function a(l,i,s){return l&&(l.__c&&l.__c.__H&&(l.__c.__H.__.forEach(function(u){typeof u.__c=="function"&&u.__c()}),l.__c.__H=null),(l=qa({},l)).__c!=null&&(l.__c.__P===s&&(l.__c.__P=i),l.__c=null),l.__k=l.__k&&l.__k.map(function(u){return a(u,i,s)})),l}(this.__b,n,r.__O=r.__P)}this.__b=null}var o=t.__e&&Te(Qe,null,e.fallback);return o&&(o.__h=null),[Te(Qe,null,t.__e?null:e.children),o]};var co=function(e,t,n){if(++n[1]===n[0]&&e.o.delete(t),e.props.revealOrder&&(e.props.revealOrder[0]!=="t"||!e.o.size))for(n=e.u;n;){for(;n.length>3;)n.pop()();if(n[1]>>1,1),t.i.removeChild(r)}}),Kt(Te(wu,{context:t.context},e.__v),t.l)):t.l&&t.componentWillUnmount()}function Fa(e,t){return Te(Su,{__v:e,i:t})}(It.prototype=new Ce).__e=function(e){var t=this,n=Ba(t.__v),r=t.o.get(e);return r[0]++,function(o){var a=function(){t.props.revealOrder?(r.push(o),co(t,e,r)):o()};n?n(a):a()}},It.prototype.render=function(e){this.u=null,this.o=new Map;var t=$e(e.children);e.revealOrder&&e.revealOrder[0]==="b"&&t.reverse();for(var n=t.length;n--;)this.o.set(t[n],this.u=[1,0,this.u]);return e.children},It.prototype.componentDidUpdate=It.prototype.componentDidMount=function(){var e=this;this.o.forEach(function(t,n){co(e,n,t)})};var Ua=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.element")||60103,Pu=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|fill|flood|font|glyph(?!R)|horiz|marker(?!H|W|U)|overline|paint|stop|strikethrough|stroke|text(?!L)|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,Iu=function(e){return(typeof Symbol<"u"&&V(Symbol())=="symbol"?/fil|che|rad/i:/fil|che|ra/i).test(e)};function Wa(e,t,n){return t.__k==null&&(t.textContent=""),Kt(e,t),typeof n=="function"&&n(),e?e.__c:null}Ce.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(e){Object.defineProperty(Ce.prototype,e,{configurable:!0,get:function(){return this["UNSAFE_"+e]},set:function(t){Object.defineProperty(this,e,{configurable:!0,writable:!0,value:t})}})});var lo=x.event;function ku(){}function Du(){return this.cancelBubble}function Lu(){return this.defaultPrevented}x.event=function(e){return lo&&(e=lo(e)),e.persist=ku,e.isPropagationStopped=Du,e.isDefaultPrevented=Lu,e.nativeEvent=e};var Ka,so={configurable:!0,get:function(){return this.class}},uo=x.vnode;x.vnode=function(e){var t=e.type,n=e.props,r=n;if(typeof t=="string"){for(var o in r={},n){var a=n[o];o==="value"&&"defaultValue"in n&&a==null||(o==="defaultValue"&&"value"in n&&n.value==null?o="value":o==="download"&&a===!0?a="":/ondoubleclick/i.test(o)?o="ondblclick":/^onchange(textarea|input)/i.test(o+t)&&!Iu(n.type)?o="oninput":/^on(Ani|Tra|Tou|BeforeInp)/.test(o)?o=o.toLowerCase():Pu.test(o)?o=o.replace(/[A-Z0-9]/,"-$&").toLowerCase():a===null&&(a=void 0),r[o]=a)}t=="select"&&r.multiple&&Array.isArray(r.value)&&(r.value=$e(n.children).forEach(function(l){l.props.selected=r.value.indexOf(l.props.value)!=-1})),t=="select"&&r.defaultValue!=null&&(r.value=$e(n.children).forEach(function(l){l.props.selected=r.multiple?r.defaultValue.indexOf(l.props.value)!=-1:r.defaultValue==l.props.value})),e.props=r}t&&n.class!=n.className&&(so.enumerable="className"in n,n.className!=null&&(r.class=n.className),Object.defineProperty(r,"className",so)),e.$$typeof=Ua,uo&&uo(e)};var mo=x.__r;x.__r=function(e){mo&&mo(e),Ka=e.__c};var Au={ReactCurrentDispatcher:{current:{readContext:function(e){return Ka.__n[e.__c].props.value}}}};function fo(e){return!!e&&e.$$typeof===Ua}(typeof performance>"u"?"undefined":V(performance))=="object"&&typeof performance.now=="function"&&performance.now.bind(performance);var f={useState:gr,useReducer:$a,useEffect:yr,useLayoutEffect:ro,useRef:function(e){return ht=5,xn(function(){return{current:e}},[])},useImperativeHandle:function(e,t,n){ht=6,ro(function(){typeof e=="function"?e(t()):e&&(e.current=t())},n==null?n:n.concat(e))},useMemo:xn,useCallback:function(e,t){return ht=8,xn(function(){return e},t)},useContext:function(e){var t=he.context[e.__c],n=Yt(yt++,9);return n.__c=e,t?(n.__==null&&(n.__=!0,t.sub(he)),t.props.value):e.__},useDebugValue:function(e,t){x.useDebugValue&&x.useDebugValue(t?t(e):e)},version:"16.8.0",Children:Eu,render:Wa,hydrate:function(e,t,n){return Ma(e,t),typeof n=="function"&&n(),e?e.__c:null},unmountComponentAtNode:function(e){return!!e.__k&&(Kt(null,e),!0)},createPortal:Fa,createElement:Te,createContext:function(e,t){var n={__c:t="__cC"+Aa++,__:e,Consumer:function(r,o){return r.children(o)},Provider:function(r){var o,a;return this.getChildContext||(o=[],(a={})[t]=this,this.getChildContext=function(){return a},this.shouldComponentUpdate=function(l){this.props.value!==l.value&&o.some(Wn)},this.sub=function(l){o.push(l);var i=l.componentWillUnmount;l.componentWillUnmount=function(){o.splice(o.indexOf(l),1),i&&i.call(l)}}),r.children}};return n.Provider.__=n.Consumer.contextType=n},createFactory:function(e){return Te.bind(null,e)},cloneElement:function(e){return fo(e)?gu.apply(null,arguments):e},createRef:function(){return{current:null}},Fragment:Qe,isValidElement:fo,findDOMNode:function(e){return e&&(e.base||e.nodeType===1&&e)||null},Component:Ce,PureComponent:Qn,memo:function(e,t){function n(o){var a=this.props.ref,l=a==o.ref;return!l&&a&&(a.call?a(null):a.current=null),t?!t(this.props,o)||!l:Jn(this.props,o)}function r(o){return this.shouldComponentUpdate=n,Te(e,o)}return r.displayName="Memo("+(e.displayName||e.name)+")",r.prototype.isReactComponent=!0,r.__f=!0,r},forwardRef:function(e){function t(n,r){var o=qa({},n);return delete o.ref,e(o,(r=n.ref||r)&&(V(r)!="object"||"current"in r)?r:null)}return t.$$typeof=bu,t.render=t,t.prototype.isReactComponent=t.__f=!0,t.displayName="ForwardRef("+(e.displayName||e.name)+")",t},unstable_batchedUpdates:function(e,t){return e(t)},StrictMode:Qe,Suspense:gn,SuspenseList:It,lazy:function(e){var t,n,r;function o(a){if(t||(t=e()).then(function(l){n=l.default||l},function(l){r=l}),r)throw r;if(!n)throw t;return Te(n,a)}return o.displayName="Lazy",o.__f=!0,o},__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:Au},Tu=["facetName","facetQuery"];function po(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function At(e){for(var t=1;t=0||(c[s]=l[s]);return c}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function ot(e,t){return function(n){if(Array.isArray(n))return n}(e)||function(n,r){var o=n==null?null:typeof Symbol<"u"&&n[Symbol.iterator]||n["@@iterator"];if(o!=null){var a,l,i=[],s=!0,u=!1;try{for(o=o.call(n);!(s=(a=o.next()).done)&&(i.push(a.value),!r||i.length!==r);s=!0);}catch(c){u=!0,l=c}finally{try{s||o.return==null||o.return()}finally{if(u)throw l}}return i}}(e,t)||Ga(e,t)||function(){throw new TypeError(`Invalid attempt to destructure non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}function Ga(e,t){if(e){if(typeof e=="string")return Zn(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return n==="Object"&&e.constructor&&(n=e.constructor.name),n==="Map"||n==="Set"?Array.from(e):n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?Zn(e,t):void 0}}function Zn(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(c[s]=l[s]);return c}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function yo(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function gt(e){for(var t=1;t1&&arguments[1]!==void 0?arguments[1]:20,n=[],r=0;re.length)&&(t=e.length);for(var n=0,r=new Array(t);n=3||m===2&&p>=4||m===1&&p>=10);function _(v,g,y){if(h&&y!==void 0){var O=y[0].__autocomplete_algoliaCredentials,w={"X-Algolia-Application-Id":O.appId,"X-Algolia-API-Key":O.apiKey};c.apply(void 0,[v].concat(on(g),[{headers:w}]))}else c.apply(void 0,[v].concat(on(g)))}return{init:function(v,g){c("init",{appId:v,apiKey:g})},setUserToken:function(v){c("setUserToken",v)},clickedObjectIDsAfterSearch:function(){for(var v=arguments.length,g=new Array(v),y=0;y0&&_("clickedObjectIDsAfterSearch",an(g),g[0].items)},clickedObjectIDs:function(){for(var v=arguments.length,g=new Array(v),y=0;y0&&_("clickedObjectIDs",an(g),g[0].items)},clickedFilters:function(){for(var v=arguments.length,g=new Array(v),y=0;y0&&c.apply(void 0,["clickedFilters"].concat(g))},convertedObjectIDsAfterSearch:function(){for(var v=arguments.length,g=new Array(v),y=0;y0&&_("convertedObjectIDsAfterSearch",an(g),g[0].items)},convertedObjectIDs:function(){for(var v=arguments.length,g=new Array(v),y=0;y0&&_("convertedObjectIDs",an(g),g[0].items)},convertedFilters:function(){for(var v=arguments.length,g=new Array(v),y=0;y0&&c.apply(void 0,["convertedFilters"].concat(g))},viewedObjectIDs:function(){for(var v=arguments.length,g=new Array(v),y=0;y0&&g.reduce(function(O,w){var S=w.items,T=Ya(w,Mu);return[].concat(on(O),on(zu(gt(gt({},T),{},{objectIDs:(S==null?void 0:S.map(function(k){return k.objectID}))||T.objectIDs})).map(function(k){return{items:S,payload:k}})))},[]).forEach(function(O){var w=O.items;return _("viewedObjectIDs",[O.payload],w)})},viewedFilters:function(){for(var v=arguments.length,g=new Array(v),y=0;y0&&c.apply(void 0,["viewedFilters"].concat(g))}}}(l),s={current:[]},u=Qa(function(c){var d=c.state;if(d.isOpen){var m=d.collections.reduce(function(p,h){return[].concat(ct(p),ct(h.items))},[]).filter(Nn);Za(s.current.map(function(p){return p.objectID}),m.map(function(p){return p.objectID}))||(s.current=m,m.length>0&&Uu({onItemsChange:r,items:m,insights:i,state:d}))}},0);return{name:"aa.algoliaInsightsPlugin",subscribe:function(c){var d=c.setContext,m=c.onSelect,p=c.onActive;l("addAlgoliaAgent","insights-plugin"),d({algoliaInsightsPlugin:{__algoliaSearchParameters:{clickAnalytics:!0},insights:i}}),m(function(h){var _=h.item,v=h.state,g=h.event;Nn(_)&&o({state:v,event:g,insights:i,item:_,insightsEvents:[We({eventName:"Item Selected"},_o({item:_,items:s.current}))]})}),p(function(h){var _=h.item,v=h.state,g=h.event;Nn(_)&&a({state:v,event:g,insights:i,item:_,insightsEvents:[We({eventName:"Item Active"},_o({item:_,items:s.current}))]})})},onStateChange:function(c){var d=c.state;u({state:d})},__autocomplete_pluginOptions:e}}function bn(e,t){var n=t;return{then:function(r,o){return bn(e.then(cn(r,n,e),cn(o,n,e)),n)},catch:function(r){return bn(e.catch(cn(r,n,e)),n)},finally:function(r){return r&&n.onCancelList.push(r),bn(e.finally(cn(r&&function(){return n.onCancelList=[],r()},n,e)),n)},cancel:function(){n.isCanceled=!0;var r=n.onCancelList;n.onCancelList=[],r.forEach(function(o){o()})},isCanceled:function(){return n.isCanceled===!0}}}function Eo(e){return bn(e,{isCanceled:!1,onCancelList:[]})}function cn(e,t,n){return e?function(r){return t.isCanceled?r:e(r)}:n}function Oo(e,t,n,r){if(!n)return null;if(e<0&&(t===null||r!==null&&t===0))return n+e;var o=(t===null?-1:t)+e;return o<=-1||o>=n?r===null?null:0:o}function wo(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function So(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(j[P]=b[P]);return j}(y,O);if(Object.getOwnPropertySymbols){var k=Object.getOwnPropertySymbols(y);for(S=0;S=0||Object.prototype.propertyIsEnumerable.call(y,w)&&(T[w]=y[w])}return T}(e,ed);Pt&&o.environment.clearTimeout(Pt);var u=s.setCollections,c=s.setIsOpen,d=s.setQuery,m=s.setActiveItemId,p=s.setStatus;if(d(a),m(o.defaultActiveItemId),!a&&o.openOnFocus===!1){var h,_=i.getState().collections.map(function(y){return St(St({},y),{},{items:[]})});p("idle"),u(_),c((h=r.isOpen)!==null&&h!==void 0?h:o.shouldPanelOpen({state:i.getState()}));var v=Eo(jo(_).then(function(){return Promise.resolve()}));return i.pendingRequests.add(v)}p("loading"),Pt=o.environment.setTimeout(function(){p("stalled")},o.stallThreshold);var g=Eo(jo(o.getSources(St({query:a,refresh:l,state:i.getState()},s)).then(function(y){return Promise.all(y.map(function(O){return Promise.resolve(O.getItems(St({query:a,refresh:l,state:i.getState()},s))).then(function(w){return function(S,T,k){if(L=S,Boolean(L==null?void 0:L.execute)){var b=S.requesterId==="algolia"?Object.assign.apply(Object,[{}].concat(ei(Object.keys(k.context).map(function(P){var N;return(N=k.context[P])===null||N===void 0?void 0:N.__algoliaSearchParameters})))):{};return st(st({},S),{},{requests:S.queries.map(function(P){return{query:S.requesterId==="algolia"?st(st({},P),{},{params:st(st({},b),P.params)}):P,sourceId:T,transformResponse:S.transformResponse}})})}var L;return{items:S,sourceId:T}}(w,O.sourceId,i.getState())})})).then(Xu).then(function(O){return function(w,S,T){return S.map(function(k){var b,L=w.filter(function(D){return D.sourceId===k.sourceId}),P=L.map(function(D){return D.items}),N=L[0].transformResponse,j=N?N({results:b=P,hits:b.map(function(D){return D.hits}).filter(Boolean),facetHits:b.map(function(D){var z;return(z=D.facetHits)===null||z===void 0?void 0:z.map(function(G){return{label:G.value,count:G.count,_highlightResult:{label:{value:G.highlighted}}}})}).filter(Boolean)}):P;return k.onResolve({source:k,results:P,items:j,state:T.getState()}),j.every(Boolean),'The `getItems` function from source "'.concat(k.sourceId,'" must return an array of items but returned ').concat(JSON.stringify(void 0),`. + +Did you forget to return items? + +See: https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/sources/#param-getitems`),{source:k,items:j}})}(O,y,i)}).then(function(O){return function(w){var S=w.props,T=w.state,k=w.collections.reduce(function(L,P){return sn(sn({},L),{},Xa({},P.source.sourceId,sn(sn({},P.source),{},{getItems:function(){return Gt(P.items)}})))},{}),b=S.plugins.reduce(function(L,P){return P.reshape?P.reshape(L):L},{sourcesBySourceId:k,state:T}).sourcesBySourceId;return Gt(S.reshape({sourcesBySourceId:b,sources:Object.values(b),state:T})).filter(Boolean).map(function(L){return{source:L,items:L.getItems()}})}({collections:O,props:o,state:i.getState()})})}))).then(function(y){var O;p("idle"),u(y);var w=o.shouldPanelOpen({state:i.getState()});c((O=r.isOpen)!==null&&O!==void 0?O:o.openOnFocus&&!a&&w||w);var S=pt(i.getState());if(i.getState().activeItemId!==null&&S){var T=S.item,k=S.itemInputValue,b=S.itemUrl,L=S.source;L.onActive(St({event:t,item:T,itemInputValue:k,itemUrl:b,refresh:l,source:L,state:i.getState()},s))}}).finally(function(){p("idle"),Pt&&o.environment.clearTimeout(Pt)});return i.pendingRequests.add(g)}function $t(e){return $t=typeof Symbol=="function"&&V(Symbol.iterator)=="symbol"?function(t){return V(t)}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":V(t)},$t(e)}var nd=["event","props","refresh","store"];function Co(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function Xe(e){for(var t=1;t=0||(c[s]=l[s]);return c}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function qt(e){return qt=typeof Symbol=="function"&&V(Symbol.iterator)=="symbol"?function(t){return V(t)}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":V(t)},qt(e)}function Ro(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function md(e){for(var t=1;t0},reshape:function(p){return p.sources}},s),{},{id:(c=s.id)!==null&&c!==void 0?c:"autocomplete-".concat(Nu++),plugins:m,initialState:lt({activeItemId:null,query:"",completion:null,collections:[],isOpen:!1,status:"idle",context:{}},s.initialState),onStateChange:function(p){var h;(h=s.onStateChange)===null||h===void 0||h.call(s,p),m.forEach(function(_){var v;return(v=_.onStateChange)===null||v===void 0?void 0:v.call(_,p)})},onSubmit:function(p){var h;(h=s.onSubmit)===null||h===void 0||h.call(s,p),m.forEach(function(_){var v;return(v=_.onSubmit)===null||v===void 0?void 0:v.call(_,p)})},onReset:function(p){var h;(h=s.onReset)===null||h===void 0||h.call(s,p),m.forEach(function(_){var v;return(v=_.onReset)===null||v===void 0?void 0:v.call(_,p)})},getSources:function(p){return Promise.all([].concat(function(h){return function(_){if(Array.isArray(_))return Hn(_)}(h)||function(_){if(typeof Symbol<"u"&&_[Symbol.iterator]!=null||_["@@iterator"]!=null)return Array.from(_)}(h)||function(_,v){if(_){if(typeof _=="string")return Hn(_,v);var g=Object.prototype.toString.call(_).slice(8,-1);return g==="Object"&&_.constructor&&(g=_.constructor.name),g==="Map"||g==="Set"?Array.from(_):g==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(g)?Hn(_,v):void 0}}(h)||function(){throw new TypeError(`Invalid attempt to spread non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}(m.map(function(h){return h.getSources})),[s.getSources]).filter(Boolean).map(function(h){return function(_,v){var g=[];return Promise.resolve(_(v)).then(function(y){return Promise.all(y.filter(function(O){return Boolean(O)}).map(function(O){if(O.sourceId,g.includes(O.sourceId))throw new Error("[Autocomplete] The `sourceId` ".concat(JSON.stringify(O.sourceId)," is not unique."));g.push(O.sourceId);var w={getItemInputValue:function(T){return T.state.query},getItemUrl:function(){},onSelect:function(T){(0,T.setIsOpen)(!1)},onActive:yn,onResolve:yn};Object.keys(w).forEach(function(T){w[T].__default=!0});var S=So(So({},w),O);return Promise.resolve(S)}))})}(h,p)})).then(function(h){return Gt(h)}).then(function(h){return h.map(function(_){return lt(lt({},_),{},{onSelect:function(v){_.onSelect(v),u.forEach(function(g){var y;return(y=g.onSelect)===null||y===void 0?void 0:y.call(g,v)})},onActive:function(v){_.onActive(v),u.forEach(function(g){var y;return(y=g.onActive)===null||y===void 0?void 0:y.call(g,v)})},onResolve:function(v){_.onResolve(v),u.forEach(function(g){var y;return(y=g.onResolve)===null||y===void 0?void 0:y.call(g,v)})}})})})},navigator:lt({navigate:function(p){var h=p.itemUrl;d.location.assign(h)},navigateNewTab:function(p){var h=p.itemUrl,_=d.open(h,"_blank","noopener");_==null||_.focus()},navigateNewWindow:function(p){var h=p.itemUrl;d.open(h,"_blank","noopener")}},s.navigator)})}(e,t),r=function(s,u,c){var d,m=u.initialState;return{getState:function(){return m},dispatch:function(p,h){var _=function(v){for(var g=1;g=0||(Be[Ee]=Pe[Ee]);return Be}(ee,Se);if(Object.getOwnPropertySymbols){var be=Object.getOwnPropertySymbols(ee);for(oe=0;oe=0||Object.prototype.propertyIsEnumerable.call(ee,pe)&&(ke[pe]=ee[pe])}return ke}(L,nd);if(P.key==="ArrowUp"||P.key==="ArrowDown"){var G=function(){var ee=N.environment.document.getElementById("".concat(N.id,"-item-").concat(D.getState().activeItemId));ee&&(ee.scrollIntoViewIfNeeded?ee.scrollIntoViewIfNeeded(!1):ee.scrollIntoView(!1))},$=function(){var ee=pt(D.getState());if(D.getState().activeItemId!==null&&ee){var Se=ee.item,pe=ee.itemInputValue,oe=ee.itemUrl,ke=ee.source;ke.onActive(Xe({event:P,item:Se,itemInputValue:pe,itemUrl:oe,refresh:j,source:ke,state:D.getState()},z))}};P.preventDefault(),D.getState().isOpen===!1&&(N.openOnFocus||Boolean(D.getState().query))?ut(Xe({event:P,props:N,query:D.getState().query,refresh:j,store:D},z)).then(function(){D.dispatch(P.key,{nextActiveItemId:N.defaultActiveItemId}),$(),setTimeout(G,0)}):(D.dispatch(P.key,{}),$(),G())}else if(P.key==="Escape")P.preventDefault(),D.dispatch(P.key,null),D.pendingRequests.cancelAll();else if(P.key==="Tab")D.dispatch("blur",null),D.pendingRequests.cancelAll();else if(P.key==="Enter"){if(D.getState().activeItemId===null||D.getState().collections.every(function(ee){return ee.items.length===0}))return void(N.debug||D.pendingRequests.cancelAll());P.preventDefault();var X=pt(D.getState()),J=X.item,_e=X.itemInputValue,ne=X.itemUrl,se=X.source;if(P.metaKey||P.ctrlKey)ne!==void 0&&(se.onSelect(Xe({event:P,item:J,itemInputValue:_e,itemUrl:ne,refresh:j,source:se,state:D.getState()},z)),N.navigator.navigateNewTab({itemUrl:ne,item:J,state:D.getState()}));else if(P.shiftKey)ne!==void 0&&(se.onSelect(Xe({event:P,item:J,itemInputValue:_e,itemUrl:ne,refresh:j,source:se,state:D.getState()},z)),N.navigator.navigateNewWindow({itemUrl:ne,item:J,state:D.getState()}));else if(!P.altKey){if(ne!==void 0)return se.onSelect(Xe({event:P,item:J,itemInputValue:_e,itemUrl:ne,refresh:j,source:se,state:D.getState()},z)),void N.navigator.navigate({itemUrl:ne,item:J,state:D.getState()});ut(Xe({event:P,nextState:{isOpen:!1},props:N,query:_e,refresh:j,store:D},z)).then(function(){se.onSelect(Xe({event:P,item:J,itemInputValue:_e,itemUrl:ne,refresh:j,source:se,state:D.getState()},z))})}}})(de({event:b,props:u,refresh:c,store:d},m))},onFocus:v,onBlur:yn,onClick:function(b){h.inputElement!==u.environment.document.activeElement||d.getState().isOpen||v(b)}},w)},getPanelProps:function(h){return de({onMouseDown:function(_){_.preventDefault()},onMouseLeave:function(){d.dispatch("mouseleave",null)}},h)},getListProps:function(h){var _=h||{},v=_.sourceIndex,g=et(_,sd);return de({role:"listbox","aria-labelledby":"".concat(p(u.id,v),"-label"),id:"".concat(p(u.id,v),"-list")},g)},getItemProps:function(h){var _=h.item,v=h.source,g=h.sourceIndex,y=et(h,ud);return de({id:"".concat(p(u.id,g),"-item-").concat(_.__autocomplete_id),role:"option","aria-selected":d.getState().activeItemId===_.__autocomplete_id,onMouseMove:function(O){if(_.__autocomplete_id!==d.getState().activeItemId){d.dispatch("mousemove",_.__autocomplete_id);var w=pt(d.getState());if(d.getState().activeItemId!==null&&w){var S=w.item,T=w.itemInputValue,k=w.itemUrl,b=w.source;b.onActive(de({event:O,item:S,itemInputValue:T,itemUrl:k,refresh:c,source:b,state:d.getState()},m))}}},onMouseDown:function(O){O.preventDefault()},onClick:function(O){var w=v.getItemInputValue({item:_,state:d.getState()}),S=v.getItemUrl({item:_,state:d.getState()});(S?Promise.resolve():ut(de({event:O,nextState:{isOpen:!1},props:u,query:w,refresh:c,store:d},m))).then(function(){v.onSelect(de({event:O,item:_,itemInputValue:w,itemUrl:S,refresh:c,source:v,state:d.getState()},m))})}},y)}}}(tt({props:n,refresh:l,store:r,navigator:n.navigator},o));function l(){return ut(tt({event:new Event("input"),nextState:{isOpen:r.getState().isOpen},props:n,navigator:n.navigator,query:r.getState().query,refresh:l,store:r},o))}if(e.insights&&!n.plugins.some(function(s){return s.name==="aa.algoliaInsightsPlugin"})){var i=typeof e.insights=="boolean"?{}:e.insights;n.plugins.push(Wu(i))}return n.plugins.forEach(function(s){var u;return(u=s.subscribe)===null||u===void 0?void 0:u.call(s,tt(tt({},o),{},{navigator:n.navigator,refresh:l,onSelect:function(c){t.push({onSelect:c})},onActive:function(c){t.push({onActive:c})},onResolve:function(c){t.push({onResolve:c})}}))}),function(s){var u,c,d=s.metadata,m=s.environment;if(!((u=m.navigator)===null||u===void 0||(c=u.userAgent)===null||c===void 0)&&c.includes("Algolia Crawler")){var p=m.document.createElement("meta"),h=m.document.querySelector("head");p.name="algolia:metadata",setTimeout(function(){p.content=JSON.stringify(d),h.appendChild(p)},0)}}({metadata:fd({plugins:n.plugins,options:e}),environment:n.environment}),tt(tt({refresh:l,navigator:n.navigator},a),o)}function gd(e){var t=e.translations,n=(t===void 0?{}:t).searchByText,r=n===void 0?"Search by":n;return f.createElement("a",{href:"https://www.algolia.com/ref/docsearch/?utm_source=".concat(window.location.hostname,"&utm_medium=referral&utm_content=powered_by&utm_campaign=docsearch"),target:"_blank",rel:"noopener noreferrer"},f.createElement("span",{className:"DocSearch-Label"},r),f.createElement("svg",{width:"77",height:"19","aria-label":"Algolia",role:"img",id:"Layer_1",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 2196.2 500"},f.createElement("defs",null,f.createElement("style",null,".cls-1,.cls-2{fill:#003dff;}.cls-2{fill-rule:evenodd;}")),f.createElement("path",{className:"cls-2",d:"M1070.38,275.3V5.91c0-3.63-3.24-6.39-6.82-5.83l-50.46,7.94c-2.87,.45-4.99,2.93-4.99,5.84l.17,273.22c0,12.92,0,92.7,95.97,95.49,3.33,.1,6.09-2.58,6.09-5.91v-40.78c0-2.96-2.19-5.51-5.12-5.84-34.85-4.01-34.85-47.57-34.85-54.72Z"}),f.createElement("rect",{className:"cls-1",x:"1845.88",y:"104.73",width:"62.58",height:"277.9",rx:"5.9",ry:"5.9"}),f.createElement("path",{className:"cls-2",d:"M1851.78,71.38h50.77c3.26,0,5.9-2.64,5.9-5.9V5.9c0-3.62-3.24-6.39-6.82-5.83l-50.77,7.95c-2.87,.45-4.99,2.92-4.99,5.83v51.62c0,3.26,2.64,5.9,5.9,5.9Z"}),f.createElement("path",{className:"cls-2",d:"M1764.03,275.3V5.91c0-3.63-3.24-6.39-6.82-5.83l-50.46,7.94c-2.87,.45-4.99,2.93-4.99,5.84l.17,273.22c0,12.92,0,92.7,95.97,95.49,3.33,.1,6.09-2.58,6.09-5.91v-40.78c0-2.96-2.19-5.51-5.12-5.84-34.85-4.01-34.85-47.57-34.85-54.72Z"}),f.createElement("path",{className:"cls-2",d:"M1631.95,142.72c-11.14-12.25-24.83-21.65-40.78-28.31-15.92-6.53-33.26-9.85-52.07-9.85-18.78,0-36.15,3.17-51.92,9.85-15.59,6.66-29.29,16.05-40.76,28.31-11.47,12.23-20.38,26.87-26.76,44.03-6.38,17.17-9.24,37.37-9.24,58.36,0,20.99,3.19,36.87,9.55,54.21,6.38,17.32,15.14,32.11,26.45,44.36,11.29,12.23,24.83,21.62,40.6,28.46,15.77,6.83,40.12,10.33,52.4,10.48,12.25,0,36.78-3.82,52.7-10.48,15.92-6.68,29.46-16.23,40.78-28.46,11.29-12.25,20.05-27.04,26.25-44.36,6.22-17.34,9.24-33.22,9.24-54.21,0-20.99-3.34-41.19-10.03-58.36-6.38-17.17-15.14-31.8-26.43-44.03Zm-44.43,163.75c-11.47,15.75-27.56,23.7-48.09,23.7-20.55,0-36.63-7.8-48.1-23.7-11.47-15.75-17.21-34.01-17.21-61.2,0-26.89,5.59-49.14,17.06-64.87,11.45-15.75,27.54-23.52,48.07-23.52,20.55,0,36.63,7.78,48.09,23.52,11.47,15.57,17.36,37.98,17.36,64.87,0,27.19-5.72,45.3-17.19,61.2Z"}),f.createElement("path",{className:"cls-2",d:"M894.42,104.73h-49.33c-48.36,0-90.91,25.48-115.75,64.1-14.52,22.58-22.99,49.63-22.99,78.73,0,44.89,20.13,84.92,51.59,111.1,2.93,2.6,6.05,4.98,9.31,7.14,12.86,8.49,28.11,13.47,44.52,13.47,1.23,0,2.46-.03,3.68-.09,.36-.02,.71-.05,1.07-.07,.87-.05,1.75-.11,2.62-.2,.34-.03,.68-.08,1.02-.12,.91-.1,1.82-.21,2.73-.34,.21-.03,.42-.07,.63-.1,32.89-5.07,61.56-30.82,70.9-62.81v57.83c0,3.26,2.64,5.9,5.9,5.9h50.42c3.26,0,5.9-2.64,5.9-5.9V110.63c0-3.26-2.64-5.9-5.9-5.9h-56.32Zm0,206.92c-12.2,10.16-27.97,13.98-44.84,15.12-.16,.01-.33,.03-.49,.04-1.12,.07-2.24,.1-3.36,.1-42.24,0-77.12-35.89-77.12-79.37,0-10.25,1.96-20.01,5.42-28.98,11.22-29.12,38.77-49.74,71.06-49.74h49.33v142.83Z"}),f.createElement("path",{className:"cls-2",d:"M2133.97,104.73h-49.33c-48.36,0-90.91,25.48-115.75,64.1-14.52,22.58-22.99,49.63-22.99,78.73,0,44.89,20.13,84.92,51.59,111.1,2.93,2.6,6.05,4.98,9.31,7.14,12.86,8.49,28.11,13.47,44.52,13.47,1.23,0,2.46-.03,3.68-.09,.36-.02,.71-.05,1.07-.07,.87-.05,1.75-.11,2.62-.2,.34-.03,.68-.08,1.02-.12,.91-.1,1.82-.21,2.73-.34,.21-.03,.42-.07,.63-.1,32.89-5.07,61.56-30.82,70.9-62.81v57.83c0,3.26,2.64,5.9,5.9,5.9h50.42c3.26,0,5.9-2.64,5.9-5.9V110.63c0-3.26-2.64-5.9-5.9-5.9h-56.32Zm0,206.92c-12.2,10.16-27.97,13.98-44.84,15.12-.16,.01-.33,.03-.49,.04-1.12,.07-2.24,.1-3.36,.1-42.24,0-77.12-35.89-77.12-79.37,0-10.25,1.96-20.01,5.42-28.98,11.22-29.12,38.77-49.74,71.06-49.74h49.33v142.83Z"}),f.createElement("path",{className:"cls-2",d:"M1314.05,104.73h-49.33c-48.36,0-90.91,25.48-115.75,64.1-11.79,18.34-19.6,39.64-22.11,62.59-.58,5.3-.88,10.68-.88,16.14s.31,11.15,.93,16.59c4.28,38.09,23.14,71.61,50.66,94.52,2.93,2.6,6.05,4.98,9.31,7.14,12.86,8.49,28.11,13.47,44.52,13.47h0c17.99,0,34.61-5.93,48.16-15.97,16.29-11.58,28.88-28.54,34.48-47.75v50.26h-.11v11.08c0,21.84-5.71,38.27-17.34,49.36-11.61,11.08-31.04,16.63-58.25,16.63-11.12,0-28.79-.59-46.6-2.41-2.83-.29-5.46,1.5-6.27,4.22l-12.78,43.11c-1.02,3.46,1.27,7.02,4.83,7.53,21.52,3.08,42.52,4.68,54.65,4.68,48.91,0,85.16-10.75,108.89-32.21,21.48-19.41,33.15-48.89,35.2-88.52V110.63c0-3.26-2.64-5.9-5.9-5.9h-56.32Zm0,64.1s.65,139.13,0,143.36c-12.08,9.77-27.11,13.59-43.49,14.7-.16,.01-.33,.03-.49,.04-1.12,.07-2.24,.1-3.36,.1-1.32,0-2.63-.03-3.94-.1-40.41-2.11-74.52-37.26-74.52-79.38,0-10.25,1.96-20.01,5.42-28.98,11.22-29.12,38.77-49.74,71.06-49.74h49.33Z"}),f.createElement("path",{className:"cls-1",d:"M249.83,0C113.3,0,2,110.09,.03,246.16c-2,138.19,110.12,252.7,248.33,253.5,42.68,.25,83.79-10.19,120.3-30.03,3.56-1.93,4.11-6.83,1.08-9.51l-23.38-20.72c-4.75-4.21-11.51-5.4-17.36-2.92-25.48,10.84-53.17,16.38-81.71,16.03-111.68-1.37-201.91-94.29-200.13-205.96,1.76-110.26,92-199.41,202.67-199.41h202.69V407.41l-115-102.18c-3.72-3.31-9.42-2.66-12.42,1.31-18.46,24.44-48.53,39.64-81.93,37.34-46.33-3.2-83.87-40.5-87.34-86.81-4.15-55.24,39.63-101.52,94-101.52,49.18,0,89.68,37.85,93.91,85.95,.38,4.28,2.31,8.27,5.52,11.12l29.95,26.55c3.4,3.01,8.79,1.17,9.63-3.3,2.16-11.55,2.92-23.58,2.07-35.92-4.82-70.34-61.8-126.93-132.17-131.26-80.68-4.97-148.13,58.14-150.27,137.25-2.09,77.1,61.08,143.56,138.19,145.26,32.19,.71,62.03-9.41,86.14-26.95l150.26,133.2c6.44,5.71,16.61,1.14,16.61-7.47V9.48C499.66,4.25,495.42,0,490.18,0H249.83Z"})))}function mn(e){return f.createElement("svg",{width:"15",height:"15","aria-label":e.ariaLabel,role:"img"},f.createElement("g",{fill:"none",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:"1.2"},e.children))}function yd(e){var t=e.translations,n=t===void 0?{}:t,r=n.selectText,o=r===void 0?"to select":r,a=n.selectKeyAriaLabel,l=a===void 0?"Enter key":a,i=n.navigateText,s=i===void 0?"to navigate":i,u=n.navigateUpKeyAriaLabel,c=u===void 0?"Arrow up":u,d=n.navigateDownKeyAriaLabel,m=d===void 0?"Arrow down":d,p=n.closeText,h=p===void 0?"to close":p,_=n.closeKeyAriaLabel,v=_===void 0?"Escape key":_,g=n.searchByText,y=g===void 0?"Search by":g;return f.createElement(f.Fragment,null,f.createElement("div",{className:"DocSearch-Logo"},f.createElement(gd,{translations:{searchByText:y}})),f.createElement("ul",{className:"DocSearch-Commands"},f.createElement("li",null,f.createElement("kbd",{className:"DocSearch-Commands-Key"},f.createElement(mn,{ariaLabel:l},f.createElement("path",{d:"M12 3.53088v3c0 1-1 2-2 2H4M7 11.53088l-3-3 3-3"}))),f.createElement("span",{className:"DocSearch-Label"},o)),f.createElement("li",null,f.createElement("kbd",{className:"DocSearch-Commands-Key"},f.createElement(mn,{ariaLabel:m},f.createElement("path",{d:"M7.5 3.5v8M10.5 8.5l-3 3-3-3"}))),f.createElement("kbd",{className:"DocSearch-Commands-Key"},f.createElement(mn,{ariaLabel:c},f.createElement("path",{d:"M7.5 11.5v-8M10.5 6.5l-3-3-3 3"}))),f.createElement("span",{className:"DocSearch-Label"},s)),f.createElement("li",null,f.createElement("kbd",{className:"DocSearch-Commands-Key"},f.createElement(mn,{ariaLabel:v},f.createElement("path",{d:"M13.6167 8.936c-.1065.3583-.6883.962-1.4875.962-.7993 0-1.653-.9165-1.653-2.1258v-.5678c0-1.2548.7896-2.1016 1.653-2.1016.8634 0 1.3601.4778 1.4875 1.0724M9 6c-.1352-.4735-.7506-.9219-1.46-.8972-.7092.0246-1.344.57-1.344 1.2166s.4198.8812 1.3445.9805C8.465 7.3992 8.968 7.9337 9 8.5c.032.5663-.454 1.398-1.4595 1.398C6.6593 9.898 6 9 5.963 8.4851m-1.4748.5368c-.2635.5941-.8099.876-1.5443.876s-1.7073-.6248-1.7073-2.204v-.4603c0-1.0416.721-2.131 1.7073-2.131.9864 0 1.6425 1.031 1.5443 2.2492h-2.956"}))),f.createElement("span",{className:"DocSearch-Label"},h))))}function bd(e){var t=e.hit,n=e.children;return f.createElement("a",{href:t.url},n)}function Ed(){return f.createElement("svg",{viewBox:"0 0 38 38",stroke:"currentColor",strokeOpacity:".5"},f.createElement("g",{fill:"none",fillRule:"evenodd"},f.createElement("g",{transform:"translate(1 1)",strokeWidth:"2"},f.createElement("circle",{strokeOpacity:".3",cx:"18",cy:"18",r:"18"}),f.createElement("path",{d:"M36 18c0-9.94-8.06-18-18-18"},f.createElement("animateTransform",{attributeName:"transform",type:"rotate",from:"0 18 18",to:"360 18 18",dur:"1s",repeatCount:"indefinite"})))))}function Od(){return f.createElement("svg",{width:"20",height:"20",viewBox:"0 0 20 20"},f.createElement("g",{stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinecap:"round",strokeLinejoin:"round"},f.createElement("path",{d:"M3.18 6.6a8.23 8.23 0 1112.93 9.94h0a8.23 8.23 0 01-11.63 0"}),f.createElement("path",{d:"M6.44 7.25H2.55V3.36M10.45 6v5.6M10.45 11.6L13 13"})))}function Xn(){return f.createElement("svg",{width:"20",height:"20",viewBox:"0 0 20 20"},f.createElement("path",{d:"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z",stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinecap:"round",strokeLinejoin:"round"}))}function wd(){return f.createElement("svg",{className:"DocSearch-Hit-Select-Icon",width:"20",height:"20",viewBox:"0 0 20 20"},f.createElement("g",{stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinecap:"round",strokeLinejoin:"round"},f.createElement("path",{d:"M18 3v4c0 2-2 4-4 4H2"}),f.createElement("path",{d:"M8 17l-6-6 6-6"})))}var Sd=function(){return f.createElement("svg",{width:"20",height:"20",viewBox:"0 0 20 20"},f.createElement("path",{d:"M17 6v12c0 .52-.2 1-1 1H4c-.7 0-1-.33-1-1V2c0-.55.42-1 1-1h8l5 5zM14 8h-3.13c-.51 0-.87-.34-.87-.87V4",stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinejoin:"round"}))};function Pd(e){switch(e.type){case"lvl1":return f.createElement(Sd,null);case"content":return f.createElement(kd,null);default:return f.createElement(Id,null)}}function Id(){return f.createElement("svg",{width:"20",height:"20",viewBox:"0 0 20 20"},f.createElement("path",{d:"M13 13h4-4V8H7v5h6v4-4H7V8H3h4V3v5h6V3v5h4-4v5zm-6 0v4-4H3h4z",stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinecap:"round",strokeLinejoin:"round"}))}function kd(){return f.createElement("svg",{width:"20",height:"20",viewBox:"0 0 20 20"},f.createElement("path",{d:"M17 5H3h14zm0 5H3h14zm0 5H3h14z",stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinejoin:"round"}))}function Mo(){return f.createElement("svg",{width:"20",height:"20",viewBox:"0 0 20 20"},f.createElement("path",{d:"M10 14.2L5 17l1-5.6-4-4 5.5-.7 2.5-5 2.5 5 5.6.8-4 4 .9 5.5z",stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinejoin:"round"}))}function Dd(){return f.createElement("svg",{width:"40",height:"40",viewBox:"0 0 20 20",fill:"none",fillRule:"evenodd",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round"},f.createElement("path",{d:"M19 4.8a16 16 0 00-2-1.2m-3.3-1.2A16 16 0 001.1 4.7M16.7 8a12 12 0 00-2.8-1.4M10 6a12 12 0 00-6.7 2M12.3 14.7a4 4 0 00-4.5 0M14.5 11.4A8 8 0 0010 10M3 16L18 2M10 18h0"}))}function Ld(){return f.createElement("svg",{width:"40",height:"40",viewBox:"0 0 20 20",fill:"none",fillRule:"evenodd",stroke:"currentColor",strokeLinecap:"round",strokeLinejoin:"round"},f.createElement("path",{d:"M15.5 4.8c2 3 1.7 7-1 9.7h0l4.3 4.3-4.3-4.3a7.8 7.8 0 01-9.8 1m-2.2-2.2A7.8 7.8 0 0113.2 2.4M2 18L18 2"}))}function Ad(e){var t=e.translations,n=t===void 0?{}:t,r=n.titleText,o=r===void 0?"Unable to fetch results":r,a=n.helpText,l=a===void 0?"You might want to check your network connection.":a;return f.createElement("div",{className:"DocSearch-ErrorScreen"},f.createElement("div",{className:"DocSearch-Screen-Icon"},f.createElement(Dd,null)),f.createElement("p",{className:"DocSearch-Title"},o),f.createElement("p",{className:"DocSearch-Help"},l))}var Td=["translations"];function jd(e){var t=e.translations,n=t===void 0?{}:t,r=Ye(e,Td),o=n.noResultsText,a=o===void 0?"No results for":o,l=n.suggestedQueryText,i=l===void 0?"Try searching for":l,s=n.reportMissingResultsText,u=s===void 0?"Believe this query should return results?":s,c=n.reportMissingResultsLinkText,d=c===void 0?"Let us know.":c,m=r.state.context.searchSuggestions;return f.createElement("div",{className:"DocSearch-NoResults"},f.createElement("div",{className:"DocSearch-Screen-Icon"},f.createElement(Ld,null)),f.createElement("p",{className:"DocSearch-Title"},a,' "',f.createElement("strong",null,r.state.query),'"'),m&&m.length>0&&f.createElement("div",{className:"DocSearch-NoResults-Prefill-List"},f.createElement("p",{className:"DocSearch-Help"},i,":"),f.createElement("ul",null,m.slice(0,3).reduce(function(p,h){return[].concat(function(_){return function(v){if(Array.isArray(v))return Zn(v)}(_)||function(v){if(typeof Symbol<"u"&&v[Symbol.iterator]!=null||v["@@iterator"]!=null)return Array.from(v)}(_)||Ga(_)||function(){throw new TypeError(`Invalid attempt to spread non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}(p),[f.createElement("li",{key:h},f.createElement("button",{className:"DocSearch-Prefill",key:h,type:"button",onClick:function(){r.setQuery(h.toLowerCase()+" "),r.refresh(),r.inputRef.current.focus()}},h))])},[]))),r.getMissingResultsUrl&&f.createElement("p",{className:"DocSearch-Help"},"".concat(u," "),f.createElement("a",{href:r.getMissingResultsUrl({query:r.state.query}),target:"_blank",rel:"noopener noreferrer"},d)))}var Cd=["hit","attribute","tagName"];function $o(e,t){return t.split(".").reduce(function(n,r){return n!=null&&n[r]?n[r]:null},e)}function dt(e){var t=e.hit,n=e.attribute,r=e.tagName;return Te(r===void 0?"span":r,At(At({},Ye(e,Cd)),{},{dangerouslySetInnerHTML:{__html:$o(t,"_snippetResult.".concat(n,".value"))||$o(t,n)}}))}function er(e){return e.collection&&e.collection.items.length!==0?f.createElement("section",{className:"DocSearch-Hits"},f.createElement("div",{className:"DocSearch-Hit-source"},e.title),f.createElement("ul",e.getListProps(),e.collection.items.map(function(t,n){return f.createElement(xd,fe({key:[e.title,t.objectID].join(":"),item:t,index:n},e))}))):null}function xd(e){var t=e.item,n=e.index,r=e.renderIcon,o=e.renderAction,a=e.getItemProps,l=e.onItemClick,i=e.collection,s=e.hitComponent,u=ot(f.useState(!1),2),c=u[0],d=u[1],m=ot(f.useState(!1),2),p=m[0],h=m[1],_=f.useRef(null),v=s;return f.createElement("li",fe({className:["DocSearch-Hit",t.__docsearch_parent&&"DocSearch-Hit--Child",c&&"DocSearch-Hit--deleting",p&&"DocSearch-Hit--favoriting"].filter(Boolean).join(" "),onTransitionEnd:function(){_.current&&_.current()}},a({item:t,source:i.source,onClick:function(g){l(t,g)}})),f.createElement(v,{hit:t},f.createElement("div",{className:"DocSearch-Hit-Container"},r({item:t,index:n}),t.hierarchy[t.type]&&t.type==="lvl1"&&f.createElement("div",{className:"DocSearch-Hit-content-wrapper"},f.createElement(dt,{className:"DocSearch-Hit-title",hit:t,attribute:"hierarchy.lvl1"}),t.content&&f.createElement(dt,{className:"DocSearch-Hit-path",hit:t,attribute:"content"})),t.hierarchy[t.type]&&(t.type==="lvl2"||t.type==="lvl3"||t.type==="lvl4"||t.type==="lvl5"||t.type==="lvl6")&&f.createElement("div",{className:"DocSearch-Hit-content-wrapper"},f.createElement(dt,{className:"DocSearch-Hit-title",hit:t,attribute:"hierarchy.".concat(t.type)}),f.createElement(dt,{className:"DocSearch-Hit-path",hit:t,attribute:"hierarchy.lvl1"})),t.type==="content"&&f.createElement("div",{className:"DocSearch-Hit-content-wrapper"},f.createElement(dt,{className:"DocSearch-Hit-title",hit:t,attribute:"content"}),f.createElement(dt,{className:"DocSearch-Hit-path",hit:t,attribute:"hierarchy.lvl1"})),o({item:t,runDeleteTransition:function(g){d(!0),_.current=g},runFavoriteTransition:function(g){h(!0),_.current=g}}))))}function zo(e,t,n){return e.reduce(function(r,o){var a=t(o);return r.hasOwnProperty(a)||(r[a]=[]),r[a].length<(n||5)&&r[a].push(o),r},{})}function qo(e){return e}function fn(e){return e.button===1||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey}function Rd(){}var ni=/(|<\/mark>)/g,Nd=RegExp(ni.source);function ri(e){var t,n,r=e;if(!r.__docsearch_parent&&!e._highlightResult)return e.hierarchy.lvl0;var o=((r.__docsearch_parent?(t=r.__docsearch_parent)===null||t===void 0||(t=t._highlightResult)===null||t===void 0||(t=t.hierarchy)===null||t===void 0?void 0:t.lvl0:(n=e._highlightResult)===null||n===void 0||(n=n.hierarchy)===null||n===void 0?void 0:n.lvl0)||{}).value;return o&&Nd.test(o)?o.replace(ni,""):o}function Vd(e){return f.createElement("div",{className:"DocSearch-Dropdown-Container"},e.state.collections.map(function(t){if(t.items.length===0)return null;var n=ri(t.items[0]);return f.createElement(er,fe({},e,{key:t.source.sourceId,title:n,collection:t,renderIcon:function(r){var o,a=r.item,l=r.index;return f.createElement(f.Fragment,null,a.__docsearch_parent&&f.createElement("svg",{className:"DocSearch-Hit-Tree",viewBox:"0 0 24 54"},f.createElement("g",{stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinecap:"round",strokeLinejoin:"round"},a.__docsearch_parent!==((o=t.items[l+1])===null||o===void 0?void 0:o.__docsearch_parent)?f.createElement("path",{d:"M8 6v21M20 27H8.3"}):f.createElement("path",{d:"M8 6v42M20 27H8.3"}))),f.createElement("div",{className:"DocSearch-Hit-icon"},f.createElement(Pd,{type:a.type})))},renderAction:function(){return f.createElement("div",{className:"DocSearch-Hit-action"},f.createElement(wd,null))}}))}),e.resultsFooterComponent&&f.createElement("section",{className:"DocSearch-HitsFooter"},f.createElement(e.resultsFooterComponent,{state:e.state})))}var Hd=["translations"];function Md(e){var t=e.translations,n=t===void 0?{}:t,r=Ye(e,Hd),o=n.recentSearchesTitle,a=o===void 0?"Recent":o,l=n.noRecentSearchesText,i=l===void 0?"No recent searches":l,s=n.saveRecentSearchButtonTitle,u=s===void 0?"Save this search":s,c=n.removeRecentSearchButtonTitle,d=c===void 0?"Remove this search from history":c,m=n.favoriteSearchesTitle,p=m===void 0?"Favorite":m,h=n.removeFavoriteSearchButtonTitle,_=h===void 0?"Remove this search from favorites":h;return r.state.status==="idle"&&r.hasCollections===!1?r.disableUserPersonalization?null:f.createElement("div",{className:"DocSearch-StartScreen"},f.createElement("p",{className:"DocSearch-Help"},i)):r.hasCollections===!1?null:f.createElement("div",{className:"DocSearch-Dropdown-Container"},f.createElement(er,fe({},r,{title:a,collection:r.state.collections[0],renderIcon:function(){return f.createElement("div",{className:"DocSearch-Hit-icon"},f.createElement(Od,null))},renderAction:function(v){var g=v.item,y=v.runFavoriteTransition,O=v.runDeleteTransition;return f.createElement(f.Fragment,null,f.createElement("div",{className:"DocSearch-Hit-action"},f.createElement("button",{className:"DocSearch-Hit-action-button",title:u,type:"submit",onClick:function(w){w.preventDefault(),w.stopPropagation(),y(function(){r.favoriteSearches.add(g),r.recentSearches.remove(g),r.refresh()})}},f.createElement(Mo,null))),f.createElement("div",{className:"DocSearch-Hit-action"},f.createElement("button",{className:"DocSearch-Hit-action-button",title:d,type:"submit",onClick:function(w){w.preventDefault(),w.stopPropagation(),O(function(){r.recentSearches.remove(g),r.refresh()})}},f.createElement(Xn,null))))}})),f.createElement(er,fe({},r,{title:p,collection:r.state.collections[1],renderIcon:function(){return f.createElement("div",{className:"DocSearch-Hit-icon"},f.createElement(Mo,null))},renderAction:function(v){var g=v.item,y=v.runDeleteTransition;return f.createElement("div",{className:"DocSearch-Hit-action"},f.createElement("button",{className:"DocSearch-Hit-action-button",title:_,type:"submit",onClick:function(O){O.preventDefault(),O.stopPropagation(),y(function(){r.favoriteSearches.remove(g),r.refresh()})}},f.createElement(Xn,null)))}})))}var $d=["translations"],zd=f.memo(function(e){var t=e.translations,n=t===void 0?{}:t,r=Ye(e,$d);if(r.state.status==="error")return f.createElement(Ad,{translations:n==null?void 0:n.errorScreen});var o=r.state.collections.some(function(a){return a.items.length>0});return r.state.query?o===!1?f.createElement(jd,fe({},r,{translations:n==null?void 0:n.noResultsScreen})):f.createElement(Vd,r):f.createElement(Md,fe({},r,{hasCollections:o,translations:n==null?void 0:n.startScreen}))},function(e,t){return t.state.status==="loading"||t.state.status==="stalled"}),qd=["translations"];function Bd(e){var t=e.translations,n=t===void 0?{}:t,r=Ye(e,qd),o=n.resetButtonTitle,a=o===void 0?"Clear the query":o,l=n.resetButtonAriaLabel,i=l===void 0?"Clear the query":l,s=n.cancelButtonText,u=s===void 0?"Cancel":s,c=n.cancelButtonAriaLabel,d=c===void 0?"Cancel":c,m=n.searchInputLabel,p=m===void 0?"Search":m,h=r.getFormProps({inputElement:r.inputRef.current}).onReset;return f.useEffect(function(){r.autoFocus&&r.inputRef.current&&r.inputRef.current.focus()},[r.autoFocus,r.inputRef]),f.useEffect(function(){r.isFromSelection&&r.inputRef.current&&r.inputRef.current.select()},[r.isFromSelection,r.inputRef]),f.createElement(f.Fragment,null,f.createElement("form",{className:"DocSearch-Form",onSubmit:function(_){_.preventDefault()},onReset:h},f.createElement("label",fe({className:"DocSearch-MagnifierLabel"},r.getLabelProps()),f.createElement(Ja,null),f.createElement("span",{className:"DocSearch-VisuallyHiddenForAccessibility"},p)),f.createElement("div",{className:"DocSearch-LoadingIndicator"},f.createElement(Ed,null)),f.createElement("input",fe({className:"DocSearch-Input",ref:r.inputRef},r.getInputProps({inputElement:r.inputRef.current,autoFocus:r.autoFocus,maxLength:64}))),f.createElement("button",{type:"reset",title:a,className:"DocSearch-Reset","aria-label":i,hidden:!r.state.query},f.createElement(Xn,null))),f.createElement("button",{className:"DocSearch-Cancel",type:"reset","aria-label":d,onClick:r.onClose},u))}var Fd=["_highlightResult","_snippetResult"];function Bo(e){var t=e.key,n=e.limit,r=n===void 0?5:n,o=function(l){return function(){var i="__TEST_KEY__";try{return localStorage.setItem(i,""),localStorage.removeItem(i),!0}catch{return!1}}()===!1?{setItem:function(){},getItem:function(){return[]}}:{setItem:function(i){return window.localStorage.setItem(l,JSON.stringify(i))},getItem:function(){var i=window.localStorage.getItem(l);return i?JSON.parse(i):[]}}}(t),a=o.getItem().slice(0,r);return{add:function(l){var i=l,s=(i._highlightResult,i._snippetResult,Ye(i,Fd)),u=a.findIndex(function(c){return c.objectID===s.objectID});u>-1&&a.splice(u,1),a.unshift(s),a=a.slice(0,r),o.setItem(a)},remove:function(l){a=a.filter(function(i){return i.objectID!==l.objectID}),o.setItem(a)},getAll:function(){return a}}}function Ud(e){var t,n="algoliasearch-client-js-".concat(e.key),r=function(){return t===void 0&&(t=e.localStorage||window.localStorage),t},o=function(){return JSON.parse(r().getItem(n)||"{}")},a=function(l){r().setItem(n,JSON.stringify(l))};return{get:function(l,i){var s=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{miss:function(){return Promise.resolve()}};return Promise.resolve().then(function(){(function(){var c=e.timeToLive?1e3*e.timeToLive:null,d=o(),m=Object.fromEntries(Object.entries(d).filter(function(h){return ft(h,2)[1].timestamp!==void 0}));if(a(m),c){var p=Object.fromEntries(Object.entries(m).filter(function(h){var _=ft(h,2)[1],v=new Date().getTime();return!(_.timestamp+c2&&arguments[2]!==void 0?arguments[2]:{miss:function(){return Promise.resolve()}};return o().then(function(l){return Promise.all([l,a.miss(l)])}).then(function(l){return ft(l,1)[0]})},set:function(r,o){return Promise.resolve(o)},delete:function(r){return Promise.resolve()},clear:function(){return Promise.resolve()}}:{get:function(r,o){var a=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{miss:function(){return Promise.resolve()}};return n.get(r,o,a).catch(function(){return kt({caches:t}).get(r,o,a)})},set:function(r,o){return n.set(r,o).catch(function(){return kt({caches:t}).set(r,o)})},delete:function(r){return n.delete(r).catch(function(){return kt({caches:t}).delete(r)})},clear:function(){return n.clear().catch(function(){return kt({caches:t}).clear()})}}}function zn(){var e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{serializable:!0},t={};return{get:function(n,r){var o=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{miss:function(){return Promise.resolve()}},a=JSON.stringify(n);if(a in t)return Promise.resolve(e.serializable?JSON.parse(t[a]):t[a]);var l=r(),i=o&&o.miss||function(){return Promise.resolve()};return l.then(function(s){return i(s)}).then(function(){return l})},set:function(n,r){return t[JSON.stringify(n)]=e.serializable?JSON.stringify(r):r,Promise.resolve(r)},delete:function(n){return delete t[JSON.stringify(n)],Promise.resolve()},clear:function(){return t={},Promise.resolve()}}}function Wd(e){for(var t=e.length-1;t>0;t--){var n=Math.floor(Math.random()*(t+1)),r=e[t];e[t]=e[n],e[n]=r}return e}function oi(e,t){return t&&Object.keys(t).forEach(function(n){e[n]=t[n](e)}),e}function kn(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r0?r:void 0,timeout:n.timeout||t,headers:n.headers||{},queryParameters:n.queryParameters||{},cacheable:n.cacheable}}var vt={Read:1,Write:2,Any:3};function Uo(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:1;return K(K({},e),{},{status:t,lastUpdate:Date.now()})}function ai(e){return typeof e=="string"?{protocol:"https",url:e,accept:vt.Any}:{protocol:e.protocol||"https",url:e.url,accept:e.accept||vt.Any}}var Dn="POST";function Wo(e,t,n,r){var o=[],a=function(m,p){if(m.method!=="GET"&&(m.data!==void 0||p.data!==void 0)){var h=Array.isArray(m.data)?m.data:K(K({},m.data),p.data);return JSON.stringify(h)}}(n,r),l=function(m,p){var h=K(K({},m.headers),p.headers),_={};return Object.keys(h).forEach(function(v){var g=h[v];_[v.toLowerCase()]=g}),_}(e,r),i=n.method,s=n.method!=="GET"?{}:K(K({},n.data),r.data),u=K(K(K({"x-algolia-agent":e.userAgent.value},e.queryParameters),s),r.queryParameters),c=0,d=function m(p,h){var _=p.pop();if(_===void 0)throw{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:Ko(o)};var v={data:a,headers:l,method:i,url:Gd(_,n.path,u),connectTimeout:h(c,e.timeouts.connect),responseTimeout:h(c,r.timeout)},g=function(O){var w={request:v,response:O,host:_,triesLeft:p.length};return o.push(w),w},y={onSuccess:function(O){return function(w){try{return JSON.parse(w.content)}catch(S){throw function(T,k){return{name:"DeserializationError",message:T,response:k}}(S.message,w)}}(O)},onRetry:function(O){var w=g(O);return O.isTimedOut&&c++,Promise.all([e.logger.info("Retryable failure",ci(w)),e.hostsCache.set(_,Uo(_,O.isTimedOut?3:2))]).then(function(){return m(p,h)})},onFail:function(O){throw g(O),function(w,S){var T=w.content,k=w.status,b=T;try{b=JSON.parse(T).message}catch{}return function(L,P,N){return{name:"ApiError",message:L,status:P,transporterStackTrace:N}}(b,k,S)}(O,Ko(o))}};return e.requester.send(v).then(function(O){return function(w,S){return function(T){var k=T.status;return T.isTimedOut||function(b){var L=b.isTimedOut,P=b.status;return!L&&~~P==0}(T)||~~(k/100)!=2&&~~(k/100)!=4}(w)?S.onRetry(w):~~(w.status/100)==2?S.onSuccess(w):S.onFail(w)}(O,y)})};return function(m,p){return Promise.all(p.map(function(h){return m.get(h,function(){return Promise.resolve(Uo(h))})})).then(function(h){var _=h.filter(function(y){return function(O){return O.status===1||Date.now()-O.lastUpdate>12e4}(y)}),v=h.filter(function(y){return function(O){return O.status===3&&Date.now()-O.lastUpdate<=12e4}(y)}),g=[].concat(hn(_),hn(v));return{getTimeout:function(y,O){return(v.length===0&&y===0?1:v.length+3+y)*O},statelessHosts:g.length>0?g.map(function(y){return ai(y)}):p}})}(e.hostsCache,t).then(function(m){return d(hn(m.statelessHosts).reverse(),m.getTimeout)})}function Kd(e){var t={value:"Algolia for JavaScript (".concat(e,")"),add:function(n){var r="; ".concat(n.segment).concat(n.version!==void 0?" (".concat(n.version,")"):"");return t.value.indexOf(r)===-1&&(t.value="".concat(t.value).concat(r)),t}};return t}function Gd(e,t,n){var r=ii(n),o="".concat(e.protocol,"://").concat(e.url,"/").concat(t.charAt(0)==="/"?t.substr(1):t);return r.length&&(o+="?".concat(r)),o}function ii(e){return Object.keys(e).map(function(t){return kn("%s=%s",t,(n=e[t],Object.prototype.toString.call(n)==="[object Object]"||Object.prototype.toString.call(n)==="[object Array]"?JSON.stringify(e[t]):e[t]));var n}).join("&")}function Ko(e){return e.map(function(t){return ci(t)})}function ci(e){var t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return K(K({},e),{},{request:K(K({},e.request),{},{headers:K(K({},e.request.headers),t)})})}var Jd=function(e){return function(t,n){return t.method==="GET"?e.transporter.read(t,n):e.transporter.write(t,n)}},li=function(e){return function(t){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};return oi({transporter:e.transporter,appId:e.appId,indexName:t},n.methods)}},Go=function(e){return function(t,n){var r=t.map(function(o){return K(K({},o),{},{params:ii(o.params||{})})});return e.transporter.read({method:Dn,path:"1/indexes/*/queries",data:{requests:r},cacheable:!0},n)}},Jo=function(e){return function(t,n){return Promise.all(t.map(function(r){var o=r.params,a=o.facetName,l=o.facetQuery,i=function(s,u){if(s==null)return{};var c,d,m=function(h,_){if(h==null)return{};var v,g,y={},O=Object.keys(h);for(g=0;g=0||(y[v]=h[v]);return y}(s,u);if(Object.getOwnPropertySymbols){var p=Object.getOwnPropertySymbols(s);for(d=0;d=0||Object.prototype.propertyIsEnumerable.call(s,c)&&(m[c]=s[c])}return m}(o,Tu);return li(e)(r.indexName,{methods:{searchForFacetValues:si}}).searchForFacetValues(a,l,K(K({},n),i))}))}},Qd=function(e){return function(t,n,r){return e.transporter.read({method:Dn,path:kn("1/answers/%s/prediction",e.indexName),data:{query:t,queryLanguages:n},cacheable:!0},r)}},Zd=function(e){return function(t,n){return e.transporter.read({method:Dn,path:kn("1/indexes/%s/query",e.indexName),data:{query:t},cacheable:!0},n)}},si=function(e){return function(t,n,r){return e.transporter.read({method:Dn,path:kn("1/indexes/%s/facets/%s/query",e.indexName,t),data:{facetQuery:n},cacheable:!0},r)}};function ui(e,t,n){var r={appId:e,apiKey:t,timeouts:{connect:1,read:2,write:30},requester:{send:function(o){return new Promise(function(a){var l=new XMLHttpRequest;l.open(o.method,o.url,!0),Object.keys(o.headers).forEach(function(c){return l.setRequestHeader(c,o.headers[c])});var i,s=function(c,d){return setTimeout(function(){l.abort(),a({status:0,content:d,isTimedOut:!0})},1e3*c)},u=s(o.connectTimeout,"Connection timeout");l.onreadystatechange=function(){l.readyState>l.OPENED&&i===void 0&&(clearTimeout(u),i=s(o.responseTimeout,"Socket timeout"))},l.onerror=function(){l.status===0&&(clearTimeout(u),clearTimeout(i),a({content:l.responseText||"Network request failed",status:l.status,isTimedOut:!1}))},l.onload=function(){clearTimeout(u),clearTimeout(i),a({content:l.responseText,status:l.status,isTimedOut:!1})},l.send(o.data)})}},logger:{debug:function(o,a){return Promise.resolve()},info:function(o,a){return Promise.resolve()},error:function(o,a){return console.error(o,a),Promise.resolve()}},responsesCache:zn(),requestsCache:zn({serializable:!1}),hostsCache:kt({caches:[Ud({key:"4.19.1-".concat(e)}),zn()]}),userAgent:Kd("4.19.1").add({segment:"Browser",version:"lite"}),authMode:0};return function(o){var a=o.appId,l=function(u,c,d){var m={"x-algolia-api-key":d,"x-algolia-application-id":c};return{headers:function(){return u===1?m:{}},queryParameters:function(){return u===0?m:{}}}}(o.authMode!==void 0?o.authMode:1,a,o.apiKey),i=function(u){var c=u.hostsCache,d=u.logger,m=u.requester,p=u.requestsCache,h=u.responsesCache,_=u.timeouts,v=u.userAgent,g=u.hosts,y=u.queryParameters,O={hostsCache:c,logger:d,requester:m,requestsCache:p,responsesCache:h,timeouts:_,userAgent:v,headers:u.headers,queryParameters:y,hosts:g.map(function(w){return ai(w)}),read:function(w,S){var T=Fo(S,O.timeouts.read),k=function(){return Wo(O,O.hosts.filter(function(L){return(L.accept&vt.Read)!=0}),w,T)};if((T.cacheable!==void 0?T.cacheable:w.cacheable)!==!0)return k();var b={request:w,mappedRequestOptions:T,transporter:{queryParameters:O.queryParameters,headers:O.headers}};return O.responsesCache.get(b,function(){return O.requestsCache.get(b,function(){return O.requestsCache.set(b,k()).then(function(L){return Promise.all([O.requestsCache.delete(b),L])},function(L){return Promise.all([O.requestsCache.delete(b),Promise.reject(L)])}).then(function(L){var P=ft(L,2);return P[0],P[1]})})},{miss:function(L){return O.responsesCache.set(b,L)}})},write:function(w,S){return Wo(O,O.hosts.filter(function(T){return(T.accept&vt.Write)!=0}),w,Fo(S,O.timeouts.write))}};return O}(K(K({hosts:[{url:"".concat(a,"-dsn.algolia.net"),accept:vt.Read},{url:"".concat(a,".algolia.net"),accept:vt.Write}].concat(Wd([{url:"".concat(a,"-1.algolianet.com")},{url:"".concat(a,"-2.algolianet.com")},{url:"".concat(a,"-3.algolianet.com")}]))},o),{},{headers:K(K({},l.headers()),{},{"content-type":"application/x-www-form-urlencoded"},o.headers),queryParameters:K(K({},l.queryParameters()),o.queryParameters)})),s={transporter:i,appId:a,addAlgoliaAgent:function(u,c){i.userAgent.add({segment:u,version:c})},clearCache:function(){return Promise.all([i.requestsCache.clear(),i.responsesCache.clear()]).then(function(){})}};return oi(s,o.methods)}(K(K(K({},r),n),{},{methods:{search:Go,searchForFacetValues:Jo,multipleQueries:Go,multipleSearchForFacetValues:Jo,customRequest:Jd,initIndex:function(o){return function(a){return li(o)(a,{methods:{search:Zd,searchForFacetValues:si,findAnswers:Qd}})}}}}))}ui.version="4.19.1";var Yd=["footer","searchBox"];function Xd(e){var t=e.appId,n=e.apiKey,r=e.indexName,o=e.placeholder,a=o===void 0?"Search docs":o,l=e.searchParameters,i=e.maxResultsPerGroup,s=e.onClose,u=s===void 0?Rd:s,c=e.transformItems,d=c===void 0?qo:c,m=e.hitComponent,p=m===void 0?bd:m,h=e.resultsFooterComponent,_=h===void 0?function(){return null}:h,v=e.navigator,g=e.initialScrollY,y=g===void 0?0:g,O=e.transformSearchClient,w=O===void 0?qo:O,S=e.disableUserPersonalization,T=S!==void 0&&S,k=e.initialQuery,b=k===void 0?"":k,L=e.translations,P=L===void 0?{}:L,N=e.getMissingResultsUrl,j=e.insights,D=j!==void 0&&j,z=P.footer,G=P.searchBox,$=Ye(P,Yd),X=ot(f.useState({query:"",collections:[],completion:null,context:{},isOpen:!1,activeItemId:null,status:"idle"}),2),J=X[0],_e=X[1],ne=f.useRef(null),se=f.useRef(null),ee=f.useRef(null),Se=f.useRef(null),pe=f.useRef(null),oe=f.useRef(10),ke=f.useRef(typeof window<"u"?window.getSelection().toString().slice(0,64):"").current,be=f.useRef(b||ke).current,Pe=function(M,F,ue){return f.useMemo(function(){var ye=ui(M,F);return ye.addAlgoliaAgent("docsearch","3.6.1"),/docsearch.js \(.*\)/.test(ye.transporter.userAgent.value)===!1&&ye.addAlgoliaAgent("docsearch-react","3.6.1"),ue(ye)},[M,F,ue])}(t,n,w),De=f.useRef(Bo({key:"__DOCSEARCH_FAVORITE_SEARCHES__".concat(r),limit:10})).current,Ee=f.useRef(Bo({key:"__DOCSEARCH_RECENT_SEARCHES__".concat(r),limit:De.getAll().length===0?7:4})).current,ge=f.useCallback(function(M){if(!T){var F=M.type==="content"?M.__docsearch_parent:M;F&&De.getAll().findIndex(function(ue){return ue.objectID===F.objectID})===-1&&Ee.add(F)}},[De,Ee,T]),Be=f.useCallback(function(M){if(J.context.algoliaInsightsPlugin&&M.__autocomplete_id){var F=M,ue={eventName:"Item Selected",index:F.__autocomplete_indexName,items:[F],positions:[M.__autocomplete_id],queryID:F.__autocomplete_queryID};J.context.algoliaInsightsPlugin.insights.clickedObjectIDsAfterSearch(ue)}},[J.context.algoliaInsightsPlugin]),Le=f.useMemo(function(){return _d({id:"docsearch",defaultActiveItemId:0,placeholder:a,openOnFocus:!0,initialState:{query:be,context:{searchSuggestions:[]}},insights:D,navigator:v,onStateChange:function(M){_e(M.state)},getSources:function(M){var F=M.query,ue=M.state,ye=M.setContext,Fe=M.setStatus;if(!F)return T?[]:[{sourceId:"recentSearches",onSelect:function(re){var Ae=re.item,Et=re.event;ge(Ae),fn(Et)||u()},getItemUrl:function(re){return re.item.url},getItems:function(){return Ee.getAll()}},{sourceId:"favoriteSearches",onSelect:function(re){var Ae=re.item,Et=re.event;ge(Ae),fn(Et)||u()},getItemUrl:function(re){return re.item.url},getItems:function(){return De.getAll()}}];var Ue=Boolean(D);return Pe.search([{query:F,indexName:r,params:At({attributesToRetrieve:["hierarchy.lvl0","hierarchy.lvl1","hierarchy.lvl2","hierarchy.lvl3","hierarchy.lvl4","hierarchy.lvl5","hierarchy.lvl6","content","type","url"],attributesToSnippet:["hierarchy.lvl1:".concat(oe.current),"hierarchy.lvl2:".concat(oe.current),"hierarchy.lvl3:".concat(oe.current),"hierarchy.lvl4:".concat(oe.current),"hierarchy.lvl5:".concat(oe.current),"hierarchy.lvl6:".concat(oe.current),"content:".concat(oe.current)],snippetEllipsisText:"โ€ฆ",highlightPreTag:"",highlightPostTag:"",hitsPerPage:20,clickAnalytics:Ue},l)}]).catch(function(re){throw re.name==="RetryError"&&Fe("error"),re}).then(function(re){var Ae=re.results[0],Et=Ae.hits,di=Ae.nbHits,An=zo(Et,function(Tn){return ri(Tn)},i);ue.context.searchSuggestions.length0&&(Xt(),pe.current&&pe.current.focus())},[be,Xt]),f.useEffect(function(){function M(){if(se.current){var F=.01*window.innerHeight;se.current.style.setProperty("--docsearch-vh","".concat(F,"px"))}}return M(),window.addEventListener("resize",M),function(){window.removeEventListener("resize",M)}},[]),f.createElement("div",fe({ref:ne},bt({"aria-expanded":!0}),{className:["DocSearch","DocSearch-Container",J.status==="stalled"&&"DocSearch-Container--Stalled",J.status==="error"&&"DocSearch-Container--Errored"].filter(Boolean).join(" "),role:"button",tabIndex:0,onMouseDown:function(M){M.target===M.currentTarget&&u()}}),f.createElement("div",{className:"DocSearch-Modal",ref:se},f.createElement("header",{className:"DocSearch-SearchBar",ref:ee},f.createElement(Bd,fe({},Le,{state:J,autoFocus:be.length===0,inputRef:pe,isFromSelection:Boolean(be)&&be===ke,translations:G,onClose:u}))),f.createElement("div",{className:"DocSearch-Dropdown",ref:Se},f.createElement(zd,fe({},Le,{indexName:r,state:J,hitComponent:p,resultsFooterComponent:_,disableUserPersonalization:T,recentSearches:Ee,favoriteSearches:De,inputRef:pe,translations:$,getMissingResultsUrl:N,onItemClick:function(M,F){Be(M),ge(M),fn(F)||u()}}))),f.createElement("footer",{className:"DocSearch-Footer"},f.createElement(yd,{translations:z}))))}function em(e){var t,n,r=f.useRef(null),o=ot(f.useState(!1),2),a=o[0],l=o[1],i=ot(f.useState((e==null?void 0:e.initialQuery)||void 0),2),s=i[0],u=i[1],c=f.useCallback(function(){l(!0)},[l]),d=f.useCallback(function(){l(!1)},[l]);return function(m){var p=m.isOpen,h=m.onOpen,_=m.onClose,v=m.onInput,g=m.searchButtonRef;f.useEffect(function(){function y(O){var w;(O.keyCode===27&&p||((w=O.key)===null||w===void 0?void 0:w.toLowerCase())==="k"&&(O.metaKey||O.ctrlKey)||!function(S){var T=S.target,k=T.tagName;return T.isContentEditable||k==="INPUT"||k==="SELECT"||k==="TEXTAREA"}(O)&&O.key==="/"&&!p)&&(O.preventDefault(),p?_():document.body.classList.contains("DocSearch--active")||document.body.classList.contains("DocSearch--active")||h()),g&&g.current===document.activeElement&&v&&/[a-zA-Z0-9]/.test(String.fromCharCode(O.keyCode))&&v(O)}return window.addEventListener("keydown",y),function(){window.removeEventListener("keydown",y)}},[p,h,_,v,g])}({isOpen:a,onOpen:c,onClose:d,onInput:f.useCallback(function(m){l(!0),u(m.key)},[l,u]),searchButtonRef:r}),f.createElement(f.Fragment,null,f.createElement(Ru,{ref:r,translations:e==null||(t=e.translations)===null||t===void 0?void 0:t.button,onClick:c}),a&&Fa(f.createElement(Xd,fe({},e,{initialScrollY:window.scrollY,initialQuery:s,translations:e==null||(n=e.translations)===null||n===void 0?void 0:n.modal,onClose:d})),document.body))}function tm(e){Wa(f.createElement(em,Fn({},e,{transformSearchClient:function(t){return t.addAlgoliaAgent("docsearch.js","3.6.1"),e.transformSearchClient?e.transformSearchClient(t):t}})),function(t){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:window;return typeof t=="string"?n.document.querySelector(t):t}(e.container,e.environment))}const nm=e=>e.button===1||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey,rm=()=>{const e=at();return{hitComponent:({hit:t,children:n})=>({type:"a",ref:void 0,constructor:void 0,key:void 0,props:{href:t.url,onClick:r=>{nm(r)||(r.preventDefault(),e.push(Pr(t.url,"/docs/")))},children:n},__v:null}),navigator:{navigate:({itemUrl:t})=>{e.push(Pr(t,"/docs/"))}},transformSearchClient:t=>{const n=ur(t.search,500);return{...t,search:async(...r)=>n(...r)}}}};E(()=>import("./style-e9220a04.js"),[]),E(()=>import("./docsearch-1d421ddb.js"),[]);const om=W({name:"Docsearch",props:{containerId:{type:String,required:!1,default:"docsearch-container"},options:{type:Object,required:!0}},setup(e){const t=Zt(),n=sa(),r=rm(),o=C(()=>{var i;return{...e.options,...(i=e.options.locales)==null?void 0:i[t.value]}}),a=[],l=()=>{var s;const i=((s=o.value.searchParameters)==null?void 0:s.facetFilters)??[];a.splice(0,a.length,`lang:${n.value}`,...Jt(i)?i:[i]),tm({...r,...o.value,container:`#${e.containerId}`,searchParameters:{...o.value.searchParameters,facetFilters:a}})};return we(()=>{l(),Ge([t,o],([i,s],[u,c])=>{i!==u&&JSON.stringify(s)!==JSON.stringify(c)&&l()}),Ge(n,(i,s)=>{if(i!==s){const u=a.findIndex(c=>c===`lang:${s}`);u>-1&&a.splice(u,1,`lang:${i}`)}})}),()=>Z("div",{id:e.containerId})}}),am={appId:"2LT25GG3KX",apiKey:"389be16f732f82c611e1b0f22c031dff",indexName:"engine-needle"},im=xe({enhance({app:e}){e.component("Docsearch",()=>Z(om,{options:am}))}}),cm={enhance:({app:e})=>{e.component("action",I(()=>E(()=>import("./action-ca178bd9.js"),["assets/action-ca178bd9.js","assets/framework-f6820c83.js"]))),e.component("actiongroup",I(()=>E(()=>import("./actiongroup-1feb3d4e.js"),["assets/actiongroup-1feb3d4e.js","assets/framework-f6820c83.js"]))),e.component("contribution-header",I(()=>E(()=>import("./contribution-header-f6071865.js"),["assets/contribution-header-f6071865.js","assets/framework-f6820c83.js"]))),e.component("contribution-listentry",I(()=>E(()=>import("./contribution-listentry-41a12262.js"),["assets/contribution-listentry-41a12262.js","assets/framework-f6820c83.js"]))),e.component("contribution-preview",I(()=>E(()=>import("./contribution-preview-754df1c1.js"),["assets/contribution-preview-754df1c1.js","assets/framework-f6820c83.js"]))),e.component("contributions-author",I(()=>E(()=>import("./contributions-author-f0bcc61a.js"),["assets/contributions-author-f0bcc61a.js","assets/framework-f6820c83.js"]))),e.component("contributions-overview",I(()=>E(()=>import("./contributions-overview-5f531929.js"),["assets/contributions-overview-5f531929.js","assets/framework-f6820c83.js"]))),e.component("copyright",I(()=>E(()=>import("./copyright-6b449b0c.js"),["assets/copyright-6b449b0c.js","assets/framework-f6820c83.js"]))),e.component("github-star",I(()=>E(()=>import("./github-star-e3e26cb5.js"),["assets/github-star-e3e26cb5.js","assets/framework-f6820c83.js"]))),e.component("metalink",I(()=>E(()=>import("./metalink-e99a74bf.js"),["assets/metalink-e99a74bf.js","assets/framework-f6820c83.js"]))),e.component("needle-button",I(()=>E(()=>import("./needle-button-a6fac229.js"),["assets/needle-button-a6fac229.js","assets/framework-f6820c83.js"]))),e.component("os-link",I(()=>E(()=>import("./os-link-31cc79d0.js"),["assets/os-link-31cc79d0.js","assets/framework-f6820c83.js"]))),e.component("quoteslides",I(()=>E(()=>import("./quoteslides-a8d399c2.js"),["assets/quoteslides-a8d399c2.js","assets/framework-f6820c83.js"]))),e.component("removeserviceworker",I(()=>E(()=>import("./removeserviceworker-8e285252.js"),["assets/removeserviceworker-8e285252.js","assets/framework-f6820c83.js"]))),e.component("sample",I(()=>E(()=>import("./sample-d7ec0e26.js"),["assets/sample-d7ec0e26.js","assets/framework-f6820c83.js"]))),e.component("stackblitz",I(()=>E(()=>import("./stackblitz-b078650b.js"),["assets/stackblitz-b078650b.js","assets/framework-f6820c83.js"]))),e.component("testimonial",I(()=>E(()=>import("./testimonial-9717e223.js"),["assets/testimonial-9717e223.js","assets/framework-f6820c83.js"]))),e.component("video-embed",I(()=>E(()=>import("./video-embed-0d320302.js"),["assets/video-embed-0d320302.js","assets/framework-f6820c83.js"])))}},lm=e=>{if(window.dataLayer&&window.gtag)return;const t=document.createElement("script");t.src=`https://www.googletagmanager.com/gtag/js?id=${e.id}`,t.async=!0,document.head.appendChild(t),window.dataLayer=window.dataLayer||[],window.gtag=function(){dataLayer.push(arguments)},gtag("js",new Date),e.debug?gtag("config",e.id,{debug_mode:!0}):gtag("config",e.id)},sm={id:"G-V2Q445L3XQ",debug:!1},um=xe({enhance(){lm(sm)}}),dm=xe({enhance({app:e,router:t,siteData:n}){},setup(){},rootComponents:[]}),pn=[Qi,Xi,rc,vc,yc,Pc,fu,im,cm,um,dm],mm=[["v-1179968c","/SUMMARY.html",{title:"Get Started"},["/SUMMARY","/SUMMARY.md"]],["v-45b6c339","/backlog-mermaid.html",{title:""},["/backlog-mermaid","/_backlog-mermaid.html","/_backlog-mermaid.md"]],["v-66ba7b75","/backlog.html",{title:"Documentation Backlog"},["/backlog","/_backlog.html","/_backlog.md"]],["v-0ca6c8f8","/meta-test.html",{title:""},["/meta-test","/_meta-test.html","/_meta-test.md"]],["v-5572989e","/component-compiler.html",{title:"Automatic Component Generation"},["/component-compiler","/component-compiler.md"]],["v-d8eff192","/component-reference.html",{title:"Built-in Components"},["/component-reference","/component-reference.md"]],["v-f588dc38","/debugging.html",{title:"How To Debug"},["/debugging","/debugging.md"]],["v-4d21151b","/deployment.html",{title:"Deployment and Optimization"},["/deployment","/deployment.md"]],["v-f2e4a738","/everywhere-actions.html",{title:"Everywhere Actions"},["/everywhere-actions","/everywhere-actions.md"]],["v-63f25852","/examples.html",{title:"Example Projects โœจ"},["/examples","/examples.md"]],["v-99bff5e8","/export.html",{title:"Exporting Assets to glTF"},["/export","/export.md"]],["v-092a1d7c","/faq.html",{title:"Questions and Answers (FAQ) ๐Ÿ’ก"},["/faq","/faq.md"]],["v-bcfe00ae","/features-overview.html",{title:"Feature Overview"},["/features-overview","/features-overview.md"]],["v-7f349b5b","/for-unity-developers.html",{title:"Scripting basics For Unity Developers"},["/for-unity-developers","/for-unity-developers.md"]],["v-5dc4b15a","/getting-started.html",{title:""},["/getting-started","/getting-started.md"]],["v-e554bd96","/html.html",{title:"Frameworks, Bundlers, HTML"},["/html","/html.md"]],["v-8daa1a0e","/",{title:""},["/index.html","/index.md"]],["v-bb6009aa","/modules.html",{title:"Additional Modules"},["/modules","/modules.md"]],["v-dda7a368","/networking.html",{title:"Networking"},["/networking","/networking.md"]],["v-5c95b2b3","/project-structure.html",{title:"Project Structure"},["/project-structure","/project-structure.md"]],["v-761831b6","/samples-and-modules.html",{title:"Samples Projects"},["/samples-and-modules","/samples-and-modules.md"]],["v-ecef79fe","/scripting-examples.html",{title:"Scripting Examples"},["/scripting-examples","/scripting-examples.md"]],["v-fab6318a","/scripting.html",{title:"Creating and using Components"},["/scripting","/scripting.md"]],["v-48ca6631","/showcase-bike.html",{title:"Bike Configurator ๐Ÿšฒ"},["/showcase-bike","/showcase-bike.md"]],["v-9d01ad8c","/showcase-castle.html",{title:"Castle Builder ๐Ÿฐ"},["/showcase-castle","/showcase-castle.md"]],["v-aa56fb8c","/showcase-mercedes-benz.html",{title:"Mercedes-Benz Showcase"},["/showcase-mercedes-benz","/showcase-mercedes-benz.md"]],["v-da75c730","/showcase-monsterhands.html",{title:"Monster Hands ๐Ÿ’€"},["/showcase-monsterhands","/showcase-monsterhands.md"]],["v-3d5fe3ab","/showcase-towerdefence.html",{title:"Tower Defense"},["/showcase-towerdefence","/showcase-towerdefence.md"]],["v-499adc36","/showcase-website.html",{title:"Castle Builder ๐Ÿฐ"},["/showcase-website","/showcase-website.md"]],["v-7c306381","/showcase-zenrepublic.html",{title:"Monster Hands ๐Ÿ’€"},["/showcase-zenrepublic","/showcase-zenrepublic.md"]],["v-3b00a577","/technical-overview.html",{title:"Technical Overview"},["/technical-overview","/technical-overview.md"]],["v-7e348068","/testimonials.html",{title:"Testimonials"},["/testimonials","/testimonials.md"]],["v-53401e42","/testing.html",{title:"Testing on local devices"},["/testing","/testing.md"]],["v-7acec8c5","/vanilla-js.html",{title:"Using Needle Engine directly from HTML"},["/vanilla-js","/vanilla-js.md"]],["v-2ace8550","/vision.html",{title:"Our Vision ๐Ÿ”ฎ"},["/vision","/vision.md"]],["v-099a1df4","/xr.html",{title:"VR & AR (WebXR)"},["/xr","/xr.md"]],["v-c2d8b5ac","/blender/",{title:"Needle Engine for Blender"},["/blender/index.html","/blender/index.md"]],["v-63ed5104","/getting-started/for-unity-developers.html",{title:"Scripting Introduction for Unity Developers"},["/getting-started/for-unity-developers","/getting-started/for-unity-developers.md"]],["v-ccdc4da0","/getting-started/",{title:"Getting Started & Installation"},["/getting-started/index.html","/getting-started/index.md"]],["v-2e0e70fc","/getting-started/typescript-essentials.html",{title:"Scripting in Needle Engine"},["/getting-started/typescript-essentials","/getting-started/typescript-essentials.md"]],["v-f7b8f1f2","/reference/needle-config-json.html",{title:"needle.config.json"},["/reference/needle-config-json","/reference/needle-config-json.md"]],["v-caed7310","/reference/needle-engine-attributes.html",{title:" Configuration"},["/reference/needle-engine-attributes","/reference/needle-engine-attributes.md"]],["v-6d28ca94","/reference/typescript-decorators.html",{title:"@serializable and other decorators"},["/reference/typescript-decorators","/reference/typescript-decorators.md"]],["v-3706649a","/404.html",{title:""},["/404"]],["v-165956bc","/community/contributions/llllkatjallll/custom-vr-button-that-appears-only-on-headsets-and-not-on-mobile-phones/",{title:""},["/community/contributions/llllkatjallll/custom-vr-button-that-appears-only-on-headsets-and-not-on-mobile-phones/index.html"]],["v-e6fbcab8","/community/contributions/llllkatjallll/set-fallback-material-for-usdz-exporter/",{title:""},["/community/contributions/llllkatjallll/set-fallback-material-for-usdz-exporter/index.html"]],["v-289be385","/community/contributions/llllkatjallll/",{title:""},["/community/contributions/llllkatjallll/index.html"]],["v-44471890","/community/contributions/robyer1/microphone-access-in-a-browser-window-and-streamed-playback/",{title:""},["/community/contributions/robyer1/microphone-access-in-a-browser-window-and-streamed-playback/index.html"]],["v-ea622532","/community/contributions/robyer1/ar-move-scale-rotate-controls-for-needle-on-mobile/",{title:""},["/community/contributions/robyer1/ar-move-scale-rotate-controls-for-needle-on-mobile/index.html"]],["v-28e23eea","/community/contributions/robyer1/",{title:""},["/community/contributions/robyer1/index.html"]],["v-1d2bf24a","/community/contributions/ericcraft-mh/quicklook-vertical-image-tracker/",{title:""},["/community/contributions/ericcraft-mh/quicklook-vertical-image-tracker/index.html"]],["v-b5125a16","/community/contributions/ericcraft-mh/",{title:""},["/community/contributions/ericcraft-mh/index.html"]],["v-fd5a4508","/community/contributions/krisrok/always-open-in-specific-browser/",{title:""},["/community/contributions/krisrok/always-open-in-specific-browser/index.html"]],["v-07e00b06","/community/contributions/krisrok/",{title:""},["/community/contributions/krisrok/index.html"]],["v-10c99314","/community/contributions/marwie/camera-video-background/",{title:""},["/community/contributions/marwie/camera-video-background/index.html"]],["v-acde0924","/community/contributions/marwie/usdz-hide-object-on-start/",{title:""},["/community/contributions/marwie/usdz-hide-object-on-start/index.html"]],["v-93aa7836","/community/contributions/marwie/everywhere-action-emphasize-on-click/",{title:""},["/community/contributions/marwie/everywhere-action-emphasize-on-click/index.html"]],["v-e9b14526","/community/contributions/marwie/control-a-timeline-by-scroll/",{title:""},["/community/contributions/marwie/control-a-timeline-by-scroll/index.html"]],["v-0c347631","/community/contributions/marwie/code-contribution-example/",{title:""},["/community/contributions/marwie/code-contribution-example/index.html"]],["v-5d69cca2","/community/contributions/marwie/",{title:""},["/community/contributions/marwie/index.html"]],["v-4fe6a858","/community/contributions/kipash/calculate-pointer-world-position/",{title:""},["/community/contributions/kipash/calculate-pointer-world-position/index.html"]],["v-74ff5ee0","/community/contributions/kipash/",{title:""},["/community/contributions/kipash/index.html"]],["v-3721011d","/community/contributions/web3kev/vertical-move-in-vr-using-the-right-joystick-quest/",{title:""},["/community/contributions/web3kev/vertical-move-in-vr-using-the-right-joystick-quest/index.html"]],["v-a305d722","/community/contributions/web3kev/squeeze-to-scale-object-or-world-in-vr/",{title:""},["/community/contributions/web3kev/squeeze-to-scale-object-or-world-in-vr/index.html"]],["v-51c63c84","/community/contributions/web3kev/network-instantiation-of-multiple-objects/",{title:""},["/community/contributions/web3kev/network-instantiation-of-multiple-objects/index.html"]],["v-1b71f33d","/community/contributions/web3kev/",{title:""},["/community/contributions/web3kev/index.html"]],["v-40fd290b","/community/contributions/",{title:""},["/community/contributions/index.html"]]];var Qo=W({name:"Vuepress",setup(){const e=Mi();return()=>Z(e.value)}}),fm=()=>mm.reduce((e,[t,n,r,o])=>(e.push({name:t,path:n,component:Qo,meta:r},...o.map(a=>({path:a,redirect:n}))),e),[{name:"404",path:"/:catchAll(.*)",component:Qo}]),pm=Li,vm=()=>{const e=Ii({history:pm(ra("/docs/")),routes:fm(),scrollBehavior:(t,n,r)=>r||(t.hash?{el:t.hash}:{top:0})});return e.beforeResolve(async(t,n)=>{var r;(t.path!==n.path||n===ki)&&([Ke.value]=await Promise.all([Ne.resolvePageData(t.name),(r=oa[t.name])==null?void 0:r.__asyncLoader()]))}),e},hm=e=>{e.component("ClientOnly",lr),e.component("Content",Fi)},_m=(e,t,n)=>{const r=C(()=>Ne.resolveLayouts(n)),o=C(()=>Ne.resolveRouteLocale(mt.value.locales,t.currentRoute.value.path)),a=C(()=>Ne.resolveSiteLocaleData(mt.value,o.value)),l=C(()=>Ne.resolvePageFrontmatter(Ke.value)),i=C(()=>Ne.resolvePageHeadTitle(Ke.value,a.value)),s=C(()=>Ne.resolvePageHead(i.value,l.value,a.value)),u=C(()=>Ne.resolvePageLang(Ke.value)),c=C(()=>Ne.resolvePageLayout(Ke.value,r.value));return e.provide(Ri,r),e.provide(ia,l),e.provide(Hi,i),e.provide(ca,s),e.provide(la,u),e.provide(ua,c),e.provide(ir,o),e.provide(da,a),Object.defineProperties(e.config.globalProperties,{$frontmatter:{get:()=>l.value},$head:{get:()=>s.value},$headTitle:{get:()=>i.value},$lang:{get:()=>u.value},$page:{get:()=>Ke.value},$routeLocale:{get:()=>o.value},$site:{get:()=>mt.value},$siteLocale:{get:()=>a.value},$withBase:{get:()=>sr}}),{layouts:r,pageData:Ke,pageFrontmatter:l,pageHead:s,pageHeadTitle:i,pageLang:u,pageLayout:c,routeLocale:o,siteData:mt,siteLocaleData:a}},gm=()=>{const e=Ze(),t=Vi(),n=sa(),r=ie([]),o=()=>{t.value.forEach(l=>{const i=ym(l);i&&r.value.push(i)})},a=()=>{document.documentElement.lang=n.value,r.value.forEach(l=>{l.parentNode===document.head&&document.head.removeChild(l)}),r.value.splice(0,r.value.length),t.value.forEach(l=>{const i=bm(l);i!==null&&(document.head.appendChild(i),r.value.push(i))})};rr(zi,a),we(()=>{o(),a(),Ge(()=>e.path,()=>a())})},ym=([e,t,n=""])=>{const r=Object.entries(t).map(([i,s])=>je(s)?`[${i}=${JSON.stringify(s)}]`:s===!0?`[${i}]`:"").join(""),o=`head > ${e}${r}`;return Array.from(document.querySelectorAll(o)).find(i=>i.innerText===n)||null},bm=([e,t,n])=>{if(!je(e))return null;const r=document.createElement(e);return or(t)&&Object.entries(t).forEach(([o,a])=>{je(a)?r.setAttribute(o,a):a===!0&&r.setAttribute(o,"")}),je(n)&&r.appendChild(document.createTextNode(n)),r},Em=Di,Om=async()=>{var n;const e=Em({name:"VuepressApp",setup(){var r;gm();for(const o of pn)(r=o.setup)==null||r.call(o);return()=>[Z(Ai),...pn.flatMap(({rootComponents:o=[]})=>o.map(a=>Z(a)))]}}),t=vm();hm(e),_m(e,t,pn);for(const r of pn)await((n=r.enhance)==null?void 0:n.call(r,{app:e,router:t,siteData:mt}));return e.use(t),{app:e,router:t}};Om().then(({app:e,router:t})=>{t.isReady().then(()=>{e.mount("#app")})});export{E as _,Om as createVueApp,xe as d}; diff --git a/assets/back-to-top-8efcbe56.svg b/assets/back-to-top-8efcbe56.svg new file mode 100644 index 000000000..83236781a --- /dev/null +++ b/assets/back-to-top-8efcbe56.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/backlog-mermaid.html-43151d51.js b/assets/backlog-mermaid.html-43151d51.js new file mode 100644 index 000000000..edfc45756 --- /dev/null +++ b/assets/backlog-mermaid.html-43151d51.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-45b6c339","path":"/backlog-mermaid.html","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/backlog mermaid.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":1666603563000},"filePathRelative":"_backlog-mermaid.md"}');export{e as data}; diff --git a/assets/backlog-mermaid.html-af84429f.js b/assets/backlog-mermaid.html-af84429f.js new file mode 100644 index 000000000..54b4be3c5 --- /dev/null +++ b/assets/backlog-mermaid.html-af84429f.js @@ -0,0 +1,16 @@ +import{_ as n,p as e,q as t,Q as s,a1 as a}from"./framework-f6820c83.js";const r={},o=a(`
flowchart LR
+  Editor([<b>C# components</b><br/>on GameObjects]) --> gltf[<b>JSON data</b><br/>as glTF Extension] --> Runtime([<b>JavaScript components</b><br/>on Object3D])
+  class Editor,gltf,Runtime bg;
+
`,1),l=a(`
flowchart LR
+    Editor([Unity Editor]) --> EditorExt([Components + Tools])
+    EditorExt -- export data --> glTF([glTF + Extensions])
+    glTF --> Bundler([Bundler - vite])
+    Runtime([Needle Runtime]) --> Bundler
+    Three([Three.js]) --> Bundler
+    YourWebsite([Classic web files - HTML, CSS, JS]) --> Bundler
+    Bundler -- outputs --> DevPage([web app - dev])
+    Bundler -- outputs --> DeploymentPage([web app - deploy])
+    glTF -- compressed with --> gltfTransform([glTF-transform]) --> DeploymentPage
+    class EditorExt,glTF,Runtime ndl;
+    class Editor,Three,Bundler,Page,gltfTransform,DeploymentPage,DevPage,YourWebsite ext;
+
`,1);function p(i,c){return e(),t("div",null,[s(" scripting "),o,s(" technical overview "),l])}const g=n(r,[["render",p],["__file","backlog-mermaid.html.vue"]]);export{g as default}; diff --git a/assets/backlog.html-0991873b.js b/assets/backlog.html-0991873b.js new file mode 100644 index 000000000..072a8503c --- /dev/null +++ b/assets/backlog.html-0991873b.js @@ -0,0 +1 @@ +import{_ as i,M as a,p as r,q as c,R as e,t,N as o,a1 as s}from"./framework-f6820c83.js";const l={},d=s('

Documentation Backlog

This section contains pieces of information that are important, but need to be sorted into their correct categories.

  • Unity 2020.3.8f1+ or 2022.1+
  • Render Pipeline: Universal
  • Color Space: Linear
  • Non-Directional Lightmaps
  • Lightmap Encoding: Normal Quality

Supported Unity configurations

  • Unity 2020.3+ | Unity 2021.3+ | Unity 2022.1+
  • Render Pipeline: Universal | Built-In1
  • Color Space: Linear

1: no custom shader support

Source Control

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.

Licensing Setup

Note: This section is deprecated. Needle Engine is currently on Open Beta, and there's no need to authenticate against our registry at this point.

Authentication

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.

',15),h=e("li",null,[t("Clone this repository and open "),e("code",null,"starter/Authenticate"),t(" with Unity 2020.3.x")],-1),u={href:"https://packages.needle.tools",target:"_blank",rel:"noopener noreferrer"},p={href:"https://packages.needle.tools",target:"_blank",rel:"noopener noreferrer"},g=e("code",null,"i",-1),f=e("code",null,"Registry Info",-1),m=e("li",null,[t("Copy the line containing "),e("code",null,"_authToken"),t(" (see the video below)"),e("br"),e("video",{src:"https://user-images.githubusercontent.com/5083203/166433857-a0c9e29f-9413-4e10-a1a1-2029e3d3ab06.mp4",autoplay:""})],-1),b=e("li",null,"Focus Unity - a notification window should open that the information has been added successfully from your clipboard.",-1),y=e("li",null,"Click save and close Unity. You should now have access rights to the needle package registry.",-1);function _(k,x){const n=a("ExternalLinkIcon");return r(),c("div",null,[d,e("ol",null,[h,e("li",null,[t("Open "),e("a",u,[t("https://packages.needle.tools โ‡ก"),o(n)]),t(" in your browser and login (top right corner) with your github account.")]),e("li",null,[t("Return to "),e("a",p,[t("packages.needle.tools โ‡ก"),o(n)]),t(" and click the "),g,t(" icon in the top right corner opening the "),f,t(" window.")]),m,b,y])])}const v=i(l,[["render",_],["__file","backlog.html.vue"]]);export{v as default}; diff --git a/assets/backlog.html-5f538ac1.js b/assets/backlog.html-5f538ac1.js new file mode 100644 index 000000000..6d9ed0dfd --- /dev/null +++ b/assets/backlog.html-5f538ac1.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-66ba7b75","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{e as data}; diff --git a/assets/buttons.esm-5a367c11.js b/assets/buttons.esm-5a367c11.js new file mode 100644 index 000000000..52baca35a --- /dev/null +++ b/assets/buttons.esm-5a367c11.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/component-compiler.html-7fadcdfe.js b/assets/component-compiler.html-7fadcdfe.js new file mode 100644 index 000000000..f15a35913 --- /dev/null +++ b/assets/component-compiler.html-7fadcdfe.js @@ -0,0 +1,99 @@ +import{_ as i,M as o,p,q as r,R as n,t as s,N as e,V as a,a1 as u}from"./framework-f6820c83.js";const d={},k=n("h3",{id:"automatically-generating-editor-components",tabindex:"-1"},[n("a",{class:"header-anchor",href:"automatically-generating-editor-components","aria-hidden":"true"},"#"),s(" Automatically generating Editor components")],-1),m=n("p",null,"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.",-1),b={href:"https://www.npmjs.com/package/@needle-tools/needle-component-compiler",target:"_blank",rel:"noopener noreferrer"},y=u(`

Controlling component generation

You can use the following comments in your typescript code to control C# code generation behavior:

AttributeResult
// @generate-componentForce generation of next class
// @dont-generate-componentDisable generation of next class, this is useful in cases where you already have an existing C# script in your project
// @serializeFieldDecorate generated field with [SerializeField]
// @type UnityEngine.CameraSpecify generated C# field type
// @nonSerializedSkip generating the next field or method

Examples

Force the component compiler to generate a C# AudioClip field named myAudioClip

export class MyComponent extends Behaviour {
+	//@type UnityEngine.AudioClip
+	@serializable()
+	myAudioClip?:string;
+}
+

Force the component compiler to derive from a specific subclass

//@type MyNamespace.MyCustomBaseClass
+export class MyComponent extends MyCustomBaseClass {
+}
+

Component Compiler in Unity

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

`,12),v=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" AssetReference"),n("span",{class:"token punctuation"},","),s(" Behaviour"),n("span",{class:"token punctuation"},","),s(" serializable "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Object3D "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"three"'),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"MyCustomComponent"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + myFloatValue`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token builtin"},"number"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"42"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),s("Object3D"),n("span",{class:"token punctuation"},")"),s(` + myOtherObject`),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s(" Object3D"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),s("AssetReference"),n("span",{class:"token punctuation"},")"),s(` + prefabs`),n("span",{class:"token operator"},":"),s(" AssetReference"),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token function"},"start"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"sayHello"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token function"},"sayHello"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token builtin"},"console"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"log"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"Hello World"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),h=n("div",{class:"language-csharp line-numbers-mode","data-ext":"cs"},[n("pre",{class:"language-csharp"},[n("code",null,[n("span",{class:"token comment"},"// NEEDLE_CODEGEN_START"),s(` +`),n("span",{class:"token comment"},"// auto generated code - do not edit directly"),s(` + +`),n("span",{class:"token preprocessor property"},[s("#"),n("span",{class:"token directive keyword"},"pragma"),s(" warning disable")]),s(` + +`),n("span",{class:"token keyword"},"namespace"),s(),n("span",{class:"token namespace"},[s("Needle"),n("span",{class:"token punctuation"},"."),s("Typescript"),n("span",{class:"token punctuation"},"."),s("GeneratedComponents")]),s(` +`),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token keyword"},"partial"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"MyCustomComponent"),s(),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token type-list"},[n("span",{class:"token class-name"},[s("UnityEngine"),n("span",{class:"token punctuation"},"."),s("MonoBehaviour")])]),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token class-name"},[n("span",{class:"token keyword"},"float")]),s(" @myFloatValue "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"42f"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token class-name"},[s("UnityEngine"),n("span",{class:"token punctuation"},"."),s("Transform")]),s(" @myOtherObject"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token class-name"},[s("UnityEngine"),n("span",{class:"token punctuation"},"."),s("Transform"),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]")]),s(" @prefabs "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token constructor-invocation class-name"},[s("UnityEngine"),n("span",{class:"token punctuation"},"."),s("Transform"),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]")]),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token return-type class-name"},[n("span",{class:"token keyword"},"void")]),s(),n("span",{class:"token function"},"start"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"{"),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token return-type class-name"},[n("span",{class:"token keyword"},"void")]),s(),n("span",{class:"token function"},"update"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"{"),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` + +`),n("span",{class:"token comment"},"// NEEDLE_CODEGEN_END"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),g=n("div",{class:"language-csharp line-numbers-mode","data-ext":"cs"},[n("pre",{class:"language-csharp"},[n("code",null,[n("span",{class:"token keyword"},"using"),s(),n("span",{class:"token namespace"},"UnityEditor"),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token comment"},"// you can add code above or below the NEEDLE_CODEGEN_ blocks"),s(` + +`),n("span",{class:"token comment"},"// NEEDLE_CODEGEN_START"),s(` +`),n("span",{class:"token comment"},"// auto generated code - do not edit directly"),s(` + +`),n("span",{class:"token preprocessor property"},[s("#"),n("span",{class:"token directive keyword"},"pragma"),s(" warning disable")]),s(` + +`),n("span",{class:"token keyword"},"namespace"),s(),n("span",{class:"token namespace"},[s("Needle"),n("span",{class:"token punctuation"},"."),s("Typescript"),n("span",{class:"token punctuation"},"."),s("GeneratedComponents")]),s(` +`),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token keyword"},"partial"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"MyCustomComponent"),s(),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token type-list"},[n("span",{class:"token class-name"},[s("UnityEngine"),n("span",{class:"token punctuation"},"."),s("MonoBehaviour")])]),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token class-name"},[n("span",{class:"token keyword"},"float")]),s(" @myFloatValue "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"42f"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token class-name"},[s("UnityEngine"),n("span",{class:"token punctuation"},"."),s("Transform")]),s(" @myOtherObject"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token class-name"},[s("UnityEngine"),n("span",{class:"token punctuation"},"."),s("Transform"),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]")]),s(" @prefabs "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token constructor-invocation class-name"},[s("UnityEngine"),n("span",{class:"token punctuation"},"."),s("Transform"),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]")]),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token return-type class-name"},[n("span",{class:"token keyword"},"void")]),s(),n("span",{class:"token function"},"start"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"{"),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token return-type class-name"},[n("span",{class:"token keyword"},"void")]),s(),n("span",{class:"token function"},"update"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"{"),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` + +`),n("span",{class:"token comment"},"// NEEDLE_CODEGEN_END"),s(` + +`),n("span",{class:"token keyword"},"namespace"),s(),n("span",{class:"token namespace"},[s("Needle"),n("span",{class:"token punctuation"},"."),s("Typescript"),n("span",{class:"token punctuation"},"."),s("GeneratedComponents")]),s(` +`),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"// This is how you extend the generated component (namespace and class name must match!)"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token keyword"},"partial"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"MyCustomComponent"),s(),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token type-list"},[n("span",{class:"token class-name"},[s("UnityEngine"),n("span",{class:"token punctuation"},"."),s("MonoBehaviour")])]),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token return-type class-name"},[n("span",{class:"token keyword"},"void")]),s(),n("span",{class:"token function"},"MyAdditionalMethod"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token return-type class-name"},[n("span",{class:"token keyword"},"void")]),s(),n("span",{class:"token function"},"OnValidate"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + myFloatValue `),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"42"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token comment"},"// of course you can also add custom editors"),s(` + `),n("span",{class:"token punctuation"},"["),n("span",{class:"token function"},"CustomEditor"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"typeof"),n("span",{class:"token punctuation"},"("),n("span",{class:"token type-expression class-name"},"MyCustomComponent"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"MyCustomComponentEditor"),s(),n("span",{class:"token punctuation"},":"),s(),n("span",{class:"token type-list"},[n("span",{class:"token class-name"},"Editor")]),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token keyword"},"override"),s(),n("span",{class:"token return-type class-name"},[n("span",{class:"token keyword"},"void")]),s(),n("span",{class:"token function"},"OnInspectorGUI"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + EditorGUILayout`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"HelpBox"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"This is my sample component"'),n("span",{class:"token punctuation"},","),s(" MessageType"),n("span",{class:"token punctuation"},"."),s("None"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"base"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"OnInspectorGUI"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` + +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),w=n("h3",{id:"extending-generated-components",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#extending-generated-components","aria-hidden":"true"},"#"),s(" Extending generated components")],-1),f={href:"https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods",target:"_blank",rel:"noopener noreferrer"},C=n("code",null,"partial",-1),_=n("div",{class:"custom-container tip"},[n("p",{class:"custom-container-title"},"Member Casing"),n("p",null,[s("Exported members will start with a lowercase letter. For example if your C# member is named "),n("code",null,"MyString"),s(" it will be assigned to "),n("code",null,"myString"),s(".")])],-1);function E(x,N){const c=o("ExternalLinkIcon"),t=o("CodeGroupItem"),l=o("CodeGroup");return p(),r("div",null,[k,m,n("p",null,[s("This is thanks to the magic of the "),n("a",b,[s("Needle component compiler"),e(c)]),s(" 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.")]),y,e(l,null,{default:a(()=>[e(t,{title:"Typescript"},{default:a(()=>[v]),_:1}),e(t,{title:"Generated C#"},{default:a(()=>[h]),_:1}),e(t,{title:"Extending Generated C#"},{default:a(()=>[g]),_:1})]),_:1}),w,n("p",null,[s("Component C# classes are generated with the "),n("a",f,[C,e(c)]),s(" 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.")]),_])}const M=i(d,[["render",E],["__file","component-compiler.html.vue"]]);export{M as default}; diff --git a/assets/component-compiler.html-a9955c85.js b/assets/component-compiler.html-a9955c85.js new file mode 100644 index 000000000..09881267f --- /dev/null +++ b/assets/component-compiler.html-a9955c85.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5572989e","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":1725399379000},"filePathRelative":"component-compiler.md"}');export{e as data}; diff --git a/assets/component-reference.html-47d337de.js b/assets/component-reference.html-47d337de.js new file mode 100644 index 000000000..970ca5eb2 --- /dev/null +++ b/assets/component-reference.html-47d337de.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-d8eff192","path":"/component-reference.html","title":"Built-in Components","lang":"en-US","frontmatter":{"title":"Built-in Components","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/built in 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":1725399379000},"filePathRelative":"component-reference.md"}`);export{e as data}; diff --git a/assets/component-reference.html-7433ba9c.js b/assets/component-reference.html-7433ba9c.js new file mode 100644 index 000000000..9a0e2a723 --- /dev/null +++ b/assets/component-reference.html-7433ba9c.js @@ -0,0 +1 @@ +import{_ as l,M as i,p as s,q as c,R as t,t as e,N as o,V as a,a1 as n}from"./framework-f6820c83.js";const h={},u=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),p={href:"https://engine.needle.tools/docs/api/latest",target:"_blank",rel:"noopener noreferrer"},m=t("p",null,"You can always add your own components or add wrappers for Unity components we haven't provided yet.",-1),b=n('

Audio

NameDescription
AudioListener
AudioSourceUse to play audio

Animation

NameDescription
Animator with AnimatorControllerExport with animation state machine, conditions, transitions
AnimationMost basic animation component. Only first clip is exported
PlayableDirector with TimelineAssetExport powerful sequences to control animation, audio, state and more

Rendering

NameDescription
Camera
LightDirectionalLight, PointLight, Spotlight. Note that you can use it to bake light (e.g. Rectangular Light shapes) as well
XRFlagControl when objects will be visible. E.g. only enable object when in AR
DeviceFlagControl on which device objects will be visible
LODGroup
ParticleSystemExperimental and currently not fully supported
VideoPlayerPlayback 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
MeshRendererUsed to handle rendering of objects including lightmapping and instancing
SkinnedMeshRendererSee MeshRenderer
SpriteRendererUsed to render Sprites and Spriteanimations
Volume with PostProcessing assetSee table below

Postprocessing

',7),g={href:"https://www.npmjs.com/package/postprocessing",target:"_blank",rel:"noopener noreferrer"},f=t("ul",null,[t("li",null,[t("strong",null,"Unity only"),e(": "),t("em",null,"Note that Postprocessing effects using a Volume in Unity is only supported with URP")])],-1),y=t("thead",null,[t("tr",null,[t("th",null,"Effect Name"),t("th")])],-1),_=t("tr",null,[t("td",null,"Antialiasing"),t("td",null,[t("em",null,"extra Unity Component")])],-1),w=t("tr",null,[t("td",null,"Bloom"),t("td",null,[t("em",null,"via Volume asset")])],-1),x=t("tr",null,[t("td",null,"Chromatic Aberration"),t("td",null,[t("em",null,"via Volume asset")])],-1),R=t("tr",null,[t("td",null,"Color Adjustments / Color Correction"),t("td",null,[t("em",null,"via Volume asset")])],-1),v=t("tr",null,[t("td",null,"Depth Of Field"),t("td",null,[t("em",null,"via Volume asset")])],-1),k=t("tr",null,[t("td",null,"Vignette"),t("td",null,[t("em",null,"via Volume asset")])],-1),S=t("tr",null,[t("td",null,"ToneMappingEffect"),t("td",null,[t("em",null,"via Volume asset or separate component")])],-1),A=t("tr",null,[t("td",null,"Pixelation"),t("td")],-1),D=t("tr",null,[t("td",null,"Screenspace Ambient Occlusion N8"),t("td")],-1),C=t("tr",null,[t("td",null,"Screenspace Ambient Occlusion"),t("td")],-1),U=t("tr",null,[t("td",null,"Tilt Shift Effect"),t("td")],-1),j=t("tr",null,[t("td",null,"SharpeningEffect"),t("td")],-1),N=t("td",null,[t("em",null,"Your custom effect")],-1),I={href:"https://stackblitz.com/edit/needle-engine-custom-postprocessing-effect",target:"_blank",rel:"noopener noreferrer"},P=n('

Networking

NameDescription
SyncedRoomMain networking component. Put in your scene to enable networking
NetworkingUsed to setup backend server for networking.
SyncedTransformAutomatically network object transformation
SyncedCameraAutomatically network camera position and view to other users in room. You can define how the camera is being rendered by referencing an object
WebXRSyncNetworks WebXR avatars (AR and VR)
VoipEnables voice-chat
ScreensharingEnables screen-sharing capabilities

Interaction

NameDescription
EventSystemHandles raising pointer events and UI events on objects in the scene
ObjectRaycaterRequired for DragControls and Duplicatable
GraphicsRaycasterSame as ObjectRaycaster but for UI elements
DragControlsAllows objects to be dragged in the scene. Requires raycaster in parent hierarchy, e.g. ObjectRaycaster
DuplicatableCan duplicate assigned objects by drag. Requires DragControls
InteractableBasic component to mark an object to be interactable.
OrbitControlsAdd to camera to add camera orbit control functionality
SmoothFollowAllows to interpolate smoothly to another object's transform
DeleteBoxWill destroy objects with the Deletable component when entering the box
DeletableThe GameObject this component is attached to will be deleted when it enters or intersects with a DeleteBox
DropListenerAdd to receive file drop events for uploading
SpatialTriggerUse to raise event if an object enters a specific space or area. You can also use Physics events
SpatialTriggerReceiverUse to receive events from SpatialTrigger

Physics

',5),T={href:"https://rapier.rs/",target:"_blank",rel:"noopener noreferrer"},E=n('
NameDescription
RigidbodyAdd to make an object react to gravity (or be kinematic and static)
BoxColliderA Box collider shape that objects can collide with or raise trigger events when set to trigger
SphereColliderSee BoxCollider
CapsuleColliderSee BoxCollider
MeshColliderSee BoxCollider
Physics MaterialsPhysics materials can be used to define e.g. the bouncyness of a collider

XR / WebXR

',2),V=t("thead",null,[t("tr",null,[t("th",null,"Name"),t("th",null,"Description")])],-1),L=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),B=t("code",null,"USDZExporter",-1),M=t("td",null,"Add to enable USD and Quicklook support",-1),X=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),O=t("tr",null,[t("td",null,[t("code",null,"WebARSessionRoot")]),t("td",null,"Handles placement and scale of your scene in AR mode")],-1),W=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),F=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),H=t("tr",null,[t("td",null,[t("code",null,"WebXRPlaneTracking")]),t("td",null,"Create plane meshes or colliders for tracked planes")],-1),G=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),q=t("tr",null,[t("td",null,[t("code",null,"XRControllerMovement")]),t("td",null,"Can be added to provide default movement and teleport controls")],-1),z=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),Y=n('

Debugging

NameDescription
GridHelperDraws a grid
BoxGizmoDraws a box
AxesHelperDraws XYZ axes
Note: When you're writing custom code you can use the static Gizmos methods for drawing debugging lines and shapes

Runtime File Input/Output

NameDescription
GltfExportExperimental! Use to export gltf from web runtime.
DropListenerReceive file drop events for uploading and networking

UI

',5),Z={href:"https://github.com/felixmariotto/three-mesh-ui",target:"_blank",rel:"noopener noreferrer"},Q=n('
NameDescription
CanvasUnity'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)
ButtonReceives 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)
ImageRenders a sprite image
RawImageRenders a texture
InputFieldAllows 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).

Other

NameDescription
SceneSwitcherHandles loading and unloading of other scenes or prefabs / glTF files. Has features to preload, change scenes via swiping, keyboard events or URL navigation

Editor Only

NameDescription
ExportInfoMain component for managing the web project(s) to e.g. install or start the web app
EditorSyncAdd to enable networking material or component value changes to the running three.js app directly from the Unity Editor without having to reload
',6);function J(K,$){const d=i("ExternalLinkIcon"),r=i("RouterLink");return s(),c("div",null,[u,t("p",null,[e("For a complete list please have a look at our "),t("a",p,[e("API docs"),o(d)]),e(".")]),m,t("p",null,[e("Learn more in the "),o(r,{to:"/scripting.html"},{default:a(()=>[e("Scripting")]),_:1}),e(" section of our docs.")]),b,t("p",null,[e("Postprocessing effects use the "),t("a",g,[e("pmndrs postprocessing library"),o(d)]),e(" under the hood. This means you can also easily add your own custom effects and get an automatically optimized postprocessing pass.")]),f,t("table",null,[y,t("tbody",null,[_,w,x,R,v,k,S,A,D,C,U,j,t("tr",null,[N,t("td",null,[t("a",I,[e("See example on stackblitz"),o(d)])])])])]),P,t("p",null,[e("Physics is implemented using "),t("a",T,[e("Rapier"),o(d)]),e(".")]),E,t("p",null,[o(r,{to:"/xr.html"},{default:a(()=>[e("Read the XR docs")]),_:1})]),t("table",null,[V,t("tbody",null,[L,t("tr",null,[t("td",null,[o(r,{to:"/everywhere-actions.html"},{default:a(()=>[B]),_:1})]),M]),X,O,W,F,H,G,q,z])]),Y,t("p",null,[e("Spatial UI components are mapped from Unity UI (Canvas, not UI Toolkit) to "),t("a",Z,[e("three-mesh-ui"),o(d)]),e(". UI can be animated.")]),Q])}const et=l(h,[["render",J],["__file","component-reference.html.vue"]]);export{et as default}; diff --git a/assets/contribution-header-f6071865.js b/assets/contribution-header-f6071865.js new file mode 100644 index 000000000..ee31146e0 --- /dev/null +++ b/assets/contribution-header-f6071865.js @@ -0,0 +1 @@ +import{_ as s,p as i,q as n,R as t,v as r,Q as a,w as l,s as c}from"./framework-f6820c83.js";const d={class:"contribution"},u={class:"profile"},h=["src"],_=["href"],g={class:"links"},f=["href"],m={key:0,class:"title"},v={__name:"contribution-header",props:{title:String,url:String,page:String,author:String,profileImage:String,githubUrl:String,gradient:Boolean},setup(e){return(o,b)=>(i(),n("div",d,[t("div",{class:l(["header",e.gradient?"gradient":""])},[t("div",u,[t("img",{src:e.profileImage,alt:"profile image"},null,8,h),t("a",{class:"authorname",href:e.page},[t("span",null,r(e.author),1)],8,_)]),t("div",g,[e.githubUrl?(i(),n("a",{key:0,href:e.githubUrl,target:"_blank",rel:"noopener noreferrer"},"View on Github",8,f)):a("v-if",!0)])],2),e.title?(i(),n("div",m,[t("h2",null,r(e.title),1)])):a("v-if",!0),c(o.$slots,"default",{},void 0,!0)]))}},k=s(v,[["__scopeId","data-v-543822ed"],["__file","contribution-header.vue"]]);export{k as default}; diff --git a/assets/contribution-listentry-41a12262.js b/assets/contribution-listentry-41a12262.js new file mode 100644 index 000000000..683772d2a --- /dev/null +++ b/assets/contribution-listentry-41a12262.js @@ -0,0 +1 @@ +import{_ as e,p as n,q as r,R as s,v as c}from"./framework-f6820c83.js";const i=["href"],o={__name:"contribution-listentry",props:{title:String,url:String},setup(t){return(_,a)=>(n(),r("a",{class:"entry",href:t.url},[s("div",null,c(t.title),1)],8,i))}},u=e(o,[["__scopeId","data-v-bcc3d6f6"],["__file","contribution-listentry.vue"]]);export{u as default}; diff --git a/assets/contribution-preview-754df1c1.js b/assets/contribution-preview-754df1c1.js new file mode 100644 index 000000000..c9fd63f66 --- /dev/null +++ b/assets/contribution-preview-754df1c1.js @@ -0,0 +1 @@ +import{_ as s,p as c,q as r,R as t,v as a,s as _}from"./framework-f6820c83.js";const l={class:"title"},p={class:"content"},d={__name:"contribution-preview",props:{title:String,pageUrl:String},setup(e){const o=e;function n(){window.location.href=o.pageUrl}return(i,u)=>(c(),r("div",{class:"preview",onClick:n},[t("div",l,a(e.title),1),t("div",p,[_(i.$slots,"default",{},void 0,!0)])]))}},f=s(d,[["__scopeId","data-v-f801cb6e"],["__file","contribution-preview.vue"]]);export{f as default}; diff --git a/assets/contributions-author-f0bcc61a.js b/assets/contributions-author-f0bcc61a.js new file mode 100644 index 000000000..e56f243a3 --- /dev/null +++ b/assets/contributions-author-f0bcc61a.js @@ -0,0 +1 @@ +import{_ as s,M as a,p as o,q as r,Q as u,N as c,R as t,s as l,O as h}from"./framework-f6820c83.js";const d=["href"],g={class:"previews"},v=t("div",{class:"footer"},[t("a",{href:"https://github.com/needle-tools/needle-engine-support/discussions/new?category=share"},"Add your contribution")],-1),_={__name:"contributions-author",props:{name:String,url:String,githubUrl:String,profileImage:String,overviewLink:String},setup(e){return(n,m)=>{const i=a("contribution-header");return o(),r(h,null,[e.overviewLink?(o(),r("a",{key:0,href:e.overviewLink,class:"overview-link"},"โ—€ Overview",8,d)):u("v-if",!0),c(i,{profileImage:e.profileImage,author:e.name,githubUrl:e.githubUrl},null,8,["profileImage","author","githubUrl"]),t("div",g,[l(n.$slots,"default")]),v],64)}}},b=s(_,[["__file","contributions-author.vue"]]);export{b as default}; diff --git a/assets/contributions-overview-5f531929.js b/assets/contributions-overview-5f531929.js new file mode 100644 index 000000000..77f76fbfa --- /dev/null +++ b/assets/contributions-overview-5f531929.js @@ -0,0 +1 @@ +import{_ as t,p as s,q as o,s as n}from"./framework-f6820c83.js";const r={},_={class:"list"};function c(e,i){return s(),o("div",_,[n(e.$slots,"default")])}const l=t(r,[["render",c],["__file","contributions-overview.vue"]]);export{l as default}; diff --git a/assets/copyright-6b449b0c.js b/assets/copyright-6b449b0c.js new file mode 100644 index 000000000..bd8888609 --- /dev/null +++ b/assets/copyright-6b449b0c.js @@ -0,0 +1 @@ +import{_ as a,p as c,q as s,t as _,a9 as p,aa as r,R as t}from"./framework-f6820c83.js";const n={},o=e=>(p("data-v-682f5e2f"),e=e(),r(),e),d={class:"footer"},i=o(()=>t("a",{target:"_blank",href:"https://needle.tools/contact"},"About ยท ",-1)),l=o(()=>t("a",{target:"_blank",href:"https://needle.tools/contact#privacy-policy"},"Privacy Policy",-1));function f(e,h,u,y,v,g){return c(),s("div",d,[_(" ยฉ2024 Needle Tools GmbH ยท "),i,l])}const x=a(n,[["render",f],["__scopeId","data-v-682f5e2f"],["__file","copyright.vue"]]);export{x as default}; diff --git a/assets/custom-loading-style-65a23c0d.js b/assets/custom-loading-style-65a23c0d.js new file mode 100644 index 000000000..7aab91972 --- /dev/null +++ b/assets/custom-loading-style-65a23c0d.js @@ -0,0 +1 @@ +const s="/docs/imgs/custom-loading-style.webp";export{s as _}; diff --git a/assets/debugging.html-0370ea20.js b/assets/debugging.html-0370ea20.js new file mode 100644 index 000000000..69daf86f3 --- /dev/null +++ b/assets/debugging.html-0370ea20.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-f588dc38","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{e as data}; diff --git a/assets/debugging.html-b90d9481.js b/assets/debugging.html-b90d9481.js new file mode 100644 index 000000000..b570a4e0a --- /dev/null +++ b/assets/debugging.html-b90d9481.js @@ -0,0 +1,13 @@ +import{_ as l,M as a,p as i,q as d,R as e,t as o,N as n,V as c,a1 as s}from"./framework-f6820c83.js";const u="/docs/debugging/vscode-start-debugging.webp",p={},h=e("h2",{id:"useful-resources-for-working-with-gltf",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#useful-resources-for-working-with-gltf","aria-hidden":"true"},"#"),o(" Useful resources for working with glTF")],-1),g=e("p",null,"To inspect glTF or glb files online:",-1),b={href:"https://gltf.report/",target:"_blank",rel:"noopener noreferrer"},m={href:"https://modelviewer.dev/editor",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.khronos.org/glTF-Sample-Viewer-Release/",target:"_blank",rel:"noopener noreferrer"},f={href:"https://sandbox.babylonjs.com/",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.khronos.org/glTF-Validator/",target:"_blank",rel:"noopener noreferrer"},k=e("p",null,"To inspect them locally:",-1),w={href:"https://apps.microsoft.com/store/detail/gltf-shell-extensions/9NPGVJ9N57MV?hl=en-us&gl=US",target:"_blank",rel:"noopener noreferrer"},y={href:"https://marketplace.visualstudio.com/items?itemName=cesium.gltf-vscode",target:"_blank",rel:"noopener noreferrer"},q=s('

Built-in URL parameters

Debug Flags can be appended as URL query parameters.
Use ?help to get a list of ALL parameters available.

Here are some of the most commonly used:

  • help print all available url parameter in the console
  • console opens an on-screen dev console, useful for mobile debugging
  • printGltf logs loaded gltf files to the console
  • stats shows FPS module and logs threejs renderer stats every few seconds
  • showcolliders visualizes physics colliders
  • gizmos enables gizmo rendering (e.g. when using BoxCollider or AxesHelper components)
  • and a lot more: please use help to see them all

Debug Methods

',5),x=e("code",null,"Gizmos",-1),S=e("h2",{id:"local-testing-of-release-builds",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#local-testing-of-release-builds","aria-hidden":"true"},"#"),o(" Local Testing of release builds")],-1),V=s("
  • First, install http-server: npm install -g http-server
  • make a build (development or production)
  • open the dist directory with a commandline tool
  • run http-server -g | -g enables gzip support
  • ",4),F={href:"https://stackoverflow.com/a/35231213",target:"_blank",rel:"noopener noreferrer"},T=e("code",null,"http-server -g -S",-1),C=e("h2",{id:"vscode",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#vscode","aria-hidden":"true"},"#"),o(" VSCode")],-1),j={href:"https://code.visualstudio.com/docs/editor/debugging",target:"_blank",rel:"noopener noreferrer"},R=s(`

    Create a launch.json file at .vscode/launch.json in your web project with the following content:

    {
    +    "version": "0.2.0",
    +    "configurations": [
    +        {
    +            "type": "chrome",
    +            "request": "launch",
    +            "name": "Attach Chrome",
    +            "url": "https://localhost:3000",
    +            "webRoot": "\${workspaceFolder}"
    +        }
    +    ]
    +}
    +

    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:

    Mobile

    Android Debugging

    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.

    ',7),L={href:"https://developer.chrome.com/docs/devtools/remote-debugging/",target:"_blank",rel:"noopener noreferrer"},B={href:"https://developer.android.com/studio/debug/dev-options",target:"_blank",rel:"noopener noreferrer"},N=e("li",null,"Connect your phone to your computer via USB",-1),U=e("li",null,[o("Open this url in your browser "),e("code",null,"chrome://inspect/#devices")],-1),z=e("li",null,"On your mobile device allow the USB connection to your computer",-1),A=e("li",null,[o("On your computer in chrome you should see a list of open tabs after a while (on "),e("code",null,"chrome://inspect/#devices"),o(")")],-1),D=e("li",null,[o("Click "),e("code",null,"Inspect"),o(" on the tab you want to debug")],-1),M=e("h3",{id:"ios-debugging",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ios-debugging","aria-hidden":"true"},"#"),o(" iOS Debugging")],-1),E=e("p",null,[o("For easy iOS debugging add the "),e("code",null,"?console"),o(" URL parameter to get a useful on-screen JavaScript console.")],-1),O=e("p",null,"If you have a Mac, you can also attach to Safari (similar to the Android workflow above).",-1),I={href:"https://labs.mozilla.org/projects/webxr-viewer/",target:"_blank",rel:"noopener noreferrer"},W=e("h3",{id:"quest-debugging",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#quest-debugging","aria-hidden":"true"},"#"),o(" Quest Debugging")],-1),X=e("p",null,[o("Quest is just an Android device - see the "),e("a",{href:"#android-debugging"},"Android Debugging"),o(" section for steps.")],-1);function Y(G,P){const t=a("ExternalLinkIcon"),r=a("RouterLink");return i(),d("div",null,[h,g,e("ul",null,[e("li",null,[e("a",b,[o("gltf.report"),n(t)]),o(" - three.js based")]),e("li",null,[e("a",m,[o("modelviewer.dev/editor"),n(t)]),o(" - three.js based")]),e("li",null,[e("a",_,[o("Khronos glTF Sample Viewer"),n(t)])]),e("li",null,[e("a",f,[o("Babylon Sandbox"),n(t)])]),e("li",null,[e("a",v,[o("glTF Validator"),n(t)])])]),k,e("ul",null,[e("li",null,[o("use the "),e("a",w,[o("glTF Shell Extension for Windows"),n(t)]),o(" to convert between glTF and glb")]),e("li",null,[o("use the "),e("a",y,[o("glTF Tools VS Code Extension"),n(t)]),o(" to see validation errors and in-engine previews locally")])]),q,e("p",null,[o("Needle Engine also has some very powerful and useful debugging methods that are part of the static "),x,o(" class. See the "),n(r,{to:"/scripting.html#gizmos"},{default:c(()=>[o("scripting documentation")]),_:1}),o(" for more information.")]),S,e("ul",null,[V,e("li",null,[o("optional: if you want to test WebXR, generate a "),e("a",F,[o("self-signed SSL certificate"),n(t)]),o(", then run "),T,o(" to enable https (required for WebXR).")])]),C,e("p",null,[o("You can attach VSCode to the running local server to set breakpoints and debug your code. You can read more about "),e("a",j,[o("debugging with VSCode"),n(t)]),o(" here.")]),R,e("p",null,[o("See the official chrome documentation "),e("a",L,[o("here"),n(t)])]),e("ul",null,[e("li",null,[o("Make sure "),e("a",B,[o("Developer Mode"),n(t)]),o(" is enabled on your phone")]),N,U,z,A,D]),M,E,O,e("p",null,[o("WebXR usage and debugging on iOS requires using a third-party browser: "),e("a",I,[o("Mozilla WebXR Viewer"),n(t)]),o(".")]),W,X])}const J=l(p,[["render",Y],["__file","debugging.html.vue"]]);export{J as default}; diff --git a/assets/deployment.html-679a59a7.js b/assets/deployment.html-679a59a7.js new file mode 100644 index 000000000..5df6959b7 --- /dev/null +++ b/assets/deployment.html-679a59a7.js @@ -0,0 +1,5 @@ +import{_ as d}from"./texture-compression-8cd31165.js";import{_ as c}from"./ktx-env-variable-d006aea1.js";import{_ as p,M as s,p as h,q as u,R as e,t,N as o,V as m,a1 as n}from"./framework-f6820c83.js";const a="/docs/imgs/unity-texture-compression.jpg",g="/docs/imgs/unity-texture-compression-options.jpg",y="/docs/imgs/unity-mesh-compression-component.jpg",b="/docs/imgs/unity-mesh-simplification.jpg",f="/docs/imgs/unity-progressive-textures.jpg",_="/docs/imgs/unity-lods-settings-1.jpg",w="/docs/imgs/unity-lods-settings-2.jpg",v="/docs/deployment/deploytoglitch-1.jpg",x="/docs/deployment/deploytoglitch-2.jpg",k="/docs/blender/deploy_to_glitch.webp",T="/docs/deployment/deploytonetlify-2.jpg",j="/docs/deployment/deploytonetlify.jpg",D="/docs/deployment/deploytoftp.jpg",P="/docs/deployment/deploytoftp2.jpg",F="/docs/deployment/deploytoftp3.jpg",I="/docs/deployment/buildoptions_gzip.jpg",G="/docs/deployment/deploytogithubpages.jpg",U="/docs/deployment/deploytofacebookinstantgames.jpg",N="/docs/deployment/deploytofacebookinstantgames-hosting.jpg",B="/docs/deployment/deploytofacebookinstantgames-upload.jpg",C="/docs/deployment/facebookinstantgames-1.jpg",S="/docs/deployment/facebookinstantgames-2.jpg",A="/docs/deployment/facebookinstantgames-3.jpg",E="/docs/imgs/unity-build-window-menu.jpg",O="/docs/imgs/unity-build-window.jpg",z={},L=n('

    What does deployment mean?

    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.

    Available Deployment Targets

    ',4),H={class:"custom-container tip"},M=e("p",{class:"custom-container-title"},"Feel something is missing?",-1),W={href:"https://discord.needle.tools",target:"_blank",rel:"noopener noreferrer"},q=e("h2",{id:"development-builds",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#development-builds","aria-hidden":"true"},"#"),t(" Development Builds")],-1),Y=e("p",null,"See guides above on how to access the options from within your Editor (e.g. Unity or Blender).",-1),K={href:"https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html",target:"_blank",rel:"noopener noreferrer"},R={href:"https://google.github.io/draco/",target:"_blank",rel:"noopener noreferrer"},V=e("p",null,"We generally recommend making production builds for optimized file size and loading speed (see more information below).",-1),X=e("h2",{id:"production-builds",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#production-builds","aria-hidden":"true"},"#"),t(" Production Builds")],-1),J={href:"https://github.com/KhronosGroup/KTX-Software/releases",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/KhronosGroup/KTX-Software/releases",target:"_blank",rel:"noopener noreferrer"},Z=e("br",null,null,-1),$=e("em",null,"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.",-1),ee={class:"custom-container details"},te=e("summary",null,"Advanced: Custom glTF extensions",-1),oe=e("code",null,"gltf-transform",-1),ie={href:"https://www.npmjs.com/package/@needle-tools/gltf-build-pipeline",target:"_blank",rel:"noopener noreferrer"},ne=n('

    Optimization and Compression Options

    Texture compression

    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.

    How do I choose between ETC1S, UASTC and WebP compression?

    FormatETC1SUASTCWebP
    GPU Memory UsageLowLowHigh (uncompressed)
    File SizeLowHighVery low
    QualityMediumVery highDepends on quality setting
    Typical usageWorks for everything, but best for color texturesHigh-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?

    image
    image

    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.
    Texture Compression options in Blender

    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
    image

    Mesh compression

    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.

    How do I choose between Draco and Meshopt?

    FormatDracoMeshopt
    GPU Memory UsageMediumLow
    File SizeLowestLow
    Animation compressionNoYes
    How can I set draco and meshopt compression settings?

    Add the MeshCompression component to select which compression should be applied per exported glTF.

    image

    • To change compression for the current scene just add it anywhere in your root scene.
    • To change compression for a prefab or NestedGltf add it to a GltfObject or the prefab that is referenced / exported by any of your components.
    • To change compression for a referenced scene just add it to the referenced scene that is exported
    Where to find mesh simplification options to reduce the vertex count when building for production?

    Select a Mesh and open the Needle importer options to see available options for the selected mesh:
    image

    Progressive Textures

    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.

    How can I enable progressive texture loading?

    Progressive textures can be enabled per texture
    or for all textures in your project:

    image

    Enable for all textures in the project that don't have any other specific setting:

    image

    Automatic Mesh LODs (Level of Detail)

    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.

    image

    image

    Deployment Options

    Deploy to Glitch ๐ŸŽ

    ',28),se={href:"https://glitch.com/",target:"_blank",rel:"noopener noreferrer"},ae=n('

    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?
    1. Add the DeployToGlitch component to the GameObject that also has the ExportInfo component.

    2. Click the Create new Glitch Remix button on the component image

    3. Glitch will now create a remix of the template. Copy the URL from your browser
      image

    4. Open Unity again and paste the URL in the Project Name field of your Deploy To Glitch component
      image

    5. 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)
      waiting for the key

    6. 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?

    Deploy To Glitch from Blender component

    1. Find the Deploy To Glitch panel in the Scene tab
    2. Click the Remix on glitch button on the component
    3. Your browser will open the glitch project template
    4. Wait for Glitch to generate a new project
    5. 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)
    6. On Glitch open the .env file and enter a password in the field Variable Value next to the DEPLOY_KEY
    7. Enter the same password in Blender in the Key field
    8. 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.

    Troubleshooting Glitch

    ',5),re=e("code",null,"Create new Glitch Remix",-1),le=e("code",null,"there was an error starting the editor",-1),de=e("strong",null,"OK",-1),ce={href:"https://glitch.com/",target:"_blank",rel:"noopener noreferrer"},pe=n('

    Deploy to Netlify

    How do I deploy to Netlify from Unity?

    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.

    Deploy to netlify component

    Deploy to netlify component

    Deploy to Vercel

    1. Create a new project on vercel
    2. Add your web project to a github repository
    3. Add the repository to your project on vercel
    ',4),he={href:"https://github.com/needle-engine/nextjs-sample",target:"_blank",rel:"noopener noreferrer"},ue=e("h3",{id:"deploy-to-itch.io",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#deploy-to-itch.io","aria-hidden":"true"},"#"),t(" Deploy to itch.io")],-1),me={class:"custom-container details"},ge=e("summary",null,"How do I deploy to itch.io from Unity?",-1),ye={href:"https://itch.io/game/new",target:"_blank",rel:"noopener noreferrer"},be=n('
  • Set Kind of project to HTML
    image

  • Add the DeployToItch component to your scene and click the Build button
    image

  • Wait for the build to finish, it will open a folder with the final zip when it has finished

  • Upload to final zip to itch.io
    20220920-104629_Create_a_new_project_-itch io-_Google_Chrome-needle

  • Select This file will be played in the browser
    image

  • Save your itch page and view the itch project page.
    It should now load your Needle Engine project ๐Ÿ˜Š

  • ',6),fe=e("h4",{id:"optional-settings",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#optional-settings","aria-hidden":"true"},"#"),t(" Optional settings")],-1),_e=e("p",null,[e("img",{src:"https://user-images.githubusercontent.com/5083203/191217263-355d9b72-5431-4170-8eca-bfbbb39ae810.png",alt:"image"})],-1),we={class:"custom-container details"},ve=e("summary",null,"Itch.io: failed to find index.html",-1),xe={id:"failed-to-find-index.html",tabindex:"-1"},ke=e("p",null,[e("img",{src:"https://user-images.githubusercontent.com/5083203/191213162-2be63e46-2a65-4d41-a713-98c753ccb600.png",alt:"image"}),e("br"),t(" 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 "),e("code",null,"vite.config.js"),t(" in your Needle web project folder. Just remove the line with "),e("code",null,"viteCompression({ deleteOriginFile: true })"),t(". The build your project again and upload to itch.")],-1),Te=n('

    Deploy to FTP

    How do I deploy to my FTP server from Unity?
    1. 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)
    2. 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
    3. Click the Build & Deploy button on the DeployToFTP component to build your project and uploading it to your FTP account

    Deploy to FTP component in Unity
    ยน Deploy to FTP component

    Deploy to FTP server asset
    ยฒ FTP Server asset containing the access information of your FTP user account

    Deploy to FTP component in Unity with server asset assigned
    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?
    1. Open File > Build Settings, select Needle Engine, and click on Build
    2. Wait for the build to complete - the resulting dist folder will open automatically after all build and compression steps have run.
    3. Copy the files from the dist folder to your FTP storage.

    That's it! ๐Ÿ˜‰

    20220830-003602_explorer-needle

    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.

    Unity build window showing Needle Engine platform

    Enabling gzip using a .htaccess file

    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:

    <IfModule mod_mime.c>
    +RemoveType .gz
    +AddEncoding gzip .gz
    +AddType application/javascript .js.gz
    +

    Deploy to Github Pages

    `,7),je={class:"custom-container details"},De=e("summary",null,"How do I deploy to Github Pages from Unity?",-1),Pe=e("p",null,[t("Add the DeployToGithubPages component to your scene and copy-paste the github repository (or github pages url) that you want to deploy to."),e("br"),e("img",{src:G,alt:"Deploy To github pages component"})],-1),Fe=n('

    Deploy to Facebook Instant Games

    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: Deploy to facebook instant games component
    • 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 hostingHosting a facebook instant games
    • 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) Upload the zip to facebook instant games
    • That's it - you can then click the Play button next to each version to test your game on facebook.
    ',3),Ie={class:"custom-container details"},Ge=e("summary",null,"How do I create a app on Facebook (with Instant Games capabilities)",-1),Ue={href:"https://developers.facebook.com/apps/creation/",target:"_blank",rel:"noopener noreferrer"},Ne=e("code",null,"Other",-1),Be=e("code",null,"Next",-1),Ce=e("img",{src:C,alt:"Create facebook instant games app"},null,-1),Se=e("li",null,[e("p",null,[t("Select type "),e("code",null,"Instant Games"),e("img",{src:S,alt:"Create facebook instant games app"})])],-1),Ae=e("li",null,[e("p",null,[t("After creating the app add the "),e("code",null,"Instant Games"),t(" product "),e("img",{src:A,alt:"Add instant games product"})])],-1),Ee={href:"https://developers.facebook.com/docs/games/build/instant-games",target:"_blank",rel:"noopener noreferrer"},Oe=e("br",null,null,-1),ze=e("strong",null,"Note",-1),Le=e("br",null,null,-1),He=n('

    Build To Folder

    In Unity open File/Build Settings and select Needle Engine for options:

    image

    image

    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).


    Cross-Platform Deployment Workflows

    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:
    image

    Needle Engine Commandline Arguments for Unity

    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:

    -scenepath 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)
    -buildProductionrun a production build
    -buildDevelopmentrun a development build
    -debugopen a console window for debugging
    ',15);function Me(We,qe){const i=s("ExternalLinkIcon"),r=s("RouterLink"),l=s("video-embed");return h(),u("div",null,[L,e("div",H,[M,e("p",null,[t("Please let us know in our "),e("a",W,[t("discord"),o(i)]),t("!")])]),q,Y,e("p",null,[t("The main difference to a production build is that it does not perform "),e("a",K,[t("ktx2"),o(i)]),t(" and "),e("a",R,[t("draco"),o(i)]),t(" compression (for reduction of file size and loading speed) as well as the option to progressively load high-quality textures.")]),V,X,e("p",null,[t("To make a production build, you need to have "),e("a",J,[t("toktx"),o(i)]),t(" installed, which provides texture compression using the KTX2 supercompression format. Please go to the "),e("a",Q,[t("toktx Releases Page"),o(i)]),t(" and download and install the latest version (v4.1.0 at the time of writing). You may need to restart Unity after installing it."),Z,$]),e("details",ee,[te,e("p",null,[t("If you plan on adding your own custom glTF extensions, building for production requires handling those in "),oe,t(". See "),e("a",ie,[t("@needle-tools/gltf-build-pipeline"),o(i)]),t(" for reference.")])]),ne,e("p",null,[e("a",se,[t("Glitch"),o(i)]),t(" 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.")]),ae,e("p",null,[t("If you click "),re,t(" and the browser shows an error like "),le,t(" you can click "),de,t(". Then go to "),e("a",ce,[t("glitch.com"),o(i)]),t(" and make sure you are signed in. After that you then try clicking the button again in Unity or Blender.")]),pe,e("p",null,[t("See our "),e("a",he,[t("sample project"),o(i)]),t(" for the project configuration")]),ue,e("details",me,[ge,e("ol",null,[e("li",null,[e("p",null,[t("Create a new project on "),e("a",ye,[t("itch.io"),o(i)])])]),be]),fe,_e]),e("details",we,[ve,e("h4",xe,[o(r,{class:"header-anchor",to:"/#failed-to-find-index.html","aria-hidden":"true"},{default:m(()=>[t("#")]),_:1}),t(" Failed to find index.html")]),ke]),Te,e("details",je,[De,Pe,o(l,{src:"https://www.youtube.com/watch?v=Vyk3cWB6u-c"})]),Fe,e("details",Ie,[Ge,e("ol",null,[e("li",null,[e("p",null,[e("a",Ue,[t("Create a new app"),o(i)]),t(" and select "),Ne,t(". Then click "),Be,Ce])]),Se,Ae]),e("p",null,[t("Here you can find "),e("a",Ee,[t("the official instant games documentation"),o(i)]),t(" on facebook."),Oe,ze,t(" that all you have to do is to create an app with instant games capabilities."),Le,t(" We will take care of everything else and no manual adjustments to your Needle Engine website are required.")])]),He])}const Ve=p(z,[["render",Me],["__file","deployment.html.vue"]]);export{Ve as default}; diff --git a/assets/deployment.html-a77d8c0c.js b/assets/deployment.html-a77d8c0c.js new file mode 100644 index 000000000..f1f63902c --- /dev/null +++ b/assets/deployment.html-a77d8c0c.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4d21151b","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{e as data}; diff --git a/assets/docsearch-1d421ddb.js b/assets/docsearch-1d421ddb.js new file mode 100644 index 000000000..182023d2f --- /dev/null +++ b/assets/docsearch-1d421ddb.js @@ -0,0 +1,2 @@ +const i=`@media (min-width: 751px){#docsearch-container{min-width:171.36px}}@media (max-width: 750px){.DocSearch-Container{position:fixed}#docsearch-container{min-width:52px}}@media print{#docsearch-container{display:none}} +`;export{i as default}; diff --git a/assets/everywhere-actions.html-440cc1d9.js b/assets/everywhere-actions.html-440cc1d9.js new file mode 100644 index 000000000..6801714c7 --- /dev/null +++ b/assets/everywhere-actions.html-440cc1d9.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-f2e4a738","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":1718696909000},"filePathRelative":"everywhere-actions.md"}`);export{e as data}; diff --git a/assets/everywhere-actions.html-7590cd35.js b/assets/everywhere-actions.html-7590cd35.js new file mode 100644 index 000000000..d022df9fb --- /dev/null +++ b/assets/everywhere-actions.html-7590cd35.js @@ -0,0 +1,23 @@ +import{_ as c,M as o,p as d,q as l,N as n,R as e,t,V as p,a1 as i}from"./framework-f6820c83.js";const u="/docs/imgs/everywhere-actions-component-menu.gif",h={},m=i('

    What are Everywhere Actions?

    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.

    Supported Platforms

    • Desktop
    • Mobile (Android / iOS)
    • VR Glasses
    • AR Devices
    • iOS AR โ€“ QuickLook (yes, really!)

    How do I use Everywhere Actions?

    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].

    List of Everywhere Actions

    ActionDescriptionExample Use Cases
    Play Animation on ClickPlays a selected animation state from an Animator. After playing, it can optionally transition to another animation.Product presentations, interactive tutorials, character movement
    Change Material on ClickSwitch out one material for others. All objects with that material will be switched together.Product configurators, characters
    Look AtMake an object look at the camera.UI elements, sprites, info graphics, billboard effects, clickable hotspots
    Play Audio on ClickPlays a selected audio clip.Sound effects, Narration, Museum exhibits
    Hide on StartHides an object at scene start for later reveal.
    Set Active on ClickShow or hide objects.
    Change Transform on ClickMove, rotate or scale an object. Allows for absolute or relative movement.Characters, products, UI animation (use animation for more complex movements)
    Audio SourcePlays audio on start and keeps looping. Spatial or non-spatialBackground music, ambient sounds
    WebXR Image TrackingTracks an image target and shows or hides objects.AR experiences, product presentations

    Samples

    Musical Instrument

    Demonstrates spatial audio, animation, and interactions.

    ',14),k=e("h3",{id:"simple-character-controllers",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#simple-character-controllers","aria-hidden":"true"},"#"),t(" Simple Character Controllers")],-1),b=e("p",null,"Demonstrates combining animations, look at, and movement.",-1),v=e("h3",{id:"image-tracking",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#image-tracking","aria-hidden":"true"},"#"),t(" Image Tracking")],-1),g=e("p",null,"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.",-1),f=e("img",{src:"https://engine.needle.tools/samples-uploads/image-tracking/assets/needle-marker.png",alt:"Image Marker",width:"300"},null,-1),w=e("p",null,[e("a",{href:"https://engine.needle.tools/samples-uploads/image-tracking/assets/needle-marker.png",target:"_blank"},"Download Sample Image Marker")],-1),y=e("strong",null,"On Android:",-1),A=e("h3",{id:"interactive-building-blocks-overview",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#interactive-building-blocks-overview","aria-hidden":"true"},"#"),t(" Interactive Building Blocks Overview")],-1),_=e("h2",{id:"create-your-own-everywhere-actions",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#create-your-own-everywhere-actions","aria-hidden":"true"},"#"),t(" Create your own Everywhere Actions")],-1),x=e("p",null,"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.",-1),S=e("div",{class:"custom-container tip"},[e("p",{class:"custom-container-title"},"TIP"),e("p",null,'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.')],-1),B={href:"https://developer.apple.com/documentation/arkit/usdz_schemas_for_ar/actions_and_triggers",target:"_blank",rel:"noopener noreferrer"},C=i(`

    Code Example

    Here's the implementation for HideOnStart as an example for how to create an Everywhere Action with implementations for both the browser and QuickLook:

    export class HideOnStart extends Behaviour implements UsdzBehaviour {
    +
    +    start() {
    +        this.gameObject.visible = false;
    +    }
    +
    +    createBehaviours(ext, model, _context) {
    +        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;
    +    }
    +}
    +

    TIP

    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.

    Low level methods for building your own actions

    Triggers
    TriggerBuilder.sceneStartTrigger
    TriggerBuilder.tapTrigger
    Actions
    ActionBuilder.fadeAction
    ActionBuilder.startAnimationAction
    ActionBuilder.waitAction
    ActionBuilder.lookAtCameraAction
    ActionBuilder.emphasize
    ActionBuilder.transformAction
    ActionBuilder.playAudioAction
    Group Actions
    ActionBuilder.sequence
    ActionBuilder.parallel
    GroupAction.addAction
    GroupAction.makeParallel
    GroupAction.makeSequence
    GroupAction.makeLooping
    GroupAction.makeRepeat

    To see the implementation of our built-in Everywhere Actions, please take look at src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts.

    Further reading

    The following pages provide more examples and samples that you can test and explore right now:

    `,11),T={href:"https://engine.needle.tools/projects/ar-showcase/",target:"_blank",rel:"noopener noreferrer"},R={href:"https://engine.needle.tools/samples/?overlay=samples&tag=everywhere+actions",target:"_blank",rel:"noopener noreferrer"};function j(O,E){const a=o("sample"),r=o("RouterLink"),s=o("ExternalLinkIcon");return d(),l("div",null,[m,n(a,{src:"https://engine.needle.tools/samples-uploads/musical-instrument"}),k,b,n(a,{src:"https://engine.needle.tools/samples-uploads/usdz-characters"}),v,g,f,w,e("p",null,[y,t(' please turn on "WebXR Incubations" in the Chrome Flags. You can find those by pasting '),n(r,{to:"/chrome:/flags/#webxr-incubations"},{default:p(()=>[t("chrome://flags/#webxr-incubations")]),_:1}),t(" into the Chrome browser address bar of your Android phone.")]),n(a,{src:"https://engine.needle.tools/samples-uploads/image-tracking"}),A,n(a,{src:"https://engine.needle.tools/samples-uploads/usdz-interactivity"}),_,x,S,e("p",null,[t("Triggers and Actions for QuickLook are based on "),e("a",B,[t("Apple's Preliminary Interactive USD Schemas"),n(s)])]),C,e("ul",null,[e("li",null,[t("Visit our "),e("a",T,[t("AR Showcase Website"),n(s)]),t(" that has many interactive AR examples with a focus on iOS AR & Quicklook")]),e("li",null,[e("a",R,[t("Needle Engine Everywhere Action Samples"),n(s)])])])])}const L=c(h,[["render",j],["__file","everywhere-actions.html.vue"]]);export{L as default}; diff --git a/assets/examples.html-1d035fb3.js b/assets/examples.html-1d035fb3.js new file mode 100644 index 000000000..f97e37bf8 --- /dev/null +++ b/assets/examples.html-1d035fb3.js @@ -0,0 +1 @@ +import{_ as a,M as r,p as s,q as d,R as e,t,N as o,a1 as l}from"./framework-f6820c83.js";const i={},c=l('

    Example Projects โœจ

    Explore some real world applications, websites and demos made with Needle Engine.

    Get started now โ€ข Learn more about our vision โ€ข Features Overview โ€ข Samples to download

    Needle Website

    Visit Website โ€” by Needle

    https://user-images.githubusercontent.com/5083203/186126996-27b45c5f-f3b9-40f7-b8c7-6ecba1d25a6e.mp4

    Castle Builder

    ',7),h={href:"https://castle.needle.tools",target:"_blank",rel:"noopener noreferrer"},p=e("p",null,"https://user-images.githubusercontent.com/5083203/186145731-705cfec2-1779-4a0b-97d9-95f3edaaf2d0.mp4",-1),u=e("h2",{id:"bike-configurator",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#bike-configurator","aria-hidden":"true"},"#"),t(" Bike Configurator")],-1),f={href:"https://bike.needle.tools",target:"_blank",rel:"noopener noreferrer"},_=e("p",null,"https://user-images.githubusercontent.com/5083203/186146814-52fb05c7-a073-4efa-a226-47a9c1835413.mp4",-1),b=e("h2",{id:"sandbox-template",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#sandbox-template","aria-hidden":"true"},"#"),t(" Sandbox Template")],-1),m={href:"https://fwd.needle.tools/needle-engine/glitch-starter",target:"_blank",rel:"noopener noreferrer"},g=e("p",null,"https://user-images.githubusercontent.com/5083203/186149117-ca7cf22f-dc7d-4c74-86d4-d78fe53a208c.mp4",-1),x=e("h2",{id:"songs-of-cultures",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#songs-of-cultures","aria-hidden":"true"},"#"),t(" Songs of Cultures")],-1),k={href:"https://fwd.needle.tools/needle-engine/projects/songs-of-cultures",target:"_blank",rel:"noopener noreferrer"},y=e("p",null,"https://user-images.githubusercontent.com/5083203/186147814-159a33f9-f1a6-47d4-804f-5f8f5a63125d.mp4",-1),w=e("h2",{id:"pok-mon-card",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pok-mon-card","aria-hidden":"true"},"#"),t(" Pokรฉmon Card")],-1),N={href:"https://fwd.needle.tools/needle-engine/projects/pokemon-card",target:"_blank",rel:"noopener noreferrer"},S={href:"https://alexanderameye.github.io/notes/holographic-card-shader/",target:"_blank",rel:"noopener noreferrer"},v=e("p",null,"https://user-images.githubusercontent.com/5083203/186149736-49a697b3-4282-4b71-ab13-a6b176955c13.mp4",-1),E=e("h2",{id:"encryption-in-space",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#encryption-in-space","aria-hidden":"true"},"#"),t(" Encryption in Space")],-1),P={href:"https://fwd.needle.tools/needle-engine/projects/encryption",target:"_blank",rel:"noopener noreferrer"},B=e("p",null,"https://user-images.githubusercontent.com/5083203/186151157-0c0a7d05-ad42-44be-b553-8d4cd48cbb81.mp4",-1),C=e("h2",{id:"physics-playground",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#physics-playground","aria-hidden":"true"},"#"),t(" Physics Playground")],-1),j={href:"https://bruno-simon-20k-needle.glitch.me/",target:"_blank",rel:"noopener noreferrer"},V=e("p",null,"https://user-images.githubusercontent.com/5083203/186149536-987ee796-3fe0-42bc-bd80-4c25aaf174aa.mp4",-1);function A(L,T){const n=r("ExternalLinkIcon");return s(),d("div",null,[c,e("p",null,[e("a",h,[t("Play Now โ‡ก"),o(n)]),t(" โ€” by Needle")]),p,u,e("p",null,[e("a",f,[t("Bike Configurator โ‡ก"),o(n)]),t(" โ€” by Needle")]),_,b,e("p",null,[e("a",m,[t("Sandbox Template โ‡ก"),o(n)]),t(" โ€” by Needle")]),g,x,e("p",null,[e("a",k,[t("Songs of Cultures โ‡ก"),o(n)]),t(" โ€” by A.MUSE")]),y,w,e("p",null,[e("a",N,[t("Pokรฉmon Card โ‡ก"),o(n)]),t(" โ€” Scene from Alex Ameye โ€ข "),e("a",S,[t("Original Blog Post by Alex โ‡ก"),o(n)])]),v,E,e("p",null,[e("a",P,[t("Encryption in Space โ‡ก"),o(n)]),t(" โ€” by Katja Rempel & Nick Jwu")]),B,C,e("p",null,[e("a",j,[t("Physics Playground โ‡ก"),o(n)]),t(" โ€” Scene from Bruno Simon")]),V])}const M=a(i,[["render",A],["__file","examples.html.vue"]]);export{M as default}; diff --git a/assets/examples.html-b9380b47.js b/assets/examples.html-b9380b47.js new file mode 100644 index 000000000..fac33e819 --- /dev/null +++ b/assets/examples.html-b9380b47.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-63f25852","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":1724755401000},"filePathRelative":"examples.md"}');export{e as data}; diff --git a/assets/export.html-4f710d49.js b/assets/export.html-4f710d49.js new file mode 100644 index 000000000..53c209bb6 --- /dev/null +++ b/assets/export.html-4f710d49.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-99bff5e8","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":1724755401000},"filePathRelative":"export.md"}`);export{e as data}; diff --git a/assets/export.html-a783fce9.js b/assets/export.html-a783fce9.js new file mode 100644 index 000000000..5c82b821d --- /dev/null +++ b/assets/export.html-a783fce9.js @@ -0,0 +1,26 @@ +import{_ as r,M as o,p as l,q as c,R as t,t as e,N as a,V as d,a1 as s}from"./framework-f6820c83.js";const p={},h=s('

    Exporting Assets, Animations, Prefabs, Materials, Lightmaps...

    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.

    ๐Ÿ“ฆ Exporting glTF files

    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.

    Lazy loading and multiple levels / scenes

    ',10),u=t("code",null,"SceneSwitcher",-1),m={href:"https://needle.tools?utm_source=needle_docs&utm_content=export_scenes",target:"_blank",rel:"noopener noreferrer"},g=t("h3",{id:"recommended-complexity-per-gltf",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#recommended-complexity-per-gltf","aria-hidden":"true"},"#"),e(" Recommended Complexity per glTF")],-1),b=t("ul",null,[t("li",null,"Max. 50 MB export size uncompressed (usually ends up ~10-20 MB compressed)"),t("li",null,"Max. 500k vertices (less if you target mobile VR as well)"),t("li",null,"Max. 4x 2k lightmaps")],-1),f=t("p",null,"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.",-1),y=t("h3",{id:"prefabs",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#prefabs","aria-hidden":"true"},"#"),e(" Prefabs")],-1),x={href:"https://fwd.needle.tools/needle-engine/docs/addressables",target:"_blank",rel:"noopener noreferrer"},v=s('

    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.

    Scene Assets

    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.)

    ',4),k={href:"https://needle.tools?utm_source=needle_docs&utm_content=export_sceneassets",target:"_blank",rel:"noopener noreferrer"},w=s(`

    2022-08-22-172605_Needle_Website_-Website-_Windows,_Mac,Linux-_U

    Loading a Prefab or Scene from a custom script

    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
    +    }  
    +} 
    +

    ๐Ÿ‡ Exporting Animations

    Needle Engine supports a considerable and powerful subset of Unity's animation features:

    • Timeline incl. activation tracks, animation tracks, track offsets
    • Animator incl. top-level state transitions
      • Blend trees are currently not supported.
      • Sub state machines are currently not supported.
    • AnimationClips incl. Loop modes
    • Procedural Animations can be created via scripting
    `,7),_={href:"https://github.com/ux3d/glTF/tree/extensions/KHR_animation_pointer/extensions/2.0/Khronos/KHR_animation_pointer",target:"_blank",rel:"noopener noreferrer"},T=t("br",null,null,-1),S=s('

    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.

    ๐ŸŒ Exporting the Skybox

    The Unity skybox and custom reflection (if any) are baked into a texture on export and automatically exported inside the NEEDLE_lightmaps extension.

    To change the skybox resolution you can add a SkyboxExportSettings component to your scene.

    image

    If you don't want to skybox to be exported at all in a glb file you can untick the Embed Skybox option on your GltfObject component

    image

    โœจ Exporting Materials

    Physically Based Materials (PBR)

    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.

    Custom Shaders

    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

    2022-08-22-172029_Needle_Website_-CustomShaders-_Windows,_Mac,_Lin

    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).

    Current limitations

    • 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).

    ๐Ÿ’ก Exporting Lightmaps

    2022-08-22-171650_Needle_-_Google_Chrome

    ',25),j={href:"https://docs.unity3d.com/Manual/Lightmapping.html",target:"_blank",rel:"noopener noreferrer"},A=s(`

    When working on multiple scenes, disable "Auto Generate" and bake lightmaps explicitly. Otherwise, Unity will discard temporary lightmaps on scene change.

    • Lightmap Encoding: Normal Quality (adjust in Project Settings > Player)
    • Progressive GPU (faster and usually accurate enough for small scenes)
    • Non-Directional Lightmaps
    • Max Lightmap Size 2k (you can go higher, but expect large files)
    • Max 4x 2k lightmaps per scene (you can go higher, but expect large files)
    • Compress Lightmaps OFF (increases quality; otherwise will be compressed again at export time)

    2022-08-22-171356_Needle_Website_-Lightmaps-_Windows,_Mac,Linux-

    Mixing Baked and Non-Baked Objects

    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+
    20220826-175324-SqBL-Unity_pMXa-needle

    2020.3+
    20220826-175514-tnGc-Unity_mycs-needle

    If you have no baked objects in your scene, then the following settings should also yield correct results:

    Environment Lighting: Color
    +Ambient Color: any
    +
    `,12);function L(E,P){const n=o("ExternalLinkIcon"),i=o("RouterLink");return l(),c("div",null,[h,t("p",null,[e("If you want to split up your application into multiple levels or scenes then you can simply use the "),u,e(" 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 "),t("a",m,[e("needle.tools"),a(n)]),e(" by separating each section of your website into its own scene and only loading them when necessary)")]),g,b,t("p",null,[e("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 "),a(i,{to:"/scripting.html#assetreference-and-addressables"},{default:d(()=>[e("AssetReference section in the Scripting docs")]),_:1}),e(".")]),f,y,t("p",null,[e("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) "),t("a",x,[e("from one of your scripts"),a(n)]),e(".")]),v,t("p",null,[e("As an example on "),t("a",k,[e("our website"),a(n)]),e(" each section is setup as a separate scene and on export packed into multiple glb files that we load on demand:")]),w,t("p",null,[e("Needle Engine is one of the first to support the new "),t("a",_,[e("glTF extension KHR_ANIMATION_POINTER"),a(n)]),e("."),T,e(" This means that almost all properties, including script variables, are animatable.")]),S,t("p",null,[e("To export lightmaps simply "),t("a",j,[e("generate lightmaps"),a(n)]),e(" in Unity. Lightmaps will be automatically exported.")]),A])}const U=r(p,[["render",L],["__file","export.html.vue"]]);export{U as default}; diff --git a/assets/faq.html-e7435103.js b/assets/faq.html-e7435103.js new file mode 100644 index 000000000..83f0db755 --- /dev/null +++ b/assets/faq.html-e7435103.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-092a1d7c","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":1724755401000},"filePathRelative":"faq.md"}`);export{e as data}; diff --git a/assets/faq.html-f846be88.js b/assets/faq.html-f846be88.js new file mode 100644 index 000000000..cf09e04f6 --- /dev/null +++ b/assets/faq.html-f846be88.js @@ -0,0 +1,16 @@ +import{_ as l}from"./ktx-env-variable-d006aea1.js";import{_ as c,M as r,p as d,q as h,R as e,t as n,N as t,V as i,a1 as o}from"./framework-f6820c83.js";const p="/docs/imgs/unity-needle-engine-license.jpg",u="/docs/faq/lightmap_encoding.jpg",m={},g=o('

    I purchased a license - how can I activate my Needle Engine License?

    Activating the license in Unity

    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

    unity license window

    Activating the license in Blender

    Open Addon Preferences/Needle Engine to get to the Needle Engine addon settings

    • Email - Enter the email you purchased the license with
    • Invoice ID - Enter one of the invoice ids that you received by email

    My objects are white after export

    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.

    • Read more about recommended glTF workflows:
    • Read more about custom shaders:

    There's a SSL error when opening the local website

    This is expected. We're enforcing HTTPS to make sure that WebXR and other modern web APIs work out-of-the-box, but that means some browsers complain that the SSL connection (between your local development server and the local website) can't be verified. It also prevents Automatic Reload from working on iOS and MacOS.

    ',13),f=o(`

    My local website stays black

    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.

    How to fix Uncaught ReferenceError: NEEDLE_ENGINE_META is not defined / NEEDLE_USE_RAPIER is not defined

    If you are using vite or next.js make sure to add the Needle Engine plugins to your config. Example for vite:

    const { needlePlugins } = await import('@needle-tools/engine/plugins/vite/index.js');
    +plugins: [needlePlugins(command, needleConfig)]
    +

    Example for next.js

    const { needleNext } = await import("@needle-tools/engine/plugins/next/index.js");
    +return needleNext({}, { modules: { webpack } });
    +

    You can also just declare the missing variables in e.g. your root index.html in a script tag like so:

    <script>
    +  var NEEDLE_ENGINE_META = {}
    +  var NEEDLE_USE_RAPIER = true;
    +</script>
    +

    THREE.EXRLoader: provided file doesnt appear to be in OpenEXR format

    Please make sure that sure that you have set Lightmap Encoding to Normal Quality.
    Go to Edit/Project Settings/Player for changing the setting.

    My website becomes too large / is loading slow (too many MB)

    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),b=e("strong",null,"try to split up your content into multiple glb files",-1),y=o('

    My UI is not rendering Text

    • For Unity: Make sure that you use the UI/Legacy/Text component and not the TextMeshPro - Text component

    My scripts don't work after export

    • Your existing C# code will not export as-is, you have to write matching typescript / javascript for it.
    • Needle uses typescript / javascript for components and generates C# stubs for them.
    • Components that already have matching JS will show that in the Inspector.

    My lightmaps look different / too bright

    ',5),k={href:"https://docs.needle.tools/lightmaps?utm_source=needle_docs",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/needle-tools/needle-engine-support/blob/main/documentation/export.md#mixing-baked-and-non-baked-objects",target:"_blank",rel:"noopener noreferrer"},v=o('

    My scene is too bright / lighting looks different than in Unity

    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
      image
    • Light shadow intensity can currently not be changed due to a three.js limitation.
    ',3),x={href:"https://github.com/needle-tools/needle-engine-support/blob/main/documentation/export.md#mixing-baked-and-non-baked-objects",target:"_blank",rel:"noopener noreferrer"},_=o('

    My skybox resolution is low? How to change my skybox resolution

    • If you use a custom cubemap: You can override the texture import settings of the skybox texture (assigned to your cubemap)

      image

    • If you use the default skybox: Add a SkyboxExportSettings component anywhere in your scene to override the default resolution

      image

    My Shadows are not visible or cut off

    Please the following points:

    • 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.

    My colors look wrong

    Ensure your project is set to Linear colorspace.

    image

    I'm using networking and Glitch and it doesn't work if more than 30 people visit the Glitch page at the same time

    ',9),j=e("li",null,"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.",-1),E={href:"https://www.npmjs.com/package/@needle-tools/needle-tiny-networking-ws",target:"_blank",rel:"noopener noreferrer"},S=o('

    My website doesn't have AR/VR buttons

    • 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

    I created a new script in a sub-scene but it does not work

    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.

    My local server does not start / I do not see a website

    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.

    ',7),I=o("
  • 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.
  • ",3),T=o(`

    Does C# component generation work with javascript only too?

    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.

    class MyScript extends Behaviour
    +{
    +    //@type float
    +    myField = 5;
    +}
    +

    I don't have any buttons like "Generate Project" in my components/inspector

    Please check that you're not accidentally in the Inspector's Debug mode โ€“ switch back to Normal:
    20220824-025011-S2GQ-Unity_lKlT-needle

    Toktx can not be found / toktx is not installed

    `,6),q={href:"http://localhost:8080/docs/getting-started.html#install-these-tools-for-production-builds",target:"_blank",rel:"noopener noreferrer"},M=e("li",null,[e("p",null,[n("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 "),e("code",null,"C:\\Program Files\\KTX-Software\\bin")])],-1),N=o('

    image

    Installing the web project takes forever / does never finish / EONET: no such file or directory

    • 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:
      1. Open "System Information" (either windows key and type that or enter "msinfo32" in cmd)
      2. Select Components > Storage > Drives
      3. Select all (Ctrl + A) on the right side of the screen and copy that (Ctrl + C) and paste here (Ctrl + V)

    NPM install fails and there are errors about hard drive / IO

    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:

    1. Open "System Information" (either Windows key and type "System Information" or enter msinfo32 in cmd Windows + R)
    2. Select "Components > Storage > Drives"
    3. There, you can see all drives and their formatting listed. Put your projects on a drive that is NTFS formatted.

    I'm getting errors with "Unexpected token @. Expected identifier, string literal, numeric literal or ..."

    Needle Engine uses typescript decorators for serialization.
    To fix this error make sure to enable experimentalDecorators in your tsconfig.json

    I'm getting an error 'failed to load config ... vite.config.js' when running npm commands on Mac OS

    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.

    Circular reference error

    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
    `,18),R={href:"https://discord.needle.tools",target:"_blank",rel:"noopener noreferrer"},A=o('

    My scene is not loading and the console contains a warning with 'circular references' or 'failed to update active state'

    Please see the circular reference error section.

    Does my machine support WebGL 2?

    ',3),C={href:"https://get.webgl.org/webgl2/",target:"_blank",rel:"noopener noreferrer"},P=e("h4",{id:"known-devices-to-cause-issues",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#known-devices-to-cause-issues","aria-hidden":"true"},"#"),n(" Known devices to cause issues:")],-1),O=e("ul",null,[e("li",null,"Lenovo Thinkpad - T495")],-1),L=e("h2",{id:"still-have-questions",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#still-have-questions","aria-hidden":"true"},"#"),n(" Still have questions? ๐Ÿ˜ฑ")],-1),U={href:"https://discord.needle.tools",target:"_blank",rel:"noopener noreferrer"},G=e("p",null,[e("a",{href:"https://discord.needle.tools",target:"_blank"},[e("img",{height:"20",src:"https://img.shields.io/discord/717429793926283276?color=5562ea&label=Discord"})])],-1);function F(Y,D){const s=r("RouterLink"),a=r("ExternalLinkIcon");return d(),h("div",null,[g,e("p",null,[n("See "),t(s,{to:"/testing.html"},{default:i(()=>[n("the Testing docs")]),_:1}),n(" for information on how to set up a self-signed certificate for a smoother development experience.")]),f,e("p",null,[n("If loading time itself is an issue you can "),b,n(" 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 "),t(s,{to:"/scripting.html#assetreference-and-addressables"},{default:i(()=>[n("Scripting/Addressables in the documentation")]),_:1}),n(".")]),y,e("p",null,[n("Ensure you're following "),e("a",k,[n("best practices for lightmaps"),t(a)]),n(" and read about "),e("a",w,[n("mixing baked and non-baked objects"),t(a)])]),v,e("p",null,[n("Also see the docs on "),e("a",x,[n("mixing baked and non-baked objects"),t(a)]),n(".")]),_,e("ul",null,[j,e("li",null,[n("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 "),e("a",E,[n("networking backend package"),t(a)]),n(" itself somewhere else where it can scale e.g. Google Cloud.")])]),S,e("ul",null,[e("li",null,[n("Make sure you follow the "),t(s,{to:"/getting-started/#prerequisites"},{default:i(()=>[n("Prerequisites")]),_:1}),n(".")]),I]),T,e("ul",null,[e("li",null,[e("p",null,[n("Make sure to "),e("a",q,[n("download and install toktx"),t(a)])])]),M]),N,e("p",null,[n("If this doesn't fix the problem please ask "),e("a",R,[n("in our discord"),t(a)]),n(".")]),A,e("p",null,[n("Use a detector "),e("a",C,[n("like this one"),t(a)]),n(" 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.")]),P,O,L,e("p",null,[e("a",U,[n("Ask in our friendly discord community"),t(a)])]),G])}const B=c(m,[["render",F],["__file","faq.html.vue"]]);export{B as default}; diff --git a/assets/features-overview.html-04daa736.js b/assets/features-overview.html-04daa736.js new file mode 100644 index 000000000..c79bfa7e4 --- /dev/null +++ b/assets/features-overview.html-04daa736.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-bcfe00ae","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":1725399379000},"filePathRelative":"features-overview.md"}');export{e as data}; diff --git a/assets/features-overview.html-103369e6.js b/assets/features-overview.html-103369e6.js new file mode 100644 index 000000000..c00ba7779 --- /dev/null +++ b/assets/features-overview.html-103369e6.js @@ -0,0 +1 @@ +import{_ as d,M as l,p as c,q as h,R as e,t,N as n,V as o,a1 as u}from"./framework-f6820c83.js";const p={},m=e("h1",{id:"feature-overview",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#feature-overview","aria-hidden":"true"},"#"),t(" Feature Overview")],-1),_={href:"https://needle.tools",target:"_blank",rel:"noopener noreferrer"},g={href:"https://engine.needle.tools/samples",target:"_blank",rel:"noopener noreferrer"},f={class:"table-of-contents"},b=e("h2",{id:"shaders-and-materials",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#shaders-and-materials","aria-hidden":"true"},"#"),t(" Shaders and Materials")],-1),y=e("p",null,"Both PBR Materials and Custom shaders created with Shader Graph or other systems can be exported.",-1),w=e("img",{src:"https://user-images.githubusercontent.com/5083203/186012027-9bbe3944-fa56-41fa-bfbb-c989fa87aebb.png"},null,-1),k={href:"https://unity.com/features/shader-graph",target:"_blank",rel:"noopener noreferrer"},v=e("h2",{id:"crossplatform-vr-ar-mobile-desktop",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#crossplatform-vr-ar-mobile-desktop","aria-hidden":"true"},"#"),t(" Crossplatform: VR, AR, Mobile, Desktop")],-1),x=e("strong",null,"Interactive AR on both Android and iOS",-1),S=e("h2",{id:"lightmaps",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#lightmaps","aria-hidden":"true"},"#"),t(" Lightmaps")],-1),A=e("p",null,[e("img",{src:"https://user-images.githubusercontent.com/5083203/186163693-093c7ae2-96eb-4d75-b98f-bf19f78032ff.gif",alt:"lightmaps"})],-1),E=e("p",null,"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!",-1),N=e("strong",null,"Note",-1),R={href:"https://assetstore.unity.com/packages/tools/level-design/bakery-gpu-lightmapper-122218",target:"_blank",rel:"noopener noreferrer"},U={href:"https://fwd.needle.tools/needle-engine/docs/lightmaps",target:"_blank",rel:"noopener noreferrer"},C=e("h2",{id:"multiplayer-and-networking",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#multiplayer-and-networking","aria-hidden":"true"},"#"),t(" Multiplayer and Networking")],-1),B=e("p",null,"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!",-1),T={href:"https://fwd.needle.tools/needle-engine/docs/networking",target:"_blank",rel:"noopener noreferrer"},I={href:"https://fwd.needle.tools/needle-engine/docs/scripting",target:"_blank",rel:"noopener noreferrer"},P=e("h2",{id:"animations-and-sequencing",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#animations-and-sequencing","aria-hidden":"true"},"#"),t(" Animations and Sequencing")],-1),M=e("p",null,[t("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."),e("br"),t(" 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.")],-1),q=e("h3",{id:"animator",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#animator","aria-hidden":"true"},"#"),t(" Animator")],-1),L=e("img",{src:"https://user-images.githubusercontent.com/5083203/186011302-176524b3-e8e5-4e6e-9b77-7faf3561bb15.png"},null,-1),O={href:"https://docs.unity3d.com/Manual/class-AnimatorController.html",target:"_blank",rel:"noopener noreferrer"},D=e("code",null,"OnStateEnter",-1),V=e("code",null,"OnStateUpdate",-1),F=e("code",null,"OnStateExit",-1),j=e("blockquote",null,[e("p",null,[e("strong",null,"Note"),t(": Sub-states and Blend Trees are not supported.")])],-1),G=e("h3",{id:"timeline",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#timeline","aria-hidden":"true"},"#"),t(" Timeline")],-1),W=e("p",null,[e("img",{src:"https://user-images.githubusercontent.com/5083203/186037829-ee99340d-b19c-484d-b551-94797519c9d9.png",alt:"2022-08-23-013517_Scene"})],-1),z={href:"https://unity.com/features/timeline",target:"_blank",rel:"noopener noreferrer"},H=e("br",null,null,-1),J=e("blockquote",null,[e("p",null,[e("strong",null,"Note"),t(": Sub-Timelines are currently not supported.")])],-1),X=e("strong",null,"Note",-1),Y={href:"https://github.com/needle-tools/needle-engine-modules/tree/main/package/TimelineHtml",target:"_blank",rel:"noopener noreferrer"},K=e("h2",{id:"physics",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#physics","aria-hidden":"true"},"#"),t(" Physics")],-1),Q=e("p",null,"Use Rigidbodies, Mesh Colliders, Box Colliders and SphereColliders to add some juicy physics to your world.",-1),Z=e("h2",{id:"ui",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ui","aria-hidden":"true"},"#"),t(" UI")],-1),$=e("p",null,"Building UI using Unity's UI canvas system is in development. Features currently include exporting Text (including fonts), Images, Buttons.",-1),ee=e("h2",{id:"particles",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#particles","aria-hidden":"true"},"#"),t(" Particles")],-1),te=e("br",null,null,-1),ne={href:"https://engine.needle.tools/samples/particles",target:"_blank",rel:"noopener noreferrer"},oe=e("h2",{id:"postprocessing",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#postprocessing","aria-hidden":"true"},"#"),t(" PostProcessing")],-1),ae=e("h2",{id:"editor-integrations",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#editor-integrations","aria-hidden":"true"},"#"),t(" Editor Integrations")],-1),re=e("p",null,[t("Needle Engine comes with powerful integrations into the Unity Editor and Blender."),e("br"),t(" It allows you to setup and export complex scenes in a visual way providing easy and flexible collaboration between artists and developers.")],-1),ie=e("h2",{id:"scripting",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#scripting","aria-hidden":"true"},"#"),t(" Scripting")],-1),se={href:"https://fwd.needle.tools/needle-engine/docs/npmdef",target:"_blank",rel:"noopener noreferrer"},le={href:"https://fwd.needle.tools/needle-engine/docs/component-compiler",target:"_blank",rel:"noopener noreferrer"},de=e("a",{href:"scripting"},"Scripting Reference",-1),ce={href:"https://fwd.needle.tools/needle-engine/docs/npmdef",target:"_blank",rel:"noopener noreferrer"},he=e("h2",{id:"and-there-is-more",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#and-there-is-more","aria-hidden":"true"},"#"),t(" And there is more")],-1),ue=e("li",null,"PostProcessing โ†’ Bloom, Screenspace Ambient Occlusion, Depth of Field, Color Correction...",-1),pe=e("li",null,"EditorSync โ†’ Live synchronize editing in Unity to the running three.js application for local development",-1),me=u('

    Where to go next

    See our Getting Started Guide to learn about how to download and set up Needle Engine.
    Learn about our vision or dive deeper into some of the technical background and glTF powering it all.

    ',3);function _e(ge,fe){const a=l("ExternalLinkIcon"),r=l("router-link"),i=l("RouterLink"),s=l("sample");return c(),h("div",null,[m,e("p",null,[t("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 "),e("a",_,[t("Homepage"),n(a)]),t(" and our "),e("a",g,[t("Samples and Showcase"),n(a)]),t(".")]),e("nav",f,[e("ul",null,[e("li",null,[n(r,{to:"#shaders-and-materials"},{default:o(()=>[t("Shaders and Materials")]),_:1})]),e("li",null,[n(r,{to:"#crossplatform-vr-ar-mobile-desktop"},{default:o(()=>[t("Crossplatform: VR, AR, Mobile, Desktop")]),_:1})]),e("li",null,[n(r,{to:"#lightmaps"},{default:o(()=>[t("Lightmaps")]),_:1})]),e("li",null,[n(r,{to:"#multiplayer-and-networking"},{default:o(()=>[t("Multiplayer and Networking")]),_:1})]),e("li",null,[n(r,{to:"#animations-and-sequencing"},{default:o(()=>[t("Animations and Sequencing")]),_:1}),e("ul",null,[e("li",null,[n(r,{to:"#animator"},{default:o(()=>[t("Animator")]),_:1})]),e("li",null,[n(r,{to:"#timeline"},{default:o(()=>[t("Timeline")]),_:1})])])]),e("li",null,[n(r,{to:"#physics"},{default:o(()=>[t("Physics")]),_:1})]),e("li",null,[n(r,{to:"#ui"},{default:o(()=>[t("UI")]),_:1})]),e("li",null,[n(r,{to:"#particles"},{default:o(()=>[t("Particles")]),_:1})]),e("li",null,[n(r,{to:"#postprocessing"},{default:o(()=>[t("PostProcessing")]),_:1})]),e("li",null,[n(r,{to:"#editor-integrations"},{default:o(()=>[t("Editor Integrations")]),_:1})]),e("li",null,[n(r,{to:"#scripting"},{default:o(()=>[t("Scripting")]),_:1})]),e("li",null,[n(r,{to:"#and-there-is-more"},{default:o(()=>[t("And there is more")]),_:1})])])]),b,y,w,e("p",null,[t("Use the node based "),e("a",k,[t("ShaderGraph"),n(a)]),t(" to create shaders for the web. ShaderGraph makes it easy for artists to keep creating without having to worry about syntax.")]),e("p",null,[t("Read more about "),n(i,{to:"/export.html#physically-based-materials-pbr"},{default:o(()=>[t("PBR Materials")]),_:1}),t(" โ€ข "),n(i,{to:"/export.html#custom-shaders"},{default:o(()=>[t("Custom Shaders")]),_:1})]),v,e("p",null,[t("Needle Engine runs everywhere web technology does: run the same application on desktop, mobile, AR or VR. We build Needle Engine "),n(i,{to:"/xr.html"},{default:o(()=>[t("with XR in mind")]),_:1}),t(" and consider this as and integral part of responsive webdesign!")]),e("p",null,[t("Use "),n(i,{to:"/everywhere-actions.html"},{default:o(()=>[t("Everywhere Actions")]),_:1}),t(" for "),x,t(".")]),S,A,E,e("blockquote",null,[e("p",null,[N,t(": 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 "),e("a",R,[t("Bakery"),n(a)]),t(" thus are also supported.")])]),e("ul",null,[e("li",null,[t("Read more about "),e("a",U,[t("Exporting Lightmaps"),n(a)])])]),C,B,e("ul",null,[e("li",null,[t("Read more about "),e("a",T,[t("Networking"),n(a)]),t(" โ€ข "),e("a",I,[t("Scripting"),n(a)])])]),P,M,e("ul",null,[e("li",null,[t("Read more about "),n(i,{to:"/component-reference.html#animation"},{default:o(()=>[t("Animation Components")]),_:1})])]),q,L,e("p",null,[t("The "),e("a",O,[t("Animator and AnimatorController"),n(a)]),t(" 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 "),D,t(", "),V,t(" and "),F,t(" events.")]),j,G,W,e("p",null,[t("We're also translating "),e("a",z,[t("Unity's Timeline"),n(a)]),t(" setup and tracks into a web-ready format."),H,t(" Supported tracks include: AnimationTrack, AudioTrack, ActivationTrack, ControlTrack, SignalTrack.")]),J,e("blockquote",null,[e("p",null,[X,t(": It's possible to "),e("a",Y,[t("export custom timeline tracks"),n(a)]),t(".")])]),e("ul",null,[e("li",null,[t("Read more about "),n(i,{to:"/component-reference.html#animation"},{default:o(()=>[t("Animation Components")]),_:1})])]),K,Q,e("ul",null,[e("li",null,[t("Read more about "),n(i,{to:"/component-reference.html#physics"},{default:o(()=>[t("Physics Components")]),_:1})])]),n(s,{src:"https://engine.needle.tools/samples-uploads/physics-animation/"}),Z,$,e("p",null,[t("See the "),n(i,{to:"/component-reference.html#ui"},{default:o(()=>[t("ui component reference")]),_:1}),t(" for supported component.")]),n(s,{src:"https://engine.needle.tools/samples-uploads/screenspace-ui"}),ee,e("p",null,[t("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."),te,t(" See a "),e("a",ne,[t("live sample"),n(a)]),t(" of supported features below:")]),n(s,{src:"https://engine.needle.tools/samples-uploads/particles/"}),oe,e("p",null,[t("Builtin effects include Bloom, Screenspace Ambient Occlusion, Depth of Field, Color Correction. You can also create your own custom effects. See "),n(i,{to:"/component-reference.html#postprocessing"},{default:o(()=>[t("the component reference")]),_:1}),t(" for a complete list.")]),n(s,{src:"https://engine.needle.tools/samples-uploads/postprocessing/"}),ae,re,ie,e("p",null,[t("Needle Engine uses as "),n(i,{to:"/scripting.html#component-architecture"},{default:o(()=>[t("component based workflow")]),_:1}),t(". Create custom scripts in typescript or javascript. Use our "),e("a",se,[t("modular npm-based package workflow"),n(a)]),t(" integrated into Unity. A "),e("a",le,[t("typescript to C# component compiler"),n(a)]),t(" produces Unity components magically on the fly.")]),e("ul",null,[e("li",null,[t("Read more about "),de,t(" โ€ข "),e("a",ce,[t("Npm Definition Files"),n(a)])])]),he,e("ul",null,[ue,pe,e("li",null,[t("Interactive AR on iOS and Android โ†’ Use our "),n(i,{to:"/everywhere-actions.html"},{default:o(()=>[t("Everywhere Actions")]),_:1}),t(" feature set or build your own")])]),me])}const ye=d(p,[["render",_e],["__file","features-overview.html.vue"]]);export{ye as default}; diff --git a/assets/for-unity-developers.html-36af0499.js b/assets/for-unity-developers.html-36af0499.js new file mode 100644 index 000000000..1e2f6035e --- /dev/null +++ b/assets/for-unity-developers.html-36af0499.js @@ -0,0 +1,116 @@ +import{_ as l,M as c,p as r,q as d,R as e,t as n,N as s,V as i,a1 as t}from"./framework-f6820c83.js";const u={},h=e("p",null,"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.",-1),m=e("p",null,"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.",-1),k=e("p",null,[n("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 "),e("a",{href:"./typescript-essentials"},"Typescript Essentials Guide"),n(" for a basic understanding between the differences between C# and Javascript/Typescript.")],-1),b={href:"https://engine.needle.tools/new",target:"_blank",rel:"noopener noreferrer"},v=e("h2",{id:"the-basics",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#the-basics","aria-hidden":"true"},"#"),n(" The Basics")],-1),g={href:"https://threejs.org/",target:"_blank",rel:"noopener noreferrer"},y=e("code",null,"gameObject",-1),f=e("em",null,"actually",-1),w=e("code",null,"Object3D",-1),_=e("code",null,"gameObject",-1),j=e("em",null,"is",-1),x=e("code",null,"Object3D",-1),q=e("p",null,"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.",-1),E=e("strong",null,[n("Needle Engine's Exporter does "),e("em",null,"NOT"),n(" compile your existing C# code to Web Assembly")],-1),T=e("br",null,null,-1),O=e("em",null,"may",-1),D=e("a",{href:"./technical-overview"},"technical overview",-1),N={class:"custom-container details"},I=e("summary",null,"How to create a new Unity project with Needle Engine? (Video)",-1),U=t(`

    Creating a Component

    In Unity you create a new component by deriving from MonoBehaviour:

    using UnityEngine;
    +public class MyComponent : MonoBehaviour { 
    +}
    +

    A custom component in Needle Engine on the other hand is written as follows:

    import { Behaviour } from "@needle-tools/engine"
    +export class MyComponent extends Behaviour { 
    +}
    +

    Script Fields

    serializable

    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:

    @serializable(Behaviour)
    +myOtherComponent?: Behaviour;
    +@serializable(Object3D)
    +someOtherObject?: Object3D;
    +

    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.

    `,10),C={href:"https://developer.mozilla.org/en-US/docs/Glossary/Primitive",target:"_blank",rel:"noopener noreferrer"},P=e("code",null,"boolean",-1),R=e("code",null,"number",-1),G=e("code",null,"bigint",-1),z=e("code",null,"string",-1),S=e("code",null,"null",-1),A=e("code",null,"undefined",-1),W=t(`
    @serializable() // < no type is needed here because the field type is a primitive
    +myString?: string;
    +

    public vs private

    Field without any accessor modified like private, public or protected will by default be public in javascript

    /// no accessor means it is public:
    +myNumber?: number;
    +// explicitly making it private:
    +private myPrivateNumber?: number;
    +protected myProtectedNumber?: number;
    +

    The same is true for methods as well.

    GameObjects and the Scene

    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++) 
    +    const ch = this.gameObject.children[i];
    +

    or you can iterate using the foreach equivalent:

    for(const child of this.gameObject.children) {
    +    console.log(child);
    +}
    +
    `,11),B={href:"https://threejs.org/docs/#api/en/core/Object3D.traverse",target:"_blank",rel:"noopener noreferrer"},M=e("code",null,"traverse",-1),F=t(`
    this.gameObject.traverse(obj => console.log(obj))
    +
    `,1),L={href:"https://threejs.org/docs/#api/en/core/Object3D.traverseVisible",target:"_blank",rel:"noopener noreferrer"},H=e("code",null,"traverseVisible",-1),V=t(`

    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:

    for(const renderer of this.gameObject.getComponentsInChildren(Renderer))
    +    console.log(renderer);
    +

    For more information about getting components see the next section.

    Components

    `,4),Y=e("code",null,"Object3D",-1),J=e("code",null,"GameObject",-1),X=e("code",null,"addNewComponent(, )",-1),K=e("br",null,null,-1),Q=e("code",null,"update",-1),Z=e("code",null,"onBeforeRender",-1),$=t('

    Finding Components in the Scene

    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 nameDesciption
    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).

    Renamed Unity Types

    Some Unity-specific types are mapped to different type names in our engine. See the following list:

    Type in UnityType in Needle Engine
    UnityEventEventListA UnityEvent will be exported as a EventList type (use serializable(EventList) to deserialize UnityEvents)
    GameObjectObject3D
    TransformObject3DIn 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.
    ColorRGBAColorThe 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

    Transform

    Transform data can be accessed on the GameObject / Object3D directly. Unlike to Unity there is no extra transform component that holds this data.

    ',10),nn=e("code",null,"this.gameObject.position",-1),en={href:"https://threejs.org/docs/?q=obj#api/en/core/Object3D.position",target:"_blank",rel:"noopener noreferrer"},sn=e("code",null,"this.gameObject.rotation",-1),an={href:"https://threejs.org/docs/?q=obj#api/en/core/Object3D.rotation",target:"_blank",rel:"noopener noreferrer"},tn=e("code",null,"this.gameObject.quaternion",-1),on={href:"https://threejs.org/docs/?q=obj#api/en/core/Object3D.quaternion",target:"_blank",rel:"noopener noreferrer"},cn=e("code",null,"this.gameObject.scale",-1),pn={href:"https://threejs.org/docs/?q=obj#api/en/core/Object3D.scale",target:"_blank",rel:"noopener noreferrer"},ln=t('

    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.

    WORLD- Position, Rotation, Scale...

    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"

    ',4),rn=e("code",null,"getWorldPosition",-1),dn=e("code",null,"getWorldRotation",-1),un=e("code",null,"getWorldScale",-1),hn=e("code",null,"getWorldPosition",-1),mn=e("strong",null,"Primitive Types",-1),kn=t('

    Time

    Use this.context.time to get access to time data:

    • this.context.time.time is the time since the application started running
    • this.context.time.deltaTime is the time that has passed since the last frame
    • this.context.time.frameCount is the number of frames that have passed since the application started
    • this.context.time.realtimeSinceStartup is the unscaled time since the application has started running

    It is also possible to use this.context.time.timeScale to deliberately slow down time for e.g. slow motion effects.

    Raycasting

    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.

    ',6),bn=e("code",null,"this.context.physics.raycastFromRay(your_ray)",-1),vn={href:"https://threejs.org/docs/#api/en/math/Ray",target:"_blank",rel:"noopener noreferrer"},gn=t(`

    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();
    +
    `,3),yn={href:"https://stackblitz.com/edit/needle-engine-physics-raycast-example?file=src%2Fmain.ts,package.json,.gitignore",target:"_blank",rel:"noopener noreferrer"},fn=t(`

    Input

    Use this.context.input to poll input state:

    import { Behaviour } from "@needle-tools/engine";
    +export class MyScript extends Behaviour
    +{
    +    update(){
    +        if(this.context.input.getPointerDown(0)){
    +            console.log("POINTER DOWN")
    +        }
    +    }
    +}
    +

    You can also subscribe to events in the InputEvents enum like so:

    import { Behaviour, InputEvents } from "@needle-tools/engine";
    +
    +export class MyScript extends Behaviour
    +{
    +    onEnable(){
    +        this.context.input.addEventListener(InputEvents.PointerDown, this.onPointerDown);
    +    }
    +    onDisable() {
    +        // it is recommended to also unsubscribe from events when your component becomes inactive
    +        this.context.input.removeEventListener(InputEvents.PointerDown, this.onPointerDown);
    +    }
    +
    +    private onPointerDown = (evt) => { console.log(evt); }
    +}
    +
    `,5),wn={href:"https://developer.mozilla.org/en-US/docs/Web/Events",target:"_blank",rel:"noopener noreferrer"},_n=t(`
    window.addEventListener("click", () => { console.log("MOUSE CLICK"); });
    +

    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).

    InputSystem Callbacks

    `,3),jn={href:"https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.EventSystems.IPointerClickHandler.html",target:"_blank",rel:"noopener noreferrer"},xn=t(`

    To make this work make sure your object has a ObjectRaycaster or GraphicRaycaster component in the parent hierarchy.

    import { Behaviour, IPointerEventHandler, PointerEventData } from "@needle-tools/engine";
    +
    +export class ReceiveClickEvent extends Behaviour implements IPointerEventHandler {
    +    onPointerClick(args: PointerEventData) {
    +        console.log("Click", args);
    +    }
    +}
    +

    Note: IPointerEventHandler subscribes the object to all possible pointer events. The handlers for them are:

    • onPointerDown
    • onPointerUp
    • onPointerEnter
    • onPointerMove
    • onPointerExit
    • onPointerClick

    All have a PointerEventData argument describing the event.

    Debug.Log

    The Debug.Log() equivalent in javascript is console.log(). You can also use console.warn() or console.error().

    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);
    +

    Gizmos

    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

    Gizmos.DrawWireSphere(hit.point, 0.05, 0xff0000, 1);
    +

    Here are some of the available gizmo methods:

    Method name
    Gizmos.DrawArrow
    Gizmos.DrawBox
    Gizmos.DrawBox3
    Gizmos.DrawDirection
    Gizmos.DrawLine
    Gizmos.DrawRay
    Gizmos.DrawRay
    Gizmos.DrawSphere
    Gizmos.DrawWireSphere

    Useful Utility Methods

    Import from @needle-tools/engine e.g. import { getParam } from "@needle-tools/engine"

    Method nameDescription
    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() )
    +
    import { getParam } from "@needle-tools/engine"
    +// returns true 
    +const myFlag = getParam("some_flag")
    +console.log(myFlag)
    +

    The Web project

    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.

    `,22),qn={href:"https://npmjs.com/",target:"_blank",rel:"noopener noreferrer"},En=t(`

    Here is an example of how a package.json might look like:

    {
    +  "name": "@optional_org/package_name",
    +  "version": "1.0.0",
    +  "scripts": {
    +    "start": "vite --host"
    +  },
    +  "dependencies": {
    +	  "@needle-tools/engine": "^3.5.9-beta",
    +	  "three": "npm:@needle-tools/three@0.146.8"
    +	},
    +  "devDependencies": {
    +	  "@types/three": "0.146.0",
    +	  "@vitejs/plugin-basic-ssl": "^1.0.1",
    +	  "typescript": "^5.0.4",
    +	  "vite": "^4.3.4",
    +	  "vite-plugin-compression": "^0.5.1"
    +	}
    +}
    +

    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.

    Installing packages & dependencies

    `,4),Tn=e("code",null,"npm i ",-1),On=e("code",null,"npm install",-1),Dn=e("br",null,null,-1),Nn=e("code",null,"npm i @needle-tools/engine",-1),In={href:"https://www.npmjs.com/package/@needle-tools/engine",target:"_blank",rel:"noopener noreferrer"},Un=e("code",null,"package.json",-1),Cn=e("code",null,"dependencies",-1),Pn=e("br",null,null,-1),Rn=e("code",null,"npm i --save-dev ",-1),Gn=t('

    What's the difference between 'dependencies' and 'devDependencies'

    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.

    How do I install another package or dependency and how to use it?

    ',5),zn=e("a",{href:"#installing"},"Installing",-1),Sn=e("code",null,"npm i ",-1),An=e("code",null,"package_name",-1),Wn={href:"https://npmjs.org",target:"_blank",rel:"noopener noreferrer"},Bn={href:"https://www.npmjs.com/package/@tweenjs/tween.js",target:"_blank",rel:"noopener noreferrer"},Mn=e("code",null,"@tweenjs/tween.js",-1),Fn={href:"https://stackblitz.com/edit/needle-engine-tweenjs-example?file=src%2Fmain.ts",target:"_blank",rel:"noopener noreferrer"},Ln=t(`

    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:

    "dependencies": {
    +    "@needle-tools/engine": "^3.5.11-beta",
    +    "@tweenjs/tween.js": "^20.0.3",
    +    "three": "npm:@needle-tools/three@0.146.8"
    +}
    +

    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.

    `,5),Hn={href:"https://github.com/tweenjs/tween.js/blob/HEAD/docs/user_guide.md",target:"_blank",rel:"noopener noreferrer"},Vn=t(`

    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:

    export class TweenRotation extends Behaviour {
    +
    +    // save the instance of our tweener
    +    private _tween?: TWEEN.Tween<any>; 
    +
    +    start() {
    +        // create the tween instance
    +        this._tween = new TWEEN.Tween(this.gameObject.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();
    +    }
    +}
    +
    `,2),Yn=e("br",null,null,-1),Jn={href:"https://stackblitz.com/edit/needle-engine-tweenjs-example?file=src%2Fmain.ts",target:"_blank",rel:"noopener noreferrer"},Xn=e("h1",{id:"learning-more",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#learning-more","aria-hidden":"true"},"#"),n(" Learning more")],-1),Kn=e("li",null,[e("a",{href:"../scripting"},"Scripting in Needle Engine")],-1);function Qn(Zn,$n){const a=c("ExternalLinkIcon"),o=c("RouterLink"),p=c("video-embed");return r(),d("div",null,[h,m,k,e("p",null,[n("If you want to code-along you can "),e("a",b,[n("open engine.needle.tools/new"),s(a)]),n(" to create a small project that you can edit in the browser โšก")]),v,e("p",null,[n("Needle Engine is a 3d web engine running on-top of "),e("a",g,[n("three.js"),s(a)]),n(". Three.js is one of the most popular 3D webgl based rendering libraries for the web. Whenever we refer to a "),y,n(" in Needle Engine we are "),f,n(" also talking about a three.js "),w,n(", the base type of any object in three.js. Both terms can be used interchangeably. Any "),_,n(),j,n(" a "),x,n(".")]),q,e("p",null,[n("Note: "),E,n("."),T,n(" While using Web Assembly "),O,n(" 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 "),s(o,{to:"/getting-started/vision.html"},{default:i(()=>[n("vision")]),_:1}),n(" and "),D,n(".")]),e("details",N,[I,s(p,{src:"https://www.youtube.com/watch?v=gZX_sqrne8U",limit_height:""})]),U,e("p",null,[n("Note that in some cases the type can be ommitted. This can be done for all "),e("a",C,[n("primitive types in Javascript"),s(a)]),n(". These are "),P,n(", "),R,n(", "),G,n(", "),z,n(", "),S,n(" and "),A,n(".")]),W,e("p",null,[n("You can also use three.js specific methods to quickly iterate all objects recursively using the "),e("a",B,[M,s(a)]),n(" method:")]),F,e("p",null,[n("or to just traverse visible objects use "),e("a",L,[H,s(a)]),n(" instead.")]),V,e("p",null,[n("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 "),Y,n(" / "),J,n(" in the scene. A component will be registered to the engine when using "),X,n("."),K,n(" The event methods that the attached component will then automatically be called by the engine (e.g. "),Q,n(" or "),Z,n("). A full list of event methods can be found in the "),s(o,{to:"/scripting.html#lifecycle-methods"},{default:i(()=>[n("scripting documentation")]),_:1})]),$,e("ul",null,[e("li",null,[nn,n(" is the "),e("a",en,[n("position"),s(a)]),n(" in local space")]),e("li",null,[sn,n(" is the "),e("a",an,[n("rotation in euler angles"),s(a)]),n(" in local space")]),e("li",null,[tn,n(" - is the "),e("a",on,[n("quaternion"),s(a)]),n(" in local space")]),e("li",null,[cn,n(" - is the "),e("a",pn,[n("scale"),s(a)]),n(" in local space")])]),ln,e("p",null,[n("Note that these utility methods like "),rn,n(", "),dn,n(", "),un,n(" 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 "),hn,n(" 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 "),mn,n(" section in the "),s(o,{to:"/getting-started/typescript-essentials.html#primitive-types"},{default:i(()=>[n("Typescript Essentials Guide")]),_:1})]),kn,e("p",null,[n("Use "),bn,n(" to perform a raycast using a "),e("a",vn,[n("three.js ray"),s(a)])]),gn,e("p",null,[n("Here is a editable "),e("a",yn,[n("example for physics raycast"),s(a)])]),fn,e("p",null,[n("If you want to handle inputs yourself you can also subscribe to "),e("a",wn,[n("all events the browser provides"),s(a)]),n(" (there are a ton). For example to subscribe to the browsers click event you can write:")]),_n,e("p",null,[n("Similar to Unity (see "),e("a",jn,[n("IPointerClickHandler in Unity"),s(a)]),n(") you can also register to receive input events on the component itself.")]),xn,e("p",null,[n("When working with a web project most of you dependencies are installed from "),e("a",qn,[n("npmjs.com"),s(a)]),n(". It is the most popular package registry out there for web projects.")]),En,e("p",null,[n("To install a dependency from npm you can open your web project in a commandline (or terminal) and run "),Tn,n(" (shorthand for "),On,n(")"),Dn,n(" For example run "),Nn,n(" to install "),e("a",In,[n("Needle Engine"),s(a)]),n(". This will then add the package to your "),Un,n(" to the "),Cn,n(" array."),Pn,n(" To install a package as a devDependency only you can run "),Rn,n(". More about the difference between dependencies and devDependencies below.")]),Gn,e("p",null,[n("The "),zn,n(" section taught us that you can install dependencies by running "),Sn,n(" in your project directory where the "),An,n(" can be any package that you find on "),e("a",Wn,[n("npm.js"),s(a)]),n(".")]),e("p",null,[n("Let's assume you want to add a tweening library to your project. We will use "),e("a",Bn,[Mn,s(a)]),n(" for this example. "),e("a",Fn,[n("Here"),s(a)]),n(" is the final project if you want to jump ahead and just see the result.")]),Ln,e("p",null,[n("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 "),e("a",Hn,[n("user guide"),s(a)]),n(" that we can follow. Usually the Readme page of the package on npm contains information on how to install and use the package.")]),Vn,e("p",null,[n("Now we only have to add it to any of the objects in our scene to rotate them forever."),Yn,n(" You can see the final script in action "),e("a",Jn,[n("here"),s(a)]),n(".")]),Xn,e("ul",null,[Kn,e("li",null,[s(o,{to:"/getting-started/typescript-essentials.html"},{default:i(()=>[n("Typescript Essentials")]),_:1})]),e("li",null,[s(o,{to:"/component-reference.html"},{default:i(()=>[n("Component Reference")]),_:1})])])])}const ee=l(u,[["render",Qn],["__file","for-unity-developers.html.vue"]]);export{ee as default}; diff --git a/assets/for-unity-developers.html-916accf3.js b/assets/for-unity-developers.html-916accf3.js new file mode 100644 index 000000000..768264733 --- /dev/null +++ b/assets/for-unity-developers.html-916accf3.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7f349b5b","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{e as data}; diff --git a/assets/for-unity-developers.html-b653e255.js b/assets/for-unity-developers.html-b653e255.js new file mode 100644 index 000000000..51175c25e --- /dev/null +++ b/assets/for-unity-developers.html-b653e255.js @@ -0,0 +1 @@ +import{_ as t,p as n,q as o,R as e,t as r}from"./framework-f6820c83.js";const a={},s=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","aria-hidden":"true"},"#"),r(" This page has been moved: "),e("a",{href:"./getting-started/for-unity-developers"},"continue here")],-1),c=[s];function i(h,d){return n(),o("div",null,c)}const l=t(a,[["render",i],["__file","for-unity-developers.html.vue"]]);export{l as default}; diff --git a/assets/for-unity-developers.html-d7f50ac7.js b/assets/for-unity-developers.html-d7f50ac7.js new file mode 100644 index 000000000..b6b875dd3 --- /dev/null +++ b/assets/for-unity-developers.html-d7f50ac7.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-63ed5104","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":1725399379000},"filePathRelative":"getting-started/for-unity-developers.md"}`);export{e as data}; diff --git a/assets/framework-f6820c83.js b/assets/framework-f6820c83.js new file mode 100644 index 000000000..c55526bf4 --- /dev/null +++ b/assets/framework-f6820c83.js @@ -0,0 +1,21 @@ +/** +* @vue/shared v3.4.38 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**//*! #__NO_SIDE_EFFECTS__ */function vs(e,t){const n=new Set(e.split(","));return t?s=>n.has(s.toLowerCase()):s=>n.has(s)}const fe={},Rt=[],Ae=()=>{},bo=()=>!1,en=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Es=e=>e.startsWith("onUpdate:"),ye=Object.assign,Cs=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},vo=Object.prototype.hasOwnProperty,ee=(e,t)=>vo.call(e,t),q=Array.isArray,Tt=e=>Mn(e)==="[object Map]",Wr=e=>Mn(e)==="[object Set]",z=e=>typeof e=="function",he=e=>typeof e=="string",ct=e=>typeof e=="symbol",ce=e=>e!==null&&typeof e=="object",qr=e=>(ce(e)||z(e))&&z(e.then)&&z(e.catch),Gr=Object.prototype.toString,Mn=e=>Gr.call(e),Eo=e=>Mn(e).slice(8,-1),zr=e=>Mn(e)==="[object Object]",ws=e=>he(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,At=vs(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),In=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},Co=/-(\w)/g,je=In(e=>e.replace(Co,(t,n)=>n?n.toUpperCase():"")),wo=/\B([A-Z])/g,ut=In(e=>e.replace(wo,"-$1").toLowerCase()),Ln=In(e=>e.charAt(0).toUpperCase()+e.slice(1)),Kn=In(e=>e?`on${Ln(e)}`:""),ot=(e,t)=>!Object.is(e,t),Wn=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:s,value:n})},xo=e=>{const t=parseFloat(e);return isNaN(t)?e:t},So=e=>{const t=he(e)?Number(e):NaN;return isNaN(t)?e:t};let qs;const Jr=()=>qs||(qs=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function xs(e){if(q(e)){const t={};for(let n=0;n{if(n){const s=n.split(To);s.length>1&&(t[s[0].trim()]=s[1].trim())}}),t}function Ss(e){let t="";if(he(e))t=e;else if(q(e))for(let n=0;n!!(e&&e.__v_isRef===!0),Io=e=>he(e)?e:e==null?"":q(e)||ce(e)&&(e.toString===Gr||!z(e.toString))?Xr(e)?Io(e.value):JSON.stringify(e,Zr,2):String(e),Zr=(e,t)=>Xr(t)?Zr(e,t.value):Tt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[s,r],i)=>(n[qn(s,i)+" =>"]=r,n),{})}:Wr(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>qn(n))}:ct(t)?qn(t):ce(t)&&!q(t)&&!zr(t)?String(t):t,qn=(e,t="")=>{var n;return ct(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};/** +* @vue/reactivity v3.4.38 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let Te;class Lo{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this.parent=Te,!t&&Te&&(this.index=(Te.scopes||(Te.scopes=[])).push(this)-1)}get active(){return this._active}run(t){if(this._active){const n=Te;try{return Te=this,t()}finally{Te=n}}}on(){Te=this}off(){Te=this.parent}stop(t){if(this._active){let n,s;for(n=0,s=this.effects.length;n=4))break}this._dirtyLevel===1&&(this._dirtyLevel=0),at()}return this._dirtyLevel>=4}set dirty(t){this._dirtyLevel=t?4:0}run(){if(this._dirtyLevel=0,!this.active)return this.fn();let t=rt,n=yt;try{return rt=!0,yt=this,this._runnings++,Gs(this),this.fn()}finally{zs(this),this._runnings--,yt=n,rt=t}}stop(){this.active&&(Gs(this),zs(this),this.onStop&&this.onStop(),this.active=!1)}}function $o(e){return e.value}function Gs(e){e._trackId++,e._depsLength=0}function zs(e){if(e.deps.length>e._depsLength){for(let t=e._depsLength;t{const n=new Map;return n.cleanup=e,n.computed=t,n},Cn=new WeakMap,_t=Symbol(""),ls=Symbol("");function Se(e,t,n){if(rt&&yt){let s=Cn.get(e);s||Cn.set(e,s=new Map);let r=s.get(n);r||s.set(n,r=ri(()=>s.delete(n))),ni(yt,r)}}function qe(e,t,n,s,r,i){const o=Cn.get(e);if(!o)return;let c=[];if(t==="clear")c=[...o.values()];else if(n==="length"&&q(e)){const l=Number(s);o.forEach((a,h)=>{(h==="length"||!ct(h)&&h>=l)&&c.push(a)})}else switch(n!==void 0&&c.push(o.get(n)),t){case"add":q(e)?ws(n)&&c.push(o.get("length")):(c.push(o.get(_t)),Tt(e)&&c.push(o.get(ls)));break;case"delete":q(e)||(c.push(o.get(_t)),Tt(e)&&c.push(o.get(ls)));break;case"set":Tt(e)&&c.push(o.get(_t));break}Ts();for(const l of c)l&&si(l,4);As()}function Ho(e,t){const n=Cn.get(e);return n&&n.get(t)}const jo=vs("__proto__,__v_isRef,__isVue"),ii=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(ct)),Qs=ko();function ko(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const s=te(this);for(let i=0,o=this.length;i{e[t]=function(...n){ft(),Ts();const s=te(this)[t].apply(this,n);return As(),at(),s}}),e}function Bo(e){ct(e)||(e=String(e));const t=te(this);return Se(t,"has",e),t.hasOwnProperty(e)}class oi{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,s){const r=this._isReadonly,i=this._isShallow;if(n==="__v_isReactive")return!r;if(n==="__v_isReadonly")return r;if(n==="__v_isShallow")return i;if(n==="__v_raw")return s===(r?i?Zo:fi:i?ui:ci).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(s)?t:void 0;const o=q(t);if(!r){if(o&&ee(Qs,n))return Reflect.get(Qs,n,s);if(n==="hasOwnProperty")return Bo}const c=Reflect.get(t,n,s);return(ct(n)?ii.has(n):jo(n))||(r||Se(t,"get",n),i)?c:Ce(c)?o&&ws(n)?c:c.value:ce(c)?r?hi(c):Fn(c):c}}class li extends oi{constructor(t=!1){super(!1,t)}set(t,n,s,r){let i=t[n];if(!this._isShallow){const l=vt(i);if(!Ft(s)&&!vt(s)&&(i=te(i),s=te(s)),!q(t)&&Ce(i)&&!Ce(s))return l?!1:(i.value=s,!0)}const o=q(t)&&ws(n)?Number(n)e,Nn=e=>Reflect.getPrototypeOf(e);function ln(e,t,n=!1,s=!1){e=e.__v_raw;const r=te(e),i=te(t);n||(ot(t,i)&&Se(r,"get",t),Se(r,"get",i));const{has:o}=Nn(r),c=s?Ps:n?Is:Gt;if(o.call(r,t))return c(e.get(t));if(o.call(r,i))return c(e.get(i));e!==r&&e.get(t)}function cn(e,t=!1){const n=this.__v_raw,s=te(n),r=te(e);return t||(ot(e,r)&&Se(s,"has",e),Se(s,"has",r)),e===r?n.has(e):n.has(e)||n.has(r)}function un(e,t=!1){return e=e.__v_raw,!t&&Se(te(e),"iterate",_t),Reflect.get(e,"size",e)}function Js(e,t=!1){!t&&!Ft(e)&&!vt(e)&&(e=te(e));const n=te(this);return Nn(n).has.call(n,e)||(n.add(e),qe(n,"add",e,e)),this}function Ys(e,t,n=!1){!n&&!Ft(t)&&!vt(t)&&(t=te(t));const s=te(this),{has:r,get:i}=Nn(s);let o=r.call(s,e);o||(e=te(e),o=r.call(s,e));const c=i.call(s,e);return s.set(e,t),o?ot(t,c)&&qe(s,"set",e,t):qe(s,"add",e,t),this}function Xs(e){const t=te(this),{has:n,get:s}=Nn(t);let r=n.call(t,e);r||(e=te(e),r=n.call(t,e)),s&&s.call(t,e);const i=t.delete(e);return r&&qe(t,"delete",e,void 0),i}function Zs(){const e=te(this),t=e.size!==0,n=e.clear();return t&&qe(e,"clear",void 0,void 0),n}function fn(e,t){return function(s,r){const i=this,o=i.__v_raw,c=te(o),l=t?Ps:e?Is:Gt;return!e&&Se(c,"iterate",_t),o.forEach((a,h)=>s.call(r,l(a),l(h),i))}}function an(e,t,n){return function(...s){const r=this.__v_raw,i=te(r),o=Tt(i),c=e==="entries"||e===Symbol.iterator&&o,l=e==="keys"&&o,a=r[e](...s),h=n?Ps:t?Is:Gt;return!t&&Se(i,"iterate",l?ls:_t),{next(){const{value:d,done:p}=a.next();return p?{value:d,done:p}:{value:c?[h(d[0]),h(d[1])]:h(d),done:p}},[Symbol.iterator](){return this}}}}function Qe(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function Wo(){const e={get(i){return ln(this,i)},get size(){return un(this)},has:cn,add:Js,set:Ys,delete:Xs,clear:Zs,forEach:fn(!1,!1)},t={get(i){return ln(this,i,!1,!0)},get size(){return un(this)},has:cn,add(i){return Js.call(this,i,!0)},set(i,o){return Ys.call(this,i,o,!0)},delete:Xs,clear:Zs,forEach:fn(!1,!0)},n={get(i){return ln(this,i,!0)},get size(){return un(this,!0)},has(i){return cn.call(this,i,!0)},add:Qe("add"),set:Qe("set"),delete:Qe("delete"),clear:Qe("clear"),forEach:fn(!0,!1)},s={get(i){return ln(this,i,!0,!0)},get size(){return un(this,!0)},has(i){return cn.call(this,i,!0)},add:Qe("add"),set:Qe("set"),delete:Qe("delete"),clear:Qe("clear"),forEach:fn(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(i=>{e[i]=an(i,!1,!1),n[i]=an(i,!0,!1),t[i]=an(i,!1,!0),s[i]=an(i,!0,!0)}),[e,n,t,s]}const[qo,Go,zo,Qo]=Wo();function Os(e,t){const n=t?e?Qo:zo:e?Go:qo;return(s,r,i)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?s:Reflect.get(ee(n,r)&&r in s?n:s,r,i)}const Jo={get:Os(!1,!1)},Yo={get:Os(!1,!0)},Xo={get:Os(!0,!1)};const ci=new WeakMap,ui=new WeakMap,fi=new WeakMap,Zo=new WeakMap;function el(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function tl(e){return e.__v_skip||!Object.isExtensible(e)?0:el(Eo(e))}function Fn(e){return vt(e)?e:Ms(e,!1,Do,Jo,ci)}function ai(e){return Ms(e,!1,Ko,Yo,ui)}function hi(e){return Ms(e,!0,Uo,Xo,fi)}function Ms(e,t,n,s,r){if(!ce(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const i=r.get(e);if(i)return i;const o=tl(e);if(o===0)return e;const c=new Proxy(e,o===2?s:n);return r.set(e,c),c}function Pt(e){return vt(e)?Pt(e.__v_raw):!!(e&&e.__v_isReactive)}function vt(e){return!!(e&&e.__v_isReadonly)}function Ft(e){return!!(e&&e.__v_isShallow)}function di(e){return e?!!e.__v_raw:!1}function te(e){const t=e&&e.__v_raw;return t?te(t):e}function nl(e){return Object.isExtensible(e)&&Qr(e,"__v_skip",!0),e}const Gt=e=>ce(e)?Fn(e):e,Is=e=>ce(e)?hi(e):e;class pi{constructor(t,n,s,r){this.getter=t,this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this.effect=new Rs(()=>t(this._value),()=>mn(this,this.effect._dirtyLevel===2?2:3)),this.effect.computed=this,this.effect.active=this._cacheable=!r,this.__v_isReadonly=s}get value(){const t=te(this);return(!t._cacheable||t.effect.dirty)&&ot(t._value,t._value=t.effect.run())&&mn(t,4),gi(t),t.effect._dirtyLevel>=2&&mn(t,2),t._value}set value(t){this._setter(t)}get _dirty(){return this.effect.dirty}set _dirty(t){this.effect.dirty=t}}function sl(e,t,n=!1){let s,r;const i=z(e);return i?(s=e,r=Ae):(s=e.get,r=e.set),new pi(s,r,i||!r,n)}function gi(e){var t;rt&&yt&&(e=te(e),ni(yt,(t=e.dep)!=null?t:e.dep=ri(()=>e.dep=void 0,e instanceof pi?e:void 0)))}function mn(e,t=4,n,s){e=te(e);const r=e.dep;r&&si(r,t)}function Ce(e){return!!(e&&e.__v_isRef===!0)}function yn(e){return mi(e,!1)}function rl(e){return mi(e,!0)}function mi(e,t){return Ce(e)?e:new il(e,t)}class il{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:te(t),this._value=n?t:Gt(t)}get value(){return gi(this),this._value}set value(t){const n=this.__v_isShallow||Ft(t)||vt(t);t=n?t:te(t),ot(t,this._rawValue)&&(this._rawValue,this._rawValue=t,this._value=n?t:Gt(t),mn(this,4))}}function Ot(e){return Ce(e)?e.value:e}const ol={get:(e,t,n)=>Ot(Reflect.get(e,t,n)),set:(e,t,n,s)=>{const r=e[t];return Ce(r)&&!Ce(n)?(r.value=n,!0):Reflect.set(e,t,n,s)}};function yi(e){return Pt(e)?e:new Proxy(e,ol)}function ef(e){const t=q(e)?new Array(e.length):{};for(const n in e)t[n]=cl(e,n);return t}class ll{constructor(t,n,s){this._object=t,this._key=n,this._defaultValue=s,this.__v_isRef=!0}get value(){const t=this._object[this._key];return t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return Ho(te(this._object),this._key)}}function cl(e,t,n){const s=e[t];return Ce(s)?s:new ll(e,t,n)}/** +* @vue/runtime-core v3.4.38 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function it(e,t,n,s){try{return s?e(...s):e()}catch(r){tn(r,t,n)}}function Ne(e,t,n,s){if(z(e)){const r=it(e,t,n,s);return r&&qr(r)&&r.catch(i=>{tn(i,t,n)}),r}if(q(e)){const r=[];for(let i=0;i>>1,r=_e[s],i=Qt(r);iUe&&_e.splice(t,1)}function hl(e){q(e)?Mt.push(...e):(!Ze||!Ze.includes(e,e.allowRecurse?gt+1:gt))&&Mt.push(e),vi()}function er(e,t,n=zt?Ue+1:0){for(;n<_e.length;n++){const s=_e[n];if(s&&s.pre){if(e&&s.id!==e.uid)continue;_e.splice(n,1),n--,s()}}}function wn(e){if(Mt.length){const t=[...new Set(Mt)].sort((n,s)=>Qt(n)-Qt(s));if(Mt.length=0,Ze){Ze.push(...t);return}for(Ze=t,gt=0;gte.id==null?1/0:e.id,dl=(e,t)=>{const n=Qt(e)-Qt(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function Ei(e){cs=!1,zt=!0,_e.sort(dl);const t=Ae;try{for(Ue=0;Ue<_e.length;Ue++){const n=_e[Ue];n&&n.active!==!1&&it(n,n.i,n.i?15:14)}}finally{Ue=0,_e.length=0,wn(),zt=!1,Ls=null,(_e.length||Mt.length)&&Ei()}}let me=null,Hn=null;function xn(e){const t=me;return me=e,Hn=e&&e.type.__scopeId||null,t}function tf(e){Hn=e}function nf(){Hn=null}function pl(e,t=me,n){if(!t||e._n)return e;const s=(...r)=>{s._d&&ar(-1);const i=xn(t);let o;try{o=e(...r)}finally{xn(i),s._d&&ar(1)}return o};return s._n=!0,s._c=!0,s._d=!0,s}function sf(e,t){if(me===null)return e;const n=Vn(me),s=e.dirs||(e.dirs=[]);for(let r=0;r{e.isMounted=!0}),Ai(()=>{e.isUnmounting=!0}),e}const Me=[Function,Array],Ci={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Me,onEnter:Me,onAfterEnter:Me,onEnterCancelled:Me,onBeforeLeave:Me,onLeave:Me,onAfterLeave:Me,onLeaveCancelled:Me,onBeforeAppear:Me,onAppear:Me,onAfterAppear:Me,onAppearCancelled:Me},wi=e=>{const t=e.subTree;return t.component?wi(t.component):t},ml={name:"BaseTransition",props:Ci,setup(e,{slots:t}){const n=eo(),s=gl();return()=>{const r=t.default&&Si(t.default(),!0);if(!r||!r.length)return;let i=r[0];if(r.length>1){for(const p of r)if(p.type!==Ee){i=p;break}}const o=te(e),{mode:c}=o;if(s.isLeaving)return Gn(i);const l=tr(i);if(!l)return Gn(i);let a=us(l,o,s,n,p=>a=p);Sn(l,a);const h=n.subTree,d=h&&tr(h);if(d&&d.type!==Ee&&!mt(l,d)&&wi(n).type!==Ee){const p=us(d,o,s,n);if(Sn(d,p),c==="out-in"&&l.type!==Ee)return s.isLeaving=!0,p.afterLeave=()=>{s.isLeaving=!1,n.update.active!==!1&&(n.effect.dirty=!0,n.update())},Gn(i);c==="in-out"&&l.type!==Ee&&(p.delayLeave=(m,x,P)=>{const j=xi(s,d);j[String(d.key)]=d,m[et]=()=>{x(),m[et]=void 0,delete a.delayedLeave},a.delayedLeave=P})}return i}}},yl=ml;function xi(e,t){const{leavingVNodes:n}=e;let s=n.get(t.type);return s||(s=Object.create(null),n.set(t.type,s)),s}function us(e,t,n,s,r){const{appear:i,mode:o,persisted:c=!1,onBeforeEnter:l,onEnter:a,onAfterEnter:h,onEnterCancelled:d,onBeforeLeave:p,onLeave:m,onAfterLeave:x,onLeaveCancelled:P,onBeforeAppear:j,onAppear:B,onAfterAppear:O,onAppearCancelled:g}=t,E=String(e.key),H=xi(n,e),T=($,L)=>{$&&Ne($,s,9,L)},K=($,L)=>{const Q=L[1];T($,L),q($)?$.every(A=>A.length<=1)&&Q():$.length<=1&&Q()},X={mode:o,persisted:c,beforeEnter($){let L=l;if(!n.isMounted)if(i)L=j||l;else return;$[et]&&$[et](!0);const Q=H[E];Q&&mt(e,Q)&&Q.el[et]&&Q.el[et](),T(L,[$])},enter($){let L=a,Q=h,A=d;if(!n.isMounted)if(i)L=B||a,Q=O||h,A=g||d;else return;let G=!1;const ie=$[hn]=oe=>{G||(G=!0,oe?T(A,[$]):T(Q,[$]),X.delayedLeave&&X.delayedLeave(),$[hn]=void 0)};L?K(L,[$,ie]):ie()},leave($,L){const Q=String(e.key);if($[hn]&&$[hn](!0),n.isUnmounting)return L();T(p,[$]);let A=!1;const G=$[et]=ie=>{A||(A=!0,L(),ie?T(P,[$]):T(x,[$]),$[et]=void 0,H[Q]===e&&delete H[Q])};H[Q]=e,m?K(m,[$,G]):G()},clone($){const L=us($,t,n,s,r);return r&&r(L),L}};return X}function Gn(e){if(nn(e))return e=lt(e),e.children=null,e}function tr(e){if(!nn(e))return e;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&z(n.default))return n.default()}}function Sn(e,t){e.shapeFlag&6&&e.component?Sn(e.component.subTree,t):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Si(e,t=!1,n){let s=[],r=0;for(let i=0;i1)for(let i=0;iye({name:e.name},t,{setup:e}))():e}const It=e=>!!e.type.__asyncLoader;/*! #__NO_SIDE_EFFECTS__ */function rf(e){z(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:s,delay:r=200,timeout:i,suspensible:o=!0,onError:c}=e;let l=null,a,h=0;const d=()=>(h++,l=null,p()),p=()=>{let m;return l||(m=l=t().catch(x=>{if(x=x instanceof Error?x:new Error(String(x)),c)return new Promise((P,j)=>{c(x,()=>P(d()),()=>j(x),h+1)});throw x}).then(x=>m!==l&&l?l:(x&&(x.__esModule||x[Symbol.toStringTag]==="Module")&&(x=x.default),a=x,x)))};return Ns({name:"AsyncComponentWrapper",__asyncLoader:p,get __asyncResolved(){return a},setup(){const m=ge;if(a)return()=>zn(a,m);const x=O=>{l=null,tn(O,m,13,!s)};if(o&&m.suspense||rn)return p().then(O=>()=>zn(O,m)).catch(O=>(x(O),()=>s?ae(s,{error:O}):null));const P=yn(!1),j=yn(),B=yn(!!r);return r&&setTimeout(()=>{B.value=!1},r),i!=null&&setTimeout(()=>{if(!P.value&&!j.value){const O=new Error(`Async component timed out after ${i}ms.`);x(O),j.value=O}},i),p().then(()=>{P.value=!0,m.parent&&nn(m.parent.vnode)&&(m.parent.effect.dirty=!0,$n(m.parent.update))}).catch(O=>{x(O),j.value=O}),()=>{if(P.value&&a)return zn(a,m);if(j.value&&s)return ae(s,{error:j.value});if(n&&!B.value)return ae(n)}}})}function zn(e,t){const{ref:n,props:s,children:r,ce:i}=t.vnode,o=ae(e,s,r);return o.ref=n,o.ce=i,delete t.vnode.ce,o}const nn=e=>e.type.__isKeepAlive;function _l(e,t){Ri(e,"a",t)}function bl(e,t){Ri(e,"da",t)}function Ri(e,t,n=ge){const s=e.__wdc||(e.__wdc=()=>{let r=n;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(jn(t,s,n),n){let r=n.parent;for(;r&&r.parent;)nn(r.parent.vnode)&&vl(s,t,n,r),r=r.parent}}function vl(e,t,n,s){const r=jn(t,e,s,!0);$s(()=>{Cs(s[t],r)},n)}function jn(e,t,n=ge,s=!1){if(n){const r=n[e]||(n[e]=[]),i=t.__weh||(t.__weh=(...o)=>{ft();const c=sn(n),l=Ne(t,n,e,o);return c(),at(),l});return s?r.unshift(i):r.push(i),i}}const Ge=e=>(t,n=ge)=>{(!rn||e==="sp")&&jn(e,(...s)=>t(...s),n)},Ti=Ge("bm"),Fs=Ge("m"),El=Ge("bu"),Cl=Ge("u"),Ai=Ge("bum"),$s=Ge("um"),wl=Ge("sp"),xl=Ge("rtg"),Sl=Ge("rtc");function Rl(e,t=ge){jn("ec",e,t)}const Pi="components";function of(e,t){return Al(Pi,e,!0,t)||e}const Tl=Symbol.for("v-ndc");function Al(e,t,n=!0,s=!1){const r=me||ge;if(r){const i=r.type;if(e===Pi){const c=bc(i,!1);if(c&&(c===t||c===je(t)||c===Ln(je(t))))return i}const o=nr(r[e]||i[e],t)||nr(r.appContext[e],t);return!o&&s?i:o}}function nr(e,t){return e&&(e[t]||e[je(t)]||e[Ln(je(t))])}function lf(e,t,n,s){let r;const i=n&&n[s];if(q(e)||he(e)){r=new Array(e.length);for(let o=0,c=e.length;ot(o,c,void 0,i&&i[c]));else{const o=Object.keys(e);r=new Array(o.length);for(let c=0,l=o.length;cAn(t)?!(t.type===Ee||t.type===ve&&!Oi(t.children)):!0)?e:null}const fs=e=>e?to(e)?Vn(e):fs(e.parent):null,Ut=ye(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>fs(e.parent),$root:e=>fs(e.root),$emit:e=>e.emit,$options:e=>Hs(e),$forceUpdate:e=>e.f||(e.f=()=>{e.effect.dirty=!0,$n(e.update)}),$nextTick:e=>e.n||(e.n=bi.bind(e.proxy)),$watch:e=>ec.bind(e)}),Qn=(e,t)=>e!==fe&&!e.__isScriptSetup&&ee(e,t),Pl={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:s,data:r,props:i,accessCache:o,type:c,appContext:l}=e;let a;if(t[0]!=="$"){const m=o[t];if(m!==void 0)switch(m){case 1:return s[t];case 2:return r[t];case 4:return n[t];case 3:return i[t]}else{if(Qn(s,t))return o[t]=1,s[t];if(r!==fe&&ee(r,t))return o[t]=2,r[t];if((a=e.propsOptions[0])&&ee(a,t))return o[t]=3,i[t];if(n!==fe&&ee(n,t))return o[t]=4,n[t];as&&(o[t]=0)}}const h=Ut[t];let d,p;if(h)return t==="$attrs"&&Se(e.attrs,"get",""),h(e);if((d=c.__cssModules)&&(d=d[t]))return d;if(n!==fe&&ee(n,t))return o[t]=4,n[t];if(p=l.config.globalProperties,ee(p,t))return p[t]},set({_:e},t,n){const{data:s,setupState:r,ctx:i}=e;return Qn(r,t)?(r[t]=n,!0):s!==fe&&ee(s,t)?(s[t]=n,!0):ee(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(i[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:s,appContext:r,propsOptions:i}},o){let c;return!!n[o]||e!==fe&&ee(e,o)||Qn(t,o)||(c=i[0])&&ee(c,o)||ee(s,o)||ee(Ut,o)||ee(r.config.globalProperties,o)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:ee(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function sr(e){return q(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let as=!0;function Ol(e){const t=Hs(e),n=e.proxy,s=e.ctx;as=!1,t.beforeCreate&&rr(t.beforeCreate,e,"bc");const{data:r,computed:i,methods:o,watch:c,provide:l,inject:a,created:h,beforeMount:d,mounted:p,beforeUpdate:m,updated:x,activated:P,deactivated:j,beforeDestroy:B,beforeUnmount:O,destroyed:g,unmounted:E,render:H,renderTracked:T,renderTriggered:K,errorCaptured:X,serverPrefetch:$,expose:L,inheritAttrs:Q,components:A,directives:G,filters:ie}=t;if(a&&Ml(a,s,null),o)for(const Y in o){const W=o[Y];z(W)&&(s[Y]=W.bind(n))}if(r){const Y=r.call(n,n);ce(Y)&&(e.data=Fn(Y))}if(as=!0,i)for(const Y in i){const W=i[Y],de=z(W)?W.bind(n,n):z(W.get)?W.get.bind(n,n):Ae,ze=!z(W)&&z(W.set)?W.set.bind(n):Ae,Be=$e({get:de,set:ze});Object.defineProperty(s,Y,{enumerable:!0,configurable:!0,get:()=>Be.value,set:we=>Be.value=we})}if(c)for(const Y in c)Mi(c[Y],s,n,Y);if(l){const Y=z(l)?l.call(n):l;Reflect.ownKeys(Y).forEach(W=>{_n(W,Y[W])})}h&&rr(h,e,"c");function D(Y,W){q(W)?W.forEach(de=>Y(de.bind(n))):W&&Y(W.bind(n))}if(D(Ti,d),D(Fs,p),D(El,m),D(Cl,x),D(_l,P),D(bl,j),D(Rl,X),D(Sl,T),D(xl,K),D(Ai,O),D($s,E),D(wl,$),q(L))if(L.length){const Y=e.exposed||(e.exposed={});L.forEach(W=>{Object.defineProperty(Y,W,{get:()=>n[W],set:de=>n[W]=de})})}else e.exposed||(e.exposed={});H&&e.render===Ae&&(e.render=H),Q!=null&&(e.inheritAttrs=Q),A&&(e.components=A),G&&(e.directives=G)}function Ml(e,t,n=Ae){q(e)&&(e=hs(e));for(const s in e){const r=e[s];let i;ce(r)?"default"in r?i=He(r.from||s,r.default,!0):i=He(r.from||s):i=He(r),Ce(i)?Object.defineProperty(t,s,{enumerable:!0,configurable:!0,get:()=>i.value,set:o=>i.value=o}):t[s]=i}}function rr(e,t,n){Ne(q(e)?e.map(s=>s.bind(t.proxy)):e.bind(t.proxy),t,n)}function Mi(e,t,n,s){const r=s.includes(".")?Wi(n,s):()=>n[s];if(he(e)){const i=t[e];z(i)&&bn(r,i)}else if(z(e))bn(r,e.bind(n));else if(ce(e))if(q(e))e.forEach(i=>Mi(i,t,n,s));else{const i=z(e.handler)?e.handler.bind(n):t[e.handler];z(i)&&bn(r,i,e)}}function Hs(e){const t=e.type,{mixins:n,extends:s}=t,{mixins:r,optionsCache:i,config:{optionMergeStrategies:o}}=e.appContext,c=i.get(t);let l;return c?l=c:!r.length&&!n&&!s?l=t:(l={},r.length&&r.forEach(a=>Rn(l,a,o,!0)),Rn(l,t,o)),ce(t)&&i.set(t,l),l}function Rn(e,t,n,s=!1){const{mixins:r,extends:i}=t;i&&Rn(e,i,n,!0),r&&r.forEach(o=>Rn(e,o,n,!0));for(const o in t)if(!(s&&o==="expose")){const c=Il[o]||n&&n[o];e[o]=c?c(e[o],t[o]):t[o]}return e}const Il={data:ir,props:or,emits:or,methods:Dt,computed:Dt,beforeCreate:be,created:be,beforeMount:be,mounted:be,beforeUpdate:be,updated:be,beforeDestroy:be,beforeUnmount:be,destroyed:be,unmounted:be,activated:be,deactivated:be,errorCaptured:be,serverPrefetch:be,components:Dt,directives:Dt,watch:Nl,provide:ir,inject:Ll};function ir(e,t){return t?e?function(){return ye(z(e)?e.call(this,this):e,z(t)?t.call(this,this):t)}:t:e}function Ll(e,t){return Dt(hs(e),hs(t))}function hs(e){if(q(e)){const t={};for(let n=0;n1)return n&&z(t)?t.call(s&&s.proxy):t}}const Li={},Ni=()=>Object.create(Li),Fi=e=>Object.getPrototypeOf(e)===Li;function Hl(e,t,n,s=!1){const r={},i=Ni();e.propsDefaults=Object.create(null),$i(e,t,r,i);for(const o in e.propsOptions[0])o in r||(r[o]=void 0);n?e.props=s?r:ai(r):e.type.props?e.props=r:e.props=i,e.attrs=i}function jl(e,t,n,s){const{props:r,attrs:i,vnode:{patchFlag:o}}=e,c=te(r),[l]=e.propsOptions;let a=!1;if((s||o>0)&&!(o&16)){if(o&8){const h=e.vnode.dynamicProps;for(let d=0;d{l=!0;const[p,m]=Hi(d,t,!0);ye(o,p),m&&c.push(...m)};!n&&t.mixins.length&&t.mixins.forEach(h),e.extends&&h(e.extends),e.mixins&&e.mixins.forEach(h)}if(!i&&!l)return ce(e)&&s.set(e,Rt),Rt;if(q(i))for(let h=0;he[0]==="_"||e==="$stable",js=e=>q(e)?e.map(Le):[Le(e)],Bl=(e,t,n)=>{if(t._n)return t;const s=pl((...r)=>js(t(...r)),n);return s._c=!1,s},ki=(e,t,n)=>{const s=e._ctx;for(const r in e){if(ji(r))continue;const i=e[r];if(z(i))t[r]=Bl(r,i,s);else if(i!=null){const o=js(i);t[r]=()=>o}}},Bi=(e,t)=>{const n=js(t);e.slots.default=()=>n},Vi=(e,t,n)=>{for(const s in t)(n||s!=="_")&&(e[s]=t[s])},Vl=(e,t,n)=>{const s=e.slots=Ni();if(e.vnode.shapeFlag&32){const r=t._;r?(Vi(s,t,n),n&&Qr(s,"_",r,!0)):ki(t,s)}else t&&Bi(e,t)},Dl=(e,t,n)=>{const{vnode:s,slots:r}=e;let i=!0,o=fe;if(s.shapeFlag&32){const c=t._;c?n&&c===1?i=!1:Vi(r,t,n):(i=!t.$stable,ki(t,r)),o=t}else t&&(Bi(e,t),o={default:1});if(i)for(const c in r)!ji(c)&&o[c]==null&&delete r[c]};function Tn(e,t,n,s,r=!1){if(q(e)){e.forEach((p,m)=>Tn(p,t&&(q(t)?t[m]:t),n,s,r));return}if(It(s)&&!r)return;const i=s.shapeFlag&4?Vn(s.component):s.el,o=r?null:i,{i:c,r:l}=e,a=t&&t.r,h=c.refs===fe?c.refs={}:c.refs,d=c.setupState;if(a!=null&&a!==l&&(he(a)?(h[a]=null,ee(d,a)&&(d[a]=null)):Ce(a)&&(a.value=null)),z(l))it(l,c,12,[o,h]);else{const p=he(l),m=Ce(l);if(p||m){const x=()=>{if(e.f){const P=p?ee(d,l)?d[l]:h[l]:l.value;r?q(P)&&Cs(P,i):q(P)?P.includes(i)||P.push(i):p?(h[l]=[i],ee(d,l)&&(d[l]=h[l])):(l.value=[i],e.k&&(h[e.k]=l.value))}else p?(h[l]=o,ee(d,l)&&(d[l]=o)):m&&(l.value=o,e.k&&(h[e.k]=o))};o?(x.id=-1,xe(x,n)):x()}}}const Ul=Symbol("_vte"),Kl=e=>e.__isTeleport;let cr=!1;const xt=()=>{cr||(console.error("Hydration completed but contains mismatches."),cr=!0)},Wl=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",ql=e=>e.namespaceURI.includes("MathML"),dn=e=>{if(Wl(e))return"svg";if(ql(e))return"mathml"},pn=e=>e.nodeType===8;function Gl(e){const{mt:t,p:n,o:{patchProp:s,createText:r,nextSibling:i,parentNode:o,remove:c,insert:l,createComment:a}}=e,h=(g,E)=>{if(!E.hasChildNodes()){n(null,g,E),wn(),E._vnode=g;return}d(E.firstChild,g,null,null,null),wn(),E._vnode=g},d=(g,E,H,T,K,X=!1)=>{X=X||!!E.dynamicChildren;const $=pn(g)&&g.data==="[",L=()=>P(g,E,H,T,K,$),{type:Q,ref:A,shapeFlag:G,patchFlag:ie}=E;let oe=g.nodeType;E.el=g,ie===-2&&(X=!1,E.dynamicChildren=null);let D=null;switch(Q){case bt:oe!==3?E.children===""?(l(E.el=r(""),o(g),g),D=g):D=L():(g.data!==E.children&&(xt(),g.data=E.children),D=i(g));break;case Ee:O(g)?(D=i(g),B(E.el=g.content.firstChild,g,H)):oe!==8||$?D=L():D=i(g);break;case Nt:if($&&(g=i(g),oe=g.nodeType),oe===1||oe===3){D=g;const Y=!E.children.length;for(let W=0;W{X=X||!!E.dynamicChildren;const{type:$,props:L,patchFlag:Q,shapeFlag:A,dirs:G,transition:ie}=E,oe=$==="input"||$==="option";if(oe||Q!==-1){G&&De(E,null,H,"created");let D=!1;if(O(g)){D=Di(T,ie)&&H&&H.vnode.props&&H.vnode.props.appear;const W=g.content.firstChild;D&&ie.beforeEnter(W),B(W,g,H),E.el=g=W}if(A&16&&!(L&&(L.innerHTML||L.textContent))){let W=m(g.firstChild,E,g,H,T,K,X);for(;W;){xt();const de=W;W=W.nextSibling,c(de)}}else A&8&&g.textContent!==E.children&&(xt(),g.textContent=E.children);if(L){if(oe||!X||Q&48){const W=g.tagName.includes("-");for(const de in L)(oe&&(de.endsWith("value")||de==="indeterminate")||en(de)&&!At(de)||de[0]==="."||W)&&s(g,de,null,L[de],void 0,H)}else if(L.onClick)s(g,"onClick",null,L.onClick,void 0,H);else if(Q&4&&Pt(L.style))for(const W in L.style)L.style[W]}let Y;(Y=L&&L.onVnodeBeforeMount)&&Ie(Y,H,E),G&&De(E,null,H,"beforeMount"),((Y=L&&L.onVnodeMounted)||G||D)&&Gi(()=>{Y&&Ie(Y,H,E),D&&ie.enter(g),G&&De(E,null,H,"mounted")},T)}return g.nextSibling},m=(g,E,H,T,K,X,$)=>{$=$||!!E.dynamicChildren;const L=E.children,Q=L.length;for(let A=0;A{const{slotScopeIds:$}=E;$&&(K=K?K.concat($):$);const L=o(g),Q=m(i(g),E,L,H,T,K,X);return Q&&pn(Q)&&Q.data==="]"?i(E.anchor=Q):(xt(),l(E.anchor=a("]"),L,Q),Q)},P=(g,E,H,T,K,X)=>{if(xt(),E.el=null,X){const Q=j(g);for(;;){const A=i(g);if(A&&A!==Q)c(A);else break}}const $=i(g),L=o(g);return c(g),n(null,E,L,$,H,T,dn(L),K),$},j=(g,E="[",H="]")=>{let T=0;for(;g;)if(g=i(g),g&&pn(g)&&(g.data===E&&T++,g.data===H)){if(T===0)return i(g);T--}return g},B=(g,E,H)=>{const T=E.parentNode;T&&T.replaceChild(g,E);let K=H;for(;K;)K.vnode.el===E&&(K.vnode.el=K.subTree.el=g),K=K.parent},O=g=>g.nodeType===1&&g.tagName.toLowerCase()==="template";return[h,d]}const xe=Gi;function zl(e){return Ql(e,Gl)}function Ql(e,t){const n=Jr();n.__VUE__=!0;const{insert:s,remove:r,patchProp:i,createElement:o,createText:c,createComment:l,setText:a,setElementText:h,parentNode:d,nextSibling:p,setScopeId:m=Ae,insertStaticContent:x}=e,P=(u,f,y,v=null,_=null,w=null,M=void 0,S=null,R=!!f.dynamicChildren)=>{if(u===f)return;u&&!mt(u,f)&&(v=b(u),we(u,_,w,!0),u=null),f.patchFlag===-2&&(R=!1,f.dynamicChildren=null);const{type:C,ref:F,shapeFlag:U}=f;switch(C){case bt:j(u,f,y,v);break;case Ee:B(u,f,y,v);break;case Nt:u==null&&O(f,y,v,M);break;case ve:A(u,f,y,v,_,w,M,S,R);break;default:U&1?H(u,f,y,v,_,w,M,S,R):U&6?G(u,f,y,v,_,w,M,S,R):(U&64||U&128)&&C.process(u,f,y,v,_,w,M,S,R,k)}F!=null&&_&&Tn(F,u&&u.ref,w,f||u,!f)},j=(u,f,y,v)=>{if(u==null)s(f.el=c(f.children),y,v);else{const _=f.el=u.el;f.children!==u.children&&a(_,f.children)}},B=(u,f,y,v)=>{u==null?s(f.el=l(f.children||""),y,v):f.el=u.el},O=(u,f,y,v)=>{[u.el,u.anchor]=x(u.children,f,y,v,u.el,u.anchor)},g=({el:u,anchor:f},y,v)=>{let _;for(;u&&u!==f;)_=p(u),s(u,y,v),u=_;s(f,y,v)},E=({el:u,anchor:f})=>{let y;for(;u&&u!==f;)y=p(u),r(u),u=y;r(f)},H=(u,f,y,v,_,w,M,S,R)=>{f.type==="svg"?M="svg":f.type==="math"&&(M="mathml"),u==null?T(f,y,v,_,w,M,S,R):$(u,f,_,w,M,S,R)},T=(u,f,y,v,_,w,M,S)=>{let R,C;const{props:F,shapeFlag:U,transition:V,dirs:J}=u;if(R=u.el=o(u.type,w,F&&F.is,F),U&8?h(R,u.children):U&16&&X(u.children,R,null,v,_,Jn(u,w),M,S),J&&De(u,null,v,"created"),K(R,u,u.scopeId,M,v),F){for(const le in F)le!=="value"&&!At(le)&&i(R,le,null,F[le],w,v);"value"in F&&i(R,"value",null,F.value,w),(C=F.onVnodeBeforeMount)&&Ie(C,v,u)}J&&De(u,null,v,"beforeMount");const Z=Di(_,V);Z&&V.beforeEnter(R),s(R,f,y),((C=F&&F.onVnodeMounted)||Z||J)&&xe(()=>{C&&Ie(C,v,u),Z&&V.enter(R),J&&De(u,null,v,"mounted")},_)},K=(u,f,y,v,_)=>{if(y&&m(u,y),v)for(let w=0;w{for(let C=R;C{const S=f.el=u.el;let{patchFlag:R,dynamicChildren:C,dirs:F}=f;R|=u.patchFlag&16;const U=u.props||fe,V=f.props||fe;let J;if(y&&ht(y,!1),(J=V.onVnodeBeforeUpdate)&&Ie(J,y,f,u),F&&De(f,u,y,"beforeUpdate"),y&&ht(y,!0),(U.innerHTML&&V.innerHTML==null||U.textContent&&V.textContent==null)&&h(S,""),C?L(u.dynamicChildren,C,S,y,v,Jn(f,_),w):M||W(u,f,S,null,y,v,Jn(f,_),w,!1),R>0){if(R&16)Q(S,U,V,y,_);else if(R&2&&U.class!==V.class&&i(S,"class",null,V.class,_),R&4&&i(S,"style",U.style,V.style,_),R&8){const Z=f.dynamicProps;for(let le=0;le{J&&Ie(J,y,f,u),F&&De(f,u,y,"updated")},v)},L=(u,f,y,v,_,w,M)=>{for(let S=0;S{if(f!==y){if(f!==fe)for(const w in f)!At(w)&&!(w in y)&&i(u,w,f[w],null,_,v);for(const w in y){if(At(w))continue;const M=y[w],S=f[w];M!==S&&w!=="value"&&i(u,w,S,M,_,v)}"value"in y&&i(u,"value",f.value,y.value,_)}},A=(u,f,y,v,_,w,M,S,R)=>{const C=f.el=u?u.el:c(""),F=f.anchor=u?u.anchor:c("");let{patchFlag:U,dynamicChildren:V,slotScopeIds:J}=f;J&&(S=S?S.concat(J):J),u==null?(s(C,y,v),s(F,y,v),X(f.children||[],y,F,_,w,M,S,R)):U>0&&U&64&&V&&u.dynamicChildren?(L(u.dynamicChildren,V,y,_,w,M,S),(f.key!=null||_&&f===_.subTree)&&Ui(u,f,!0)):W(u,f,y,F,_,w,M,S,R)},G=(u,f,y,v,_,w,M,S,R)=>{f.slotScopeIds=S,u==null?f.shapeFlag&512?_.ctx.activate(f,y,v,M,R):ie(f,y,v,_,w,M,R):oe(u,f,R)},ie=(u,f,y,v,_,w,M)=>{const S=u.component=pc(u,v,_);if(nn(u)&&(S.ctx.renderer=k),gc(S,!1,M),S.asyncDep){if(_&&_.registerDep(S,D,M),!u.el){const R=S.subTree=ae(Ee);B(null,R,f,y)}}else D(S,u,f,y,_,w,M)},oe=(u,f,y)=>{const v=f.component=u.component;if(ic(u,f,y))if(v.asyncDep&&!v.asyncResolved){Y(v,f,y);return}else v.next=f,al(v.update),v.effect.dirty=!0,v.update();else f.el=u.el,v.vnode=f},D=(u,f,y,v,_,w,M)=>{const S=()=>{if(u.isMounted){let{next:F,bu:U,u:V,parent:J,vnode:Z}=u;{const wt=Ki(u);if(wt){F&&(F.el=Z.el,Y(u,F,M)),wt.asyncDep.then(()=>{u.isUnmounted||S()});return}}let le=F,se;ht(u,!1),F?(F.el=Z.el,Y(u,F,M)):F=Z,U&&Wn(U),(se=F.props&&F.props.onVnodeBeforeUpdate)&&Ie(se,J,F,Z),ht(u,!0);const pe=Yn(u),Fe=u.subTree;u.subTree=pe,P(Fe,pe,d(Fe.el),b(Fe),u,_,w),F.el=pe.el,le===null&&oc(u,pe.el),V&&xe(V,_),(se=F.props&&F.props.onVnodeUpdated)&&xe(()=>Ie(se,J,F,Z),_)}else{let F;const{el:U,props:V}=f,{bm:J,m:Z,parent:le}=u,se=It(f);if(ht(u,!1),J&&Wn(J),!se&&(F=V&&V.onVnodeBeforeMount)&&Ie(F,le,f),ht(u,!0),U&&ue){const pe=()=>{u.subTree=Yn(u),ue(U,u.subTree,u,_,null)};se?f.type.__asyncLoader().then(()=>!u.isUnmounted&&pe()):pe()}else{const pe=u.subTree=Yn(u);P(null,pe,y,v,u,_,w),f.el=pe.el}if(Z&&xe(Z,_),!se&&(F=V&&V.onVnodeMounted)){const pe=f;xe(()=>Ie(F,le,pe),_)}(f.shapeFlag&256||le&&It(le.vnode)&&le.vnode.shapeFlag&256)&&u.a&&xe(u.a,_),u.isMounted=!0,f=y=v=null}},R=u.effect=new Rs(S,Ae,()=>$n(C),u.scope),C=u.update=()=>{R.dirty&&R.run()};C.i=u,C.id=u.uid,ht(u,!0),C()},Y=(u,f,y)=>{f.component=u;const v=u.vnode.props;u.vnode=f,u.next=null,jl(u,f.props,v,y),Dl(u,f.children,y),ft(),er(u),at()},W=(u,f,y,v,_,w,M,S,R=!1)=>{const C=u&&u.children,F=u?u.shapeFlag:0,U=f.children,{patchFlag:V,shapeFlag:J}=f;if(V>0){if(V&128){ze(C,U,y,v,_,w,M,S,R);return}else if(V&256){de(C,U,y,v,_,w,M,S,R);return}}J&8?(F&16&&Oe(C,_,w),U!==C&&h(y,U)):F&16?J&16?ze(C,U,y,v,_,w,M,S,R):Oe(C,_,w,!0):(F&8&&h(y,""),J&16&&X(U,y,v,_,w,M,S,R))},de=(u,f,y,v,_,w,M,S,R)=>{u=u||Rt,f=f||Rt;const C=u.length,F=f.length,U=Math.min(C,F);let V;for(V=0;VF?Oe(u,_,w,!0,!1,U):X(f,y,v,_,w,M,S,R,U)},ze=(u,f,y,v,_,w,M,S,R)=>{let C=0;const F=f.length;let U=u.length-1,V=F-1;for(;C<=U&&C<=V;){const J=u[C],Z=f[C]=R?tt(f[C]):Le(f[C]);if(mt(J,Z))P(J,Z,y,null,_,w,M,S,R);else break;C++}for(;C<=U&&C<=V;){const J=u[U],Z=f[V]=R?tt(f[V]):Le(f[V]);if(mt(J,Z))P(J,Z,y,null,_,w,M,S,R);else break;U--,V--}if(C>U){if(C<=V){const J=V+1,Z=JV)for(;C<=U;)we(u[C],_,w,!0),C++;else{const J=C,Z=C,le=new Map;for(C=Z;C<=V;C++){const Re=f[C]=R?tt(f[C]):Le(f[C]);Re.key!=null&&le.set(Re.key,C)}let se,pe=0;const Fe=V-Z+1;let wt=!1,Us=0;const jt=new Array(Fe);for(C=0;C=Fe){we(Re,_,w,!0);continue}let Ve;if(Re.key!=null)Ve=le.get(Re.key);else for(se=Z;se<=V;se++)if(jt[se-Z]===0&&mt(Re,f[se])){Ve=se;break}Ve===void 0?we(Re,_,w,!0):(jt[Ve-Z]=C+1,Ve>=Us?Us=Ve:wt=!0,P(Re,f[Ve],y,null,_,w,M,S,R),pe++)}const Ks=wt?Jl(jt):Rt;for(se=Ks.length-1,C=Fe-1;C>=0;C--){const Re=Z+C,Ve=f[Re],Ws=Re+1{const{el:w,type:M,transition:S,children:R,shapeFlag:C}=u;if(C&6){Be(u.component.subTree,f,y,v);return}if(C&128){u.suspense.move(f,y,v);return}if(C&64){M.move(u,f,y,k);return}if(M===ve){s(w,f,y);for(let U=0;US.enter(w),_);else{const{leave:U,delayLeave:V,afterLeave:J}=S,Z=()=>s(w,f,y),le=()=>{U(w,()=>{Z(),J&&J()})};V?V(w,Z,le):le()}else s(w,f,y)},we=(u,f,y,v=!1,_=!1)=>{const{type:w,props:M,ref:S,children:R,dynamicChildren:C,shapeFlag:F,patchFlag:U,dirs:V,cacheIndex:J}=u;if(U===-2&&(_=!1),S!=null&&Tn(S,null,y,u,!0),J!=null&&(f.renderCache[J]=void 0),F&256){f.ctx.deactivate(u);return}const Z=F&1&&V,le=!It(u);let se;if(le&&(se=M&&M.onVnodeBeforeUnmount)&&Ie(se,f,u),F&6)on(u.component,y,v);else{if(F&128){u.suspense.unmount(y,v);return}Z&&De(u,null,f,"beforeUnmount"),F&64?u.type.remove(u,f,y,k,v):C&&!C.hasOnce&&(w!==ve||U>0&&U&64)?Oe(C,f,y,!1,!0):(w===ve&&U&384||!_&&F&16)&&Oe(R,f,y),v&&Et(u)}(le&&(se=M&&M.onVnodeUnmounted)||Z)&&xe(()=>{se&&Ie(se,f,u),Z&&De(u,null,f,"unmounted")},y)},Et=u=>{const{type:f,el:y,anchor:v,transition:_}=u;if(f===ve){Ct(y,v);return}if(f===Nt){E(u);return}const w=()=>{r(y),_&&!_.persisted&&_.afterLeave&&_.afterLeave()};if(u.shapeFlag&1&&_&&!_.persisted){const{leave:M,delayLeave:S}=_,R=()=>M(y,w);S?S(u.el,w,R):R()}else w()},Ct=(u,f)=>{let y;for(;u!==f;)y=p(u),r(u),u=y;r(f)},on=(u,f,y)=>{const{bum:v,scope:_,update:w,subTree:M,um:S,m:R,a:C}=u;ur(R),ur(C),v&&Wn(v),_.stop(),w&&(w.active=!1,we(M,u,f,y)),S&&xe(S,f),xe(()=>{u.isUnmounted=!0},f),f&&f.pendingBranch&&!f.isUnmounted&&u.asyncDep&&!u.asyncResolved&&u.suspenseId===f.pendingId&&(f.deps--,f.deps===0&&f.resolve())},Oe=(u,f,y,v=!1,_=!1,w=0)=>{for(let M=w;M{if(u.shapeFlag&6)return b(u.component.subTree);if(u.shapeFlag&128)return u.suspense.next();const f=p(u.anchor||u.el),y=f&&f[Ul];return y?p(y):f};let N=!1;const I=(u,f,y)=>{u==null?f._vnode&&we(f._vnode,null,null,!0):P(f._vnode||null,u,f,null,null,null,y),f._vnode=u,N||(N=!0,er(),wn(),N=!1)},k={p:P,um:we,m:Be,r:Et,mt:ie,mc:X,pc:W,pbc:L,n:b,o:e};let ne,ue;return t&&([ne,ue]=t(k)),{render:I,hydrate:ne,createApp:$l(I,ne)}}function Jn({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function ht({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function Di(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function Ui(e,t,n=!1){const s=e.children,r=t.children;if(q(s)&&q(r))for(let i=0;i>1,e[n[c]]0&&(t[s]=n[i-1]),n[i]=s)}}for(i=n.length,o=n[i-1];i-- >0;)n[i]=o,o=t[o];return n}function Ki(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Ki(t)}function ur(e){if(e)for(let t=0;tHe(Yl);function uf(e,t){return kn(e,null,t)}function Zl(e,t){return kn(e,null,{flush:"post"})}const gn={};function bn(e,t,n){return kn(e,t,n)}function kn(e,t,{immediate:n,deep:s,flush:r,once:i,onTrack:o,onTrigger:c}=fe){if(t&&i){const T=t;t=(...K)=>{T(...K),H()}}const l=ge,a=T=>s===!0?T:st(T,s===!1?1:void 0);let h,d=!1,p=!1;if(Ce(e)?(h=()=>e.value,d=Ft(e)):Pt(e)?(h=()=>a(e),d=!0):q(e)?(p=!0,d=e.some(T=>Pt(T)||Ft(T)),h=()=>e.map(T=>{if(Ce(T))return T.value;if(Pt(T))return a(T);if(z(T))return it(T,l,2)})):z(e)?t?h=()=>it(e,l,2):h=()=>(m&&m(),Ne(e,l,3,[x])):h=Ae,t&&s){const T=h;h=()=>st(T())}let m,x=T=>{m=g.onStop=()=>{it(T,l,4),m=g.onStop=void 0}},P;if(rn)if(x=Ae,t?n&&Ne(t,l,3,[h(),p?[]:void 0,x]):h(),r==="sync"){const T=Xl();P=T.__watcherHandles||(T.__watcherHandles=[])}else return Ae;let j=p?new Array(e.length).fill(gn):gn;const B=()=>{if(!(!g.active||!g.dirty))if(t){const T=g.run();(s||d||(p?T.some((K,X)=>ot(K,j[X])):ot(T,j)))&&(m&&m(),Ne(t,l,3,[T,j===gn?void 0:p&&j[0]===gn?[]:j,x]),j=T)}else g.run()};B.allowRecurse=!!t;let O;r==="sync"?O=B:r==="post"?O=()=>xe(B,l&&l.suspense):(B.pre=!0,l&&(B.id=l.uid),O=()=>$n(B));const g=new Rs(h,Ae,O),E=Fo(),H=()=>{g.stop(),E&&Cs(E.effects,g)};return t?n?B():j=g.run():r==="post"?xe(g.run.bind(g),l&&l.suspense):g.run(),P&&P.push(H),H}function ec(e,t,n){const s=this.proxy,r=he(e)?e.includes(".")?Wi(s,e):()=>s[e]:e.bind(s,s);let i;z(t)?i=t:(i=t.handler,n=t);const o=sn(this),c=kn(r,i.bind(s),n);return o(),c}function Wi(e,t){const n=t.split(".");return()=>{let s=e;for(let r=0;r{st(s,t,n)});else if(zr(e)){for(const s in e)st(e[s],t,n);for(const s of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,s)&&st(e[s],t,n)}return e}const tc=(e,t)=>t==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${je(t)}Modifiers`]||e[`${ut(t)}Modifiers`];function nc(e,t,...n){if(e.isUnmounted)return;const s=e.vnode.props||fe;let r=n;const i=t.startsWith("update:"),o=i&&tc(s,t.slice(7));o&&(o.trim&&(r=n.map(h=>he(h)?h.trim():h)),o.number&&(r=n.map(xo)));let c,l=s[c=Kn(t)]||s[c=Kn(je(t))];!l&&i&&(l=s[c=Kn(ut(t))]),l&&Ne(l,e,6,r);const a=s[c+"Once"];if(a){if(!e.emitted)e.emitted={};else if(e.emitted[c])return;e.emitted[c]=!0,Ne(a,e,6,r)}}function qi(e,t,n=!1){const s=t.emitsCache,r=s.get(e);if(r!==void 0)return r;const i=e.emits;let o={},c=!1;if(!z(e)){const l=a=>{const h=qi(a,t,!0);h&&(c=!0,ye(o,h))};!n&&t.mixins.length&&t.mixins.forEach(l),e.extends&&l(e.extends),e.mixins&&e.mixins.forEach(l)}return!i&&!c?(ce(e)&&s.set(e,null),null):(q(i)?i.forEach(l=>o[l]=null):ye(o,i),ce(e)&&s.set(e,o),o)}function Bn(e,t){return!e||!en(t)?!1:(t=t.slice(2).replace(/Once$/,""),ee(e,t[0].toLowerCase()+t.slice(1))||ee(e,ut(t))||ee(e,t))}function Yn(e){const{type:t,vnode:n,proxy:s,withProxy:r,propsOptions:[i],slots:o,attrs:c,emit:l,render:a,renderCache:h,props:d,data:p,setupState:m,ctx:x,inheritAttrs:P}=e,j=xn(e);let B,O;try{if(n.shapeFlag&4){const E=r||s,H=E;B=Le(a.call(H,E,h,d,m,p,x)),O=c}else{const E=t;B=Le(E.length>1?E(d,{attrs:c,slots:o,emit:l}):E(d,null)),O=t.props?c:sc(c)}}catch(E){Kt.length=0,tn(E,e,1),B=ae(Ee)}let g=B;if(O&&P!==!1){const E=Object.keys(O),{shapeFlag:H}=g;E.length&&H&7&&(i&&E.some(Es)&&(O=rc(O,i)),g=lt(g,O,!1,!0))}return n.dirs&&(g=lt(g,null,!1,!0),g.dirs=g.dirs?g.dirs.concat(n.dirs):n.dirs),n.transition&&(g.transition=n.transition),B=g,xn(j),B}const sc=e=>{let t;for(const n in e)(n==="class"||n==="style"||en(n))&&((t||(t={}))[n]=e[n]);return t},rc=(e,t)=>{const n={};for(const s in e)(!Es(s)||!(s.slice(9)in t))&&(n[s]=e[s]);return n};function ic(e,t,n){const{props:s,children:r,component:i}=e,{props:o,children:c,patchFlag:l}=t,a=i.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&l>=0){if(l&1024)return!0;if(l&16)return s?fr(s,o,a):!!o;if(l&8){const h=t.dynamicProps;for(let d=0;de.__isSuspense;function Gi(e,t){t&&t.pendingBranch?q(e)?t.effects.push(...e):t.effects.push(e):hl(e)}const ve=Symbol.for("v-fgt"),bt=Symbol.for("v-txt"),Ee=Symbol.for("v-cmt"),Nt=Symbol.for("v-stc"),Kt=[];let Pe=null;function zi(e=!1){Kt.push(Pe=e?null:[])}function cc(){Kt.pop(),Pe=Kt[Kt.length-1]||null}let Jt=1;function ar(e){Jt+=e,e<0&&Pe&&(Pe.hasOnce=!0)}function Qi(e){return e.dynamicChildren=Jt>0?Pe||Rt:null,cc(),Jt>0&&Pe&&Pe.push(e),e}function ff(e,t,n,s,r,i){return Qi(Xi(e,t,n,s,r,i,!0))}function Ji(e,t,n,s,r){return Qi(ae(e,t,n,s,r,!0))}function An(e){return e?e.__v_isVNode===!0:!1}function mt(e,t){return e.type===t.type&&e.key===t.key}const Yi=({key:e})=>e??null,vn=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?he(e)||Ce(e)||z(e)?{i:me,r:e,k:t,f:!!n}:e:null);function Xi(e,t=null,n=null,s=0,r=null,i=e===ve?0:1,o=!1,c=!1){const l={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Yi(t),ref:t&&vn(t),scopeId:Hn,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:s,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:me};return c?(ks(l,n),i&128&&e.normalize(l)):n&&(l.shapeFlag|=he(n)?8:16),Jt>0&&!o&&Pe&&(l.patchFlag>0||i&6)&&l.patchFlag!==32&&Pe.push(l),l}const ae=uc;function uc(e,t=null,n=null,s=0,r=null,i=!1){if((!e||e===Tl)&&(e=Ee),An(e)){const c=lt(e,t,!0);return n&&ks(c,n),Jt>0&&!i&&Pe&&(c.shapeFlag&6?Pe[Pe.indexOf(e)]=c:Pe.push(c)),c.patchFlag=-2,c}if(vc(e)&&(e=e.__vccOpts),t){t=fc(t);let{class:c,style:l}=t;c&&!he(c)&&(t.class=Ss(c)),ce(l)&&(di(l)&&!q(l)&&(l=ye({},l)),t.style=xs(l))}const o=he(e)?1:lc(e)?128:Kl(e)?64:ce(e)?4:z(e)?2:0;return Xi(e,t,n,s,r,o,i,!0)}function fc(e){return e?di(e)||Fi(e)?ye({},e):e:null}function lt(e,t,n=!1,s=!1){const{props:r,ref:i,patchFlag:o,children:c,transition:l}=e,a=t?ac(r||{},t):r,h={__v_isVNode:!0,__v_skip:!0,type:e.type,props:a,key:a&&Yi(a),ref:t&&t.ref?n&&i?q(i)?i.concat(vn(t)):[i,vn(t)]:vn(t):i,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:c,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==ve?o===-1?16:o|16:o,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:l,component:e.component,suspense:e.suspense,ssContent:e.ssContent&<(e.ssContent),ssFallback:e.ssFallback&<(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return l&&s&&Sn(h,l.clone(h)),h}function Zi(e=" ",t=0){return ae(bt,null,e,t)}function af(e,t){const n=ae(Nt,null,e);return n.staticCount=t,n}function hf(e="",t=!1){return t?(zi(),Ji(Ee,null,e)):ae(Ee,null,e)}function Le(e){return e==null||typeof e=="boolean"?ae(Ee):q(e)?ae(ve,null,e.slice()):typeof e=="object"?tt(e):ae(bt,null,String(e))}function tt(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:lt(e)}function ks(e,t){let n=0;const{shapeFlag:s}=e;if(t==null)t=null;else if(q(t))n=16;else if(typeof t=="object")if(s&65){const r=t.default;r&&(r._c&&(r._d=!1),ks(e,r()),r._c&&(r._d=!0));return}else{n=32;const r=t._;!r&&!Fi(t)?t._ctx=me:r===3&&me&&(me.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else z(t)?(t={default:t,_ctx:me},n=32):(t=String(t),s&64?(n=16,t=[Zi(t)]):n=8);e.children=t,e.shapeFlag|=n}function ac(...e){const t={};for(let n=0;nge||me;let Pn,ps;{const e=Jr(),t=(n,s)=>{let r;return(r=e[n])||(r=e[n]=[]),r.push(s),i=>{r.length>1?r.forEach(o=>o(i)):r[0](i)}};Pn=t("__VUE_INSTANCE_SETTERS__",n=>ge=n),ps=t("__VUE_SSR_SETTERS__",n=>rn=n)}const sn=e=>{const t=ge;return Pn(e),e.scope.on(),()=>{e.scope.off(),Pn(t)}},hr=()=>{ge&&ge.scope.off(),Pn(null)};function to(e){return e.vnode.shapeFlag&4}let rn=!1;function gc(e,t=!1,n=!1){t&&ps(t);const{props:s,children:r}=e.vnode,i=to(e);Hl(e,s,i,t),Vl(e,r,n);const o=i?mc(e,t):void 0;return t&&ps(!1),o}function mc(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,Pl);const{setup:s}=n;if(s){const r=e.setupContext=s.length>1?_c(e):null,i=sn(e);ft();const o=it(s,e,0,[e.props,r]);if(at(),i(),qr(o)){if(o.then(hr,hr),t)return o.then(c=>{dr(e,c,t)}).catch(c=>{tn(c,e,0)});e.asyncDep=o}else dr(e,o,t)}else no(e,t)}function dr(e,t,n){z(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ce(t)&&(e.setupState=yi(t)),no(e,n)}let pr;function no(e,t,n){const s=e.type;if(!e.render){if(!t&&pr&&!s.render){const r=s.template||Hs(e).template;if(r){const{isCustomElement:i,compilerOptions:o}=e.appContext.config,{delimiters:c,compilerOptions:l}=s,a=ye(ye({isCustomElement:i,delimiters:c},o),l);s.render=pr(r,a)}}e.render=s.render||Ae}{const r=sn(e);ft();try{Ol(e)}finally{at(),r()}}}const yc={get(e,t){return Se(e,"get",""),e[t]}};function _c(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,yc),slots:e.slots,emit:e.emit,expose:t}}function Vn(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(yi(nl(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Ut)return Ut[n](e)},has(t,n){return n in t||n in Ut}})):e.proxy}function bc(e,t=!0){return z(e)?e.displayName||e.name:e.name||t&&e.__name}function vc(e){return z(e)&&"__vccOpts"in e}const $e=(e,t)=>sl(e,t,rn);function Bs(e,t,n){const s=arguments.length;return s===2?ce(t)&&!q(t)?An(t)?ae(e,null,[t]):ae(e,t):ae(e,null,t):(s>3?n=Array.prototype.slice.call(arguments,2):s===3&&An(n)&&(n=[n]),ae(e,t,n))}const Ec="3.4.38";/** +* @vue/runtime-dom v3.4.38 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/const Cc="http://www.w3.org/2000/svg",wc="http://www.w3.org/1998/Math/MathML",We=typeof document<"u"?document:null,gr=We&&We.createElement("template"),xc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const r=t==="svg"?We.createElementNS(Cc,e):t==="mathml"?We.createElementNS(wc,e):n?We.createElement(e,{is:n}):We.createElement(e);return e==="select"&&s&&s.multiple!=null&&r.setAttribute("multiple",s.multiple),r},createText:e=>We.createTextNode(e),createComment:e=>We.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>We.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,r,i){const o=n?n.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===i||!(r=r.nextSibling)););else{gr.innerHTML=s==="svg"?`${e}`:s==="mathml"?`${e}`:e;const c=gr.content;if(s==="svg"||s==="mathml"){const l=c.firstChild;for(;l.firstChild;)c.appendChild(l.firstChild);c.removeChild(l)}t.insertBefore(c,n)}return[o?o.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},Je="transition",kt="animation",Yt=Symbol("_vtc"),so=(e,{slots:t})=>Bs(yl,Sc(e),t);so.displayName="Transition";const ro={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String};so.props=ye({},Ci,ro);const dt=(e,t=[])=>{q(e)?e.forEach(n=>n(...t)):e&&e(...t)},mr=e=>e?q(e)?e.some(t=>t.length>1):e.length>1:!1;function Sc(e){const t={};for(const A in e)A in ro||(t[A]=e[A]);if(e.css===!1)return t;const{name:n="v",type:s,duration:r,enterFromClass:i=`${n}-enter-from`,enterActiveClass:o=`${n}-enter-active`,enterToClass:c=`${n}-enter-to`,appearFromClass:l=i,appearActiveClass:a=o,appearToClass:h=c,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:p=`${n}-leave-active`,leaveToClass:m=`${n}-leave-to`}=e,x=Rc(r),P=x&&x[0],j=x&&x[1],{onBeforeEnter:B,onEnter:O,onEnterCancelled:g,onLeave:E,onLeaveCancelled:H,onBeforeAppear:T=B,onAppear:K=O,onAppearCancelled:X=g}=t,$=(A,G,ie)=>{pt(A,G?h:c),pt(A,G?a:o),ie&&ie()},L=(A,G)=>{A._isLeaving=!1,pt(A,d),pt(A,m),pt(A,p),G&&G()},Q=A=>(G,ie)=>{const oe=A?K:O,D=()=>$(G,A,ie);dt(oe,[G,D]),yr(()=>{pt(G,A?l:i),Ye(G,A?h:c),mr(oe)||_r(G,s,P,D)})};return ye(t,{onBeforeEnter(A){dt(B,[A]),Ye(A,i),Ye(A,o)},onBeforeAppear(A){dt(T,[A]),Ye(A,l),Ye(A,a)},onEnter:Q(!1),onAppear:Q(!0),onLeave(A,G){A._isLeaving=!0;const ie=()=>L(A,G);Ye(A,d),Ye(A,p),Pc(),yr(()=>{A._isLeaving&&(pt(A,d),Ye(A,m),mr(E)||_r(A,s,j,ie))}),dt(E,[A,ie])},onEnterCancelled(A){$(A,!1),dt(g,[A])},onAppearCancelled(A){$(A,!0),dt(X,[A])},onLeaveCancelled(A){L(A),dt(H,[A])}})}function Rc(e){if(e==null)return null;if(ce(e))return[Xn(e.enter),Xn(e.leave)];{const t=Xn(e);return[t,t]}}function Xn(e){return So(e)}function Ye(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[Yt]||(e[Yt]=new Set)).add(t)}function pt(e,t){t.split(/\s+/).forEach(s=>s&&e.classList.remove(s));const n=e[Yt];n&&(n.delete(t),n.size||(e[Yt]=void 0))}function yr(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let Tc=0;function _r(e,t,n,s){const r=e._endId=++Tc,i=()=>{r===e._endId&&s()};if(n)return setTimeout(i,n);const{type:o,timeout:c,propCount:l}=Ac(e,t);if(!o)return s();const a=o+"end";let h=0;const d=()=>{e.removeEventListener(a,p),i()},p=m=>{m.target===e&&++h>=l&&d()};setTimeout(()=>{h(n[x]||"").split(", "),r=s(`${Je}Delay`),i=s(`${Je}Duration`),o=br(r,i),c=s(`${kt}Delay`),l=s(`${kt}Duration`),a=br(c,l);let h=null,d=0,p=0;t===Je?o>0&&(h=Je,d=o,p=i.length):t===kt?a>0&&(h=kt,d=a,p=l.length):(d=Math.max(o,a),h=d>0?o>a?Je:kt:null,p=h?h===Je?i.length:l.length:0);const m=h===Je&&/\b(transform|all)(,|$)/.test(s(`${Je}Property`).toString());return{type:h,timeout:d,propCount:p,hasTransform:m}}function br(e,t){for(;e.lengthvr(n)+vr(e[s])))}function vr(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function Pc(){return document.body.offsetHeight}function Oc(e,t,n){const s=e[Yt];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const On=Symbol("_vod"),io=Symbol("_vsh"),df={beforeMount(e,{value:t},{transition:n}){e[On]=e.style.display==="none"?"":e.style.display,n&&t?n.beforeEnter(e):Bt(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:s}){!t!=!n&&(s?t?(s.beforeEnter(e),Bt(e,!0),s.enter(e)):s.leave(e,()=>{Bt(e,!1)}):Bt(e,t))},beforeUnmount(e,{value:t}){Bt(e,t)}};function Bt(e,t){e.style.display=t?e[On]:"none",e[io]=!t}const oo=Symbol("");function pf(e){const t=eo();if(!t)return;const n=t.ut=(r=e(t.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner="${t.uid}"]`)).forEach(i=>ms(i,r))},s=()=>{const r=e(t.proxy);gs(t.subTree,r),n(r)};Ti(()=>{Zl(s)}),Fs(()=>{const r=new MutationObserver(s);r.observe(t.subTree.el.parentNode,{childList:!0}),$s(()=>r.disconnect())})}function gs(e,t){if(e.shapeFlag&128){const n=e.suspense;e=n.activeBranch,n.pendingBranch&&!n.isHydrating&&n.effects.push(()=>{gs(n.activeBranch,t)})}for(;e.component;)e=e.component.subTree;if(e.shapeFlag&1&&e.el)ms(e.el,t);else if(e.type===ve)e.children.forEach(n=>gs(n,t));else if(e.type===Nt){let{el:n,anchor:s}=e;for(;n&&(ms(n,t),n!==s);)n=n.nextSibling}}function ms(e,t){if(e.nodeType===1){const n=e.style;let s="";for(const r in t)n.setProperty(`--${r}`,t[r]),s+=`--${r}: ${t[r]};`;n[oo]=s}}const Mc=/(^|;)\s*display\s*:/;function Ic(e,t,n){const s=e.style,r=he(n);let i=!1;if(n&&!r){if(t)if(he(t))for(const o of t.split(";")){const c=o.slice(0,o.indexOf(":")).trim();n[c]==null&&En(s,c,"")}else for(const o in t)n[o]==null&&En(s,o,"");for(const o in n)o==="display"&&(i=!0),En(s,o,n[o])}else if(r){if(t!==n){const o=s[oo];o&&(n+=";"+o),s.cssText=n,i=Mc.test(n)}}else t&&e.removeAttribute("style");On in e&&(e[On]=i?s.display:"",e[io]&&(s.display="none"))}const Er=/\s*!important$/;function En(e,t,n){if(q(n))n.forEach(s=>En(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=Lc(e,t);Er.test(n)?e.setProperty(ut(s),n.replace(Er,""),"important"):e[s]=n}}const Cr=["Webkit","Moz","ms"],Zn={};function Lc(e,t){const n=Zn[t];if(n)return n;let s=je(t);if(s!=="filter"&&s in e)return Zn[t]=s;s=Ln(s);for(let r=0;res||(kc.then(()=>es=0),es=Date.now());function Vc(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;Ne(Dc(s,n.value),t,5,[s])};return n.value=e,n.attached=Bc(),n}function Dc(e,t){if(q(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>r=>!r._stopped&&s&&s(r))}else return t}const Tr=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,Uc=(e,t,n,s,r,i)=>{const o=r==="svg";t==="class"?Oc(e,s,o):t==="style"?Ic(e,n,s):en(t)?Es(t)||Hc(e,t,n,s,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):Kc(e,t,s,o))?(Nc(e,t,s),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&xr(e,t,s,o,i,t!=="value")):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),xr(e,t,s,o))};function Kc(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&Tr(t)&&z(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return Tr(t)&&he(n)?!1:t in e}const Wc={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},gf=(e,t)=>{const n=e._withKeys||(e._withKeys={}),s=t.join(".");return n[s]||(n[s]=r=>{if(!("key"in r))return;const i=ut(r.key);if(t.some(o=>o===i||Wc[o]===i))return e(r)})},qc=ye({patchProp:Uc},xc);let ts,Ar=!1;function Gc(){return ts=Ar?ts:zl(qc),Ar=!0,ts}const mf=(...e)=>{const t=Gc().createApp(...e),{mount:n}=t;return t.mount=s=>{const r=Qc(s);if(r)return n(r,!0,zc(r))},t};function zc(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function Qc(e){return he(e)?document.querySelector(e):e}var Jc=([e,t,n])=>e==="meta"&&t.name?`${e}.${t.name}`:["title","base"].includes(e)?e:e==="template"&&t.id?`${e}.${t.id}`:JSON.stringify([e,t,n]),yf=e=>{const t=new Set,n=[];return e.forEach(s=>{const r=Jc(s);t.has(r)||(t.add(r),n.push(s))}),n},_f=e=>/^(https?:)?\/\//.test(e),bf=e=>/^mailto:/.test(e),vf=e=>/^tel:/.test(e),Ef=e=>Object.prototype.toString.call(e)==="[object Object]",Cf=e=>e.replace(/\/$/,""),wf=e=>e.replace(/^\//,""),xf=(e,t)=>{const n=Object.keys(e).sort((s,r)=>{const i=r.split("/").length-s.split("/").length;return i!==0?i:r.length-s.length});for(const s of n)if(t.startsWith(s))return s;return"/"},Sf=(e,t="/")=>e.replace(/^(https?:)?\/\/[^/]*/,"").replace(new RegExp(`^${t}`),"/");/*! + * vue-router v4.4.3 + * (c) 2024 Eduardo San Martin Morote + * @license MIT + */const St=typeof document<"u";function Yc(e){return e.__esModule||e[Symbol.toStringTag]==="Module"}const re=Object.assign;function ns(e,t){const n={};for(const s in t){const r=t[s];n[s]=ke(r)?r.map(e):e(r)}return n}const Wt=()=>{},ke=Array.isArray,lo=/#/g,Xc=/&/g,Zc=/\//g,eu=/=/g,tu=/\?/g,co=/\+/g,nu=/%5B/g,su=/%5D/g,uo=/%5E/g,ru=/%60/g,fo=/%7B/g,iu=/%7C/g,ao=/%7D/g,ou=/%20/g;function Vs(e){return encodeURI(""+e).replace(iu,"|").replace(nu,"[").replace(su,"]")}function lu(e){return Vs(e).replace(fo,"{").replace(ao,"}").replace(uo,"^")}function ys(e){return Vs(e).replace(co,"%2B").replace(ou,"+").replace(lo,"%23").replace(Xc,"%26").replace(ru,"`").replace(fo,"{").replace(ao,"}").replace(uo,"^")}function cu(e){return ys(e).replace(eu,"%3D")}function uu(e){return Vs(e).replace(lo,"%23").replace(tu,"%3F")}function fu(e){return e==null?"":uu(e).replace(Zc,"%2F")}function Xt(e){try{return decodeURIComponent(""+e)}catch{}return""+e}const au=/\/$/,hu=e=>e.replace(au,"");function ss(e,t,n="/"){let s,r={},i="",o="";const c=t.indexOf("#");let l=t.indexOf("?");return c=0&&(l=-1),l>-1&&(s=t.slice(0,l),i=t.slice(l+1,c>-1?c:t.length),r=e(i)),c>-1&&(s=s||t.slice(0,c),o=t.slice(c,t.length)),s=mu(s??t,n),{fullPath:s+(i&&"?")+i+o,path:s,query:r,hash:Xt(o)}}function du(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function Pr(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||"/"}function pu(e,t,n){const s=t.matched.length-1,r=n.matched.length-1;return s>-1&&s===r&&$t(t.matched[s],n.matched[r])&&ho(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function $t(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function ho(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e)if(!gu(e[n],t[n]))return!1;return!0}function gu(e,t){return ke(e)?Or(e,t):ke(t)?Or(t,e):e===t}function Or(e,t){return ke(t)?e.length===t.length&&e.every((n,s)=>n===t[s]):e.length===1&&e[0]===t}function mu(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),s=e.split("/"),r=s[s.length-1];(r===".."||r===".")&&s.push("");let i=n.length-1,o,c;for(o=0;o1&&i--;else break;return n.slice(0,i).join("/")+"/"+s.slice(o).join("/")}const Xe={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0};var Zt;(function(e){e.pop="pop",e.push="push"})(Zt||(Zt={}));var qt;(function(e){e.back="back",e.forward="forward",e.unknown=""})(qt||(qt={}));function yu(e){if(!e)if(St){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return e[0]!=="/"&&e[0]!=="#"&&(e="/"+e),hu(e)}const _u=/^[^#]+#/;function bu(e,t){return e.replace(_u,"#")+t}function vu(e,t){const n=document.documentElement.getBoundingClientRect(),s=e.getBoundingClientRect();return{behavior:t.behavior,left:s.left-n.left-(t.left||0),top:s.top-n.top-(t.top||0)}}const Dn=()=>({left:window.scrollX,top:window.scrollY});function Eu(e){let t;if("el"in e){const n=e.el,s=typeof n=="string"&&n.startsWith("#"),r=typeof n=="string"?s?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!r)return;t=vu(r,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left!=null?t.left:window.scrollX,t.top!=null?t.top:window.scrollY)}function Mr(e,t){return(history.state?history.state.position-t:-1)+e}const _s=new Map;function Cu(e,t){_s.set(e,t)}function wu(e){const t=_s.get(e);return _s.delete(e),t}let xu=()=>location.protocol+"//"+location.host;function po(e,t){const{pathname:n,search:s,hash:r}=t,i=e.indexOf("#");if(i>-1){let c=r.includes(e.slice(i))?e.slice(i).length:1,l=r.slice(c);return l[0]!=="/"&&(l="/"+l),Pr(l,"")}return Pr(n,e)+s+r}function Su(e,t,n,s){let r=[],i=[],o=null;const c=({state:p})=>{const m=po(e,location),x=n.value,P=t.value;let j=0;if(p){if(n.value=m,t.value=p,o&&o===x){o=null;return}j=P?p.position-P.position:0}else s(m);r.forEach(B=>{B(n.value,x,{delta:j,type:Zt.pop,direction:j?j>0?qt.forward:qt.back:qt.unknown})})};function l(){o=n.value}function a(p){r.push(p);const m=()=>{const x=r.indexOf(p);x>-1&&r.splice(x,1)};return i.push(m),m}function h(){const{history:p}=window;p.state&&p.replaceState(re({},p.state,{scroll:Dn()}),"")}function d(){for(const p of i)p();i=[],window.removeEventListener("popstate",c),window.removeEventListener("beforeunload",h)}return window.addEventListener("popstate",c),window.addEventListener("beforeunload",h,{passive:!0}),{pauseListeners:l,listen:a,destroy:d}}function Ir(e,t,n,s=!1,r=!1){return{back:e,current:t,forward:n,replaced:s,position:window.history.length,scroll:r?Dn():null}}function Ru(e){const{history:t,location:n}=window,s={value:po(e,n)},r={value:t.state};r.value||i(s.value,{back:null,current:s.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function i(l,a,h){const d=e.indexOf("#"),p=d>-1?(n.host&&document.querySelector("base")?e:e.slice(d))+l:xu()+e+l;try{t[h?"replaceState":"pushState"](a,"",p),r.value=a}catch(m){console.error(m),n[h?"replace":"assign"](p)}}function o(l,a){const h=re({},t.state,Ir(r.value.back,l,r.value.forward,!0),a,{position:r.value.position});i(l,h,!0),s.value=l}function c(l,a){const h=re({},r.value,t.state,{forward:l,scroll:Dn()});i(h.current,h,!0);const d=re({},Ir(s.value,l,null),{position:h.position+1},a);i(l,d,!1),s.value=l}return{location:s,state:r,push:c,replace:o}}function Rf(e){e=yu(e);const t=Ru(e),n=Su(e,t.state,t.location,t.replace);function s(i,o=!0){o||n.pauseListeners(),history.go(i)}const r=re({location:"",base:e,go:s,createHref:bu.bind(null,e)},t,n);return Object.defineProperty(r,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(r,"state",{enumerable:!0,get:()=>t.state.value}),r}function Tu(e){return typeof e=="string"||e&&typeof e=="object"}function go(e){return typeof e=="string"||typeof e=="symbol"}const mo=Symbol("");var Lr;(function(e){e[e.aborted=4]="aborted",e[e.cancelled=8]="cancelled",e[e.duplicated=16]="duplicated"})(Lr||(Lr={}));function Ht(e,t){return re(new Error,{type:e,[mo]:!0},t)}function Ke(e,t){return e instanceof Error&&mo in e&&(t==null||!!(e.type&t))}const Nr="[^/]+?",Au={sensitive:!1,strict:!1,start:!0,end:!0},Pu=/[.+*?^${}()[\]/\\]/g;function Ou(e,t){const n=re({},Au,t),s=[];let r=n.start?"^":"";const i=[];for(const a of e){const h=a.length?[]:[90];n.strict&&!a.length&&(r+="/");for(let d=0;dt.length?t.length===1&&t[0]===40+40?1:-1:0}function yo(e,t){let n=0;const s=e.score,r=t.score;for(;n0&&t[t.length-1]<0}const Iu={type:0,value:""},Lu=/[a-zA-Z0-9_]/;function Nu(e){if(!e)return[[]];if(e==="/")return[[Iu]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(m){throw new Error(`ERR (${n})/"${a}": ${m}`)}let n=0,s=n;const r=[];let i;function o(){i&&r.push(i),i=[]}let c=0,l,a="",h="";function d(){a&&(n===0?i.push({type:0,value:a}):n===1||n===2||n===3?(i.length>1&&(l==="*"||l==="+")&&t(`A repeatable param (${a}) must be alone in its segment. eg: '/:ids+.`),i.push({type:1,value:a,regexp:h,repeatable:l==="*"||l==="+",optional:l==="*"||l==="?"})):t("Invalid state to consume buffer"),a="")}function p(){a+=l}for(;c{o(g)}:Wt}function o(d){if(go(d)){const p=s.get(d);p&&(s.delete(d),n.splice(n.indexOf(p),1),p.children.forEach(o),p.alias.forEach(o))}else{const p=n.indexOf(d);p>-1&&(n.splice(p,1),d.record.name&&s.delete(d.record.name),d.children.forEach(o),d.alias.forEach(o))}}function c(){return n}function l(d){const p=Bu(d,n);n.splice(p,0,d),d.record.name&&!Hr(d)&&s.set(d.record.name,d)}function a(d,p){let m,x={},P,j;if("name"in d&&d.name){if(m=s.get(d.name),!m)throw Ht(1,{location:d});j=m.record.name,x=re($r(p.params,m.keys.filter(g=>!g.optional).concat(m.parent?m.parent.keys.filter(g=>g.optional):[]).map(g=>g.name)),d.params&&$r(d.params,m.keys.map(g=>g.name))),P=m.stringify(x)}else if(d.path!=null)P=d.path,m=n.find(g=>g.re.test(P)),m&&(x=m.parse(P),j=m.record.name);else{if(m=p.name?s.get(p.name):n.find(g=>g.re.test(p.path)),!m)throw Ht(1,{location:d,currentLocation:p});j=m.record.name,x=re({},p.params,d.params),P=m.stringify(x)}const B=[];let O=m;for(;O;)B.unshift(O.record),O=O.parent;return{name:j,path:P,params:x,matched:B,meta:ku(B)}}e.forEach(d=>i(d));function h(){n.length=0,s.clear()}return{addRoute:i,resolve:a,removeRoute:o,clearRoutes:h,getRoutes:c,getRecordMatcher:r}}function $r(e,t){const n={};for(const s of t)s in e&&(n[s]=e[s]);return n}function Hu(e){return{path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:void 0,beforeEnter:e.beforeEnter,props:ju(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}}}function ju(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(const s in e.components)t[s]=typeof n=="object"?n[s]:n;return t}function Hr(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function ku(e){return e.reduce((t,n)=>re(t,n.meta),{})}function jr(e,t){const n={};for(const s in e)n[s]=s in t?t[s]:e[s];return n}function Bu(e,t){let n=0,s=t.length;for(;n!==s;){const i=n+s>>1;yo(e,t[i])<0?s=i:n=i+1}const r=Vu(e);return r&&(s=t.lastIndexOf(r,s-1)),s}function Vu(e){let t=e;for(;t=t.parent;)if(_o(t)&&yo(e,t)===0)return t}function _o({record:e}){return!!(e.name||e.components&&Object.keys(e.components).length||e.redirect)}function Du(e){const t={};if(e===""||e==="?")return t;const s=(e[0]==="?"?e.slice(1):e).split("&");for(let r=0;ri&&ys(i)):[s&&ys(s)]).forEach(i=>{i!==void 0&&(t+=(t.length?"&":"")+n,i!=null&&(t+="="+i))})}return t}function Uu(e){const t={};for(const n in e){const s=e[n];s!==void 0&&(t[n]=ke(s)?s.map(r=>r==null?null:""+r):s==null?s:""+s)}return t}const Ku=Symbol(""),Br=Symbol(""),Un=Symbol(""),Ds=Symbol(""),bs=Symbol("");function Vt(){let e=[];function t(s){return e.push(s),()=>{const r=e.indexOf(s);r>-1&&e.splice(r,1)}}function n(){e=[]}return{add:t,list:()=>e.slice(),reset:n}}function nt(e,t,n,s,r,i=o=>o()){const o=s&&(s.enterCallbacks[r]=s.enterCallbacks[r]||[]);return()=>new Promise((c,l)=>{const a=p=>{p===!1?l(Ht(4,{from:n,to:t})):p instanceof Error?l(p):Tu(p)?l(Ht(2,{from:t,to:p})):(o&&s.enterCallbacks[r]===o&&typeof p=="function"&&o.push(p),c())},h=i(()=>e.call(s&&s.instances[r],t,n,a));let d=Promise.resolve(h);e.length<3&&(d=d.then(a)),d.catch(p=>l(p))})}function rs(e,t,n,s,r=i=>i()){const i=[];for(const o of e)for(const c in o.components){let l=o.components[c];if(!(t!=="beforeRouteEnter"&&!o.instances[c]))if(Wu(l)){const h=(l.__vccOpts||l)[t];h&&i.push(nt(h,n,s,o,c,r))}else{let a=l();i.push(()=>a.then(h=>{if(!h)return Promise.reject(new Error(`Couldn't resolve component "${c}" at "${o.path}"`));const d=Yc(h)?h.default:h;o.components[c]=d;const m=(d.__vccOpts||d)[t];return m&&nt(m,n,s,o,c,r)()}))}}return i}function Wu(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function Vr(e){const t=He(Un),n=He(Ds),s=$e(()=>{const l=Ot(e.to);return t.resolve(l)}),r=$e(()=>{const{matched:l}=s.value,{length:a}=l,h=l[a-1],d=n.matched;if(!h||!d.length)return-1;const p=d.findIndex($t.bind(null,h));if(p>-1)return p;const m=Dr(l[a-2]);return a>1&&Dr(h)===m&&d[d.length-1].path!==m?d.findIndex($t.bind(null,l[a-2])):p}),i=$e(()=>r.value>-1&&Qu(n.params,s.value.params)),o=$e(()=>r.value>-1&&r.value===n.matched.length-1&&ho(n.params,s.value.params));function c(l={}){return zu(l)?t[Ot(e.replace)?"replace":"push"](Ot(e.to)).catch(Wt):Promise.resolve()}return{route:s,href:$e(()=>s.value.href),isActive:i,isExactActive:o,navigate:c}}const qu=Ns({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},useLink:Vr,setup(e,{slots:t}){const n=Fn(Vr(e)),{options:s}=He(Un),r=$e(()=>({[Ur(e.activeClass,s.linkActiveClass,"router-link-active")]:n.isActive,[Ur(e.exactActiveClass,s.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive}));return()=>{const i=t.default&&t.default(n);return e.custom?i:Bs("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:r.value},i)}}}),Gu=qu;function zu(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function Qu(e,t){for(const n in t){const s=t[n],r=e[n];if(typeof s=="string"){if(s!==r)return!1}else if(!ke(r)||r.length!==s.length||s.some((i,o)=>i!==r[o]))return!1}return!0}function Dr(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const Ur=(e,t,n)=>e??t??n,Ju=Ns({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){const s=He(bs),r=$e(()=>e.route||s.value),i=He(Br,0),o=$e(()=>{let a=Ot(i);const{matched:h}=r.value;let d;for(;(d=h[a])&&!d.components;)a++;return a}),c=$e(()=>r.value.matched[o.value]);_n(Br,$e(()=>o.value+1)),_n(Ku,c),_n(bs,r);const l=yn();return bn(()=>[l.value,c.value,e.name],([a,h,d],[p,m,x])=>{h&&(h.instances[d]=a,m&&m!==h&&a&&a===p&&(h.leaveGuards.size||(h.leaveGuards=m.leaveGuards),h.updateGuards.size||(h.updateGuards=m.updateGuards))),a&&h&&(!m||!$t(h,m)||!p)&&(h.enterCallbacks[d]||[]).forEach(P=>P(a))},{flush:"post"}),()=>{const a=r.value,h=e.name,d=c.value,p=d&&d.components[h];if(!p)return Kr(n.default,{Component:p,route:a});const m=d.props[h],x=m?m===!0?a.params:typeof m=="function"?m(a):m:null,j=Bs(p,re({},x,t,{onVnodeUnmounted:B=>{B.component.isUnmounted&&(d.instances[h]=null)},ref:l}));return Kr(n.default,{Component:j,route:a})||j}}});function Kr(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}const Yu=Ju;function Tf(e){const t=$u(e.routes,e),n=e.parseQuery||Du,s=e.stringifyQuery||kr,r=e.history,i=Vt(),o=Vt(),c=Vt(),l=rl(Xe);let a=Xe;St&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const h=ns.bind(null,b=>""+b),d=ns.bind(null,fu),p=ns.bind(null,Xt);function m(b,N){let I,k;return go(b)?(I=t.getRecordMatcher(b),k=N):k=b,t.addRoute(k,I)}function x(b){const N=t.getRecordMatcher(b);N&&t.removeRoute(N)}function P(){return t.getRoutes().map(b=>b.record)}function j(b){return!!t.getRecordMatcher(b)}function B(b,N){if(N=re({},N||l.value),typeof b=="string"){const f=ss(n,b,N.path),y=t.resolve({path:f.path},N),v=r.createHref(f.fullPath);return re(f,y,{params:p(y.params),hash:Xt(f.hash),redirectedFrom:void 0,href:v})}let I;if(b.path!=null)I=re({},b,{path:ss(n,b.path,N.path).path});else{const f=re({},b.params);for(const y in f)f[y]==null&&delete f[y];I=re({},b,{params:d(f)}),N.params=d(N.params)}const k=t.resolve(I,N),ne=b.hash||"";k.params=h(p(k.params));const ue=du(s,re({},b,{hash:lu(ne),path:k.path})),u=r.createHref(ue);return re({fullPath:ue,hash:ne,query:s===kr?Uu(b.query):b.query||{}},k,{redirectedFrom:void 0,href:u})}function O(b){return typeof b=="string"?ss(n,b,l.value.path):re({},b)}function g(b,N){if(a!==b)return Ht(8,{from:N,to:b})}function E(b){return K(b)}function H(b){return E(re(O(b),{replace:!0}))}function T(b){const N=b.matched[b.matched.length-1];if(N&&N.redirect){const{redirect:I}=N;let k=typeof I=="function"?I(b):I;return typeof k=="string"&&(k=k.includes("?")||k.includes("#")?k=O(k):{path:k},k.params={}),re({query:b.query,hash:b.hash,params:k.path!=null?{}:b.params},k)}}function K(b,N){const I=a=B(b),k=l.value,ne=b.state,ue=b.force,u=b.replace===!0,f=T(I);if(f)return K(re(O(f),{state:typeof f=="object"?re({},ne,f.state):ne,force:ue,replace:u}),N||I);const y=I;y.redirectedFrom=N;let v;return!ue&&pu(s,k,I)&&(v=Ht(16,{to:y,from:k}),Be(k,k,!0,!1)),(v?Promise.resolve(v):L(y,k)).catch(_=>Ke(_)?Ke(_,2)?_:ze(_):W(_,y,k)).then(_=>{if(_){if(Ke(_,2))return K(re({replace:u},O(_.to),{state:typeof _.to=="object"?re({},ne,_.to.state):ne,force:ue}),N||y)}else _=A(y,k,!0,u,ne);return Q(y,k,_),_})}function X(b,N){const I=g(b,N);return I?Promise.reject(I):Promise.resolve()}function $(b){const N=Ct.values().next().value;return N&&typeof N.runWithContext=="function"?N.runWithContext(b):b()}function L(b,N){let I;const[k,ne,ue]=Xu(b,N);I=rs(k.reverse(),"beforeRouteLeave",b,N);for(const f of k)f.leaveGuards.forEach(y=>{I.push(nt(y,b,N))});const u=X.bind(null,b,N);return I.push(u),Oe(I).then(()=>{I=[];for(const f of i.list())I.push(nt(f,b,N));return I.push(u),Oe(I)}).then(()=>{I=rs(ne,"beforeRouteUpdate",b,N);for(const f of ne)f.updateGuards.forEach(y=>{I.push(nt(y,b,N))});return I.push(u),Oe(I)}).then(()=>{I=[];for(const f of ue)if(f.beforeEnter)if(ke(f.beforeEnter))for(const y of f.beforeEnter)I.push(nt(y,b,N));else I.push(nt(f.beforeEnter,b,N));return I.push(u),Oe(I)}).then(()=>(b.matched.forEach(f=>f.enterCallbacks={}),I=rs(ue,"beforeRouteEnter",b,N,$),I.push(u),Oe(I))).then(()=>{I=[];for(const f of o.list())I.push(nt(f,b,N));return I.push(u),Oe(I)}).catch(f=>Ke(f,8)?f:Promise.reject(f))}function Q(b,N,I){c.list().forEach(k=>$(()=>k(b,N,I)))}function A(b,N,I,k,ne){const ue=g(b,N);if(ue)return ue;const u=N===Xe,f=St?history.state:{};I&&(k||u?r.replace(b.fullPath,re({scroll:u&&f&&f.scroll},ne)):r.push(b.fullPath,ne)),l.value=b,Be(b,N,I,u),ze()}let G;function ie(){G||(G=r.listen((b,N,I)=>{if(!on.listening)return;const k=B(b),ne=T(k);if(ne){K(re(ne,{replace:!0}),k).catch(Wt);return}a=k;const ue=l.value;St&&Cu(Mr(ue.fullPath,I.delta),Dn()),L(k,ue).catch(u=>Ke(u,12)?u:Ke(u,2)?(K(u.to,k).then(f=>{Ke(f,20)&&!I.delta&&I.type===Zt.pop&&r.go(-1,!1)}).catch(Wt),Promise.reject()):(I.delta&&r.go(-I.delta,!1),W(u,k,ue))).then(u=>{u=u||A(k,ue,!1),u&&(I.delta&&!Ke(u,8)?r.go(-I.delta,!1):I.type===Zt.pop&&Ke(u,20)&&r.go(-1,!1)),Q(k,ue,u)}).catch(Wt)}))}let oe=Vt(),D=Vt(),Y;function W(b,N,I){ze(b);const k=D.list();return k.length?k.forEach(ne=>ne(b,N,I)):console.error(b),Promise.reject(b)}function de(){return Y&&l.value!==Xe?Promise.resolve():new Promise((b,N)=>{oe.add([b,N])})}function ze(b){return Y||(Y=!b,ie(),oe.list().forEach(([N,I])=>b?I(b):N()),oe.reset()),b}function Be(b,N,I,k){const{scrollBehavior:ne}=e;if(!St||!ne)return Promise.resolve();const ue=!I&&wu(Mr(b.fullPath,0))||(k||!I)&&history.state&&history.state.scroll||null;return bi().then(()=>ne(b,N,ue)).then(u=>u&&Eu(u)).catch(u=>W(u,b,N))}const we=b=>r.go(b);let Et;const Ct=new Set,on={currentRoute:l,listening:!0,addRoute:m,removeRoute:x,clearRoutes:t.clearRoutes,hasRoute:j,getRoutes:P,resolve:B,options:e,push:E,replace:H,go:we,back:()=>we(-1),forward:()=>we(1),beforeEach:i.add,beforeResolve:o.add,afterEach:c.add,onError:D.add,isReady:de,install(b){const N=this;b.component("RouterLink",Gu),b.component("RouterView",Yu),b.config.globalProperties.$router=N,Object.defineProperty(b.config.globalProperties,"$route",{enumerable:!0,get:()=>Ot(l)}),St&&!Et&&l.value===Xe&&(Et=!0,E(r.location).catch(ne=>{}));const I={};for(const ne in Xe)Object.defineProperty(I,ne,{get:()=>l.value[ne],enumerable:!0});b.provide(Un,N),b.provide(Ds,ai(I)),b.provide(bs,l);const k=b.unmount;Ct.add(b),b.unmount=function(){Ct.delete(b),Ct.size<1&&(a=Xe,G&&G(),G=null,l.value=Xe,Et=!1,Y=!1),k()}}};function Oe(b){return b.reduce((N,I)=>N.then(()=>$(I)),Promise.resolve())}return on}function Xu(e,t){const n=[],s=[],r=[],i=Math.max(t.matched.length,e.matched.length);for(let o=0;o$t(a,c))?s.push(c):n.push(c));const l=e.matched[o];l&&(t.matched.find(a=>$t(a,l))||r.push(l))}return[n,s,r]}function Af(){return He(Un)}function Pf(e){return He(Ds)}const Of=(e,t)=>{const n=e.__vccOpts||e;for(const[s,r]of t)n[s]=r;return n};export{df as $,bn as A,eo as B,bi as C,Fo as D,Zu as E,rl as F,uf as G,_n as H,$s as I,z as J,Ef as K,Pf as L,of as M,ae as N,ve as O,lf as P,hf as Q,Xi as R,ef as S,so as T,Ji as U,pl as V,ac as W,bf as X,vf as Y,sf as Z,Of as _,hi as a,Cf as a0,af as a1,gf as a2,Sf as a3,Tf as a4,Xe as a5,mf as a6,Rf as a7,Yu as a8,tf as a9,nf as aa,ut as ab,ee as ac,pf as ad,Fn as b,q as c,rf as d,yf as e,xf as f,Ns as g,$e as h,he as i,Bs as j,_f as k,wf as l,He as m,Ai as n,Fs as o,zi as p,ff as q,yn as r,cf as s,Zi as t,Af as u,Io as v,Ss as w,xs as x,Ce as y,Ot as z}; diff --git a/assets/getting-started.html-3c0ed39c.js b/assets/getting-started.html-3c0ed39c.js new file mode 100644 index 000000000..7e1408a6b --- /dev/null +++ b/assets/getting-started.html-3c0ed39c.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-5dc4b15a","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":1684740189000},"filePathRelative":"getting-started.md"}');export{t as data}; diff --git a/assets/getting-started.html-9de2d56f.js b/assets/getting-started.html-9de2d56f.js new file mode 100644 index 000000000..a6264c618 --- /dev/null +++ b/assets/getting-started.html-9de2d56f.js @@ -0,0 +1 @@ +import{_ as e,p as a,q as n,R as t,t as o}from"./framework-f6820c83.js";const r={},s=t("p",null,[o("Moved to "),t("a",{href:"./getting-started"},"Getting Started")],-1),c=[s];function _(d,i){return a(),n("div",null,c)}const f=e(r,[["render",_],["__file","getting-started.html.vue"]]);export{f as default}; diff --git a/assets/github-star-e3e26cb5.js b/assets/github-star-e3e26cb5.js new file mode 100644 index 000000000..8e7b6a5ce --- /dev/null +++ b/assets/github-star-e3e26cb5.js @@ -0,0 +1 @@ +import{_ as a}from"./app-1ecc50a8.js";import{g as r,ab as h,j as n,ac as l,_ as u,M as p,p as c,U as d,V as f,t as _}from"./framework-f6820c83.js";const g=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[h(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;a(()=>import("./buttons.esm-5a367c11.js"),[]).then(function(o){e.$el.lastChild===t&&o.render(t.appendChild(e.$refs._),function(i){e.$el.lastChild===t&&t.parentNode.replaceChild(i,t)})})},reset:function(){this.$refs._!=null&&this.$el.replaceChild(this.$refs._,this.$el.lastChild)}}}),m={components:{GithubButton:g}};function $(t,e,o,i,b,S){const s=p("github-button");return c(),d(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(()=>[_("Star")]),_:1})}const v=u(m,[["render",$],["__file","github-star.vue"]]);export{v as default}; diff --git a/assets/html.html-42217360.js b/assets/html.html-42217360.js new file mode 100644 index 000000000..9437e7014 --- /dev/null +++ b/assets/html.html-42217360.js @@ -0,0 +1,40 @@ +import{_ as p}from"./custom-loading-style-65a23c0d.js";import{_ as r,M as l,p as c,q as d,R as e,t as n,N as a,V as i,a1 as t}from"./framework-f6820c83.js";const u="/docs/imgs/unity-needle-engine-modules-physics.jpg",h={},m=t('

    Bundling and web frontends

    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.

    ',4),g={href:"https://vitejs.dev/guide/why.html",target:"_blank",rel:"noopener noreferrer"},v=e("h3",{id:"vite-vue-react-svelte-react-three-fiber...",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#vite-vue-react-svelte-react-three-fiber...","aria-hidden":"true"},"#"),n(" Vite, Vue, React, Svelte, React Three Fiber...")],-1),b={href:"https://vitejs.dev",target:"_blank",rel:"noopener noreferrer"},k=e("p",null,"Here's some example tech stacks that are possible and that we use Needle Engine with:",-1),f=e("li",null,[e("p",null,[e("strong",null,"Vite + HTML"),n(" โ€” This is what our default template uses!")])],-1),y=e("strong",null,"Vite + Vue",-1),w={href:"https://needle.tools",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/needle-tools/needle-engine-samples",target:"_blank",rel:"noopener noreferrer"},j=e("li",null,[e("p",null,[e("strong",null,"Vite + Svelte")])],-1),P=e("li",null,[e("p",null,[e("strong",null,"Vite + SvelteKit")])],-1),x=e("li",null,[e("p",null,[e("strong",null,"Vite + React"),n(" โ€” There's an experimental template shipped with the Unity integration for this that you can pick when generating a project!")])],-1),A=e("li",null,[e("p",null,[e("strong",null,"react-three-fiber"),n(" โ€” There's an experimental template shipped with the Unity integration for this that you can pick when generating a project!")])],-1),W=e("strong",null,"Vercel & Nextjs",-1),T={href:"https://github.com/needle-engine/nextjs-sample",target:"_blank",rel:"noopener noreferrer"},E=e("strong",null,"CDN without any bundler",-1),N=e("p",null,[n("In short: we're currently providing a minimal vite template, but you can extend it or switch to other frameworks โ€“"),e("br"),n(" Let us know what and how you build, and how we can improve the experience for your usecase or provide an example!")],-1),V={class:"custom-container tip"},O=e("p",{class:"custom-container-title"},"TIP",-1),S=e("code",null,"needle.config.json",-1),I=e("code",null,"baseUrl",-1),R=t('
    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

    1. Select Create/Needle Engine/Project Template to add a ProjectTemplate into the folder you want to use as a template
    2. 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 to reduce bundle size

    ',2),q={href:"https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking",target:"_blank",rel:"noopener noreferrer"},C=t(`
    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

    Creating a PWA

    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.

    ',4),D={href:"https://vite-pwa-org.netlify.app/",target:"_blank",rel:"noopener noreferrer"},L=e("code",null,"npm install vite-plugin-pwa --save-dev",-1),M=e("li",null,[n("Modify "),e("code",null,"vite.config.js"),n(" as seen below. Make sure to pass the same "),e("code",null,"pwaOptions"),n(" object to both "),e("code",null,"needlePlugins"),n(" and "),e("code",null,"VitePWA"),n(".")],-1),z=t(`
    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.

    Testing PWAs

    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:

    `,4),U=e("li",null,"the app shows up as installable",-1),B=e("li",null,"the app works offline",-1),F={href:"https://pwabuilder.com/",target:"_blank",rel:"noopener noreferrer"},Y=t(`

    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.

    Automatically update running apps

    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.

    const pwaOptions = {
    +  updateInterval: 15 * 60 * 1000, // 15 minutes, in milliseconds
    +};
    +
    `,9),H={class:"custom-container tip"},J=e("p",{class:"custom-container-title"},"Periodic Reloads and User Data",-1),K=e("br",null,null,-1),G={href:"https://vite-pwa-org.netlify.app/guide/prompt-for-update.html",target:"_blank",rel:"noopener noreferrer"},$=e("h3",{id:"more-pwa-options",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#more-pwa-options","aria-hidden":"true"},"#"),n(" More PWA options")],-1),Q={href:"https://vite-pwa-org.netlify.app/",target:"_blank",rel:"noopener noreferrer"},X=e("br",null,null,-1),Z=t(`
    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.

    Accessing Needle Engine and Components from external javascript

    `,3),ee=e("br",null,null,-1),ne=e("h2",{id:"customizing-how-loading-looks",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#customizing-how-loading-looks","aria-hidden":"true"},"#"),n(" Customizing how loading looks")],-1),ae=e("em",null,"Loading Display",-1),se=t('

    Builtin styles

    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 loading-style="light"></needle-engine>

    Custom Loading Style โ€” PRO feature

    ',4),te=e("em",null,"Loading Display",-1),oe=e("p",null,[e("img",{src:p,alt:"custom loading"})],-1);function ie(le,pe){const s=l("ExternalLinkIcon"),o=l("RouterLink");return c(),d("div",null,[m,e("p",null,[n("Vite (our default bundler) has a good explanation why web apps should be bundled: "),e("a",g,[n("Why Bundle for Production"),a(s)])]),v,e("p",null,[n("Needle Engine is unoponiated about the choice of framework. Our default template uses the popular "),e("a",b,[n("vite"),a(s)]),n(" 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.")]),k,e("ul",null,[f,e("li",null,[e("p",null,[y,n(" โ€” This is what the "),e("a",w,[n("Needle Tools"),a(s)]),n(" website uses!. Find a sample to download "),e("a",_,[n("here"),a(s)]),n(".")])]),j,P,x,A,e("li",null,[e("p",null,[W,n(" โ€” Find a "),e("a",T,[n("example nextjs project here"),a(s)])])]),e("li",null,[e("p",null,[E,n(" โ€” Find a code example "),a(o,{to:"/vanilla-js.html"},{default:i(()=>[n("here")]),_:1})])])]),N,e("div",V,[O,e("p",null,[n("Some frameworks require custom settings in "),S,n(". Learn more "),a(o,{to:"/reference/needle-config-json.html"},{default:i(()=>[n("here")]),_:1}),n(". Typically, the "),I,n(" needs to be set.")])]),R,e("p",null,[n("Tree shaking refers to a common practice when it comes to bundling of web applications ("),e("a",q,[n("see MSDN docs"),a(s)]),n("). 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:")]),C,e("ol",null,[e("li",null,[n("Install the "),e("a",D,[n("Vite PWA plugin"),a(s)]),n(" in your web project: "),L]),M]),z,e("ul",null,[U,B,e("li",null,[n("the app is detected as offline-capable PWA by "),e("a",F,[n("pwabuilder.com"),a(s)])])]),Y,e("div",H,[J,e("p",null,[n("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."),K,n(" See the "),e("a",G,[n("Vite PWA plugin documentation"),a(s)]),n(" for more information on how to implement a reload prompt instead of automatic reloading.")])]),$,e("p",null,[n("Since Needle uses the "),e("a",Q,[n("Vite PWA plugin"),a(s)]),n(" under the hood, you can use all options and hooks provided by that."),X,n(" For example, you can provide a partial manifest with a custom app title or theme color:")]),Z,e("p",null,[n("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)."),ee,n(" For accessing components from regular javascript outside of the engine please refer to the "),a(o,{to:"/scripting.html#accessing-needle-engine-and-components-from-anywhere"},{default:i(()=>[n("interop with regular javascript section")]),_:1})]),ne,e("p",null,[n("See the "),ae,n(" section in "),a(o,{to:"/reference/needle-engine-attributes.html"},{default:i(()=>[n("needle engine component reference")]),_:1})]),se,e("p",null,[n("Please see the "),te,n(" section in "),a(o,{to:"/reference/needle-engine-attributes.html"},{default:i(()=>[n("needle engine component reference")]),_:1})]),oe])}const de=r(h,[["render",ie],["__file","html.html.vue"]]);export{de as default}; diff --git a/assets/html.html-c6d742ba.js b/assets/html.html-c6d742ba.js new file mode 100644 index 000000000..9d3551c4c --- /dev/null +++ b/assets/html.html-c6d742ba.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-e554bd96","path":"/html.html","title":"Frameworks, Bundlers, HTML","lang":"en-US","frontmatter":{"title":"Frameworks, Bundlers, HTML","head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/frameworks, bundlers, html.png"}],["meta",{"name":"og:description","content":"---\\nNeedle Engine is build as a web component.\\nThis means just install @needle-tools/engine in your project and include anywhere in your web-project."}]],"description":"---\\nNeedle Engine is build as a web component.\\nThis means just install @needle-tools/engine in your project and include anywhere in your web-project."},"headers":[{"level":2,"title":"Bundling and web frontends","slug":"bundling-and-web-frontends","link":"#bundling-and-web-frontends","children":[{"level":3,"title":"Vite, Vue, React, Svelte, React Three Fiber...","slug":"vite-vue-react-svelte-react-three-fiber...","link":"#vite-vue-react-svelte-react-three-fiber...","children":[]},{"level":3,"title":"Tree-shaking to reduce bundle size","slug":"tree-shaking-to-reduce-bundle-size","link":"#tree-shaking-to-reduce-bundle-size","children":[]}]},{"level":2,"title":"Creating a PWA","slug":"creating-a-pwa","link":"#creating-a-pwa","children":[{"level":3,"title":"Testing PWAs","slug":"testing-pwas","link":"#testing-pwas","children":[]},{"level":3,"title":"Automatically update running apps","slug":"automatically-update-running-apps","link":"#automatically-update-running-apps","children":[]},{"level":3,"title":"More PWA options","slug":"more-pwa-options","link":"#more-pwa-options","children":[]}]},{"level":2,"title":"Accessing Needle Engine and Components from external javascript","slug":"accessing-needle-engine-and-components-from-external-javascript","link":"#accessing-needle-engine-and-components-from-external-javascript","children":[]},{"level":2,"title":"Customizing how loading looks","slug":"customizing-how-loading-looks","link":"#customizing-how-loading-looks","children":[{"level":3,"title":"Builtin styles","slug":"builtin-styles","link":"#builtin-styles","children":[]},{"level":3,"title":"Custom Loading Style โ€” PRO feature","slug":"custom-loading-style-pro-feature","link":"#custom-loading-style-pro-feature","children":[]}]}],"git":{"updatedTime":1725399379000},"filePathRelative":"html.md"}');export{e as data}; diff --git a/assets/index.html-00a074af.js b/assets/index.html-00a074af.js new file mode 100644 index 000000000..d5d4b1ab6 --- /dev/null +++ b/assets/index.html-00a074af.js @@ -0,0 +1,28 @@ +import{_ as a,M as t,p as e,q as p,N as o,R as n,a1 as c}from"./framework-f6820c83.js";const i={},l=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),u=c(`

    Example for adding custom USDZ behaviours for iOS AR

    This is an USDZ / iOS AR only example

    export class EmphasizeOnClick extends Behaviour implements UsdzBehaviour {
    +
    +    @serializable()
    +    target?: Object3D;
    +
    +    @serializable()
    +    duration: number = 0.5;
    +
    +    @serializable()
    +    motionType: MotionType = MotionType.bounce;
    +
    +    beforeCreateDocument() { }
    +
    +    createBehaviours(ext, model, _context) {
    +        if (!this.target) return;
    +
    +        if (model.uuid === this.gameObject.uuid) {
    +            const emphasize = new BehaviorModel("emphasize " + this.name,
    +                TriggerBuilder.tapTrigger(this.gameObject),
    +                ActionBuilder.emphasize(this.target, this.duration, this.motionType, undefined, "basic"),
    +            );
    +            ext.addBehavior(emphasize);
    +        }
    +    }
    +
    +    afterCreateDocument(_ext, _context) { }
    +}
    +
    `,3);function r(k,d){const s=t("contribution-header");return e(),p("div",null,[l,o(s,{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"}),u])}const v=a(i,[["render",r],["__file","index.html.vue"]]);export{v as default}; diff --git a/assets/index.html-013f9a89.js b/assets/index.html-013f9a89.js new file mode 100644 index 000000000..dc636a5a4 --- /dev/null +++ b/assets/index.html-013f9a89.js @@ -0,0 +1,61 @@ +import{_ as l,M as o,p,q as i,N as a,V as t,R as n,t as s}from"./framework-f6820c83.js";const u={},r=n("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),k=n("p",null,"P.S: I am using Svelte for 2D UI handling.",-1),d=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" isMobileDevice"),n("span",{class:"token punctuation"},","),s(" NeedleXRSession "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token operator"},"..."),s(` + +`),n("span",{class:"token comment"},"// check if this is a mobile phone"),s(` +`),n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token function"},"isMobilePhone"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"return"),s(),n("span",{class:"token regex"},[n("span",{class:"token regex-delimiter"},"/"),n("span",{class:"token regex-source language-regex"},"Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini"),n("span",{class:"token regex-delimiter"},"/"),n("span",{class:"token regex-flags"},"i")]),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"test"),n("span",{class:"token punctuation"},"("),s("navigator"),n("span",{class:"token punctuation"},"."),s("userAgent"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` + +`),n("span",{class:"token operator"},"..."),s(` + +`),n("span",{class:"token comment"},"// show the button, if the device is not a mobile phone and VR is supported"),s(` +`),n("span",{class:"token punctuation"},"{"),s("#"),n("span",{class:"token keyword"},"if"),s(" "),n("span",{class:"token function"},"isMobileDevice"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"&&"),s(),n("span",{class:"token operator"},"!"),n("span",{class:"token function"},"isMobilePhone"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"&&"),s(" $haveVR "),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token operator"},"<"),s("VrButton buttonFunction"),n("span",{class:"token operator"},"="),n("span",{class:"token punctuation"},"{"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token function"},"StartVR"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token operator"},"/"),n("span",{class:"token operator"},">"),s(` +`),n("span",{class:"token punctuation"},"{"),n("span",{class:"token operator"},"/"),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),b=n("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),m=n("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),v=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Behaviour"),n("span",{class:"token punctuation"},","),s(" GameObject"),n("span",{class:"token punctuation"},","),s(" Renderer"),n("span",{class:"token punctuation"},","),s(" USDZExporter"),n("span",{class:"token punctuation"},","),s(" serializable "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Material"),n("span",{class:"token punctuation"},","),s(" Object3D "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"three"'),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"FallbackMaterial"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),s("Material"),n("span",{class:"token punctuation"},")"),s(` + fallbackMaterial`),n("span",{class:"token operator"},"!"),n("span",{class:"token operator"},":"),s(" Material"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"private"),s(" originalMaterial"),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s(" Material"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"private"),s(" usdzExporter"),n("span",{class:"token operator"},"!"),n("span",{class:"token operator"},":"),s(" USDZExporter"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token function"},"onEnable"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("usdzExporter "),n("span",{class:"token operator"},"="),s(" GameObject"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"findObjectOfType"),n("span",{class:"token punctuation"},"("),s("USDZExporter"),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},"!"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"subscribeToBeforeExportEvent"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"onDisable"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"unsubscribeFromBeforeExportEvent"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token function"},"subscribeToBeforeExportEvent"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("usdzExporter"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"addEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"before-export"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("onBeforeExport"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("usdzExporter"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"addEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"after-export"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("onAfterExport"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token function"},"unsubscribeFromBeforeExportEvent"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("usdzExporter"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"removeEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"before-export"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("onBeforeExport"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("usdzExporter"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"removeEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"after-export"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("onAfterExport"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + + `),n("span",{class:"token function-variable function"},"onBeforeExport"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token builtin"},"console"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"log"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"onBeforeExport"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" renderer "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("gameObject"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"getComponent"),n("span",{class:"token punctuation"},"("),s("Renderer"),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},"!"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("originalMaterial "),n("span",{class:"token operator"},"="),s(" renderer"),n("span",{class:"token punctuation"},"."),s("sharedMaterial"),n("span",{class:"token punctuation"},";"),s(` + renderer`),n("span",{class:"token punctuation"},"."),s("sharedMaterial "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("fallbackMaterial"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function-variable function"},"onAfterExport"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token builtin"},"console"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"log"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"onAfterExport"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" renderer "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("gameObject"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"getComponent"),n("span",{class:"token punctuation"},"("),s("Renderer"),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},"!"),n("span",{class:"token punctuation"},";"),s(` + renderer`),n("span",{class:"token punctuation"},"."),s("sharedMaterial "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("originalMaterial"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1);function f(h,y){const e=o("contribution-preview"),c=o("contributions-author");return p(),i("div",null,[a(c,{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:t(()=>[a(e,{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:t(()=>[r,k,d]),_:1}),a(e,{title:"Set fallback material for USDZ exporter",pageUrl:"/docs/community/contributions/llllkatjallll/set-fallback-material-for-usdz-exporter"},{default:t(()=>[b,m,v]),_:1})]),_:1})])}const x=l(u,[["render",f],["__file","index.html.vue"]]);export{x as default}; diff --git a/assets/index.html-0784c019.js b/assets/index.html-0784c019.js new file mode 100644 index 000000000..a7aadf632 --- /dev/null +++ b/assets/index.html-0784c019.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-07e00b06","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{e as data}; diff --git a/assets/index.html-0991b7d8.js b/assets/index.html-0991b7d8.js new file mode 100644 index 000000000..21829847d --- /dev/null +++ b/assets/index.html-0991b7d8.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-1b71f33d","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{e as data}; diff --git a/assets/index.html-0a16bdd2.js b/assets/index.html-0a16bdd2.js new file mode 100644 index 000000000..22955d28b --- /dev/null +++ b/assets/index.html-0a16bdd2.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4fe6a858","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{e as data}; diff --git a/assets/index.html-0f3c5ccd.js b/assets/index.html-0f3c5ccd.js new file mode 100644 index 000000000..744d2a3a0 --- /dev/null +++ b/assets/index.html-0f3c5ccd.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-165956bc","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{e as data}; diff --git a/assets/index.html-1a7264fe.js b/assets/index.html-1a7264fe.js new file mode 100644 index 000000000..4e005d44f --- /dev/null +++ b/assets/index.html-1a7264fe.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-74ff5ee0","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{e as data}; diff --git a/assets/index.html-1ac4d74c.js b/assets/index.html-1ac4d74c.js new file mode 100644 index 000000000..9d77d2891 --- /dev/null +++ b/assets/index.html-1ac4d74c.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-51c63c84","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{e as data}; diff --git a/assets/index.html-1c180a2f.js b/assets/index.html-1c180a2f.js new file mode 100644 index 000000000..6bec151bb --- /dev/null +++ b/assets/index.html-1c180a2f.js @@ -0,0 +1,23 @@ +import{_ as a,M as t,p as e,q as p,N as o,R as n,a1 as c}from"./framework-f6820c83.js";const i={},l=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),u=c(`

    This is an example from our Everywhere Actions. The following script hides an object on start on Android and on iOS AR

    export class HideOnStart extends Behaviour implements UsdzBehaviour {
    +
    +    start() {
    +        this.gameObject.visible = false;
    +    }
    +
    +    createBehaviours(ext, model, _context) {
    +        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;
    +    }
    +}
    +
    `,2);function r(d,k){const s=t("contribution-header");return e(),p("div",null,[l,o(s,{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"}),u])}const v=a(i,[["render",r],["__file","index.html.vue"]]);export{v as default}; diff --git a/assets/index.html-238978c8.js b/assets/index.html-238978c8.js new file mode 100644 index 000000000..5030a9e1a --- /dev/null +++ b/assets/index.html-238978c8.js @@ -0,0 +1 @@ +import{_ as l,M as r,p as u,q as m,R as n,t as c,N as t,V as e}from"./framework-f6820c83.js";const b={},d=n("h1",null,"Community Scripts",-1),p={href:"https://github.com/needle-tools/needle-engine-support/discussions/categories/share",target:"_blank",rel:"noopener noreferrer"};function h(f,k){const a=r("ExternalLinkIcon"),o=r("contribution-listentry"),i=r("contribution-header"),s=r("contributions-overview");return u(),m("div",null,[d,n("p",null,[c("To contribute a script, please create a new discussion in the "),n("a",p,[c("Share category"),t(a)])]),t(s,null,{default:e(()=>[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:e(()=>[t(o,{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(o,{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:e(()=>[t(o,{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(o,{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:e(()=>[t(o,{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:e(()=>[t(o,{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:e(()=>[t(o,{title:"Camera Video Background",url:"/docs/community/contributions/marwie/camera-video-background"}),t(o,{title:"USDZ: Hide Object on Start",url:"/docs/community/contributions/marwie/usdz-hide-object-on-start"}),t(o,{title:"Everywhere Action: Emphasize on Click",url:"/docs/community/contributions/marwie/everywhere-action-emphasize-on-click"}),t(o,{title:"Control a Timeline by scroll",url:"/docs/community/contributions/marwie/control-a-timeline-by-scroll"}),t(o,{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:e(()=>[t(o,{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:e(()=>[t(o,{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(o,{title:"Squeeze to Scale (Object or World) in VR",url:"/docs/community/contributions/web3kev/squeeze-to-scale-object-or-world-in-vr"}),t(o,{title:"Network instantiation of multiple objects",url:"/docs/community/contributions/web3kev/network-instantiation-of-multiple-objects"})]),_:1})]),_:1})])}const y=l(b,[["render",h],["__file","index.html.vue"]]);export{y as default}; diff --git a/assets/index.html-2c69ad93.js b/assets/index.html-2c69ad93.js new file mode 100644 index 000000000..ed24344ea --- /dev/null +++ b/assets/index.html-2c69ad93.js @@ -0,0 +1,46 @@ +import{_ as a,M as t,p as e,q as p,N as o,R as n,a1 as c}from"./framework-f6820c83.js";const i={},l=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),u=c(`

    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).

    import { Behaviour, GameObject, Renderer, USDZExporter, serializable } from "@needle-tools/engine";
    +import { Material, Object3D } from "three";
    +
    +export class FallbackMaterial extends Behaviour {
    +
    +    @serializable(Material)
    +    fallbackMaterial!: Material;
    +
    +    private originalMaterial?: Material;
    +    private usdzExporter!: USDZExporter;
    +
    +    onEnable() {
    +        this.usdzExporter = GameObject.findObjectOfType(USDZExporter)!;
    +        this.subscribeToBeforeExportEvent();
    +    }
    +
    +    onDisable() {
    +        this.unsubscribeFromBeforeExportEvent();
    +    }
    +
    +    private subscribeToBeforeExportEvent() {
    +        this.usdzExporter.addEventListener("before-export", this.onBeforeExport);
    +        this.usdzExporter.addEventListener("after-export", this.onAfterExport);
    +    }
    +
    +    private unsubscribeFromBeforeExportEvent() {
    +        this.usdzExporter.removeEventListener("before-export", this.onBeforeExport);
    +        this.usdzExporter.removeEventListener("after-export", this.onAfterExport);
    +    }
    +
    +
    +    onBeforeExport = () => {
    +        console.log("onBeforeExport");
    +        const renderer = this.gameObject.getComponent(Renderer)!;
    +        this.originalMaterial = renderer.sharedMaterial;
    +        renderer.sharedMaterial = this.fallbackMaterial;
    +
    +    }
    +
    +    onAfterExport = () => {
    +        console.log("onAfterExport");
    +        const renderer = this.gameObject.getComponent(Renderer)!;
    +        renderer.sharedMaterial = this.originalMaterial;
    +    }
    +}
    +
    `,3);function r(k,d){const s=t("contribution-header");return e(),p("div",null,[l,o(s,{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"}),u])}const b=a(i,[["render",r],["__file","index.html.vue"]]);export{b as default}; diff --git a/assets/index.html-32447045.js b/assets/index.html-32447045.js new file mode 100644 index 000000000..1cdaa57e1 --- /dev/null +++ b/assets/index.html-32447045.js @@ -0,0 +1,66 @@ +import{_ as p,M as e,p as l,q as u,N as a,V as t,R as n,t as s}from"./framework-f6820c83.js";const i={},r=n("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),k=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Behaviour "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"Microphone"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"start"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"getLocalStream"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"public"),s(" el"),n("span",{class:"token operator"},":"),s(" Element"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token function"},"attachStream"),n("span",{class:"token punctuation"},"("),s("stream"),n("span",{class:"token punctuation"},","),s(" el"),n("span",{class:"token punctuation"},","),s(" options"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"var"),s(" item"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"var"),s(),n("span",{class:"token constant"},"URL"),s(),n("span",{class:"token operator"},"="),s(" window"),n("span",{class:"token punctuation"},"."),n("span",{class:"token constant"},"URL"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"var"),s(" element "),n("span",{class:"token operator"},"="),s(" el"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"var"),s(" opts "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"{"),s(` + autoplay`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},","),s(` + mirror`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},","),s(` + muted`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},","),s(` + audio`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},","),s(` + disableContextMenu`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},","),s(` + `),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("options"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),s("item "),n("span",{class:"token keyword"},"in"),s(" options"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + opts`),n("span",{class:"token punctuation"},"["),s("item"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"="),s(" options"),n("span",{class:"token punctuation"},"["),s("item"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token operator"},"!"),s("element"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + element `),n("span",{class:"token operator"},"="),s(" document"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"createElement"),n("span",{class:"token punctuation"},"("),s("opts"),n("span",{class:"token punctuation"},"."),s("audio "),n("span",{class:"token operator"},"?"),s(),n("span",{class:"token string"},'"audio"'),s(),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},'"video"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("element"),n("span",{class:"token punctuation"},"."),s("tagName"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"toLowerCase"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token string"},'"audio"'),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + opts`),n("span",{class:"token punctuation"},"."),s("audio "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("opts"),n("span",{class:"token punctuation"},"."),s("autoplay"),n("span",{class:"token punctuation"},")"),s(" element"),n("span",{class:"token punctuation"},"."),s("autoplay "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token string"},'"autoplay"'),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("opts"),n("span",{class:"token punctuation"},"."),s("muted"),n("span",{class:"token punctuation"},")"),s(" element"),n("span",{class:"token punctuation"},"."),s("muted "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token operator"},"!"),s("opts"),n("span",{class:"token punctuation"},"."),s("audio "),n("span",{class:"token operator"},"&&"),s(" opts"),n("span",{class:"token punctuation"},"."),s("mirror"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token punctuation"},"["),n("span",{class:"token string"},'""'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token string"},'"moz"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token string"},'"webkit"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token string"},'"o"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token string"},'"ms"'),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"forEach"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"function"),s(),n("span",{class:"token punctuation"},"("),s("prefix"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"var"),s(" styleName "),n("span",{class:"token operator"},"="),s(" prefix "),n("span",{class:"token operator"},"?"),s(" prefix "),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token string"},'"Transform"'),s(),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},'"transform"'),n("span",{class:"token punctuation"},";"),s(` + element`),n("span",{class:"token punctuation"},"."),s("style"),n("span",{class:"token punctuation"},"["),s("styleName"),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token string"},'"scaleX(-1)"'),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + element`),n("span",{class:"token punctuation"},"."),s("srcObject "),n("span",{class:"token operator"},"="),s(" stream"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(" element"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"getLocalStream"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + navigator`),n("span",{class:"token punctuation"},"."),s(`mediaDevices + `),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"getUserMedia"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"{"),s(` + video`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},","),s(` + audio`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},","),s(` + `),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"then"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"("),s("stream"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"var"),s(" doesnotexist "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token operator"},"!"),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("el"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("el "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"attachStream"),n("span",{class:"token punctuation"},"("),s("stream"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("el"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token punctuation"},"{"),s(` + audio`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},","),s(` + autoplay`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},","),s(` + `),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("doesnotexist"),n("span",{class:"token punctuation"},")"),s(" document"),n("span",{class:"token punctuation"},"."),s("body"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"appendChild"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("el"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"catch"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"("),s("err"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token builtin"},"console"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"log"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"u got an error:"'),s(),n("span",{class:"token operator"},"+"),s(" err"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),d=n("p",null,"This is live for preview over at https://needle-ar.glitch.me/",-1),m=n("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),b=n("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),v=n("p",null,"Public Github Repository: https://github.com/ROBYER1/Needle-AR-Demo",-1);function h(f,w){const o=e("contribution-preview"),c=e("contributions-author");return l(),u("div",null,[a(c,{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:t(()=>[a(o,{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:t(()=>[r,k]),_:1}),a(o,{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:t(()=>[d,m,b,v]),_:1})]),_:1})])}const g=p(i,[["render",h],["__file","index.html.vue"]]);export{g as default}; diff --git a/assets/index.html-3604b913.js b/assets/index.html-3604b913.js new file mode 100644 index 000000000..46613a7b6 --- /dev/null +++ b/assets/index.html-3604b913.js @@ -0,0 +1,33 @@ +import{_ as i,M as a,p as l,q as p,N as e,V as t,R as n,t as s}from"./framework-f6820c83.js";const u={},r=n("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 '),n("code",null,"#if UNITY_EDITOR"),s(" and "),n("code",null,"#endif"),s(".")],-1),k=n("div",{class:"language-csharp line-numbers-mode","data-ext":"cs"},[n("pre",{class:"language-csharp"},[n("code",null,[n("span",{class:"token keyword"},"using"),s(),n("span",{class:"token namespace"},[s("System"),n("span",{class:"token punctuation"},"."),s("Diagnostics")]),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"using"),s(),n("span",{class:"token namespace"},"UnityEditor"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"using"),s(),n("span",{class:"token namespace"},"UnityEngine"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"using"),s(),n("span",{class:"token namespace"},[s("Needle"),n("span",{class:"token punctuation"},"."),s("Engine")]),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token punctuation"},"["),n("span",{class:"token attribute"},[n("span",{class:"token class-name"},"InitializeOnLoad")]),n("span",{class:"token punctuation"},"]"),s(` +`),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token keyword"},"static"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"CustomBrowserOpen"),s(` +`),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"static"),s(),n("span",{class:"token function"},"CustomBrowserOpen"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"Init"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token punctuation"},"["),n("span",{class:"token attribute"},[n("span",{class:"token class-name"},"RuntimeInitializeOnLoadMethod")]),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token keyword"},"static"),s(),n("span",{class:"token return-type class-name"},[n("span",{class:"token keyword"},"void")]),s(),n("span",{class:"token function"},"Init"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + ActionsBrowser`),n("span",{class:"token punctuation"},"."),s("BeforeOpen "),n("span",{class:"token operator"},"+="),s(" ActionsBrowser_BeforeOpen"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token keyword"},"static"),s(),n("span",{class:"token return-type class-name"},[n("span",{class:"token keyword"},"void")]),s(),n("span",{class:"token function"},"ActionsBrowser_BeforeOpen"),n("span",{class:"token punctuation"},"("),n("span",{class:"token class-name"},[s("ActionsBrowser"),n("span",{class:"token punctuation"},"."),s("OpenBrowserArguments")]),s(" args"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + args`),n("span",{class:"token punctuation"},"."),s("PreventDefault "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token class-name"},[n("span",{class:"token keyword"},"string")]),s(" processArgs "),n("span",{class:"token operator"},"="),s(" args"),n("span",{class:"token punctuation"},"."),s("Url"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token class-name"},[n("span",{class:"token keyword"},"var")]),s(" psi "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token constructor-invocation class-name"},"ProcessStartInfo"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + FileName `),n("span",{class:"token operator"},"="),s(),n("span",{class:"token string"},'"chrome.exe"'),n("span",{class:"token punctuation"},","),s(` + Arguments `),n("span",{class:"token operator"},"="),s(` processArgs + `),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},";"),s(` + Process`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"Start"),n("span",{class:"token punctuation"},"("),s("psi"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` + +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1);function d(m,b){const o=a("contribution-preview"),c=a("contributions-author");return l(),p("div",null,[e(c,{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:t(()=>[e(o,{title:"Always open in specific browser",pageUrl:"/docs/community/contributions/krisrok/always-open-in-specific-browser"},{default:t(()=>[r,k]),_:1})]),_:1})])}const w=i(u,[["render",d],["__file","index.html.vue"]]);export{w as default}; diff --git a/assets/index.html-3b58543e.js b/assets/index.html-3b58543e.js new file mode 100644 index 000000000..11694cf83 --- /dev/null +++ b/assets/index.html-3b58543e.js @@ -0,0 +1,33 @@ +import{_ as a,M as e,p as t,q as o,N as p,R as n,a1 as c}from"./framework-f6820c83.js";const i={},l=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),u=c(`

    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.

    using System.Diagnostics;
    +using UnityEditor;
    +using UnityEngine;
    +using Needle.Engine;
    +
    +[InitializeOnLoad]
    +public static class CustomBrowserOpen
    +{
    +    static CustomBrowserOpen()
    +    {
    +        Init();
    +    }
    +
    +    [RuntimeInitializeOnLoadMethod]
    +    static void Init()
    +    {
    +        ActionsBrowser.BeforeOpen += ActionsBrowser_BeforeOpen;
    +    }
    +
    +    private static void ActionsBrowser_BeforeOpen(ActionsBrowser.OpenBrowserArguments args)
    +    {
    +        args.PreventDefault = true;
    +        string processArgs = args.Url;
    +        var psi = new ProcessStartInfo
    +        {
    +            FileName = "chrome.exe",
    +            Arguments = processArgs
    +        };
    +        Process.Start(psi);
    +    }
    +}
    +
    +
    `,2);function r(d,k){const s=e("contribution-header");return t(),o("div",null,[l,p(s,{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"}),u])}const m=a(i,[["render",r],["__file","index.html.vue"]]);export{m as default}; diff --git a/assets/index.html-3fc0407e.js b/assets/index.html-3fc0407e.js new file mode 100644 index 000000000..3c151a87b --- /dev/null +++ b/assets/index.html-3fc0407e.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-93aa7836","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{e as data}; diff --git a/assets/index.html-4556c585.js b/assets/index.html-4556c585.js new file mode 100644 index 000000000..3978c2af7 --- /dev/null +++ b/assets/index.html-4556c585.js @@ -0,0 +1,37 @@ +import{_ as p}from"./logo-b695892f.js";import{_,M as i,p as m,q as g,Q as r,N as n,V as o,R as e,t,a1 as a}from"./framework-f6820c83.js";const b="/docs/imgs/unity-logo.webp",w="/docs/imgs/threejs-logo.webp",y={},f=a('

    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.

    Needle Engine is available as a download for Unity, for Blender, and for web projects without an editor integration.

    ',4),v=e("br",null,null,-1),x=e("br",null,null,-1),k=e("img",{src:b,style:{"max-height":"70px"}},null,-1),S=e("h2",{id:"needle-engine-for-unity",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#needle-engine-for-unity","aria-hidden":"true"},"#"),t(" Needle Engine for Unity")],-1),j=e("p",null,[e("em",null,"Supported Unity versions: 2021.3 LTS, 2022.3 LTS")],-1),N=e("strong",null,"Download Needle Engine for Unity",-1),E={class:"custom-container details"},T=e("summary",null,"Next Steps",-1),C=e("li",null,[e("p",null,"Drop the downloaded .unitypackage file into a Unity project and confirm that you want to import it.")],-1),I=e("li",null,[e("p",null,'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.')],-1),B=e("strong",null,"Explore Samples",-1),D=e("em",null,"Needle Engine > Explore Samples",-1),q={href:"https://engine.needle.tools/samples",target:"_blank",rel:"noopener noreferrer"},U={class:"custom-container details"},P=e("summary",null,"Video Tutorial: Starting from a fresh Unity project",-1),O=e("hr",null,null,-1),F={class:"custom-container details"},W=e("summary",null,"Create a new scene from a Scene Template",-1),L=e("p",null,[t("We provide a number of Scene Templates for quickly starting new projects."),e("br"),t(" These allow you to go from idea to prototype in a few clicks.")],-1),M=e("li",null,[e("p",null,[t("Click on "),e("code",null,"File > New Scene")])],-1),V=e("code",null,"Create",-1),A=e("br",null,null,-1),R={href:"https://engine.needle.tools/samples/collaborative-sandbox",target:"_blank",rel:"noopener noreferrer"},z=e("li",null,[e("p",null,"Click Play to install and startup your new web project.")],-1),Y=e("p",null,[e("img",{src:"https://user-images.githubusercontent.com/2693840/185917275-a147cd90-d515-4086-950d-78358185b1ef.png",alt:"20220822-140539-wqvW-Unity_oC0z-needle"})],-1),G={class:"custom-container details"},Q=a('Create a new scene from scratch

    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.

    1. Create a new empty scene

    2. 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.

    Note

    By default, the project name matches the name of your scene. If you want to change that, you can enter a Directory Name where you want to create your new runtime project. The path is relative to your Unity project.

    ',4),H={start:"3"},K=e("strong",null,"Choose a web project template",-1),X={href:"https://vitejs.dev/",target:"_blank",rel:"noopener noreferrer"},J=e("li",null,[e("p",null,"Click Play to install and start your new web project")],-1),Z=e("br",null,null,-1),$=e("br",null,null,-1),ee=e("br",null,null,-1),te=e("img",{src:p,style:{"max-height":"70px"}},null,-1),ne=e("h2",{id:"needle-engine-for-blender",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#needle-engine-for-blender","aria-hidden":"true"},"#"),t(" Needle Engine for Blender")],-1),oe=e("p",null,[e("em",null,"Supported Blender versions: 4.0+")],-1),le=e("strong",null,"Download Needle Engine for Blender",-1),se=e("br",null,null,-1),ie=e("ul",null,[e("li",null,"The Blender add-on is downloaded as a zip file."),e("li",null,[t("In Blender, go to "),e("code",null,"File > Settings > Add-ons"),t(" and click the "),e("code",null,"Install"),t(" button.")]),e("li",null,"Then select the downloaded zip to install it.")],-1),re=e("br",null,null,-1),ae=e("br",null,null,-1),de=e("br",null,null,-1),ce=e("img",{src:w,style:{"max-height":"70px"}},null,-1),ue=e("h2",{id:"needle-engine-without-editor-integration",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#needle-engine-without-editor-integration","aria-hidden":"true"},"#"),t(" Needle Engine without Editor Integration")],-1),he={href:"https://threejs.org/",target:"_blank",rel:"noopener noreferrer"},pe=e("p",null,[t("You can install Needle Engine from "),e("code",null,"npm"),t(" by running:"),e("br"),e("br"),e("code",null,"npm i @needle-tools/engine")],-1),_e={href:"https://stackblitz.com/",target:"_blank",rel:"noopener noreferrer"},me=e("br",null,null,-1),ge={href:"https://engine.needle.tools/new",target:"_blank",rel:"noopener noreferrer"},be=a('

    If you're not using npm or a bundler, you can instead add a prebundled version of Needle Engine to your website:

    https://unpkg.com/@needle-tools/engine/dist/needle-engine.min.js or
    https://unpkg.com/@needle-tools/engine/dist/needle-engine.light.min.js (no physics module, smaller)




    Third-Party Dependencies

    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. Please download it from the official website:

    ',7),we=e("br",null,null,-1),ye=e("br",null,null,-1),fe=e("hr",null,null,-1),ve=e("h3",{id:"recommended-dependencies",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#recommended-dependencies","aria-hidden":"true"},"#"),t(" Recommended Dependencies")],-1),xe=e("br",null,null,-1),ke=e("br",null,null,-1),Se=e("br",null,null,-1),je=e("br",null,null,-1),Ne=e("em",null,"recommend",-1),Ee=e("br",null,null,-1),Te=a('

    Questions?

    The local website shows a warning: website not secure

    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. Now you should see your scene in the browser!

    Something is not working as expected? Where can I see logs?

    Keep an eye for console warnings! We log useful details about recommended project settings and so on. For example, your project should be set to Linear color space (not Gamma), and we'll log an error to the Unity console if that's not the case.

    ',3),Ce=e("h2",{id:"what-s-next",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-s-next","aria-hidden":"true"},"#"),t(" What's next?")],-1),Ie=e("br",null,null,-1),Be={href:"https://discord.needle.tools",target:"_blank",rel:"noopener noreferrer"},De={href:"https://forum.needle.tools",target:"_blank",rel:"noopener noreferrer"};function qe(Ue,Pe){const c=i("needle-button"),s=i("ExternalLinkIcon"),u=i("video-embed"),l=i("RouterLink"),d=i("os-link"),h=i("ClientOnly");return m(),g("div",null,[f,r(` | Tool | | | +| -- | -- | -- | +| Node.js **(required)** | 16.x or 18.x
    [Windows](https://nodejs.org/dist/v18.16.0/node-v18.16.0-x64.msi)
    [MacOS](https://nodejs.org/dist/v18.16.0/node-v18.16.0.pkg) | For running a local development server +| VS Code *(recommended)* | any version
    [Windows](https://code.visualstudio.com/sha/download?build=stable&os=win32-x64-user)
    [MacOS](https://code.visualstudio.com/sha/download?build=stable&os=darwin-universal) | For code editing (optional) | +| **Supported Editors** | | +| Unity | 2020.3.16+
    2021.3.9+
    2022.3.0+
    [Get Unity Hub](https://unity.com/download) | For setting up your scenes, components, animations... | +| Blender | 3.3
    3.4
    3.5
    3.6
    [Get Blender](https://www.blender.org/download/) | For setting up your scenes, components, animations... | + `),r(` ### For optimized builds + +| Tool | | | +| -- | -- | -- | +| | | | +| **toktx** | 4.1
    [Windows](https://fwd.needle.tools/needle-engine/toktx/win)
    [MacOS](https://fwd.needle.tools/needle-engine/toktx/osx)
    [Mac OS Apple Silicon](https://fwd.needle.tools/needle-engine/toktx/osx-silicon)
    [Other Releases](https://github.com/KhronosGroup/KTX-Software/releases/tag/v4.1.0-rc3) | For texture compression (recommended)
    You can read more about that [here](./deployment.md#production-builds) in our docs `),v,x,k,S,j,n(c,{event_goal:"download_unity",event_position:"getting_started",large:"",href:"https://engine.needle.tools/downloads/unity?utm_source=needle_docs&utm_content=getting_started"},{default:o(()=>[N]),_:1}),r(" [Mirror](https://package-installer.glitch.me/v1/installer/needle/com.needle.engine-exporter?registry=https://packages.needle.tools&scope=com.needle&scope=org.khronos) "),e("details",E,[T,e("ul",null,[C,I,e("li",null,[e("p",null,[B,t(" โ€“ Select the menu option "),D,t(" to view, open and modify all available "),e("a",q,[t("sample scenes"),n(s)]),t(".")])])])]),e("details",U,[P,n(u,{src:"https://www.youtube.com/watch?v=3dB-d1Jo_Mk",limit_height:""}),n(u,{src:"https://www.youtube.com/watch?v=gZX_sqrne8U",limit_height:""})]),O,e("details",F,[W,L,e("ol",null,[M,e("li",null,[e("p",null,[t("Select one of the templates with (needle) in their name and click "),V,t("."),A,t(" We recommend the "),e("a",R,[t("Collaborative Sandbox"),n(s)]),t(" template which is a great way to get started with interactivity, multiplayer, and adding assets.")])]),z]),Y]),e("details",G,[Q,e("ol",H,[e("li",null,[e("p",null,[K,t(" Now, select a web project template for your project. The default template is based on "),e("a",X,[t("Vite"),n(s)]),t(", a fast web app bundler.")])]),J])]),Z,$,ee,te,ne,oe,n(c,{event_goal:"download_blender",event_position:"getting_started",large:"",href:"https://engine.needle.tools/downloads/blender?utm_source=needle_docs&utm_content=getting_started"},{default:o(()=>[le]),_:1}),t(),se,ie,e("p",null,[e("strong",null,[t("See "),n(l,{to:"/blender/"},{default:o(()=>[t("Needle Engine for Blender")]),_:1})]),t(" for a full list of features and instructions on getting started.")]),re,ae,de,ce,ue,e("p",null,[t("You can work directly with Needle Engine without using any Integration. Needle Engine uses "),e("a",he,[t("three.js"),n(s)]),t(" as scene graph and rendering backend, so all functionality from three.js is available in Needle as well.")]),pe,e("p",null,[t("For quick experiments, we provide a convenient link to create a new project ready to start, powered by "),e("a",_e,[t("StackBlitz"),n(s)]),t(":"),me,e("a",ge,[t("engine.needle.tools/new"),n(s)])]),be,n(h,null,{default:o(()=>[r('
    Needle Engine for Unity โ€” Needle Engine for Blender '),n(d,{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:o(()=>[t("Download: Node.js 18+ โญ")]),_:1}),we,t(),ye,fe,ve,e("p",null,[xe,n(d,{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:o(()=>[t("Download: toktx texture tools ๐Ÿ—œ")]),_:1}),ke,t(" We use toktx to locally optimize your files. Learn more about production builds "),n(l,{to:"/deployment.html#production-builds"},{default:o(()=>[t("in the docs")]),_:1}),t(".")]),e("p",null,[Se,n(d,{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:o(()=>[t("Download: Visual Studio Code ๐Ÿ“‘")]),_:1}),je,t(" When you plan to edit or write code (js or HTML) then we "),Ne,t(" that you use VSCode as your code editor.")]),Ee]),_:1}),Te,e("p",null,[t("Please also have a look at "),n(l,{to:"/faq.html"},{default:o(()=>[t("our FAQ")]),_:1}),t(" if your question is not answered here.")]),r(` +## Option 1: Quick Start โ€” Starter Project โšก +1. **Download or Clone this repository** + It's set up with the right packages and settings to get you started right away. + + _Clone with HTTPS:_ \`\`https://github.com/needle-tools/needle-engine-support.git\`\` + _OR clone with SSH:_ \`\`git@github.com:needle-tools/needle-engine-support.git\`\` + _OR download directly:_ Download Repository + + +2. **Open the starter project** + Open \`starter/Needle Engine Starter 2020_3\` for a full sandbox project that's ready to run (including a couple of simple example scenes for lightmaps and custom shaders). + This is a sandbox builder project! It already comes with multi-player capabilities, and works across mobile, desktop, VR and AR. + +3. **Press Play** + Make sure the scene CollaborativeSandbox is open, and press Play! This will automatically do some setup steps and start a local server. + Once the setup is complete, a browser window will open, and your project is live. + From now on, all changes you do in Unity will be immediately visible in your browser. + + > **Note**: Your browser might warn you about an untrusted SSL connection. Don't worry, the connection is still encrypted โ€“ please click "Advance" if your browser asks you to verify that you're sure you want to visit your server. + +4. **Make it your own** + Add assets and components, play around with lighting, add scripts and logic โ€“ this is your world now! + You can also [publish it on the web for free](#deploy-your-project-to-glitch-) so that others can join you. +`),Ce,e("ul",null,[e("li",null,[n(l,{to:"/export.html"},{default:o(()=>[t("Exporting 3D objects and content")]),_:1})]),e("li",null,[n(l,{to:"/project-structure.html"},{default:o(()=>[t("Project Structure")]),_:1})]),e("li",null,[n(l,{to:"/deployment.html"},{default:o(()=>[t("Deploy your website to the web")]),_:1})]),e("li",null,[n(l,{to:"/getting-started/typescript-essentials.html"},{default:o(()=>[t("Typescript Essentials")]),_:1})]),e("li",null,[n(l,{to:"/getting-started/for-unity-developers.html"},{default:o(()=>[t("Needle Engine for Unity Developers")]),_:1})]),e("li",null,[n(l,{to:"/scripting.html"},{default:o(()=>[t("Scripting Reference")]),_:1})])]),e("p",null,[t("In case you need more troubleshooting help, please see the "),n(l,{to:"/faq.html"},{default:o(()=>[t("Questions and Answers")]),_:1}),t(" section."),Ie,t(" You can also join our "),e("a",Be,[t("Discord Community"),n(s)]),t(" or "),e("a",De,[t("Forum"),n(s)])])])}const We=_(y,[["render",qe],["__file","index.html.vue"]]);export{We as default}; diff --git a/assets/index.html-45b10041.js b/assets/index.html-45b10041.js new file mode 100644 index 000000000..59e34aecd --- /dev/null +++ b/assets/index.html-45b10041.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3721011d","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{e as data}; diff --git a/assets/index.html-45bf7509.js b/assets/index.html-45bf7509.js new file mode 100644 index 000000000..306322c4c --- /dev/null +++ b/assets/index.html-45bf7509.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-b5125a16","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{e as data}; diff --git a/assets/index.html-4d11b3c8.js b/assets/index.html-4d11b3c8.js new file mode 100644 index 000000000..c13b51fe6 --- /dev/null +++ b/assets/index.html-4d11b3c8.js @@ -0,0 +1,38 @@ +import{_ as a,M as t,p as e,q as o,N as p,R as n,a1 as c}from"./framework-f6820c83.js";const i={},l=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),u=c(`

    https://github.com/needle-tools/needle-engine-support/assets/30328735/92404e43-9d45-4ee2-a018-fb00412b6bd5

    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);function r(k,d){const s=t("contribution-header");return e(),o("div",null,[l,p(s,{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"}),u])}const m=a(i,[["render",r],["__file","index.html.vue"]]);export{m as default}; diff --git a/assets/index.html-4f00c2ff.js b/assets/index.html-4f00c2ff.js new file mode 100644 index 000000000..369942922 --- /dev/null +++ b/assets/index.html-4f00c2ff.js @@ -0,0 +1,44 @@ +import{_ as l,M as e,p as i,q as u,N as t,V as o,R as n,t as s}from"./framework-f6820c83.js";const r={},k={href:"https://developer.apple.com/documentation/arkit/arkit_in_ios/content_anchors/detecting_images_in_an_ar_experience",target:"_blank",rel:"noopener noreferrer"},d=n("blockquote",null,[n("p",null,[n("code",null,"SCNPlane"),s(" is vertically oriented in its local coordinate space, but "),n("code",null,"ARImageAnchor"),s(" assumes the image is horizontal in its local space, so rotate the plane to match.")])],-1),m=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Behaviour"),n("span",{class:"token punctuation"},","),s(" GameObject"),n("span",{class:"token punctuation"},","),s(" serializable"),n("span",{class:"token punctuation"},","),s(" USDZExporter "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Object3D"),n("span",{class:"token punctuation"},","),s(" Euler "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"three"'),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"QuickLookObjectsToFix"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),s("Object3D"),n("span",{class:"token punctuation"},")"),s(` + objectToFix`),n("span",{class:"token operator"},"!"),n("span",{class:"token operator"},":"),s(" Object3D"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"private"),s(" usdzExporter"),n("span",{class:"token operator"},"!"),n("span",{class:"token operator"},":"),s(" USDZExporter"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"private"),s(" startRot"),n("span",{class:"token operator"},":"),s(" Euler "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"Euler"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token function"},"onEnable"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("usdzExporter "),n("span",{class:"token operator"},"="),s(" GameObject"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"findObjectOfType"),n("span",{class:"token punctuation"},"("),s("USDZExporter"),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},"!"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("startRot "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("objectToFix"),n("span",{class:"token punctuation"},"."),s("rotation"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"subscribeToBeforeExportEvent"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"onDisable"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"unsubscribeFromBeforeExportEvent"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token function"},"subscribeToBeforeExportEvent"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("usdzExporter"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"addEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"before-export"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("onBeforeExport"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("usdzExporter"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"addEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"after-export"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("onAfterExport"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token function"},"unsubscribeFromBeforeExportEvent"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("usdzExporter"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"removeEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"before-export"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("onBeforeExport"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("usdzExporter"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"removeEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"after-export"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("onAfterExport"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token function-variable function"},"onBeforeExport"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("objectToFix"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"updateMatrixWorld"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("objectToFix"),n("span",{class:"token punctuation"},"."),s("rotation"),n("span",{class:"token punctuation"},"."),s("x "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token operator"},"-"),s("Math"),n("span",{class:"token punctuation"},"."),n("span",{class:"token constant"},"PI"),s(),n("span",{class:"token operator"},"/"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("objectToFix"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"updateMatrixWorld"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token function-variable function"},"onAfterExport"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("objectToFix"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"updateMatrixWorld"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("objectToFix"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"setRotationFromEuler"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("startRot"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("objectToFix"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"updateMatrixWorld"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),b={href:"https://github.com/llllkatjallll",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/needle-tools/needle-engine-support/discussions/184",target:"_blank",rel:"noopener noreferrer"},h=n("p",null,"EDIT: Code cleanup and fixes.",-1);function f(x,_){const a=e("ExternalLinkIcon"),c=e("contribution-preview"),p=e("contributions-author");return i(),u("div",null,[t(p,{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:o(()=>[t(c,{title:"QuickLook Vertical Image Tracker",pageUrl:"/docs/community/contributions/ericcraft-mh/quicklook-vertical-image-tracker"},{default:o(()=>[n("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 "),n("a",k,[s("Detecting Images in an AR Experience"),t(a)]),s(" page:")]),d,m,n("p",null,[s("Thanks to "),n("a",b,[s("llllkatjallll"),t(a)]),s(" as their "),n("a",v,[s("Set fallback material for USDZ exporter"),t(a)]),s(" solution helped me come up with a working solution for this.")]),h]),_:1})]),_:1})])}const y=l(r,[["render",f],["__file","index.html.vue"]]);export{y as default}; diff --git a/assets/index.html-57d757de.js b/assets/index.html-57d757de.js new file mode 100644 index 000000000..2421b4230 --- /dev/null +++ b/assets/index.html-57d757de.js @@ -0,0 +1,44 @@ +import{_ as p,M as e,p as c,q as i,N as a,R as s,t as n,a1 as l}from"./framework-f6820c83.js";const u={},r=s("p",null,[s("a",{href:"/docs/community/contributions"},"Overview")],-1),k={href:"https://developer.apple.com/documentation/arkit/arkit_in_ios/content_anchors/detecting_images_in_an_ar_experience",target:"_blank",rel:"noopener noreferrer"},d=l(`

    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.

    import { Behaviour, GameObject, serializable, USDZExporter } from "@needle-tools/engine";
    +import { Object3D, Euler } from "three";
    +
    +export class QuickLookObjectsToFix extends Behaviour {
    +
    +    @serializable(Object3D)
    +    objectToFix!: Object3D;
    +
    +    private usdzExporter!: USDZExporter;
    +    private startRot: Euler = new Euler();
    +
    +    onEnable() {
    +        this.usdzExporter = GameObject.findObjectOfType(USDZExporter)!;
    +        this.startRot = this.objectToFix.rotation;
    +        this.subscribeToBeforeExportEvent();
    +    }
    +
    +    onDisable() {
    +        this.unsubscribeFromBeforeExportEvent();
    +    }
    +
    +    private subscribeToBeforeExportEvent() {
    +        this.usdzExporter.addEventListener("before-export", this.onBeforeExport);
    +        this.usdzExporter.addEventListener("after-export", this.onAfterExport);
    +    }
    +
    +    private unsubscribeFromBeforeExportEvent() {
    +        this.usdzExporter.removeEventListener("before-export", this.onBeforeExport);
    +        this.usdzExporter.removeEventListener("after-export", this.onAfterExport);
    +    }
    +
    +    private onBeforeExport = () => {
    +        this.objectToFix.updateMatrixWorld();
    +        this.objectToFix.rotation.x = -Math.PI / 2;
    +        this.objectToFix.updateMatrixWorld();
    +    }
    +
    +    private onAfterExport = () => {
    +        this.objectToFix.updateMatrixWorld();
    +        this.objectToFix.setRotationFromEuler(this.startRot);
    +        this.objectToFix.updateMatrixWorld();
    +    }
    +}
    +
    `,2),v={href:"https://github.com/llllkatjallll",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/needle-tools/needle-engine-support/discussions/184",target:"_blank",rel:"noopener noreferrer"},b=s("p",null,"EDIT: Code cleanup and fixes.",-1);function h(f,x){const o=e("contribution-header"),t=e("ExternalLinkIcon");return c(),i("div",null,[r,a(o,{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"}),s("p",null,[n("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 "),s("a",k,[n("Detecting Images in an AR Experience"),a(t)]),n(" page:")]),d,s("p",null,[n("Thanks to "),s("a",v,[n("llllkatjallll"),a(t)]),n(" as their "),s("a",m,[n("Set fallback material for USDZ exporter"),a(t)]),n(" solution helped me come up with a working solution for this.")]),b])}const w=p(u,[["render",h],["__file","index.html.vue"]]);export{w as default}; diff --git a/assets/index.html-615ba1f2.js b/assets/index.html-615ba1f2.js new file mode 100644 index 000000000..0c0279a7e --- /dev/null +++ b/assets/index.html-615ba1f2.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-e6fbcab8","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{e as data}; diff --git a/assets/index.html-64bc0c3c.js b/assets/index.html-64bc0c3c.js new file mode 100644 index 000000000..a59e5c7d6 --- /dev/null +++ b/assets/index.html-64bc0c3c.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-1d2bf24a","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{e as data}; diff --git a/assets/index.html-6af3f72b.js b/assets/index.html-6af3f72b.js new file mode 100644 index 000000000..a569acdfe --- /dev/null +++ b/assets/index.html-6af3f72b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-e9b14526","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{e as data}; diff --git a/assets/index.html-7104cbee.js b/assets/index.html-7104cbee.js new file mode 100644 index 000000000..0d60d6f5f --- /dev/null +++ b/assets/index.html-7104cbee.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-40fd290b","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{e as data}; diff --git a/assets/index.html-72dcd24e.js b/assets/index.html-72dcd24e.js new file mode 100644 index 000000000..c8edc5123 --- /dev/null +++ b/assets/index.html-72dcd24e.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-10c99314","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{e as data}; diff --git a/assets/index.html-7517d60a.js b/assets/index.html-7517d60a.js new file mode 100644 index 000000000..f932ab77c --- /dev/null +++ b/assets/index.html-7517d60a.js @@ -0,0 +1,164 @@ +import{_ as a,M as t,p as e,q as p,N as o,R as n,a1 as c}from"./framework-f6820c83.js";const i={},l=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),u=c(`

    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);function k(r,d){const s=t("contribution-header");return e(),p("div",null,[l,o(s,{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"}),u])}const m=a(i,[["render",k],["__file","index.html.vue"]]);export{m as default}; diff --git a/assets/index.html-75e422fa.js b/assets/index.html-75e422fa.js new file mode 100644 index 000000000..15f390d66 --- /dev/null +++ b/assets/index.html-75e422fa.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-44471890","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{e as data}; diff --git a/assets/index.html-76f967a7.js b/assets/index.html-76f967a7.js new file mode 100644 index 000000000..995b746da --- /dev/null +++ b/assets/index.html-76f967a7.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-289be385","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{e as data}; diff --git a/assets/index.html-7afee307.js b/assets/index.html-7afee307.js new file mode 100644 index 000000000..cdf0ef5f2 --- /dev/null +++ b/assets/index.html-7afee307.js @@ -0,0 +1,66 @@ +import{_ as a,M as t,p,q as o,N as e,R as n,a1 as c}from"./framework-f6820c83.js";const i={},l=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),u=c(`

    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.

    import { Behaviour } from "@needle-tools/engine";
    +
    +export class Microphone extends Behaviour {
    +  start() {
    +    this.getLocalStream();
    +  }
    +
    +  public el: Element;
    +
    +  attachStream(stream, el, options) {
    +    var item;
    +    var URL = window.URL;
    +    var element = el;
    +    var opts = {
    +      autoplay: true,
    +      mirror: false,
    +      muted: false,
    +      audio: false,
    +      disableContextMenu: false,
    +    };
    +
    +    if (options) {
    +      for (item in options) {
    +        opts[item] = options[item];
    +      }
    +    }
    +
    +    if (!element) {
    +      element = document.createElement(opts.audio ? "audio" : "video");
    +    } else if (element.tagName.toLowerCase() === "audio") {
    +      opts.audio = true;
    +    }
    +
    +    if (opts.autoplay) element.autoplay = "autoplay";
    +    if (opts.muted) element.muted = true;
    +    if (!opts.audio && opts.mirror) {
    +      ["", "moz", "webkit", "o", "ms"].forEach(function (prefix) {
    +        var styleName = prefix ? prefix + "Transform" : "transform";
    +        element.style[styleName] = "scaleX(-1)";
    +      });
    +    }
    +
    +    element.srcObject = stream;
    +    return element;
    +  }
    +
    +  getLocalStream() {
    +    navigator.mediaDevices
    +      .getUserMedia({
    +        video: false,
    +        audio: true,
    +      })
    +      .then((stream) => {
    +        var doesnotexist = !this.el;
    +        this.el = this.attachStream(stream, this.el, {
    +          audio: true,
    +          autoplay: true,
    +        });
    +        if (doesnotexist) document.body.appendChild(this.el);
    +      })
    +      .catch((err) => {
    +        console.log("u got an error:" + err);
    +      });
    +  }
    +}
    +
    `,2);function k(r,d){const s=t("contribution-header");return p(),o("div",null,[l,e(s,{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"}),u])}const m=a(i,[["render",k],["__file","index.html.vue"]]);export{m as default}; diff --git a/assets/index.html-7de73919.js b/assets/index.html-7de73919.js new file mode 100644 index 000000000..9593e932c --- /dev/null +++ b/assets/index.html-7de73919.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-8daa1a0e","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":1724764801000},"filePathRelative":"index.md"}');export{e as data}; diff --git a/assets/index.html-7dfa6204.js b/assets/index.html-7dfa6204.js new file mode 100644 index 000000000..a654a7b7a --- /dev/null +++ b/assets/index.html-7dfa6204.js @@ -0,0 +1,38 @@ +import{_ as l,M as a,p,q as i,N as t,V as e,R as n,t as s}from"./framework-f6820c83.js";const u={},r=n("p",null,"https://github.com/needle-tools/needle-engine-support/assets/30328735/92404e43-9d45-4ee2-a018-fb00412b6bd5",-1),k=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Behaviour"),n("span",{class:"token punctuation"},","),s(" serializable"),n("span",{class:"token punctuation"},","),s(" setWorldPosition "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Object3D"),n("span",{class:"token punctuation"},","),s(" Vector3 "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"three"'),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token keyword"},"const"),s(" vector1 "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"Vector3"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"const"),s(" vector2 "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"Vector3"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"PointerFollower"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),s("Object3D"),n("span",{class:"token punctuation"},")"),s(` + target`),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s(" Object3D"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + offset`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token builtin"},"number"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"10"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token function"},"update"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token keyword"},"void"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" cam "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("mainCamera"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" input "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("input"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token operator"},"!"),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("target "),n("span",{class:"token operator"},"||"),s(),n("span",{class:"token operator"},"!"),s("cam"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token keyword"},"return"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"// get relative mouse position, in range -1 to 1"),s(` + `),n("span",{class:"token keyword"},"const"),s(" mouse "),n("span",{class:"token operator"},"="),s(" input"),n("span",{class:"token punctuation"},"."),s("mousePositionRC"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"// get world position of mouse on the near plane"),s(` + vector1`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"set"),n("span",{class:"token punctuation"},"("),s("mouse"),n("span",{class:"token punctuation"},"."),s("x"),n("span",{class:"token punctuation"},","),s(" mouse"),n("span",{class:"token punctuation"},"."),s("y"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token operator"},"-"),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"unproject"),n("span",{class:"token punctuation"},"("),s("cam"),n("span",{class:"token operator"},"!"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"// caulculate direction from camera to world mouse"),s(` + vector2`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"copy"),n("span",{class:"token punctuation"},"("),s("vector1"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"sub"),n("span",{class:"token punctuation"},"("),s("cam"),n("span",{class:"token punctuation"},"."),s("position"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"normalize"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"// offset it to the wanted distance"),s(` + vector1`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"addScaledVector"),n("span",{class:"token punctuation"},"("),s("vector2"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("offset"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"// apply the result"),s(` + `),n("span",{class:"token function"},"setWorldPosition"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("target"),n("span",{class:"token punctuation"},","),s(" vector1"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1);function d(m,b){const o=a("contribution-preview"),c=a("contributions-author");return p(),i("div",null,[t(c,{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:e(()=>[t(o,{title:"Calculate pointer world position",pageUrl:"/docs/community/contributions/kipash/calculate-pointer-world-position"},{default:e(()=>[r,k]),_:1})]),_:1})])}const f=l(u,[["render",d],["__file","index.html.vue"]]);export{f as default}; diff --git a/assets/index.html-81a91362.js b/assets/index.html-81a91362.js new file mode 100644 index 000000000..e1929902b --- /dev/null +++ b/assets/index.html-81a91362.js @@ -0,0 +1,16 @@ +import{_ as a,M as e,p as t,q as o,N as p,R as n,a1 as i}from"./framework-f6820c83.js";const c={},l=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),u=i(`

    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);function r(d,k){const s=e("contribution-header");return t(),o("div",null,[l,p(s,{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"}),u])}const v=a(c,[["render",r],["__file","index.html.vue"]]);export{v as default}; diff --git a/assets/index.html-8eb2326b.js b/assets/index.html-8eb2326b.js new file mode 100644 index 000000000..dc7dc1d2b --- /dev/null +++ b/assets/index.html-8eb2326b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-fd5a4508","path":"/community/contributions/krisrok/always-open-in-specific-browser/","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/krisrok: always open in specific browser.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{e as data}; diff --git a/assets/index.html-915af730.js b/assets/index.html-915af730.js new file mode 100644 index 000000000..1c61e8155 --- /dev/null +++ b/assets/index.html-915af730.js @@ -0,0 +1 @@ +import{_ as o,M as n,p as a,q as r,N as i,R as e}from"./framework-f6820c83.js";const s={},l=e("p",null,[e("a",{href:"/docs/community/contributions"},"Overview")],-1),c=e("p",null,"This is live for preview over at https://needle-ar.glitch.me/",-1),h=e("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),u=e("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),d=e("p",null,"Public Github Repository: https://github.com/ROBYER1/Needle-AR-Demo",-1);function p(_,m){const t=n("contribution-header");return a(),r("div",null,[l,i(t,{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/174",title:"AR Move/Scale/Rotate Controls for Needle on Mobile",gradient:"True"}),c,h,u,d])}const g=o(s,[["render",p],["__file","index.html.vue"]]);export{g as default}; diff --git a/assets/index.html-932f723a.js b/assets/index.html-932f723a.js new file mode 100644 index 000000000..e0ddb8c1c --- /dev/null +++ b/assets/index.html-932f723a.js @@ -0,0 +1,30 @@ +import{_ as p,M as s,p as c,q as i,N as a,R as n,t as e,a1 as l}from"./framework-f6820c83.js";const u={},r=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),d={href:"https://linen-upbeat-sailboat.glitch.me/",target:"_blank",rel:"noopener noreferrer"},k=l(`
    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);
    +        }
    +    }
    +}
    +
    `,1);function m(v,b){const t=s("contribution-header"),o=s("ExternalLinkIcon");return c(),i("div",null,[r,a(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/166",title:"Camera Video Background",gradient:"True"}),n("p",null,[e("Put it anywhere in your scene to render a camera video behind your 3D scene "),n("a",d,[e("Live demo"),a(o)])]),k])}const h=p(u,[["render",m],["__file","index.html.vue"]]);export{h as default}; diff --git a/assets/index.html-9603f594.js b/assets/index.html-9603f594.js new file mode 100644 index 000000000..d01de56fc --- /dev/null +++ b/assets/index.html-9603f594.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-a305d722","path":"/community/contributions/web3kev/squeeze-to-scale-object-or-world-in-vr/","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/web3kev: squeeze to scale object or world in vr.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{e as data}; diff --git a/assets/index.html-9d6db80a.js b/assets/index.html-9d6db80a.js new file mode 100644 index 000000000..3ff6d0ea8 --- /dev/null +++ b/assets/index.html-9d6db80a.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-28e23eea","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{e as data}; diff --git a/assets/index.html-ac7d998f.js b/assets/index.html-ac7d998f.js new file mode 100644 index 000000000..5d1172346 --- /dev/null +++ b/assets/index.html-ac7d998f.js @@ -0,0 +1,36 @@ +import{_ as u}from"./logo-b695892f.js";import{_ as h}from"./texture-compression-8cd31165.js";import{_ as m,M as r,p as b,q as g,N as t,R as e,V as a,t as n,a1 as i}from"./framework-f6820c83.js";const k="/docs/blender/settings.webp",_="/docs/blender/project-panel.webp",w="/docs/blender/project-panel-2.webp",f="/docs/blender/project-panel-3.webp",v="/docs/blender/settings-color-management.webp",y="/docs/blender/environment.webp",x="/docs/blender/environment-camera.webp",j="/docs/blender/dont-export.webp",C="/docs/blender/animatorcontroller-open.webp",T="/docs/blender/animatorcontroller-overview.webp",B="/docs/blender/animatorcontroller-assigning.webp",A="/docs/blender/timeline_setup.webp",P="/docs/blender/timeline.webp",S="/docs/blender/components-panel.webp",N="/docs/blender/components-panel-select.webp",E="/docs/blender/remove-component.webp",I="/docs/blender/lightmapping-object.webp",R="/docs/blender/lightmapping-scene-panel.webp",D="/docs/blender/lightmapping-panel.webp",L="/docs/blender/updates.webp",Y="/docs/blender/bugreporter.webp",q={},M=e("br",null,null,-1),U=e("img",{src:u,style:{"max-height":"70px"}},null,-1),V=e("h1",{id:"needle-engine-for-blender",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#needle-engine-for-blender","aria-hidden":"true"},"#"),n(" Needle Engine for Blender")],-1),W=e("p",null,[n("Thank you for using Needle Engine for Blender."),e("br"),n(" With this addon you can create highly interactive and optimized WebGL and WebXR experiences inside Blender that run using Needle Engine and three.js."),e("br"),n(" 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. You own your content!")],-1),z=e("p",null,[e("em",null,"Automatically export HDRI environment lights directly from blender. Save to reload your local server")],-1),F=e("h1",{id:"content-overview",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#content-overview","aria-hidden":"true"},"#"),n(" Content Overview")],-1),O={class:"table-of-contents"},G=e("a",{target:"_blank",href:"https://www.blender.org/download/"},"Install Blender",-1),H=e("a",{class:"plausible-event-name=download_blender plausible-event-position=blender_download",target:"_blank",href:"https://engine.needle.tools/downloads/blender?utm_source=needle_docs&utm_content=blender"},"Download Needle Engine for Blender",-1),X=e("h2",{id:"preface",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#preface","aria-hidden":"true"},"#"),n(" Preface")],-1),J=e("strong",null,"Your feedback is invaluable",-1),$=e("br",null,null,-1),K={href:"https://forum.needle.tools",target:"_blank",rel:"noopener noreferrer"},Q=i('

    Download and Installation ๐Ÿ’ฟ

    Step 1 โ€ข Install Blender 3.6, 4.0, 4.1 or 4.2

    ',2),Z=i('

    Step 3 โ€ข Download Needle Engine for Blender

    The Blender addon is downloaded as a zip file.
    In Blender go to File / Settings / Add-ons and click the Install button.
    Then select the downloaded zip to install it.

    Settings

    Getting Started ๐Ÿšฉ

    ',4),ee={href:"https://engine.needle.tools/downloads/blender/download-samples?utm_source=needle_docs&utm_content=blender",target:"_blank",rel:"noopener noreferrer"},ne=i('

    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.

    Project panel

    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:
    Project panel

    Project Panel overview

    Project panel

    1. The path to your web project. You can use the little folder button on the right to select a different path.
    2. 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
    3. Directory open the directory of your web project (the Project Path)
    4. 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.
    5. Code Editor tries to open the vscode workspace in your web project
    6. 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.
    7. 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)
    8. Open the documentation

    Blender Settings

    Color Management

    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

    Correct color management settings

    Environment Lighting

    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.

    Environment

    ',16),te=e("p",null,[n("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"),n(" - note that the Camera "),e("code",null,"backgroundBlurriness"),n(" and "),e("code",null,"backgroundIntensity"),n(" settings override the Viewport shading settings.")],-1),ae=e("p",null,[e("img",{src:x,alt:"Environment Camera"})],-1),oe=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","aria-hidden":"true"},"#"),n(" Add your custom HDRi / EXR environment lighting and skybox")],-1),se=i('

    Export

    To exclude an object from being exported you can disable the Viewport and the Render display (see image below)

    Exclude from export

    Animation ๐Ÿ‡

    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)

    ',5),ie=e("h3",{id:"animatorcontroller",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#animatorcontroller","aria-hidden":"true"},"#"),n(" AnimatorController")],-1),le=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),re=e("p",null,[e("em",null,[n("Create and export "),e("a",{href:"#animatorcontroller"},"animator statemachines"),n(" for controlling complex character animations")])],-1),pe=e("h4",{id:"creating-an-animatorcontroller",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#creating-an-animatorcontroller","aria-hidden":"true"},"#"),n(" Creating an AnimatorController")],-1),ce=e("p",null,"The AnimatorController editor can be opened using the EditorType dropdown in the topleft corner of each panel:",-1),de=e("p",null,[e("img",{src:C,alt:"AnimatorController open window"})],-1),ue=i('

    Creating a new animator-controller asset โ˜ or select one from your previously created assets

    Graph overview

    AnimatorController overview

    1. Use Shift+A to create a new AnimatorState
    2. 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)
    3. 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)
    4. 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.

    Using an AnimatorController

    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.

    AnimatorController assign to animator

    You can set the Animator parameters from typescript or by e.g. using the event of a Button component

    Timeline โ€” nla tracks export ๐ŸŽฌ

    Exporting Blender nla tracks to threejs.
    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

    import { Behaviour, PlayableDirector, serializable, Mathf } from "@needle-tools/engine";
    +
    +export class ScrollTimeline extends Behaviour {
    +
    +    @serializable(PlayableDirector)
    +    timeline?: PlayableDirector;
    +
    +    @serializable()
    +    sensitivity: number = .5;
    +
    +    @serializable()
    +    clamp: boolean = false;
    +
    +    private _targetTime: number = 0;
    +
    +    awake() {
    +        this.context.domElement.addEventListener("wheel", this.onWheel);
    +        if (this.timeline) this.timeline.pause();
    +    }
    +
    +    private onWheel = (e: WheelEvent) => {
    +        if (this.timeline) {
    +            this._targetTime = this.timeline.time + e.deltaY * 0.01 * this.sensitivity;
    +            if (this.clamp) this._targetTime = Mathf.clamp(this._targetTime, 0, this.timeline.duration);
    +        }
    +    }
    +
    +    update(): void {
    +        if (!this.timeline) return;
    +        const time = Mathf.lerp(this.timeline.time, this._targetTime, this.context.time.deltaTime / .3);
    +        this.timeline.time = time;
    +        this.timeline.pause();
    +        this.timeline.evaluate();
    +    }
    +}
    +

    Interactivity ๐Ÿ˜Ž

    You can add or remove components to objects in your hierarchy using the Needle Components panel:

    Component panel

    Component 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:

    Remove component

    Custom Components

    Custom components can also be easily added by simply writing Typescript classes. They will automatically compile and show up in Blender when saved.

    ',20),he=e("code",null,".ts",-1),me=e("code",null,"src/scripts",-1),be={href:"http://docs.needle.tools/scripting",target:"_blank",rel:"noopener noreferrer"},ge=e("div",{class:"custom-container warning"},[e("p",{class:"custom-container-title"},"Note"),e("p",null,[n("Make sure "),e("code",null,"@needle-tools/needle-component-compiler"),n(" 2.x is installed in your web project (package.json devDependencies)")])],-1),ke=e("h2",{id:"lightmapping",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#lightmapping","aria-hidden":"true"},"#"),n(" Lightmapping ๐Ÿ’ก")],-1),_e=e("p",null,[n("Needle Lightmapping will automatically generate lightmap UVs for all models marked to be lightmapped. For lightmapping to work you need at least one light and one object with "),e("code",null,"Lightmapped"),n(" turned on.")],-1),we={class:"custom-container danger"},fe=e("p",{class:"custom-container-title"},"Please keep in mind:",-1),ve={href:"https://discord.needle.tools",target:"_blank",rel:"noopener noreferrer"},ye={href:"https://engine.needle.tools/downloads/blender/lightmaps.blend",target:"_blank",rel:"noopener noreferrer"},xe=i('

    Use the Needle Object panel to enable lightmapping for a mesh object or light:

    Lightmapping object

    For quick access to lightmap settings and baking options you can use the scene view panel in the Needle tab:

    Lightmapping scene panel

    Alternatively you can also use the Lightmapping panel in the Render Properties tab:

    Lightmapping object

    Texture Compression

    ',7),je=e("code",null,"Material",-1),Ce=e("code",null,"Needle Material Settings",-1),Te=i('

    Texture Compression options in Blender

    Updating

    The lightbulb in the Needle Project panel informs you when a new version of the addon is available.
    Simply click the icon to download the new version.
    Update notification

    Debugging / Reporting a problem

    ',4),Be={href:"https://discord.needle.tools",target:"_blank",rel:"noopener noreferrer"},Ae=e("p",null,[n("Please also check the logs in Blender. You can find logs specific to the Needle Engine Addon via "),e("code",null,"Help/Needle"),n(" in Blender.")],-1),Pe=e("h3",{id:"integrated-bugreporter",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#integrated-bugreporter","aria-hidden":"true"},"#"),n(" Integrated Bugreporter")],-1),Se=e("p",null,[e("img",{src:Y,alt:"Needle Blender bugreporter panel"}),e("br"),n(" You can also automatically create and upload a bugreport directly from Blender (this currently requires a node.js web project being setup). Uploaded bugreports will solely used for debugging, they are encrypted on our backend and will deleted after 30 days.")],-1);function Ne(Ee,Ie){const s=r("video-embed"),o=r("router-link"),l=r("ExternalLinkIcon"),c=r("os-link"),d=r("ClientOnly"),p=r("RouterLink");return b(),g("div",null,[M,U,V,W,t(s,{src:"/docs/blender/environment-light.mp4"}),z,F,e("nav",O,[e("ul",null,[e("li",null,[t(o,{to:"#preface"},{default:a(()=>[n("Preface")]),_:1})]),e("li",null,[t(o,{to:"#download-and-installation"},{default:a(()=>[n("Download and Installation ๐Ÿ’ฟ")]),_:1}),e("ul",null,[e("li",null,[t(o,{to:"#step-1-install-blender-3.6-4.0-4.1-or-4.2"},{default:a(()=>[n("Step 1 โ€ข "),G,n(" 3.6, 4.0, 4.1 or 4.2")]),_:1})]),e("li",null,[t(o,{to:"#step-3-download-needle-engine-for-blender"},{default:a(()=>[n("Step 3 โ€ข "),H]),_:1})])])]),e("li",null,[t(o,{to:"#getting-started"},{default:a(()=>[n("Getting Started ๐Ÿšฉ")]),_:1}),e("ul",null,[e("li",null,[t(o,{to:"#project-panel-overview"},{default:a(()=>[n("Project Panel overview")]),_:1})])])]),e("li",null,[t(o,{to:"#blender-settings"},{default:a(()=>[n("Blender Settings")]),_:1}),e("ul",null,[e("li",null,[t(o,{to:"#color-management"},{default:a(()=>[n("Color Management")]),_:1})])])]),e("li",null,[t(o,{to:"#environment-lighting"},{default:a(()=>[n("Environment Lighting")]),_:1}),e("ul",null,[e("li",null,[t(o,{to:"#add-your-custom-hdri-exr-environment-lighting-and-skybox"},{default:a(()=>[n("Add your custom HDRi / EXR environment lighting and skybox")]),_:1})])])]),e("li",null,[t(o,{to:"#export"},{default:a(()=>[n("Export")]),_:1})]),e("li",null,[t(o,{to:"#animation"},{default:a(()=>[n("Animation ๐Ÿ‡")]),_:1}),e("ul",null,[e("li",null,[t(o,{to:"#animatorcontroller"},{default:a(()=>[n("AnimatorController")]),_:1})]),e("li",null,[t(o,{to:"#timeline-nla-tracks-export"},{default:a(()=>[n("Timeline โ€” nla tracks export ๐ŸŽฌ")]),_:1})])])]),e("li",null,[t(o,{to:"#interactivity"},{default:a(()=>[n("Interactivity ๐Ÿ˜Ž")]),_:1}),e("ul",null,[e("li",null,[t(o,{to:"#custom-components"},{default:a(()=>[n("Custom Components")]),_:1})])])]),e("li",null,[t(o,{to:"#lightmapping"},{default:a(()=>[n("Lightmapping ๐Ÿ’ก")]),_:1})]),e("li",null,[t(o,{to:"#texture-compression"},{default:a(()=>[n("Texture Compression")]),_:1})]),e("li",null,[t(o,{to:"#updating"},{default:a(()=>[n("Updating")]),_:1})]),e("li",null,[t(o,{to:"#debugging-reporting-a-problem"},{default:a(()=>[n("Debugging / Reporting a problem")]),_:1}),e("ul",null,[e("li",null,[t(o,{to:"#integrated-bugreporter"},{default:a(()=>[n("Integrated Bugreporter")]),_:1})])])])])]),X,e("p",null,[J,n(" when it comes to deciding which of those features should be prioritizes."),$,n(" If you have feedback for us please let us know in "),e("a",K,[n("our forum"),t(l)]),n("!")]),Q,t(d,null,{default:a(()=>[n(" ### Step 2 โ€ข "),t(c,{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:a(()=>[n("Install Nodejs โญ")]),_:1})]),_:1}),Z,e("ul",null,[e("li",null,[e("a",ee,[n("Download Blender Samples"),t(l)])])]),ne,t(s,{limit_height:"",max_height:"300px",src:"/docs/blender/environment.mp4"}),te,ae,oe,t(s,{limit_height:"",src:"/docs/blender/custom_hdri.mp4"}),n(),se,t(s,{limit_height:"",src:"/docs/blender/animation.mp4"}),n(),ie,le,t(s,{src:"/docs/blender/animatorcontroller-web.mp4"}),re,pe,ce,de,t(s,{limit_height:"",max_height:"188px",src:"/docs/blender/animatorcontroller-create.mp4"}),n(),ue,e("p",null,[n("To create custom components open the workspace via the Needle Project panel and add a "),he,n(" script file in "),me,n(" inside your web project. Please refer to the "),e("a",be,[n("scripting documentation"),t(l)]),n(" to learn how to write custom components for Needle Engine.")]),ge,ke,_e,e("div",we,[fe,e("p",null,[n("You are using an early preview of these features - we recommend creating a backup of your blend file when using Lightmapping at this point in time. Please report problems or errors you encounter in "),e("a",ve,[n("our discord"),t(l)]),n(" ๐Ÿ™")])]),t(s,{limit_height:"",max_height:"800px",src:"/docs/blender/lightmapping.mp4"}),n(),e("blockquote",null,[e("p",null,[n("You can download the original blend file from the video "),e("a",ye,[n("here"),t(l)]),n(".")])]),xe,e("p",null,[n("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,[n("requires "),t(p,{to:"/getting-started/#install-these-tools-for-production-builds"},{default:a(()=>[n("toktx")]),_:1}),n(" being installed")]),n("). But you can override or change the compression type per texture in the Material panel.")]),e("p",null,[n("You can modify the compression that is being applied per texture. To override the default compression settings go to the "),je,n(" tab and open the "),Ce,n(". There you will find a toggle to override the texture settings per texture used in your material. See the "),t(p,{to:"/deployment.html#how-do-i-choose-between-etc1s-uastc-and-webp-compression"},{default:a(()=>[n("texture compression table")]),_:1}),n(" for a brief overview over the differences between each compression algorithm.")]),Te,e("p",null,[n("If you run into any problems we're more than happy to help! Please join "),e("a",Be,[n("our discord"),t(l)]),n(" for fast support.")]),Ae,Pe,Se])}const Ye=m(q,[["render",Ne],["__file","index.html.vue"]]);export{Ye as default}; diff --git a/assets/index.html-cb603ab8.js b/assets/index.html-cb603ab8.js new file mode 100644 index 000000000..d8fa976bb --- /dev/null +++ b/assets/index.html-cb603ab8.js @@ -0,0 +1,17 @@ +import{_ as a,M as e,p as t,q as o,N as p,R as n,a1 as i}from"./framework-f6820c83.js";const c={},l=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),u=i(`

    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);function r(d,k){const s=e("contribution-header");return t(),o("div",null,[l,p(s,{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"}),u])}const v=a(c,[["render",r],["__file","index.html.vue"]]);export{v as default}; diff --git a/assets/index.html-cfe2bf15.js b/assets/index.html-cfe2bf15.js new file mode 100644 index 000000000..1a09f1a07 --- /dev/null +++ b/assets/index.html-cfe2bf15.js @@ -0,0 +1,150 @@ +import{_ as p,M as o,p as i,q as u,N as a,V as t,R as n,t as s}from"./framework-f6820c83.js";const r={},k={href:"https://linen-upbeat-sailboat.glitch.me/",target:"_blank",rel:"noopener noreferrer"},d=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Behaviour"),n("span",{class:"token punctuation"},","),s(" ClearFlags"),n("span",{class:"token punctuation"},","),s(" RGBAColor "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"VideoBackground"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token keyword"},"async"),s(),n("span",{class:"token function"},"awake"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"// create video element and put it inside the component"),s(` + `),n("span",{class:"token keyword"},"const"),s(" video "),n("span",{class:"token operator"},"="),s(" document"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"createElement"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"video"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + video`),n("span",{class:"token punctuation"},"."),s("style"),n("span",{class:"token punctuation"},"."),s("cssText "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token template-string"},[n("span",{class:"token template-punctuation string"},"`"),n("span",{class:"token string"},` + position: fixed; + min-width: 100%; + min-height: 100%; + z-index: -1; + `),n("span",{class:"token template-punctuation string"},"`")]),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("domElement"),n("span",{class:"token punctuation"},"."),s("shadowRoot"),n("span",{class:"token operator"},"!"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"appendChild"),n("span",{class:"token punctuation"},"("),s("video"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"// get webcam input"),s(` + `),n("span",{class:"token keyword"},"const"),s(" input "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"await"),s(" navigator"),n("span",{class:"token punctuation"},"."),s("mediaDevices"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"getUserMedia"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},"{"),s(" video"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token boolean"},"true"),s(),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token operator"},"!"),s("input"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"return"),n("span",{class:"token punctuation"},";"),s(` + video`),n("span",{class:"token punctuation"},"."),s("srcObject "),n("span",{class:"token operator"},"="),s(" input"),n("span",{class:"token punctuation"},";"),s(` + video`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"play"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"// make sure the camera background is transparent"),s(` + `),n("span",{class:"token keyword"},"const"),s(" camera "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("mainCameraComponent"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("camera"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + camera`),n("span",{class:"token punctuation"},"."),s("clearFlags "),n("span",{class:"token operator"},"="),s(" ClearFlags"),n("span",{class:"token punctuation"},"."),s("SolidColor"),n("span",{class:"token punctuation"},";"),s(` + camera`),n("span",{class:"token punctuation"},"."),s("backgroundColor "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"RGBAColor"),n("span",{class:"token punctuation"},"("),n("span",{class:"token number"},"125"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"125"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"125"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),m=n("p",null,[s("This is an example from our "),n("em",null,"Everywhere Actions"),s(". The following script hides an object on start on Android and on iOS AR")],-1),b=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"HideOnStart"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(),n("span",{class:"token keyword"},"implements"),s(),n("span",{class:"token class-name"},"UsdzBehaviour"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token function"},"start"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("gameObject"),n("span",{class:"token punctuation"},"."),s("visible "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"createBehaviours"),n("span",{class:"token punctuation"},"("),s("ext"),n("span",{class:"token punctuation"},","),s(" model"),n("span",{class:"token punctuation"},","),s(" _context"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("model"),n("span",{class:"token punctuation"},"."),s("uuid "),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("gameObject"),n("span",{class:"token punctuation"},"."),s("uuid"),n("span",{class:"token punctuation"},")"),s(` + ext`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"addBehavior"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"BehaviorModel"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"HideOnStart_"'),s(),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("gameObject"),n("span",{class:"token punctuation"},"."),s("name"),n("span",{class:"token punctuation"},","),s(` + TriggerBuilder`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"sceneStartTrigger"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},","),s(` + ActionBuilder`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"fadeAction"),n("span",{class:"token punctuation"},"("),s("model"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"beforeCreateDocument"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("gameObject"),n("span",{class:"token punctuation"},"."),s("visible "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"afterCreateDocument"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("gameObject"),n("span",{class:"token punctuation"},"."),s("visible "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),v=n("p",null,"Example for adding custom USDZ behaviours for iOS AR",-1),h=n("p",null,"This is an USDZ / iOS AR only example",-1),y=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"EmphasizeOnClick"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(),n("span",{class:"token keyword"},"implements"),s(),n("span",{class:"token class-name"},"UsdzBehaviour"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + target`),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s(" Object3D"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + duration`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token builtin"},"number"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0.5"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + motionType`),n("span",{class:"token operator"},":"),s(" MotionType "),n("span",{class:"token operator"},"="),s(" MotionType"),n("span",{class:"token punctuation"},"."),s("bounce"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token function"},"beforeCreateDocument"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"createBehaviours"),n("span",{class:"token punctuation"},"("),s("ext"),n("span",{class:"token punctuation"},","),s(" model"),n("span",{class:"token punctuation"},","),s(" _context"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token operator"},"!"),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("target"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"return"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("model"),n("span",{class:"token punctuation"},"."),s("uuid "),n("span",{class:"token operator"},"==="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("gameObject"),n("span",{class:"token punctuation"},"."),s("uuid"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" emphasize "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"BehaviorModel"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"emphasize "'),s(),n("span",{class:"token operator"},"+"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("name"),n("span",{class:"token punctuation"},","),s(` + TriggerBuilder`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"tapTrigger"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("gameObject"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},","),s(` + ActionBuilder`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"emphasize"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("target"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("duration"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("motionType"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"undefined"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token string"},'"basic"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},","),s(` + `),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + ext`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"addBehavior"),n("span",{class:"token punctuation"},"("),s("emphasize"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"afterCreateDocument"),n("span",{class:"token punctuation"},"("),s("_ext"),n("span",{class:"token punctuation"},","),s(" _context"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),w=n("p",null,"Use the mouse wheel or touch delta to update a timeline's time.",-1),g=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Behaviour"),n("span",{class:"token punctuation"},","),s(" PlayableDirector"),n("span",{class:"token punctuation"},","),s(" serializeable "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Mathf "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token comment"},"// Example of setting a timeline's time "),s(` +`),n("span",{class:"token comment"},"// without relying on any HTML elements."),s(` +`),n("span",{class:"token comment"},"// Here we directly use the mousewheel scroll and the touch delta"),s(` + +`),n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"ScrollTimeline_2"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializeable")]),n("span",{class:"token punctuation"},"("),s("PlayableDirector"),n("span",{class:"token punctuation"},")"),s(` + timeline`),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s(" PlayableDirector"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializeable")]),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + scrollSpeed`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token builtin"},"number"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0.5"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializeable")]),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + lerpSpeed`),n("span",{class:"token operator"},":"),s(),n("span",{class:"token builtin"},"number"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"2.5"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"private"),s(" targetTime"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token builtin"},"number"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token function"},"start"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("timeline"),n("span",{class:"token operator"},"?."),n("span",{class:"token function"},"pause"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"// Grab the mousewheel event"),s(` + window`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"addEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"wheel"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token punctuation"},"("),s("evt"),n("span",{class:"token operator"},":"),s(" WheelEvent"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"updateTime"),n("span",{class:"token punctuation"},"("),s("evt"),n("span",{class:"token punctuation"},"."),s("deltaY"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"// Touch events are a bit more complicated"),s(` + `),n("span",{class:"token comment"},"// We need to keep track of the last touch position"),s(` + `),n("span",{class:"token comment"},"// and calculate the delta between the current and the last position"),s(` + `),n("span",{class:"token keyword"},"let"),s(" lastTouchPosition "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token operator"},"-"),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + window`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"addEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"touchmove"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token punctuation"},"("),s("evt"),n("span",{class:"token operator"},":"),s(" TouchEvent"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" delta "),n("span",{class:"token operator"},"="),s(" evt"),n("span",{class:"token punctuation"},"."),s("touches"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"."),s("clientY "),n("span",{class:"token operator"},"-"),s(" lastTouchPosition"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token comment"},"// We only want to apply the delta if it's not TOO big"),s(` + `),n("span",{class:"token comment"},"// e.g. when the user is scrolling the page"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),s("delta "),n("span",{class:"token operator"},"<"),s(),n("span",{class:"token number"},"10"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"updateTime"),n("span",{class:"token punctuation"},"("),n("span",{class:"token operator"},"-"),s("delta"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token comment"},"// Update the last touch position"),s(` + lastTouchPosition `),n("span",{class:"token operator"},"="),s(" evt"),n("span",{class:"token punctuation"},"."),s("touches"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"."),s("clientY"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token function"},"updateTime"),n("span",{class:"token punctuation"},"("),s("delta"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token operator"},"!"),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("timeline"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"return"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("targetTime "),n("span",{class:"token operator"},"+="),s(" delta "),n("span",{class:"token operator"},"*"),s(),n("span",{class:"token number"},"0.01"),s(),n("span",{class:"token operator"},"*"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("scrollSpeed"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("targetTime "),n("span",{class:"token operator"},"="),s(" Mathf"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"clamp"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("targetTime"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("timeline"),n("span",{class:"token punctuation"},"."),s("duration"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"onBeforeRender"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token keyword"},"void"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token operator"},"!"),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("timeline"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"return"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("timeline"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"pause"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("timeline"),n("span",{class:"token punctuation"},"."),s("time "),n("span",{class:"token operator"},"="),s(" Mathf"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"lerp"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("timeline"),n("span",{class:"token punctuation"},"."),s("time"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("targetTime"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("lerpSpeed "),n("span",{class:"token operator"},"*"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("time"),n("span",{class:"token punctuation"},"."),s("deltaTime"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("timeline"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"evaluate"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` + +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),f=n("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),_=n("p",null,"Please include at least one code snippet, for example like this:",-1),x=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Behaviour"),n("span",{class:"token punctuation"},","),s(" serializable "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),s(` +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Object3D "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"three"'),s(` + +`),n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"MyComponent"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),s("Object3D"),n("span",{class:"token punctuation"},")"),s(` + myObjectReference`),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s(" Object3D"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token function"},"start"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token builtin"},"console"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"log"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"Hello world"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"update"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"// called every frame"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1);function T(B,C){const c=o("ExternalLinkIcon"),e=o("contribution-preview"),l=o("contributions-author");return i(),u("div",null,[a(l,{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:t(()=>[a(e,{title:"Camera Video Background",pageUrl:"/docs/community/contributions/marwie/camera-video-background"},{default:t(()=>[n("p",null,[s("Put it anywhere in your scene to render a camera video behind your 3D scene "),n("a",k,[s("Live demo"),a(c)])]),d]),_:1}),a(e,{title:"USDZ: Hide Object on Start",pageUrl:"/docs/community/contributions/marwie/usdz-hide-object-on-start"},{default:t(()=>[m,b]),_:1}),a(e,{title:"Everywhere Action: Emphasize on Click",pageUrl:"/docs/community/contributions/marwie/everywhere-action-emphasize-on-click"},{default:t(()=>[v,h,y]),_:1}),a(e,{title:"Control a Timeline by scroll",pageUrl:"/docs/community/contributions/marwie/control-a-timeline-by-scroll"},{default:t(()=>[w,g]),_:1}),a(e,{title:"Code Contribution Example",pageUrl:"/docs/community/contributions/marwie/code-contribution-example"},{default:t(()=>[f,_,x]),_:1})]),_:1})])}const z=p(r,[["render",T],["__file","index.html.vue"]]);export{z as default}; diff --git a/assets/index.html-d8d34b98.js b/assets/index.html-d8d34b98.js new file mode 100644 index 000000000..18fb71569 --- /dev/null +++ b/assets/index.html-d8d34b98.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-acde0924","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{e as data}; diff --git a/assets/index.html-dccb5a4a.js b/assets/index.html-dccb5a4a.js new file mode 100644 index 000000000..07c3b1a0a --- /dev/null +++ b/assets/index.html-dccb5a4a.js @@ -0,0 +1,474 @@ +import{_ as l,M as o,p,q as i,N as a,V as t,R as n,t as s}from"./framework-f6820c83.js";const u={},k=n("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),r=n("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),d=n("p",null,"You can place this script anywhere.",-1),b=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[s(` +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Behaviour"),n("span",{class:"token punctuation"},","),s(" WebXR"),n("span",{class:"token punctuation"},","),s(" GameObject"),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Vector3"),n("span",{class:"token punctuation"},","),s("Quaternion"),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"three"'),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Mathf "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"VerticalMove"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token keyword"},"private"),s(" webXR"),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s(" WebXR"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"private"),s(" joystickY"),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),n("span",{class:"token builtin"},"number"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"private"),s(" worldRot"),n("span",{class:"token operator"},":"),s(" Quaternion "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"Quaternion"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token function"},"start"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token keyword"},"void"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token keyword"},"let"),s(" _webxr"),n("span",{class:"token operator"},"="),s("GameObject"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"findObjectOfType"),n("span",{class:"token punctuation"},"("),s("WebXR"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),s("_webxr"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token operator"},"="),s("_webxr"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token builtin"},"console"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"log"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"webxr found"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + + `),n("span",{class:"token function"},"update"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("isInVR"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//get y value from right joystick"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"verticalMove"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"verticalMove"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),n("span",{class:"token keyword"},"void"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token operator"},"?."),s("RightController"),n("span",{class:"token operator"},"?."),s("input"),n("span",{class:"token operator"},"?."),s("gamepad"),n("span",{class:"token operator"},"?."),s("axes"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("joystickY"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token punctuation"},"."),s("RightController"),n("span",{class:"token punctuation"},"."),s("input"),n("span",{class:"token punctuation"},"."),s("gamepad"),n("span",{class:"token punctuation"},"."),s("axes"),n("span",{class:"token punctuation"},"["),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"const"),s(" speedFactor "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" powFactor "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" speed "),n("span",{class:"token operator"},"="),s(" Mathf"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"clamp01"),n("span",{class:"token punctuation"},"("),n("span",{class:"token number"},"2"),s(),n("span",{class:"token operator"},"*"),s(),n("span",{class:"token number"},"2"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"const"),s(" verticalDir "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("joystickY "),n("span",{class:"token operator"},"<"),s(),n("span",{class:"token number"},"0"),s(),n("span",{class:"token operator"},"?"),s(),n("span",{class:"token number"},"1"),s(),n("span",{class:"token operator"},":"),s(),n("span",{class:"token operator"},"-"),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"let"),s(" vertical "),n("span",{class:"token operator"},"="),s(" Math"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"pow"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("joystickY"),n("span",{class:"token punctuation"},","),s(" powFactor"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + vertical `),n("span",{class:"token operator"},"*="),s(" verticalDir"),n("span",{class:"token punctuation"},";"),s(` + vertical `),n("span",{class:"token operator"},"*="),s(" speed"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token punctuation"},"."),s("Rig"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"getWorldQuaternion"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("worldRot"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"let"),s(" movementVector"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"Vector3"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + movementVector`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"set"),n("span",{class:"token punctuation"},"("),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(" vertical"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + movementVector`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"applyQuaternion"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token punctuation"},"."),s("TransformOrientation"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + movementVector`),n("span",{class:"token punctuation"},"."),s("x "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(` + movementVector`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"applyQuaternion"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("worldRot"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + movementVector`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"multiplyScalar"),n("span",{class:"token punctuation"},"("),s("speedFactor "),n("span",{class:"token operator"},"*"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("time"),n("span",{class:"token punctuation"},"."),s("deltaTime"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token punctuation"},"."),s("Rig"),n("span",{class:"token punctuation"},"."),s("position"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"add"),n("span",{class:"token punctuation"},"("),s("movementVector"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` + + +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),m=n("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),v=n("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),w=n("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),h=n("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),y=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Behaviour"),n("span",{class:"token punctuation"},","),s(" WebXR"),n("span",{class:"token punctuation"},","),s("serializeable"),n("span",{class:"token punctuation"},","),s(" WebXREvent"),n("span",{class:"token punctuation"},","),s("WebXRAvatar"),n("span",{class:"token punctuation"},","),s("GameObject"),n("span",{class:"token punctuation"},","),s(" AvatarMarker"),n("span",{class:"token punctuation"},","),s("Text"),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Object3D"),n("span",{class:"token punctuation"},","),s(" Vector3"),n("span",{class:"token punctuation"},","),s("Quaternion"),n("span",{class:"token punctuation"},","),s("PerspectiveCamera"),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"three"'),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"SqueezeScale"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(),n("span",{class:"token punctuation"},"{"),s(` + + + `),n("span",{class:"token keyword"},"private"),s(" webXR"),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s(" WebXR"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"private"),s(" selectedObj"),n("span",{class:"token operator"},":"),s(" Object3D"),n("span",{class:"token operator"},"|"),s(),n("span",{class:"token keyword"},"null"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"null"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializeable")]),n("span",{class:"token punctuation"},"("),s("Object3D"),n("span",{class:"token punctuation"},")"),s(` + scaleTextObject`),n("span",{class:"token operator"},":"),s(" Object3D"),n("span",{class:"token operator"},"|"),s(),n("span",{class:"token keyword"},"null"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"null"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializeable")]),n("span",{class:"token punctuation"},"("),s("Text"),n("span",{class:"token punctuation"},")"),s(` + scaleText`),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s(" Text"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"public"),s(" allowWorldScaling"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token builtin"},"boolean"),s(),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"private"),s(" leftSqueeze"),n("span",{class:"token operator"},":"),n("span",{class:"token builtin"},"boolean"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"private"),s(" rightSqueeze"),n("span",{class:"token operator"},":"),n("span",{class:"token builtin"},"boolean"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"private"),s(" bothSqueezeStarted"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"private"),s(" rigScaleUpdated"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"private"),s(" initialDistance"),n("span",{class:"token operator"},":"),n("span",{class:"token builtin"},"number"),n("span",{class:"token operator"},"="),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"private"),s(" initialScale"),n("span",{class:"token operator"},":"),n("span",{class:"token builtin"},"number"),n("span",{class:"token operator"},"="),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"private"),s(" newScale"),n("span",{class:"token operator"},":"),n("span",{class:"token builtin"},"number"),s(),n("span",{class:"token operator"},"|"),s(),n("span",{class:"token keyword"},"null"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"null"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"private"),s(" leftHand"),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s("Object3D"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"private"),s(" rightHand"),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s("Object3D"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"private"),s(" head"),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s("Object3D"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token function"},"start"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token keyword"},"void"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token keyword"},"let"),s(" _webxr"),n("span",{class:"token operator"},"="),s("GameObject"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"findObjectOfType"),n("span",{class:"token punctuation"},"("),s("WebXR"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),s("_webxr"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token operator"},"="),s("_webxr"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token builtin"},"console"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"log"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"webxr found"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token comment"},"//Wait for XR Session"),s(` + WebXR`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"addEventListener"),n("span",{class:"token punctuation"},"("),s("WebXREvent"),n("span",{class:"token punctuation"},"."),s("XRStarted"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//listen to squeeze events"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("xrSession"),n("span",{class:"token operator"},"?."),n("span",{class:"token function"},"addEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"squeezestart"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token punctuation"},"("),s("event"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"onSqueezeEvent"),n("span",{class:"token punctuation"},"("),s("event"),n("span",{class:"token punctuation"},","),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("xrSession"),n("span",{class:"token operator"},"?."),n("span",{class:"token function"},"addEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"squeezeend"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token punctuation"},"("),s("event"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"onSqueezeEvent"),n("span",{class:"token punctuation"},"("),s("event"),n("span",{class:"token punctuation"},","),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + + `),n("span",{class:"token function"},"onSqueezeEvent"),n("span",{class:"token punctuation"},"("),s("event"),n("span",{class:"token operator"},":"),s(" XRInputSourceEvent"),n("span",{class:"token punctuation"},","),s(" status"),n("span",{class:"token operator"},":"),n("span",{class:"token builtin"},"boolean"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),s("event"),n("span",{class:"token punctuation"},"."),s("inputSource"),n("span",{class:"token punctuation"},"."),s("handedness"),n("span",{class:"token operator"},"==="),n("span",{class:"token string"},'"right"'),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("rightSqueeze"),n("span",{class:"token operator"},"="),s("status"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),s("event"),n("span",{class:"token punctuation"},"."),s("inputSource"),n("span",{class:"token punctuation"},"."),s("handedness"),n("span",{class:"token operator"},"==="),n("span",{class:"token string"},'"left"'),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("leftSqueeze"),n("span",{class:"token operator"},"="),s("status"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"update"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("isInVR"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//cache object selected if any"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"objectGrab"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"//if both grips are squeezed "),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("leftSqueeze "),n("span",{class:"token operator"},"&&"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("rightSqueeze"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//if object is selected either in the left or right controller (only one)"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("selectedObj"),n("span",{class:"token operator"},"!="),n("span",{class:"token keyword"},"null"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//after initial distance value has been set"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("bothSqueezeStarted"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//get current distance between controllers"),s(` + `),n("span",{class:"token keyword"},"const"),s(" scaleValue"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"calculateDistance"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},'//get distance change since beginning of squeeze to get a "pinch in/out" effect'),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("initialScale"),n("span",{class:"token operator"},"+"),s("scaleValue"),n("span",{class:"token operator"},"-"),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("initialDistance"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"//avoid 0 and negative scales"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token operator"},"<"),n("span",{class:"token number"},"0.001"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"{"),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token operator"},"="),n("span",{class:"token number"},"0.001"),n("span",{class:"token punctuation"},";"),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token comment"},"// scale object according to new distance since initial distance"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("selectedObj"),n("span",{class:"token punctuation"},"."),s("scale"),n("span",{class:"token punctuation"},"."),s("x"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("selectedObj"),n("span",{class:"token punctuation"},"."),s("scale"),n("span",{class:"token punctuation"},"."),s("y"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("selectedObj"),n("span",{class:"token punctuation"},"."),s("scale"),n("span",{class:"token punctuation"},"."),s("z"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"showVisual"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token punctuation"},","),n("span",{class:"token string"},'"Object :"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"else"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//get initial distance value (only once at a new squeeze both hands event)"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("bothSqueezeStarted"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("initialDistance"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"calculateDistance"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"//cache object's initial scale"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("initialScale"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("selectedObj"),n("span",{class:"token punctuation"},"."),s("scale"),n("span",{class:"token punctuation"},"."),s("x"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"else"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//scale world ?"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token operator"},"?."),s("Rig "),n("span",{class:"token operator"},"&&"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("allowWorldScaling"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//after initial distance value has been set"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("bothSqueezeStarted"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//get current distance between controllers"),s(` + `),n("span",{class:"token keyword"},"const"),s(" scaleValue"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"calculateDistance"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},'//get distance change since beginning of squeeze to get a "pinch in/out" effect'),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("initialScale"),n("span",{class:"token operator"},"+"),s("scaleValue"),n("span",{class:"token operator"},"-"),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("initialDistance"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"//avoid 0 and negative scales"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token operator"},"<"),n("span",{class:"token number"},"0.001"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"{"),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token operator"},"="),n("span",{class:"token number"},"0.001"),n("span",{class:"token punctuation"},";"),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"showVisual"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token string"},'"World :"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("rigScaleUpdated"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"else"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//get initial distance value (only once at a new squeeze both hands event)"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("bothSqueezeStarted"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("initialDistance"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"calculateDistance"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"//cache object's initial scale"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("initialScale"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token punctuation"},"."),s("Rig"),n("span",{class:"token punctuation"},"."),s("scale"),n("span",{class:"token punctuation"},"."),s("x"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"else"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//reset values"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("bothSqueezeStarted"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"//if world has been scaled, scale rig accordingly at the end of squeezing and once only"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token operator"},"?."),s("Rig "),n("span",{class:"token operator"},"&&"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("rigScaleUpdated "),n("span",{class:"token operator"},"&&"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//change rig scale"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token punctuation"},"."),s("Rig"),n("span",{class:"token punctuation"},"."),s("scale"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"set"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token punctuation"},"."),s("Rig"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"updateMatrixWorld"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"const"),s(" cam "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("mainCamera "),n("span",{class:"token keyword"},"as"),s(" PerspectiveCamera"),n("span",{class:"token punctuation"},";"),s(` + cam`),n("span",{class:"token punctuation"},"."),s("near"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token operator"},">"),n("span",{class:"token number"},"2"),n("span",{class:"token operator"},"?"),n("span",{class:"token number"},"0.0001"),n("span",{class:"token operator"},":"),n("span",{class:"token number"},"0.2"),n("span",{class:"token punctuation"},";"),s(` + cam`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"updateProjectionMatrix"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"//reset"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("rigScaleUpdated"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("scaleTextObject"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("scaleTextObject"),n("span",{class:"token punctuation"},"."),s("visible"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("newScale"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"null"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token function"},"calculateDistance"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),n("span",{class:"token builtin"},"number"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"let"),s(" distance"),n("span",{class:"token operator"},"="),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("leftHand "),n("span",{class:"token operator"},"&&"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("rightHand"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token keyword"},"const"),s(" left"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("leftHand"),n("span",{class:"token punctuation"},"."),s("position"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" right"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("rightHand"),n("span",{class:"token punctuation"},"."),s("position"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"// Calculate the difference between the positions"),s(` + `),n("span",{class:"token keyword"},"const"),s(" dx "),n("span",{class:"token operator"},"="),s(" left"),n("span",{class:"token punctuation"},"."),s("x "),n("span",{class:"token operator"},"-"),s(" right"),n("span",{class:"token punctuation"},"."),s("x"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" dy "),n("span",{class:"token operator"},"="),s(" left"),n("span",{class:"token punctuation"},"."),s("y "),n("span",{class:"token operator"},"-"),s(" right"),n("span",{class:"token punctuation"},"."),s("y"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" dz "),n("span",{class:"token operator"},"="),s(" left"),n("span",{class:"token punctuation"},"."),s("z "),n("span",{class:"token operator"},"-"),s(" right"),n("span",{class:"token punctuation"},"."),s("z"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"// Calculate the distance using the Euclidean distance formula"),s(` + distance `),n("span",{class:"token operator"},"="),s(" Math"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"sqrt"),n("span",{class:"token punctuation"},"("),s("dx "),n("span",{class:"token operator"},"*"),s(" dx "),n("span",{class:"token operator"},"+"),s(" dy "),n("span",{class:"token operator"},"*"),s(" dy "),n("span",{class:"token operator"},"+"),s(" dz "),n("span",{class:"token operator"},"*"),s(" dz"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"else"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//set positions of controllers from your avatar (only once)"),s(` + `),n("span",{class:"token keyword"},"let"),s(" allAvatars "),n("span",{class:"token operator"},"="),s(" AvatarMarker"),n("span",{class:"token punctuation"},"."),s("instances"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),s("allAvatars"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token operator"},">"),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"for"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i"),n("span",{class:"token operator"},"="),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s("i"),n("span",{class:"token operator"},"<"),s("allAvatars"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s("i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),s("allAvatars"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"isLocalAvatar"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" av"),n("span",{class:"token operator"},"="),s("allAvatars"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},"."),s("avatar "),n("span",{class:"token keyword"},"as"),s(" WebXRAvatar"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),s("av"),n("span",{class:"token operator"},"!="),n("span",{class:"token keyword"},"null"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("leftHand"),n("span",{class:"token operator"},"="),s("av"),n("span",{class:"token punctuation"},"."),s("handLeft "),n("span",{class:"token keyword"},"as"),s(" Object3D"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("rightHand"),n("span",{class:"token operator"},"="),s("av"),n("span",{class:"token punctuation"},"."),s("handRight "),n("span",{class:"token keyword"},"as"),s(" Object3D"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("head "),n("span",{class:"token operator"},"="),s(" av"),n("span",{class:"token punctuation"},"."),s("head "),n("span",{class:"token keyword"},"as"),s(" Object3D"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"return"),s(" distance"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function"},"showVisual"),n("span",{class:"token punctuation"},"("),s("scale"),n("span",{class:"token operator"},":"),n("span",{class:"token builtin"},"number"),n("span",{class:"token punctuation"},","),s(" mesg"),n("span",{class:"token operator"},":"),n("span",{class:"token builtin"},"string"),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),n("span",{class:"token keyword"},"void"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("scaleTextObject "),n("span",{class:"token operator"},"&&"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("head "),n("span",{class:"token operator"},"&&"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("scaleText"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("scaleTextObject"),n("span",{class:"token punctuation"},"."),s("visible"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"const"),s(" offset "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"Vector3"),n("span",{class:"token punctuation"},"("),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),n("span",{class:"token number"},"7"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + offset`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"applyQuaternion"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("head"),n("span",{class:"token punctuation"},"."),s("quaternion"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("scaleTextObject"),n("span",{class:"token punctuation"},"."),s("position"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"copy"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("head"),n("span",{class:"token punctuation"},"."),s("position"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"add"),n("span",{class:"token punctuation"},"("),s("offset"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"const"),s(" roundedNum"),n("span",{class:"token operator"},"="),s(),n("span",{class:"token operator"},"+"),s("scale"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"toFixed"),n("span",{class:"token punctuation"},"("),n("span",{class:"token number"},"3"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("scaleText"),n("span",{class:"token punctuation"},"."),s("text"),n("span",{class:"token operator"},"="),s("mesg"),n("span",{class:"token operator"},"+"),n("span",{class:"token string"},'" "'),n("span",{class:"token operator"},"+"),s("roundedNum"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token punctuation"},"}"),s(` + + + `),n("span",{class:"token function"},"objectGrab"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),n("span",{class:"token keyword"},"void"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token operator"},"?."),s("RightController"),n("span",{class:"token operator"},"?."),s("grabbed"),n("span",{class:"token operator"},"?."),s("selected"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("selectedObj "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token punctuation"},"."),s("RightController"),n("span",{class:"token punctuation"},"."),s("grabbed"),n("span",{class:"token punctuation"},"."),s("selected"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"else"),s(),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token operator"},"?."),s("LeftController"),n("span",{class:"token operator"},"?."),s("grabbed"),n("span",{class:"token operator"},"?."),s("selected"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("selectedObj "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("webXR"),n("span",{class:"token punctuation"},"."),s("LeftController"),n("span",{class:"token punctuation"},"."),s("grabbed"),n("span",{class:"token punctuation"},"."),s("selected"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token keyword"},"else"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("selectedObj"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"null"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),f=n("p",null,"In a multiuser session, typically objects are instantiated using instantiateSynced as such:",-1),g=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Behaviour"),n("span",{class:"token punctuation"},","),s("GameObject"),n("span",{class:"token punctuation"},","),s("serializable"),n("span",{class:"token punctuation"},","),s("InstantiateOptions"),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Vector3"),n("span",{class:"token punctuation"},","),s(" Object3D"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"three"'),n("span",{class:"token punctuation"},";"),s(` + +`),n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"InstantiateObjectForAll"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(` +`),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),s("Object3D"),n("span",{class:"token punctuation"},")"),s(` + myPrefab`),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s(" GameObject"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token function"},"makeObject"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),n("span",{class:"token keyword"},"void"),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" options "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"InstantiateOptions"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + options`),n("span",{class:"token punctuation"},"."),s("context "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},";"),s(` + options`),n("span",{class:"token punctuation"},"."),s("position "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"Vector3"),n("span",{class:"token punctuation"},"("),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + GameObject`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"instantiateSynced"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("myPrefab"),n("span",{class:"token punctuation"},","),s(" options"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"as"),s(" GameObject"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),S=n("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),x=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("connection"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"send"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"})])],-1),j=n("p",null,"All users using :",-1),_=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("connection"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"beginListen"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"})])],-1),R=n("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),O=n("p",null,"Here is a script illustrating the use of the send method and the beginListen counterpart:",-1),z=n("div",{class:"language-typescript line-numbers-mode","data-ext":"ts"},[n("pre",{class:"language-typescript"},[n("code",null,[s(` +`),n("span",{class:"token comment"},"//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(` + +`),n("span",{class:"token comment"},"//This script requires a prefab (e.g. a 1x1x1 Cube)"),s(` +`),n("span",{class:"token comment"},"//This script will generate and build randomly positioned cubes (random walk) as a child of the object it is attached to. "),s(` +`),n("span",{class:"token comment"},"//The generateSeed() method is in this script called via a button. The button is deactivated once the seed has been transmitted."),s(` +`),n("span",{class:"token comment"},"//Any users joining the same room will receive the seed and build the exact same scene"),s(` + + +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Behaviour"),n("span",{class:"token punctuation"},","),s("GameObject"),n("span",{class:"token punctuation"},","),s("serializable"),n("span",{class:"token punctuation"},","),s("InstantiateOptions"),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"@needle-tools/engine"'),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token keyword"},"import"),s(),n("span",{class:"token punctuation"},"{"),s(" Vector3"),n("span",{class:"token punctuation"},","),s(" Object3D "),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"from"),s(),n("span",{class:"token string"},'"three"'),n("span",{class:"token punctuation"},";"),s(` + + +`),n("span",{class:"token keyword"},"export"),s(),n("span",{class:"token keyword"},"class"),s(),n("span",{class:"token class-name"},"NetworkedSeed"),s(),n("span",{class:"token keyword"},"extends"),s(),n("span",{class:"token class-name"},"Behaviour"),s(` +`),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),s("Object3D"),n("span",{class:"token punctuation"},")"),s(` + prefab`),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s(" GameObject"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token decorator"},[n("span",{class:"token at operator"},"@"),n("span",{class:"token function"},"serializable")]),n("span",{class:"token punctuation"},"("),s("Object3D"),n("span",{class:"token punctuation"},")"),s(` + generateButton`),n("span",{class:"token operator"},"?"),n("span",{class:"token operator"},":"),s(" Object3D"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"public"),s(" seedSize"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token builtin"},"number"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token number"},"30"),n("span",{class:"token punctuation"},";"),s(` + + seed`),n("span",{class:"token operator"},":"),s(" Vector3"),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token function"},"onEnable"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token keyword"},"void"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("connection"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"beginListen"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"mySeed"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("onDataReceived"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("generateButton"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("generateButton"),n("span",{class:"token punctuation"},"."),s("visible"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"true"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token function"},"onDisable"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token keyword"},"void"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("connection"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"stopListen"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"mySeed"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("onDataReceived"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token function-variable function"},"onDataReceived"),s(),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"("),s("data"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token builtin"},"any"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token builtin"},"console"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"log"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"Received data:"'),n("span",{class:"token punctuation"},","),s(" data"),n("span",{class:"token punctuation"},"."),s("mySeed"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token operator"},"==="),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token comment"},"//prevent other generations of the seed"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("generateButton"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("generateButton"),n("span",{class:"token punctuation"},"."),s("visible"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token operator"},"="),s("data"),n("span",{class:"token punctuation"},"."),s("mySeed"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token comment"},"//build scene"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"buildScene"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},";"),s(` + + + `),n("span",{class:"token comment"},"//generate and send seed to all from the button generateButton"),s(` + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token function"},"generateSeed"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),n("span",{class:"token keyword"},"void"),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token operator"},"=="),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token comment"},"//no seed found => generate one"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" uniquePositions "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},[s("Set"),n("span",{class:"token operator"},"<"),n("span",{class:"token builtin"},"string"),n("span",{class:"token operator"},">")]),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"//start at origin"),s(` + `),n("span",{class:"token keyword"},"const"),s(" startPosition "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"Vector3"),n("span",{class:"token punctuation"},"("),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"push"),n("span",{class:"token punctuation"},"("),s("startPosition"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"clone"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + uniquePositions`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"add"),n("span",{class:"token punctuation"},"("),s("startPosition"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"toArray"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"toString"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"//go for a random walk of length : seedSize"),s(` + `),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token punctuation"},"."),s("length "),n("span",{class:"token operator"},"<"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seedSize"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" lastPosition "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token punctuation"},"["),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token punctuation"},"."),s("length "),n("span",{class:"token operator"},"-"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"let"),s(" newPosition"),n("span",{class:"token operator"},":"),s(" Vector3"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"//walk and add position, making sure they are unique"),s(` + `),n("span",{class:"token keyword"},"do"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" direction "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"getRandomDirection"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + newPosition `),n("span",{class:"token operator"},"="),s(" lastPosition"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"clone"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"add"),n("span",{class:"token punctuation"},"("),s("direction"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(),n("span",{class:"token keyword"},"while"),s(),n("span",{class:"token punctuation"},"("),s("uniquePositions"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"has"),n("span",{class:"token punctuation"},"("),s("newPosition"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"toArray"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"toString"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"push"),n("span",{class:"token punctuation"},"("),s("newPosition"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"clone"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + uniquePositions`),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"add"),n("span",{class:"token punctuation"},"("),s("newPosition"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"toArray"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"toString"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token comment"},"//send the seed to all on the server"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"sendSeed"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token comment"},"//prevent other generations of the seed"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("generateButton"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("generateButton"),n("span",{class:"token punctuation"},"."),s("visible"),n("span",{class:"token operator"},"="),n("span",{class:"token boolean"},"false"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token comment"},"//build scene locally"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"buildScene"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token function"},"sendSeed"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),n("span",{class:"token keyword"},"void"),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token operator"},"!="),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},"."),s("connection"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"send"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"mySeed"'),n("span",{class:"token punctuation"},","),n("span",{class:"token punctuation"},"{"),s("guid"),n("span",{class:"token operator"},":"),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("guid"),n("span",{class:"token punctuation"},","),s(" mySeed"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token builtin"},"console"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"log"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"------ SEED SENT -------"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"public"),s(),n("span",{class:"token function"},"buildScene"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),n("span",{class:"token keyword"},"void"),n("span",{class:"token punctuation"},"{"),s(` + + `),n("span",{class:"token comment"},"//check if the seed is not empty"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token operator"},"=="),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token builtin"},"console"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"log"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"array was empty"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token comment"},"//check if the scene has already been built"),s(` + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("gameObject"),n("span",{class:"token punctuation"},"."),s("children"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token operator"},">"),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token builtin"},"console"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"log"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"Scene already present"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token comment"},"// Create cubes at each position of the random walk "),s(` + `),n("span",{class:"token keyword"},"for"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"let"),s(" i"),n("span",{class:"token operator"},"="),n("span",{class:"token number"},"0"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"<"),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token punctuation"},"."),s("length"),n("span",{class:"token punctuation"},";"),s(" i"),n("span",{class:"token operator"},"++"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" option "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"InstantiateOptions"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + option`),n("span",{class:"token punctuation"},"."),s("context "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("context"),n("span",{class:"token punctuation"},";"),s(` + option`),n("span",{class:"token punctuation"},"."),s("parent"),n("span",{class:"token operator"},"="),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("gameObject"),n("span",{class:"token punctuation"},";"),s(` + option`),n("span",{class:"token punctuation"},"."),s("position "),n("span",{class:"token operator"},"="),s(),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("seed"),n("span",{class:"token punctuation"},"["),s("i"),n("span",{class:"token punctuation"},"]"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token keyword"},"if"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("prefab"),n("span",{class:"token operator"},"!="),n("span",{class:"token keyword"},"null"),n("span",{class:"token punctuation"},")"),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" cube "),n("span",{class:"token operator"},"="),s(" GameObject"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"instantiate"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"this"),n("span",{class:"token punctuation"},"."),s("prefab"),n("span",{class:"token punctuation"},","),s(" option"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token keyword"},"as"),s(" GameObject"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token builtin"},"console"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"log"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"----------- Scene Built ---------"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"private"),s(),n("span",{class:"token function"},"getRandomDirection"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token operator"},":"),s(" Vector3 "),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"const"),s(" x "),n("span",{class:"token operator"},"="),s(" Math"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"random"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"<"),s(),n("span",{class:"token number"},"0.5"),s(),n("span",{class:"token operator"},"?"),s(),n("span",{class:"token operator"},"-"),n("span",{class:"token number"},"1"),s(),n("span",{class:"token operator"},":"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" y "),n("span",{class:"token operator"},"="),s(" Math"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"random"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"<"),s(),n("span",{class:"token number"},"0.5"),s(),n("span",{class:"token operator"},"?"),s(),n("span",{class:"token operator"},"-"),n("span",{class:"token number"},"1"),s(),n("span",{class:"token operator"},":"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"const"),s(" z "),n("span",{class:"token operator"},"="),s(" Math"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"random"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"<"),s(),n("span",{class:"token number"},"0.5"),s(),n("span",{class:"token operator"},"?"),s(),n("span",{class:"token operator"},"-"),n("span",{class:"token number"},"1"),s(),n("span",{class:"token operator"},":"),s(),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token keyword"},"return"),s(),n("span",{class:"token keyword"},"new"),s(),n("span",{class:"token class-name"},"Vector3"),n("span",{class:"token punctuation"},"("),s("x"),n("span",{class:"token punctuation"},","),s(" y"),n("span",{class:"token punctuation"},","),s(" z"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),q=n("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),V=n("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),D=n("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),T=n("p",null,"This gives a way to generate a scene and communicate the seed of that scene, for each user to build locally.",-1),X=n("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);function W(B,A){const e=o("contribution-preview"),c=o("contributions-author");return p(),i("div",null,[a(c,{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(()=>[a(e,{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(()=>[k,r,d,b]),_:1}),a(e,{title:"Squeeze to Scale (Object or World) in VR",pageUrl:"/docs/community/contributions/web3kev/squeeze-to-scale-object-or-world-in-vr"},{default:t(()=>[m,v,w,h,y]),_:1}),a(e,{title:"Network instantiation of multiple objects",pageUrl:"/docs/community/contributions/web3kev/network-instantiation-of-multiple-objects"},{default:t(()=>[f,g,S,x,j,_,R,O,z,q,V,D,T,X]),_:1})]),_:1})])}const M=l(u,[["render",W],["__file","index.html.vue"]]);export{M as default}; diff --git a/assets/index.html-dda688da.js b/assets/index.html-dda688da.js new file mode 100644 index 000000000..ca6146147 --- /dev/null +++ b/assets/index.html-dda688da.js @@ -0,0 +1 @@ +import{_ as u,M as s,p as h,q as m,N as t,V as n,t as o,Q as _,a1 as p,R as e}from"./framework-f6820c83.js";const g="/docs/imgs/banner.webp",f={},w=p('

    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),b=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),v=e("div",null,"This is the best thing I have seen after cinemachine in Unity โ€” Rinesh Thomas",-1),y=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),k=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),x=e("div",null,"Played with this a bit this morning ๐Ÿคฏ๐Ÿคฏ pretty magical โ€” Brit Gardner",-1),N=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),T=e("div",null,"This is amazing and if you are curious about WebXR with Unity this will help us get there โ€” Dilmer Valecillos",-1),W=e("div",null,"We just gotta say WOW ๐Ÿคฉ โ€” Unity for Digital Twins",-1),B=e("br",null,null,-1),C=e("br",null,null,-1),U=e("p",null,[e("a",{href:"https://www.npmjs.com/package/@needle-tools/engine"},[e("img",{src:"https://img.shields.io/npm/v/@needle-tools/engine?style=flat&colorA=000000&colorB=000000"})])],-1),V=e("p",null,[e("a",{href:"https://www.npmjs.com/package/@needle-tools/engine"},[e("img",{src:"https://img.shields.io/npm/dt/@needle-tools/engine.svg?style=flat&colorA=000000&colorB=000000"})])],-1),q=e("p",null,[e("a",{href:"https://discord.needle.tools"},[e("img",{src:"https://img.shields.io/discord/717429793926283276?style=flat&colorA=000000&colorB=000000&label=discord&logo=discord&logoColor=ffffff"})])],-1),D=e("p",null,null,-1);function R(S,j){const a=s("quoteslides"),i=s("action"),l=s("actiongroup"),r=s("copyright"),d=s("removeserviceworker"),c=s("ClientOnly");return h(),m("div",null,[w,t(a,null,{default:n(()=>[b,v,y,k,x,N,T,W]),_:1}),t(l,null,{default:n(()=>[t(i,{href:"getting-started"},{default:n(()=>[o(" Get started โญ ")]),_:1}),t(i,{href:"features-overview"},{default:n(()=>[o(" Features ๐ŸŽจ ")]),_:1}),t(i,{href:"https://engine.needle.tools/samples?utm_source=needle_docs&utm_content=actionbutton"},{default:n(()=>[o(" Samples ๐ŸŽ ")]),_:1}),t(i,{href:"https://forum.needle.tools?utm_source=needle_docs&utm_content=actionbutton"},{default:n(()=>[o(" Forum ๐Ÿ’ฌ ")]),_:1})]),_:1}),_(' '),B,C,t(l,null,{default:n(()=>[U,V,q]),_:1}),D,t(r),t(c,null,{default:n(()=>[t(d)]),_:1})])}const I=u(f,[["render",R],["__file","index.html.vue"]]);export{I as default}; diff --git a/assets/index.html-dda76fbd.js b/assets/index.html-dda76fbd.js new file mode 100644 index 000000000..b084c2247 --- /dev/null +++ b/assets/index.html-dda76fbd.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-ea622532","path":"/community/contributions/robyer1/ar-move-scale-rotate-controls-for-needle-on-mobile/","title":"","lang":"en-US","frontmatter":{"head":[["meta",{"name":"og:image","content":"https://engine.needle.tools/docs/.preview/robyer1: ar move scale rotate controls for needle on mobile.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{e as data}; diff --git a/assets/index.html-de7540da.js b/assets/index.html-de7540da.js new file mode 100644 index 000000000..4abee1f7b --- /dev/null +++ b/assets/index.html-de7540da.js @@ -0,0 +1,56 @@ +import{_ as a,M as t,p as e,q as p,N as o,R as n,a1 as c}from"./framework-f6820c83.js";const i={},l=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),u=c(`

    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);function r(k,d){const s=t("contribution-header");return e(),p("div",null,[l,o(s,{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"}),u])}const v=a(i,[["render",r],["__file","index.html.vue"]]);export{v as default}; diff --git a/assets/index.html-de8c7aa7.js b/assets/index.html-de8c7aa7.js new file mode 100644 index 000000000..152766d6f --- /dev/null +++ b/assets/index.html-de8c7aa7.js @@ -0,0 +1,250 @@ +import{_ as a,M as t,p as e,q as p,N as o,R as n,a1 as c}from"./framework-f6820c83.js";const l={},i=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),u=c(`

    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;
    +        }
    +    }
    +}
    +
    `,5);function k(r,d){const s=t("contribution-header");return e(),p("div",null,[i,o(s,{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"}),u])}const b=a(l,[["render",k],["__file","index.html.vue"]]);export{b as default}; diff --git a/assets/index.html-ee156bf2.js b/assets/index.html-ee156bf2.js new file mode 100644 index 000000000..947a53bcd --- /dev/null +++ b/assets/index.html-ee156bf2.js @@ -0,0 +1,62 @@ +import{_ as a,M as t,p,q as e,N as o,R as n,a1 as c}from"./framework-f6820c83.js";const i={},l=n("p",null,[n("a",{href:"/docs/community/contributions"},"Overview")],-1),u=c(`

    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.

    You can place this script anywhere.

    
    +import { Behaviour, WebXR, GameObject} from "@needle-tools/engine";
    +import { Vector3,Quaternion} from "three";
    +import { Mathf } from "@needle-tools/engine";
    +
    +export class VerticalMove extends Behaviour {
    +
    +    private webXR?: WebXR;
    +    private joystickY?:number;
    +    private worldRot: Quaternion = new Quaternion();
    +
    +    start(): void {
    +
    +        let _webxr=GameObject.findObjectOfType(WebXR);
    +        if(_webxr)
    +        {
    +            this.webXR=_webxr;
    +            console.log("webxr found");
    +        }
    +    }
    +
    +
    +    update()
    +    {
    +        if(this.context.isInVR)
    +        {
    +            //get y value from right joystick
    +            this.verticalMove();
    +        }
    +    }
    +
    +    verticalMove():void
    +    {
    +        if(this.webXR?.RightController?.input?.gamepad?.axes[3]) 
    +        {
    +            this.joystickY=this.webXR.RightController.input.gamepad.axes[3];
    +
    +            const speedFactor = 3;
    +            const powFactor = 2;
    +            const speed = Mathf.clamp01(2 * 2);
    +            
    +            const verticalDir = this.joystickY < 0 ? 1 : -1;
    +            let vertical = Math.pow(this.joystickY, powFactor);
    +            vertical *= verticalDir;
    +            vertical *= speed;
    +
    +            this.webXR.Rig.getWorldQuaternion(this.worldRot);
    +            
    +            let movementVector=new Vector3();
    +            movementVector.set(0, vertical, 0);
    +            movementVector.applyQuaternion(this.webXR.TransformOrientation);
    +            movementVector.x = 0;
    +            movementVector.applyQuaternion(this.worldRot);
    +            movementVector.multiplyScalar(speedFactor * this.context.time.deltaTime);
    +
    +            this.webXR.Rig.position.add(movementVector);
    +        }
    +    }
    +}
    +
    +
    +
    `,4);function r(k,d){const s=t("contribution-header");return p(),e("div",null,[l,o(s,{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"}),u])}const m=a(i,[["render",r],["__file","index.html.vue"]]);export{m as default}; diff --git a/assets/index.html-ee5a34cc.js b/assets/index.html-ee5a34cc.js new file mode 100644 index 000000000..ce4efbdb1 --- /dev/null +++ b/assets/index.html-ee5a34cc.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-c2d8b5ac","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/needle engine for blender.png"}],["meta",{"name":"og:description","content":"---\\nThank you for using Needle Engine for Blender.\\nWith this addon you can create highly interactive and optimized WebGL and WebXR experiences inside Blender that run using Needle Engine and three.js.\\nYou'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. You own your content!\\nAutomatically export HDRI environment lights directly from blender. Save to reload your local server\\n\\nYour feedback is invaluable when it comes to deciding which of those features should be prioritizes.\\nIf you have feedback for us please let us know in our forum!"}]],"description":"---\\nThank you for using Needle Engine for Blender.\\nWith this addon you can create highly interactive and optimized WebGL and WebXR experiences inside Blender that run using Needle Engine and three.js.\\nYou'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. You own your content!\\nAutomatically export HDRI environment lights directly from blender. Save to reload your local server\\n\\nYour feedback is invaluable when it comes to deciding which of those features should be prioritizes.\\nIf you have feedback for us please let us know in our forum!"},"headers":[{"level":2,"title":"Preface","slug":"preface","link":"#preface","children":[]},{"level":2,"title":"Download and Installation ๐Ÿ’ฟ","slug":"download-and-installation","link":"#download-and-installation","children":[{"level":3,"title":"Step 1 โ€ข Install Blender 3.6, 4.0, 4.1 or 4.2","slug":"step-1-install-blender-3.6-4.0-4.1-or-4.2","link":"#step-1-install-blender-3.6-4.0-4.1-or-4.2","children":[]},{"level":3,"title":"Step 3 โ€ข Download Needle Engine for Blender","slug":"step-3-download-needle-engine-for-blender","link":"#step-3-download-needle-engine-for-blender","children":[]}]},{"level":2,"title":"Getting Started ๐Ÿšฉ","slug":"getting-started","link":"#getting-started","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":"Debugging / Reporting a problem","slug":"debugging-reporting-a-problem","link":"#debugging-reporting-a-problem","children":[{"level":3,"title":"Integrated Bugreporter","slug":"integrated-bugreporter","link":"#integrated-bugreporter","children":[]}]}],"git":{"updatedTime":1724756390000},"filePathRelative":"blender/index.md"}`);export{e as data}; diff --git a/assets/index.html-f2afcbd3.js b/assets/index.html-f2afcbd3.js new file mode 100644 index 000000000..330088a8c --- /dev/null +++ b/assets/index.html-f2afcbd3.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5d69cca2","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{e as data}; diff --git a/assets/index.html-f975bb82.js b/assets/index.html-f975bb82.js new file mode 100644 index 000000000..db1f9e7ff --- /dev/null +++ b/assets/index.html-f975bb82.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0c347631","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{e as data}; diff --git a/assets/index.html-fd183cb5.js b/assets/index.html-fd183cb5.js new file mode 100644 index 000000000..5bfdbdfea --- /dev/null +++ b/assets/index.html-fd183cb5.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-ccdc4da0","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.\\nNeedle Engine is available as a download for Unity, for Blender, and for web projects without an editor integration."}]],"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.\\nNeedle Engine is available as a download for Unity, for Blender, and for web projects without an editor integration."},"headers":[{"level":2,"title":"Needle Engine for Unity","slug":"needle-engine-for-unity","link":"#needle-engine-for-unity","children":[]},{"level":2,"title":"Needle Engine for Blender","slug":"needle-engine-for-blender","link":"#needle-engine-for-blender","children":[]},{"level":2,"title":"Needle Engine without Editor Integration","slug":"needle-engine-without-editor-integration","link":"#needle-engine-without-editor-integration","children":[]},{"level":2,"title":"Third-Party Dependencies","slug":"third-party-dependencies","link":"#third-party-dependencies","children":[{"level":3,"title":"Recommended Dependencies","slug":"recommended-dependencies","link":"#recommended-dependencies","children":[]}]},{"level":2,"title":"Questions?","slug":"questions","link":"#questions","children":[]},{"level":2,"title":"What's next?","slug":"what-s-next","link":"#what-s-next","children":[]}],"git":{"updatedTime":1724788496000},"filePathRelative":"getting-started/index.md"}`);export{e as data}; diff --git a/assets/ktx-env-variable-d006aea1.js b/assets/ktx-env-variable-d006aea1.js new file mode 100644 index 000000000..c6b04a827 --- /dev/null +++ b/assets/ktx-env-variable-d006aea1.js @@ -0,0 +1 @@ +const s="/docs/imgs/ktx-env-variable.webp";export{s as _}; diff --git a/assets/logo-b695892f.js b/assets/logo-b695892f.js new file mode 100644 index 000000000..d1b892766 --- /dev/null +++ b/assets/logo-b695892f.js @@ -0,0 +1 @@ +const o="/docs/blender/logo.png";export{o as _}; diff --git a/assets/meta-test.html-32ee71cc.js b/assets/meta-test.html-32ee71cc.js new file mode 100644 index 000000000..617076b5a --- /dev/null +++ b/assets/meta-test.html-32ee71cc.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0ca6c8f8","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{e as data}; diff --git a/assets/meta-test.html-ba3f3381.js b/assets/meta-test.html-ba3f3381.js new file mode 100644 index 000000000..c9abac796 --- /dev/null +++ b/assets/meta-test.html-ba3f3381.js @@ -0,0 +1,3 @@ +import{_ as t,M as o,p as r,q as n,R as a,t as s,N as i,a1 as h}from"./framework-f6820c83.js";const d={},m=h(`
    meta:
    +
    +

    my_test_header

    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

    UI Canvas

    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

    How physics works

    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);function u(l,f){const e=o("metalink");return r(),n("div",null,[a("p",null,[s("Hello world "),i(e)]),m])}const g=t(d,[["render",u],["__file","meta-test.html.vue"]]);export{g as default}; diff --git a/assets/metalink-e99a74bf.js b/assets/metalink-e99a74bf.js new file mode 100644 index 000000000..0f11e5be9 --- /dev/null +++ b/assets/metalink-e99a74bf.js @@ -0,0 +1 @@ +import{_ as e}from"./framework-f6820c83.js";const t={__name:"metalink",setup(_){return(a,n)=>" ``` hello ``` "}},o=e(t,[["__file","metalink.vue"]]);export{o as default}; diff --git a/assets/modules.html-d4ea4cf8.js b/assets/modules.html-d4ea4cf8.js new file mode 100644 index 000000000..c96add963 --- /dev/null +++ b/assets/modules.html-d4ea4cf8.js @@ -0,0 +1 @@ +import{_ as i,M as t,p as a,q as l,R as e,t as o,N as n,V as c,a1 as p}from"./framework-f6820c83.js";const d={},u=e("strong",null,"NpmDef",-1),m=p("

    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

    ",2),h={href:"https://github.com/needle-tools/needle-engine-modules",target:"_blank",rel:"noopener noreferrer"};function _(f,g){const r=t("RouterLink"),s=t("ExternalLinkIcon");return a(),l("div",null,[e("p",null,[o("Projects can be composed of re-usable pieces that we call "),n(r,{to:"/project-structure.html#npm-definition-files"},{default:c(()=>[u]),_:1}),o(" (which stands for Npm Defintion File).")]),m,e("p",null,[e("a",h,[o("Github repository"),n(s)])])])}const b=i(d,[["render",_],["__file","modules.html.vue"]]);export{b as default}; diff --git a/assets/modules.html-ffce8241.js b/assets/modules.html-ffce8241.js new file mode 100644 index 000000000..379916890 --- /dev/null +++ b/assets/modules.html-ffce8241.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-bb6009aa","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{e as data}; diff --git a/assets/needle-button-a6fac229.js b/assets/needle-button-a6fac229.js new file mode 100644 index 000000000..1985f3439 --- /dev/null +++ b/assets/needle-button-a6fac229.js @@ -0,0 +1 @@ +import{_ as l,p as i,q as c,R as r,s as d,w as s,ad as u}from"./framework-f6820c83.js";const n={props:{href:String,secondary:Boolean,same_tab:Boolean,large:Boolean,event_goal:String,event_position:String}},o=()=>{u(t=>({"67fdb382":t.secondary?"#aaa":"#826bed","0b33cc28":t.large?"1em":".8em","1b4cfd03":t.secondary?"#bbb":"#6248be"}))},_=n.setup;n.setup=_?(t,a)=>(o(),_(t,a)):o;const b=["href","target"];function v(t,a,e,f,g,m){return i(),c("a",{href:e.href,target:e.same_tab?"_self":"_blank",class:s(e.event_goal?"plausible-event-name="+e.event_goal+(e.event_position?" plausible-event-position="+e.event_position:""):"")},[r("button",{class:s(e.event_goal?"plausible-event-name="+e.event_goal+(e.event_position?" plausible-event-position="+e.event_position:""):"")},[d(t.$slots,"default",{},void 0,!0)],2)],10,b)}const B=l(n,[["render",v],["__scopeId","data-v-b590c0c3"],["__file","needle-button.vue"]]);export{B as default}; diff --git a/assets/needle-config-json.html-aac10705.js b/assets/needle-config-json.html-aac10705.js new file mode 100644 index 000000000..c7301ee13 --- /dev/null +++ b/assets/needle-config-json.html-aac10705.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-f7b8f1f2","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{e as data}; diff --git a/assets/needle-config-json.html-f91034da.js b/assets/needle-config-json.html-f91034da.js new file mode 100644 index 000000000..62f8b509a --- /dev/null +++ b/assets/needle-config-json.html-f91034da.js @@ -0,0 +1,25 @@ +import{_ as t,M as n,p as a,q as o,R as s,N as r,V as i,t as c,a1 as p}from"./framework-f6820c83.js";const d={},l=p(`

    The needle.config.json is used to provide configuration for the Needle Editor integrations and for the Needle Engine build pipeline plugins.

    Paths
    buildDirectoryThis is where the built project files are being copied to
    assetsDirectoryThis is where the Editor integration assets will be copied to or created at (e.g. the .glb files exported from Unity or Blender)
    scriptsDirectoryThis is the directory the Editor integration is watching for code changes to re-generate components
    codegenDirectoryThis is where the Editor integration is outputting generated files to.
    baseUrlRequired 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.

    Basic Example

    {
    +  "buildDirectory": "dist",
    +  "assetsDirectory": "assets",
    +  "scriptsDirectory": "src/scripts",
    +  "codegenDirectory": "src/generated"
    +}
    +

    Copy Example

    {
    +  "buildDirectory": "dist",
    +  "assetsDirectory": "assets",
    +  "scriptsDirectory": "src/scripts",
    +  "codegenDirectory": "src/generated",
    +  "build": {
    +    "copy": [
    +      "cards"
    +    ]
    +  }
    +}
    +

    Example with different baseUrl (e.g. SvelteKit, Next.js)

    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.

    {
    +  "baseUrl": "assets",
    +  "buildDirectory": "dist",
    +  "assetsDirectory": "static/assets",
    +  "scriptsDirectory": "src/scripts",
    +  "codegenDirectory": "src/generated"
    +}
    +
    `,10);function u(h,k){const e=n("RouterLink");return a(),o("div",null,[l,s("ul",null,[s("li",null,[r(e,{to:"/project-structure.html"},{default:i(()=>[c("Project Structure")]),_:1})])])])}const q=t(d,[["render",u],["__file","needle-config-json.html.vue"]]);export{q as default}; diff --git a/assets/needle-engine-attributes.html-2b93dca4.js b/assets/needle-engine-attributes.html-2b93dca4.js new file mode 100644 index 000000000..df57ca292 --- /dev/null +++ b/assets/needle-engine-attributes.html-2b93dca4.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-caed7310","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":1725399379000},"filePathRelative":"reference/needle-engine-attributes.md"}');export{e as data}; diff --git a/assets/needle-engine-attributes.html-b6293290.js b/assets/needle-engine-attributes.html-b6293290.js new file mode 100644 index 000000000..62e3e4d6b --- /dev/null +++ b/assets/needle-engine-attributes.html-b6293290.js @@ -0,0 +1,20 @@ +import{_ as l}from"./custom-loading-style-65a23c0d.js";import{_ as o,M as d,p as c,q as u,R as n,t,N as a,a1 as s}from"./framework-f6820c83.js";const i={},r=n("p",null,[t("The "),n("code",null,""),t(" 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."),n("br"),t(" The table below shows a list of the most important ones:")],-1),p=n("thead",null,[n("tr",null,[n("th",null,"Attribute"),n("th",null,"Description")])],-1),g=n("tr",null,[n("td",null,[n("strong",null,"Loading")]),n("td")],-1),h=n("tr",null,[n("td",null,[n("code",null,"src")]),n("td",null,[t("Path to one or multiple glTF or glb files."),n("br"),t("Supported types are "),n("code",null,"string"),t(", "),n("code",null,"string[]"),t(" or a stringified array ("),n("code",null,","),t(" separated)")])],-1),m=n("tr",null,[n("td",null,[n("code",null,"dracoDecoderPath")]),n("td",null,"URL to the draco decoder")],-1),k=n("td",null,[n("code",null,"dracoDecoderType")],-1),_=n("code",null,"wasm",-1),v=n("code",null,"js",-1),b={href:"https://threejs.org/docs/#examples/en/loaders/DRACOLoader.setDecoderConfig",target:"_blank",rel:"noopener noreferrer"},f=n("tr",null,[n("td",null,[n("code",null,"ktx2DecoderPath")]),n("td",null,"URL to the KTX2 decoder")],-1),y=n("tr",null,[n("td",null,[n("strong",null,"Rendering")]),n("td")],-1),x=n("tr",null,[n("td",null,[n("code",null,"skybox-image")]),n("td",null,[t("optional, URL to a skybox image (background image) or a preset string: "),n("code",null,"studio"),t(", "),n("code",null,"blurred-skybox"),t(", "),n("code",null,"quicklook"),t(", "),n("code",null,"quicklook-ar")])],-1),q=n("tr",null,[n("td",null,[n("code",null,"environment-image")]),n("td",null,[t("optional, URL to a environment image (environment light) or a preset string: "),n("code",null,"studio"),t(", "),n("code",null,"blurred-skybox"),t(", "),n("code",null,"quicklook"),t(", "),n("code",null,"quicklook-ar")])],-1),w=n("tr",null,[n("td",null,[n("code",null,"contactshadows")]),n("td",null,"optional, render contact shadows")],-1),R=n("tr",null,[n("td",null,[n("code",null,"tone-mapping")]),n("td",null,[t("optional, supported values are "),n("code",null,"none"),t(", "),n("code",null,"linear"),t(", "),n("code",null,"neutral"),t(", "),n("code",null,"agx")])],-1),D=n("tr",null,[n("td",null,[n("code",null,"tone-mapping-exposure")]),n("td",null,[t("optional number e.g. increase exposure with "),n("code",null,'tone-mapping-exposure="1.5"'),t(", requires "),n("code",null,"tone-mapping"),t(" to be set")])],-1),P=n("tr",null,[n("td",null,[n("strong",null,"Interaction")]),n("td")],-1),C=n("tr",null,[n("td",null,[n("code",null,"autoplay")]),n("td",null,[t("add or set to "),n("code",null,"true"),t(" to auto play animations e.g. "),n("code",null," 3.17.1")])],-1),z=n("tr",null,[n("td",null,[n("strong",null,"Internal")]),n("td")],-1),K=n("tr",null,[n("td",null,[n("code",null,"hash")]),n("td",null,"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.")],-1),M=s(`

    Examples

    <!-- Setting the path to a custom glb to be loaded -->
    +<needle-engine src="path/to/your.glb"></needle-engine>
    +
    <!-- Overriding where the draco decoder is located -->
    +<needle-engine src="path/to/your.glb" dracoDecoderPath="path/to/draco/folder"></needle-engine>
    +
    `,3),Y={href:"https://stackblitz.com/edit/needle-engine-cycle-src?file=index.html",target:"_blank",rel:"noopener noreferrer"},G=s(`
    <needle-engine
    +      camera-controls
    +      auto-rotate
    +      autoplay
    +      skybox-image="https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/industrial_sunset_puresky_1k.hdr"
    +      environment-image="https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/industrial_sunset_puresky_1k.hdr"
    +      src="https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/DamagedHelmet/glTF-Embedded/DamagedHelmet.gltf"
    +      >
    +      </needle-engine>
    +

    Receiving an event when the needle-engine context has finished loading:

    <needle-engine loadfinished="onLoadFinished"> </needle-engine>
    +<script>
    +    function onLoadFinished() {
    +        console.log("Needle Engine has finished loading");
    +    }
    +</script>
    +

    Custom Loading Style (PRO)

    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.

    `,5),X=n("img",{src:l,alt:"custom loading"},null,-1),J=n("br",null,null,-1),Q={href:"https://github.com/needle-engine/vite-template/blob/loading-style/custom/index.html",target:"_blank",rel:"noopener noreferrer"};function W(Z,$){const e=d("ExternalLinkIcon");return c(),u("div",null,[r,n("table",null,[p,n("tbody",null,[g,h,m,n("tr",null,[k,n("td",null,[t("draco decoder type. Options are "),_,t(" or "),v,t(". See "),n("a",b,[t("three.js documentation"),a(e)])])]),f,y,x,q,w,R,D,P,C,N,E,L,O,S,T,I,U,j,F,A,H,V,B,z,K])]),M,n("p",null,[t("Setting environment images, playing animation and automatic camera controls. "),n("a",Y,[t("See it live on stackblitz"),a(e)])]),G,n("p",null,[X,J,n("a",Q,[t("See code on github"),a(e)])])])}const en=o(i,[["render",W],["__file","needle-engine-attributes.html.vue"]]);export{en as default}; diff --git a/assets/networking.html-91b3a83a.js b/assets/networking.html-91b3a83a.js new file mode 100644 index 000000000..2eae5ec77 --- /dev/null +++ b/assets/networking.html-91b3a83a.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-dda7a368","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":1698329319000},"filePathRelative":"networking.md"}');export{e as data}; diff --git a/assets/networking.html-e1e616de.js b/assets/networking.html-e1e616de.js new file mode 100644 index 000000000..e76f1cf46 --- /dev/null +++ b/assets/networking.html-e1e616de.js @@ -0,0 +1,65 @@ +import{_ as o,M as i,p as c,q as p,R as n,t as e,N as a,a1 as t}from"./framework-f6820c83.js";const r="/docs/imgs/networking_absolute.webp",l={},d=n("h1",{id:"networking",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#networking","aria-hidden":"true"},"#"),e(" Networking")],-1),u=n("p",null,[e("Access to core networking functionality can be obtained by using "),n("code",null,"this.context.connection"),e(" 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.")],-1),k={href:"https://github.com/jjxxs/websocket-ts",target:"_blank",rel:"noopener noreferrer"},h={href:"https://google.github.io/flatbuffers/",target:"_blank",rel:"noopener noreferrer"},m=t(`

    Using Multiplayer

    • Enable Networking
      Add a SyncedRoom component.

    • Enable Desktop Viewer Sync
      Add a SyncedCamera component.

    • Enable XR Avatar Sync
      Add a WebXRSync component.

    • Enable Voice Chat
      Add a VoIP component.

    • Enable Screensharing
      Add a ScreenCapture component.

    Core Components

    • 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
    • SyncedTransform โ€” handles synchronizing transforms
    • SyncedCamera โ€” spawns a prefab for any user connected to the room which will follow their position
    • WebXRSync โ€” handles synchronization for AR and VR users
    • VoIP โ€” handles voice-over-IP audio connections, microphone access etc. between users
    • Networking โ€” use to customize the server backend url

    Manual Networking

    Sending

    Send a json message to all users in the same room:
    this.context.connection.send(key:string, data: IModel | object | boolean | string | number | null)

    Send a flatbuffer binary array to all users in the same room:
    this.context.connect.sendBinary(arr:Uint8Array)

    Persistence

    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" }

    Receiving

    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

    Common Events

    Room Events
    this.context.beginListen(RoomEvents.JoinedRoom, () => { })Listen to the event when you have joined a networked room
    this.context.beginListen(RoomEvents.LeftRoom, () => { })Listen to the event when you have left a networked room
    this.context.beginListen(RoomEvents.UserJoinedRoom, () => { })Listen to the event when another user has joined your networked room
    this.context.beginListen(RoomEvents.UserLeftRoom, () => { })Listen to the event when another user has left your networked room

    Auto Networking (experimental)

    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 } 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();
    +    }
    +}
    +

    Flatbuffers for your own components

    `,19),g={href:"https://google.github.io/flatbuffers/flatbuffers_guide_using_schema_compiler.html",target:"_blank",rel:"noopener noreferrer"},b={href:"https://google.github.io/flatbuffers/flatbuffers_guide_writing_schema.html",target:"_blank",rel:"noopener noreferrer"},v={href:"https://google.github.io/flatbuffers/flatbuffers_guide_use_typescript.html",target:"_blank",rel:"noopener noreferrer"},f=n("h2",{id:"networking-package",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#networking-package","aria-hidden":"true"},"#"),e(" Networking Package")],-1),w={href:"https://fwd.needle.tools/needle-engine/packages/needle-engine-networking",target:"_blank",rel:"noopener noreferrer"},y=n("code",null,"Networking",-1),_=t(`

    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:

    endpoint stringrelative path to the websocket endpoint (e.g. /socket)
    maxUsers numberAmount of users allowed per room
    defaultUserTimeout numberTimeout length in seconds until a user is kicked from a room (if no ping is received). Defaults to 30 seconds

    Networking on Glitch

    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.

    Limitations

    • approx. 15-20 people maximum โ€“ afterwards the small default Glitch server instance becomes slow

    Local Networking

    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:

    `,10),x={href:"https://fwd.needle.tools/needle-engine/local-networking-repository",target:"_blank",rel:"noopener noreferrer"},C=n("h2",{id:"hosting-your-own-networking-server",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#hosting-your-own-networking-server","aria-hidden":"true"},"#"),e(" Hosting your own Networking Server")],-1),R=n("strong",null,"google cloud",-1),j={href:"https://fwd.needle.tools/needle-engine/local-networking-repository",target:"_blank",rel:"noopener noreferrer"},S=n("p",null,[e("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 "),n("code",null,"Networking"),e(" component "),n("code",null,"URL"),e(" field as well:")],-1),N=n("p",null,[n("img",{src:r,alt:"Needle Engine Networking component with networking server hosted elswhere"})],-1),q=n("h2",{id:"peerjs",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#peerjs","aria-hidden":"true"},"#"),e(" peerjs")],-1),E=n("code",null,"Screencapture",-1),L=n("code",null,"Voip",-1),T={href:"https://peerjs.com/",target:"_blank",rel:"noopener noreferrer"},M=n("h3",{id:"customizing-peerjs-options",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#customizing-peerjs-options","aria-hidden":"true"},"#"),e(" Customizing peerjs options")],-1),A=n("ul",null,[n("li",null,[e("If you want to modify the default peerjs options you can call "),n("code",null,"setPeerOptions(opts: PeerjsOptions)"),e(" with your custom options. This can be used to modify the hosting provider in case where you host your own peerjs server.")])],-1);function V(F,P){const s=i("ExternalLinkIcon");return c(),p("div",null,[d,u,n("p",null,[e("Networking is currently based on "),n("a",k,[e("websockets"),a(s)]),e(" and sending either json strings (for infrequent updates) or "),n("a",h,[e("flatbuffers"),a(s)]),e(" (for frequent updates). Continue reading below for more details:")]),m,n("ul",null,[n("li",null,[n("a",g,[e("Using the schema compiler"),a(s)])]),n("li",null,[n("a",b,[e("Generating a schema"),a(s)])]),n("li",null,[n("a",v,[e("Flatbuffer in Typescript"),a(s)])])]),f,n("p",null,[e("Needle Engine currently uses its "),n("a",w,[e("own networking package"),a(s)]),e(" hosted on npm. By default if not configured differently using the "),y,e(" component Needle Engine will connect to a server running on Glitch.")]),_,n("ul",null,[n("li",null,[n("a",x,[e("Local Networking Repository"),a(s)])])]),C,n("p",null,[e("You can also deploy your own networking server on e.g. "),R,e(". For further instructions please refer to the description found here: "),n("a",j,[e("Local Networking Repository"),a(s)])]),S,N,q,n("p",null,[e("Needle Engine "),E,e(" / Screensharing and "),L,e(" components use "),n("a",T,[e("peerjs"),a(s)]),e(" for networking audio and video.")]),M,A])}const U=o(l,[["render",V],["__file","networking.html.vue"]]);export{U as default}; diff --git a/assets/os-link-31cc79d0.js b/assets/os-link-31cc79d0.js new file mode 100644 index 000000000..3b9c7c69a --- /dev/null +++ b/assets/os-link-31cc79d0.js @@ -0,0 +1 @@ +import{_ as s,p as e,q as n,s as o,t as l,v as u}from"./framework-f6820c83.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-a65539aa.js b/assets/project-structure.html-a65539aa.js new file mode 100644 index 000000000..d65a1cc1c --- /dev/null +++ b/assets/project-structure.html-a65539aa.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5c95b2b3","path":"/project-structure.html","title":"Project Structure","lang":"en-US","frontmatter":{"title":"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":[]},{"level":2,"title":"Projects in Unity","slug":"projects-in-unity","link":"#projects-in-unity","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":1696452754000},"filePathRelative":"project-structure.md"}');export{e as data}; diff --git a/assets/project-structure.html-ff62d7b3.js b/assets/project-structure.html-ff62d7b3.js new file mode 100644 index 000000000..9f6db6c21 --- /dev/null +++ b/assets/project-structure.html-ff62d7b3.js @@ -0,0 +1 @@ +import{_ as a,M as s,p as d,q as c,R as e,N as n,V as l,t,a1 as r}from"./framework-f6820c83.js";const u="/docs/imgs/unity-project-local-template.jpg",h="/docs/imgs/unity-project-remote-template.jpg",p={},g=e("h1",{id:"needle-engine-project-structure",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#needle-engine-project-structure","aria-hidden":"true"},"#"),t(" Needle Engine Project Structure")],-1),f=e("h3",{id:"web-project-files",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#web-project-files","aria-hidden":"true"},"#"),t(" Web Project Files")],-1),_=e("thead",null,[e("tr",null,[e("th"),e("th")])],-1),m=e("tr",null,[e("td",null,[e("strong",null,"Needle Engine")]),e("td")],-1),y=e("code",null,"needle.config.json",-1),b=e("td",null,"Configuration for Needle Engine builds and integrations",-1),j=e("tr",null,[e("td",null,[e("strong",null,"Ecosystem")]),e("td")],-1),w=e("tr",null,[e("td",null,[e("code",null,"package.json")]),e("td",null,"Project configuration containing name, version, dependencies and scripts")],-1),k=e("tr",null,[e("td",null,[e("code",null,"tsconfig.json")]),e("td",null,"Typescript compiler configuration")],-1),v=e("tr",null,[e("td",null,[e("code",null,".gitignore")]),e("td",null,"Files and folders to be ignored in git")],-1),x=e("tr",null,[e("td",null,[e("code",null,"vite.config.js")]),e("td",null,[t("Contains vite specific configuration."),e("br"),t("It also adds the Needle Engine vite plugins.")])],-1),E=e("h3",{id:"default-vite-project-structure",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#default-vite-project-structure","aria-hidden":"true"},"#"),t(" Default Vite project structure")],-1),P={href:"https://vitejs.dev/",target:"_blank",rel:"noopener noreferrer"},N=e("thead",null,[e("tr",null,[e("th"),e("th")])],-1),T=e("tr",null,[e("td",null,[e("strong",null,"Folders")]),e("td")],-1),U=e("tr",null,[e("td",null,[e("code",null,"assets/")]),e("td",null,[t("The asset folder contains exported assets from Unity. E.g. generated "),e("code",null,"gltf"),t(" files, audio or video files. It is not recommended to manually add files to "),e("code",null,"assets"),t(" as it will get cleared on building the distribution for the project.")])],-1),I=e("tr",null,[e("td",null,[e("code",null,"include/")]),e("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),C=e("tr",null,[e("td",null,[e("code",null,"src/generated/")]),e("td",null,"The generated javascript code. Do not edit manually!")],-1),D=e("tr",null,[e("td",null,[e("code",null,"src/scripts/")]),e("td",null,"Your project specific scripts / components")],-1),L=e("tr",null,[e("td",null,[e("code",null,"src/styles/")]),e("td",null,"Stylesheets")],-1),M=e("td",null,[e("code",null,"*")],-1),F=e("tr",null,[e("td",null,[e("strong",null,"Files")]),e("td")],-1),V=e("tr",null,[e("td",null,[e("code",null,"index.html")]),e("td",null,"The landing- or homepage of your website")],-1),S=e("td",null,[e("code",null,"vite.config")],-1),R={href:"https://vitejs.dev/config/",target:"_blank",rel:"noopener noreferrer"},W=e("tr",null,[e("td",null,[e("code",null,"src/main.ts")]),e("td",null,[t("Included from "),e("code",null,"index.html"),t(" and importing "),e("code",null,"needle-engine")])],-1),Y=e("td",null,[e("code",null,"*")],-1),q=e("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),A=e("hr",null,null,-1),B=e("h1",{id:"unity-project-folders-and-files",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#unity-project-folders-and-files","aria-hidden":"true"},"#"),t(" Unity Project Folders and Files")],-1),O=e("thead",null,[e("tr",null,[e("th",null,"Folder"),e("th")])],-1),G=e("tr",null,[e("td",null,[e("strong",null,"Unity")]),e("td")],-1),J=e("tr",null,[e("td",null,[e("code",null,"Assets")]),e("td",null,"This is where project specific/exclusive assets live.")],-1),z=e("td",null,[e("code",null,"Packages")],-1),H={href:"https://docs.unity3d.com/Manual/PackagesList.html",target:"_blank",rel:"noopener noreferrer"},K=e("tr",null,[e("td",null,[e("strong",null,"Needle Engine Unity Package")]),e("td")],-1),Q=e("tr",null,[e("td",null,[e("code",null,"Core/Runtime/Components")]),e("td",null,[t("Contains all Needle Engine built-in runtime components (See "),e("code",null,"Packages/Needle Engine Exporter"),t(" in the Unity Project Window)")])],-1),X=e("h2",{id:"projects-in-unity",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#projects-in-unity","aria-hidden":"true"},"#"),t(" Projects in Unity")],-1),Z=e("p",null,[t("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). It is also possible to create custom local templates using the Project View context menu in "),e("code",null,"Create/Needle Engine/Project Template"),t(".")],-1),$=e("p",null,[e("img",{src:u,alt:"Unity ExportInfo local templates"})],-1),ee=e("code",null,"needle.config.json",-1),te={href:"https://github.com/needle-engine",target:"_blank",rel:"noopener noreferrer"},ne=r('

    Unity ExportInfo local templates

    Temporary Projects

    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!

    Typescript in Unity

    ',4),oe=e("strong",null,"NPM Definition",-1),le={href:"https://docs.npmjs.com/about-packages-and-modules",target:"_blank",rel:"noopener noreferrer"},ie=r('

    C# component stubs for typescript files will also be automatically generated for scripts inside npmdef packages.

    Creating and installing a npmdef

    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).

    image

    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.

    Continue Reading

    ',7);function se(re,ae){const o=s("RouterLink"),i=s("ExternalLinkIcon");return d(),c("div",null,[g,f,e("table",null,[_,e("tbody",null,[m,e("tr",null,[e("td",null,[n(o,{to:"/reference/needle-config-json.html"},{default:l(()=>[y]),_:1})]),b]),j,w,k,v,x])]),E,e("p",null,[t("Our main project template uses the superfast "),e("a",P,[t("vite"),n(i)]),t(" 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).")]),e("table",null,[N,e("tbody",null,[T,U,I,C,D,L,e("tr",null,[M,e("td",null,[t("You can add any new folders here as you please. Make sure to "),n(o,{to:"/reference/needle-config-json.html"},{default:l(()=>[t("copy")]),_:1}),t(" them to the output directory when building")])]),F,V,e("tr",null,[S,e("td",null,[t("The "),e("a",R,[t("vite config"),n(i)]),t(". Settings for building the distribution and hosting the development server are made here. It is usually not necessary to edit these settings.")])]),W,e("tr",null,[Y,e("td",null,[t("You can add any new files here as you please. Make sure to "),n(o,{to:"/reference/needle-config-json.html"},{default:l(()=>[t("copy")]),_:1}),t(" them to the output directory when building (unless they are just being used during development)")])])])]),q,e("p",null,[n(o,{to:"/html.html"},{default:l(()=>[t("Learn more in the docs about bundling and usage with other frameworks")]),_:1})]),A,B,e("table",null,[O,e("tbody",null,[G,J,e("tr",null,[z,e("td",null,[t("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 "),e("a",H,[t("the Unity documentation about packages"),n(i)]),t(".")])]),K,Q])]),X,Z,$,e("p",null,[t("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 "),ee,t(" 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 "),e("a",te,[t("github.com/needle-engine"),n(i)])]),ne,e("p",null,[oe,t(" are "),e("a",le,[t("npm packages"),n(i)]),t(" tightly integrated into the Unity Editor which makes it easily possible to share scripts with multiple web- or even Unity projects.")]),ie,e("ul",null,[e("li",null,[n(o,{to:"/getting-started/for-unity-developers.html"},{default:l(()=>[t("Typescript Guide for Unity Developers")]),_:1})]),e("li",null,[n(o,{to:"/getting-started/typescript-essentials.html"},{default:l(()=>[t("Typescript Essentials")]),_:1})]),e("li",null,[n(o,{to:"/scripting.html"},{default:l(()=>[t("Writing custom scripts")]),_:1})]),e("li",null,[n(o,{to:"/everywhere-actions.html"},{default:l(()=>[t("Everywhere Actions")]),_:1})])])])}const ce=a(p,[["render",se],["__file","project-structure.html.vue"]]);export{ce as default}; diff --git a/assets/quoteslides-a8d399c2.js b/assets/quoteslides-a8d399c2.js new file mode 100644 index 000000000..0431a1f14 --- /dev/null +++ b/assets/quoteslides-a8d399c2.js @@ -0,0 +1 @@ +import{d as n}from"./app-1ecc50a8.js";import{_ as l,o as d,p as a,q as i,s as r}from"./framework-f6820c83.js";const u=n({setup(){d(()=>{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)})}}),c={class:"quotes"};function _(o,e,t,s,p,f){return a(),i("div",c,[r(o.$slots,"default",{},void 0,!0)])}const m=l(u,[["render",_],["__scopeId","data-v-96af3692"],["__file","quoteslides.vue"]]);export{m as default}; diff --git a/assets/removeserviceworker-8e285252.js b/assets/removeserviceworker-8e285252.js new file mode 100644 index 000000000..ae8ececca --- /dev/null +++ b/assets/removeserviceworker-8e285252.js @@ -0,0 +1 @@ +import{_ as t}from"./framework-f6820c83.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/sample-d7ec0e26.js b/assets/sample-d7ec0e26.js new file mode 100644 index 000000000..9880067ab --- /dev/null +++ b/assets/sample-d7ec0e26.js @@ -0,0 +1 @@ +import{_ as c,p as r,q as s,R as l,Q as i}from"./framework-f6820c83.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,o,a,_){return r(),s("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(),s("iframe",{key:0,src:a.sanitizedUrl,ref:"frame2",allow:"xr; xr-spatial-tracking; camera; microphone; fullscreen;display-capture"},null,8,p)):i("v-if",!0)])}const h=c(d,[["render",u],["__scopeId","data-v-65d547d3"],["__file","sample.vue"]]);export{h as default}; diff --git a/assets/samples-and-modules.html-57e9f6ca.js b/assets/samples-and-modules.html-57e9f6ca.js new file mode 100644 index 000000000..12b9c047a --- /dev/null +++ b/assets/samples-and-modules.html-57e9f6ca.js @@ -0,0 +1,31 @@ +import{_ as i,M as s,p as r,q as p,N as t,R as n,t as e,Q as o}from"./framework-f6820c83.js";const d={},m=n("h2",{id:"samples-to-download-and-play",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#samples-to-download-and-play","aria-hidden":"true"},"#"),e(" Samples to download and play")],-1),c={href:"https://engine.needle.tools/samples",target:"_blank",rel:"noopener noreferrer"};function h(u,g){const a=s("sample"),l=s("ExternalLinkIcon");return r(),p("div",null,[t(a,{src:"https://engine.needle.tools/samples-uploads/physics-cannon/"}),m,n("p",null,[e("View all samples at "),n("a",c,[e("engine.needle.tools/samples"),t(l)]),e(" with a live preview and links for download and installation.")]),o(` - [Brune Simons 20k](https://engine.needle.tools/samples/buno-simon-20k) + *Physics* +- [Stencil Portals](https://engine.needle.tools/samples/stencil-portals) + *URP RenderObject stencil to threejs* +- [Custom Shaders](https://engine.needle.tools/samples/custom-shaders) + *Unity ShaderGraph to threejs* +- [Sandbox](https://needle-tiny-starter.glitch.me/) + *Multi-user sandbox builder* +- [VideoPlayer](https://needle-videoplayer-sample.glitch.me/) + *Minimal Unity VideoPlayer sample* +- [UI Button](https://engine.needle.tools/samples/ui-button) + *Minimal UI button event sample* +- [Spatial Trigger](https://needle-spatial-trigger-sample.glitch.me/) + *Define areas in your world and hookup events from Unity* +- and [more](https://engine.needle.tools/samples/) + +--- + + + + + + + + + + + + `),o(` +Get the samples on github + `)])}const f=i(d,[["render",h],["__file","samples-and-modules.html.vue"]]);export{f as default}; diff --git a/assets/samples-and-modules.html-a3fd2522.js b/assets/samples-and-modules.html-a3fd2522.js new file mode 100644 index 000000000..8dfe4c3a2 --- /dev/null +++ b/assets/samples-and-modules.html-a3fd2522.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-761831b6","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.\\n
    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);
    +    }
    +}
    +

    see scripting for all component events

    Reference an Object from Unity

    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;
    +} 
    +

    Reference and load an asset from Unity (Prefab or SceneAsset)

    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
    +    }  
    +} 
    +

    Reference and load scenes from Unity

    `,7),_={class:"custom-container tip"},q=n("p",{class:"custom-container-title"},"TIP",-1),C={href:"https://engine.needle.tools/samples/multi-scenes-(dynamic-loading)",target:"_blank",rel:"noopener noreferrer"},S=e(`
    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();
    +        }
    +    }
    +} 
    +

    Receive Clicks on Objects

    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.

    `,3),E=e(`
    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);
    +    }
    +}
    +

    Networking Clicks on Objects

    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.

    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();
    +    }
    +    
    +}
    +

    Play Animation on click

    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();
    +        }
    +    }
    +}
    +

    Reference an Animation Clip

    This can be useful if you want to run your custom animation logic.
    You can also export an array of clips.

    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);
    +    }
    +}
    +

    Create and invoke a UnityEvent

    import { Behaviour, serializable, EventList } from "@needle-tools/engine"
    +
    +export class MyComponent extends Behaviour {
    +
    +    @serializable(EventList)
    +    myEvent? : EventList;
    +
    +    start() {
    +        this.myEvent?.invoke();
    +    }
    +}
    +
    `,11),j={class:"custom-container tip"},B=n("p",{class:"custom-container-title"},"TIP",-1),O=n("code",null,'myComponent.addEventListener("my-event", evt => {...})',-1),M=n("br",null,null,-1),L={href:"https://discord.needle.tools",target:"_blank",rel:"noopener noreferrer"},R=e(`

    Declare a custom event type

    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);
    +    }
    +}
    +

    Example use:
    20221128-210735_Unity-needle

    Use nested objects and serialization

    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.

    In your typescript component:

    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();
    +    }
    +}
    +

    In C# in any script:

    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;
    +}
    +

    TIP

    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.

    Use Web APIs

    `,12),z={class:"custom-container tip"},P=n("p",{class:"custom-container-title"},"TIP",-1),T={href:"https://npmjs.org",target:"_blank",rel:"noopener noreferrer"},D=n("br",null,null,-1),A=e(`

    Display current location

    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);
    +        });
    +    }
    +}
    +

    Display current time using a Coroutine

    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)
    +        }
    +    };
    +}
    +
    `,4),H=n("h2",{id:"change-custom-shader-property",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#change-custom-shader-property","aria-hidden":"true"},"#"),s(" Change custom shader property")],-1),V=n("code",null,"_Speed",-1),G=n("br",null,null,-1),F={href:"https://engine.needle.tools/samples/shaders/",target:"_blank",rel:"noopener noreferrer"},I=e(`
    import { Behaviour, serializable } from "@needle-tools/engine";
    +import { Material } from "three";
    +
    +declare type MyCustomShaderMaterial = {
    +   _Speed: number;
    +};
    +
    +export class IncreaseShaderSpeedOverTime extends Behaviour {
    +
    +   @serializable(Material)
    +   myMaterial?: Material & MyCustomShaderMaterial;
    +
    +   update() {
    +       if (this.myMaterial) {
    +           this.myMaterial._Speed *= 1 + this.context.time.deltaTime;
    +           if(this.myMaterial._Speed > 1) this.myMaterial._Speed = .0005;
    +           if(this.context.time.frame % 30 === 0) console.log(this.myMaterial._Speed)
    +       }
    +   }
    +}
    +

    Switching src attribute

    `,3),U={href:"https://stackblitz.com/edit/needle-engine-cycle-src?file=index.html",target:"_blank",rel:"noopener noreferrer"},N=n("h2",{id:"adding-new-postprocessing-effects",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#adding-new-postprocessing-effects","aria-hidden":"true"},"#"),s(" Adding new postprocessing effects")],-1),W={href:"https://github.com/pmndrs/postprocessing",target:"_blank",rel:"noopener noreferrer"},Y=n("code",null,"npm i postprocessing",-1),K=n("code",null,"PostProcessingEffect",-1),Q=n("p",null,[s("To use the effect add it to the same object as your "),n("code",null,"Volume"),s(" component.")],-1),J={href:"https://pmndrs.github.io/postprocessing/public/demo/#outline",target:"_blank",rel:"noopener noreferrer"},X=e(`
    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);
    +

    Custom ParticleSystem Behaviour

    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())
    +    }
    +}
    +

    Custom 2D Audio Component

    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();
    +        })
    +    }
    +}
    +

    Arbitrary external files

    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());
    +    }
    +}
    +

    Receiving html element click in component

    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();
    +    }
    +}
    +

    Disable environment light

    import { Behaviour } from "@needle-tools/engine";
    +import { Texture } from "three";
    +
    +export class DisableEnvironmentLight extends Behaviour {
    +
    +   private _previousEnvironmentTexture: Texture | null = null;
    +
    +   onEnable(): void {
    +       this._previousEnvironmentTexture = this.context.scene.environment;
    +       this.context.scene.environment = null;
    +   }
    +
    +   onDisable(): void {
    +       this.context.scene.environment = this._previousEnvironmentTexture;
    +   }
    +}
    +

    Use mediapipe package to control the 3D scene with hands

    `,16),$=n("br",null,null,-1),Z={href:"https://engine.needle.tools/samples/mediapipe-hands/",target:"_blank",rel:"noopener noreferrer"},nn=e(`
    import { FilesetResolver, HandLandmarker, HandLandmarkerResult, NormalizedLandmark } from "@mediapipe/tasks-vision";
    +import { Behaviour, Mathf, serializable, showBalloonMessage } from "@needle-tools/engine";
    +import { ParticleSphere } from "./ParticleSphere";
    +
    +export class MediapipeHands extends Behaviour {
    +
    +    @serializable(ParticleSphere)
    +    spheres: ParticleSphere[] = [];
    +
    +    private _video!: HTMLVideoElement;
    +    private _handLandmarker!: HandLandmarker;
    +
    +    async awake() {
    +        showBalloonMessage("Initializing mediapipe...")
    +
    +        const vision = await FilesetResolver.forVisionTasks(
    +            // path/to/wasm/root
    +            "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"
    +        );
    +        this._handLandmarker = await HandLandmarker.createFromOptions(
    +            vision,
    +            {
    +                baseOptions: {
    +                    modelAssetPath: "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/latest/hand_landmarker.task",
    +                    delegate: "GPU"
    +                },
    +                numHands: 2
    +            });
    +        //@ts-ignore
    +        await this._handLandmarker.setOptions({ runningMode: "VIDEO" });
    +
    +        this._video = document.createElement("video");
    +        this._video.setAttribute("style", "max-width: 30vw; height: auto;");
    +        console.log(this._video);
    +        this._video.autoplay = true;
    +        this._video.playsInline = true;
    +        this.context.domElement.appendChild(this._video);
    +        this.startWebcam(this._video);
    +    }
    +
    +    private _lastVideoTime: number = 0;
    +
    +    update(): void {
    +        if (!this._video || !this._handLandmarker) return;
    +        const video = this._video;
    +        if (video.currentTime !== this._lastVideoTime) {
    +            let startTimeMs = performance.now();
    +            showBalloonMessage("<strong>Control the spheres with one or two hands</strong>!<br/><br/>Sample scene by <a href='https://twitter.com/llllkatjallll/status/1659280435023605773'>Katja Rempel</a>")
    +            const detections = this._handLandmarker.detectForVideo(video, startTimeMs);
    +            this.processResults(detections);
    +            this._lastVideoTime = video.currentTime;
    +        }
    +
    +    }
    +
    +    private processResults(results: HandLandmarkerResult) {
    +        const hand1 = results.landmarks[0];
    +        // check if we have even one hand
    +        if (!hand1) return;
    +
    +        if (hand1.length >= 4 && this.spheres[0]) {
    +            const pos = hand1[4];
    +            this.processLandmark(this.spheres[0], pos);
    +        }
    +
    +        // if we have a second sphere:
    +        if (this.spheres.length >= 2) {
    +            const hand2 = results.landmarks[1];
    +            if (!hand2) {
    +                const pos = hand1[8];
    +                this.processLandmark(this.spheres[1], pos);
    +            }
    +            else {
    +                const pos = hand2[4];
    +                this.processLandmark(this.spheres[1], pos);
    +            }
    +        }
    +    }
    +
    +    private processLandmark(sphere: ParticleSphere, pos: NormalizedLandmark) {
    +        const px = Mathf.remap(pos.x, 0, 1, -6, 6);
    +        const py = Mathf.remap(pos.y, 0, 1, 6, -6);
    +        sphere.setTarget(px, py, 0);
    +    }
    +
    +    private async startWebcam(video: HTMLVideoElement) {
    +        const constraints = { video: true, audio: false };
    +        const stream = await navigator.mediaDevices.getUserMedia(constraints);
    +        video.srcObject = stream;
    +    }
    +}
    +

    Change Color On Collision

    import { Behaviour, Collision, Renderer } from "@needle-tools/engine";
    +import{ Color } from "three";
    +
    +export class ChangeColorOnCollision extends Behaviour {
    +
    +    private renderer: Renderer | null = null;
    +    private collisionCount: number = 0;
    +
    +    private _startColor? : Color[];
    +
    +    start() {
    +        this.renderer = this.gameObject.getComponent(Renderer);
    +        if (!this.renderer) return;
    +        if(!this._startColor) this._startColor = [];
    +        for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
    +            this.renderer.sharedMaterials[i] = this.renderer.sharedMaterials[i].clone();
    +            this._startColor[i] = this.renderer.sharedMaterials[i]["color"].clone();
    +        }
    +    }
    +
    +    onCollisionEnter(_col: Collision) {
    +        if (!this.renderer) return;
    +        this.collisionCount += 1;
    +        for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
    +            this.renderer.sharedMaterials[i]["color"].setRGB(Math.random(), Math.random(), Math.random());
    +        }
    +    }
    +
    +    onCollisionExit(_col: Collision) {
    +        if (!this.renderer || !this._startColor) return;
    +        this.collisionCount -= 1;
    +        if (this.collisionCount === 0) {
    +            for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
    +                this.renderer.sharedMaterials[i]["color"].copy(this._startColor[i])
    +                // .setRGB(.1, .1, .1);
    +            }
    +        }
    +    }
    +
    +    // more events:
    +    // onCollisionStay(_col: Collision)
    +    // onCollisionExit(_col: Collision)
    +}
    +

    Physics Trigger Relay

    Invoke events using an objects physics trigger methods

    export class PhysicsTrigger extends Behaviour {
    +
    +    @serializeable(GameObject)
    +    triggerObjects?:GameObject[];
    +
    +    @serializeable(EventList)
    +    onEnter?: EventList;
    +
    +    @serializeable(EventList)
    +    onStay?: EventList;
    +
    +    @serializeable(EventList)
    +    onExit?: EventList;
    +
    +    onTriggerEnter(col: Collider) {
    +        if(this.triggerObjects && this.triggerObjects.length > 0 && !this.triggerObjects?.includes(col.gameObject)) return;
    +        this.onEnter?.invoke();
    +    }
    +
    +    onTriggerStay(col: Collider) {
    +        if(this.triggerObjects && this.triggerObjects.length > 0 && !this.triggerObjects?.includes(col.gameObject)) return;
    +        this.onStay?.invoke();
    +    }
    +
    +    onTriggerExit(col: Collider) {
    +        if(this.triggerObjects && this.triggerObjects.length > 0 && !this.triggerObjects?.includes(col.gameObject)) return;
    +        this.onExit?.invoke();
    +    }
    +}
    +

    Auto Reset

    Reset an object's position automatically when it's leaving a physics trigger

    import { Behaviour, Collider, GameObject, Rigidbody, serializeable } from "@needle-tools/engine";
    +import { Vector3 } from "three";
    +
    +export class StartPosition extends Behaviour {
    +
    +    //@nonSerialized
    +    startPosition?: Vector3;
    +
    +    start() {
    +        this.updateStartPosition();
    +    }
    +
    +    updateStartPosition(){
    +        this.startPosition = this.gameObject.position.clone();
    +    }
    +
    +    resetToStart() {
    +        if (!this.startPosition) return;
    +        const rb = GameObject.getComponent(this.gameObject, Rigidbody);
    +        rb?.teleport(this.startPosition);
    +    }
    +}
    +
    +/** Reset to start position when object is exiting the collider */
    +export class AutoReset extends StartPosition {
    +
    +    @serializeable(Collider)
    +    worldCollider?: Collider;
    +
    +    start(){
    +        super.start();
    +        if(!this.worldCollider) console.warn("Missing collider to reset", this);
    +    }
    +    
    +    onTriggerExit(col) {
    +        if(col === this.worldCollider){
    +            this.resetToStart();
    +        }
    +    }
    +}
    +

    Play Audio On Collision

    import { AudioSource, Behaviour, serializeable } from "@needle-tools/engine";
    +
    +export class PlayAudioOnCollision extends Behaviour {
    +    @serializeable(AudioSource)
    +    audioSource?: AudioSource;
    +
    +    onCollisionEnter() {
    +        this.audioSource?.play();
    +    }
    +}
    +

    Set Random Color

    Randomize the color of an object on start. Note that the materials are cloned in the start method

    import { Behaviour, serializeable, Renderer } from "@needle-tools/engine";
    +import { Color } from "three";
    +
    +export class RandomColor extends Behaviour {
    +
    +    @serializeable()
    +    applyOnStart: boolean = true;
    +
    +    start() {
    +        if (this.applyOnStart)
    +            this.applyRandomColor();
    +
    +        // if materials are not cloned and we change the color they might also change on other objects
    +        const cloneMaterials = true;
    +        if (cloneMaterials) {
    +            const renderer = this.gameObject.getComponent(Renderer);
    +            if (!renderer) {
    +                return;
    +            }
    +            for (let i = 0; i < renderer.sharedMaterials.length; i++) {
    +                renderer.sharedMaterials[i] = renderer.sharedMaterials[i].clone();
    +            }
    +        }
    +    }
    +
    +    applyRandomColor() {
    +        const renderer = this.gameObject.getComponent(Renderer);
    +        if (!renderer) {
    +            console.warn("Can not change color: No renderer on " + this.name);
    +            return;
    +        }
    +        for (let i = 0; i < renderer.sharedMaterials.length; i++) {
    +            renderer.sharedMaterials[i].color = new Color(Math.random(), Math.random(), Math.random());
    +        }
    +    }
    +}
    +

    Spawn Objects Over Time

    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);
    +        }
    +    }
    +}
    +
    `,23);function sn(an,tn){const t=p("ExternalLinkIcon"),o=p("stackblitz"),c=p("video-embed");return l(),u("div",null,[d,n("p",null,[n("a",v,[s("Video tutorial: How to write custom components"),a(t)])]),m,b,n("ul",null,[n("li",null,[n("a",h,[s("Visit Samples Website"),a(t)])]),n("li",null,[n("a",y,[s("Download Samples Package"),a(t)])]),n("li",null,[n("a",w,[s("Needle Engine Stackblitz Collection"),a(t)])]),n("li",null,[n("a",g,[s("Needle Engine API"),a(t)])])]),f,a(o,{file:"@code/basic-component.ts"}),s(),x,n("div",_,[q,n("p",null,[s("Find a "),n("a",C,[s("working example in our samples"),a(t)]),s(" to download and try")])]),S,a(o,{file:"@code/component-click.ts"},{default:r(()=>[s(" test ")]),_:1}),E,n("div",j,[B,n("p",null,[s("EventList events are also invoked on the component level. This means you can also subscribe to the event declared above using "),O,s(" as well."),M,s(" This is an experimental feature: please provide feedback in our "),n("a",L,[s("discord"),a(t)])])]),R,n("div",z,[P,n("p",null,[s("Keep in mind that you still have access to all web apis and "),n("a",T,[s("npm"),a(t)]),s(" packages!"),D,s(" That's the beauty of Needle Engine if we're allowed to say this here ๐Ÿ˜Š")])]),A,a(c,{src:"./videos/component-time.mp4",limit_height:""}),H,n("p",null,[s("Assuming you have a custom shader with a property name "),V,s(" that is a float value this is how you would change it from a script."),G,s(" You can find a live "),n("a",F,[s("example to download in our samples"),a(t)])]),I,n("p",null,[s("See "),n("a",U,[s("live example"),a(t)]),s(" on StackBlitz")]),N,n("p",null,[s("Make sure to install "),n("a",W,[Y,a(t)]),s(" in your web project. Then you can add new effects by deriving from "),K,s(".")]),Q,n("p",null,[s("Here is an example that wraps the "),n("a",J,[s("Outline postprocessing effect"),a(t)]),s(". You can expose variables and settings as usual as any effect is also just a component in your three.js scene.")]),X,n("p",null,[s("Make sure to install the mediapipe package. Visit the github link below to see the complete project setup."),$,s(" Try it "),n("a",Z,[s("live here"),a(t)]),s(" - requires a webcam/camera")]),nn])}const pn=i(k,[["render",sn],["__file","scripting-examples.html.vue"]]);export{pn as default}; diff --git a/assets/scripting.html-0734bdfb.js b/assets/scripting.html-0734bdfb.js new file mode 100644 index 000000000..b47278ca2 --- /dev/null +++ b/assets/scripting.html-0734bdfb.js @@ -0,0 +1,157 @@ +import{_ as p,M as i,p as l,q as r,R as e,N as s,V as c,t as n,a1 as a}from"./framework-f6820c83.js";const d={},u=e("p",null,"If you are new to scripting we recommend reading the following guides first:",-1),h={href:"https://engine.needle.tools/docs/api/latest",target:"_blank",rel:"noopener noreferrer"},m=e("hr",null,null,-1),k={href:"https://typescriptlang.org",target:"_blank",rel:"noopener noreferrer"},b={href:"https://javascript.info/",target:"_blank",rel:"noopener noreferrer"},v=e("p",null,"If you want to code-along with the following examples without having to install anything you just click the following link:",-1),g={href:"https://stackblitz.com/fork/github/needle-engine/vite-template?file=src%2Fmain.ts",target:"_blank",rel:"noopener noreferrer"},y=a('

    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.


    When you don't need to write code

    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.

    image
    An example of a Button Click Event that is working out-of-the-box in Needle Engine โ€” no code needed.

    Creating a new component

    Scripts are written in TypeScript (recommended) or JavaScript.
    There are two ways to add custom scripts to your project:

    ',9),f=e("li",null,[e("p",null,[n("Simply add a file with an "),e("code",null,".ts"),n(" or "),e("code",null,".js"),n(" extension inside "),e("code",null,"src/scripts/"),n(" in your generated project directory, for example "),e("code",null,"src/scripts/MyFirstScript.ts")])],-1),w=e("br",null,null,-1),x=e("br",null,null,-1),C=e("code",null,"Create > NPM Definition",-1),_=e("code",null,"Create > TypeScript",-1),j=e("p",null,[n("In both approaches, source directories are watched for changes and C# stub components or Blender panels are regenerated whenever a change is detected."),e("br"),n(" 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),R=e("p",null,[n("You can even have multiple component types inside one file (e.g. you can declare "),e("code",null,"export class MyComponent1"),n(" and "),e("code",null,"export class MyOtherComponent"),n(" in the same Typescript file).")],-1),E=a(`
    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),O={class:"custom-container details"},D=e("summary",null,"Create component with a custom function",-1),T=a(`
    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),q=a('
    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.

    Component architecture

    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.

    Lifecycle methods

    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 nameDescription
    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
    earlyUpdate()First update event
    update()Default update event
    lateUpdate()Called after update
    onBeforeRender()Last update event before render call
    onAfterRender()Called after render event

    Physic event methods

    Method nameDescription
    onCollisionEnter(col : Collision)
    onCollisionStay(col : Collision)
    onCollisionExit(col : Collision)
    onTriggerEnter(col : Collision)
    onTriggerStay(col : Collision)
    onTriggerExit(col : Collision)

    Input event methods

    Method nameDescription
    onPointerEnter(args : PointerEventData)Called when a cursor starts to hover over an object (or any of it's children)
    onPointerMove(args : PointerEventData)Called when a cursor moves over an object (or any of it's children)
    onPointerExit(args : PointerEventData)Called when a cursor exists (stops hovering) an object
    onPointerDown(args : PointerEventData)Called when a cursor is pressed over an object
    onPointerUp(args : PointerEventData)Called when a cursor is released over an object
    onPointerClick(args : PointerEventData)Called when a cursor is clicked over an object

    XR event methods

    requires Needle Engine >= 3.32.0

    Method nameDescription
    supportsXR(mode: XRSessionMode)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
    onBeforeXR(mode: XRSessionMode, init: XRSessionInit)Called right before a XRSession is requested and can be used to modify the XRSessionInit object
    onEnterXR(args: NeedleXREventArgs)Callback when this component joins a xr session (or becomes active in a running XR session)
    onUpdateXR(args: NeedleXREventArgs)Callback when a xr session updates (while it is still active in XR session)
    onLeaveXR(args: NeedleXREventArgs)allback when this component exists a xr session (or when it becomes inactive in a running XR session)
    onControllerAdded(args: NeedleXRControllerEventArgs)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
    onControllerRemoved(args: NeedleXRControllerEventArgs)callback when a controller is removed while in a XR session OR when the component becomes inactive during a running XR session

    Additional XR events

    Method nameDescription
    window.addEventListener("needle-xrsession-start")CustomEvent that is invoked when a XRSession starts. details contains the NeedleXRSession
    window.addEventListener("needle-xrsession-end")CustomEvent that is invoked when a XRSession starts. details contains the NeedleXRSession
    onXRSessionStart(args: { session:NeedleXRSession } )global event hook. To unsubscribe use offXRSessionStart

    Coroutines

    ',17),N={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator",target:"_blank",rel:"noopener noreferrer"},S=e("br",null,null,-1),I=e("code",null,"this.startCoroutine(this.myRoutineName());",-1),z=a(`

    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.

    Special Lifecycle hooks

    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 nameDescription
    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
    onUpdate(cb, options)Called directly after components update
    onBeforeRender(cb, options)called before calling render
    onAfterRender(cb, options)called before calling render
    `,6),U={href:"https://stackblitz.com/edit/needle-engine-lifecycle-hooks?file=src%2Fmain.ts",target:"_blank",rel:"noopener noreferrer"},P=a(`
    // this can be put into e.g. main.ts or a svelte component (similar to onMount)
    +import { onUpdate, onBeforeRender } 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);
    +

    Finding, adding and removing components

    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).

    Example:

    import { Behaviour, GameObject, Renderer } from "@needle-tools/engine";
    +
    +export class MyComponent extends Behaviour {
    +
    +    start() {
    +        const renderer = GameObject.getComponentInParent(this.gameObject, Renderer);
    +        console.log(renderer);
    +    }
    +}
    +

    Some of the available methods:

    Method
    GameObject.instantiate(Object3D, InstantiateOptions)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.getComponentInChildrensame as getComponent but also searches in child objects.
    GameObject.getComponentsInChildrensame as getComponents but also searches in child objects.
    GameObject.getComponentInParentsame as getComponent but also searches in parent objects.
    GameObject.getComponentsInParentsame as getComponents but also searches in parent objects.
    GameObject.findObjectOfTypesearches the whole scene for a type.
    GameObject.findObjectsOfTypesearches the whole scene for all matching types.

    Three.js and the HTML DOM

    `,8),A={href:"https://developer.mozilla.org/en-US/docs/Web/Web_Components",target:"_blank",rel:"noopener noreferrer"},G=e("br",null,null,-1),X=e("code",null,"",-1),B=e("em",null,"index.html",-1),M=e("code",null,"",-1),F=e("code",null,"this.context.domElement",-1),L=a(`

    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.

    Access the scene

    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++) 
    +    const ch = this.gameObject.children[i];
    +

    or you can iterate using the foreach equivalent:

    for(const child of this.gameObject.children) {
    +    console.log(child);
    +}
    +
    `,7),Y={href:"https://threejs.org/docs/#api/en/core/Object3D.traverse",target:"_blank",rel:"noopener noreferrer"},W=e("code",null,"traverse",-1),V=a(`
    this.gameObject.traverse(obj => console.log(obj))
    +
    `,1),J={href:"https://threejs.org/docs/#api/en/core/Object3D.traverseVisible",target:"_blank",rel:"noopener noreferrer"},H=e("code",null,"traverseVisible",-1),K=a(`

    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:

    for(const renderer of this.gameObject.getComponentsInChildren(Renderer))
    +    console.log(renderer);
    +

    For more information about getting components see the next section.

    Time

    Use this.context.time to get access to time data:

    • this.context.time.time is the time since the application started running
    • this.context.time.deltaTime is the time that has passed since the last frame
    • this.context.time.frameCount is the number of frames that have passed since the application started
    • this.context.time.realtimeSinceStartup is the unscaled time since the application has started running

    It is also possible to use this.context.time.timeScale to deliberately slow down time for e.g. slow motion effects.

    Input

    Use this.context.input to poll input state:

    import { Behaviour } from "@needle-tools/engine";
    +export class MyScript extends Behaviour
    +{
    +    update(){
    +        if(this.context.input.getPointerDown(0)){
    +            console.log("POINTER DOWN")
    +        }
    +    }
    +}
    +

    You can also subscribe to events in the InputEvents enum like so:

    import { Behaviour, InputEvents } from "@needle-tools/engine";
    +
    +export class MyScript extends Behaviour
    +{
    +    onEnable(){
    +        this.context.input.addEventListener(InputEvents.PointerDown, this.onPointerDown);
    +    }
    +    onDisable() {
    +        // it is recommended to also unsubscribe from events when your component becomes inactive
    +        this.context.input.removeEventListener(InputEvents.PointerDown, this.onPointerDown);
    +    }
    +
    +    private onPointerDown = (evt) => { console.log(evt); }
    +}
    +
    `,12),Q={href:"https://developer.mozilla.org/en-US/docs/Web/Events",target:"_blank",rel:"noopener noreferrer"},Z=a(`
    window.addEventListener("click", () => { console.log("MOUSE CLICK"); });
    +

    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).

    Physics

    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.

    `,4),$=e("code",null,"this.context.physics.raycastFromRay(your_ray)",-1),nn={href:"https://threejs.org/docs/#api/en/math/Ray",target:"_blank",rel:"noopener noreferrer"},en=a(`

    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();
    +
    `,3),sn={href:"https://stackblitz.com/edit/needle-engine-physics-raycast-example?file=src%2Fmain.ts,package.json,.gitignore",target:"_blank",rel:"noopener noreferrer"},tn=e("h3",{id:"networking",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#networking","aria-hidden":"true"},"#"),n(" Networking")],-1),an=e("code",null,"this.context.connection",-1),on=a(`

    Accessing Needle Engine and components from anywhere

    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:

    <needle-engine loadstart="loadingStarted" progress="loadingProgress" loadfinished="loadingFinished"></needle-engine>
    +
    +<script type="text/javascript">
    +function loadingStarted() { console.log("START") }
    +function loadingProgress() { console.log("LOADING...") }
    +function loadingFinished() { console.log("FINISHED!") }
    +</script>
    +

    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:

    import { NeedleEngine } from "@needle-tools/engine";
    +NeedleEngine.addContextCreatedCallback((args) => {
    +  const context = args.context;
    +  const scene = context.scene;
    +  const myInstance = GameObject.getComponentInChildren(scene, YourComponentType);
    +});
    +

    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.ContextRegisteredCalled when the context is registered to the registry.
    ContextEvent.ContextCreationStartCalled before the first glb is loaded and can be used to initialize the physics engine. Can return a promise
    ContextEvent.ContextCreatedCalled when the context has been created before the first frame
    ContextEvent.ContextDestroyedCalled when the context has been destroyed
    ContextEvent.MissingCameraCalled when the context could not find a camera, currently only called during creation
    ContextEvent.ContextClearingCalled when the context is being cleared: all objects in the scene are being destroyed and internal state is reset
    ContextEvent.ContextClearedCalled after the context has been cleared

    Gizmos

    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.DrawLabelDraws 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.DrawRayTakes an origin and direction in worldspace to draw an infinite ray line
    Gizmos.DrawDirectionTakes a origin and direction to draw a direction in worldspace
    Gizmos.DrawLineTakes two vec3 worldspace points to draw a line
    Gizmos.DrawWireSphereDraws a wireframe sphere in worldspace
    Gizmos.DrawSphereDraws a solid sphere in worldspace
    Gizmos.DrawWireBoxDraws a wireframe box in worldspace
    Gizmos.DrawWireBox3Draws a wireframe box3
    Gizmos.DrawArrowDraws an arrow taking two points in worldspace

    Serialization / Components in glTF files

    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.

    Loading Scenes

    `,21),cn={href:"https://docs.unity3d.com/Packages/com.unity.addressables@latest/manual/AddressableAssetsGettingStarted.html",target:"_blank",rel:"noopener noreferrer"},pn=e("code",null,"AssetReferences",-1),ln=a(`

    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);function rn(dn,un){const o=i("RouterLink"),t=i("ExternalLinkIcon");return l(),r("div",null,[u,e("ul",null,[e("li",null,[s(o,{to:"/getting-started/typescript-essentials.html"},{default:c(()=>[n("Typescript Essentials")]),_:1})]),e("li",null,[s(o,{to:"/getting-started/for-unity-developers.html"},{default:c(()=>[n("Needle Engine for Unity Developers")]),_:1})])]),e("p",null,[n("If you know what you're doing, feel free to jump right into the "),e("a",h,[n("Needle Engine API documentation"),s(t)]),n(".")]),m,e("p",null,[n("Runtime code for Needle Engine is written in "),e("a",k,[n("TypeScript"),s(t)]),n(" (recommended) or "),e("a",b,[n("JavaScript"),s(t)]),n(". 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.")]),e("p",null,[n("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 "),s(o,{to:"/component-reference.html#unity-components"},{default:c(()=>[n("included in Needle Engine")]),_:1}),n(".")]),v,e("ul",null,[e("li",null,[e("a",g,[n("Create virtual workspace to code along"),s(t)]),n(".")])]),y,e("ul",null,[f,e("li",null,[e("p",null,[n("Unity specific:"),w,n(" 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."),x,n(" In Unity you can create NpmDef files via "),C,n(" and then add TypeScript files by right-clicking an NpmDef file and selecting "),_,n(". Please see "),s(o,{to:"/project-structure.html#npm-definition-files"},{default:c(()=>[n("this chapter")]),_:1}),n(" for more information.")])])]),j,R,e("p",null,[n("If you are new to writing Javascript or Typescript we recommend reading the "),s(o,{to:"/getting-started/typescript-essentials.html"},{default:c(()=>[n("Typescript Essentials Guide")]),_:1}),n(" guide first before continuing with this guide.")]),E,e("details",O,[D,e("p",null,[n("Refer to the "),s(o,{to:"/getting-started/typescript-essentials.html"},{default:c(()=>[n("Typescript Essentials Guide")]),_:1}),n(" to learn more about the syntax and language.")]),T]),q,e("p",null,[n("Coroutines can be declared using the "),e("a",N,[n("JavaScript Generator Syntax"),s(t)]),n("."),S,n(" To start a coroutine, call "),I]),z,e("p",null,[n("For example ("),e("a",U,[n("See example on stackblitz"),s(t)]),n(")")]),P,e("p",null,[n("The context refers to the runtime inside a "),e("a",A,[n("web component"),s(t)]),n("."),G,n(" The three.js scene lives inside a custom HTML component called "),X,n(" (see the "),B,n(" in your project). You can access the "),M,n(" web component using "),F,n(".")]),L,e("p",null,[n("You can also use three.js specific methods to quickly iterate all objects recursively using the "),e("a",Y,[W,s(t)]),n(" method:")]),V,e("p",null,[n("or to just traverse visible objects use "),e("a",J,[H,s(t)]),n(" instead.")]),K,e("p",null,[n("If you want to handle inputs yourself you can also subscribe to "),e("a",Q,[n("all events the browser provides"),s(t)]),n(" (there are a ton). For example to subscribe to the browsers click event you can write:")]),Z,e("p",null,[n("Use "),$,n(" to perform a raycast using a "),e("a",nn,[n("three.js ray"),s(t)])]),en,e("p",null,[n("Here is a editable "),e("a",sn,[n("example for physics raycast"),s(t)])]),tn,e("p",null,[n("Networking methods can be accessed via "),an,n(". Please refer to the "),s(o,{to:"/networking.html"},{default:c(()=>[n("networking docs")]),_:1}),n(" for further information.")]),on,e("p",null,[n("Referenced Prefabs, SceneAssets and "),e("a",cn,[pn,s(t)]),n(" in Unity will automatically be exported as glTF files (please refer to the "),s(o,{to:"/export.html"},{default:c(()=>[n("Export Prefabs")]),_:1}),n(" documentation).")]),ln])}const mn=p(d,[["render",rn],["__file","scripting.html.vue"]]);export{mn as default}; diff --git a/assets/scripting.html-2ce40752.js b/assets/scripting.html-2ce40752.js new file mode 100644 index 000000000..034e3ee1b --- /dev/null +++ b/assets/scripting.html-2ce40752.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-fab6318a","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":1725399379000},"filePathRelative":"scripting.md"}`);export{e as data}; diff --git a/assets/showcase-bike.html-aed7c46a.js b/assets/showcase-bike.html-aed7c46a.js new file mode 100644 index 000000000..13508d26c --- /dev/null +++ b/assets/showcase-bike.html-aed7c46a.js @@ -0,0 +1 @@ +import{_ as r,M as t,p as c,q as l,N as o,R as e,t as n}from"./framework-f6820c83.js";const i={},_=e("h3",{id:"live",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#live","aria-hidden":"true"},"#"),n(" Live")],-1),d={href:"https://bike.needle.tools",target:"_blank",rel:"noopener noreferrer"};function h(p,m){const s=t("sample"),a=t("ExternalLinkIcon");return c(),l("div",null,[_,o(s,{src:"https://bike.needle.tools"}),e("p",null,[e("a",d,[n("Visit website"),o(a)])])])}const k=r(i,[["render",h],["__file","showcase-bike.html.vue"]]);export{k as default}; diff --git a/assets/showcase-bike.html-baf689ea.js b/assets/showcase-bike.html-baf689ea.js new file mode 100644 index 000000000..91cc65171 --- /dev/null +++ b/assets/showcase-bike.html-baf689ea.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-48ca6631","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{e as data}; diff --git a/assets/showcase-castle.html-86fe547b.js b/assets/showcase-castle.html-86fe547b.js new file mode 100644 index 000000000..34d7f4d50 --- /dev/null +++ b/assets/showcase-castle.html-86fe547b.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-9d01ad8c","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{e as data}; diff --git a/assets/showcase-castle.html-cdb5db57.js b/assets/showcase-castle.html-cdb5db57.js new file mode 100644 index 000000000..da6ed1c60 --- /dev/null +++ b/assets/showcase-castle.html-cdb5db57.js @@ -0,0 +1 @@ +import{_ as r,M as t,p as i,q as l,N as n,R as e,t as o}from"./framework-f6820c83.js";const c={},d=e("h3",{id:"live",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#live","aria-hidden":"true"},"#"),o(" Live")],-1),h={href:"https://castle.needle.tools",target:"_blank",rel:"noopener noreferrer"},u=e("h3",{id:"how-to-play",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#how-to-play","aria-hidden":"true"},"#"),o(" How To Play")],-1),_=e("p",null,"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!",-1),p=e("p",null,`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.`,-1),m=e("h3",{id:"info",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#info","aria-hidden":"true"},"#"),o(" Info")],-1),f=e("p",null,"This page was authored in Unity and exported to three.js using tools and technologies by ๐ŸŒต needle.",-1),y=e("p",null,"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.",-1);function v(b,g){const a=t("sample"),s=t("ExternalLinkIcon");return i(),l("div",null,[d,n(a,{src:"https://castle.needle.tools"}),e("p",null,[e("a",h,[o("Visit website"),n(s)])]),u,_,p,m,f,y])}const x=r(c,[["render",v],["__file","showcase-castle.html.vue"]]);export{x as default}; diff --git a/assets/showcase-mercedes-benz.html-555f99b8.js b/assets/showcase-mercedes-benz.html-555f99b8.js new file mode 100644 index 000000000..abbc562a3 --- /dev/null +++ b/assets/showcase-mercedes-benz.html-555f99b8.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-aa56fb8c","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":1668622149000},"filePathRelative":"showcase-mercedes-benz.md"}`);export{e as data}; diff --git a/assets/showcase-mercedes-benz.html-7eabd1f0.js b/assets/showcase-mercedes-benz.html-7eabd1f0.js new file mode 100644 index 000000000..8bb5c0faf --- /dev/null +++ b/assets/showcase-mercedes-benz.html-7eabd1f0.js @@ -0,0 +1,30 @@ +import{_ as i,M as n,p,q as c,R as s,t as e,N as a,a1 as r}from"./framework-f6820c83.js";const l="/docs/showcase-mercedes/1_skybox.png",d="/docs/showcase-mercedes/2_paintjob_simple.jpg",h="/docs/showcase-mercedes/3_SpecularHighlights_off.jpg",u="/docs/showcase-mercedes/4_SpecularHighlights_on.jpg",m="/docs/showcase-mercedes/5_NoBackground.jpg",k="/docs/showcase-mercedes/6_MapBackground.png",g="/docs/showcase-mercedes/7_EnvShaderGraph.jpg",w="/docs/showcase-mercedes/8_Gradiant.png",b="/docs/showcase-mercedes/9_Rotator.png",v="/docs/showcase-mercedes/10_WheelsAndGrid.png",f="/docs/showcase-mercedes/11_GridShader.jpg",y="/docs/showcase-mercedes/12_WheelWithText.png",_="/docs/showcase-mercedes/13_WheelShader.jpg",x="/docs/showcase-mercedes/14_RearUI.jpg",j={},W=s("h2",{id:"about",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#about","aria-hidden":"true"},"#"),e(" About")],-1),T={href:"https://www.ishowroom.cz/home/",target:"_blank",rel:"noopener noreferrer"},R=r('

    Context

    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 ๐Ÿ’š

    Lighting

    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:

    Skybox

    Which looks like this on the paint job:

    Paintjob

    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:

    Specular off

    But with the added directional lights it added a better dynamic. The effect could be deepened with higher intensity:

    Specular on

    Background

    The scene now looks like this:

    No background

    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.

    With background

    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:

    Evironemnt shader

    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! ๐ŸŒต

    Gradiant

    Car fake movement

    The scene right now is static since nothing moves. We can negate that by adding a fake feeling of motion. Let's start by adding motion to the wheels.

    With a simple component called Rotator, we define an axis and speed along it.

    Rotator

    import { Behaviour, serializable } from "@needle-tools/engine";
    +
    +export enum RotationAxis {
    +    X, Y, Z
    +}
    +
    +export class Rotator extends Behaviour {
    +    //@type RotationAxis
    +    @serializable()
    +    axis : RotationAxis = RotationAxis.X;
    +
    +    @serializable()
    +    speed : number = 1;
    +
    +    update() {
    +        const angle = this.speed * this.context.time.deltaTime;
    +        switch(this.axis) {
    +            case RotationAxis.X:
    +                this.gameObject.rotateX(angle);
    +                break;
    +            case RotationAxis.Y:
    +                this.gameObject.rotateY(angle);
    +                break;
    +            case RotationAxis.Z:
    +                this.gameObject.rotateZ(angle);
    +                break;
    +        }
    +    }
    +}
    +

    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:

    Motion

    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.

    Grid

    Extra elements

    This tech demo takes it's goal to showcase the car's capabilities.

    Let's start by highlighting the wheels.

    Wheel highlight

    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.

    Wheel shader

    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.

    Rear UI

    Wrap up

    By using the Unity's timeline we can control when the wheel dashes and text will be shown. This is complemented by the camera animation.

    Conclusion

    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);function S(A,B){const t=n("ExternalLinkIcon"),o=n("sample");return p(),c("div",null,[W,s("p",null,[e("Hello, my name is Kryลกtof and i did a research project about Needle. At "),s("a",T,[e("our company"),a(t)]),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.")]),a(o,{src:"https://engine.needle.tools/demos/mercedes-benz-demo/"}),R])}const I=i(j,[["render",S],["__file","showcase-mercedes-benz.html.vue"]]);export{I as default}; diff --git a/assets/showcase-monsterhands.html-5723a356.js b/assets/showcase-monsterhands.html-5723a356.js new file mode 100644 index 000000000..e14bf4524 --- /dev/null +++ b/assets/showcase-monsterhands.html-5723a356.js @@ -0,0 +1 @@ +import{_ as r,M as n,p as c,q as l,N as t,R as e,t as s}from"./framework-f6820c83.js";const _={},d=e("h3",{id:"live",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#live","aria-hidden":"true"},"#"),s(" Live")],-1),h={href:"https://monster-hands.needle.tools/",target:"_blank",rel:"noopener noreferrer"};function i(m,p){const o=n("sample"),a=n("ExternalLinkIcon");return c(),l("div",null,[d,t(o,{src:"https://monster-hands.needle.tools/"}),e("p",null,[e("a",h,[s("Visit website"),t(a)])])])}const u=r(_,[["render",i],["__file","showcase-monsterhands.html.vue"]]);export{u as default}; diff --git a/assets/showcase-monsterhands.html-bac5de01.js b/assets/showcase-monsterhands.html-bac5de01.js new file mode 100644 index 000000000..c61104779 --- /dev/null +++ b/assets/showcase-monsterhands.html-bac5de01.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-da75c730","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{e as data}; diff --git a/assets/showcase-towerdefence.html-5bc7dd74.js b/assets/showcase-towerdefence.html-5bc7dd74.js new file mode 100644 index 000000000..f0be5c1dd --- /dev/null +++ b/assets/showcase-towerdefence.html-5bc7dd74.js @@ -0,0 +1 @@ +import{_ as a,M as t,p as c,q as l,N as n,R as e,t as o}from"./framework-f6820c83.js";const i={},d=e("h3",{id:"live",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#live","aria-hidden":"true"},"#"),o(" Live")],-1),_={href:"https://willitaugment.itch.io/tumbleweed-defender",target:"_blank",rel:"noopener noreferrer"};function h(m,p){const s=t("sample"),r=t("ExternalLinkIcon");return c(),l("div",null,[d,n(s,{src:"https://v6p9d9t4.ssl.hwcdn.net/html/7746989/index.html"}),e("p",null,[e("a",_,[o("Visit website"),n(r)])])])}const u=a(i,[["render",h],["__file","showcase-towerdefence.html.vue"]]);export{u as default}; diff --git a/assets/showcase-towerdefence.html-c76eabe0.js b/assets/showcase-towerdefence.html-c76eabe0.js new file mode 100644 index 000000000..b69a91c77 --- /dev/null +++ b/assets/showcase-towerdefence.html-c76eabe0.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3d5fe3ab","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{e as data}; diff --git a/assets/showcase-website.html-b68487d3.js b/assets/showcase-website.html-b68487d3.js new file mode 100644 index 000000000..939c8b7f7 --- /dev/null +++ b/assets/showcase-website.html-b68487d3.js @@ -0,0 +1 @@ +import{_ as r,M as t,p as c,q as l,N as o,R as e,t as s}from"./framework-f6820c83.js";const i={},_=e("h3",{id:"live",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#live","aria-hidden":"true"},"#"),s(" Live")],-1),d={href:"https://needle.tools",target:"_blank",rel:"noopener noreferrer"};function h(p,m){const n=t("sample"),a=t("ExternalLinkIcon");return c(),l("div",null,[_,o(n,{src:"https://needle.tools"}),e("p",null,[e("a",d,[s("Visit website"),o(a)])])])}const u=r(i,[["render",h],["__file","showcase-website.html.vue"]]);export{u as default}; diff --git a/assets/showcase-website.html-fad31bcf.js b/assets/showcase-website.html-fad31bcf.js new file mode 100644 index 000000000..bebac527d --- /dev/null +++ b/assets/showcase-website.html-fad31bcf.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-499adc36","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{e as data}; diff --git a/assets/showcase-zenrepublic.html-7fc38408.js b/assets/showcase-zenrepublic.html-7fc38408.js new file mode 100644 index 000000000..085162da9 --- /dev/null +++ b/assets/showcase-zenrepublic.html-7fc38408.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7c306381","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{e as data}; diff --git a/assets/showcase-zenrepublic.html-df85526d.js b/assets/showcase-zenrepublic.html-df85526d.js new file mode 100644 index 000000000..1869d486b --- /dev/null +++ b/assets/showcase-zenrepublic.html-df85526d.js @@ -0,0 +1 @@ +import{_ as c,M as n,p as o,q as l,N as t,R as e,t as r}from"./framework-f6820c83.js";const i={},_=e("h3",{id:"live",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#live","aria-hidden":"true"},"#"),r(" Live")],-1),p={href:"https://zenrepublic.space/?realm=3",target:"_blank",rel:"noopener noreferrer"};function h(d,m){const a=n("sample"),s=n("ExternalLinkIcon");return o(),l("div",null,[_,t(a,{src:"https://zenrepublic.space/?realm=3"}),e("p",null,[e("a",p,[r("Visit website"),t(s)])])])}const f=c(i,[["render",h],["__file","showcase-zenrepublic.html.vue"]]);export{f as default}; diff --git a/assets/stackblitz-b078650b.js b/assets/stackblitz-b078650b.js new file mode 100644 index 000000000..2bb2f2771 --- /dev/null +++ b/assets/stackblitz-b078650b.js @@ -0,0 +1,33 @@ +import{_ as h,p as b,q as f,R as g,s as w}from"./framework-f6820c83.js";function x(e){return e.startsWith("@code")?e.replace(/^@code/,"./code-samples/"):e}async function y(e,n,t,o){let s=await(await fetch(n)).text();o&&(s=o(s)),t[e]=s}async function c(e,n,t,o,a){e.push(y(n,t,o,a))}async function k(e,n){const t=[];c(t,"package.json","https://raw.githubusercontent.com/needle-engine/vite-template/main/package.json",e),c(t,"package-lock.json","https://raw.githubusercontent.com/needle-engine/vite-template/main/package-lock.json",e),c(t,"src/styles/style.css","https://raw.githubusercontent.com/needle-engine/vite-template/main/src/styles/style.css",e),c(t,"vite.config.js","https://raw.githubusercontent.com/needle-engine/vite-template/main/vite.config.js",e,o=>o.replace("needlePlugins(command, needleConfig)","needlePlugins(command, needleConfig, { noPoster: true })")),c(t,"tsconfig.json","https://raw.githubusercontent.com/needle-engine/vite-template/main/tsconfig.json",e),c(t,"index.html","https://raw.githubusercontent.com/needle-engine/vite-template/main/index.html",e,o=>o.replace(/\/,``)),await Promise.all(t)}async function j(e,n){var p,u;let t=`// Generated via ${window.location.href} at ${new Date().toISOString()} +import { NeedleEngine, RemoteSkybox, GameObject, ObjectRaycaster } from '@needle-tools/engine'; +import * as THREE from 'three'; +`;const o=x(n),a=window.location,s=a.pathname.split("/").slice(0,-1).join("/"),i=`${a.origin}${s}/${o}`,r=await(await fetch(i)).text(),d="src/main.ts",l=r.match(/export class\s+?(?.+?)\s+extends Behaviour/),m=(u=(p=l==null?void 0:l.groups)==null?void 0:p.component_name)==null?void 0:u.trim();return console.log(m),t+=` +// SAMPLE SCRIPT START +`+r+` +// SAMPLE SCRIPT END +`,t+=` + +NeedleEngine.addContextCreatedCallback((args) => { + const context = args.context; + const scene = context.scene; + + const grid = new THREE.GridHelper(); + scene.add(grid); + + const geometry = new THREE.BoxGeometry(1, 1, 1); + const material = new THREE.MeshStandardMaterial({ color: 0xdddddd }); + const cube = new THREE.Mesh(geometry, material); + cube.name = "Cube"; + cube.position.y += 0.5; + scene.add(cube); + GameObject.addComponent(cube, new ${m}()); + ${r.includes("IPointerClickHandler")?"GameObject.addNewComponent(cube, ObjectRaycaster)":""} + + const remoteSkybox = new RemoteSkybox(); + remoteSkybox.background = false; + remoteSkybox.url = + 'https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/cyclorama_hard_light_1k.hdr'; + GameObject.addComponent(grid, remoteSkybox); +}); +`,e[d]=t,d}const _={props:{file:String},methods:{async openProject(){const e={};await k(e,"https://github.com/needle-engine/vite-template/raw/main/assets/basic.glb");const n=await j(e,this.file),t=n.split("/").pop();StackBlitzSDK.openProject({files:{...e,"index.ts":`import './src/main'; +import '${n}';`},template:"node",title:`${t}`,description:"This is a generated project via https://docs.needle.engine. Please note that this feature is experimental(!) and the project might not work as expected."},{newWindow:!0,openFile:n})}}},P={class:"code"};function S(e,n,t,o,a,s){return b(),f("div",null,[g("button",{onClick:n[0]||(n[0]=(...i)=>s.openProject&&s.openProject(...i)),class:"plausible-event-name=Click+Create+Stackblitz+Project"}," Open in StackBlitz "),g("div",P,[w(e.$slots,"default")])])}const E=h(_,[["render",S],["__file","stackblitz.vue"]]);export{E as default}; diff --git a/assets/style-2ab1a774.css b/assets/style-2ab1a774.css new file mode 100644 index 000000000..f8f92b55d --- /dev/null +++ b/assets/style-2ab1a774.css @@ -0,0 +1 @@ +@import"https://fonts.googleapis.com/css2?family=Nunito+Sans:opsz,wght@6..12,300..900&display=swap";:root{--back-to-top-z-index: 5;--back-to-top-color: #3eaf7c;--back-to-top-color-hover: #71cda3}.back-to-top{cursor:pointer;position:fixed;bottom:2rem;right:2.5rem;width:2rem;height:1.2rem;background-color:var(--back-to-top-color);-webkit-mask:url(/docs/assets/back-to-top-8efcbe56.svg) no-repeat;mask:url(/docs/assets/back-to-top-8efcbe56.svg) no-repeat;z-index:var(--back-to-top-z-index)}.back-to-top:hover{background-color:var(--back-to-top-color-hover)}@media (max-width: 959px){.back-to-top{display:none}}@media print{.back-to-top{display:none}}.back-to-top-enter-active,.back-to-top-leave-active{transition:opacity .3s}.back-to-top-enter-from,.back-to-top-leave-to{opacity:0}:root{--external-link-icon-color: #aaa}.external-link-icon{position:relative;display:inline-block;color:var(--external-link-icon-color);vertical-align:middle;top:-1px}@media print{.external-link-icon{display:none}}.external-link-icon-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}:root{--medium-zoom-z-index: 100;--medium-zoom-bg-color: #ffffff;--medium-zoom-opacity: 1}.medium-zoom-overlay{background-color:var(--medium-zoom-bg-color)!important;z-index:var(--medium-zoom-z-index)}.medium-zoom-overlay~img{z-index:calc(var(--medium-zoom-z-index) + 1)}.medium-zoom--opened .medium-zoom-overlay{opacity:var(--medium-zoom-opacity)}:root{--nprogress-color: #29d;--nprogress-z-index: 1031}#nprogress{pointer-events:none}#nprogress .bar{background:var(--nprogress-color);position:fixed;z-index:var(--nprogress-z-index);top:0;left:0;width:100%;height:2px}:root{--c-brand: #3eaf7c;--c-brand-light: #4abf8a;--c-bg: #ffffff;--c-bg-light: #f3f4f5;--c-bg-lighter: #eeeeee;--c-bg-dark: #ebebec;--c-bg-darker: #e6e6e6;--c-bg-navbar: var(--c-bg);--c-bg-sidebar: var(--c-bg);--c-bg-arrow: #cccccc;--c-text: #2c3e50;--c-text-accent: var(--c-brand);--c-text-light: #3a5169;--c-text-lighter: #4e6e8e;--c-text-lightest: #6a8bad;--c-text-quote: #999999;--c-border: #eaecef;--c-border-dark: #dfe2e5;--c-tip: #42b983;--c-tip-bg: var(--c-bg-light);--c-tip-title: var(--c-text);--c-tip-text: var(--c-text);--c-tip-text-accent: var(--c-text-accent);--c-warning: #ffc310;--c-warning-bg: #fffae3;--c-warning-bg-light: #fff3ba;--c-warning-bg-lighter: #fff0b0;--c-warning-border-dark: #f7dc91;--c-warning-details-bg: #fff5ca;--c-warning-title: #f1b300;--c-warning-text: #746000;--c-warning-text-accent: #edb100;--c-warning-text-light: #c1971c;--c-warning-text-quote: #ccab49;--c-danger: #f11e37;--c-danger-bg: #ffe0e0;--c-danger-bg-light: #ffcfde;--c-danger-bg-lighter: #ffc9c9;--c-danger-border-dark: #f1abab;--c-danger-details-bg: #ffd4d4;--c-danger-title: #ed1e2c;--c-danger-text: #660000;--c-danger-text-accent: #bd1a1a;--c-danger-text-light: #b5474d;--c-danger-text-quote: #c15b5b;--c-details-bg: #eeeeee;--c-badge-tip: var(--c-tip);--c-badge-warning: #ecc808;--c-badge-warning-text: var(--c-bg);--c-badge-danger: #dc2626;--c-badge-danger-text: var(--c-bg);--t-color: .3s ease;--t-transform: .3s ease;--code-bg-color: #282c34;--code-hl-bg-color: rgba(0, 0, 0, .66);--code-ln-color: #9e9e9e;--code-ln-wrapper-width: 3.5rem;--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;--font-family-code: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;--navbar-height: 3.6rem;--navbar-padding-v: .7rem;--navbar-padding-h: 1.5rem;--sidebar-width: 20rem;--sidebar-width-mobile: calc(var(--sidebar-width) * .82);--content-width: 740px;--homepage-width: 960px}.back-to-top{--back-to-top-color: var(--c-brand);--back-to-top-color-hover: var(--c-brand-light)}.DocSearch{--docsearch-primary-color: var(--c-brand);--docsearch-text-color: var(--c-text);--docsearch-highlight-color: var(--c-brand);--docsearch-muted-color: var(--c-text-quote);--docsearch-container-background: rgba(9, 10, 17, .8);--docsearch-modal-background: var(--c-bg-light);--docsearch-searchbox-background: var(--c-bg-lighter);--docsearch-searchbox-focus-background: var(--c-bg);--docsearch-searchbox-shadow: inset 0 0 0 2px var(--c-brand);--docsearch-hit-color: var(--c-text-light);--docsearch-hit-active-color: var(--c-bg);--docsearch-hit-background: var(--c-bg);--docsearch-hit-shadow: 0 1px 3px 0 var(--c-border-dark);--docsearch-footer-background: var(--c-bg)}.external-link-icon{--external-link-icon-color: var(--c-text-quote)}.medium-zoom-overlay{--medium-zoom-bg-color: var(--c-bg)}#nprogress{--nprogress-color: var(--c-brand)}.pwa-popup{--pwa-popup-text-color: var(--c-text);--pwa-popup-bg-color: var(--c-bg);--pwa-popup-border-color: var(--c-brand);--pwa-popup-shadow: 0 4px 16px var(--c-brand);--pwa-popup-btn-text-color: var(--c-bg);--pwa-popup-btn-bg-color: var(--c-brand);--pwa-popup-btn-hover-bg-color: var(--c-brand-light)}.search-box{--search-bg-color: var(--c-bg);--search-accent-color: var(--c-brand);--search-text-color: var(--c-text);--search-border-color: var(--c-border);--search-item-text-color: var(--c-text-lighter);--search-item-focus-bg-color: var(--c-bg-light)}html.dark{--c-brand: #3aa675;--c-brand-light: #349469;--c-bg: #22272e;--c-bg-light: #2b313a;--c-bg-lighter: #262c34;--c-bg-dark: #343b44;--c-bg-darker: #37404c;--c-text: #adbac7;--c-text-light: #96a7b7;--c-text-lighter: #8b9eb0;--c-text-lightest: #8094a8;--c-border: #3e4c5a;--c-border-dark: #34404c;--c-tip: #318a62;--c-warning: #e0ad15;--c-warning-bg: #2d2f2d;--c-warning-bg-light: #423e2a;--c-warning-bg-lighter: #44442f;--c-warning-border-dark: #957c35;--c-warning-details-bg: #39392d;--c-warning-title: #fdca31;--c-warning-text: #d8d96d;--c-warning-text-accent: #ffbf00;--c-warning-text-light: #ddb84b;--c-warning-text-quote: #ccab49;--c-danger: #fc1e38;--c-danger-bg: #39232c;--c-danger-bg-light: #4b2b35;--c-danger-bg-lighter: #553040;--c-danger-border-dark: #a25151;--c-danger-details-bg: #482936;--c-danger-title: #fc2d3b;--c-danger-text: #ea9ca0;--c-danger-text-accent: #fd3636;--c-danger-text-light: #d9777c;--c-danger-text-quote: #d56b6b;--c-details-bg: #323843;--c-badge-warning: var(--c-warning);--c-badge-warning-text: #3c2e05;--c-badge-danger: var(--c-danger);--c-badge-danger-text: #401416;--code-hl-bg-color: #363b46}html.dark .DocSearch{--docsearch-logo-color: var(--c-text);--docsearch-modal-shadow: inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309;--docsearch-key-shadow: inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 0 2px 2px 0 rgba(3, 4, 9, .3);--docsearch-key-gradient: linear-gradient(-225deg, #444950, #1c1e21);--docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, .5), 0 -4px 8px 0 rgba(0, 0, 0, .2)}html,body{padding:0;margin:0;background-color:var(--c-bg);transition:background-color var(--t-color)}html.dark{color-scheme:dark}html{font-size:16px}body{font-family:var(--font-family);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:1rem;color:var(--c-text)}a{font-weight:500;color:var(--c-text-accent);text-decoration:none;overflow-wrap:break-word}p a code{font-weight:400;color:var(--c-text-accent)}kbd{font-family:var(--font-family-code);color:var(--c-text);background:var(--c-bg-lighter);border:solid .15rem var(--c-border-dark);border-bottom:solid .25rem var(--c-border-dark);border-radius:.15rem;padding:0 .15em}code{font-family:var(--font-family-code);color:var(--c-text-lighter);padding:.25rem .5rem;margin:0;font-size:.85em;background-color:var(--c-bg-light);border-radius:3px;overflow-wrap:break-word;transition:background-color var(--t-color)}blockquote{font-size:1rem;color:var(--c-text-quote);border-left:.2rem solid var(--c-border-dark);margin:1rem 0;padding:.25rem 0 .25rem 1rem;overflow-wrap:break-word}blockquote>p{margin:0}ul,ol{padding-left:1.2em}strong{font-weight:600}h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25;overflow-wrap:break-word}h1:focus-visible,h2:focus-visible,h3:focus-visible,h4:focus-visible,h5:focus-visible,h6:focus-visible{outline:none}h1:hover .header-anchor,h2:hover .header-anchor,h3:hover .header-anchor,h4:hover .header-anchor,h5:hover .header-anchor,h6:hover .header-anchor{opacity:1}h1{font-size:2.2rem}h2{font-size:1.65rem;padding-bottom:.3rem;border-bottom:1px solid var(--c-border);transition:border-color var(--t-color)}h3{font-size:1.35rem}h4{font-size:1.15rem}h5{font-size:1.05rem}h6{font-size:1rem}a.header-anchor{font-size:.85em;float:left;margin-left:-.87em;padding-right:.23em;margin-top:.125em;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media print{a.header-anchor{display:none}}a.header-anchor:hover{text-decoration:none}a.header-anchor:focus-visible{opacity:1}@media print{a[href^="http://"]:after,a[href^="https://"]:after{content:" (" attr(href) ") "}}p,ul,ol{line-height:1.7;overflow-wrap:break-word}hr{border:0;border-top:1px solid var(--c-border)}table{border-collapse:collapse;margin:1rem 0;display:block;overflow-x:auto;transition:border-color var(--t-color)}tr{border-top:1px solid var(--c-border-dark);transition:border-color var(--t-color)}tr:nth-child(2n){background-color:var(--c-bg-light);transition:background-color var(--t-color)}tr:nth-child(2n) code{background-color:var(--c-bg-dark)}th,td{padding:.6em 1em;border:1px solid var(--c-border-dark);transition:border-color var(--t-color)}.arrow{display:inline-block;width:0;height:0}.arrow.up{border-left:4px solid transparent;border-right:4px solid transparent;border-bottom:6px solid var(--c-bg-arrow)}.arrow.down{border-left:4px solid transparent;border-right:4px solid transparent;border-top:6px solid var(--c-bg-arrow)}.arrow.right{border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:6px solid var(--c-bg-arrow)}.arrow.left{border-top:4px solid transparent;border-bottom:4px solid transparent;border-right:6px solid var(--c-bg-arrow)}.badge{display:inline-block;font-size:14px;font-weight:600;height:18px;line-height:18px;border-radius:3px;padding:0 6px;color:var(--c-bg);vertical-align:top;transition:color var(--t-color),background-color var(--t-color)}.badge.tip{background-color:var(--c-badge-tip)}.badge.warning{background-color:var(--c-badge-warning);color:var(--c-badge-warning-text)}.badge.danger{background-color:var(--c-badge-danger);color:var(--c-badge-danger-text)}.badge+.badge{margin-left:5px}code[class*=language-],pre[class*=language-]{color:#ccc;background:none;font-family:var(--font-family-code);font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata{color:#999}.token.punctuation{color:#ccc}.token.tag,.token.attr-name,.token.namespace,.token.deleted{color:#ec5975}.token.function-name{color:#6196cc}.token.boolean,.token.number,.token.function{color:#f08d49}.token.property,.token.class-name,.token.constant,.token.symbol{color:#f8c555}.token.selector,.token.important,.token.atrule,.token.keyword,.token.builtin{color:#cc99cd}.token.string,.token.char,.token.attr-value,.token.regex,.token.variable{color:#7ec699}.token.operator,.token.entity,.token.url{color:#67cdcc}.token.important,.token.bold{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:#3eaf7c}.theme-default-content pre,.theme-default-content pre[class*=language-]{line-height:1.375;padding:1.3rem 1.5rem;margin:.85rem 0;border-radius:6px;overflow:auto}.theme-default-content pre code,.theme-default-content pre[class*=language-] code{color:#fff;padding:0;background-color:transparent!important;border-radius:0;overflow-wrap:unset;-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}.theme-default-content .line-number{font-family:var(--font-family-code)}div[class*=language-]{position:relative;background-color:var(--code-bg-color);border-radius:6px}div[class*=language-]:before{content:attr(data-ext);position:absolute;z-index:3;top:.8em;right:1em;font-size:.75rem;color:var(--code-ln-color)}div[class*=language-] pre,div[class*=language-] pre[class*=language-]{background:transparent!important;position:relative;z-index:1}div[class*=language-] .highlight-lines{-webkit-user-select:none;-moz-user-select:none;user-select:none;padding-top:1.3rem;position:absolute;top:0;left:0;width:100%;line-height:1.375}div[class*=language-] .highlight-lines .highlight-line{background-color:var(--code-hl-bg-color)}div[class*=language-]:not(.line-numbers-mode) .line-numbers{display:none}div[class*=language-].line-numbers-mode .highlight-lines .highlight-line{position:relative}div[class*=language-].line-numbers-mode .highlight-lines .highlight-line:before{content:" ";position:absolute;z-index:2;left:0;top:0;display:block;width:var(--code-ln-wrapper-width);height:100%}div[class*=language-].line-numbers-mode pre{margin-left:var(--code-ln-wrapper-width);padding-left:1rem;vertical-align:middle}div[class*=language-].line-numbers-mode .line-numbers{position:absolute;top:0;width:var(--code-ln-wrapper-width);text-align:center;color:var(--code-ln-color);padding-top:1.25rem;line-height:1.375;counter-reset:line-number}div[class*=language-].line-numbers-mode .line-numbers .line-number{position:relative;z-index:3;-webkit-user-select:none;-moz-user-select:none;user-select:none;height:1.375em}div[class*=language-].line-numbers-mode .line-numbers .line-number:before{counter-increment:line-number;content:counter(line-number);font-size:.85em}div[class*=language-].line-numbers-mode:after{content:"";position:absolute;top:0;left:0;width:var(--code-ln-wrapper-width);height:100%;border-radius:6px 0 0 6px;border-right:1px solid var(--code-hl-bg-color)}@media (max-width: 419px){.theme-default-content div[class*=language-]{margin:.85rem -1.5rem;border-radius:0}}.code-group__nav{margin-top:.85rem;margin-bottom:calc(-1.7rem - 6px);padding-bottom:calc(1.7rem - 6px);padding-left:10px;padding-top:10px;border-top-left-radius:6px;border-top-right-radius:6px;background-color:var(--code-bg-color)}.code-group__ul{margin:auto 0;padding-left:0;display:inline-flex;list-style:none}.code-group__nav-tab{border:0;padding:5px;cursor:pointer;background-color:transparent;font-size:.85em;line-height:1.4;color:#ffffffe6;font-weight:600}.code-group__nav-tab:focus{outline:none}.code-group__nav-tab:focus-visible{outline:1px solid rgba(255,255,255,.9)}.code-group__nav-tab-active{border-bottom:var(--c-brand) 1px solid}@media (max-width: 419px){.code-group__nav{margin-left:-1.5rem;margin-right:-1.5rem;border-radius:0}}.code-group-item{display:none}.code-group-item__active{display:block}.code-group-item>pre{background-color:orange}.custom-container{transition:color var(--t-color),border-color var(--t-color),background-color var(--t-color)}.custom-container .custom-container-title{font-weight:600}.custom-container .custom-container-title:not(:only-child){margin-bottom:-.4rem}.custom-container.tip,.custom-container.warning,.custom-container.danger{padding:.1rem 1.5rem;border-left-width:.5rem;border-left-style:solid;margin:1rem 0}.custom-container.tip{border-color:var(--c-tip);background-color:var(--c-tip-bg);color:var(--c-tip-text)}.custom-container.tip .custom-container-title{color:var(--c-tip-title)}.custom-container.tip a{color:var(--c-tip-text-accent)}.custom-container.tip code{background-color:var(--c-bg-dark)}.custom-container.warning{border-color:var(--c-warning);background-color:var(--c-warning-bg);color:var(--c-warning-text)}.custom-container.warning .custom-container-title{color:var(--c-warning-title)}.custom-container.warning a{color:var(--c-warning-text-accent)}.custom-container.warning blockquote{border-left-color:var(--c-warning-border-dark);color:var(--c-warning-text-quote)}.custom-container.warning code{color:var(--c-warning-text-light);background-color:var(--c-warning-bg-light)}.custom-container.warning details{background-color:var(--c-warning-details-bg)}.custom-container.warning details code{background-color:var(--c-warning-bg-lighter)}.custom-container.warning .external-link-icon{--external-link-icon-color: var(--c-warning-text-quote)}.custom-container.danger{border-color:var(--c-danger);background-color:var(--c-danger-bg);color:var(--c-danger-text)}.custom-container.danger .custom-container-title{color:var(--c-danger-title)}.custom-container.danger a{color:var(--c-danger-text-accent)}.custom-container.danger blockquote{border-left-color:var(--c-danger-border-dark);color:var(--c-danger-text-quote)}.custom-container.danger code{color:var(--c-danger-text-light);background-color:var(--c-danger-bg-light)}.custom-container.danger details{background-color:var(--c-danger-details-bg)}.custom-container.danger details code{background-color:var(--c-danger-bg-lighter)}.custom-container.danger .external-link-icon{--external-link-icon-color: var(--c-danger-text-quote)}.custom-container.details{display:block;position:relative;border-radius:2px;margin:1.6em 0;padding:1.6em;background-color:var(--c-details-bg)}.custom-container.details code{background-color:var(--c-bg-darker)}.custom-container.details h4{margin-top:0}.custom-container.details figure:last-child,.custom-container.details p:last-child{margin-bottom:0;padding-bottom:0}.custom-container.details summary{outline:none;cursor:pointer}.home{padding:var(--navbar-height) 2rem 0;max-width:var(--homepage-width);margin:0 auto;display:block}.home .hero{text-align:center}.home .hero img{max-width:100%;max-height:280px;display:block;margin:3rem auto 1.5rem}.home .hero h1{font-size:3rem}.home .hero h1,.home .hero .description,.home .hero .actions{margin:1.8rem auto}.home .hero .actions{display:flex;flex-wrap:wrap;gap:1rem;justify-content:center}.home .hero .description{max-width:35rem;font-size:1.6rem;line-height:1.3;color:var(--c-text-lightest)}.home .hero .action-button{display:inline-block;font-size:1.2rem;padding:.8rem 1.6rem;border-width:2px;border-style:solid;border-radius:4px;transition:background-color var(--t-color);box-sizing:border-box}.home .hero .action-button.primary{color:var(--c-bg);background-color:var(--c-brand);border-color:var(--c-brand)}.home .hero .action-button.primary:hover{background-color:var(--c-brand-light)}.home .hero .action-button.secondary{color:var(--c-brand);background-color:var(--c-bg);border-color:var(--c-brand)}.home .hero .action-button.secondary:hover{color:var(--c-bg);background-color:var(--c-brand-light)}.home .features{border-top:1px solid var(--c-border);transition:border-color var(--t-color);padding:1.2rem 0;margin-top:2.5rem;display:flex;flex-wrap:wrap;align-items:flex-start;align-content:stretch;justify-content:space-between}.home .feature{flex-grow:1;flex-basis:30%;max-width:30%}.home .feature h2{font-size:1.4rem;font-weight:500;border-bottom:none;padding-bottom:0;color:var(--c-text-light)}.home .feature p{color:var(--c-text-lighter)}.home .theme-default-content{padding:0;margin:0}.home .footer{padding:2.5rem;border-top:1px solid var(--c-border);text-align:center;color:var(--c-text-lighter);transition:border-color var(--t-color)}@media (max-width: 719px){.home .features{flex-direction:column}.home .feature{max-width:100%;padding:0 2.5rem}}@media (max-width: 419px){.home{padding-left:1.5rem;padding-right:1.5rem}.home .hero img{max-height:210px;margin:2rem auto 1.2rem}.home .hero h1{font-size:2rem}.home .hero h1,.home .hero .description,.home .hero .actions{margin:1.2rem auto}.home .hero .description{font-size:1.2rem}.home .hero .action-button{font-size:1rem;padding:.6rem 1.2rem}.home .feature h2{font-size:1.25rem}}.page{padding-top:var(--navbar-height);padding-left:var(--sidebar-width)}.navbar{position:fixed;z-index:20;top:0;left:0;right:0;height:var(--navbar-height);box-sizing:border-box;border-bottom:1px solid var(--c-border);background-color:var(--c-bg-navbar);transition:background-color var(--t-color),border-color var(--t-color)}.sidebar{font-size:16px;width:var(--sidebar-width);position:fixed;z-index:10;margin:0;top:var(--navbar-height);left:0;bottom:0;box-sizing:border-box;border-right:1px solid var(--c-border);overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--c-brand) var(--c-border);background-color:var(--c-bg-sidebar);transition:transform var(--t-transform),background-color var(--t-color),border-color var(--t-color)}.sidebar::-webkit-scrollbar{width:7px}.sidebar::-webkit-scrollbar-track{background-color:var(--c-border)}.sidebar::-webkit-scrollbar-thumb{background-color:var(--c-brand)}.sidebar-mask{position:fixed;z-index:9;top:0;left:0;width:100vw;height:100vh;display:none}.theme-container.sidebar-open .sidebar-mask{display:block}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(1){transform:rotate(45deg) translate3d(5.5px,5.5px,0)}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(2){transform:scale3d(0,1,1)}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(3){transform:rotate(-45deg) translate3d(6px,-6px,0)}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(1),.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(3){transform-origin:center}.theme-container.no-navbar .theme-default-content h1,.theme-container.no-navbar .theme-default-content h2,.theme-container.no-navbar .theme-default-content h3,.theme-container.no-navbar .theme-default-content h4,.theme-container.no-navbar .theme-default-content h5,.theme-container.no-navbar .theme-default-content h6{margin-top:1.5rem;padding-top:0}.theme-container.no-navbar .page{padding-top:0}.theme-container.no-navbar .sidebar{top:0}.theme-container.no-sidebar .sidebar{display:none}@media (max-width: 719px){.theme-container.no-sidebar .sidebar{display:block}}.theme-container.no-sidebar .page{padding-left:0}.theme-default-content a:hover{text-decoration:underline}.theme-default-content img{max-width:100%}.theme-default-content h1,.theme-default-content h2,.theme-default-content h3,.theme-default-content h4,.theme-default-content h5,.theme-default-content h6{margin-top:calc(.5rem - var(--navbar-height));padding-top:calc(1rem + var(--navbar-height));margin-bottom:0}.theme-default-content h1:first-child,.theme-default-content h2:first-child,.theme-default-content h3:first-child,.theme-default-content h4:first-child,.theme-default-content h5:first-child,.theme-default-content h6:first-child{margin-bottom:1rem}.theme-default-content h1:first-child+p,.theme-default-content h1:first-child+pre,.theme-default-content h1:first-child+.custom-container,.theme-default-content h2:first-child+p,.theme-default-content h2:first-child+pre,.theme-default-content h2:first-child+.custom-container,.theme-default-content h3:first-child+p,.theme-default-content h3:first-child+pre,.theme-default-content h3:first-child+.custom-container,.theme-default-content h4:first-child+p,.theme-default-content h4:first-child+pre,.theme-default-content h4:first-child+.custom-container,.theme-default-content h5:first-child+p,.theme-default-content h5:first-child+pre,.theme-default-content h5:first-child+.custom-container,.theme-default-content h6:first-child+p,.theme-default-content h6:first-child+pre,.theme-default-content h6:first-child+.custom-container{margin-top:2rem}@media (max-width: 959px){.sidebar{font-size:15px;width:var(--sidebar-width-mobile)}.page{padding-left:var(--sidebar-width-mobile)}}@media (max-width: 719px){.sidebar{top:0;padding-top:var(--navbar-height);transform:translate(-100%)}.page{padding-left:0}.theme-container.sidebar-open .sidebar{transform:translate(0)}.theme-container.no-navbar .sidebar{padding-top:0}}@media (max-width: 419px){h1{font-size:1.9rem}}.navbar{--navbar-line-height: calc( var(--navbar-height) - 2 * var(--navbar-padding-v) );padding:var(--navbar-padding-v) var(--navbar-padding-h);line-height:var(--navbar-line-height)}.navbar .logo{height:var(--navbar-line-height);margin-right:var(--navbar-padding-v);vertical-align:top}.navbar .site-name{font-size:1.3rem;font-weight:600;color:var(--c-text);position:relative}.navbar .navbar-items-wrapper{display:flex;position:absolute;box-sizing:border-box;top:var(--navbar-padding-v);right:var(--navbar-padding-h);height:var(--navbar-line-height);padding-left:var(--navbar-padding-h);white-space:nowrap;font-size:.9rem}.navbar .navbar-items-wrapper .search-box{flex:0 0 auto;vertical-align:top}@media screen and (max-width: 719px){.navbar{padding-left:4rem}.navbar .can-hide{display:none}.navbar .site-name{width:calc(100vw - 9.4rem);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}}.navbar-items{display:inline-block}@media print{.navbar-items{display:none}}.navbar-items a{display:inline-block;line-height:1.4rem;color:inherit}.navbar-items a:hover,.navbar-items a.router-link-active{color:var(--c-text)}.navbar-items .navbar-item{position:relative;display:inline-block;margin-left:1.5rem;line-height:var(--navbar-line-height)}.navbar-items .navbar-item:first-child{margin-left:0}.navbar-items .navbar-item>a:hover,.navbar-items .navbar-item>a.router-link-active{margin-bottom:-2px;border-bottom:2px solid var(--c-text-accent)}@media (max-width: 719px){.navbar-items .navbar-item{margin-left:0}.navbar-items .navbar-item>a:hover,.navbar-items .navbar-item>a.router-link-active{margin-bottom:0;border-bottom:none}.navbar-items a:hover,.navbar-items a.router-link-active{color:var(--c-text-accent)}}.toggle-sidebar-button{position:absolute;top:.6rem;left:1rem;display:none;padding:.6rem;cursor:pointer}.toggle-sidebar-button .icon{display:flex;flex-direction:column;justify-content:center;align-items:center;width:1.25rem;height:1.25rem;cursor:inherit}.toggle-sidebar-button .icon span{display:inline-block;width:100%;height:2px;border-radius:2px;background-color:var(--c-text);transition:transform var(--t-transform)}.toggle-sidebar-button .icon span:nth-child(2){margin:6px 0}@media screen and (max-width: 719px){.toggle-sidebar-button{display:block}}.toggle-color-mode-button{display:flex;margin:auto;margin-left:1rem;border:0;background:none;color:var(--c-text);opacity:.8;cursor:pointer}@media print{.toggle-color-mode-button{display:none}}.toggle-color-mode-button:hover{opacity:1}.toggle-color-mode-button .icon{width:1.25rem;height:1.25rem}.DocSearch{transition:background-color var(--t-color)}.navbar-dropdown-wrapper{cursor:pointer}.navbar-dropdown-wrapper .navbar-dropdown-title,.navbar-dropdown-wrapper .navbar-dropdown-title-mobile{display:block;font-size:.9rem;font-family:inherit;cursor:inherit;padding:inherit;line-height:1.4rem;background:transparent;border:none;font-weight:500;color:var(--c-text)}.navbar-dropdown-wrapper .navbar-dropdown-title:hover,.navbar-dropdown-wrapper .navbar-dropdown-title-mobile:hover{border-color:transparent}.navbar-dropdown-wrapper .navbar-dropdown-title .arrow,.navbar-dropdown-wrapper .navbar-dropdown-title-mobile .arrow{vertical-align:middle;margin-top:-1px;margin-left:.4rem}.navbar-dropdown-wrapper .navbar-dropdown-title-mobile{display:none;font-weight:600;font-size:inherit}.navbar-dropdown-wrapper .navbar-dropdown-title-mobile:hover{color:var(--c-text-accent)}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item{color:inherit;line-height:1.7rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle{margin:.45rem 0 0;border-top:1px solid var(--c-border);padding:1rem 0 .45rem;font-size:.9rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle>span{padding:0 1.5rem 0 1.25rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle>a{font-weight:inherit}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle>a.router-link-active:after{display:none}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subitem-wrapper{padding:0;list-style:none}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subitem-wrapper .navbar-dropdown-subitem{font-size:.9em}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a{display:block;line-height:1.7rem;position:relative;border-bottom:none;font-weight:400;margin-bottom:0;padding:0 1.5rem 0 1.25rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a:hover,.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a.router-link-active{color:var(--c-text-accent)}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a.router-link-active:after{content:"";width:0;height:0;border-left:5px solid var(--c-text-accent);border-top:3px solid transparent;border-bottom:3px solid transparent;position:absolute;top:calc(50% - 2px);left:9px}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item:first-child .navbar-dropdown-subtitle{margin-top:0;padding-top:0;border-top:0}.navbar-dropdown-wrapper.mobile.open .navbar-dropdown-title,.navbar-dropdown-wrapper.mobile.open .navbar-dropdown-title-mobile{margin-bottom:.5rem}.navbar-dropdown-wrapper.mobile .navbar-dropdown-title,.navbar-dropdown-wrapper.mobile .navbar-dropdown-title-mobile{display:none}.navbar-dropdown-wrapper.mobile .navbar-dropdown-title-mobile{display:block}.navbar-dropdown-wrapper.mobile .navbar-dropdown{transition:height .1s ease-out;overflow:hidden}.navbar-dropdown-wrapper.mobile .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle{border-top:0;margin-top:0;padding-top:0;padding-bottom:0}.navbar-dropdown-wrapper.mobile .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle,.navbar-dropdown-wrapper.mobile .navbar-dropdown .navbar-dropdown-item>a{font-size:15px;line-height:2rem}.navbar-dropdown-wrapper.mobile .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subitem{font-size:14px;padding-left:1rem}.navbar-dropdown-wrapper:not(.mobile){height:1.8rem}.navbar-dropdown-wrapper:not(.mobile):hover .navbar-dropdown,.navbar-dropdown-wrapper:not(.mobile).open .navbar-dropdown{display:block!important}.navbar-dropdown-wrapper:not(.mobile).open:blur{display:none}.navbar-dropdown-wrapper:not(.mobile) .navbar-dropdown{display:none;height:auto!important;box-sizing:border-box;max-height:calc(100vh - 2.7rem);overflow-y:auto;position:absolute;top:100%;right:0;background-color:var(--c-bg-navbar);padding:.6rem 0;border:1px solid var(--c-border);border-bottom-color:var(--c-border-dark);text-align:left;border-radius:.25rem;white-space:nowrap;margin:0}.page{padding-bottom:2rem;display:block}.page .theme-default-content{max-width:var(--content-width);margin:0 auto;padding:2rem 2.5rem;padding-top:0}@media (max-width: 959px){.page .theme-default-content{padding:2rem}}@media (max-width: 419px){.page .theme-default-content{padding:1.5rem}}.page-meta{max-width:var(--content-width);margin:0 auto;padding:1rem 2.5rem;overflow:auto}@media (max-width: 959px){.page-meta{padding:2rem}}@media (max-width: 419px){.page-meta{padding:1.5rem}}.page-meta .meta-item{cursor:default;margin-top:.8rem}.page-meta .meta-item .meta-item-label{font-weight:500;color:var(--c-text-lighter)}.page-meta .meta-item .meta-item-info{font-weight:400;color:var(--c-text-quote)}.page-meta .edit-link{display:inline-block;margin-right:.25rem}@media print{.page-meta .edit-link{display:none}}.page-meta .last-updated{float:right}@media (max-width: 719px){.page-meta .last-updated{font-size:.8em;float:none}.page-meta .contributors{font-size:.8em}}.page-nav{max-width:var(--content-width);margin:0 auto;padding:1rem 2.5rem 2rem;padding-bottom:0}@media (max-width: 959px){.page-nav{padding:2rem}}@media (max-width: 419px){.page-nav{padding:1.5rem}}.page-nav .inner{min-height:2rem;margin-top:0;border-top:1px solid var(--c-border);transition:border-color var(--t-color);padding-top:1rem;overflow:auto}.page-nav .prev a:before{content:"\2190"}.page-nav .next{float:right}.page-nav .next a:after{content:"\2192"}.sidebar ul{padding:0;margin:0;list-style-type:none}.sidebar a{display:inline-block}.sidebar .navbar-items{display:none;border-bottom:1px solid var(--c-border);transition:border-color var(--t-color);padding:.5rem 0 .75rem}.sidebar .navbar-items a{font-weight:600}.sidebar .navbar-items .navbar-item{display:block;line-height:1.25rem;font-size:1.1em;padding:.5rem 0 .5rem 1.5rem}.sidebar .sidebar-items{padding:1.5rem 0}@media (max-width: 719px){.sidebar .navbar-items{display:block}.sidebar .navbar-items .navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a.router-link-active:after{top:calc(1rem - 2px)}.sidebar .sidebar-items{padding:1rem 0}}.sidebar-item{cursor:default;border-left:.25rem solid transparent;color:var(--c-text)}.sidebar-item:focus-visible{outline-width:1px;outline-offset:-1px}.sidebar-item.active:not(p.sidebar-heading){font-weight:600;color:var(--c-text-accent);border-left-color:var(--c-text-accent)}.sidebar-item.sidebar-heading{transition:color .15s ease;font-size:1.1em;font-weight:700;padding:.35rem 1.5rem .35rem 1.25rem;width:100%;box-sizing:border-box;margin:0}.sidebar-item.sidebar-heading+.sidebar-item-children{transition:height .1s ease-out;overflow:hidden;margin-bottom:.75rem}.sidebar-item.collapsible{cursor:pointer}.sidebar-item.collapsible .arrow{position:relative;top:-.12em;left:.5em}.sidebar-item:not(.sidebar-heading){font-size:1em;font-weight:400;display:inline-block;margin:0;padding:.35rem 1rem .35rem 2rem;line-height:1.4;width:100%;box-sizing:border-box}.sidebar-item:not(.sidebar-heading)+.sidebar-item-children{padding-left:1rem;font-size:.95em}.sidebar-item-children .sidebar-item-children .sidebar-item:not(.sidebar-heading){padding:.25rem 1rem .25rem 1.75rem}.sidebar-item-children .sidebar-item-children .sidebar-item:not(.sidebar-heading).active{font-weight:500;border-left-color:transparent}a.sidebar-heading+.sidebar-item-children .sidebar-item:not(.sidebar-heading).active{border-left-color:transparent}a.sidebar-item{cursor:pointer}a.sidebar-item:hover{color:var(--c-text-accent)}.table-of-contents .badge{vertical-align:middle}.dropdown-enter-from,.dropdown-leave-to{height:0!important}.fade-slide-y-enter-active{transition:all .2s ease}.fade-slide-y-leave-active{transition:all .2s cubic-bezier(1,.5,.8,1)}.fade-slide-y-enter-from,.fade-slide-y-leave-to{transform:translateY(10px);opacity:0}:root{--c-brand: #99CC33;--c-brand-light: #D7DB0A;--c-text-accent: #826aed;--code-bg-color: #383838;--code-hl-bg-color: rgba(0, 0, 0, .5);--code-ln-color: #fff;--code-ln-wrapper-width: 2rem;--content-width: 850px;--homepage-width: 100%;--c-text-quote: rgb(130, 122, 165);--c-tip: #b5d935 !important;--c-details-bg: rgba(125,125,125,.05);--font-family: "Nunito Sans", sans-serif;--navbar-padding-h: .7rem}:root .navbar-dropdown{box-shadow:0 0 10px #14141433;border-radius:.7em}html.dark a{color:#a493f1}.sidebar-item-children .sidebar-item-children{opacity:.9;font-size:.92em!important;padding-bottom:.3em;border-left:.3em solid var(--c-text-accent)}.sidebar-item-children a{padding-top:.1em!important;padding-bottom:.1em!important}.sidebar-item-children .sidebar-item-children a:before{content:" ";margin-left:-.7em;color:#0000004d}.theme-container,blockquote{font-size:1.1rem}p,ul,ol{line-height:1.5em}code{border-radius:.4rem;background-color:#b4b4b41a!important;box-shadow:inset 0 0 10px #96969633!important;border:1px solid rgba(150,150,150,.3);padding:.1em .3em!important}pre code{background:none!important;box-shadow:none!important;border:none!important}a code{color:var(--c-text-accent)!important}::-moz-selection{color:#000;background:#a3d03d}::selection{color:#000;background:#a3d03d}.search-box{--search-border-color: rgba(138, 125, 189, .5)}img{border-radius:.5em}.tip{border-left-width:.25em!important;padding:.2em .2em .5em 2.4em!important;max-width:700px}details,.custom-container{border-radius:10px!important;border:2px solid rgba(255,255,255,.05)!important;outline:1px solid rgba(0,0,0,.2);box-shadow:inset 0 0 20px #0000000d!important;padding:1em!important;margin:.5em 0!important}details summary{font-weight:700!important}details p{margin-top:.5em}.custom-container p:not(:first){margin:0}.custom-container-title{padding-bottom:.5em}table{box-shadow:inset 0 0 40px #6464641a!important;border-radius:.5em;padding:.5em .5em .5em 1em;min-width:100%}@media screen and (min-width: 1700px){table{min-width:1000px}}@media screen and (min-width: 1900px){table{min-width:1200px}}td{width:20%}td:first-child{min-width:10%}td:last-child{width:30%}th{text-align:left}tr,td,th{border:none!important;padding:.5em 1em .8em 0!important}tr,td,th{font-size:.9em!important;background:none!important}tr{border-bottom:1px solid rgba(155,155,155,.2)!important;display:table-row}tr:last-child{border-bottom:none!important}td{vertical-align:top}.sample-code-links{display:flex;margin-top:-15px;top:0px;border-radius:0 0 10px 10px;width:calc(100% - .6em);padding:.4em .1em .15em .6em}.navbar{backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);display:flex;padding-left:1.3rem;width:-moz-fit-content;width:fit-content;background-color:#fffc;border-radius:3rem;outline:1px solid rgba(255,255,255,.1);box-shadow:0 2px 15px #02022b33;margin:5px auto}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subitem-wrapper .navbar-dropdown-subitem{font-size:inherit}@media screen and (max-width: 719px){.navbar{padding-left:4rem}}.navbar .site-name{font-size:1.5rem;margin-right:20px;opacity:.5;color:#000}.sidebar{scrollbar-color:var(--c-brand) transparent}.navbar .navbar-items-wrapper{position:initial}.navbar span a{white-space:nowrap}.navbar-dropdown-wrapper:not(.mobile) .navbar-dropdown{border-radius:12px;scrollbar-width:thin;scrollbar-color:var(--c-brand) transparent;max-height:calc(100vh - 4rem)}:root .navbar-dropdown{box-shadow:0 1px #1414141a}.theme-container{font-size:1rem}span.DocSearch-Button-Keys,kbd.DocSearch-Button-Key{display:none}span.DocSearch-Button-Placeholder{font-family:Nunito Sans,sans-serif;font-size:.9rem}div.DocSearch-Modal{border-radius:20px}footer.DocSearch-Footer{border-radius:0 0 20px 20px}form.DocSearch-Form{border-radius:9px}@media (min-width: 751px){div#docsearch-container{min-width:initial}}footer.page-meta{font-size:.9em;opacity:.5}.navbar-item>a.external-link .external-link-icon{display:none}.action[data-v-1f05b024]{display:inline-block;background:rgba(125,125,125,.1);color:inherit;padding:1em 1em 1.2em;border-radius:1em;margin:.5em;box-shadow:0 0 .5em #64646433,inset 0 0 1.5em #6464641a;border:1px solid rgba(100,100,100,.3);transition:transform .2s ease-in-out;font-size:1.2em}a[data-v-1f05b024]:hover{text-decoration:none}.action[data-v-1f05b024]:hover{transform:scale(1.1);transition:transform .1s ease-in-out}.actiongroup{display:flex;align-items:center;justify-content:center;flex-wrap:wrap}.actiongroup>*{margin:0 .5em}.contribution[data-v-543822ed]{margin-bottom:10px}.header[data-v-543822ed]{display:flex;align-items:center;justify-content:space-between;border-radius:1em;padding-right:1em;-webkit-user-select:none;-moz-user-select:none;user-select:none}.header.gradient[data-v-543822ed]{background:linear-gradient(180deg,rgba(100,100,200,0) 0%,rgba(100,100,200,.1) 3%,rgba(200,200,200,0) 100%)}.profile[data-v-543822ed]{display:flex;align-items:center;margin-bottom:1em;margin-left:.9em;margin-top:1em}.profile img[data-v-543822ed]{width:50px;height:50px;margin-right:10px;border-radius:50%;border:2px solid #ccc;pointer-events:none}.profile .authorname[data-v-543822ed]{font-size:1.2rem;font-weight:600;color:#000;text-decoration:none}html.dark .authorname[data-v-543822ed]{color:#fff}.entry[data-v-bcc3d6f6]{display:block;padding:.2em .2em .3em 1em;border-radius:.7em;font-size:1.4em;font-weight:400}.entry[data-v-bcc3d6f6]:hover{text-decoration:none;background-color:#6464de33;box-shadow:inset 0 0 50px #fff}html.dark .entry[data-v-bcc3d6f6]:hover{box-shadow:inset 0 0 30px #323232}a[data-v-f801cb6e]{color:initial;text-decoration:none}.preview[data-v-f801cb6e]{margin:1%;display:inline-block;position:relative;min-width:48%;flex-grow:1;flex-shrink:1;height:300px;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;user-select:none;border-radius:.5em;box-shadow:0 0 1px #00006433;border:1px solid rgba(0,0,100,.3);transition:all .1s ease-in-out}.preview[data-v-f801cb6e]:hover{cursor:pointer;box-shadow:0 0 10px #0000641a;transition:all .1s ease-in-out}.content[data-v-f801cb6e]{overflow:hidden;bottom:10px;padding:0 10px;position:relative}.title[data-v-f801cb6e]{display:flex;align-items:center;font-size:1.5em;margin:0;padding:1em;height:3em;background:linear-gradient(180deg,rgba(100,100,200,.2) 20%,rgba(200,200,200,0) 100%);-webkit-user-select:initial!important;-moz-user-select:initial!important;user-select:initial!important}.overview-link{display:block;padding-top:1em;padding-bottom:1em;text-decoration:none}.overview-link:hover{text-decoration:none}.previews{display:flex;flex-direction:row;flex-wrap:wrap}.footer{margin-top:1em;display:block;padding-top:1em;border-top:1px solid rgba(100,100,100,.2)}.list{display:flex;flex-direction:column}.list>*{width:95%;margin-bottom:10px;border:1px solid rgba(100,100,200,.2);border-radius:.5em;padding:.5em .5em 2em;background:linear-gradient(180deg,rgba(100,100,200,.1) 0%,rgba(200,200,200,.1) 50%)}html.dark .list>*{border:1px solid rgba(200,200,200,.05);background:linear-gradient(180deg,rgba(100,100,200,.1) 0%,rgba(100,100,100,.2) 50%)}.footer[data-v-682f5e2f]{display:flex;width:100%;justify-content:center;align-items:center;padding:1em;font-size:.8em;opacity:.8}a[data-v-682f5e2f]{color:inherit;font-weight:400;margin-left:5px}button[data-v-b590c0c3]{border:none;border-radius:10em;text-shadow:none;padding:1em 2em;background-color:var(--67fdb382);transition:background-color .2s ease-in-out;cursor:pointer;margin:.2em .2em .2em -.3em;color:#fff;font-size:var(--0b33cc28)}button[data-v-b590c0c3]:hover{background-color:#6248be;background-color:var(--1b4cfd03);transition:background-color .2s ease-in-out}a[data-v-b590c0c3]{text-shadow:none!important}.quotes[data-v-96af3692]{align-items:center;justify-content:center;flex-wrap:wrap;padding:0 .5em .5em;text-align:center;min-height:90px;display:none}@media (max-width: 768px){.quotes[data-v-96af3692]{min-height:200px}}.quotes[data-v-96af3692]{font-size:1.2em;font-style:italic}div[data-v-65d547d3]{margin-top:.3em;display:flex;flex-direction:column}iframe[data-v-65d547d3]{width:100%;height:100%;border:0;aspect-ratio:16/9}iframe[data-v-65d547d3]:only-of-type{border-radius:1em}iframe[data-v-65d547d3]:first-of-type{border-top-left-radius:1em;border-top-right-radius:1em;margin-bottom:.1em}iframe[data-v-65d547d3]:last-of-type{border-bottom-left-radius:1em;border-bottom-right-radius:1em;margin-top:.1em}@media (max-aspect-ratio: 1/1){iframe[data-v-65d547d3]{aspect-ratio:9/9}}@media (max-aspect-ratio: 9/16){iframe[data-v-65d547d3]{aspect-ratio:9/14}}button{background-color:#1374ef;border:none;color:#fff;padding:.3em;border-radius:.5em;margin-top:.5em}button:hover{background-color:#277ee9;cursor:pointer}.code{display:none}.root[data-v-47fef520]{display:flex}.testimonial[data-v-47fef520]{display:inline-block;margin:0 0 1rem;padding:1rem;background-color:#7d7d7d1a;border-radius:1em;border:1px solid rgba(125,125,125,.2);box-shadow:inset 0 0 10px #7d7d7d1a}.marker[data-v-47fef520]{position:absolute;font-size:2rem;color:#32323280;margin:-1.2rem -.7rem}.container[data-v-144c2fbe]{max-width:100%;height:var(--5159d46b);max-height:var(--4cc05253)}video[data-v-144c2fbe],#ytplayer[data-v-144c2fbe]{background:rgba(0,0,0,.2);display:block;width:var(--49dcf46c);height:var(--25a42c34);max-width:100%;max-height:100%;margin:.75em 0;max-height:var(--02f9f579)}#ytplayer[data-v-144c2fbe]{aspect-ratio:16/9;border-radius:1em}@media screen and (max-width: 1200px){.container[data-v-144c2fbe]{width:100%;height:auto}video[data-v-144c2fbe],#ytplayer[data-v-144c2fbe]{width:100%;height:auto}}/*! @docsearch/css 3.6.1 | MIT License | ยฉ Algolia, Inc. and contributors | https://docsearch.algolia.com */:root{--docsearch-primary-color:#5468ff;--docsearch-text-color:#1c1e21;--docsearch-spacing:12px;--docsearch-icon-stroke-width:1.4;--docsearch-highlight-color:var(--docsearch-primary-color);--docsearch-muted-color:#969faf;--docsearch-container-background:rgba(101,108,133,.8);--docsearch-logo-color:#5468ff;--docsearch-modal-width:560px;--docsearch-modal-height:600px;--docsearch-modal-background:#f5f6f7;--docsearch-modal-shadow:inset 1px 1px 0 0 hsla(0,0%,100%,.5),0 3px 8px 0 #555a64;--docsearch-searchbox-height:56px;--docsearch-searchbox-background:#ebedf0;--docsearch-searchbox-focus-background:#fff;--docsearch-searchbox-shadow:inset 0 0 0 2px var(--docsearch-primary-color);--docsearch-hit-height:56px;--docsearch-hit-color:#444950;--docsearch-hit-active-color:#fff;--docsearch-hit-background:#fff;--docsearch-hit-shadow:0 1px 3px 0 #d4d9e1;--docsearch-key-gradient:linear-gradient(-225deg,#d5dbe4,#f8f8f8);--docsearch-key-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px rgba(30,35,90,.4);--docsearch-key-pressed-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 1px 0 rgba(30,35,90,.4);--docsearch-footer-height:44px;--docsearch-footer-background:#fff;--docsearch-footer-shadow:0 -1px 0 0 #e0e3e8,0 -3px 6px 0 rgba(69,98,155,.12)}html[data-theme=dark]{--docsearch-text-color:#f5f6f7;--docsearch-container-background:rgba(9,10,17,.8);--docsearch-modal-background:#15172a;--docsearch-modal-shadow:inset 1px 1px 0 0 #2c2e40,0 3px 8px 0 #000309;--docsearch-searchbox-background:#090a11;--docsearch-searchbox-focus-background:#000;--docsearch-hit-color:#bec3c9;--docsearch-hit-shadow:none;--docsearch-hit-background:#090a11;--docsearch-key-gradient:linear-gradient(-26.5deg,#565872,#31355b);--docsearch-key-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 2px 2px 0 rgba(3,4,9,.3);--docsearch-key-pressed-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 1px 1px 0 rgba(3,4,9,.30196078431372547);--docsearch-footer-background:#1e2136;--docsearch-footer-shadow:inset 0 1px 0 0 rgba(73,76,106,.5),0 -4px 8px 0 rgba(0,0,0,.2);--docsearch-logo-color:#fff;--docsearch-muted-color:#7f8497}.DocSearch-Button{align-items:center;background:var(--docsearch-searchbox-background);border:0;border-radius:40px;color:var(--docsearch-muted-color);cursor:pointer;display:flex;font-weight:500;height:36px;justify-content:space-between;margin:0 0 0 16px;padding:0 8px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.DocSearch-Button:active,.DocSearch-Button:focus,.DocSearch-Button:hover{background:var(--docsearch-searchbox-focus-background);box-shadow:var(--docsearch-searchbox-shadow);color:var(--docsearch-text-color);outline:none}.DocSearch-Button-Container{align-items:center;display:flex}.DocSearch-Search-Icon{stroke-width:1.6}.DocSearch-Button .DocSearch-Search-Icon{color:var(--docsearch-text-color)}.DocSearch-Button-Placeholder{font-size:1rem;padding:0 12px 0 6px}.DocSearch-Button-Keys{display:flex;min-width:calc(40px + .8em)}.DocSearch-Button-Key{align-items:center;background:var(--docsearch-key-gradient);border-radius:3px;box-shadow:var(--docsearch-key-shadow);color:var(--docsearch-muted-color);display:flex;height:18px;justify-content:center;margin-right:.4em;position:relative;padding:0 0 2px;border:0;top:-1px;width:20px}.DocSearch-Button-Key--pressed{transform:translate3d(0,1px,0);box-shadow:var(--docsearch-key-pressed-shadow)}@media (max-width:768px){.DocSearch-Button-Keys,.DocSearch-Button-Placeholder{display:none}}.DocSearch--active{overflow:hidden!important}.DocSearch-Container,.DocSearch-Container *{box-sizing:border-box}.DocSearch-Container{background-color:var(--docsearch-container-background);height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:200}.DocSearch-Container a{text-decoration:none}.DocSearch-Link{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;font:inherit;margin:0;padding:0}.DocSearch-Modal{background:var(--docsearch-modal-background);border-radius:6px;box-shadow:var(--docsearch-modal-shadow);flex-direction:column;margin:60px auto auto;max-width:var(--docsearch-modal-width);position:relative}.DocSearch-SearchBar{display:flex;padding:var(--docsearch-spacing) var(--docsearch-spacing) 0}.DocSearch-Form{align-items:center;background:var(--docsearch-searchbox-focus-background);border-radius:4px;box-shadow:var(--docsearch-searchbox-shadow);display:flex;height:var(--docsearch-searchbox-height);margin:0;padding:0 var(--docsearch-spacing);position:relative;width:100%}.DocSearch-Input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;color:var(--docsearch-text-color);flex:1;font:inherit;font-size:1.2em;height:100%;outline:none;padding:0 0 0 8px;width:80%}.DocSearch-Input::-moz-placeholder{color:var(--docsearch-muted-color);opacity:1}.DocSearch-Input::placeholder{color:var(--docsearch-muted-color);opacity:1}.DocSearch-Input::-webkit-search-cancel-button,.DocSearch-Input::-webkit-search-decoration,.DocSearch-Input::-webkit-search-results-button,.DocSearch-Input::-webkit-search-results-decoration{display:none}.DocSearch-LoadingIndicator,.DocSearch-MagnifierLabel,.DocSearch-Reset{margin:0;padding:0}.DocSearch-MagnifierLabel,.DocSearch-Reset{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}.DocSearch-Container--Stalled .DocSearch-MagnifierLabel,.DocSearch-LoadingIndicator{display:none}.DocSearch-Container--Stalled .DocSearch-LoadingIndicator{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Reset{animation:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;right:0;stroke-width:var(--docsearch-icon-stroke-width)}}.DocSearch-Reset{animation:fade-in .1s ease-in forwards;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;padding:2px;right:0;stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Reset[hidden]{display:none}.DocSearch-Reset:hover{color:var(--docsearch-highlight-color)}.DocSearch-LoadingIndicator svg,.DocSearch-MagnifierLabel svg{height:24px;width:24px}.DocSearch-Cancel{display:none}.DocSearch-Dropdown{max-height:calc(var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height));min-height:var(--docsearch-spacing);overflow-y:auto;overflow-y:overlay;padding:0 var(--docsearch-spacing);scrollbar-color:var(--docsearch-muted-color) var(--docsearch-modal-background);scrollbar-width:thin}.DocSearch-Dropdown::-webkit-scrollbar{width:12px}.DocSearch-Dropdown::-webkit-scrollbar-track{background:transparent}.DocSearch-Dropdown::-webkit-scrollbar-thumb{background-color:var(--docsearch-muted-color);border:3px solid var(--docsearch-modal-background);border-radius:20px}.DocSearch-Dropdown ul{list-style:none;margin:0;padding:0}.DocSearch-Label{font-size:.75em;line-height:1.6em}.DocSearch-Help,.DocSearch-Label{color:var(--docsearch-muted-color)}.DocSearch-Help{font-size:.9em;margin:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.DocSearch-Title{font-size:1.2em}.DocSearch-Logo a{display:flex}.DocSearch-Logo svg{color:var(--docsearch-logo-color);margin-left:8px}.DocSearch-Hits:last-of-type{margin-bottom:24px}.DocSearch-Hits mark{background:none;color:var(--docsearch-highlight-color)}.DocSearch-HitsFooter{color:var(--docsearch-muted-color);display:flex;font-size:.85em;justify-content:center;margin-bottom:var(--docsearch-spacing);padding:var(--docsearch-spacing)}.DocSearch-HitsFooter a{border-bottom:1px solid;color:inherit}.DocSearch-Hit{border-radius:4px;display:flex;padding-bottom:4px;position:relative}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--deleting{transition:none}}.DocSearch-Hit--deleting{opacity:0;transition:all .25s linear}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--favoriting{transition:none}}.DocSearch-Hit--favoriting{transform:scale(0);transform-origin:top center;transition:all .25s linear;transition-delay:.25s}.DocSearch-Hit a{background:var(--docsearch-hit-background);border-radius:4px;box-shadow:var(--docsearch-hit-shadow);display:block;padding-left:var(--docsearch-spacing);width:100%}.DocSearch-Hit-source{background:var(--docsearch-modal-background);color:var(--docsearch-highlight-color);font-size:.85em;font-weight:600;line-height:32px;margin:0 -4px;padding:8px 4px 0;position:sticky;top:0;z-index:10}.DocSearch-Hit-Tree{color:var(--docsearch-muted-color);height:var(--docsearch-hit-height);opacity:.5;stroke-width:var(--docsearch-icon-stroke-width);width:24px}.DocSearch-Hit[aria-selected=true] a{background-color:var(--docsearch-highlight-color)}.DocSearch-Hit[aria-selected=true] mark{text-decoration:underline}.DocSearch-Hit-Container{align-items:center;color:var(--docsearch-hit-color);display:flex;flex-direction:row;height:var(--docsearch-hit-height);padding:0 var(--docsearch-spacing) 0 0}.DocSearch-Hit-icon{height:20px;width:20px}.DocSearch-Hit-action,.DocSearch-Hit-icon{color:var(--docsearch-muted-color);stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Hit-action{align-items:center;display:flex;height:22px;width:22px}.DocSearch-Hit-action svg{display:block;height:18px;width:18px}.DocSearch-Hit-action+.DocSearch-Hit-action{margin-left:6px}.DocSearch-Hit-action-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:inherit;cursor:pointer;padding:2px}svg.DocSearch-Hit-Select-Icon{display:none}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Select-Icon{display:block}.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:rgba(0,0,0,.2);transition:background-color .1s ease-in}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{transition:none}}.DocSearch-Hit-action-button:focus path,.DocSearch-Hit-action-button:hover path{fill:#fff}.DocSearch-Hit-content-wrapper{display:flex;flex:1 1 auto;flex-direction:column;font-weight:500;justify-content:center;line-height:1.2em;margin:0 8px;overflow-x:hidden;position:relative;text-overflow:ellipsis;white-space:nowrap;width:80%}.DocSearch-Hit-title{font-size:.9em}.DocSearch-Hit-path{color:var(--docsearch-muted-color);font-size:.75em}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-action,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-icon,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-text,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-title,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Tree,.DocSearch-Hit[aria-selected=true] mark{color:var(--docsearch-hit-active-color)!important}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:rgba(0,0,0,.2);transition:none}}.DocSearch-ErrorScreen,.DocSearch-NoResults,.DocSearch-StartScreen{font-size:.9em;margin:0 auto;padding:36px 0;text-align:center;width:80%}.DocSearch-Screen-Icon{color:var(--docsearch-muted-color);padding-bottom:12px}.DocSearch-NoResults-Prefill-List{display:inline-block;padding-bottom:24px;text-align:left}.DocSearch-NoResults-Prefill-List ul{display:inline-block;padding:8px 0 0}.DocSearch-NoResults-Prefill-List li{list-style-position:inside;list-style-type:"\bb "}.DocSearch-Prefill{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:1em;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;font-size:1em;font-weight:700;padding:0}.DocSearch-Prefill:focus,.DocSearch-Prefill:hover{outline:none;text-decoration:underline}.DocSearch-Footer{align-items:center;background:var(--docsearch-footer-background);border-radius:0 0 8px 8px;box-shadow:var(--docsearch-footer-shadow);display:flex;flex-direction:row-reverse;flex-shrink:0;height:var(--docsearch-footer-height);justify-content:space-between;padding:0 var(--docsearch-spacing);position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%;z-index:300}.DocSearch-Commands{color:var(--docsearch-muted-color);display:flex;list-style:none;margin:0;padding:0}.DocSearch-Commands li{align-items:center;display:flex}.DocSearch-Commands li:not(:last-of-type){margin-right:.8em}.DocSearch-Commands-Key{align-items:center;background:var(--docsearch-key-gradient);border-radius:2px;box-shadow:var(--docsearch-key-shadow);display:flex;height:18px;justify-content:center;margin-right:.4em;padding:0 0 1px;color:var(--docsearch-muted-color);border:0;width:20px}.DocSearch-VisuallyHiddenForAccessibility{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}@media (max-width:768px){:root{--docsearch-spacing:10px;--docsearch-footer-height:40px}.DocSearch-Dropdown{height:100%}.DocSearch-Container{height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);position:absolute}.DocSearch-Footer{border-radius:0;bottom:0;position:absolute}.DocSearch-Hit-content-wrapper{display:flex;position:relative;width:80%}.DocSearch-Modal{border-radius:0;box-shadow:none;height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);margin:0;max-width:100%;width:100%}.DocSearch-Dropdown{max-height:calc(var(--docsearch-vh, 1vh)*100 - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height))}.DocSearch-Cancel{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;flex:none;font:inherit;font-size:1em;font-weight:500;margin-left:var(--docsearch-spacing);outline:none;overflow:hidden;padding:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.DocSearch-Commands,.DocSearch-Hit-Tree{display:none}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@media (min-width: 751px){#docsearch-container{min-width:171.36px}}@media (max-width: 750px){.DocSearch-Container{position:fixed}#docsearch-container{min-width:52px}}@media print{#docsearch-container{display:none}} diff --git a/assets/style-e9220a04.js b/assets/style-e9220a04.js new file mode 100644 index 000000000..c93054809 --- /dev/null +++ b/assets/style-e9220a04.js @@ -0,0 +1 @@ +const t="";export{t as default}; diff --git a/assets/technical-overview.html-3daf2543.js b/assets/technical-overview.html-3daf2543.js new file mode 100644 index 000000000..1095af127 --- /dev/null +++ b/assets/technical-overview.html-3daf2543.js @@ -0,0 +1,264 @@ +import{_ as i,M as o,p as r,q as l,R as s,t as n,N as a,V as c,a1 as t}from"./framework-f6820c83.js";const u={},d=t('

    Technical Overview

    How it works

    Needle Engine roughly consists of three parts:

    • 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.

    glTF Assets

    ',7),k={href:"https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html",target:"_blank",rel:"noopener noreferrer"},m=s("br",null,null,-1),v=s("a",{href:"#vendor-specific-gltf-extensions-needle_"},"vendor extensions",-1),b=t(`

    Supported glTF extensions

    A typical production glTF created by Needle Engine uses the following extensions:

    KHR_lights_punctual
    +KHR_materials_unlit
    +KHR_texture_transform
    +KHR_animation_pointer
    +NEEDLE_techniques_webgl
    +NEEDLE_gameobject_data
    +NEEDLE_components
    +NEEDLE_persistent_assets
    +NEEDLE_lightmaps
    +NEEDLE_lighting_settings
    +KHR_texture_basisu
    +KHR_draco_mesh_compression
    +

    Other supported extensions:

    EXT_meshopt_compression
    +EXT_mesh_gpu_instancing (import and export)
    +

    Supported material extensions:

    KHR_materials_clearcoat
    +KHR_materials_ior
    +KHR_materials_specular
    +KHR_materials_transmission
    +KHR_materials_iridescence
    +KHR_materials_unlit
    +KHR_materials_volume
    +
    `,7),q={href:"https://threejs.org/docs/#examples/en/loaders/GLTFLoader",target:"_blank",rel:"noopener noreferrer"},h=t("

    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.

    ",2),g={href:"https://threejs.org/docs/#examples/en/loaders/GLTFLoader",target:"_blank",rel:"noopener noreferrer"},y=s("h3",{id:"compression",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#compression","aria-hidden":"true"},"#"),n(" Compression")],-1),f={href:"https://gltf-transform.donmccurdy.com/",target:"_blank",rel:"noopener noreferrer"},_=s("code",null,"glTF-transform",-1),x=s("code",null,"etc1s",-1),E=s("code",null,"uastc",-1),w=s("code",null,"webp",-1),T=s("code",null,"draco",-1),N=s("code",null,"meshtopt",-1),L=t('

    Vendor-specific glTF Extensions (NEEDLE_*)

    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.

    NEEDLE_components

    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.

    ',6),j=s("code",null,"NEEDLE_components",-1),R={href:"https://github.com/ux3d/glTF/tree/extensions/KHR_animation_pointer/extensions/2.0/Khronos/KHR_animation_pointer",target:"_blank",rel:"noopener noreferrer"},D=s("code",null,"KHR_animation_pointer",-1),S=t(`
    "NEEDLE_components": {
    +  "builtin_components": [
    +    {
    +      "name": "WebARSessionRoot",
    +      "guid": "1516450550",
    +      "arScale": 50,
    +      "invertForward": true,
    +      "enabled": true,
    +      "gameObject": {
    +        "node": 0
    +      }
    +    },
    +    {
    +      "name": "SyncedRoom",
    +      "guid": "1516450552",
    +      "roomName": "network-room",
    +      "urlParameterName": "room",
    +      "joinRandomRoom": true,
    +      "requireRoomParameter": false,
    +      "autoRejoin": true,
    +      "enabled": true,
    +      "gameObject": {
    +        "node": 0
    +      }
    +    },
    +    {
    +      "name": "PlayableDirector",
    +      "guid": "2243275882009986562_1668529989451832962",
    +      "state": 0,
    +      "extrapolationMode": 1,
    +      "playableAsset": "extensions/NEEDLE_persistent_assets/4",
    +      "playableGraph": {},
    +      "playOnAwake": true,
    +      "timeUpdateMode": 0,
    +      "time": 0,
    +      "initialTime": 0,
    +      "duration": 135.383333333332,
    +      "enabled": true,
    +      "gameObject": {
    +        "node": 0
    +      }
    +    }
    +  ]
    +}
    +

    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.

    NEEDLE_gameobject_data

    `,5),F={href:"https://threejs.org/docs/#api/en/core/Layers",target:"_blank",rel:"noopener noreferrer"},M={href:"https://docs.unity3d.com/Manual/Layers.html",target:"_blank",rel:"noopener noreferrer"},W=t(`
    "NEEDLE_gameobject_data": {
    +  "layers": 0,
    +  "tag": "Untagged",
    +  "hideFlags": 0,
    +  "static": false,
    +  "activeSelf": true,
    +  "guid": "1516450549"
    +}
    +

    Note: We may need to better explain why this is not another entry in NEEDLE_components.

    NEEDLE_lighting_settings

    This is a root extension defining ambient lighting properties per glTF file.

    "NEEDLE_lighting_settings": {
    +  "ambientMode": 0,
    +  "ambientLight": [
    +    0.212,
    +    0.227,
    +    0.259,
    +    1
    +  ],
    +  "ambientIntensity": 1,
    +  "defaultReflectionMode": 0
    +}
    +

    Note: This extension might have to be defined per-scene instead of per-file.

    NEEDLE_lightmaps

    This is a root extension defining a set of lightmaps for the glTF file.

    "NEEDLE_lightmaps": {
    +  "textures": [
    +    {
    +      "pointer": "textures/20",
    +      "type": 1,
    +      "index": 0
    +    }
    +  ]
    +}
    +

    Note: At the moment this extension also contains environment texture references. We're planning to change that in a future release.

    Texture TypeValue
    Lightmap0
    Environment Map1
    Reflection Map2

    How lightmaps are applied is defined in the MeshRenderer component inside the NEEDLE_components extension per node:

    "NEEDLE_components": {
    +  "builtin_components": [
    +    {
    +      "name": "MeshRenderer",
    +      ...
    +      "lightmapIndex": 0,
    +      "lightmapScaleOffset": {
    +        "x": 1.00579774,
    +        "y": 1.00579774,
    +        "z": -0.00392889744,
    +        "w": -0.00392889744
    +      },
    +      ...
    +    }
    +  ]
    +}
    +

    Note: We may change that in a future release and move lightmap-related data to a NEEDLE_lightmap extension entry per node.

    NEEDLE_persistent_assets

    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.

    {
    +  "name": "LampionController",
    +  "guid": "9100000_ecab75bc7ab51a747a4c5c14236a43cd",
    +  "parameters": [],
    +  "layers": [
    +    {
    +      "name": "Base Layer",
    +      "stateMachine": {
    +        "name": "Base Layer",
    +        "defaultState": 0,
    +        "states": [
    +          {
    +            "name": "LampionFlying",
    +            "hash": 677739540,
    +            "motion": {
    +              "name": "LampionFlying",
    +              "isLooping": false,
    +              "guid": "7400000_c296c4d76e956b34f8b5833ba90653c1",
    +              "clips": [
    +                {
    +                  "node": "nodes/4",
    +                  "clip": "animations/0"
    +                },
    +                {
    +                  "node": "nodes/9",
    +                  "clip": "animations/6"
    +                },
    +                {
    +                  "node": "nodes/14",
    +                  "clip": "animations/12"
    +                }
    +              ]
    +            },
    +            "transitions": [
    +              {
    +                "isExit": false,
    +                "exitTime": 1,
    +                "hasFixedDuration": true,
    +                "offset": 0,
    +                "duration": 0,
    +                "hasExitTime": true,
    +                "destinationState": 0,
    +                "conditions": []
    +              }
    +            ]
    +          }
    +        ],
    +        "entryTransitions": []
    +      }
    +    }
    +  ]
    +},
    +{
    +  "name": "TrongCom Website",
    +  "guid": "11400000_93a8f856fe26af8498d94efe4835af36",
    +  "tracks": [
    +    {
    +      "name": "Markers",
    +      "type": "MarkerTrack",
    +      "muted": false,
    +      "outputs": [
    +        null
    +      ],
    +      "clips": [],
    +      "markers": [],
    +      "trackOffset": null
    +    },
    +    {
    +      "name": "Animation Track",
    +      "type": "AnimationTrack",
    +      "muted": false,
    +      "outputs": [
    +        "5017454109690854928_1668529989451832962"
    +      ],
    +      "clips": [
    +        {
    +          "start": 0,
    +          "end": 0.9833333333333333,
    +          "duration": 0.9833333333333333,
    +          "timeScale": 1,
    +          "asset": {
    +            "clip": "animations/78",
    +            "loop": false,
    +            "duration": 8,
    +            "position": {
    +              "x": 0,
    +              "y": 0,
    +              "z": 0
    +            },
    +            "rotation": {
    +              "x": 0,
    +              "y": 0,
    +              "z": 0,
    +              "w": 1
    +            },
    +            "removeStartOffset": true
    +          },
    +          "clipIn": 0,
    +          "easeInDuration": 0,
    +          "easeOutDuration": 0.41666666666666663,
    +          "preExtrapolationMode": 1,
    +          "postExtrapolationMode": 1
    +        },
    +        ... 
    +      ]
    +    }
    +  ]
    +}
    +

    Note: We might include more type and versioning information in the future.

    NEEDLE_techniques_webgl

    `,22),H={href:"https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Archived/KHR_techniques_webgl",target:"_blank",rel:"noopener noreferrer"},K=s("code",null,"KHR_techniques_webgl",-1),A=t(`
    "KHR_techniques_webgl": {
    +  "programs": [
    +    {
    +      "vertexShader": 1,
    +      "fragmentShader": 0,
    +      "id": 0
    +    }
    +  ],
    +  "shaders": [
    +    {
    +      "name": "Pass-FRAGMENT",
    +      "type": 35632,
    +      "uri": "<embedded WebGL fragment shader code ...>",
    +      "id": 1
    +    },
    +    {
    +      "name": "Pass-VERTEX",
    +      "type": 35633,
    +      "uri": "<embedded WebGL vertex shader code ...>",
    +      "id": 0
    +    }
    +  ],
    +  "techniques": [
    +    {
    +      "program": 0,
    +      "attributes": {},
    +      "uniforms": {
    +        "_TimeParameters": {
    +          "name": "_TimeParameters",
    +          "type": 35666,
    +          "semantic": null,
    +          "count": 1,
    +          "node": 0
    +        },
    +        "hlslcc_mtx4x4unity_MatrixVP": {
    +          "name": "hlslcc_mtx4x4unity_MatrixVP",
    +          "type": 35666,
    +          "semantic": "_VIEWPROJECTION",
    +          "count": 4,
    +          "node": 0
    +        }
    +      },
    +      "defines": []
    +    }
    +  ]
    +}     
    +

    Note: Currently, vertex and fragment shaders are always embedded as URI; we plan on moving that data into more reasonable bufferViews in the future.

    Note: There's some redundant properties in here that we plan on cleaning up.

    TypeScript and Data Mapping

    ๐Ÿ—๏ธ Under Construction

    Rendering with three.js

    ๐Ÿ—๏ธ Under Construction

    Why aren't you compiling to WebAssembly?

    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.

    `,13);function C(O,P){const e=o("ExternalLinkIcon"),p=o("RouterLink");return r(),l("div",null,[d,s("p",null,[n("Models, textures, animations, lights, cameras and more are stored as "),s("a",k,[n("glTF 2.0 files"),a(e)]),n(" in Needle Engine."),m,n(" Custom data is stored in "),v,n(". These cover everything from interactive components to physics, sequencing and lightmaps.")]),b,s("p",null,[n("More extensions and custom extensions can be added using the export callbacks of UnityGLTF (not documented yet) and the "),s("a",q,[n("glTF import extensions"),a(e)]),n(" of three.js.")]),h,s("p",null,[s("a",g,[n("Learn more about GLTF loading in three.js"),a(e)])]),y,s("p",null,[n("For production, we compress glTF assets with "),s("a",f,[_,a(e)]),n(". Textures use either "),x,n(", "),E,n(", "),w,n(" or no compression, depending on texture type. Meshes use "),T,n(" by default but can be configured to use "),N,n(" (per glTF file). Custom extensions are passed through in an opaque way.")]),s("p",null,[n("See the "),a(p,{to:"/deployment.html#optimization-and-compression-options"},{default:c(()=>[n("deployment & compression")]),_:1}),n(" page for more information")]),L,s("p",null,[n("Data in "),j,n(" can be animated via the currently not ratified "),s("a",R,[D,a(e)]),n(" extension.")]),S,s("p",null,[n("This extension contains additional per-node data related to state, layers, and tags. Layers are used for both rendering and physics, similar to how "),s("a",F,[n("three.js"),a(e)]),n(" and "),s("a",M,[n("Unity"),a(e)]),n(" treat them.")]),W,s("p",null,[n("This extension builds upon the archived "),s("a",H,[K,a(e)]),n(" 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.")]),A])}const G=i(u,[["render",C],["__file","technical-overview.html.vue"]]);export{G as default}; diff --git a/assets/technical-overview.html-f395cd1d.js b/assets/technical-overview.html-f395cd1d.js new file mode 100644 index 000000000..493a10ce6 --- /dev/null +++ b/assets/technical-overview.html-f395cd1d.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-3b00a577","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{e as data}; diff --git a/assets/testimonial-9717e223.js b/assets/testimonial-9717e223.js new file mode 100644 index 000000000..d1a5289b2 --- /dev/null +++ b/assets/testimonial-9717e223.js @@ -0,0 +1 @@ +import{_ as a,p as o,q as n,R as e,Q as _,s as c,t as r,v as i}from"./framework-f6820c83.js";const d={props:{name:String,src:String}},l={class:"root"},f={class:"testimonial"},p={class:"quote"},m={class:"name"},u=["href"];function h(t,v,s,g,k,x){return o(),n("div",l,[e("div",f,[_(' โ€œ '),e("span",p,[c(t.$slots,"default",{},void 0,!0)]),e("span",m,[r(" โ€” "),e("a",{href:s.src,target:"_blank"},i(s.name),9,u)])])])}const y=a(d,[["render",h],["__scopeId","data-v-47fef520"],["__file","testimonial.vue"]]);export{y as default}; diff --git a/assets/testimonials.html-e5440bb3.js b/assets/testimonials.html-e5440bb3.js new file mode 100644 index 000000000..a5af0a08f --- /dev/null +++ b/assets/testimonials.html-e5440bb3.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7e348068","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":1725399379000},"filePathRelative":"testimonials.md"}');export{e as data}; diff --git a/assets/testimonials.html-ee19aec9.js b/assets/testimonials.html-ee19aec9.js new file mode 100644 index 000000000..78b54b276 --- /dev/null +++ b/assets/testimonials.html-ee19aec9.js @@ -0,0 +1 @@ +import{_ as n,M as o,p as r,q as l,N as s,V as a,t as e,R as i}from"./framework-f6820c83.js";const h={},c=i("h1",{id:"testimonials",tabindex:"-1"},[i("a",{class:"header-anchor",href:"#testimonials","aria-hidden":"true"},"#"),e(" Testimonials")],-1),d=i("p",null,null,-1);function m(u,p){const t=o("testimonial");return r(),l("div",null,[c,d,s(t,{name:"Rinesh Thomas",src:"https://twitter.com/rineshthomas/status/1566342798063947777?t=z6sG3Z7mol-NfIRfTTKqCQ&s=19"},{default:a(()=>[e(" This is the best thing I have seen after cinemachine in unity. Unity should acquire this ")]),_:1}),s(t,{name:"Chris Mahoney",src:"https://twitter.com/mahoneymatic/status/1562981022932684800?t=qNqojoZkk2CZrJa7dGzqng&s=19"},{default:a(()=>[e(" Unbelievable Unity editor integration by an order of magnitude, and as straightforward as the docs claim. Wow. ")]),_:1}),s(t,{name:"Kevin Curry",src:"https://twitter.com/kmcurry/status/1574333302022062080"},{default:a(()=>[e(" needle.tools is a wonderful showcase of what @NeedleTools contributes to 3D via the web. I just love it. ")]),_:1}),s(t,{name:"Stella Cannefax",src:"https://twitter.com/0xstella/status/1574853012585172993"},{default:a(()=>[e(" 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(t,{name:"Brit Gardner",src:"https://twitter.com/britg/status/1562443905580163072"},{default:a(()=>[e(" Played with this a bit this morning ๐Ÿคฏ๐Ÿคฏ pretty magical ")]),_:1}),s(t,{name:"Marc Wakefield",src:"https://twitter.com/mrm_design/status/1567391880169545729"},{default:a(()=>[e(" 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(t,{name:"Pete Patterson",src:"https://twitter.com/VRSpatialist/status/1572300394285383680"},{default:a(()=>[e(" Finally checking out @NeedleTools with Unity. Super easy to get something up and running in the cloud using their glitch integration ")]),_:1}),s(t,{name:"Dilmer Valecillos",src:"https://twitter.com/Dilmerv/status/1562209049856188420"},{default:a(()=>[e(" This is amazing and if you are curious about #WebXR with Unity this will help us get there ")]),_:1}),s(t,{name:"VRSpatialist",src:"https://discord.com/channels/717429793926283276/722046635525537842/1030201907513405530"},{default:a(()=>[e(" 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(t,{name:"Unity for Digital Twins",src:"https://twitter.com/DigitalTwin/status/1576934958681055233"},{default:a(()=>[e(" We just gotta say WOW ๐Ÿคฉ ")]),_:1}),s(t,{name:"Matthew Pieri",src:"https://discord.com/channels/717429793926283276/1097572505738301571/1097572505738301571"},{default:a(()=>[e(" 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(t,{name:"Yuzu",src:"https://discord.com/channels/717429793926283276/1264966222467043433/1265268833485066240"},{default:a(()=>[e(" My workflow has been optimized 10X ever since i started using needle ")]),_:1})])}const f=n(h,[["render",m],["__file","testimonials.html.vue"]]);export{f as default}; diff --git a/assets/testing.html-09ed932c.js b/assets/testing.html-09ed932c.js new file mode 100644 index 000000000..00dc88859 --- /dev/null +++ b/assets/testing.html-09ed932c.js @@ -0,0 +1 @@ +import{_ as a,M as r,p as l,q as s,R as e,t,N as n,a1 as i}from"./framework-f6820c83.js";const d="/docs/testing/switch-to-mkcert.webp",c={},h=i('

    Testing on local devices

    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.

    TIP

    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".

    Setting up a self-signed certificate for development

    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:

    TIP

    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.

    ',7),u=e("strong",null,"Default โ€“ with auto-generated untrusted certificate",-1),p=e("br",null,null,-1),f=e("em",null,"Displays a certificate warning upon opening the project in a browser.",-1),m={href:"https://github.com/vitejs/vite-plugin-basic-ssl",target:"_blank",rel:"noopener noreferrer"},g=i("

    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.

    OSViewing 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)โŒ
    ",2),w=e("strong",null,"With a self-signed, trusted root certificate",-1),v=e("br",null,null,-1),y=e("em",null,"No security warning is displayed. You need to install the generated certificate on your device(s).",-1),b=e("br",null,null,-1),k={href:"https://github.com/liuweiGL/vite-plugin-mkcert",target:"_blank",rel:"noopener noreferrer"},_=i('
    OSViewing in the browserAutomatic reloads
    Windowsโœ“โœ“
    Linuxโœ“โœ“
    Androidโœ“โœ“
    macOSโœ“โœ“
    iOSโœ“โœ“

    Generating a self-signed development certificate

    • 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.

    Installing the certificate on your development devices

    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.

    Installing the certificate on Android

    1. Open the file. You'll be prompted to install the certificate.

    Installing the certificate on iOS / iPadOS / VisionOS

    1. Open the file.
    2. You'll be prompted to add the profile to your device. Confirm.
    3. Go to Settings
    4. There will be a new entry "Profile". Select it and allow the profile to be active for this device.
    5. 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.

    TIP

    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.

    Installing the certificate on another MacOS machine

    1. Open the file. Keychain Access will open and allow you to install the certificate.
    2. You may have to set "Trust" to "Always allow".

    Installing the certificate on another Windows machine

    1. Open certmgr ("Manage user certificates") by typing Windows key + certmgr.
    2. In the left sidebar, select "Trusted Root Certification Authorities".
    3. Right-click on "Certificates" and select "All Tasks > Import".
    4. Select the rootCA.pem file (you may have to change the file type to "all") and follow the instructions.
    ',16);function S(q,O){const o=r("ExternalLinkIcon");return l(),s("div",null,[h,e("p",null,[u,p,f,e("em",null,[t("Uses the "),e("a",m,[t("vite-plugin-basic-ssl"),n(o)]),t(" npm package.")])]),g,e("p",null,[w,v,y,b,e("em",null,[t("Uses the "),e("a",k,[t("vite-plugin-mkcert"),n(o)]),t(" npm package.")])]),_])}const x=a(c,[["render",S],["__file","testing.html.vue"]]);export{x as default}; diff --git a/assets/testing.html-4f37a182.js b/assets/testing.html-4f37a182.js new file mode 100644 index 000000000..70427c886 --- /dev/null +++ b/assets/testing.html-4f37a182.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-53401e42","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{e as data}; diff --git a/assets/texture-compression-8cd31165.js b/assets/texture-compression-8cd31165.js new file mode 100644 index 000000000..e374e6f24 --- /dev/null +++ b/assets/texture-compression-8cd31165.js @@ -0,0 +1 @@ +const e="/docs/blender/texture-compression.webp";export{e as _}; diff --git a/assets/typescript-decorators.html-64c32bd4.js b/assets/typescript-decorators.html-64c32bd4.js new file mode 100644 index 000000000..c7a675d3d --- /dev/null +++ b/assets/typescript-decorators.html-64c32bd4.js @@ -0,0 +1,51 @@ +import{_ as e,M as t,p as o,q as p,R as n,t as c,N as i,a1 as s}from"./framework-f6820c83.js";const l={},r=s(`

    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
    @registerTypeNo argument. Can be added to a custom component class to be registered to the Needle Engine types and to enable hot reloading support.

    Examples

    Serializable

    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[];
    +}
    +

    SyncField

    export class MyScript extends Behaviour {
    +
    +    @syncField(MyScript.prototype.onNumberChanged)
    +    @serializable()
    +    myNumber: number = 42;
    +
    +    private onNumberChanged(newValue : number, oldValue : number){
    +        console.log("Number changed from ", oldValue, "to", newValue)
    +    }
    +}
    +

    Validate

    export class MyScript extends Behaviour {
    +
    +    @validate()
    +    @serializable()
    +    myNumber?: number;
    +
    +    start() { setInterval(() => this.myNumber = Math.random(), 1000) }
    +
    +    onValidate(fieldName: string) {
    +        console.log("Validate", fieldName, this.myNumber);
    +    }
    +}
    +

    Prefix

    `,11),u={href:"https://stackblitz.com/edit/needle-engine-prefix-example?file=src%2Fmain.ts",target:"_blank",rel:"noopener noreferrer"},d=s(`
    import { Camera } 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
    +    }
    +}
    +
    `,1);function k(m,v){const a=t("ExternalLinkIcon");return o(),p("div",null,[r,n("p",null,[n("a",u,[c("Live example"),i(a)])]),d])}const h=e(l,[["render",k],["__file","typescript-decorators.html.vue"]]);export{h as default}; diff --git a/assets/typescript-decorators.html-d58d6f08.js b/assets/typescript-decorators.html-d58d6f08.js new file mode 100644 index 000000000..f6a0c83c2 --- /dev/null +++ b/assets/typescript-decorators.html-d58d6f08.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6d28ca94","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":1725399379000},"filePathRelative":"reference/typescript-decorators.md"}');export{e as data}; diff --git a/assets/typescript-essentials.html-534c1795.js b/assets/typescript-essentials.html-534c1795.js new file mode 100644 index 000000000..05bcd7e1d --- /dev/null +++ b/assets/typescript-essentials.html-534c1795.js @@ -0,0 +1,83 @@ +import{_ as c,M as o,p as i,q as l,R as n,t as s,N as a,V as r,a1 as t}from"./framework-f6820c83.js";const u={},d=n("p",null,"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.",-1),k=n("p",null,"Here are also some useful resources for learning how to write Typescript:",-1),m={href:"https://www.typescripttutorial.net/",target:"_blank",rel:"noopener noreferrer"},h={href:"https://www.tutorialsteacher.com/typescript",target:"_blank",rel:"noopener noreferrer"},v={href:"https://www.typescriptlang.org/docs/",target:"_blank",rel:"noopener noreferrer"},y=t('

    Key differences between C#, Javascript or 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.

    Types โ€” or the lack thereof

    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.

    ',8),b=n("em",null,"vanilla Javascript",-1),g={href:"https://jsdoc.app/",target:"_blank",rel:"noopener noreferrer"},w=t('

    Variables

    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;

    ',3),f=n("em",null,"Be aware of var",-1),_=n("br",null,null,-1),x=n("code",null,"var",-1),E=n("code",null,"let",-1),V={href:"https://stackoverflow.com/a/11444416",target:"_blank",rel:"noopener noreferrer"},T=t(`

    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:

    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:

    const myPosition : Vector3 = new Vector3(0, 0, 0);
    +myPosition = new Vector3(100, 0, 0); // โš  ASSIGNING TO CONST IS NOT ALLOWED
    +

    Using or Importing Types

    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);
    +

    Primitive Types

    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 Vector(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:

    const myVector = new Vector3(1,1,1);
    +const myOtherVector = myVector;
    +myOtherVector.x = 42;
    +// will log: 42, 42
    +console.log(myVector.x, myOtherVector.x); 
    +

    Vector Maths and Operators

    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)

    const myFirstVector : Vector3 = new Vector3(1, 1, 1)
    +const myFactor = 100f;
    +myFirstVector.multiplyScalar(myFactor);
    +// โ†’ myFirstVector is now 100, 100, 100
    +

    Equality Checks

    loose vs strict comparison

    In C# when you want to check if two variables are the same you can write it as follows:

    var playerIsNull = myPlayer == null;
    +

    in Javascript/Typescript there is a difference between == and === where === is more strictly checking for the type:

    const playerIsNull = myPlayer === null;
    +const playerIsNullOrUndefined = myPlayer == null;
    +
    `,33),M=n("code",null,"playerIsNullOrUndefined",-1),C=n("code",null,"==",-1),I=n("code",null,"null",-1),O=n("code",null,"undefined",-1),q=n("code",null,"true",-1),F={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness",target:"_blank",rel:"noopener noreferrer"},L=t(`

    Events, Binding and this

    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)

    `,5),J=n("strong",null,"recommended",-1),N={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions",target:"_blank",rel:"noopener noreferrer"},P=t(`
    myEvent?: EventList;
    +void onEnable() {
    +    this.myEvent.addEventListener(this.onMyEvent);
    +}
    +void onDisable() {
    +    this.myEvent.removeEventListener(this.onMyEvent);
    +}
    +// Declaring the function as an arrow method
    +// 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):

    myEvent?: EventList;
    +private _onMyEventFn?: Function;
    +void onEnable() {
    +    // bind this
    +    this._onMyEventFn = this.onMyEvent.bind(this);
    +    // add the bound method to the event
    +    this.myEvent?.addEventListener(this._onMyEventFn);
    +} 
    +void onDisable() {
    +    this.myEvent?.removeEventListener(this._onMyEventFn);
    +}
    +

    What's next?

    `,4);function S(j,U){const e=o("ExternalLinkIcon"),p=o("RouterLink");return i(),l("div",null,[d,k,n("ul",null,[n("li",null,[n("a",m,[s("Typescript Tutorial"),a(e)])]),n("li",null,[n("a",h,[s("Learn Typescript"),a(e)])]),n("li",null,[n("a",v,[s("Typescript Documentation"),a(e)])])]),y,n("p",null,[s("While "),b,s(" does not offer types you can still add type-annotations to your javascript variables, classes and methods by using "),n("strong",null,[n("a",g,[s("JSDoc"),a(e)])]),s(".")]),w,n("blockquote",null,[n("p",null,[f,_,s(" You might come across the "),x,s(" keyword in javascript as well but it is not recommended to use it and the modern replacement for it is "),E,s(". Learn more about "),n("a",V,[s("var vs let"),a(e)]),s(".")])]),T,n("p",null,[s("You notice that the second variable "),M,s(" is using "),C,s(" which does a loose equality check in which case "),I,s(" and "),O,s(" will both result in "),q,s("here. You can read more about that "),n("a",F,[s("here"),a(e)])]),L,n("p",null,[s("The short and "),J,s(" syntax for doing this is to use "),n("a",N,[s("Arrow Functions"),a(e)]),s(".")]),P,n("ul",null,[n("li",null,[a(p,{to:"/scripting.html"},{default:r(()=>[s("Needle Engine Scripting")]),_:1})])])])}const D=c(u,[["render",S],["__file","typescript-essentials.html.vue"]]);export{D as default}; diff --git a/assets/typescript-essentials.html-67bd72a7.js b/assets/typescript-essentials.html-67bd72a7.js new file mode 100644 index 000000000..dee7fe3ac --- /dev/null +++ b/assets/typescript-essentials.html-67bd72a7.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-2e0e70fc","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":1725399379000},"filePathRelative":"getting-started/typescript-essentials.md"}`);export{e as data}; diff --git a/assets/vanilla-js.html-7228f453.js b/assets/vanilla-js.html-7228f453.js new file mode 100644 index 000000000..32bbbfac3 --- /dev/null +++ b/assets/vanilla-js.html-7228f453.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7acec8c5","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":"---\\nWhile our default template uses vite as powerful bundler, Needle Engine can also be used directly with vanilla Javascript โ€“ without using any bundler.\\nA basic sample can be found here.\\nMany examples can be found on StackBlitz."}]],"description":"---\\nWhile our default template uses vite as powerful bundler, Needle Engine can also be used directly with vanilla Javascript โ€“ without using any bundler.\\nA basic sample can be found here.\\nMany examples can be found on StackBlitz."},"headers":[{"level":3,"title":"How to run locally","slug":"how-to-run-locally","link":"#how-to-run-locally","children":[]}],"git":{"updatedTime":1725399379000},"filePathRelative":"vanilla-js.md"}');export{e as data}; diff --git a/assets/vanilla-js.html-f7e2599d.js b/assets/vanilla-js.html-f7e2599d.js new file mode 100644 index 000000000..4d77d2b1d --- /dev/null +++ b/assets/vanilla-js.html-f7e2599d.js @@ -0,0 +1,53 @@ +import{_ as e,M as p,p as o,q as l,R as n,t as s,N as t,a1 as c}from"./framework-f6820c83.js";const i={},u=n("h1",{id:"using-needle-engine-directly-from-html",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#using-needle-engine-directly-from-html","aria-hidden":"true"},"#"),s(" Using Needle Engine directly from HTML")],-1),r={href:"https://vitejs.dev",target:"_blank",rel:"noopener noreferrer"},k={href:"https://engine.needle.tools/samples/vanillajs/",target:"_blank",rel:"noopener noreferrer"},d=n("br",null,null,-1),v={href:"https://stackblitz.com/@marwie/collections/needle-engine",target:"_blank",rel:"noopener noreferrer"},g=n("h3",{id:"how-to-run-locally",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#how-to-run-locally","aria-hidden":"true"},"#"),s(" How to run locally")],-1),m={href:"https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer",target:"_blank",rel:"noopener noreferrer"},h=n("li",null,[s("Then open this index.html with the live-server and navigate to "),n("code",null,"http://localhost:5500/index.html"),s(" in your web browser.")],-1),b={class:"custom-container tip"},q=n("p",{class:"custom-container-title"},"TIP",-1),_=n("code",null,'',-1),y={href:"https://github.com/needle-tools/needle-engine-samples/raw/main/vanilla/myScene.glb",target:"_blank",rel:"noopener noreferrer"},f=n("code",null,"myScene.glb",-1),w=c(`
    <!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>
    +
    `,1),x={href:"https://github.com/needle-tools/needle-engine-samples/tree/main/vanilla",target:"_blank",rel:"noopener noreferrer"};function j(N,S){const a=p("ExternalLinkIcon");return o(),l("div",null,[u,n("p",null,[s("While our default template uses "),n("a",r,[s("vite"),t(a)]),s(" as powerful bundler, Needle Engine can also be used directly with vanilla Javascript โ€“ without using any bundler.")]),n("p",null,[s("A basic sample can be "),n("a",k,[s("found here"),t(a)]),s("."),d,s(" Many examples can be found on "),n("a",v,[s("StackBlitz"),t(a)]),s(".")]),g,n("ul",null,[n("li",null,[s("Install the "),n("a",m,[s("VSCode LiveServer extension"),t(a)]),s(".")]),h]),n("div",b,[q,n("p",null,[s("Make sure to update the "),_,s(" path to an existing glb file or "),n("a",y,[s("download this sample glb"),t(a)]),s(" and put it in the same folder as the index.html, name it "),f,s(" or update the src path.")])]),w,n("p",null,[n("a",x,[s("View on github"),t(a)])])])}const T=e(i,[["render",j],["__file","vanilla-js.html.vue"]]);export{T as default}; diff --git a/assets/video-embed-0d320302.js b/assets/video-embed-0d320302.js new file mode 100644 index 000000000..8135557ce --- /dev/null +++ b/assets/video-embed-0d320302.js @@ -0,0 +1 @@ +import{_ as c,p as s,q as i,R as a,Q as l,ad as _}from"./framework-f6820c83.js";const d={props:{src:String,controls:Boolean,limit_height:Boolean,max_height:String}},o=d,n=()=>{_(e=>({"5159d46b":e.limit_height?"400px":"initial","4cc05253":e.limit_height?e.max_height:"initial","49dcf46c":e.limit_height?"auto":"100%","25a42c34":e.limit_height?"100%":"auto","02f9f579":e.limit_height?e.max_height:"100%"}))},r=o.setup;o.setup=r?(e,t)=>(n(),r(e,t)):n;const p={key:0,class:"container"},h=["src"],m={key:1,class:"container"},u=["src"];function f(e,t,v,g,y,b){return e.src.includes("youtube.com")?(s(),i("div",p,[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",frameborder:"0",allowfullscreen:""},null,8,h)])):(s(),i("div",m,[l(' '),a("video",{loop:"",autoplay:"",controls:"",src:e.src},null,8,u)]))}const k=c(o,[["render",f],["__scopeId","data-v-144c2fbe"],["__file","video-embed.vue"]]);export{k as default}; diff --git a/assets/vision.html-9d98bfd6.js b/assets/vision.html-9d98bfd6.js new file mode 100644 index 000000000..4b4c5519e --- /dev/null +++ b/assets/vision.html-9d98bfd6.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-2ace8550","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{e as data}; diff --git a/assets/vision.html-c80404e1.js b/assets/vision.html-c80404e1.js new file mode 100644 index 000000000..848dc1c3d --- /dev/null +++ b/assets/vision.html-c80404e1.js @@ -0,0 +1 @@ +import{_ as s,M as n,p as l,q as d,R as t,t as e,N as o,V as h,a1 as r}from"./framework-f6820c83.js";const c={},p=t("h1",{id:"our-vision",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#our-vision","aria-hidden":"true"},"#"),e(" Our Vision ๐Ÿ”ฎ")],-1),u=t("h2",{id:"the-future-of-the-3d-web",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#the-future-of-the-3d-web","aria-hidden":"true"},"#"),e(" The Future of the 3D Web")],-1),g={href:"https://web.dev/progressive-web-apps/",target:"_blank",rel:"noopener noreferrer"},b={href:"https://immersive-web.github.io/webxr-samples/",target:"_blank",rel:"noopener noreferrer"},m=r('

    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.

    Why another platform for 3D on the web? Aren't there enough options already?

    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.

    Creating a Workflow, not an Editor

    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.

    Open Standards instead of Proprietary Containers

    ',10),w={href:"https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html",target:"_blank",rel:"noopener noreferrer"},f=t("code",null,".glb",-1),y=t("p",null,"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.",-1),v=r('

    Goals and Non-Goals

    Goals

    • Iteration should be rapid and deployment should be fast.
    • Working on 3D web projects should be the as easy as working 2D web projects.
    • Developers and artists should be able to collaborate directly.
    • Responsive web extends beyond screens โ€“ AR and VR should be built in, not afterthoughts.
    • We want to contribute back to open-source projects.
    • Open discussion regarding 3D and web standards.
    • Ability to bring and take your data in open formats.
    • Ability to choose what web framework you use, not lock-in to particular frameworks and vendors.
    • Common usecases work without or with limited coding experience.

    Non-Goals

    • It's not a goal to have 100% coverage of all combinations of Editor versions, feature sets, render pipelines.
    • It's not a goal to provide a full no-code environment.
    • It's not a goal to match the feature set, capabilities, or runtime performance of other engines.

    Relation to other engines and frameworks

    Needle Engine and Unity WebGL

    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 and three.js

    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);function k(_,x){const a=n("ExternalLinkIcon"),i=n("RouterLink");return l(),d("div",null,[p,u,t("p",null,[e("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 "),t("a",g,[e("PWA"),o(a)]),e(". New VR and AR devices will "),t("a",b,[e("extend into the web"),o(a)]),e(`, 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!`)]),m,t("p",null,[e("At the core of Needle Engine stands the "),t("a",w,[e("glTF"),o(a)]),e(" format and its ability to be extended with custom extensions. The goal is: a single "),f,e(" file can contain your entire application's data.")]),y,t("p",null,[o(i,{to:"/technical-overview.html"},{default:h(()=>[e("Read more about our use of glTF and extensions")]),_:1})]),v])}const A=s(c,[["render",k],["__file","vision.html.vue"]]);export{A as default}; diff --git a/assets/xr.html-4b92f962.js b/assets/xr.html-4b92f962.js new file mode 100644 index 000000000..87204afbd --- /dev/null +++ b/assets/xr.html-4b92f962.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-099a1df4","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":1725182787000},"filePathRelative":"xr.md"}`);export{e as data}; diff --git a/assets/xr.html-704d5e10.js b/assets/xr.html-704d5e10.js new file mode 100644 index 000000000..37ee3a9ec --- /dev/null +++ b/assets/xr.html-704d5e10.js @@ -0,0 +1,16 @@ +import{_ as c,M as i,p as d,q as p,R as e,t,N as n,V as o,a1 as r}from"./framework-f6820c83.js";const u={},h=r('

    Supported Devices

    Theoretically all WebXR-capable devices and browsers should be supported. That being said, we've tested the following configurations:

    Tested VR DeviceBrowserNotes
    Apple Vision Proโœ”๏ธ Safari Browserhand tracking, support for transient pointer
    Meta Quest 1โœ”๏ธ Meta Browserhand tracking, support for sessiongranted1
    Meta Quest 2โœ”๏ธ Meta Browserhand tracking, support for sessiongranted1, passthrough (black and white)
    Meta Quest 3โœ”๏ธ Meta Browserhand tracking, support for sessiongranted1, passthrough, depth sensing, mesh tracking
    Meta Quest Proโœ”๏ธ Meta Browserhand tracking, support for sessiongranted1, passthrough
    Pico Neo 3โœ”๏ธ Pico Browserno hand tracking, inverted controller thumbsticks
    Pico Neo 4โœ”๏ธ Pico Browserpassthrough, hand tracking2
    Oculus Rift 1/2โœ”๏ธ Chrome
    Hololens 2โœ”๏ธ Edgehand tracking, support for AR and VR (in VR mode, background is rendered as well)
    Looking Glass Portraitโœ”๏ธ Chromerequires shim, see samples
    ',3),m=e("thead",null,[e("tr",null,[e("th",null,"Tested AR Device"),e("th",null,"Browser"),e("th",null,"Notes")])],-1),g=e("tr",null,[e("td",null,"Android 10+"),e("td",null,"โœ”๏ธ Chrome"),e("td")],-1),b=e("tr",null,[e("td",null,"Android 10+"),e("td",null,"โœ”๏ธ Firefox"),e("td")],-1),_=e("tr",null,[e("td",null,"iOS 15+"),e("td",null,"โœ”๏ธ WebXR Viewer"),e("td",null,"does not fully implement standards, but supported")],-1),f=e("td",null,"iOS 15+",-1),k=e("td",null,[t("(โœ”๏ธ)"),e("sup",null,"3"),t(" Safari")],-1),v=e("tr",null,[e("td",null,"Hololens 2"),e("td",null,"โœ”๏ธ Edge"),e("td")],-1),y=e("tr",null,[e("td",null,"Hololens 1"),e("td",null,"โŒ"),e("td",null,"no WebXR support")],-1),w=e("tr",null,[e("td",null,"Magic Leap 2"),e("td",null,"โœ”๏ธ"),e("td")],-1),R=e("table",null,[e("thead",null,[e("tr",null,[e("th",null,"Not Tested but Should Workโ„ข๏ธ"),e("th",null,"Browser"),e("th",null,"Notes")])]),e("tbody",null,[e("tr",null,[e("td",null,"Magic Leap 1"),e("td"),e("td",null,"please let us know if you tried!")])])],-1),A=e("sup",null,"1",-1),x=e("code",null,"chrome://flags/#webxr-navigation-permission",-1),S=e("br",null,null,-1),T=e("sup",null,"2",-1),E=e("br",null,null,-1),j=e("sup",null,"3",-1),q=e("a",{href:"#augmented-reality-and-webxr-on-ios"},"other approaches",-1),O=e("h2",{id:"examples",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#examples","aria-hidden":"true"},"#"),t(" Examples")],-1),B={href:"https://engine.needle.tools/samples/?overlay=samples&tag=xr",target:"_blank",rel:"noopener noreferrer"},C=r('

    Adding VR and AR capabilities to a scene

    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.

    Basic capabilities

    • 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.

    Multiplayer

    • Enable Networking
      Add a SyncedRoom component.

    • Enable Desktop Viewer Sync
      Add a SyncedCamera component.

    • Enable Voice Chat
      Add a VoIP component.

    Note: these components can be anywhere inside your GltfObject hierarchy. They can also all be on the same GameObject.

    ',7),W={href:"https://castle.needle.tools/",target:"_blank",rel:"noopener noreferrer"},X=e("br",null,null,-1),V=r('

    Special AR Components

    • 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.

    Controlling object display for XR

    • 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).

    Travelling between VR worlds

    ',5),N={href:"https://github.com/immersive-web/navigation",target:"_blank",rel:"noopener noreferrer"},M=e("code",null,"sessiongranted",-1),P=e("p",null,[t("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."),e("br"),t(" Requires enabling a browser flag: "),e("code",null,"chrome://flags/#webxr-navigation-permission")],-1),I=e("ul",null,[e("li",null,[e("strong",null,"Click on objects to open links"),e("br"),t(" Add the "),e("code",null,"OpenURL"),t(" component that makes it very easy to build connected worlds.")])],-1),D=e("h2",{id:"scripting",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#scripting","aria-hidden":"true"},"#"),t(" Scripting")],-1),L=r(`

    Avatars

    While we don't currently provide an out-of-the-box integration external avatar systems, you can create application-specific avatars or custom systems.

    • Create a custom Avatar
      • Create an empty GameObject as avatar root
      • Add an object named Head and add a XRFlag that's set to Third Person
      • Add objects named HandLeft and HandRight
      • Add your graphics below these objects.

    Experimental Avatar Components

    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.

    20220817-230858-87dG-Unity_PLjQ
    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.

      image
      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!

    HTML Content Overlays in AR

    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).

    `,11),G=e("code",null,".ar-session-active",-1),H={href:"https://www.w3.org/TR/webxr-dom-overlays-1/#css-pseudo-class",target:"_blank",rel:"noopener noreferrer"},F=e("code",null,":xr-overlay",-1),U=r(`
    .only-in-ar {
    +  display: none;
    +}
    +
    +.ar-session-active .only-in-ar {
    +  display:initial;
    +}
    +
    `,1),Q={href:"https://www.w3.org/TR/webxr-dom-overlays-1/#ua-style-sheet-defaults",target:"_blank",rel:"noopener noreferrer"},z=e("em",null,"inside",-1),K=e("code",null,'class="ar"',-1),Y=e("h2",{id:"image-tracking",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#image-tracking","aria-hidden":"true"},"#"),t(" Image Tracking")],-1),Z={href:"https://github.com/immersive-web/marker-tracking/blob/main/explainer.md",target:"_blank",rel:"noopener noreferrer"},J=e("br",null,null,-1),$=e("ul",null,[e("li",null,[t("Enable "),e("code",null,"WebXR Incubations"),t(" in chrome")]),e("li",null,[t("Add the "),e("code",null,"WebXRImageTracking"),t(" component")])],-1),ee=e("p",null,[t("Without that spec, one can still request camera image access and run custom algorithms to determine device pose."),e("br"),t(" Libraries to add image tracking:")],-1),te={href:"https://github.com/AR-js-org/AR.js",target:"_blank",rel:"noopener noreferrer"},ne={href:"https://github.com/FireDragonGameStudio/NeedleAndARjs",target:"_blank",rel:"noopener noreferrer"},ae={href:"https://github.com/hiukim/mind-ar-js",target:"_blank",rel:"noopener noreferrer"},se=e("h2",{id:"augmented-reality-and-webxr-on-ios",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#augmented-reality-and-webxr-on-ios","aria-hidden":"true"},"#"),t(" Augmented Reality and WebXR on iOS")],-1),oe=e("p",null,"Augmented Reality experiences on iOS are somewhat limited, due to Apple currently not supporting WebXR on iOS devices.",-1),re=e("h3",{id:"musical-instrument-webxr-and-quicklook-support",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#musical-instrument-webxr-and-quicklook-support","aria-hidden":"true"},"#"),t(" Musical Instrument โ€“ WebXR and QuickLook support")],-1),ie=e("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),le=e("h3",{id:"everywhere-actions-and-other-options-for-ios-ar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#everywhere-actions-and-other-options-for-ios-ar","aria-hidden":"true"},"#"),t(" Everywhere Actions and other options for iOS AR")],-1),ce=e("p",null,"There's also other options for guiding iOS users to even more capable interactive AR experiences:",-1),de={start:"3"},pe=e("strong",null,"Exporting content on-the-fly as USDZ files.",-1),ue=e("br",null,null,-1),he={href:"https://castle.needle.tools",target:"_blank",rel:"noopener noreferrer"},me={href:"https://accurate-tree-observation.glitch.me/",target:"_blank",rel:"noopener noreferrer"},ge=e("br",null,null,-1),be=e("strong",null,"Guiding users towards WebXR-compatible browsers on iOS.",-1),_e={href:"https://apps.apple.com/de/app/webxr-viewer/id1295998056",target:"_blank",rel:"noopener noreferrer"},fe=e("p",null,[e("strong",null,"Using camera access and custom algorithms on iOS devices."),e("br"),t(" One can request camera image access and run custom algorithms to determine device pose."),e("br"),t(" 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:")],-1),ke={href:"https://github.com/AR-js-org/AR.js",target:"_blank",rel:"noopener noreferrer"},ve={href:"https://github.com/FireDragonGameStudio/NeedleAndARjs",target:"_blank",rel:"noopener noreferrer"},ye={href:"https://github.com/hiukim/mind-ar-js",target:"_blank",rel:"noopener noreferrer"},we={href:"https://www.8thwall.com/",target:"_blank",rel:"noopener noreferrer"},Re=e("h2",{id:"references",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#references","aria-hidden":"true"},"#"),t(" References")],-1),Ae={href:"https://www.w3.org/TR/webxr/",target:"_blank",rel:"noopener noreferrer"},xe=e("br",null,null,-1),Se={href:"https://caniuse.com/webxr",target:"_blank",rel:"noopener noreferrer"},Te=e("br",null,null,-1),Ee={href:"https://developer.apple.com/augmented-reality/quick-look/",target:"_blank",rel:"noopener noreferrer"};function je(qe,Oe){const s=i("RouterLink"),a=i("ExternalLinkIcon"),l=i("sample");return d(),p("div",null,[h,e("table",null,[m,e("tbody",null,[g,b,_,e("tr",null,[f,k,e("td",null,[t("No full code support, but Needle "),n(s,{to:"/everywhere-actions.html"},{default:o(()=>[t("Everywhere Actions")]),_:1}),t(" are supported for creating dynamic, interactive USDZ files.")])]),v,y,w])]),R,e("p",null,[A,t(": Requires enabling a browser flag: "),x,S,T,t(": Requires enabling a toggle in the Developer settings"),E,j,t(": Uses "),n(s,{to:"/everywhere-actions.html"},{default:o(()=>[t("Everywhere Actions")]),_:1}),t(" or "),q]),O,e("p",null,[t("Visit our "),e("a",B,[t("Needle Engine XR Samples"),n(a)]),t(" to try many interactive examples right now!")]),C,e("blockquote",null,[e("p",null,[e("strong",null,[e("a",W,[t("Castle Builder"),n(a)])]),t(" uses all of the above for a cross-platform multiplayer sandbox experience."),X,t(" โ€” #madebyneedle ๐Ÿ’š")])]),V,e("p",null,[t("Needle Engine supports the "),e("a",N,[M,n(a)]),t(" state. This allows users to seamlessly traverse between WebXR applications without leaving an immersive session โ€“ they stay in VR or AR.")]),P,I,D,e("p",null,[t("Read more about scripting for XR at the "),n(s,{to:"/scripting.html#xr-event-methods"},{default:o(()=>[t("scripting XR documentation")]),_:1})]),L,e("p",null,[t("Use the "),G,t(" class to show/hide specific content while in AR. The "),e("a",H,[F,t(" pseudo class"),n(a)]),t(" shouldn't be used at this point because using it breaks Mozilla's WebXR Viewer.")]),U,e("p",null,[t("It's worth noting that the overlay element "),e("a",Q,[t("will be always displayed fullscreen while in XR"),n(a)]),t(", independent of styling that has been applied. If you want to align items differently, you should make a container "),z,t(" the "),K,t(" element.")]),n(l,{src:"https://engine.needle.tools/samples-uploads/ar-overlay/"}),Y,e("p",null,[t('WebXR ImageTracking is still in "draft" phase: '),e("a",Z,[t("Marker Tracking Explainer"),n(a)]),J,t(" But you can still use WebXR ImageTracking with Needle Engine today:")]),$,e("p",null,[t("You can find additional documentation in the "),n(s,{to:"/everywhere-actions.html#image-tracking"},{default:o(()=>[t("Everywhere Actions")]),_:1}),t(" section")]),ee,e("ul",null,[e("li",null,[e("a",te,[t("AR.js"),n(a)]),t(" (open source) "),e("ul",null,[e("li",null,[e("a",ne,[t("Experimental AR.js integration"),n(a)]),t(" by FireDragonGameStudio")])])]),e("li",null,[e("a",ae,[t("Mind AR"),n(a)]),t(" (open source)")])]),se,oe,e("p",null,[t("Needle Engine's "),n(s,{to:"/everywhere-actions.html"},{default:o(()=>[t("Everywhere Actions")]),_:1}),t(" 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 "),n(s,{to:"/everywhere-actions.html"},{default:o(()=>[t("the docs")]),_:1}),t(" for more information.")]),re,ie,n(l,{src:"https://engine.needle.tools/samples-uploads/musical-instrument"}),le,ce,e("ol",de,[e("li",null,[pe,ue,t(" 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 "),e("a",he,[t("Castle Builder"),n(a)]),t(" where creations (not the live session) can be viewed in AR.")])]),e("blockquote",null,[e("p",null,[e("strong",null,[e("a",me,[t("Encryption in Space"),n(a)])]),t(" 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."),ge,t(" โ€” #madewithneedle by Katja Rempel ๐Ÿ’š")])]),e("ol",null,[e("li",null,[e("p",null,[be,t(" Depending on your target audience, you can guide users on iOS towards for example Mozilla's "),e("a",_e,[t("WebXR Viewer"),n(a)]),t(" to experience AR on iOS.")])]),e("li",null,[fe,e("ul",null,[e("li",null,[e("a",ke,[t("AR.js"),n(a)]),t(" (open source) "),e("ul",null,[e("li",null,[e("a",ve,[t("Experimental AR.js integration"),n(a)]),t(" by FireDragonGameStudio")])])]),e("li",null,[e("a",ye,[t("Mind AR"),n(a)]),t(" (open source)")]),e("li",null,[e("a",we,[t("8th Wall"),n(a)]),t(" (commercial)")])])])]),Re,e("p",null,[e("a",Ae,[t("WebXR Device API"),n(a)]),xe,e("a",Se,[t("caniuse: WebXR"),n(a)]),Te,e("a",Ee,[t("Apple's Preliminary USD Behaviours"),n(a)])])])}const Ce=c(u,[["render",je],["__file","xr.html.vue"]]);export{Ce as default}; diff --git a/backlog-mermaid.html b/backlog-mermaid.html new file mode 100644 index 000000000..b3885bfd5 --- /dev/null +++ b/backlog-mermaid.html @@ -0,0 +1,48 @@ + + + + + + + + + Documentation + + + + +
    flowchart LR
    +  Editor([<b>C# components</b><br/>on GameObjects]) --> gltf[<b>JSON data</b><br/>as glTF Extension] --> Runtime([<b>JavaScript components</b><br/>on Object3D])
    +  class Editor,gltf,Runtime bg;
    +
    flowchart LR
    +    Editor([Unity Editor]) --> EditorExt([Components + Tools])
    +    EditorExt -- export data --> glTF([glTF + Extensions])
    +    glTF --> Bundler([Bundler - vite])
    +    Runtime([Needle Runtime]) --> Bundler
    +    Three([Three.js]) --> Bundler
    +    YourWebsite([Classic web files - HTML, CSS, JS]) --> Bundler
    +    Bundler -- outputs --> DevPage([web app - dev])
    +    Bundler -- outputs --> DeploymentPage([web app - deploy])
    +    glTF -- compressed with --> gltfTransform([glTF-transform]) --> DeploymentPage
    +    class EditorExt,glTF,Runtime ndl;
    +    class Editor,Three,Bundler,Page,gltfTransform,DeploymentPage,DevPage,YourWebsite ext;
    +
    + + + diff --git a/backlog.html b/backlog.html new file mode 100644 index 000000000..0aadce8cc --- /dev/null +++ b/backlog.html @@ -0,0 +1,33 @@ + + + + + + + + + Documentation Backlog | Documentation + + + + +

    Documentation Backlog

    This section contains pieces of information that are important, but need to be sorted into their correct categories.

    • Unity 2020.3.8f1+ or 2022.1+
    • Render Pipeline: Universal
    • Color Space: Linear
    • Non-Directional Lightmaps
    • Lightmap Encoding: Normal Quality

    Supported Unity configurations

    • Unity 2020.3+ | Unity 2021.3+ | Unity 2022.1+
    • Render Pipeline: Universal | Built-In1
    • Color Space: Linear

    1: no custom shader support

    Source Control

    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.

    Licensing Setup

    Note: This section is deprecated. Needle Engine is currently on Open Beta, and there's no need to authenticate against our registry at this point.

    Authentication

    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.

    1. Clone this repository and open starter/Authenticate with Unity 2020.3.x
    2. Open https://packages.needle.tools โ‡กopen in new window in your browser and login (top right corner) with your github account.
    3. Return to packages.needle.tools โ‡กopen in new window and click the i icon in the top right corner opening the Registry Info window.
    4. Copy the line containing _authToken (see the video below)
    5. Focus Unity - a notification window should open that the information has been added successfully from your clipboard.
    6. Click save and close Unity. You should now have access rights to the needle package registry.
    + + + diff --git a/blender/animation.mp4 b/blender/animation.mp4 new file mode 100644 index 000000000..bed4e28ab Binary files /dev/null and b/blender/animation.mp4 differ diff --git a/blender/animatorcontroller-assigning.webp b/blender/animatorcontroller-assigning.webp new file mode 100644 index 000000000..83fcc1662 Binary files /dev/null and b/blender/animatorcontroller-assigning.webp differ diff --git a/blender/animatorcontroller-create.mp4 b/blender/animatorcontroller-create.mp4 new file mode 100644 index 000000000..02f0e9ecf Binary files /dev/null and b/blender/animatorcontroller-create.mp4 differ diff --git a/blender/animatorcontroller-open.webp b/blender/animatorcontroller-open.webp new file mode 100644 index 000000000..72789a534 Binary files /dev/null and b/blender/animatorcontroller-open.webp differ diff --git a/blender/animatorcontroller-overview.webp b/blender/animatorcontroller-overview.webp new file mode 100644 index 000000000..ce4e9653d Binary files /dev/null and b/blender/animatorcontroller-overview.webp differ diff --git a/blender/animatorcontroller-web.mp4 b/blender/animatorcontroller-web.mp4 new file mode 100644 index 000000000..f16d0d4e0 Binary files /dev/null and b/blender/animatorcontroller-web.mp4 differ diff --git a/blender/bugreporter.webp b/blender/bugreporter.webp new file mode 100644 index 000000000..74631d65d Binary files /dev/null and b/blender/bugreporter.webp differ diff --git a/blender/components-panel-select.webp b/blender/components-panel-select.webp new file mode 100644 index 000000000..acc481369 Binary files /dev/null and b/blender/components-panel-select.webp differ diff --git a/blender/components-panel.webp b/blender/components-panel.webp new file mode 100644 index 000000000..ae799f141 Binary files /dev/null and b/blender/components-panel.webp differ diff --git a/blender/custom_hdri.mp4 b/blender/custom_hdri.mp4 new file mode 100644 index 000000000..9b516dd25 Binary files /dev/null and b/blender/custom_hdri.mp4 differ diff --git a/blender/deploy_to_glitch.webp b/blender/deploy_to_glitch.webp new file mode 100644 index 000000000..d5985a13d Binary files /dev/null and b/blender/deploy_to_glitch.webp differ diff --git a/blender/dont-export.webp b/blender/dont-export.webp new file mode 100644 index 000000000..8fd0cf01c Binary files /dev/null and b/blender/dont-export.webp differ diff --git a/blender/environment-camera.webp b/blender/environment-camera.webp new file mode 100644 index 000000000..71069c509 Binary files /dev/null and b/blender/environment-camera.webp differ diff --git a/blender/environment-light.mp4 b/blender/environment-light.mp4 new file mode 100644 index 000000000..f50b71977 Binary files /dev/null and b/blender/environment-light.mp4 differ diff --git a/blender/environment.mp4 b/blender/environment.mp4 new file mode 100644 index 000000000..f9db730e8 Binary files /dev/null and b/blender/environment.mp4 differ diff --git a/blender/environment.webp b/blender/environment.webp new file mode 100644 index 000000000..0b2a8d00d Binary files /dev/null and b/blender/environment.webp differ diff --git a/blender/index.html b/blender/index.html new file mode 100644 index 000000000..af7a04170 --- /dev/null +++ b/blender/index.html @@ -0,0 +1,82 @@ + + + + + + + + + Needle Engine for Blender | Documentation + + + + +

    Needle Engine for Blender

    Thank you for using Needle Engine for Blender.
    With this addon 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. You own your content!

    Automatically export HDRI environment lights directly from blender. Save to reload your local server

    Content Overview

    Preface

    Your feedback is invaluable when it comes to deciding which of those features should be prioritizes.
    If you have feedback for us please let us know in our forumopen in new window!

    Download and Installation ๐Ÿ’ฟ

    Step 1 โ€ข Install Blender 3.6, 4.0, 4.1 or 4.2

    Step 3 โ€ข Download Needle Engine for Blender

    The Blender addon is downloaded as a zip file.
    In Blender go to File / Settings / Add-ons and click the Install button.
    Then select the downloaded zip to install it.

    Settings

    Getting Started ๐Ÿšฉ

    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.

    Project panel

    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:
    Project panel

    Project Panel overview

    Project panel

    1. The path to your web project. You can use the little folder button on the right to select a different path.
    2. 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
    3. Directory open the directory of your web project (the Project Path)
    4. 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.
    5. Code Editor tries to open the vscode workspace in your web project
    6. 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.
    7. 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)
    8. Open the documentation

    Blender Settings

    Color Management

    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

    Correct color management settings

    Environment Lighting

    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.

    Environment

    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.

    Environment Camera

    Add your custom HDRi / EXR environment lighting and skybox

    Export

    To exclude an object from being exported you can disable the Viewport and the Render display (see image below)

    Exclude from export

    Animation ๐Ÿ‡

    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)

    AnimatorController

    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.

    Create and export animator statemachines for controlling complex character animations

    Creating an AnimatorController

    The AnimatorController editor can be opened using the EditorType dropdown in the topleft corner of each panel:

    AnimatorController open window

    Creating a new animator-controller asset โ˜ or select one from your previously created assets

    Graph overview

    AnimatorController overview

    1. Use Shift+A to create a new AnimatorState
    2. 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)
    3. 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)
    4. 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.

    Using an AnimatorController

    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.

    AnimatorController assign to animator

    You can set the Animator parameters from typescript or by e.g. using the event of a Button component

    Timeline โ€” nla tracks export ๐ŸŽฌ

    Exporting Blender nla tracks to threejs.
    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

    import { Behaviour, PlayableDirector, serializable, Mathf } from "@needle-tools/engine";
    +
    +export class ScrollTimeline extends Behaviour {
    +
    +    @serializable(PlayableDirector)
    +    timeline?: PlayableDirector;
    +
    +    @serializable()
    +    sensitivity: number = .5;
    +
    +    @serializable()
    +    clamp: boolean = false;
    +
    +    private _targetTime: number = 0;
    +
    +    awake() {
    +        this.context.domElement.addEventListener("wheel", this.onWheel);
    +        if (this.timeline) this.timeline.pause();
    +    }
    +
    +    private onWheel = (e: WheelEvent) => {
    +        if (this.timeline) {
    +            this._targetTime = this.timeline.time + e.deltaY * 0.01 * this.sensitivity;
    +            if (this.clamp) this._targetTime = Mathf.clamp(this._targetTime, 0, this.timeline.duration);
    +        }
    +    }
    +
    +    update(): void {
    +        if (!this.timeline) return;
    +        const time = Mathf.lerp(this.timeline.time, this._targetTime, this.context.time.deltaTime / .3);
    +        this.timeline.time = time;
    +        this.timeline.pause();
    +        this.timeline.evaluate();
    +    }
    +}
    +

    Interactivity ๐Ÿ˜Ž

    You can add or remove components to objects in your hierarchy using the Needle Components panel:

    Component panel

    Component 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:

    Remove component

    Custom Components

    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 documentationopen in new window 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)

    Lightmapping ๐Ÿ’ก

    Needle Lightmapping will automatically generate lightmap UVs for all models marked to be lightmapped. For lightmapping to work you need at least one light and one object with Lightmapped turned on.

    Please keep in mind:

    You are using an early preview of these features - we recommend creating a backup of your blend file when using Lightmapping at this point in time. Please report problems or errors you encounter in our discordopen in new window ๐Ÿ™

    You can download the original blend file from the video hereopen in new window.

    Use the Needle Object panel to enable lightmapping for a mesh object or light:

    Lightmapping object

    For quick access to lightmap settings and baking options you can use the scene view panel in the Needle tab:

    Lightmapping scene panel

    Alternatively you can also use the Lightmapping panel in the Render Properties tab:

    Lightmapping object

    Texture Compression

    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.

    Texture Compression options in Blender

    Updating

    The lightbulb in the Needle Project panel informs you when a new version of the addon is available.
    Simply click the icon to download the new version.
    Update notification

    Debugging / Reporting a problem

    If you run into any problems we're more than happy to help! Please join our discordopen in new window for fast support.

    Please also check the logs in Blender. You can find logs specific to the Needle Engine Addon via Help/Needle in Blender.

    Integrated Bugreporter

    Needle Blender bugreporter panel
    You can also automatically create and upload a bugreport directly from Blender (this currently requires a node.js web project being setup). Uploaded bugreports will solely used for debugging, they are encrypted on our backend and will deleted after 30 days.

    + + + 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/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..0c471ee28 --- /dev/null +++ b/code-samples/component-everywhere-action-hideonstart.ts @@ -0,0 +1,22 @@ +export class HideOnStart extends Behaviour implements UsdzBehaviour { + + start() { + this.gameObject.visible = false; + } + + createBehaviours(ext, model, _context) { + 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/ericcraft-mh/index.html b/community/contributions/ericcraft-mh/index.html new file mode 100644 index 000000000..c09e7a64d --- /dev/null +++ b/community/contributions/ericcraft-mh/index.html @@ -0,0 +1,76 @@ + + + + + + + + + Documentation + + + + +
    โ—€ Overview
    QuickLook Vertical Image Tracker

    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 Experienceopen in new window 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.

    import { Behaviour, GameObject, serializable, USDZExporter } from "@needle-tools/engine";
    +import { Object3D, Euler } from "three";
    +
    +export class QuickLookObjectsToFix extends Behaviour {
    +
    +    @serializable(Object3D)
    +    objectToFix!: Object3D;
    +
    +    private usdzExporter!: USDZExporter;
    +    private startRot: Euler = new Euler();
    +
    +    onEnable() {
    +        this.usdzExporter = GameObject.findObjectOfType(USDZExporter)!;
    +        this.startRot = this.objectToFix.rotation;
    +        this.subscribeToBeforeExportEvent();
    +    }
    +
    +    onDisable() {
    +        this.unsubscribeFromBeforeExportEvent();
    +    }
    +
    +    private subscribeToBeforeExportEvent() {
    +        this.usdzExporter.addEventListener("before-export", this.onBeforeExport);
    +        this.usdzExporter.addEventListener("after-export", this.onAfterExport);
    +    }
    +
    +    private unsubscribeFromBeforeExportEvent() {
    +        this.usdzExporter.removeEventListener("before-export", this.onBeforeExport);
    +        this.usdzExporter.removeEventListener("after-export", this.onAfterExport);
    +    }
    +
    +    private onBeforeExport = () => {
    +        this.objectToFix.updateMatrixWorld();
    +        this.objectToFix.rotation.x = -Math.PI / 2;
    +        this.objectToFix.updateMatrixWorld();
    +    }
    +
    +    private onAfterExport = () => {
    +        this.objectToFix.updateMatrixWorld();
    +        this.objectToFix.setRotationFromEuler(this.startRot);
    +        this.objectToFix.updateMatrixWorld();
    +    }
    +}
    +

    Thanks to llllkatjallllopen in new window as their Set fallback material for USDZ exporteropen in new window solution helped me come up with a working solution for this.

    EDIT: Code cleanup and fixes.

    + + + diff --git a/community/contributions/ericcraft-mh/quicklook-vertical-image-tracker/index.html b/community/contributions/ericcraft-mh/quicklook-vertical-image-tracker/index.html new file mode 100644 index 000000000..955fe7a7b --- /dev/null +++ b/community/contributions/ericcraft-mh/quicklook-vertical-image-tracker/index.html @@ -0,0 +1,76 @@ + + + + + + + + + Documentation + + + + +

    Overview

    QuickLook Vertical Image Tracker

    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 Experienceopen in new window 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.

    import { Behaviour, GameObject, serializable, USDZExporter } from "@needle-tools/engine";
    +import { Object3D, Euler } from "three";
    +
    +export class QuickLookObjectsToFix extends Behaviour {
    +
    +    @serializable(Object3D)
    +    objectToFix!: Object3D;
    +
    +    private usdzExporter!: USDZExporter;
    +    private startRot: Euler = new Euler();
    +
    +    onEnable() {
    +        this.usdzExporter = GameObject.findObjectOfType(USDZExporter)!;
    +        this.startRot = this.objectToFix.rotation;
    +        this.subscribeToBeforeExportEvent();
    +    }
    +
    +    onDisable() {
    +        this.unsubscribeFromBeforeExportEvent();
    +    }
    +
    +    private subscribeToBeforeExportEvent() {
    +        this.usdzExporter.addEventListener("before-export", this.onBeforeExport);
    +        this.usdzExporter.addEventListener("after-export", this.onAfterExport);
    +    }
    +
    +    private unsubscribeFromBeforeExportEvent() {
    +        this.usdzExporter.removeEventListener("before-export", this.onBeforeExport);
    +        this.usdzExporter.removeEventListener("after-export", this.onAfterExport);
    +    }
    +
    +    private onBeforeExport = () => {
    +        this.objectToFix.updateMatrixWorld();
    +        this.objectToFix.rotation.x = -Math.PI / 2;
    +        this.objectToFix.updateMatrixWorld();
    +    }
    +
    +    private onAfterExport = () => {
    +        this.objectToFix.updateMatrixWorld();
    +        this.objectToFix.setRotationFromEuler(this.startRot);
    +        this.objectToFix.updateMatrixWorld();
    +    }
    +}
    +

    Thanks to llllkatjallllopen in new window as their Set fallback material for USDZ exporteropen in new window solution helped me come up with a working solution for this.

    EDIT: Code cleanup and fixes.

    + + + diff --git a/community/contributions/index.html b/community/contributions/index.html new file mode 100644 index 000000000..df941f405 --- /dev/null +++ b/community/contributions/index.html @@ -0,0 +1,33 @@ + + + + + + + + + Documentation + + + + +
    + + + diff --git a/community/contributions/kipash/calculate-pointer-world-position/index.html b/community/contributions/kipash/calculate-pointer-world-position/index.html new file mode 100644 index 000000000..01fc441f5 --- /dev/null +++ b/community/contributions/kipash/calculate-pointer-world-position/index.html @@ -0,0 +1,70 @@ + + + + + + + + + Documentation + + + + +

    Overview

    Calculate pointer world position

    https://github.com/needle-tools/needle-engine-support/assets/30328735/92404e43-9d45-4ee2-a018-fb00412b6bd5

    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);
    +    }
    +}
    +
    + + + diff --git a/community/contributions/kipash/index.html b/community/contributions/kipash/index.html new file mode 100644 index 000000000..ef0d32759 --- /dev/null +++ b/community/contributions/kipash/index.html @@ -0,0 +1,70 @@ + + + + + + + + + Documentation + + + + +
    โ—€ Overview
    Calculate pointer world position

    https://github.com/needle-tools/needle-engine-support/assets/30328735/92404e43-9d45-4ee2-a018-fb00412b6bd5

    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);
    +    }
    +}
    +
    + + + diff --git a/community/contributions/krisrok/always-open-in-specific-browser/index.html b/community/contributions/krisrok/always-open-in-specific-browser/index.html new file mode 100644 index 000000000..74ed2611b --- /dev/null +++ b/community/contributions/krisrok/always-open-in-specific-browser/index.html @@ -0,0 +1,65 @@ + + + + + + + + + Documentation + + + + +

    Overview

    Always open in specific browser

    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.

    using System.Diagnostics;
    +using UnityEditor;
    +using UnityEngine;
    +using Needle.Engine;
    +
    +[InitializeOnLoad]
    +public static class CustomBrowserOpen
    +{
    +    static CustomBrowserOpen()
    +    {
    +        Init();
    +    }
    +
    +    [RuntimeInitializeOnLoadMethod]
    +    static void Init()
    +    {
    +        ActionsBrowser.BeforeOpen += ActionsBrowser_BeforeOpen;
    +    }
    +
    +    private static void ActionsBrowser_BeforeOpen(ActionsBrowser.OpenBrowserArguments args)
    +    {
    +        args.PreventDefault = true;
    +        string processArgs = args.Url;
    +        var psi = new ProcessStartInfo
    +        {
    +            FileName = "chrome.exe",
    +            Arguments = processArgs
    +        };
    +        Process.Start(psi);
    +    }
    +}
    +
    +
    + + + diff --git a/community/contributions/krisrok/index.html b/community/contributions/krisrok/index.html new file mode 100644 index 000000000..6ac32dc03 --- /dev/null +++ b/community/contributions/krisrok/index.html @@ -0,0 +1,65 @@ + + + + + + + + + Documentation + + + + +
    โ—€ Overview
    Always open in specific browser

    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.

    using System.Diagnostics;
    +using UnityEditor;
    +using UnityEngine;
    +using Needle.Engine;
    +
    +[InitializeOnLoad]
    +public static class CustomBrowserOpen
    +{
    +    static CustomBrowserOpen()
    +    {
    +        Init();
    +    }
    +
    +    [RuntimeInitializeOnLoadMethod]
    +    static void Init()
    +    {
    +        ActionsBrowser.BeforeOpen += ActionsBrowser_BeforeOpen;
    +    }
    +
    +    private static void ActionsBrowser_BeforeOpen(ActionsBrowser.OpenBrowserArguments args)
    +    {
    +        args.PreventDefault = true;
    +        string processArgs = args.Url;
    +        var psi = new ProcessStartInfo
    +        {
    +            FileName = "chrome.exe",
    +            Arguments = processArgs
    +        };
    +        Process.Start(psi);
    +    }
    +}
    +
    +
    + + + diff --git a/community/contributions/llllkatjallll/custom-vr-button-that-appears-only-on-headsets-and-not-on-mobile-phones/index.html b/community/contributions/llllkatjallll/custom-vr-button-that-appears-only-on-headsets-and-not-on-mobile-phones/index.html new file mode 100644 index 000000000..4e3cb1b78 --- /dev/null +++ b/community/contributions/llllkatjallll/custom-vr-button-that-appears-only-on-headsets-and-not-on-mobile-phones/index.html @@ -0,0 +1,48 @@ + + + + + + + + + Documentation + + + + +

    Overview

    Custom VR Button, that appears only on headsets and not on mobile phones

    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} 
    +
    + + + diff --git a/community/contributions/llllkatjallll/index.html b/community/contributions/llllkatjallll/index.html new file mode 100644 index 000000000..d78608847 --- /dev/null +++ b/community/contributions/llllkatjallll/index.html @@ -0,0 +1,93 @@ + + + + + + + + + Documentation + + + + +
    โ—€ Overview
    Custom VR Button, that appears only on headsets and not on mobile phones

    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} 
    +
    Set fallback material for USDZ exporter

    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).

    import { Behaviour, GameObject, Renderer, USDZExporter, serializable } from "@needle-tools/engine";
    +import { Material, Object3D } from "three";
    +
    +export class FallbackMaterial extends Behaviour {
    +
    +    @serializable(Material)
    +    fallbackMaterial!: Material;
    +
    +    private originalMaterial?: Material;
    +    private usdzExporter!: USDZExporter;
    +
    +    onEnable() {
    +        this.usdzExporter = GameObject.findObjectOfType(USDZExporter)!;
    +        this.subscribeToBeforeExportEvent();
    +    }
    +
    +    onDisable() {
    +        this.unsubscribeFromBeforeExportEvent();
    +    }
    +
    +    private subscribeToBeforeExportEvent() {
    +        this.usdzExporter.addEventListener("before-export", this.onBeforeExport);
    +        this.usdzExporter.addEventListener("after-export", this.onAfterExport);
    +    }
    +
    +    private unsubscribeFromBeforeExportEvent() {
    +        this.usdzExporter.removeEventListener("before-export", this.onBeforeExport);
    +        this.usdzExporter.removeEventListener("after-export", this.onAfterExport);
    +    }
    +
    +
    +    onBeforeExport = () => {
    +        console.log("onBeforeExport");
    +        const renderer = this.gameObject.getComponent(Renderer)!;
    +        this.originalMaterial = renderer.sharedMaterial;
    +        renderer.sharedMaterial = this.fallbackMaterial;
    +
    +    }
    +
    +    onAfterExport = () => {
    +        console.log("onAfterExport");
    +        const renderer = this.gameObject.getComponent(Renderer)!;
    +        renderer.sharedMaterial = this.originalMaterial;
    +    }
    +}
    +
    + + + diff --git a/community/contributions/llllkatjallll/set-fallback-material-for-usdz-exporter/index.html b/community/contributions/llllkatjallll/set-fallback-material-for-usdz-exporter/index.html new file mode 100644 index 000000000..3e270cae4 --- /dev/null +++ b/community/contributions/llllkatjallll/set-fallback-material-for-usdz-exporter/index.html @@ -0,0 +1,78 @@ + + + + + + + + + Documentation + + + + +

    Overview

    Set fallback material for USDZ exporter

    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).

    import { Behaviour, GameObject, Renderer, USDZExporter, serializable } from "@needle-tools/engine";
    +import { Material, Object3D } from "three";
    +
    +export class FallbackMaterial extends Behaviour {
    +
    +    @serializable(Material)
    +    fallbackMaterial!: Material;
    +
    +    private originalMaterial?: Material;
    +    private usdzExporter!: USDZExporter;
    +
    +    onEnable() {
    +        this.usdzExporter = GameObject.findObjectOfType(USDZExporter)!;
    +        this.subscribeToBeforeExportEvent();
    +    }
    +
    +    onDisable() {
    +        this.unsubscribeFromBeforeExportEvent();
    +    }
    +
    +    private subscribeToBeforeExportEvent() {
    +        this.usdzExporter.addEventListener("before-export", this.onBeforeExport);
    +        this.usdzExporter.addEventListener("after-export", this.onAfterExport);
    +    }
    +
    +    private unsubscribeFromBeforeExportEvent() {
    +        this.usdzExporter.removeEventListener("before-export", this.onBeforeExport);
    +        this.usdzExporter.removeEventListener("after-export", this.onAfterExport);
    +    }
    +
    +
    +    onBeforeExport = () => {
    +        console.log("onBeforeExport");
    +        const renderer = this.gameObject.getComponent(Renderer)!;
    +        this.originalMaterial = renderer.sharedMaterial;
    +        renderer.sharedMaterial = this.fallbackMaterial;
    +
    +    }
    +
    +    onAfterExport = () => {
    +        console.log("onAfterExport");
    +        const renderer = this.gameObject.getComponent(Renderer)!;
    +        renderer.sharedMaterial = this.originalMaterial;
    +    }
    +}
    +
    + + + diff --git a/community/contributions/marwie/camera-video-background/index.html b/community/contributions/marwie/camera-video-background/index.html new file mode 100644 index 000000000..b1297211e --- /dev/null +++ b/community/contributions/marwie/camera-video-background/index.html @@ -0,0 +1,62 @@ + + + + + + + + + Documentation + + + + +

    Overview

    Camera Video Background

    Put it anywhere in your scene to render a camera video behind your 3D scene Live demoopen in new window

    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);
    +        }
    +    }
    +}
    +
    + + + diff --git a/community/contributions/marwie/code-contribution-example/index.html b/community/contributions/marwie/code-contribution-example/index.html new file mode 100644 index 000000000..78bc04e68 --- /dev/null +++ b/community/contributions/marwie/code-contribution-example/index.html @@ -0,0 +1,49 @@ + + + + + + + + + Documentation + + + + +

    Overview

    Code Contribution Example

    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
    +    }
    +}
    +
    + + + diff --git a/community/contributions/marwie/control-a-timeline-by-scroll/index.html b/community/contributions/marwie/control-a-timeline-by-scroll/index.html new file mode 100644 index 000000000..936ad6eb0 --- /dev/null +++ b/community/contributions/marwie/control-a-timeline-by-scroll/index.html @@ -0,0 +1,88 @@ + + + + + + + + + Documentation + + + + +

    Overview

    Control a Timeline by scroll

    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();
    +    }
    +}
    +
    +
    + + + diff --git a/community/contributions/marwie/everywhere-action-emphasize-on-click/index.html b/community/contributions/marwie/everywhere-action-emphasize-on-click/index.html new file mode 100644 index 000000000..e9f59e414 --- /dev/null +++ b/community/contributions/marwie/everywhere-action-emphasize-on-click/index.html @@ -0,0 +1,60 @@ + + + + + + + + + Documentation + + + + +

    Overview

    Everywhere Action: Emphasize on Click

    Example for adding custom USDZ behaviours for iOS AR

    This is an USDZ / iOS AR only example

    export class EmphasizeOnClick extends Behaviour implements UsdzBehaviour {
    +
    +    @serializable()
    +    target?: Object3D;
    +
    +    @serializable()
    +    duration: number = 0.5;
    +
    +    @serializable()
    +    motionType: MotionType = MotionType.bounce;
    +
    +    beforeCreateDocument() { }
    +
    +    createBehaviours(ext, model, _context) {
    +        if (!this.target) return;
    +
    +        if (model.uuid === this.gameObject.uuid) {
    +            const emphasize = new BehaviorModel("emphasize " + this.name,
    +                TriggerBuilder.tapTrigger(this.gameObject),
    +                ActionBuilder.emphasize(this.target, this.duration, this.motionType, undefined, "basic"),
    +            );
    +            ext.addBehavior(emphasize);
    +        }
    +    }
    +
    +    afterCreateDocument(_ext, _context) { }
    +}
    +
    + + + diff --git a/community/contributions/marwie/index.html b/community/contributions/marwie/index.html new file mode 100644 index 000000000..d749cb3ef --- /dev/null +++ b/community/contributions/marwie/index.html @@ -0,0 +1,182 @@ + + + + + + + + + Documentation + + + + +
    โ—€ Overview
    Camera Video Background

    Put it anywhere in your scene to render a camera video behind your 3D scene Live demoopen in new window

    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);
    +        }
    +    }
    +}
    +
    USDZ: Hide Object on Start

    This is an example from our Everywhere Actions. The following script hides an object on start on Android and on iOS AR

    export class HideOnStart extends Behaviour implements UsdzBehaviour {
    +
    +    start() {
    +        this.gameObject.visible = false;
    +    }
    +
    +    createBehaviours(ext, model, _context) {
    +        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;
    +    }
    +}
    +
    Everywhere Action: Emphasize on Click

    Example for adding custom USDZ behaviours for iOS AR

    This is an USDZ / iOS AR only example

    export class EmphasizeOnClick extends Behaviour implements UsdzBehaviour {
    +
    +    @serializable()
    +    target?: Object3D;
    +
    +    @serializable()
    +    duration: number = 0.5;
    +
    +    @serializable()
    +    motionType: MotionType = MotionType.bounce;
    +
    +    beforeCreateDocument() { }
    +
    +    createBehaviours(ext, model, _context) {
    +        if (!this.target) return;
    +
    +        if (model.uuid === this.gameObject.uuid) {
    +            const emphasize = new BehaviorModel("emphasize " + this.name,
    +                TriggerBuilder.tapTrigger(this.gameObject),
    +                ActionBuilder.emphasize(this.target, this.duration, this.motionType, undefined, "basic"),
    +            );
    +            ext.addBehavior(emphasize);
    +        }
    +    }
    +
    +    afterCreateDocument(_ext, _context) { }
    +}
    +
    Control a Timeline by scroll

    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();
    +    }
    +}
    +
    +
    Code Contribution Example

    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
    +    }
    +}
    +
    + + + diff --git a/community/contributions/marwie/usdz-hide-object-on-start/index.html b/community/contributions/marwie/usdz-hide-object-on-start/index.html new file mode 100644 index 000000000..41c2709dc --- /dev/null +++ b/community/contributions/marwie/usdz-hide-object-on-start/index.html @@ -0,0 +1,55 @@ + + + + + + + + + Documentation + + + + +

    Overview

    USDZ: Hide Object on Start

    This is an example from our Everywhere Actions. The following script hides an object on start on Android and on iOS AR

    export class HideOnStart extends Behaviour implements UsdzBehaviour {
    +
    +    start() {
    +        this.gameObject.visible = false;
    +    }
    +
    +    createBehaviours(ext, model, _context) {
    +        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;
    +    }
    +}
    +
    + + + diff --git a/community/contributions/robyer1/ar-move-scale-rotate-controls-for-needle-on-mobile/index.html b/community/contributions/robyer1/ar-move-scale-rotate-controls-for-needle-on-mobile/index.html new file mode 100644 index 000000000..8d1bea852 --- /dev/null +++ b/community/contributions/robyer1/ar-move-scale-rotate-controls-for-needle-on-mobile/index.html @@ -0,0 +1,33 @@ + + + + + + + + + Documentation + + + + +

    Overview

    AR Move/Scale/Rotate Controls for Needle on Mobile

    This is live for preview over at https://needle-ar.glitch.me/

    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.

    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

    Public Github Repository: https://github.com/ROBYER1/Needle-AR-Demo

    + + + diff --git a/community/contributions/robyer1/index.html b/community/contributions/robyer1/index.html new file mode 100644 index 000000000..d1f13ec1a --- /dev/null +++ b/community/contributions/robyer1/index.html @@ -0,0 +1,98 @@ + + + + + + + + + Documentation + + + + +
    โ—€ Overview
    Microphone access in a browser window (and streamed playback)

    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.

    import { Behaviour } from "@needle-tools/engine";
    +
    +export class Microphone extends Behaviour {
    +  start() {
    +    this.getLocalStream();
    +  }
    +
    +  public el: Element;
    +
    +  attachStream(stream, el, options) {
    +    var item;
    +    var URL = window.URL;
    +    var element = el;
    +    var opts = {
    +      autoplay: true,
    +      mirror: false,
    +      muted: false,
    +      audio: false,
    +      disableContextMenu: false,
    +    };
    +
    +    if (options) {
    +      for (item in options) {
    +        opts[item] = options[item];
    +      }
    +    }
    +
    +    if (!element) {
    +      element = document.createElement(opts.audio ? "audio" : "video");
    +    } else if (element.tagName.toLowerCase() === "audio") {
    +      opts.audio = true;
    +    }
    +
    +    if (opts.autoplay) element.autoplay = "autoplay";
    +    if (opts.muted) element.muted = true;
    +    if (!opts.audio && opts.mirror) {
    +      ["", "moz", "webkit", "o", "ms"].forEach(function (prefix) {
    +        var styleName = prefix ? prefix + "Transform" : "transform";
    +        element.style[styleName] = "scaleX(-1)";
    +      });
    +    }
    +
    +    element.srcObject = stream;
    +    return element;
    +  }
    +
    +  getLocalStream() {
    +    navigator.mediaDevices
    +      .getUserMedia({
    +        video: false,
    +        audio: true,
    +      })
    +      .then((stream) => {
    +        var doesnotexist = !this.el;
    +        this.el = this.attachStream(stream, this.el, {
    +          audio: true,
    +          autoplay: true,
    +        });
    +        if (doesnotexist) document.body.appendChild(this.el);
    +      })
    +      .catch((err) => {
    +        console.log("u got an error:" + err);
    +      });
    +  }
    +}
    +
    AR Move/Scale/Rotate Controls for Needle on Mobile

    This is live for preview over at https://needle-ar.glitch.me/

    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.

    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

    Public Github Repository: https://github.com/ROBYER1/Needle-AR-Demo

    + + + diff --git a/community/contributions/robyer1/microphone-access-in-a-browser-window-and-streamed-playback/index.html b/community/contributions/robyer1/microphone-access-in-a-browser-window-and-streamed-playback/index.html new file mode 100644 index 000000000..f2db93dc2 --- /dev/null +++ b/community/contributions/robyer1/microphone-access-in-a-browser-window-and-streamed-playback/index.html @@ -0,0 +1,98 @@ + + + + + + + + + Documentation + + + + +

    Overview

    Microphone access in a browser window (and streamed playback)

    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.

    import { Behaviour } from "@needle-tools/engine";
    +
    +export class Microphone extends Behaviour {
    +  start() {
    +    this.getLocalStream();
    +  }
    +
    +  public el: Element;
    +
    +  attachStream(stream, el, options) {
    +    var item;
    +    var URL = window.URL;
    +    var element = el;
    +    var opts = {
    +      autoplay: true,
    +      mirror: false,
    +      muted: false,
    +      audio: false,
    +      disableContextMenu: false,
    +    };
    +
    +    if (options) {
    +      for (item in options) {
    +        opts[item] = options[item];
    +      }
    +    }
    +
    +    if (!element) {
    +      element = document.createElement(opts.audio ? "audio" : "video");
    +    } else if (element.tagName.toLowerCase() === "audio") {
    +      opts.audio = true;
    +    }
    +
    +    if (opts.autoplay) element.autoplay = "autoplay";
    +    if (opts.muted) element.muted = true;
    +    if (!opts.audio && opts.mirror) {
    +      ["", "moz", "webkit", "o", "ms"].forEach(function (prefix) {
    +        var styleName = prefix ? prefix + "Transform" : "transform";
    +        element.style[styleName] = "scaleX(-1)";
    +      });
    +    }
    +
    +    element.srcObject = stream;
    +    return element;
    +  }
    +
    +  getLocalStream() {
    +    navigator.mediaDevices
    +      .getUserMedia({
    +        video: false,
    +        audio: true,
    +      })
    +      .then((stream) => {
    +        var doesnotexist = !this.el;
    +        this.el = this.attachStream(stream, this.el, {
    +          audio: true,
    +          autoplay: true,
    +        });
    +        if (doesnotexist) document.body.appendChild(this.el);
    +      })
    +      .catch((err) => {
    +        console.log("u got an error:" + err);
    +      });
    +  }
    +}
    +
    + + + diff --git a/community/contributions/web3kev/index.html b/community/contributions/web3kev/index.html new file mode 100644 index 000000000..53fdce642 --- /dev/null +++ b/community/contributions/web3kev/index.html @@ -0,0 +1,506 @@ + + + + + + + + + Documentation + + + + +
    โ—€ Overview
    Vertical Move in VR using the right joystick (Quest)

    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.

    You can place this script anywhere.

    
    +import { Behaviour, WebXR, GameObject} from "@needle-tools/engine";
    +import { Vector3,Quaternion} from "three";
    +import { Mathf } from "@needle-tools/engine";
    +
    +export class VerticalMove extends Behaviour {
    +
    +    private webXR?: WebXR;
    +    private joystickY?:number;
    +    private worldRot: Quaternion = new Quaternion();
    +
    +    start(): void {
    +
    +        let _webxr=GameObject.findObjectOfType(WebXR);
    +        if(_webxr)
    +        {
    +            this.webXR=_webxr;
    +            console.log("webxr found");
    +        }
    +    }
    +
    +
    +    update()
    +    {
    +        if(this.context.isInVR)
    +        {
    +            //get y value from right joystick
    +            this.verticalMove();
    +        }
    +    }
    +
    +    verticalMove():void
    +    {
    +        if(this.webXR?.RightController?.input?.gamepad?.axes[3]) 
    +        {
    +            this.joystickY=this.webXR.RightController.input.gamepad.axes[3];
    +
    +            const speedFactor = 3;
    +            const powFactor = 2;
    +            const speed = Mathf.clamp01(2 * 2);
    +            
    +            const verticalDir = this.joystickY < 0 ? 1 : -1;
    +            let vertical = Math.pow(this.joystickY, powFactor);
    +            vertical *= verticalDir;
    +            vertical *= speed;
    +
    +            this.webXR.Rig.getWorldQuaternion(this.worldRot);
    +            
    +            let movementVector=new Vector3();
    +            movementVector.set(0, vertical, 0);
    +            movementVector.applyQuaternion(this.webXR.TransformOrientation);
    +            movementVector.x = 0;
    +            movementVector.applyQuaternion(this.worldRot);
    +            movementVector.multiplyScalar(speedFactor * this.context.time.deltaTime);
    +
    +            this.webXR.Rig.position.add(movementVector);
    +        }
    +    }
    +}
    +
    +
    +
    Squeeze to Scale (Object or World) in VR

    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;
    +        }
    +    }
    +}
    +
    Network instantiation of multiple objects

    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.

    + + + diff --git a/community/contributions/web3kev/network-instantiation-of-multiple-objects/index.html b/community/contributions/web3kev/network-instantiation-of-multiple-objects/index.html new file mode 100644 index 000000000..b917dcd9e --- /dev/null +++ b/community/contributions/web3kev/network-instantiation-of-multiple-objects/index.html @@ -0,0 +1,196 @@ + + + + + + + + + Documentation + + + + +

    Overview

    Network instantiation of multiple objects

    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.

    + + + diff --git a/community/contributions/web3kev/squeeze-to-scale-object-or-world-in-vr/index.html b/community/contributions/web3kev/squeeze-to-scale-object-or-world-in-vr/index.html new file mode 100644 index 000000000..ab886679e --- /dev/null +++ b/community/contributions/web3kev/squeeze-to-scale-object-or-world-in-vr/index.html @@ -0,0 +1,282 @@ + + + + + + + + + Documentation + + + + +

    Overview

    Squeeze to Scale (Object or World) in VR

    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;
    +        }
    +    }
    +}
    +
    + + + diff --git a/community/contributions/web3kev/vertical-move-in-vr-using-the-right-joystick-quest/index.html b/community/contributions/web3kev/vertical-move-in-vr-using-the-right-joystick-quest/index.html new file mode 100644 index 000000000..e9a5bc01a --- /dev/null +++ b/community/contributions/web3kev/vertical-move-in-vr-using-the-right-joystick-quest/index.html @@ -0,0 +1,94 @@ + + + + + + + + + Documentation + + + + +

    Overview

    Vertical Move in VR using the right joystick (Quest)

    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.

    You can place this script anywhere.

    
    +import { Behaviour, WebXR, GameObject} from "@needle-tools/engine";
    +import { Vector3,Quaternion} from "three";
    +import { Mathf } from "@needle-tools/engine";
    +
    +export class VerticalMove extends Behaviour {
    +
    +    private webXR?: WebXR;
    +    private joystickY?:number;
    +    private worldRot: Quaternion = new Quaternion();
    +
    +    start(): void {
    +
    +        let _webxr=GameObject.findObjectOfType(WebXR);
    +        if(_webxr)
    +        {
    +            this.webXR=_webxr;
    +            console.log("webxr found");
    +        }
    +    }
    +
    +
    +    update()
    +    {
    +        if(this.context.isInVR)
    +        {
    +            //get y value from right joystick
    +            this.verticalMove();
    +        }
    +    }
    +
    +    verticalMove():void
    +    {
    +        if(this.webXR?.RightController?.input?.gamepad?.axes[3]) 
    +        {
    +            this.joystickY=this.webXR.RightController.input.gamepad.axes[3];
    +
    +            const speedFactor = 3;
    +            const powFactor = 2;
    +            const speed = Mathf.clamp01(2 * 2);
    +            
    +            const verticalDir = this.joystickY < 0 ? 1 : -1;
    +            let vertical = Math.pow(this.joystickY, powFactor);
    +            vertical *= verticalDir;
    +            vertical *= speed;
    +
    +            this.webXR.Rig.getWorldQuaternion(this.worldRot);
    +            
    +            let movementVector=new Vector3();
    +            movementVector.set(0, vertical, 0);
    +            movementVector.applyQuaternion(this.webXR.TransformOrientation);
    +            movementVector.x = 0;
    +            movementVector.applyQuaternion(this.worldRot);
    +            movementVector.multiplyScalar(speedFactor * this.context.time.deltaTime);
    +
    +            this.webXR.Rig.position.add(movementVector);
    +        }
    +    }
    +}
    +
    +
    +
    + + + diff --git a/component-compiler.html b/component-compiler.html new file mode 100644 index 000000000..dcb94b163 --- /dev/null +++ b/component-compiler.html @@ -0,0 +1,137 @@ + + + + + + + + + Automatic Component Generation | Documentation + + + + +

    Automatically generating Editor components

    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 compileropen in new window 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.

    Controlling component generation

    You can use the following comments in your typescript code to control C# code generation behavior:

    AttributeResult
    // @generate-componentForce generation of next class
    // @dont-generate-componentDisable generation of next class, this is useful in cases where you already have an existing C# script in your project
    // @serializeFieldDecorate generated field with [SerializeField]
    // @type UnityEngine.CameraSpecify generated C# field type
    // @nonSerializedSkip generating the next field or method

    Examples

    Force the component compiler to generate a C# AudioClip field named myAudioClip

    export class MyComponent extends Behaviour {
    +	//@type UnityEngine.AudioClip
    +	@serializable()
    +	myAudioClip?:string;
    +}
    +

    Force the component compiler to derive from a specific subclass

    //@type MyNamespace.MyCustomBaseClass
    +export class MyComponent extends MyCustomBaseClass {
    +}
    +

    Component Compiler in Unity

    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

    import { AssetReference, Behaviour, serializable } from "@needle-tools/engine";
    +import { Object3D } from "three";
    +
    +export class MyCustomComponent extends Behaviour {
    +    @serializable()
    +    myFloatValue: number = 42;
    +
    +    @serializable(Object3D)
    +    myOtherObject?: Object3D;
    +
    +    @serializable(AssetReference)
    +    prefabs: AssetReference[] = [];
    +
    +    start() {
    +        this.sayHello();
    +    }
    +
    +    private sayHello() {
    +        console.log("Hello World", this);
    +    }
    +}
    +
    // 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();
    +		}
    +	}
    +}
    +
    +

    Extending generated components

    Component C# classes are generated with the partialopen in new window 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.

    + + + diff --git a/component-reference.html b/component-reference.html new file mode 100644 index 000000000..909313854 --- /dev/null +++ b/component-reference.html @@ -0,0 +1,41 @@ + + + + + + + + + Built-in Components | Documentation + + + + +

    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 docsopen in new window.

    You can always add your own components or add wrappers for Unity components we haven't provided yet.

    Learn more in the Scripting section of our docs.

    Audio

    NameDescription
    AudioListener
    AudioSourceUse to play audio

    Animation

    NameDescription
    Animator with AnimatorControllerExport with animation state machine, conditions, transitions
    AnimationMost basic animation component. Only first clip is exported
    PlayableDirector with TimelineAssetExport powerful sequences to control animation, audio, state and more

    Rendering

    NameDescription
    Camera
    LightDirectionalLight, PointLight, Spotlight. Note that you can use it to bake light (e.g. Rectangular Light shapes) as well
    XRFlagControl when objects will be visible. E.g. only enable object when in AR
    DeviceFlagControl on which device objects will be visible
    LODGroup
    ParticleSystemExperimental and currently not fully supported
    VideoPlayerPlayback 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
    MeshRendererUsed to handle rendering of objects including lightmapping and instancing
    SkinnedMeshRendererSee MeshRenderer
    SpriteRendererUsed to render Sprites and Spriteanimations
    Volume with PostProcessing assetSee table below

    Postprocessing

    Postprocessing effects use the pmndrs postprocessing libraryopen in new window 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
    Effect Name
    Antialiasingextra Unity Component
    Bloomvia Volume asset
    Chromatic Aberrationvia Volume asset
    Color Adjustments / Color Correctionvia Volume asset
    Depth Of Fieldvia Volume asset
    Vignettevia Volume asset
    ToneMappingEffectvia Volume asset or separate component
    Pixelation
    Screenspace Ambient Occlusion N8
    Screenspace Ambient Occlusion
    Tilt Shift Effect
    SharpeningEffect
    Your custom effectSee example on stackblitzopen in new window

    Networking

    NameDescription
    SyncedRoomMain networking component. Put in your scene to enable networking
    NetworkingUsed to setup backend server for networking.
    SyncedTransformAutomatically network object transformation
    SyncedCameraAutomatically network camera position and view to other users in room. You can define how the camera is being rendered by referencing an object
    WebXRSyncNetworks WebXR avatars (AR and VR)
    VoipEnables voice-chat
    ScreensharingEnables screen-sharing capabilities

    Interaction

    NameDescription
    EventSystemHandles raising pointer events and UI events on objects in the scene
    ObjectRaycaterRequired for DragControls and Duplicatable
    GraphicsRaycasterSame as ObjectRaycaster but for UI elements
    DragControlsAllows objects to be dragged in the scene. Requires raycaster in parent hierarchy, e.g. ObjectRaycaster
    DuplicatableCan duplicate assigned objects by drag. Requires DragControls
    InteractableBasic component to mark an object to be interactable.
    OrbitControlsAdd to camera to add camera orbit control functionality
    SmoothFollowAllows to interpolate smoothly to another object's transform
    DeleteBoxWill destroy objects with the Deletable component when entering the box
    DeletableThe GameObject this component is attached to will be deleted when it enters or intersects with a DeleteBox
    DropListenerAdd to receive file drop events for uploading
    SpatialTriggerUse to raise event if an object enters a specific space or area. You can also use Physics events
    SpatialTriggerReceiverUse to receive events from SpatialTrigger

    Physics

    Physics is implemented using Rapieropen in new window.

    NameDescription
    RigidbodyAdd to make an object react to gravity (or be kinematic and static)
    BoxColliderA Box collider shape that objects can collide with or raise trigger events when set to trigger
    SphereColliderSee BoxCollider
    CapsuleColliderSee BoxCollider
    MeshColliderSee BoxCollider
    Physics MaterialsPhysics materials can be used to define e.g. the bouncyness of a collider

    XR / WebXR

    Read the XR docs

    NameDescription
    WebXRAdd to scene for VR, AR and Passthrough support as well as rendering Avatar models
    USDZExporterAdd to enable USD and Quicklook support
    XRFlagControl when objects are visible, e.g. only in VR or AR or only in ThirdPerson
    WebARSessionRootHandles placement and scale of your scene in AR mode
    WebARCameraBackgroundAdd to access the AR camera image and apply effects or use it for rendering
    WebXRImageTrackingAssign images to be tracked and optionally instantiate an object at the image position
    WebXRPlaneTrackingCreate plane meshes or colliders for tracked planes
    XRControllerModelCan be added to render device controllers or hand models (will be created by default when enabled in the WebXR component)
    XRControllerMovementCan be added to provide default movement and teleport controls
    XRControllerFollowCan be added to any object in the scene and configured to follow either left or right hands or controllers

    Debugging

    NameDescription
    GridHelperDraws a grid
    BoxGizmoDraws a box
    AxesHelperDraws XYZ axes
    Note: When you're writing custom code you can use the static Gizmos methods for drawing debugging lines and shapes

    Runtime File Input/Output

    NameDescription
    GltfExportExperimental! Use to export gltf from web runtime.
    DropListenerReceive file drop events for uploading and networking

    UI

    Spatial UI components are mapped from Unity UI (Canvas, not UI Toolkit) to three-mesh-uiopen in new window. UI can be animated.

    NameDescription
    CanvasUnity'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)
    ButtonReceives 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)
    ImageRenders a sprite image
    RawImageRenders a texture
    InputFieldAllows 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).

    Other

    NameDescription
    SceneSwitcherHandles loading and unloading of other scenes or prefabs / glTF files. Has features to preload, change scenes via swiping, keyboard events or URL navigation

    Editor Only

    NameDescription
    ExportInfoMain component for managing the web project(s) to e.g. install or start the web app
    EditorSyncAdd to enable networking material or component value changes to the running three.js app directly from the Unity Editor without having to reload
    + + + diff --git a/debugging.html b/debugging.html new file mode 100644 index 000000000..ffe5bb781 --- /dev/null +++ b/debugging.html @@ -0,0 +1,47 @@ + + + + + + + + + How To Debug | Documentation + + + + +

    Useful resources for working with glTF

    To inspect glTF or glb files online:

    To inspect them locally:

    Built-in URL parameters

    Debug Flags can be appended as URL query parameters.
    Use ?help to get a list of ALL parameters available.

    Here are some of the most commonly used:

    • help print all available url parameter in the console
    • console opens an on-screen dev console, useful for mobile debugging
    • printGltf logs loaded gltf files to the console
    • stats shows FPS module and logs threejs renderer stats every few seconds
    • showcolliders visualizes physics colliders
    • gizmos enables gizmo rendering (e.g. when using BoxCollider or AxesHelper components)
    • and a lot more: please use help to see them all

    Debug Methods

    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.

    Local Testing of release builds

    • First, install http-server: npm install -g http-server
    • make a build (development or production)
    • open the dist directory with a commandline tool
    • run http-server -g | -g enables gzip support
    • optional: if you want to test WebXR, generate a self-signed SSL certificateopen in new window, then run http-server -g -S to enable https (required for WebXR).

    VSCode

    You can attach VSCode to the running local server to set breakpoints and debug your code. You can read more about debugging with VSCodeopen in new window here.

    Create a launch.json file at .vscode/launch.json in your web project with the following content:

    {
    +    "version": "0.2.0",
    +    "configurations": [
    +        {
    +            "type": "chrome",
    +            "request": "launch",
    +            "name": "Attach Chrome",
    +            "url": "https://localhost:3000",
    +            "webRoot": "${workspaceFolder}"
    +        }
    +    ]
    +}
    +

    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:

    Mobile

    Android Debugging

    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.

    See the official chrome documentation hereopen in new window

    • Make sure Developer Modeopen in new window is enabled on your phone
    • Connect your phone to your computer via USB
    • Open this url in your browser chrome://inspect/#devices
    • On your mobile device allow the USB connection to your computer
    • On your computer in chrome you should see a list of open tabs after a while (on chrome://inspect/#devices)
    • Click Inspect on the tab you want to debug

    iOS Debugging

    For easy iOS debugging add the ?console URL parameter to get a useful on-screen JavaScript console.

    If you have a Mac, you can also attach to Safari (similar to the Android workflow above).

    WebXR usage and debugging on iOS requires using a third-party browser: Mozilla WebXR Vieweropen in new window.

    Quest Debugging

    Quest is just an Android device - see the Android Debugging section for steps.

    + + + diff --git a/debugging/vscode-start-debugging.webp b/debugging/vscode-start-debugging.webp new file mode 100644 index 000000000..d523557d6 Binary files /dev/null and b/debugging/vscode-start-debugging.webp differ diff --git a/deployment.html b/deployment.html new file mode 100644 index 000000000..cdddc9129 --- /dev/null +++ b/deployment.html @@ -0,0 +1,39 @@ + + + + + + + + + Deployment and Optimization | Documentation + + + + +

    What does deployment mean?

    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.

    Available Deployment Targets

    Feel something is missing?

    Please let us know in our discordopen in new window!

    Development Builds

    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 ktx2open in new window and dracoopen in new window 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).

    Production Builds

    To make a production build, you need to have toktxopen in new window installed, which provides texture compression using the KTX2 supercompression format. Please go to the toktx Releases Pageopen in new window 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-pipelineopen in new window for reference.

    Optimization and Compression Options

    Texture compression

    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.

    How do I choose between ETC1S, UASTC and WebP compression?

    FormatETC1SUASTCWebP
    GPU Memory UsageLowLowHigh (uncompressed)
    File SizeLowHighVery low
    QualityMediumVery highDepends on quality setting
    Typical usageWorks for everything, but best for color texturesHigh-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?

    image
    image

    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.
    Texture Compression options in Blender

    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
    image

    Mesh compression

    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.

    How do I choose between Draco and Meshopt?

    FormatDracoMeshopt
    GPU Memory UsageMediumLow
    File SizeLowestLow
    Animation compressionNoYes
    How can I set draco and meshopt compression settings?

    Add the MeshCompression component to select which compression should be applied per exported glTF.

    image

    • To change compression for the current scene just add it anywhere in your root scene.
    • To change compression for a prefab or NestedGltf add it to a GltfObject or the prefab that is referenced / exported by any of your components.
    • To change compression for a referenced scene just add it to the referenced scene that is exported
    Where to find mesh simplification options to reduce the vertex count when building for production?

    Select a Mesh and open the Needle importer options to see available options for the selected mesh:
    image

    Progressive Textures

    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.

    How can I enable progressive texture loading?

    Progressive textures can be enabled per texture
    or for all textures in your project:

    image

    Enable for all textures in the project that don't have any other specific setting:

    image

    Automatic Mesh LODs (Level of Detail)

    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.

    image

    image

    Deployment Options

    Deploy to Glitch ๐ŸŽ

    Glitchopen in new window 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?
    1. Add the DeployToGlitch component to the GameObject that also has the ExportInfo component.

    2. Click the Create new Glitch Remix button on the component image

    3. Glitch will now create a remix of the template. Copy the URL from your browser
      image

    4. Open Unity again and paste the URL in the Project Name field of your Deploy To Glitch component
      image

    5. 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)
      waiting for the key

    6. 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?

    Deploy To Glitch from Blender component

    1. Find the Deploy To Glitch panel in the Scene tab
    2. Click the Remix on glitch button on the component
    3. Your browser will open the glitch project template
    4. Wait for Glitch to generate a new project
    5. 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)
    6. On Glitch open the .env file and enter a password in the field Variable Value next to the DEPLOY_KEY
    7. Enter the same password in Blender in the Key field
    8. 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.

    Troubleshooting Glitch

    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.comopen in new window and make sure you are signed in. After that you then try clicking the button again in Unity or Blender.

    Deploy to Netlify

    How do I deploy to Netlify from Unity?

    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.

    Deploy to netlify component

    Deploy to netlify component

    Deploy to Vercel

    1. Create a new project on vercel
    2. Add your web project to a github repository
    3. Add the repository to your project on vercel

    See our sample projectopen in new window for the project configuration

    Deploy to itch.io

    How do I deploy to itch.io from Unity?
    1. Create a new project on itch.ioopen in new window

    2. Set Kind of project to HTML
      image

    3. Add the DeployToItch component to your scene and click the Build button
      image

    4. Wait for the build to finish, it will open a folder with the final zip when it has finished

    5. Upload to final zip to itch.io
      20220920-104629_Create_a_new_project_-itch io-_Google_Chrome-needle

    6. Select This file will be played in the browser
      image

    7. Save your itch page and view the itch project page.
      It should now load your Needle Engine project ๐Ÿ˜Š

    Optional settings

    image

    Itch.io: failed to find index.html

    Failed to find index.html

    image
    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.

    Deploy to FTP

    How do I deploy to my FTP server from Unity?
    1. 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)
    2. 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
    3. Click the Build & Deploy button on the DeployToFTP component to build your project and uploading it to your FTP account

    Deploy to FTP component in Unity
    ยน Deploy to FTP component

    Deploy to FTP server asset
    ยฒ FTP Server asset containing the access information of your FTP user account

    Deploy to FTP component in Unity with server asset assigned
    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?
    1. Open File > Build Settings, select Needle Engine, and click on Build
    2. Wait for the build to complete - the resulting dist folder will open automatically after all build and compression steps have run.
    3. Copy the files from the dist folder to your FTP storage.

    That's it! ๐Ÿ˜‰

    20220830-003602_explorer-needle

    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.

    Unity build window showing Needle Engine platform

    Enabling gzip using a .htaccess file

    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:

    <IfModule mod_mime.c>
    +RemoveType .gz
    +AddEncoding gzip .gz
    +AddType application/javascript .js.gz
    +

    Deploy to Github Pages

    How do I deploy to Github Pages from Unity?

    Add the DeployToGithubPages component to your scene and copy-paste the github repository (or github pages url) that you want to deploy to.
    Deploy To github pages component

    Deploy to Facebook Instant Games

    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: Deploy to facebook instant games component
    • 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 hostingHosting a facebook instant games
    • 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) Upload the zip to facebook instant games
    • 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)
    1. Create a new appopen in new window and select Other. Then click NextCreate facebook instant games app

    2. Select type Instant GamesCreate facebook instant games app

    3. After creating the app add the Instant Games product Add instant games product

    Here you can find the official instant games documentationopen in new window 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.

    Build To Folder

    In Unity open File/Build Settings and select Needle Engine for options:

    image

    image

    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).


    Cross-Platform Deployment Workflows

    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:
    image

    Needle Engine Commandline Arguments for Unity

    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:

    -scenepath 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)
    -buildProductionrun a production build
    -buildDevelopmentrun a development build
    -debugopen 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/everywhere-actions.html b/everywhere-actions.html new file mode 100644 index 000000000..f50519ee4 --- /dev/null +++ b/everywhere-actions.html @@ -0,0 +1,61 @@ + + + + + + + + + Everywhere Actions | Documentation + + + + +

    What are Everywhere Actions?

    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.

    Supported Platforms

    • Desktop
    • Mobile (Android / iOS)
    • VR Glasses
    • AR Devices
    • iOS AR โ€“ QuickLook (yes, really!)

    How do I use Everywhere Actions?

    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].

    List of Everywhere Actions

    ActionDescriptionExample Use Cases
    Play Animation on ClickPlays a selected animation state from an Animator. After playing, it can optionally transition to another animation.Product presentations, interactive tutorials, character movement
    Change Material on ClickSwitch out one material for others. All objects with that material will be switched together.Product configurators, characters
    Look AtMake an object look at the camera.UI elements, sprites, info graphics, billboard effects, clickable hotspots
    Play Audio on ClickPlays a selected audio clip.Sound effects, Narration, Museum exhibits
    Hide on StartHides an object at scene start for later reveal.
    Set Active on ClickShow or hide objects.
    Change Transform on ClickMove, rotate or scale an object. Allows for absolute or relative movement.Characters, products, UI animation (use animation for more complex movements)
    Audio SourcePlays audio on start and keeps looping. Spatial or non-spatialBackground music, ambient sounds
    WebXR Image TrackingTracks an image target and shows or hides objects.AR experiences, product presentations

    Samples

    Musical Instrument

    Demonstrates spatial audio, animation, and interactions.

    Simple Character Controllers

    Demonstrates combining animations, look at, and movement.

    Image Tracking

    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.

    Image Marker

    Download Sample Image Marker

    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.

    Interactive Building Blocks Overview

    Create your own Everywhere Actions

    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.

    TIP

    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.

    Triggers and Actions for QuickLook are based on Apple's Preliminary Interactive USD Schemasopen in new window

    Code Example

    Here's the implementation for HideOnStart as an example for how to create an Everywhere Action with implementations for both the browser and QuickLook:

    export class HideOnStart extends Behaviour implements UsdzBehaviour {
    +
    +    start() {
    +        this.gameObject.visible = false;
    +    }
    +
    +    createBehaviours(ext, model, _context) {
    +        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;
    +    }
    +}
    +

    TIP

    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.

    Low level methods for building your own actions

    Triggers
    TriggerBuilder.sceneStartTrigger
    TriggerBuilder.tapTrigger
    Actions
    ActionBuilder.fadeAction
    ActionBuilder.startAnimationAction
    ActionBuilder.waitAction
    ActionBuilder.lookAtCameraAction
    ActionBuilder.emphasize
    ActionBuilder.transformAction
    ActionBuilder.playAudioAction
    Group Actions
    ActionBuilder.sequence
    ActionBuilder.parallel
    GroupAction.addAction
    GroupAction.makeParallel
    GroupAction.makeSequence
    GroupAction.makeLooping
    GroupAction.makeRepeat

    To see the implementation of our built-in Everywhere Actions, please take look at src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts.

    Further reading

    The following pages provide more examples and samples that you can test and explore right now:

    + + + diff --git a/examples.html b/examples.html new file mode 100644 index 000000000..296d02162 --- /dev/null +++ b/examples.html @@ -0,0 +1,33 @@ + + + + + + + + + Example Projects โœจ | Documentation + + + + +

    Example Projects โœจ

    Explore some real world applications, websites and demos made with Needle Engine.

    Get started now โ€ข Learn more about our vision โ€ข Features Overview โ€ข Samples to download

    Needle Website

    Visit Website โ€” by Needle

    https://user-images.githubusercontent.com/5083203/186126996-27b45c5f-f3b9-40f7-b8c7-6ecba1d25a6e.mp4

    Castle Builder

    Play Now โ‡กopen in new window โ€” by Needle

    https://user-images.githubusercontent.com/5083203/186145731-705cfec2-1779-4a0b-97d9-95f3edaaf2d0.mp4

    Bike Configurator

    Bike Configurator โ‡กopen in new window โ€” by Needle

    https://user-images.githubusercontent.com/5083203/186146814-52fb05c7-a073-4efa-a226-47a9c1835413.mp4

    Sandbox Template

    Sandbox Template โ‡กopen in new window โ€” by Needle

    https://user-images.githubusercontent.com/5083203/186149117-ca7cf22f-dc7d-4c74-86d4-d78fe53a208c.mp4

    Songs of Cultures

    Songs of Cultures โ‡กopen in new window โ€” by A.MUSE

    https://user-images.githubusercontent.com/5083203/186147814-159a33f9-f1a6-47d4-804f-5f8f5a63125d.mp4

    Pokรฉmon Card

    Pokรฉmon Card โ‡กopen in new window โ€” Scene from Alex Ameye โ€ข Original Blog Post by Alex โ‡กopen in new window

    https://user-images.githubusercontent.com/5083203/186149736-49a697b3-4282-4b71-ab13-a6b176955c13.mp4

    Encryption in Space

    Encryption in Space โ‡กopen in new window โ€” by Katja Rempel & Nick Jwu

    https://user-images.githubusercontent.com/5083203/186151157-0c0a7d05-ad42-44be-b553-8d4cd48cbb81.mp4

    Physics Playground

    Physics Playground โ‡กopen in new window โ€” Scene from Bruno Simon

    https://user-images.githubusercontent.com/5083203/186149536-987ee796-3fe0-42bc-bd80-4c25aaf174aa.mp4

    + + + diff --git a/export.html b/export.html new file mode 100644 index 000000000..0cb5a4175 --- /dev/null +++ b/export.html @@ -0,0 +1,80 @@ + + + + + + + + + Exporting Assets to glTF | Documentation + + + + +

    Exporting Assets, Animations, Prefabs, Materials, Lightmaps...

    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.

    ๐Ÿ“ฆ Exporting glTF files

    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.

    Lazy loading and multiple levels / scenes

    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.toolsopen in new window by separating each section of your website into its own scene and only loading them when necessary)

    • Max. 50 MB export size uncompressed (usually ends up ~10-20 MB compressed)
    • 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

    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 scriptsopen in new window.

    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.

    Scene Assets

    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 websiteopen in new window each section is setup as a separate scene and on export packed into multiple glb files that we load on demand:

    2022-08-22-172605_Needle_Website_-Website-_Windows,_Mac,Linux-_U

    Loading a Prefab or Scene from a custom script

    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
    +    }  
    +} 
    +

    ๐Ÿ‡ Exporting Animations

    Needle Engine supports a considerable and powerful subset of Unity's animation features:

    • Timeline incl. activation tracks, animation tracks, track offsets
    • Animator incl. top-level state transitions
      • Blend trees are currently not supported.
      • Sub state machines are currently not supported.
    • AnimationClips incl. Loop modes
    • Procedural Animations can be created via scripting

    Needle Engine is one of the first to support the new glTF extension KHR_ANIMATION_POINTERopen in new window.
    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.

    ๐ŸŒ Exporting the Skybox

    The Unity skybox and custom reflection (if any) are baked into a texture on export and automatically exported inside the NEEDLE_lightmaps extension.

    To change the skybox resolution you can add a SkyboxExportSettings component to your scene.

    image

    If you don't want to skybox to be exported at all in a glb file you can untick the Embed Skybox option on your GltfObject component

    image

    โœจ Exporting Materials

    Physically Based Materials (PBR)

    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.

    Custom Shaders

    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

    2022-08-22-172029_Needle_Website_-CustomShaders-_Windows,_Mac,_Lin

    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).

    Current limitations

    • 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).

    ๐Ÿ’ก Exporting Lightmaps

    2022-08-22-171650_Needle_-_Google_Chrome

    To export lightmaps simply generate lightmapsopen in new window 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.

    • Lightmap Encoding: Normal Quality (adjust in Project Settings > Player)
    • Progressive GPU (faster and usually accurate enough for small scenes)
    • Non-Directional Lightmaps
    • Max Lightmap Size 2k (you can go higher, but expect large files)
    • Max 4x 2k lightmaps per scene (you can go higher, but expect large files)
    • Compress Lightmaps OFF (increases quality; otherwise will be compressed again at export time)

    2022-08-22-171356_Needle_Website_-Lightmaps-_Windows,_Mac,Linux-

    Mixing Baked and Non-Baked Objects

    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+
    20220826-175324-SqBL-Unity_pMXa-needle

    2020.3+
    20220826-175514-tnGc-Unity_mycs-needle

    If you have no baked objects in your scene, then the following settings should also yield correct results:

    Environment Lighting: Color
    +Ambient Color: any
    +
    + + + diff --git a/faq.html b/faq.html new file mode 100644 index 000000000..5fb9e9c1c --- /dev/null +++ b/faq.html @@ -0,0 +1,48 @@ + + + + + + + + + Questions and Answers (FAQ) ๐Ÿ’ก | Documentation + + + + +

    I purchased a license - how can I activate my Needle Engine License?

    Activating the license in Unity

    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

    unity license window

    Activating the license in Blender

    Open Addon Preferences/Needle Engine to get to the Needle Engine addon settings

    • Email - Enter the email you purchased the license with
    • Invoice ID - Enter one of the invoice ids that you received by email

    My objects are white after export

    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.

    • Read more about recommended glTF workflows:
    • Read more about custom shaders:

    There's a SSL error when opening the local website

    This is expected. We're enforcing HTTPS to make sure that WebXR and other modern web APIs work out-of-the-box, but that means some browsers complain that the SSL connection (between your local development server and the local website) can't be verified. It also prevents Automatic Reload from working on iOS and MacOS.

    See the Testing docs for information on how to set up a self-signed certificate for a smoother development experience.

    My local website stays black

    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.

    How to fix Uncaught ReferenceError: NEEDLE_ENGINE_META is not defined / NEEDLE_USE_RAPIER is not defined

    If you are using vite or next.js make sure to add the Needle Engine plugins to your config. Example for vite:

    const { needlePlugins } = await import('@needle-tools/engine/plugins/vite/index.js');
    +plugins: [needlePlugins(command, needleConfig)]
    +

    Example for next.js

    const { needleNext } = await import("@needle-tools/engine/plugins/next/index.js");
    +return needleNext({}, { modules: { webpack } });
    +

    You can also just declare the missing variables in e.g. your root index.html in a script tag like so:

    <script>
    +  var NEEDLE_ENGINE_META = {}
    +  var NEEDLE_USE_RAPIER = true;
    +</script>
    +

    THREE.EXRLoader: provided file doesnt appear to be in OpenEXR format

    Please make sure that sure that you have set Lightmap Encoding to Normal Quality.
    Go to Edit/Project Settings/Player for changing the setting.

    My website becomes too large / is loading slow (too many MB)

    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.

    My UI is not rendering Text

    • For Unity: Make sure that you use the UI/Legacy/Text component and not the TextMeshPro - Text component

    My scripts don't work after export

    • Your existing C# code will not export as-is, you have to write matching typescript / javascript for it.
    • Needle uses typescript / javascript for components and generates C# stubs for them.
    • Components that already have matching JS will show that in the Inspector.

    My lightmaps look different / too bright

    Ensure you're following best practices for lightmapsopen in new window and read about mixing baked and non-baked objectsopen in new window

    My scene is too bright / lighting looks different than in Unity

    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
      image
    • Light shadow intensity can currently not be changed due to a three.js limitation.

    Also see the docs on mixing baked and non-baked objectsopen in new window.

    My skybox resolution is low? How to change my skybox resolution

    • If you use a custom cubemap: You can override the texture import settings of the skybox texture (assigned to your cubemap)

      image

    • If you use the default skybox: Add a SkyboxExportSettings component anywhere in your scene to override the default resolution

      image

    My Shadows are not visible or cut off

    Please the following points:

    • 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.

    My colors look wrong

    Ensure your project is set to Linear colorspace.

    image

    I'm using networking and Glitch and it doesn't work if more than 30 people visit the Glitch page at the same time

    • 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 packageopen in new window itself somewhere else where it can scale e.g. Google Cloud.

    My website doesn't have AR/VR buttons

    • 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

    I created a new script in a sub-scene but it does not work

    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.

    My local server does not start / I do not see a website

    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.

    • Make sure you follow the Prerequisites.
    • 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.

    Does C# component generation work with javascript only too?

    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.

    class MyScript extends Behaviour
    +{
    +    //@type float
    +    myField = 5;
    +}
    +

    I don't have any buttons like "Generate Project" in my components/inspector

    Please check that you're not accidentally in the Inspector's Debug mode โ€“ switch back to Normal:
    20220824-025011-S2GQ-Unity_lKlT-needle

    Toktx can not be found / toktx is not installed

    • Make sure to download and install toktxopen in new window

    • 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

    image

    Installing the web project takes forever / does never finish / EONET: no such file or directory

    • 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:
      1. Open "System Information" (either windows key and type that or enter "msinfo32" in cmd)
      2. Select Components > Storage > Drives
      3. Select all (Ctrl + A) on the right side of the screen and copy that (Ctrl + C) and paste here (Ctrl + V)

    NPM install fails and there are errors about hard drive / IO

    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:

    1. Open "System Information" (either Windows key and type "System Information" or enter msinfo32 in cmd Windows + R)
    2. Select "Components > Storage > Drives"
    3. There, you can see all drives and their formatting listed. Put your projects on a drive that is NTFS formatted.

    I'm getting errors with "Unexpected token @. Expected identifier, string literal, numeric literal or ..."

    Needle Engine uses typescript decorators for serialization.
    To fix this error make sure to enable experimentalDecorators in your tsconfig.json

    I'm getting an error 'failed to load config ... vite.config.js' when running npm commands on Mac OS

    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.

    Circular reference error

    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 discordopen in new window.

    My scene is not loading and the console contains a warning with 'circular references' or 'failed to update active state'

    Please see the circular reference error section.

    Does my machine support WebGL 2?

    Use a detector like this oneopen in new window 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.

    Known devices to cause issues:

    • Lenovo Thinkpad - T495

    Still have questions? ๐Ÿ˜ฑ

    Ask in our friendly discord communityopen in new window

    + + + diff --git a/faq/lightmap_encoding.jpg b/faq/lightmap_encoding.jpg new file mode 100644 index 000000000..a1b8a6222 Binary files /dev/null and b/faq/lightmap_encoding.jpg differ diff --git a/features-overview.html b/features-overview.html new file mode 100644 index 000000000..ee9ffd0bd --- /dev/null +++ b/features-overview.html @@ -0,0 +1,33 @@ + + + + + + + + + Feature Overview | Documentation + + + + +

    Feature Overview

    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 Homepageopen in new window and our Samples and Showcaseopen in new window.

    Shaders and Materials

    Both PBR Materials and Custom shaders created with Shader Graph or other systems can be exported.

    Use the node based ShaderGraphopen in new window to create shaders for the web. ShaderGraph makes it easy for artists to keep creating without having to worry about syntax.

    Read more about PBR Materials โ€ข Custom Shaders

    Crossplatform: VR, AR, Mobile, Desktop

    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!

    Use Everywhere Actions for Interactive AR on both Android and iOS.

    Lightmaps

    lightmaps

    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 Bakeryopen in new window thus are also supported.

    Multiplayer and Networking

    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!

    Animations and Sequencing

    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.

    Animator

    The Animator and AnimatorControlleropen in new window 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.

    Timeline

    2022-08-23-013517_Scene

    We're also translating Unity's Timelineopen in new window setup and tracks into a web-ready format.
    Supported tracks include: AnimationTrack, AudioTrack, ActivationTrack, ControlTrack, SignalTrack.

    Note: Sub-Timelines are currently not supported.

    Note: It's possible to export custom timeline tracksopen in new window.

    Physics

    Use Rigidbodies, Mesh Colliders, Box Colliders and SphereColliders to add some juicy physics to your world.

    UI

    Building UI using Unity's UI canvas system is in development. Features currently include exporting Text (including fonts), Images, Buttons.

    See the ui component reference for supported component.

    Particles

    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 sampleopen in new window of supported features below:

    PostProcessing

    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.

    Editor Integrations

    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.

    Scripting

    Needle Engine uses as component based workflow. Create custom scripts in typescript or javascript. Use our modular npm-based package workflowopen in new window integrated into Unity. A typescript to C# component compileropen in new window produces Unity components magically on the fly.

    And there is more

    • PostProcessing โ†’ Bloom, Screenspace Ambient Occlusion, Depth of Field, Color Correction...
    • EditorSync โ†’ Live synchronize editing in Unity to the running three.js application for local development
    • Interactive AR on iOS and Android โ†’ Use our Everywhere Actions feature set or build your own

    Where to go next

    See our Getting Started Guide to learn about how to download and set up Needle Engine.
    Learn about our vision or dive deeper into some of the technical background and glTF powering it all.

    + + + diff --git a/for-unity-developers.html b/for-unity-developers.html new file mode 100644 index 000000000..fe27083dc --- /dev/null +++ b/for-unity-developers.html @@ -0,0 +1,33 @@ + + + + + + + + + Scripting basics For Unity Developers | Documentation + + + + +

    This page has been moved: continue here

    + + + diff --git a/getting-started.html b/getting-started.html new file mode 100644 index 000000000..cde9e6e4b --- /dev/null +++ b/getting-started.html @@ -0,0 +1,33 @@ + + + + + + + + + Documentation + + + + +
    + + + diff --git a/getting-started/for-unity-developers.html b/getting-started/for-unity-developers.html new file mode 100644 index 000000000..0bd9c4c45 --- /dev/null +++ b/getting-started/for-unity-developers.html @@ -0,0 +1,160 @@ + + + + + + + + + Scripting Introduction for Unity Developers | Documentation + + + + +

    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/newopen in new window to create a small project that you can edit in the browser โšก

    The Basics

    Needle Engine is a 3d web engine running on-top of three.jsopen in new window. 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.

    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)

    Creating a Component

    In Unity you create a new component by deriving from MonoBehaviour:

    using UnityEngine;
    +public class MyComponent : MonoBehaviour { 
    +}
    +

    A custom component in Needle Engine on the other hand is written as follows:

    import { Behaviour } from "@needle-tools/engine"
    +export class MyComponent extends Behaviour { 
    +}
    +

    Script Fields

    serializable

    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:

    @serializable(Behaviour)
    +myOtherComponent?: Behaviour;
    +@serializable(Object3D)
    +someOtherObject?: Object3D;
    +

    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 Javascriptopen in new window. These are boolean, number, bigint, string, null and undefined.

    @serializable() // < no type is needed here because the field type is a primitive
    +myString?: string;
    +

    public vs private

    Field without any accessor modified like private, public or protected will by default be public in javascript

    /// no accessor means it is public:
    +myNumber?: number;
    +// explicitly making it private:
    +private myPrivateNumber?: number;
    +protected myProtectedNumber?: number;
    +

    The same is true for methods as well.

    GameObjects and the Scene

    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++) 
    +    const ch = 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 traverseopen in new window method:

    this.gameObject.traverse(obj => console.log(obj))
    +

    or to just traverse visible objects use traverseVisibleopen in new window 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:

    for(const renderer of this.gameObject.getComponentsInChildren(Renderer))
    +    console.log(renderer);
    +

    For more information about getting components see the next section.

    Components

    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

    Finding Components in the Scene

    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 nameDesciption
    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).

    Renamed Unity Types

    Some Unity-specific types are mapped to different type names in our engine. See the following list:

    Type in UnityType in Needle Engine
    UnityEventEventListA UnityEvent will be exported as a EventList type (use serializable(EventList) to deserialize UnityEvents)
    GameObjectObject3D
    TransformObject3DIn 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.
    ColorRGBAColorThe 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

    Transform

    Transform data can be accessed on the GameObject / Object3D directly. Unlike to Unity there is no extra transform component that holds this data.

    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.

    WORLD- Position, Rotation, Scale...

    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

    Time

    Use this.context.time to get access to time data:

    • this.context.time.time is the time since the application started running
    • this.context.time.deltaTime is the time that has passed since the last frame
    • this.context.time.frameCount is the number of frames that have passed since the application started
    • this.context.time.realtimeSinceStartup is the unscaled time since the application has started running

    It is also possible to use this.context.time.timeScale to deliberately slow down time for e.g. slow motion effects.

    Raycasting

    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 rayopen in new window

    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();
    +

    Here is a editable example for physics raycastopen in new window

    Input

    Use this.context.input to poll input state:

    import { Behaviour } from "@needle-tools/engine";
    +export class MyScript extends Behaviour
    +{
    +    update(){
    +        if(this.context.input.getPointerDown(0)){
    +            console.log("POINTER DOWN")
    +        }
    +    }
    +}
    +

    You can also subscribe to events in the InputEvents enum like so:

    import { Behaviour, InputEvents } from "@needle-tools/engine";
    +
    +export class MyScript extends Behaviour
    +{
    +    onEnable(){
    +        this.context.input.addEventListener(InputEvents.PointerDown, this.onPointerDown);
    +    }
    +    onDisable() {
    +        // it is recommended to also unsubscribe from events when your component becomes inactive
    +        this.context.input.removeEventListener(InputEvents.PointerDown, this.onPointerDown);
    +    }
    +
    +    private onPointerDown = (evt) => { console.log(evt); }
    +}
    +

    If you want to handle inputs yourself you can also subscribe to all events the browser providesopen in new window (there are a ton). For example to subscribe to the browsers click event you can write:

    window.addEventListener("click", () => { console.log("MOUSE CLICK"); });
    +

    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).

    InputSystem Callbacks

    Similar to Unity (see IPointerClickHandler in Unityopen in new window) you can also register to receive input events on the component itself.

    To make this work make sure your object has a ObjectRaycaster or GraphicRaycaster component in the parent hierarchy.

    import { Behaviour, IPointerEventHandler, PointerEventData } from "@needle-tools/engine";
    +
    +export class ReceiveClickEvent extends Behaviour implements IPointerEventHandler {
    +    onPointerClick(args: PointerEventData) {
    +        console.log("Click", args);
    +    }
    +}
    +

    Note: IPointerEventHandler subscribes the object to all possible pointer events. The handlers for them are:

    • onPointerDown
    • onPointerUp
    • onPointerEnter
    • onPointerMove
    • onPointerExit
    • onPointerClick

    All have a PointerEventData argument describing the event.

    Debug.Log

    The Debug.Log() equivalent in javascript is console.log(). You can also use console.warn() or console.error().

    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);
    +

    Gizmos

    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

    Gizmos.DrawWireSphere(hit.point, 0.05, 0xff0000, 1);
    +

    Here are some of the available gizmo methods:

    Method name
    Gizmos.DrawArrow
    Gizmos.DrawBox
    Gizmos.DrawBox3
    Gizmos.DrawDirection
    Gizmos.DrawLine
    Gizmos.DrawRay
    Gizmos.DrawRay
    Gizmos.DrawSphere
    Gizmos.DrawWireSphere

    Useful Utility Methods

    Import from @needle-tools/engine e.g. import { getParam } from "@needle-tools/engine"

    Method nameDescription
    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() )
    +
    import { getParam } from "@needle-tools/engine"
    +// returns true 
    +const myFlag = getParam("some_flag")
    +console.log(myFlag)
    +

    The Web project

    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.comopen in new window. It is the most popular package registry out there for web projects.

    Here is an example of how a package.json might look like:

    {
    +  "name": "@optional_org/package_name",
    +  "version": "1.0.0",
    +  "scripts": {
    +    "start": "vite --host"
    +  },
    +  "dependencies": {
    +	  "@needle-tools/engine": "^3.5.9-beta",
    +	  "three": "npm:@needle-tools/three@0.146.8"
    +	},
    +  "devDependencies": {
    +	  "@types/three": "0.146.0",
    +	  "@vitejs/plugin-basic-ssl": "^1.0.1",
    +	  "typescript": "^5.0.4",
    +	  "vite": "^4.3.4",
    +	  "vite-plugin-compression": "^0.5.1"
    +	}
    +}
    +

    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.

    Installing packages & dependencies

    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 Engineopen in new window. 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.

    What's the difference between 'dependencies' and 'devDependencies'

    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.

    How do I install another package or dependency and how to use it?

    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.jsopen in new window.

    Let's assume you want to add a tweening library to your project. We will use @tweenjs/tween.jsopen in new window for this example. Hereopen in new window 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:

    "dependencies": {
    +    "@needle-tools/engine": "^3.5.11-beta",
    +    "@tweenjs/tween.js": "^20.0.3",
    +    "three": "npm:@needle-tools/three@0.146.8"
    +}
    +

    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 guideopen in new window 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:

    export class TweenRotation extends Behaviour {
    +
    +    // save the instance of our tweener
    +    private _tween?: TWEEN.Tween<any>; 
    +
    +    start() {
    +        // create the tween instance
    +        this._tween = new TWEEN.Tween(this.gameObject.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 hereopen in new window.

    Learning more

    + + + diff --git a/getting-started/index.html b/getting-started/index.html new file mode 100644 index 000000000..f26fe92b1 --- /dev/null +++ b/getting-started/index.html @@ -0,0 +1,75 @@ + + + + + + + + + Getting Started & Installation | Documentation + + + + +

    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.

    Needle Engine is available as a download for Unity, for Blender, and for web projects without an editor integration.



    Needle Engine for Unity

    Supported Unity versions: 2021.3 LTS, 2022.3 LTS

    Next Steps
    • 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 scenesopen in new window.

    Video Tutorial: Starting from a fresh Unity project

    Create a new scene from a Scene Template

    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.

    1. Click on File > New Scene

    2. Select one of the templates with (needle) in their name and click Create.
      We recommend the Collaborative Sandboxopen in new window template which is a great way to get started with interactivity, multiplayer, and adding assets.

    3. Click Play to install and startup your new web project.

    20220822-140539-wqvW-Unity_oC0z-needle

    Create a new scene from scratch

    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.

    1. Create a new empty scene

    2. 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.

    Note

    By default, the project name matches the name of your scene. If you want to change that, you can enter a Directory Name where you want to create your new runtime project. The path is relative to your Unity project.

    1. Choose a web project template Now, select a web project template for your project. The default template is based on Viteopen in new window, a fast web app bundler.

    2. Click Play to install and start your new web project




    Needle Engine for Blender

    Supported Blender versions: 4.0+


    • The Blender add-on is downloaded as a zip file.
    • In Blender, go to File > Settings > Add-ons and click the Install button.
    • Then select the downloaded zip to install it.

    See Needle Engine for Blender for a full list of features and instructions on getting started.




    Needle Engine without Editor Integration

    You can work directly with Needle Engine without using any Integration. Needle Engine uses three.jsopen in new window as scene graph and rendering backend, so all functionality from three.js is available in Needle as well.

    You can install Needle Engine from npm by running:

    npm i @needle-tools/engine

    For quick experiments, we provide a convenient link to create a new project ready to start, powered by StackBlitzopen in new window:
    engine.needle.tools/newopen in new window

    If you're not using npm or a bundler, you can instead add a prebundled version of Needle Engine to your website:

    https://unpkg.com/@needle-tools/engine/dist/needle-engine.min.js or
    https://unpkg.com/@needle-tools/engine/dist/needle-engine.light.min.js (no physics module, smaller)




    Third-Party Dependencies

    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. Please download it from the official website:

    Questions?

    The local website shows a warning: website not secure

    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. Now you should see your scene in the browser!

    Something is not working as expected? Where can I see logs?

    Keep an eye for console warnings! We log useful details about recommended project settings and so on. For example, your project should be set to Linear color space (not Gamma), and we'll log an error to the Unity console if that's not the case.

    Please also have a look at our FAQ if your question is not answered here.

    What's next?

    In case you need more troubleshooting help, please see the Questions and Answers section.
    You can also join our Discord Communityopen in new window or Forumopen in new window

    + + + diff --git a/getting-started/typescript-essentials.html b/getting-started/typescript-essentials.html new file mode 100644 index 000000000..6de6fe7f9 --- /dev/null +++ b/getting-started/typescript-essentials.html @@ -0,0 +1,115 @@ + + + + + + + + + Scripting in Needle Engine | Documentation + + + + +

    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:

    Key differences between C#, Javascript or 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.

    Types โ€” or the lack thereof

    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 JSDocopen in new window.

    Variables

    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 letopen in new window.

    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:

    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:

    const myPosition : Vector3 = new Vector3(0, 0, 0);
    +myPosition = new Vector3(100, 0, 0); // โš  ASSIGNING TO CONST IS NOT ALLOWED
    +

    Using or Importing Types

    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);
    +

    Primitive Types

    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 Vector(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:

    const myVector = new Vector3(1,1,1);
    +const myOtherVector = myVector;
    +myOtherVector.x = 42;
    +// will log: 42, 42
    +console.log(myVector.x, myOtherVector.x); 
    +

    Vector Maths and Operators

    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)

    const myFirstVector : Vector3 = new Vector3(1, 1, 1)
    +const myFactor = 100f;
    +myFirstVector.multiplyScalar(myFactor);
    +// โ†’ myFirstVector is now 100, 100, 100
    +

    Equality Checks

    loose vs strict comparison

    In C# when you want to check if two variables are the same you can write it as follows:

    var playerIsNull = myPlayer == null;
    +

    in Javascript/Typescript there is a difference between == and === where === is more strictly checking for the type:

    const playerIsNull = myPlayer === null;
    +const playerIsNullOrUndefined = myPlayer == null;
    +

    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 hereopen in new window

    Events, Binding and this

    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 Functionsopen in new window.

    myEvent?: EventList;
    +void onEnable() {
    +    this.myEvent.addEventListener(this.onMyEvent);
    +}
    +void onDisable() {
    +    this.myEvent.removeEventListener(this.onMyEvent);
    +}
    +// Declaring the function as an arrow method
    +// 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):

    myEvent?: EventList;
    +private _onMyEventFn?: Function;
    +void onEnable() {
    +    // bind this
    +    this._onMyEventFn = this.onMyEvent.bind(this);
    +    // add the bound method to the event
    +    this.myEvent?.addEventListener(this._onMyEventFn);
    +} 
    +void onDisable() {
    +    this.myEvent?.removeEventListener(this._onMyEventFn);
    +}
    +

    What's next?

    + + + diff --git a/html.html b/html.html new file mode 100644 index 000000000..235388e5c --- /dev/null +++ b/html.html @@ -0,0 +1,76 @@ + + + + + + + + + Frameworks, Bundlers, HTML | Documentation + + + + +

    Bundling and web frontends

    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 Productionopen in new window

    Vite, Vue, React, Svelte, React Three Fiber...

    Needle Engine is unoponiated about the choice of framework. Our default template uses the popular viteopen in new window 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 Toolsopen in new window website uses!. Find a sample to download hereopen in new window.

    • 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!

    • Vercel & Nextjs โ€” Find a example nextjs project hereopen in new window

    • 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!

    TIP

    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

    1. Select Create/Needle Engine/Project Template to add a ProjectTemplate into the folder you want to use as a template
    2. 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 to reduce bundle size

    Tree shaking refers to a common practice when it comes to bundling of web applications (see MSDN docsopen in new window). 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

    Creating a PWA

    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.

    1. Install the Vite PWA pluginopen in new window in your web project: npm install vite-plugin-pwa --save-dev
    2. 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.

    Testing PWAs

    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:

    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.

    Automatically update running apps

    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.

    const pwaOptions = {
    +  updateInterval: 15 * 60 * 1000, // 15 minutes, in milliseconds
    +};
    +

    Periodic Reloads and User Data

    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 documentationopen in new window for more information on how to implement a reload prompt instead of automatic reloading.

    More PWA options

    Since Needle uses the Vite PWA pluginopen in new window 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.

    Accessing Needle Engine and Components from external javascript

    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

    Customizing how loading looks

    See the Loading Display section in needle engine component reference

    Builtin styles

    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 loading-style="light"></needle-engine>

    Custom Loading Style โ€” PRO feature

    Please see the Loading Display section in needle engine component reference

    custom loading

    + + + diff --git a/icons/android-chrome-144x144.png b/icons/android-chrome-144x144.png new file mode 100644 index 000000000..9680622fd Binary files /dev/null and b/icons/android-chrome-144x144.png differ diff --git a/icons/apple-touch-icon.png b/icons/apple-touch-icon.png new file mode 100644 index 000000000..2d25b4f84 Binary files /dev/null and b/icons/apple-touch-icon.png differ diff --git a/icons/browserconfig.xml b/icons/browserconfig.xml new file mode 100644 index 000000000..b3930d0f0 --- /dev/null +++ b/icons/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/icons/favicon-16x16.png b/icons/favicon-16x16.png new file mode 100644 index 000000000..83022bbdd Binary files /dev/null and b/icons/favicon-16x16.png differ diff --git a/icons/favicon-32x32.png b/icons/favicon-32x32.png new file mode 100644 index 000000000..9835e8bbe Binary files /dev/null and b/icons/favicon-32x32.png differ diff --git a/icons/favicon.ico b/icons/favicon.ico new file mode 100644 index 000000000..974434168 Binary files /dev/null and b/icons/favicon.ico differ diff --git a/icons/mstile-150x150.png b/icons/mstile-150x150.png new file mode 100644 index 000000000..d3610882f Binary files /dev/null and b/icons/mstile-150x150.png differ diff --git a/icons/safari-pinned-tab.svg b/icons/safari-pinned-tab.svg new file mode 100644 index 000000000..f36a1bd6a --- /dev/null +++ b/icons/safari-pinned-tab.svg @@ -0,0 +1,27 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + + diff --git a/imgs/banner.webp b/imgs/banner.webp new file mode 100644 index 000000000..9c60ec03a Binary files /dev/null and b/imgs/banner.webp differ diff --git a/imgs/custom-loading-style.webp b/imgs/custom-loading-style.webp new file mode 100644 index 000000000..ae2bba3ff Binary files /dev/null and b/imgs/custom-loading-style.webp differ diff --git a/imgs/everywhere-actions-component-menu.gif b/imgs/everywhere-actions-component-menu.gif new file mode 100644 index 000000000..88ab8c6d4 Binary files /dev/null and b/imgs/everywhere-actions-component-menu.gif differ diff --git a/imgs/ktx-env-variable.webp b/imgs/ktx-env-variable.webp new file mode 100644 index 000000000..f4c9e624a Binary files /dev/null and b/imgs/ktx-env-variable.webp differ diff --git a/imgs/networking_absolute.webp b/imgs/networking_absolute.webp new file mode 100644 index 000000000..f0adeb1e7 Binary files /dev/null and b/imgs/networking_absolute.webp differ diff --git a/imgs/threejs-logo.webp b/imgs/threejs-logo.webp new file mode 100644 index 000000000..ec0969905 Binary files /dev/null and b/imgs/threejs-logo.webp differ diff --git a/imgs/unity-build-window-menu.jpg b/imgs/unity-build-window-menu.jpg new file mode 100644 index 000000000..863cab49d Binary files /dev/null and b/imgs/unity-build-window-menu.jpg differ diff --git a/imgs/unity-build-window.jpg b/imgs/unity-build-window.jpg new file mode 100644 index 000000000..50b91cd4b Binary files /dev/null and b/imgs/unity-build-window.jpg differ diff --git a/imgs/unity-lods-settings-1.jpg b/imgs/unity-lods-settings-1.jpg new file mode 100644 index 000000000..8fd711a9a Binary files /dev/null and b/imgs/unity-lods-settings-1.jpg differ diff --git a/imgs/unity-lods-settings-2.jpg b/imgs/unity-lods-settings-2.jpg new file mode 100644 index 000000000..203fface5 Binary files /dev/null and b/imgs/unity-lods-settings-2.jpg differ diff --git a/imgs/unity-logo.webp b/imgs/unity-logo.webp new file mode 100644 index 000000000..94b943ad6 Binary files /dev/null and b/imgs/unity-logo.webp differ diff --git a/imgs/unity-mesh-compression-component.jpg b/imgs/unity-mesh-compression-component.jpg new file mode 100644 index 000000000..f68b7ac17 Binary files /dev/null and b/imgs/unity-mesh-compression-component.jpg differ diff --git a/imgs/unity-mesh-simplification.jpg b/imgs/unity-mesh-simplification.jpg new file mode 100644 index 000000000..4c3a4b11d Binary files /dev/null and b/imgs/unity-mesh-simplification.jpg differ diff --git a/imgs/unity-needle-engine-license.jpg b/imgs/unity-needle-engine-license.jpg new file mode 100644 index 000000000..6475feb07 Binary files /dev/null and b/imgs/unity-needle-engine-license.jpg differ diff --git a/imgs/unity-needle-engine-modules-physics.jpg b/imgs/unity-needle-engine-modules-physics.jpg new file mode 100644 index 000000000..164aff107 Binary files /dev/null and b/imgs/unity-needle-engine-modules-physics.jpg differ diff --git a/imgs/unity-progressive-textures.jpg b/imgs/unity-progressive-textures.jpg new file mode 100644 index 000000000..37e576f0c Binary files /dev/null and b/imgs/unity-progressive-textures.jpg differ diff --git a/imgs/unity-project-local-template.jpg b/imgs/unity-project-local-template.jpg new file mode 100644 index 000000000..fb1ca1959 Binary files /dev/null and b/imgs/unity-project-local-template.jpg differ diff --git a/imgs/unity-project-remote-template.jpg b/imgs/unity-project-remote-template.jpg new file mode 100644 index 000000000..d2c566d9b Binary files /dev/null and b/imgs/unity-project-remote-template.jpg differ diff --git a/imgs/unity-texture-compression-options.jpg b/imgs/unity-texture-compression-options.jpg new file mode 100644 index 000000000..790887490 Binary files /dev/null and b/imgs/unity-texture-compression-options.jpg differ diff --git a/imgs/unity-texture-compression.jpg b/imgs/unity-texture-compression.jpg new file mode 100644 index 000000000..2c1f9a03a Binary files /dev/null and b/imgs/unity-texture-compression.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..8a5d71137 --- /dev/null +++ b/index.html @@ -0,0 +1,39 @@ + + + + + + + + + Documentation + + + + +

    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


    + + + diff --git a/logo.png b/logo.png new file mode 100644 index 000000000..88ea1adf4 Binary files /dev/null and b/logo.png differ diff --git a/manifest.webmanifest b/manifest.webmanifest new file mode 100644 index 000000000..9daf50f46 --- /dev/null +++ b/manifest.webmanifest @@ -0,0 +1,15 @@ +{ + "id" : "needle_engine_docs", + "name": "Needle Engine Documentation", + "short_name": "Needle Engine Docs", + "description": "Needle Engine is a web-based runtime for 3D apps", + "start_url": "/docs/index.html", + "display": "standalone", + "icons": [ + { + "src": "https://engine.needle.tools/docs/android-chrome-144x144.png", + "sizes": "144x144", + "type": "image/png" + } + ] + } \ No newline at end of file diff --git a/meta-test.html b/meta-test.html new file mode 100644 index 000000000..e313d44d1 --- /dev/null +++ b/meta-test.html @@ -0,0 +1,35 @@ + + + + + + + + + Documentation + + + + +

    Hello world ``` hello ```

    meta:
    +
    +

    my_test_header

    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

    UI Canvas

    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

    How physics works

    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..51641ef62 --- /dev/null +++ b/meta/tags.json @@ -0,0 +1 @@ +{"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"}],"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"}],"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..f54483c5e --- /dev/null +++ b/modules.html @@ -0,0 +1,37 @@ + + + + + + + + + Additional Modules | 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

    Github repositoryopen in new window

    + + + diff --git a/needle-logo-black.svg b/needle-logo-black.svg new file mode 100644 index 000000000..9bd940710 --- /dev/null +++ b/needle-logo-black.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/networking.html b/networking.html new file mode 100644 index 000000000..7589556e3 --- /dev/null +++ b/networking.html @@ -0,0 +1,97 @@ + + + + + + + + + Networking | Documentation + + + + +

    Networking

    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 websocketsopen in new window and sending either json strings (for infrequent updates) or flatbuffersopen in new window (for frequent updates). Continue reading below for more details:

    Using Multiplayer

    • Enable Networking
      Add a SyncedRoom component.

    • Enable Desktop Viewer Sync
      Add a SyncedCamera component.

    • Enable XR Avatar Sync
      Add a WebXRSync component.

    • Enable Voice Chat
      Add a VoIP component.

    • Enable Screensharing
      Add a ScreenCapture component.

    Core Components

    • 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
    • SyncedTransform โ€” handles synchronizing transforms
    • SyncedCamera โ€” spawns a prefab for any user connected to the room which will follow their position
    • WebXRSync โ€” handles synchronization for AR and VR users
    • VoIP โ€” handles voice-over-IP audio connections, microphone access etc. between users
    • Networking โ€” use to customize the server backend url

    Manual Networking

    Sending

    Send a json message to all users in the same room:
    this.context.connection.send(key:string, data: IModel | object | boolean | string | number | null)

    Send a flatbuffer binary array to all users in the same room:
    this.context.connect.sendBinary(arr:Uint8Array)

    Persistence

    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" }

    Receiving

    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

    Common Events

    Room Events
    this.context.beginListen(RoomEvents.JoinedRoom, () => { })Listen to the event when you have joined a networked room
    this.context.beginListen(RoomEvents.LeftRoom, () => { })Listen to the event when you have left a networked room
    this.context.beginListen(RoomEvents.UserJoinedRoom, () => { })Listen to the event when another user has joined your networked room
    this.context.beginListen(RoomEvents.UserLeftRoom, () => { })Listen to the event when another user has left your networked room

    Auto Networking (experimental)

    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 } 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();
    +    }
    +}
    +

    Flatbuffers for your own components

    Networking Package

    Needle Engine currently uses its own networking packageopen in new window 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:

    endpoint stringrelative path to the websocket endpoint (e.g. /socket)
    maxUsers numberAmount of users allowed per room
    defaultUserTimeout numberTimeout length in seconds until a user is kicked from a room (if no ping is received). Defaults to 30 seconds

    Networking on Glitch

    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.

    Limitations

    • approx. 15-20 people maximum โ€“ afterwards the small default Glitch server instance becomes slow

    Local Networking

    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:

    Hosting your own Networking Server

    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 Repositoryopen in new window

    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:

    Needle Engine Networking component with networking server hosted elswhere

    peerjs

    Needle Engine Screencapture / Screensharing and Voip components use peerjsopen in new window for networking audio and video.

    Customizing peerjs options

    • 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..39dfdb1ab --- /dev/null +++ b/project-structure.html @@ -0,0 +1,33 @@ + + + + + + + + + Project Structure | Documentation + + + + +

    Needle Engine Project Structure

    Web Project Files

    Needle Engine
    needle.config.jsonConfiguration for Needle Engine builds and integrations
    Ecosystem
    package.jsonProject configuration containing name, version, dependencies and scripts
    tsconfig.jsonTypescript compiler configuration
    .gitignoreFiles and folders to be ignored in git
    vite.config.jsContains vite specific configuration.
    It also adds the Needle Engine vite plugins.

    Default Vite project structure

    Our main project template uses the superfast viteopen in new window 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.htmlThe landing- or homepage of your website
    vite.configThe vite configopen in new window. Settings for building the distribution and hosting the development server are made here. It is usually not necessary to edit these settings.
    src/main.tsIncluded 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.

    Learn more in the docs about bundling and usage with other frameworks


    Unity Project Folders and Files

    Folder
    Unity
    AssetsThis is where project specific/exclusive assets live.
    PackagesThis 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 packagesopen in new window.
    Needle Engine Unity Package
    Core/Runtime/ComponentsContains all Needle Engine built-in runtime components (See Packages/Needle Engine Exporter in the Unity Project Window)

    Projects in Unity

    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). It is also possible to create custom local templates using the Project View context menu in Create/Needle Engine/Project Template.

    Unity ExportInfo local templates

    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-engineopen in new window

    Unity ExportInfo local templates

    Temporary Projects

    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!

    Typescript in Unity

    NPM Definition are npm packagesopen in new window 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.

    Creating and installing a npmdef

    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).

    image

    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.

    Continue Reading

    + + + diff --git a/reference/needle-config-json.html b/reference/needle-config-json.html new file mode 100644 index 000000000..6e47a1e80 --- /dev/null +++ b/reference/needle-config-json.html @@ -0,0 +1,59 @@ + + + + + + + + + needle.config.json | Documentation + + + + +

    The needle.config.json is used to provide configuration for the Needle Editor integrations and for the Needle Engine build pipeline plugins.

    Paths
    buildDirectoryThis is where the built project files are being copied to
    assetsDirectoryThis is where the Editor integration assets will be copied to or created at (e.g. the .glb files exported from Unity or Blender)
    scriptsDirectoryThis is the directory the Editor integration is watching for code changes to re-generate components
    codegenDirectoryThis is where the Editor integration is outputting generated files to.
    baseUrlRequired 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.

    Basic Example

    {
    +  "buildDirectory": "dist",
    +  "assetsDirectory": "assets",
    +  "scriptsDirectory": "src/scripts",
    +  "codegenDirectory": "src/generated"
    +}
    +

    Copy Example

    {
    +  "buildDirectory": "dist",
    +  "assetsDirectory": "assets",
    +  "scriptsDirectory": "src/scripts",
    +  "codegenDirectory": "src/generated",
    +  "build": {
    +    "copy": [
    +      "cards"
    +    ]
    +  }
    +}
    +

    Example with different baseUrl (e.g. SvelteKit, Next.js)

    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.

    {
    +  "baseUrl": "assets",
    +  "buildDirectory": "dist",
    +  "assetsDirectory": "static/assets",
    +  "scriptsDirectory": "src/scripts",
    +  "codegenDirectory": "src/generated"
    +}
    +
    + + + diff --git a/reference/needle-engine-attributes.html b/reference/needle-engine-attributes.html new file mode 100644 index 000000000..afc6581ec --- /dev/null +++ b/reference/needle-engine-attributes.html @@ -0,0 +1,54 @@ + + + + + + + + + <needle-engine> Configuration | Documentation + + + + +

    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:

    AttributeDescription
    Loading
    srcPath to one or multiple glTF or glb files.
    Supported types are string, string[] or a stringified array (, separated)
    dracoDecoderPathURL to the draco decoder
    dracoDecoderTypedraco decoder type. Options are wasm or js. See three.js documentationopen in new window
    ktx2DecoderPathURL to the KTX2 decoder
    Rendering
    skybox-imageoptional, URL to a skybox image (background image) or a preset string: studio, blurred-skybox, quicklook, quicklook-ar
    environment-imageoptional, URL to a environment image (environment light) or a preset string: studio, blurred-skybox, quicklook, quicklook-ar
    contactshadowsoptional, render contact shadows
    tone-mappingoptional, supported values are none, linear, neutral, agx
    tone-mapping-exposureoptional number e.g. increase exposure with tone-mapping-exposure="1.5", requires tone-mapping to be set
    Interaction
    autoplayadd or set to true to auto play animations e.g. <needle-engine autoplay
    camera-controlsadd or set to true to automatically add OrbitControls if no camera controls are found in the scene
    auto-rotateadd to enable auto-rotate (only used with camera-controls)
    Events
    loadstartName 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
    progressName of the function to call when loading updates. onProgress(ctx:Context, evt: {detail: {context:Context, name:string, index:number, count:number, totalProgress01:number}) { ... }
    loadfinishedName of the function to call when loading finishes
    Loading DisplayAvailable options to change how the Needle Engine loading display looks. Use ?debugloadingrendering for easier editing
    loading-styleOptions are light or dark
    loading-background-colorPRO โ€” Change the loading background color (e.g. =#dd5500)
    loading-text-colorPRO โ€” Change the loading text color
    loading-logo-srcPRO โ€” Change the loading logo image
    primary-colorPRO โ€” Change the primary loading color
    secondary-colorPRO โ€” Change the secondary loading color
    hide-loading-overlayPRO โ€” Do not show the loading overlay, added in Needle Engine > 3.17.1
    Internal
    hashUsed 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.

    Examples

    <!-- Setting the path to a custom glb to be loaded -->
    +<needle-engine src="path/to/your.glb"></needle-engine>
    +
    <!-- Overriding where the draco decoder is located -->
    +<needle-engine src="path/to/your.glb" dracoDecoderPath="path/to/draco/folder"></needle-engine>
    +

    Setting environment images, playing animation and automatic camera controls. See it live on stackblitzopen in new window

    <needle-engine
    +      camera-controls
    +      auto-rotate
    +      autoplay
    +      skybox-image="https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/industrial_sunset_puresky_1k.hdr"
    +      environment-image="https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/industrial_sunset_puresky_1k.hdr"
    +      src="https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/DamagedHelmet/glTF-Embedded/DamagedHelmet.gltf"
    +      >
    +      </needle-engine>
    +

    Receiving an event when the needle-engine context has finished loading:

    <needle-engine loadfinished="onLoadFinished"> </needle-engine>
    +<script>
    +    function onLoadFinished() {
    +        console.log("Needle Engine has finished loading");
    +    }
    +</script>
    +

    Custom Loading Style (PRO)

    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.

    custom loading
    See code on githubopen in new window

    + + + diff --git a/reference/typescript-decorators.html b/reference/typescript-decorators.html new file mode 100644 index 000000000..db34b49c2 --- /dev/null +++ b/reference/typescript-decorators.html @@ -0,0 +1,87 @@ + + + + + + + + + @serializable and other decorators | Documentation + + + + +

    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
    @registerTypeNo argument. Can be added to a custom component class to be registered to the Needle Engine types and to enable hot reloading support.

    Examples

    Serializable

    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[];
    +}
    +

    SyncField

    export class MyScript extends Behaviour {
    +
    +    @syncField(MyScript.prototype.onNumberChanged)
    +    @serializable()
    +    myNumber: number = 42;
    +
    +    private onNumberChanged(newValue : number, oldValue : number){
    +        console.log("Number changed from ", oldValue, "to", newValue)
    +    }
    +}
    +

    Validate

    export class MyScript extends Behaviour {
    +
    +    @validate()
    +    @serializable()
    +    myNumber?: number;
    +
    +    start() { setInterval(() => this.myNumber = Math.random(), 1000) }
    +
    +    onValidate(fieldName: string) {
    +        console.log("Validate", fieldName, this.myNumber);
    +    }
    +}
    +

    Prefix

    Live exampleopen in new window

    import { Camera } 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
    +    }
    +}
    +
    + + + diff --git a/samples-and-modules.html b/samples-and-modules.html new file mode 100644 index 000000000..94eeb6549 --- /dev/null +++ b/samples-and-modules.html @@ -0,0 +1,67 @@ + + + + + + + + + Samples Projects | Documentation + + + + +

    Samples to download and play

    View all samples at engine.needle.tools/samplesopen in new window with a live preview and links for download and installation.

    + + + diff --git a/scripting-examples.html b/scripting-examples.html new file mode 100644 index 000000000..4847d740d --- /dev/null +++ b/scripting-examples.html @@ -0,0 +1,781 @@ + + + + + + + + + Scripting Examples | Documentation + + + + +

    Scripting Examples

    Video tutorial: How to write custom componentsopen in new window

    Below you will find a few basic scripts as a quick reference.

    We also offer a lot of sample scenes and complete projects that you can download and use as a starting point:

    Basic component

    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);
    +    }
    +}
    +

    see scripting for all component events

    Reference an Object from Unity

    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;
    +} 
    +

    Reference and load an asset from Unity (Prefab or SceneAsset)

    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
    +    }  
    +} 
    +

    Reference and load scenes from Unity

    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();
    +        }
    +    }
    +} 
    +

    Receive Clicks on Objects

    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);
    +    }
    +}
    +

    Networking Clicks on Objects

    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.

    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();
    +    }
    +    
    +}
    +

    Play Animation on click

    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();
    +        }
    +    }
    +}
    +

    Reference an Animation Clip

    This can be useful if you want to run your custom animation logic.
    You can also export an array of clips.

    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);
    +    }
    +}
    +

    Create and invoke a UnityEvent

    import { Behaviour, serializable, EventList } from "@needle-tools/engine"
    +
    +export class MyComponent extends Behaviour {
    +
    +    @serializable(EventList)
    +    myEvent? : EventList;
    +
    +    start() {
    +        this.myEvent?.invoke();
    +    }
    +}
    +

    TIP

    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 discordopen in new window

    Declare a custom event type

    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);
    +    }
    +}
    +

    Example use:
    20221128-210735_Unity-needle

    Use nested objects and serialization

    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.

    In your typescript component:

    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();
    +    }
    +}
    +

    In C# in any script:

    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;
    +}
    +

    TIP

    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.

    Use Web APIs

    TIP

    Keep in mind that you still have access to all web apis and npmopen in new window packages!
    That's the beauty of Needle Engine if we're allowed to say this here ๐Ÿ˜Š

    Display current location

    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);
    +        });
    +    }
    +}
    +

    Display current time using a Coroutine

    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)
    +        }
    +    };
    +}
    +

    Change custom shader property

    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 samplesopen in new window

    import { Behaviour, serializable } from "@needle-tools/engine";
    +import { Material } from "three";
    +
    +declare type MyCustomShaderMaterial = {
    +   _Speed: number;
    +};
    +
    +export class IncreaseShaderSpeedOverTime extends Behaviour {
    +
    +   @serializable(Material)
    +   myMaterial?: Material & MyCustomShaderMaterial;
    +
    +   update() {
    +       if (this.myMaterial) {
    +           this.myMaterial._Speed *= 1 + this.context.time.deltaTime;
    +           if(this.myMaterial._Speed > 1) this.myMaterial._Speed = .0005;
    +           if(this.context.time.frame % 30 === 0) console.log(this.myMaterial._Speed)
    +       }
    +   }
    +}
    +

    Switching src attribute

    See live exampleopen in new window on StackBlitz

    Adding new postprocessing effects

    Make sure to install npm i postprocessingopen in new window 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 effectopen in new window. 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);
    +

    Custom ParticleSystem Behaviour

    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())
    +    }
    +}
    +

    Custom 2D Audio Component

    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();
    +        })
    +    }
    +}
    +

    Arbitrary external files

    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());
    +    }
    +}
    +

    Receiving html element click in component

    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();
    +    }
    +}
    +

    Disable environment light

    import { Behaviour } from "@needle-tools/engine";
    +import { Texture } from "three";
    +
    +export class DisableEnvironmentLight extends Behaviour {
    +
    +   private _previousEnvironmentTexture: Texture | null = null;
    +
    +   onEnable(): void {
    +       this._previousEnvironmentTexture = this.context.scene.environment;
    +       this.context.scene.environment = null;
    +   }
    +
    +   onDisable(): void {
    +       this.context.scene.environment = this._previousEnvironmentTexture;
    +   }
    +}
    +

    Use mediapipe package to control the 3D scene with hands

    Make sure to install the mediapipe package. Visit the github link below to see the complete project setup.
    Try it live hereopen in new window - requires a webcam/camera

    import { FilesetResolver, HandLandmarker, HandLandmarkerResult, NormalizedLandmark } from "@mediapipe/tasks-vision";
    +import { Behaviour, Mathf, serializable, showBalloonMessage } from "@needle-tools/engine";
    +import { ParticleSphere } from "./ParticleSphere";
    +
    +export class MediapipeHands extends Behaviour {
    +
    +    @serializable(ParticleSphere)
    +    spheres: ParticleSphere[] = [];
    +
    +    private _video!: HTMLVideoElement;
    +    private _handLandmarker!: HandLandmarker;
    +
    +    async awake() {
    +        showBalloonMessage("Initializing mediapipe...")
    +
    +        const vision = await FilesetResolver.forVisionTasks(
    +            // path/to/wasm/root
    +            "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"
    +        );
    +        this._handLandmarker = await HandLandmarker.createFromOptions(
    +            vision,
    +            {
    +                baseOptions: {
    +                    modelAssetPath: "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/latest/hand_landmarker.task",
    +                    delegate: "GPU"
    +                },
    +                numHands: 2
    +            });
    +        //@ts-ignore
    +        await this._handLandmarker.setOptions({ runningMode: "VIDEO" });
    +
    +        this._video = document.createElement("video");
    +        this._video.setAttribute("style", "max-width: 30vw; height: auto;");
    +        console.log(this._video);
    +        this._video.autoplay = true;
    +        this._video.playsInline = true;
    +        this.context.domElement.appendChild(this._video);
    +        this.startWebcam(this._video);
    +    }
    +
    +    private _lastVideoTime: number = 0;
    +
    +    update(): void {
    +        if (!this._video || !this._handLandmarker) return;
    +        const video = this._video;
    +        if (video.currentTime !== this._lastVideoTime) {
    +            let startTimeMs = performance.now();
    +            showBalloonMessage("<strong>Control the spheres with one or two hands</strong>!<br/><br/>Sample scene by <a href='https://twitter.com/llllkatjallll/status/1659280435023605773'>Katja Rempel</a>")
    +            const detections = this._handLandmarker.detectForVideo(video, startTimeMs);
    +            this.processResults(detections);
    +            this._lastVideoTime = video.currentTime;
    +        }
    +
    +    }
    +
    +    private processResults(results: HandLandmarkerResult) {
    +        const hand1 = results.landmarks[0];
    +        // check if we have even one hand
    +        if (!hand1) return;
    +
    +        if (hand1.length >= 4 && this.spheres[0]) {
    +            const pos = hand1[4];
    +            this.processLandmark(this.spheres[0], pos);
    +        }
    +
    +        // if we have a second sphere:
    +        if (this.spheres.length >= 2) {
    +            const hand2 = results.landmarks[1];
    +            if (!hand2) {
    +                const pos = hand1[8];
    +                this.processLandmark(this.spheres[1], pos);
    +            }
    +            else {
    +                const pos = hand2[4];
    +                this.processLandmark(this.spheres[1], pos);
    +            }
    +        }
    +    }
    +
    +    private processLandmark(sphere: ParticleSphere, pos: NormalizedLandmark) {
    +        const px = Mathf.remap(pos.x, 0, 1, -6, 6);
    +        const py = Mathf.remap(pos.y, 0, 1, 6, -6);
    +        sphere.setTarget(px, py, 0);
    +    }
    +
    +    private async startWebcam(video: HTMLVideoElement) {
    +        const constraints = { video: true, audio: false };
    +        const stream = await navigator.mediaDevices.getUserMedia(constraints);
    +        video.srcObject = stream;
    +    }
    +}
    +

    Change Color On Collision

    import { Behaviour, Collision, Renderer } from "@needle-tools/engine";
    +import{ Color } from "three";
    +
    +export class ChangeColorOnCollision extends Behaviour {
    +
    +    private renderer: Renderer | null = null;
    +    private collisionCount: number = 0;
    +
    +    private _startColor? : Color[];
    +
    +    start() {
    +        this.renderer = this.gameObject.getComponent(Renderer);
    +        if (!this.renderer) return;
    +        if(!this._startColor) this._startColor = [];
    +        for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
    +            this.renderer.sharedMaterials[i] = this.renderer.sharedMaterials[i].clone();
    +            this._startColor[i] = this.renderer.sharedMaterials[i]["color"].clone();
    +        }
    +    }
    +
    +    onCollisionEnter(_col: Collision) {
    +        if (!this.renderer) return;
    +        this.collisionCount += 1;
    +        for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
    +            this.renderer.sharedMaterials[i]["color"].setRGB(Math.random(), Math.random(), Math.random());
    +        }
    +    }
    +
    +    onCollisionExit(_col: Collision) {
    +        if (!this.renderer || !this._startColor) return;
    +        this.collisionCount -= 1;
    +        if (this.collisionCount === 0) {
    +            for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
    +                this.renderer.sharedMaterials[i]["color"].copy(this._startColor[i])
    +                // .setRGB(.1, .1, .1);
    +            }
    +        }
    +    }
    +
    +    // more events:
    +    // onCollisionStay(_col: Collision)
    +    // onCollisionExit(_col: Collision)
    +}
    +

    Physics Trigger Relay

    Invoke events using an objects physics trigger methods

    export class PhysicsTrigger extends Behaviour {
    +
    +    @serializeable(GameObject)
    +    triggerObjects?:GameObject[];
    +
    +    @serializeable(EventList)
    +    onEnter?: EventList;
    +
    +    @serializeable(EventList)
    +    onStay?: EventList;
    +
    +    @serializeable(EventList)
    +    onExit?: EventList;
    +
    +    onTriggerEnter(col: Collider) {
    +        if(this.triggerObjects && this.triggerObjects.length > 0 && !this.triggerObjects?.includes(col.gameObject)) return;
    +        this.onEnter?.invoke();
    +    }
    +
    +    onTriggerStay(col: Collider) {
    +        if(this.triggerObjects && this.triggerObjects.length > 0 && !this.triggerObjects?.includes(col.gameObject)) return;
    +        this.onStay?.invoke();
    +    }
    +
    +    onTriggerExit(col: Collider) {
    +        if(this.triggerObjects && this.triggerObjects.length > 0 && !this.triggerObjects?.includes(col.gameObject)) return;
    +        this.onExit?.invoke();
    +    }
    +}
    +

    Auto Reset

    Reset an object's position automatically when it's leaving a physics trigger

    import { Behaviour, Collider, GameObject, Rigidbody, serializeable } from "@needle-tools/engine";
    +import { Vector3 } from "three";
    +
    +export class StartPosition extends Behaviour {
    +
    +    //@nonSerialized
    +    startPosition?: Vector3;
    +
    +    start() {
    +        this.updateStartPosition();
    +    }
    +
    +    updateStartPosition(){
    +        this.startPosition = this.gameObject.position.clone();
    +    }
    +
    +    resetToStart() {
    +        if (!this.startPosition) return;
    +        const rb = GameObject.getComponent(this.gameObject, Rigidbody);
    +        rb?.teleport(this.startPosition);
    +    }
    +}
    +
    +/** Reset to start position when object is exiting the collider */
    +export class AutoReset extends StartPosition {
    +
    +    @serializeable(Collider)
    +    worldCollider?: Collider;
    +
    +    start(){
    +        super.start();
    +        if(!this.worldCollider) console.warn("Missing collider to reset", this);
    +    }
    +    
    +    onTriggerExit(col) {
    +        if(col === this.worldCollider){
    +            this.resetToStart();
    +        }
    +    }
    +}
    +

    Play Audio On Collision

    import { AudioSource, Behaviour, serializeable } from "@needle-tools/engine";
    +
    +export class PlayAudioOnCollision extends Behaviour {
    +    @serializeable(AudioSource)
    +    audioSource?: AudioSource;
    +
    +    onCollisionEnter() {
    +        this.audioSource?.play();
    +    }
    +}
    +

    Set Random Color

    Randomize the color of an object on start. Note that the materials are cloned in the start method

    import { Behaviour, serializeable, Renderer } from "@needle-tools/engine";
    +import { Color } from "three";
    +
    +export class RandomColor extends Behaviour {
    +
    +    @serializeable()
    +    applyOnStart: boolean = true;
    +
    +    start() {
    +        if (this.applyOnStart)
    +            this.applyRandomColor();
    +
    +        // if materials are not cloned and we change the color they might also change on other objects
    +        const cloneMaterials = true;
    +        if (cloneMaterials) {
    +            const renderer = this.gameObject.getComponent(Renderer);
    +            if (!renderer) {
    +                return;
    +            }
    +            for (let i = 0; i < renderer.sharedMaterials.length; i++) {
    +                renderer.sharedMaterials[i] = renderer.sharedMaterials[i].clone();
    +            }
    +        }
    +    }
    +
    +    applyRandomColor() {
    +        const renderer = this.gameObject.getComponent(Renderer);
    +        if (!renderer) {
    +            console.warn("Can not change color: No renderer on " + this.name);
    +            return;
    +        }
    +        for (let i = 0; i < renderer.sharedMaterials.length; i++) {
    +            renderer.sharedMaterials[i].color = new Color(Math.random(), Math.random(), Math.random());
    +        }
    +    }
    +}
    +

    Spawn Objects Over Time

    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);
    +        }
    +    }
    +}
    +
    + + + diff --git a/scripting.html b/scripting.html new file mode 100644 index 000000000..2f2e7cfd7 --- /dev/null +++ b/scripting.html @@ -0,0 +1,191 @@ + + + + + + + + + Creating and using Components | Documentation + + + + +

    If you are new to scripting we recommend reading the following guides first:

    If you know what you're doing, feel free to jump right into the Needle Engine API documentationopen in new window.


    Runtime code for Needle Engine is written in TypeScriptopen in new window (recommended) or JavaScriptopen in new window. 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.


    When you don't need to write code

    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.

    image
    An example of a Button Click Event that is working out-of-the-box in Needle Engine โ€” no code needed.

    Creating a new component

    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.

    Create component with a custom function

    Refer to the Typescript Essentials Guide to learn more about the syntax and language.

    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.

    Component architecture

    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.

    Lifecycle methods

    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 nameDescription
    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
    earlyUpdate()First update event
    update()Default update event
    lateUpdate()Called after update
    onBeforeRender()Last update event before render call
    onAfterRender()Called after render event

    Physic event methods

    Method nameDescription
    onCollisionEnter(col : Collision)
    onCollisionStay(col : Collision)
    onCollisionExit(col : Collision)
    onTriggerEnter(col : Collision)
    onTriggerStay(col : Collision)
    onTriggerExit(col : Collision)

    Input event methods

    Method nameDescription
    onPointerEnter(args : PointerEventData)Called when a cursor starts to hover over an object (or any of it's children)
    onPointerMove(args : PointerEventData)Called when a cursor moves over an object (or any of it's children)
    onPointerExit(args : PointerEventData)Called when a cursor exists (stops hovering) an object
    onPointerDown(args : PointerEventData)Called when a cursor is pressed over an object
    onPointerUp(args : PointerEventData)Called when a cursor is released over an object
    onPointerClick(args : PointerEventData)Called when a cursor is clicked over an object

    XR event methods

    requires Needle Engine >= 3.32.0

    Method nameDescription
    supportsXR(mode: XRSessionMode)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
    onBeforeXR(mode: XRSessionMode, init: XRSessionInit)Called right before a XRSession is requested and can be used to modify the XRSessionInit object
    onEnterXR(args: NeedleXREventArgs)Callback when this component joins a xr session (or becomes active in a running XR session)
    onUpdateXR(args: NeedleXREventArgs)Callback when a xr session updates (while it is still active in XR session)
    onLeaveXR(args: NeedleXREventArgs)allback when this component exists a xr session (or when it becomes inactive in a running XR session)
    onControllerAdded(args: NeedleXRControllerEventArgs)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
    onControllerRemoved(args: NeedleXRControllerEventArgs)callback when a controller is removed while in a XR session OR when the component becomes inactive during a running XR session

    Additional XR events

    Method nameDescription
    window.addEventListener("needle-xrsession-start")CustomEvent that is invoked when a XRSession starts. details contains the NeedleXRSession
    window.addEventListener("needle-xrsession-end")CustomEvent that is invoked when a XRSession starts. details contains the NeedleXRSession
    onXRSessionStart(args: { session:NeedleXRSession } )global event hook. To unsubscribe use offXRSessionStart

    Coroutines

    Coroutines can be declared using the JavaScript Generator Syntaxopen in new window.
    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.

    Special Lifecycle hooks

    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 nameDescription
    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
    onUpdate(cb, options)Called directly after components update
    onBeforeRender(cb, options)called before calling render
    onAfterRender(cb, options)called before calling render

    For example (See example on stackblitzopen in new window)

    // this can be put into e.g. main.ts or a svelte component (similar to onMount)
    +import { onUpdate, onBeforeRender } 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);
    +

    Finding, adding and removing components

    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).

    Example:

    import { Behaviour, GameObject, Renderer } from "@needle-tools/engine";
    +
    +export class MyComponent extends Behaviour {
    +
    +    start() {
    +        const renderer = GameObject.getComponentInParent(this.gameObject, Renderer);
    +        console.log(renderer);
    +    }
    +}
    +

    Some of the available methods:

    Method
    GameObject.instantiate(Object3D, InstantiateOptions)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.getComponentInChildrensame as getComponent but also searches in child objects.
    GameObject.getComponentsInChildrensame as getComponents but also searches in child objects.
    GameObject.getComponentInParentsame as getComponent but also searches in parent objects.
    GameObject.getComponentsInParentsame as getComponents but also searches in parent objects.
    GameObject.findObjectOfTypesearches the whole scene for a type.
    GameObject.findObjectsOfTypesearches the whole scene for all matching types.

    Three.js and the HTML DOM

    The context refers to the runtime inside a web componentopen in new window.
    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.

    Access the scene

    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++) 
    +    const ch = 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 traverseopen in new window method:

    this.gameObject.traverse(obj => console.log(obj))
    +

    or to just traverse visible objects use traverseVisibleopen in new window 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:

    for(const renderer of this.gameObject.getComponentsInChildren(Renderer))
    +    console.log(renderer);
    +

    For more information about getting components see the next section.

    Time

    Use this.context.time to get access to time data:

    • this.context.time.time is the time since the application started running
    • this.context.time.deltaTime is the time that has passed since the last frame
    • this.context.time.frameCount is the number of frames that have passed since the application started
    • this.context.time.realtimeSinceStartup is the unscaled time since the application has started running

    It is also possible to use this.context.time.timeScale to deliberately slow down time for e.g. slow motion effects.

    Input

    Use this.context.input to poll input state:

    import { Behaviour } from "@needle-tools/engine";
    +export class MyScript extends Behaviour
    +{
    +    update(){
    +        if(this.context.input.getPointerDown(0)){
    +            console.log("POINTER DOWN")
    +        }
    +    }
    +}
    +

    You can also subscribe to events in the InputEvents enum like so:

    import { Behaviour, InputEvents } from "@needle-tools/engine";
    +
    +export class MyScript extends Behaviour
    +{
    +    onEnable(){
    +        this.context.input.addEventListener(InputEvents.PointerDown, this.onPointerDown);
    +    }
    +    onDisable() {
    +        // it is recommended to also unsubscribe from events when your component becomes inactive
    +        this.context.input.removeEventListener(InputEvents.PointerDown, this.onPointerDown);
    +    }
    +
    +    private onPointerDown = (evt) => { console.log(evt); }
    +}
    +

    If you want to handle inputs yourself you can also subscribe to all events the browser providesopen in new window (there are a ton). For example to subscribe to the browsers click event you can write:

    window.addEventListener("click", () => { console.log("MOUSE CLICK"); });
    +

    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).

    Physics

    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 rayopen in new window

    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();
    +

    Here is a editable example for physics raycastopen in new window

    Networking

    Networking methods can be accessed via this.context.connection. Please refer to the networking docs for further information.

    Accessing Needle Engine and components from anywhere

    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:

    <needle-engine loadstart="loadingStarted" progress="loadingProgress" loadfinished="loadingFinished"></needle-engine>
    +
    +<script type="text/javascript">
    +function loadingStarted() { console.log("START") }
    +function loadingProgress() { console.log("LOADING...") }
    +function loadingFinished() { console.log("FINISHED!") }
    +</script>
    +

    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:

    import { NeedleEngine } from "@needle-tools/engine";
    +NeedleEngine.addContextCreatedCallback((args) => {
    +  const context = args.context;
    +  const scene = context.scene;
    +  const myInstance = GameObject.getComponentInChildren(scene, YourComponentType);
    +});
    +

    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.ContextRegisteredCalled when the context is registered to the registry.
    ContextEvent.ContextCreationStartCalled before the first glb is loaded and can be used to initialize the physics engine. Can return a promise
    ContextEvent.ContextCreatedCalled when the context has been created before the first frame
    ContextEvent.ContextDestroyedCalled when the context has been destroyed
    ContextEvent.MissingCameraCalled when the context could not find a camera, currently only called during creation
    ContextEvent.ContextClearingCalled when the context is being cleared: all objects in the scene are being destroyed and internal state is reset
    ContextEvent.ContextClearedCalled after the context has been cleared

    Gizmos

    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.DrawLabelDraws 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.DrawRayTakes an origin and direction in worldspace to draw an infinite ray line
    Gizmos.DrawDirectionTakes a origin and direction to draw a direction in worldspace
    Gizmos.DrawLineTakes two vec3 worldspace points to draw a line
    Gizmos.DrawWireSphereDraws a wireframe sphere in worldspace
    Gizmos.DrawSphereDraws a solid sphere in worldspace
    Gizmos.DrawWireBoxDraws a wireframe box in worldspace
    Gizmos.DrawWireBox3Draws a wireframe box3
    Gizmos.DrawArrowDraws an arrow taking two points in worldspace

    Serialization / Components in glTF files

    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.

    Loading Scenes

    Referenced Prefabs, SceneAssets and AssetReferencesopen in new window 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.

    + + + diff --git a/showcase-bike.html b/showcase-bike.html new file mode 100644 index 000000000..3a35f856f --- /dev/null +++ b/showcase-bike.html @@ -0,0 +1,35 @@ + + + + + + + + + Bike Configurator ๐Ÿšฒ | Documentation + + + + +
    Last Updated:
    + + + diff --git a/showcase-castle.html b/showcase-castle.html new file mode 100644 index 000000000..69d751ec4 --- /dev/null +++ b/showcase-castle.html @@ -0,0 +1,43 @@ + + + + + + + + + Castle Builder ๐Ÿฐ | Documentation + + + + +

    Live

    Visit websiteopen in new window

    How To Play

    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.

    Info

    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.

    Last Updated:
    + + + diff --git a/showcase-mercedes-benz.html b/showcase-mercedes-benz.html new file mode 100644 index 000000000..1e2ec25a4 --- /dev/null +++ b/showcase-mercedes-benz.html @@ -0,0 +1,80 @@ + + + + + + + + + Mercedes-Benz Showcase | Documentation + + + + +

    About

    Hello, my name is Kryลกtof and i did a research project about Needle. At our companyopen in new window, 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.

    Context

    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 ๐Ÿ’š

    Lighting

    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:

    Skybox

    Which looks like this on the paint job:

    Paintjob

    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:

    Specular off

    But with the added directional lights it added a better dynamic. The effect could be deepened with higher intensity:

    Specular on

    Background

    The scene now looks like this:

    No background

    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.

    With background

    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:

    Evironemnt shader

    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! ๐ŸŒต

    Gradiant

    Car fake movement

    The scene right now is static since nothing moves. We can negate that by adding a fake feeling of motion. Let's start by adding motion to the wheels.

    With a simple component called Rotator, we define an axis and speed along it.

    Rotator

    import { Behaviour, serializable } from "@needle-tools/engine";
    +
    +export enum RotationAxis {
    +    X, Y, Z
    +}
    +
    +export class Rotator extends Behaviour {
    +    //@type RotationAxis
    +    @serializable()
    +    axis : RotationAxis = RotationAxis.X;
    +
    +    @serializable()
    +    speed : number = 1;
    +
    +    update() {
    +        const angle = this.speed * this.context.time.deltaTime;
    +        switch(this.axis) {
    +            case RotationAxis.X:
    +                this.gameObject.rotateX(angle);
    +                break;
    +            case RotationAxis.Y:
    +                this.gameObject.rotateY(angle);
    +                break;
    +            case RotationAxis.Z:
    +                this.gameObject.rotateZ(angle);
    +                break;
    +        }
    +    }
    +}
    +

    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:

    Motion

    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.

    Grid

    Extra elements

    This tech demo takes it's goal to showcase the car's capabilities.

    Let's start by highlighting the wheels.

    Wheel highlight

    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.

    Wheel shader

    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.

    Rear UI

    Wrap up

    By using the Unity's timeline we can control when the wheel dashes and text will be shown. This is complemented by the camera animation.

    Conclusion

    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! ๐ŸŒต

    Last Updated:
    + + + 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..2b5e8f448 --- /dev/null +++ b/showcase-monsterhands.html @@ -0,0 +1,35 @@ + + + + + + + + + Monster Hands ๐Ÿ’€ | Documentation + + + + +
    Last Updated:
    + + + diff --git a/showcase-towerdefence.html b/showcase-towerdefence.html new file mode 100644 index 000000000..200c223a5 --- /dev/null +++ b/showcase-towerdefence.html @@ -0,0 +1,35 @@ + + + + + + + + + Tower Defense | Documentation + + + + +
    Last Updated:
    + + + diff --git a/showcase-website.html b/showcase-website.html new file mode 100644 index 000000000..af62cdc15 --- /dev/null +++ b/showcase-website.html @@ -0,0 +1,35 @@ + + + + + + + + + Castle Builder ๐Ÿฐ | Documentation + + + + +
    Last Updated:
    + + + diff --git a/showcase-zenrepublic.html b/showcase-zenrepublic.html new file mode 100644 index 000000000..016b995eb --- /dev/null +++ b/showcase-zenrepublic.html @@ -0,0 +1,35 @@ + + + + + + + + + Monster Hands ๐Ÿ’€ | Documentation + + + + +
    Last Updated:
    + + + diff --git a/technical-overview.html b/technical-overview.html new file mode 100644 index 000000000..a0147d4b0 --- /dev/null +++ b/technical-overview.html @@ -0,0 +1,296 @@ + + + + + + + + + Technical Overview | Documentation + + + + +

    Technical Overview

    How it works

    Needle Engine roughly consists of three parts:

    • 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.

    glTF Assets

    Models, textures, animations, lights, cameras and more are stored as glTF 2.0 filesopen in new window in Needle Engine.
    Custom data is stored in vendor extensions. These cover everything from interactive components to physics, sequencing and lightmaps.

    Supported glTF extensions

    A typical production glTF created by Needle Engine uses the following extensions:

    KHR_lights_punctual
    +KHR_materials_unlit
    +KHR_texture_transform
    +KHR_animation_pointer
    +NEEDLE_techniques_webgl
    +NEEDLE_gameobject_data
    +NEEDLE_components
    +NEEDLE_persistent_assets
    +NEEDLE_lightmaps
    +NEEDLE_lighting_settings
    +KHR_texture_basisu
    +KHR_draco_mesh_compression
    +

    Other supported extensions:

    EXT_meshopt_compression
    +EXT_mesh_gpu_instancing (import and export)
    +

    Supported material extensions:

    KHR_materials_clearcoat
    +KHR_materials_ior
    +KHR_materials_specular
    +KHR_materials_transmission
    +KHR_materials_iridescence
    +KHR_materials_unlit
    +KHR_materials_volume
    +

    More extensions and custom extensions can be added using the export callbacks of UnityGLTF (not documented yet) and the glTF import extensionsopen in new window 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.

    Learn more about GLTF loading in three.jsopen in new window

    Compression

    For production, we compress glTF assets with glTF-transformopen in new window. 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.

    See the deployment & compression page for more information

    Vendor-specific glTF Extensions (NEEDLE_*)

    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.

    NEEDLE_components

    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_pointeropen in new window extension.

    "NEEDLE_components": {
    +  "builtin_components": [
    +    {
    +      "name": "WebARSessionRoot",
    +      "guid": "1516450550",
    +      "arScale": 50,
    +      "invertForward": true,
    +      "enabled": true,
    +      "gameObject": {
    +        "node": 0
    +      }
    +    },
    +    {
    +      "name": "SyncedRoom",
    +      "guid": "1516450552",
    +      "roomName": "network-room",
    +      "urlParameterName": "room",
    +      "joinRandomRoom": true,
    +      "requireRoomParameter": false,
    +      "autoRejoin": true,
    +      "enabled": true,
    +      "gameObject": {
    +        "node": 0
    +      }
    +    },
    +    {
    +      "name": "PlayableDirector",
    +      "guid": "2243275882009986562_1668529989451832962",
    +      "state": 0,
    +      "extrapolationMode": 1,
    +      "playableAsset": "extensions/NEEDLE_persistent_assets/4",
    +      "playableGraph": {},
    +      "playOnAwake": true,
    +      "timeUpdateMode": 0,
    +      "time": 0,
    +      "initialTime": 0,
    +      "duration": 135.383333333332,
    +      "enabled": true,
    +      "gameObject": {
    +        "node": 0
    +      }
    +    }
    +  ]
    +}
    +

    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.

    NEEDLE_gameobject_data

    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.jsopen in new window and Unityopen in new window treat them.

    "NEEDLE_gameobject_data": {
    +  "layers": 0,
    +  "tag": "Untagged",
    +  "hideFlags": 0,
    +  "static": false,
    +  "activeSelf": true,
    +  "guid": "1516450549"
    +}
    +

    Note: We may need to better explain why this is not another entry in NEEDLE_components.

    NEEDLE_lighting_settings

    This is a root extension defining ambient lighting properties per glTF file.

    "NEEDLE_lighting_settings": {
    +  "ambientMode": 0,
    +  "ambientLight": [
    +    0.212,
    +    0.227,
    +    0.259,
    +    1
    +  ],
    +  "ambientIntensity": 1,
    +  "defaultReflectionMode": 0
    +}
    +

    Note: This extension might have to be defined per-scene instead of per-file.

    NEEDLE_lightmaps

    This is a root extension defining a set of lightmaps for the glTF file.

    "NEEDLE_lightmaps": {
    +  "textures": [
    +    {
    +      "pointer": "textures/20",
    +      "type": 1,
    +      "index": 0
    +    }
    +  ]
    +}
    +

    Note: At the moment this extension also contains environment texture references. We're planning to change that in a future release.

    Texture TypeValue
    Lightmap0
    Environment Map1
    Reflection Map2

    How lightmaps are applied is defined in the MeshRenderer component inside the NEEDLE_components extension per node:

    "NEEDLE_components": {
    +  "builtin_components": [
    +    {
    +      "name": "MeshRenderer",
    +      ...
    +      "lightmapIndex": 0,
    +      "lightmapScaleOffset": {
    +        "x": 1.00579774,
    +        "y": 1.00579774,
    +        "z": -0.00392889744,
    +        "w": -0.00392889744
    +      },
    +      ...
    +    }
    +  ]
    +}
    +

    Note: We may change that in a future release and move lightmap-related data to a NEEDLE_lightmap extension entry per node.

    NEEDLE_persistent_assets

    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.

    {
    +  "name": "LampionController",
    +  "guid": "9100000_ecab75bc7ab51a747a4c5c14236a43cd",
    +  "parameters": [],
    +  "layers": [
    +    {
    +      "name": "Base Layer",
    +      "stateMachine": {
    +        "name": "Base Layer",
    +        "defaultState": 0,
    +        "states": [
    +          {
    +            "name": "LampionFlying",
    +            "hash": 677739540,
    +            "motion": {
    +              "name": "LampionFlying",
    +              "isLooping": false,
    +              "guid": "7400000_c296c4d76e956b34f8b5833ba90653c1",
    +              "clips": [
    +                {
    +                  "node": "nodes/4",
    +                  "clip": "animations/0"
    +                },
    +                {
    +                  "node": "nodes/9",
    +                  "clip": "animations/6"
    +                },
    +                {
    +                  "node": "nodes/14",
    +                  "clip": "animations/12"
    +                }
    +              ]
    +            },
    +            "transitions": [
    +              {
    +                "isExit": false,
    +                "exitTime": 1,
    +                "hasFixedDuration": true,
    +                "offset": 0,
    +                "duration": 0,
    +                "hasExitTime": true,
    +                "destinationState": 0,
    +                "conditions": []
    +              }
    +            ]
    +          }
    +        ],
    +        "entryTransitions": []
    +      }
    +    }
    +  ]
    +},
    +{
    +  "name": "TrongCom Website",
    +  "guid": "11400000_93a8f856fe26af8498d94efe4835af36",
    +  "tracks": [
    +    {
    +      "name": "Markers",
    +      "type": "MarkerTrack",
    +      "muted": false,
    +      "outputs": [
    +        null
    +      ],
    +      "clips": [],
    +      "markers": [],
    +      "trackOffset": null
    +    },
    +    {
    +      "name": "Animation Track",
    +      "type": "AnimationTrack",
    +      "muted": false,
    +      "outputs": [
    +        "5017454109690854928_1668529989451832962"
    +      ],
    +      "clips": [
    +        {
    +          "start": 0,
    +          "end": 0.9833333333333333,
    +          "duration": 0.9833333333333333,
    +          "timeScale": 1,
    +          "asset": {
    +            "clip": "animations/78",
    +            "loop": false,
    +            "duration": 8,
    +            "position": {
    +              "x": 0,
    +              "y": 0,
    +              "z": 0
    +            },
    +            "rotation": {
    +              "x": 0,
    +              "y": 0,
    +              "z": 0,
    +              "w": 1
    +            },
    +            "removeStartOffset": true
    +          },
    +          "clipIn": 0,
    +          "easeInDuration": 0,
    +          "easeOutDuration": 0.41666666666666663,
    +          "preExtrapolationMode": 1,
    +          "postExtrapolationMode": 1
    +        },
    +        ... 
    +      ]
    +    }
    +  ]
    +}
    +

    Note: We might include more type and versioning information in the future.

    NEEDLE_techniques_webgl

    This extension builds upon the archived KHR_techniques_webglopen in new window 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.

    "KHR_techniques_webgl": {
    +  "programs": [
    +    {
    +      "vertexShader": 1,
    +      "fragmentShader": 0,
    +      "id": 0
    +    }
    +  ],
    +  "shaders": [
    +    {
    +      "name": "Pass-FRAGMENT",
    +      "type": 35632,
    +      "uri": "<embedded WebGL fragment shader code ...>",
    +      "id": 1
    +    },
    +    {
    +      "name": "Pass-VERTEX",
    +      "type": 35633,
    +      "uri": "<embedded WebGL vertex shader code ...>",
    +      "id": 0
    +    }
    +  ],
    +  "techniques": [
    +    {
    +      "program": 0,
    +      "attributes": {},
    +      "uniforms": {
    +        "_TimeParameters": {
    +          "name": "_TimeParameters",
    +          "type": 35666,
    +          "semantic": null,
    +          "count": 1,
    +          "node": 0
    +        },
    +        "hlslcc_mtx4x4unity_MatrixVP": {
    +          "name": "hlslcc_mtx4x4unity_MatrixVP",
    +          "type": 35666,
    +          "semantic": "_VIEWPROJECTION",
    +          "count": 4,
    +          "node": 0
    +        }
    +      },
    +      "defines": []
    +    }
    +  ]
    +}     
    +

    Note: Currently, vertex and fragment shaders are always embedded as URI; we plan on moving that data into more reasonable bufferViews in the future.

    Note: There's some redundant properties in here that we plan on cleaning up.

    TypeScript and Data Mapping

    ๐Ÿ—๏ธ Under Construction

    Rendering with three.js

    ๐Ÿ—๏ธ Under Construction

    Why aren't you compiling to WebAssembly?

    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.

    + + + diff --git a/testimonials.html b/testimonials.html new file mode 100644 index 000000000..a53e46be1 --- /dev/null +++ b/testimonials.html @@ -0,0 +1,35 @@ + + + + + + + + + Testimonials | Documentation + + + + +

    Testimonials

    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
    We just gotta say WOW ๐Ÿคฉ โ€” Unity for Digital Twins
    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
    + + + diff --git a/testing.html b/testing.html new file mode 100644 index 000000000..efba6f7d9 --- /dev/null +++ b/testing.html @@ -0,0 +1,61 @@ + + + + + + + + + Testing on local devices | Documentation + + + + +

    Testing on local devices

    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.

    TIP

    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".

    Setting up a self-signed certificate for development

    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:

    TIP

    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-sslopen in new window 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.

    OSViewing 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-mkcertopen in new window npm package.

    OSViewing in the browserAutomatic reloads
    Windowsโœ“โœ“
    Linuxโœ“โœ“
    Androidโœ“โœ“
    macOSโœ“โœ“
    iOSโœ“โœ“

    Generating a self-signed development certificate

    • 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.

    Installing the certificate on your development devices

    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.

    Installing the certificate on Android

    1. Open the file. You'll be prompted to install the certificate.

    Installing the certificate on iOS / iPadOS / VisionOS

    1. Open the file.
    2. You'll be prompted to add the profile to your device. Confirm.
    3. Go to Settings
    4. There will be a new entry "Profile". Select it and allow the profile to be active for this device.
    5. 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.

    TIP

    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.

    Installing the certificate on another MacOS machine

    1. Open the file. Keychain Access will open and allow you to install the certificate.
    2. You may have to set "Trust" to "Always allow".

    Installing the certificate on another Windows machine

    1. Open certmgr ("Manage user certificates") by typing Windows key + certmgr.
    2. In the left sidebar, select "Trusted Root Certification Authorities".
    3. Right-click on "Certificates" and select "All Tasks > Import".
    4. Select the rootCA.pem file (you may have to change the file type to "all") and follow the instructions.
    + + + diff --git a/testing/switch-to-mkcert.webp b/testing/switch-to-mkcert.webp new file mode 100644 index 000000000..dee32c704 Binary files /dev/null and b/testing/switch-to-mkcert.webp differ diff --git a/vanilla-js.html b/vanilla-js.html new file mode 100644 index 000000000..8e24b7001 --- /dev/null +++ b/vanilla-js.html @@ -0,0 +1,91 @@ + + + + + + + + + Using Needle Engine directly from HTML | Documentation + + + + +

    Using Needle Engine directly from HTML

    While our default template uses viteopen in new window as powerful bundler, Needle Engine can also be used directly with vanilla Javascript โ€“ without using any bundler.

    A basic sample can be found hereopen in new window.
    Many examples can be found on StackBlitzopen in new window.

    How to run locally

    TIP

    Make sure to update the <needle-engine src="myScene.glb"> path to an existing glb file or download this sample glbopen in new window 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>
    +

    View on githubopen in new window

    + + + diff --git a/videos/component-time.mp4 b/videos/component-time.mp4 new file mode 100644 index 000000000..964f8dbb4 Binary files /dev/null and b/videos/component-time.mp4 differ diff --git a/vision.html b/vision.html new file mode 100644 index 000000000..85ff02b63 --- /dev/null +++ b/vision.html @@ -0,0 +1,47 @@ + + + + + + + + + Our Vision ๐Ÿ”ฎ | Documentation + + + + +

    Our Vision ๐Ÿ”ฎ

    The Future of the 3D Web

    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 PWAopen in new window. New VR and AR devices will extend into the webopen in new window, 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.

    Why another platform for 3D on the web? Aren't there enough options already?

    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.

    Creating a Workflow, not an Editor

    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.

    Open Standards instead of Proprietary Containers

    At the core of Needle Engine stands the glTFopen in new window 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.

    Read more about our use of glTF and extensions

    Goals and Non-Goals

    Goals

    • Iteration should be rapid and deployment should be fast.
    • Working on 3D web projects should be the as easy as working 2D web projects.
    • Developers and artists should be able to collaborate directly.
    • Responsive web extends beyond screens โ€“ AR and VR should be built in, not afterthoughts.
    • We want to contribute back to open-source projects.
    • Open discussion regarding 3D and web standards.
    • Ability to bring and take your data in open formats.
    • Ability to choose what web framework you use, not lock-in to particular frameworks and vendors.
    • Common usecases work without or with limited coding experience.

    Non-Goals

    • It's not a goal to have 100% coverage of all combinations of Editor versions, feature sets, render pipelines.
    • It's not a goal to provide a full no-code environment.
    • It's not a goal to match the feature set, capabilities, or runtime performance of other engines.

    Relation to other engines and frameworks

    Needle Engine and Unity WebGL

    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 and three.js

    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.

    + + + diff --git a/xr.html b/xr.html new file mode 100644 index 000000000..270a5d650 --- /dev/null +++ b/xr.html @@ -0,0 +1,50 @@ + + + + + + + + + VR & AR (WebXR) | Documentation + + + + +

    Supported Devices

    Theoretically all WebXR-capable devices and browsers should be supported. That being said, we've tested the following configurations:

    Tested VR DeviceBrowserNotes
    Apple Vision Proโœ”๏ธ Safari Browserhand tracking, support for transient pointer
    Meta Quest 1โœ”๏ธ Meta Browserhand tracking, support for sessiongranted1
    Meta Quest 2โœ”๏ธ Meta Browserhand tracking, support for sessiongranted1, passthrough (black and white)
    Meta Quest 3โœ”๏ธ Meta Browserhand tracking, support for sessiongranted1, passthrough, depth sensing, mesh tracking
    Meta Quest Proโœ”๏ธ Meta Browserhand tracking, support for sessiongranted1, passthrough
    Pico Neo 3โœ”๏ธ Pico Browserno hand tracking, inverted controller thumbsticks
    Pico Neo 4โœ”๏ธ Pico Browserpassthrough, hand tracking2
    Oculus Rift 1/2โœ”๏ธ Chrome
    Hololens 2โœ”๏ธ Edgehand tracking, support for AR and VR (in VR mode, background is rendered as well)
    Looking Glass Portraitโœ”๏ธ Chromerequires shim, see samples
    Tested AR DeviceBrowserNotes
    Android 10+โœ”๏ธ Chrome
    Android 10+โœ”๏ธ Firefox
    iOS 15+โœ”๏ธ WebXR Viewerdoes not fully implement standards, but supported
    iOS 15+(โœ”๏ธ)3 SafariNo 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โ„ข๏ธBrowserNotes
    Magic Leap 1please 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

    Examples

    Visit our Needle Engine XR Samplesopen in new window to try many interactive examples right now!

    Adding VR and AR capabilities to a scene

    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.

    Basic capabilities

    • 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.

    Multiplayer

    • Enable Networking
      Add a SyncedRoom component.

    • Enable Desktop Viewer Sync
      Add a SyncedCamera component.

    • Enable Voice Chat
      Add a VoIP component.

    Note: these components can be anywhere inside your GltfObject hierarchy. They can also all be on the same GameObject.

    Castle Builderopen in new window uses all of the above for a cross-platform multiplayer sandbox experience.
    โ€” #madebyneedle ๐Ÿ’š

    Special AR Components

    • 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.

    Controlling object display for XR

    • 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).

    Travelling between VR worlds

    Needle Engine supports the sessiongrantedopen in new window 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.

    Scripting

    Read more about scripting for XR at the scripting XR documentation

    Avatars

    While we don't currently provide an out-of-the-box integration external avatar systems, you can create application-specific avatars or custom systems.

    • Create a custom Avatar
      • Create an empty GameObject as avatar root
      • Add an object named Head and add a XRFlag that's set to Third Person
      • Add objects named HandLeft and HandRight
      • Add your graphics below these objects.

    Experimental Avatar Components

    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.

    20220817-230858-87dG-Unity_PLjQ
    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.

      image
      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!

    HTML Content Overlays in AR

    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 classopen in new window shouldn't be used at this point because using it breaks Mozilla's WebXR Viewer.

    .only-in-ar {
    +  display: none;
    +}
    +
    +.ar-session-active .only-in-ar {
    +  display:initial;
    +}
    +

    It's worth noting that the overlay element will be always displayed fullscreen while in XRopen in new window, independent of styling that has been applied. If you want to align items differently, you should make a container inside the class="ar" element.

    Image Tracking

    WebXR ImageTracking is still in "draft" phase: Marker Tracking Explaineropen in new window
    But you can still use WebXR ImageTracking with Needle Engine today:

    • Enable WebXR Incubations in chrome
    • Add the WebXRImageTracking component

    You can find additional documentation in the Everywhere Actions section

    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 and WebXR on iOS

    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.

    Musical Instrument โ€“ WebXR and QuickLook support

    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.

    Everywhere Actions and other options for iOS AR

    There's also other options for guiding iOS users to even more capable interactive AR experiences:

    1. 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 Builderopen in new window where creations (not the live session) can be viewed in AR.

    Encryption in Spaceopen in new window 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 ๐Ÿ’š

    1. 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 Vieweropen in new window to experience AR on iOS.

    2. 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:

    References

    WebXR Device APIopen in new window
    caniuse: WebXRopen in new window
    Apple's Preliminary USD Behavioursopen in new window

    + + +