Skip to content

Commit 50b3ef4

Browse files
committed
docs regarding asset loading
1 parent a5bfeec commit 50b3ef4

5 files changed

Lines changed: 258 additions & 3 deletions

File tree

documentation/how-to-guides/components/scene-switcher.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -489,8 +489,13 @@ Yes! Enable **Use Scene Lighting** and **Use Scene Background** to apply each sc
489489
**How do I prevent users from jumping scenes?**
490490
Set `useKeyboard = false` and `useSwipe = false`, then only provide your own UI for navigation.
491491

492-
**Can I load scenes from external URLs?**
493-
Yes! Use `addScene("https://example.com/scene.glb")` to add external scenes.
492+
**Can I load scenes from external URLs / a CDN / S3?**
493+
Yes! `addScene()` accepts any HTTPS URL — Needle Cloud, AWS S3, Cloudflare R2, your own CDN, or any static host. Make sure the host serves the file with permissive CORS headers. See [Load 3D Web Assets at Runtime](/docs/how-to-guides/scripting/load-3d-web-assets-at-runtime) for a full walkthrough including hosting and CORS setup.
494+
495+
```ts
496+
switcher.addScene("https://cloud.needle.tools/-/assets/Z23hmXBZ21QnG-latest-world/file");
497+
switcher.addScene("https://your-bucket.s3.amazonaws.com/products/chair.glb");
498+
```
494499

495500
**What's the difference between Loading Scene and regular scenes?**
496501
The loading scene appears while other scenes load. It's optional but improves UX for large scenes.

documentation/how-to-guides/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Head to [Getting Started](/docs/getting-started/) to install Needle Engine for [
4646
- [Handle User Input](/docs/how-to-guides/scripting/handle-input) - Mouse, touch, keyboard, VR controllers
4747
- [Perform Raycasting](/docs/how-to-guides/scripting/perform-raycasting) - Detect objects and hit points
4848
- [Use Physics](/docs/how-to-guides/scripting/use-physics) - Rigidbodies, forces, collisions, triggers — powered by [Rapier](https://rapier.rs/)
49+
- [Load 3D Web Assets at Runtime](/docs/how-to-guides/scripting/load-3d-web-assets-at-runtime) - Load GLBs from Needle Cloud, S3, or any HTTPS URL using `loadAsset`, `AssetReference`, `SceneSwitcher`, or `<needle-engine src>`
4950
- [Accessibility](/docs/how-to-guides/accessibility) - Make 3D scenes accessible to screen readers and assistive technology
5051
- [Detect Mobile Devices](/docs/how-to-guides/scripting/detect-mobile-devices) - Platform detection
5152
- [Image Tracking Scripting](/docs/how-to-guides/scripting/image-tracking) - Access tracked images and objects from code

documentation/how-to-guides/optimization/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ Removing unused physics colliders is one of the easiest wins. Double-check that
104104
**Reduce initial load time:**
105105
- [Enable progressive texture loading](/docs/how-to-guides/optimization/progressive-loading-and-lods) and [automatic mesh LODs](/docs/how-to-guides/optimization/progressive-loading-and-lods#automatic-mesh-lods-level-of-detail)
106106
- Use `SceneSwitcher` to split your project into a lightweight main scene that only contains the scene switcher and loads content scenes on demand — this keeps the initial download small
107-
- Use `AssetReference` for on-demand loading of individual assets
107+
- Use `AssetReference` for on-demand loading of individual assets — see [Load 3D Web Assets at Runtime](/docs/how-to-guides/scripting/load-3d-web-assets-at-runtime) for all four loading APIs, including loading from a CDN or external URL
108108

109109
**Improve runtime performance:**
110110
- Avoid physics colliders if you don't need physics (saves ~2 MB Rapier Wasm download)
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
---
2+
title: Load 3D Web Assets at Runtime
3+
description: Load GLB, glTF, and VRM files at runtime from any HTTPS URL — Needle Cloud, AWS S3, your own CDN, or any web host. Covers loadAsset, AssetReference, SceneSwitcher, and the needle-engine src attribute. Works with React, Vue, Svelte, vanilla JS, and plain HTML.
4+
---
5+
6+
# Load 3D Web Assets at Runtime
7+
8+
Needle Engine has four ways to load GLB / glTF / VRM files at runtime, and **all of them accept any HTTPS URL** — bundled paths, [Needle Cloud](/docs/cloud/) links, AWS S3, Cloudflare R2, your own CDN, or any web server.
9+
10+
This guide covers when to use each loading API, plus what you need in place to host assets externally (CORS, content types) so you can keep your build small and load assets on demand.
11+
12+
:::tip When to use this
13+
- Build is too large because you're shipping many GLB files
14+
- You want to update assets without rebuilding the website
15+
- Users only need a subset of available models per session
16+
- Assets are user-generated or change frequently
17+
- You're embedding Needle Engine into React/Vue/Svelte and want to load scenes dynamically
18+
:::
19+
20+
## Four Ways to Load Assets from a URL
21+
22+
Every loading API in Needle Engine accepts a full URL — there is no separate "remote" path. The four options below cover every common use case.
23+
24+
### 1. Set the root scene with `<needle-engine src="…">`
25+
26+
The simplest case — point the web component directly at a hosted GLB.
27+
28+
```html
29+
<needle-engine src="https://cloud.needle.tools/-/assets/Z23hmXBZ21QnG-latest-world/file"></needle-engine>
30+
```
31+
32+
This works with **any URL** — Needle Cloud, S3, your CDN, GitHub raw, anywhere. Use this when you have one root scene to display.
33+
34+
See [needle-engine attributes](/docs/reference/needle-engine-attributes) for the full list of HTML options (camera-controls, environment-image, etc.).
35+
36+
### 2. `SceneSwitcher.addScene(url)` — many scenes, on demand
37+
38+
Best for **galleries, configurators, and multi-scene experiences**. Keep a minimal root scene with just a `SceneSwitcher`, then add URLs at runtime.
39+
40+
```ts
41+
import { addComponent, SceneSwitcher } from "@needle-tools/engine";
42+
43+
const sceneSwitcher = addComponent(scene, SceneSwitcher, {
44+
autoLoadFirstScene: false,
45+
createMenuButtons: true,
46+
clamp: false,
47+
preloadNext: 1,
48+
preloadPrevious: 1,
49+
});
50+
51+
sceneSwitcher.addScene("https://cloud.needle.tools/-/assets/Z23hmXBZ21QnG-latest-world/file");
52+
sceneSwitcher.addScene("https://cloud.needle.tools/-/assets/Z23hmXBzvPW9-latest-product/file");
53+
sceneSwitcher.addScene("https://cloud.needle.tools/-/assets/Z23hmXBZvGGVp-latest-product/file");
54+
sceneSwitcher.addScene("https://your-bucket.s3.amazonaws.com/products/chair-red.glb");
55+
sceneSwitcher.addScene("https://your-bucket.s3.amazonaws.com/products/chair-blue.glb");
56+
57+
sceneSwitcher.select(0);
58+
```
59+
60+
Each loaded GLB is **fully interactive** — components, scripts, animations, and physics inside the loaded scene all activate. You can mix URLs from different hosts in the same list.
61+
62+
SceneSwitcher brings two extras that are especially valuable when loading from a CDN:
63+
64+
- **Preloading** — set `preloadNext` and `preloadPrevious` to download neighbouring scenes in the background while the user views the current one. Transitions then feel instant even over a slow connection. `preloadConcurrent` caps how many downloads run in parallel. You can also preload manually with `sceneSwitcher.preload(index)`.
65+
- **Browser history & deep links** — scene changes sync to a URL parameter (`?scene=chair-red`), so users can **bookmark a specific scene, share a direct link, and use the browser back/forward buttons** to navigate between scenes. Configure with `queryParameterName`, `useSceneName`, and `useHistory`.
66+
67+
See [SceneSwitcher guide](/docs/how-to-guides/components/scene-switcher) for full navigation, preloading, and lifecycle event details.
68+
69+
### 3. `AssetReference` — caching, instancing, prefabs
70+
71+
`AssetReference` is the right choice when you want to **load once and spawn many copies**, or when you want a stable handle to an asset across your code.
72+
73+
```ts
74+
import { AssetReference, Behaviour } from "@needle-tools/engine";
75+
76+
export class SpawnFromCDN extends Behaviour {
77+
async start() {
78+
// Create (or reuse) a reference to a URL.
79+
// getOrCreate caches by URL — multiple calls return the same reference.
80+
const ref = AssetReference.getOrCreate(
81+
this.context.domElement.baseURI,
82+
"https://your-cdn.com/models/tree.glb"
83+
);
84+
85+
// Spawn three independent copies
86+
await ref.instantiate({ parent: this.gameObject });
87+
await ref.instantiate({ parent: this.gameObject });
88+
await ref.instantiate({ parent: this.gameObject });
89+
90+
// Or load once and add the original (no copy):
91+
// const obj = await ref.loadAssetAsync();
92+
// this.gameObject.add(obj);
93+
}
94+
}
95+
```
96+
97+
`AssetReference` is also what gets serialized when you assign a Prefab or Scene asset in Unity/Blender — so the same API works whether the asset comes from your editor export or a runtime URL.
98+
99+
See [Reference and Load a Prefab](/docs/reference/scripting-examples#reference-and-load-a-prefab) and the [AssetReference API](https://engine.needle.tools/docs/api/AssetReference).
100+
101+
### 4. `loadAsset(url)` — one-line direct loading
102+
103+
The simplest option — load a file and add it to the scene in two lines:
104+
105+
```ts
106+
import { loadAsset } from "@needle-tools/engine";
107+
108+
const model = await loadAsset("https://your-cdn.com/models/room.glb");
109+
this.context.scene.add(model.scene);
110+
```
111+
112+
`loadAsset` returns a wrapper with `.scene`, `.animations`, etc. — works the same for GLB, FBX, OBJ, and USDZ inputs.
113+
114+
:::tip Which one should I use?
115+
- **One root scene**`<needle-engine src="…">`
116+
- **Switch between many scenes**`SceneSwitcher.addScene(url)`
117+
- **Reusable asset spawned multiple times, or editor-assigned prefab**`AssetReference`
118+
- **Quick one-off load**`loadAsset(url)`
119+
:::
120+
121+
## React, Vue, Svelte Integration
122+
123+
The same APIs work inside any framework. A typical pattern with React:
124+
125+
```tsx
126+
import { useEffect, useRef } from "react";
127+
import "@needle-tools/engine";
128+
129+
export function ProductViewer({ productUrl }: { productUrl: string }) {
130+
const ref = useRef<HTMLElement>(null);
131+
132+
useEffect(() => {
133+
if (ref.current) ref.current.setAttribute("src", productUrl);
134+
}, [productUrl]);
135+
136+
return <needle-engine ref={ref} camera-controls />;
137+
}
138+
```
139+
140+
For a SceneSwitcher-based dynamic loader, mount once and call `addScene()` / `select()` when the user picks a model — no remount needed.
141+
142+
## Hosting Options
143+
144+
### Needle Cloud — the perfect fit for runtime asset loading
145+
146+
[Needle Cloud](/docs/cloud/) is built for exactly this use case. Upload a GLB, get a URL, and you immediately get everything a production CDN setup would need:
147+
148+
- **Progressive loading** — small preview textures and low-poly meshes stream first, full quality streams on demand. Typically **~90% less bandwidth** than loading raw GLB from S3.
149+
- **Automatic compression** — KTX2 textures and Draco/meshopt meshes generated on upload, no toktx setup, no build pipeline.
150+
- **Global CDN** — assets served from edge locations close to your users.
151+
- **Correct CORS and `Content-Type` headers** out of the box — no S3 policy tuning.
152+
- **Versioning + labels** (see below) — update assets without redeploying your app.
153+
154+
Upload your GLB and copy the **Progressive-World** or **Progressive-Product** download link from the asset's Edit page — then drop that URL into `addScene()`, `loadAsset()`, `AssetReference.getOrCreate()`, or `<needle-engine src>`.
155+
156+
#### Update assets without redeploying your app — labels
157+
158+
This is the killer feature for the "load 20-30 models from the cloud" pattern: each cloud asset has **moveable labels** (`latest`, `main`) that act as stable URLs pointing at whichever version you choose.
159+
160+
| Label | URL pattern | What it does |
161+
| --- | --- | --- |
162+
| Pinned version | `cloud.needle.tools/-/assets/<id>-<version>/file` | Always serves that exact upload. Never changes. |
163+
| `latest` | `cloud.needle.tools/-/assets/<id>-latest-<name>/file` | Auto-updates to the most recent upload. |
164+
| `main` | `cloud.needle.tools/-/assets/<id>-main-<name>/file` | You manually promote a version to `main` — your production users see it. |
165+
166+
Workflow:
167+
168+
1. Hard-code the `main`-labeled URL in your app: `sceneSwitcher.addScene("https://cloud.needle.tools/-/assets/Z23h…-main-chair/file")`.
169+
2. Iterate on the GLB in Unity / Blender and upload a new version. It immediately becomes `latest` (preview / staging).
170+
3. When you're happy, click **⋮ → Set main label** on the new version in the Needle Cloud dashboard.
171+
4. All users now load the new asset — **no code change, no rebuild, no redeploy**. Roll back the same way by moving the `main` label back.
172+
173+
This is impossible with a plain S3 bucket without writing your own version-resolution layer. See [Needle Cloud: Version Control & Sharing](/docs/cloud/#version-control-sharing) for details.
174+
175+
:::tip
176+
Use pinned URLs in your tests and demos so they don't break when you ship a new version. Use the `main` label in your production app so you *can* ship updates without touching code.
177+
:::
178+
179+
### Amazon S3, Cloudflare R2, Google Cloud Storage
180+
181+
Any object store works as long as it serves the file over HTTPS with the right headers (see [CORS](#cors-content-type) below). You'll be responsible for compression, progressive LOD generation, and versioning yourself.
182+
183+
### Your own server, GitHub, or any static host
184+
185+
If the URL returns the GLB bytes with a permissive CORS policy, Needle Engine will load it.
186+
187+
## CORS & Content-Type
188+
189+
Because the browser fetches the GLB cross-origin, the host must allow your site's origin. For an S3 bucket the CORS rule looks like this:
190+
191+
```json
192+
[
193+
{
194+
"AllowedOrigins": ["https://your-site.com"],
195+
"AllowedMethods": ["GET", "HEAD"],
196+
"AllowedHeaders": ["*"],
197+
"ExposeHeaders": ["Content-Length"]
198+
}
199+
]
200+
```
201+
202+
Recommended response headers:
203+
- **`Content-Type: model/gltf-binary`** for `.glb` (or `application/octet-stream` as a fallback)
204+
- **`Content-Type: model/gltf+json`** for `.gltf`
205+
- **`Access-Control-Allow-Origin`** matching your site origin (or `*` for public assets)
206+
- **`Cache-Control: public, max-age=…`** for CDN caching
207+
208+
If a load fails, open the browser DevTools Network tab — most external-URL issues come down to a missing CORS header or a redirect that strips it.
209+
210+
## Live Example
211+
212+
Try it in your browser — the example below uses `SceneSwitcher.addScene()` with several Needle Cloud URLs. Swap them for your own to test:
213+
214+
[![Stackblitz: load scenes from URLs](/imgs/stackblitz-logo.png) Open on Stackblitz](https://stackblitz.com/edit/needle-engine-vite-template)
215+
216+
## Related
217+
218+
- [SceneSwitcher Component](/docs/how-to-guides/components/scene-switcher) — full guide to multi-scene loading
219+
- [Needle Cloud](/docs/cloud/) — managed asset hosting with progressive loading
220+
- [Progressive Loading & LODs](/docs/how-to-guides/optimization/progressive-loading-and-lods) — how big assets stream
221+
- [Reference Assets in Scripts](/docs/reference/scripting-examples#reference-and-load-a-prefab)`AssetReference` patterns
222+
- [needle-engine HTML attributes](/docs/reference/needle-engine-attributes)`src`, `camera-controls`, and more

documentation/reference/faq.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,33 @@ This can have many reasons, but a few common ones are:
652652

653653
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 Examples in the documentation](/docs/reference/scripting-examples#assetreference-and-addressables).
654654

655+
## Can I load GLB files from a CDN, S3 bucket, or external URL instead of bundling them?
656+
657+
**Yes.** Every loading API in Needle Engine accepts a full URL, so you can host GLBs on [Needle Cloud](/docs/cloud/), AWS S3, Cloudflare R2, your own CDN, or any static web host, and load them at runtime instead of bundling them into your build. This is useful when you have many models, update assets without rebuilding, or work with user-generated content.
658+
659+
You have four options depending on the use case:
660+
661+
1. **`<needle-engine src="https://…/scene.glb">`** — set the root scene to a hosted URL from HTML.
662+
2. **`SceneSwitcher.addScene(url)`** — best for galleries, configurators, or 20+ models. Add URLs at runtime; preload, swipe, keyboard nav all work the same as bundled scenes.
663+
3. **`AssetReference.getOrCreate(baseURI, url)`** + `instantiate()` — cache one asset by URL and spawn multiple independent copies. Same API as Unity/Blender prefab references.
664+
4. **`loadAsset(url)`** — quick one-off load returning a model wrapper (`.scene`, `.animations`, …).
665+
666+
Quick SceneSwitcher example loading from a mix of hosts:
667+
668+
```ts
669+
import { addComponent, SceneSwitcher } from "@needle-tools/engine";
670+
671+
const switcher = addComponent(scene, SceneSwitcher, { autoLoadFirstScene: false });
672+
switcher.addScene("https://cloud.needle.tools/-/assets/Z23hmXBZ21QnG-latest-world/file");
673+
switcher.addScene("https://your-bucket.s3.amazonaws.com/products/chair-red.glb");
674+
switcher.addScene("https://your-cdn.com/models/lamp.glb");
675+
switcher.select(0);
676+
```
677+
678+
The host must serve the file with correct **CORS** headers (`Access-Control-Allow-Origin`) and ideally `Content-Type: model/gltf-binary`. [Needle Cloud](/docs/cloud/) handles this automatically and adds progressive loading (~90% less bandwidth), automatic KTX2/Draco compression, a global CDN, and **moveable version labels** — point your app at a `main`-labeled URL once, then ship asset updates by promoting new versions in the dashboard, without rebuilding or redeploying your app.
679+
680+
See the full guide: [Load 3D Web Assets at Runtime](/docs/how-to-guides/scripting/load-3d-web-assets-at-runtime).
681+
655682
## How can I override compression or LOD settings for individual textures?
656683

657684
You don't need to change global settings when only a few textures need special treatment. Both Unity and Blender let you override compression format, max resolution, and progressive LOD generation per texture — while keeping the defaults for everything else.

0 commit comments

Comments
 (0)