--- url: /docs/guide.md description: >- Start here with Scenery: what it is, how to create your first experience, the core concepts, where it runs, and where to go next. --- # Guide The "what is this and how do I start" layer, before the deep [Editor](/editor/overview) and [Scripting](/scripting/) reference. ## Getting started * [What is Scenery?](/guide/what-is-scenery) – what the platform is and what you can build. * [What makes it different](/guide/what-makes-different) – how Scenery compares to other tools. * [Create your first experience](/guide/first-experience) – the shortest path to your first scene. * [Understanding the editor](/editor/overview) – a tour of where everything lives. ## Discover & manage * [Discover & your experiences](/guide/discover) – the Discover section and managing your experiences. * [Asset library & folders](/guide/asset-library) – browse and organize your assets. * [Settings & account](/guide/settings) – account and preferences. ## Reference & help * [Compatibility](/guide/compatibility) – which features run on which devices. * [Publishing & sharing](/guide/publishing) – getting an experience to users. * [Keyboard shortcuts](/guide/keyboard-shortcuts) – speed up your workflow. * [FAQ](/guide/faq) – common questions. --- --- url: /docs/guide/what-is-scenery.md description: >- Scenery is an Apple-native, no-code spatial creation suite for building and sharing high-quality AR experiences for mobile and Apple Vision Pro. --- # What Is Scenery? Scenery is an Apple-native **spatial creation suite** – a no-code tool for building and sharing high-quality AR experiences for **mobile** and **Apple Vision Pro**. You build visually, on Mac and iPad, and ship to every platform. Experiences are **multi-sensory** (spatial audio, custom haptics), can be **location-based** and globally accessible, and open **instantly, with no app download**, through App Clips. ## What you can build * **Contextual AR** embedded in real surroundings – tied to a place, an image, or the space around the user. * **Multi-scene, interactive** experiences through a no-code event system, with dynamic physics for realism. * **Rich media** – 3D models (USDZ), video (including spatial, transparent, and video textures), images, spatial and ambient audio, haptics, and custom shaders and post-processing. * **Native-app polish** out of the box: people and object occlusion, solid tracking, and built-in photo and video capture with your own branding. For developers, JavaScript scripting and custom-shader support take experiences further when you need it. Scenery is made by **Amped Labs**, an XR studio in Berlin, and grew out of their earlier location-based AR app, Scavengar. --- --- url: /docs/guide/what-makes-different.md description: >- What sets Scenery apart: native-app AR fidelity shared instantly through a link with no app install, plus a deep visual editor and scripting. --- # What Makes It Different Most AR tools force a trade-off: native apps look great but need a download, while WebAR is instant but rough. **Scenery gives you both** – native-app fidelity and performance, shared instantly with no app install. * **Quality without friction.** Best-in-class tracking, people and object occlusion, and high visual fidelity, delivered through App Clips so there's nothing to download. (Think "WebAR, but good.") * **Build once, deploy everywhere.** Author on Mac and iPad, then ship to mobile and Apple Vision Pro from the same project. * **Learnable in a day.** A human-centric interface that follows Apple's design guidelines – quick to pick up, masterable in weeks rather than months. * **Contextual and multi-sensory.** Content embedded in real surroundings, with spatial audio and custom haptics for genuine immersion – not AR as a gimmick. * **No-code to low-code.** A no-code event system for most work, with JavaScript scripting and custom shaders when you want to go deeper. --- --- url: /docs/guide/first-experience.md description: >- Create your first AR experience in Scenery: from the Create button through picking a template and tracking context to landing in the editor. --- # Create Your First Experience This walks through starting a brand-new experience, from the Create button to the editor. ::: tip Where to build The **Create experience** button currently appears on **Mac** and **iPad** – the platforms suited to building. **iPhone** and **Apple Vision Pro** are mainly for viewing experiences. ::: ## 1. Start from a template, or from scratch Click **Create experience**, and you're shown a **template** picker: * **Custom** – a blank experience built entirely from scratch. The best choice when you're learning, or when no template fits. * **Templates** – ready-made starting points that come with content and logic already set up, for common use cases: image tracking, face tracking, a showroom, location-based AR, a physics playground, a puzzle, architecture, spatial audio, a scavenger hunt, visual effects / spatial video, custom materials, a mixed-reality portal, and more. The template list grows over time, so check back for new ones. ![The Create Experience dialog – a category sidebar and a grid of templates (Image Tracking, Face-Tracking, Showroom, Location-based AR, and more)](./images/first-experience/template-picker.jpg) *The Create Experience template picker.* ## 2. Choose a tracking context Next, set the experience's **[tracking context](/editor/tracking-contexts)** – how it understands and anchors to the world (the space around the user, a real-world location, an image, or a face). ::: warning This choice is permanent You can only set the context **at the very beginning**, and it **can't be changed later**. Make sure it's right before you continue. Most experiences should use **World-Tracking**, the most flexible and compatible context. ::: As you select a context, a [compatibility](/guide/compatibility) indicator shows which platforms support it. ## 3. Create and enter the editor Click **Create**, and your new experience opens in the **editor**, where you place content, anchor it to the world, and make it interactive. Next: [Understanding the editor](/editor/overview). --- --- url: /docs/guide/discover.md description: >- The Experiences tab is your Scenery library: create, organize into folders, share, edit, duplicate, and start your experiences, plus Discover. --- # Experience Library The **Experiences** tab is your library – every experience you create lives here. Organize them into **folders**, just like the asset library. Each experience offers: * **Share** – share it with others. * **Edit** – open it in the editor. * **Start** – run (simulate) it on the device you're using. * **Duplicate** and **Delete**, from the **⋯** menu. Duplicating is a great way to keep a **backup** before reworking part of an experience. ## Discover When you open Scenery, you land on the **Discover** section – the home for browsing experiences and finding your way around. * **Featured experiences** – picks from the Scenery team, featured creators, and agency / enterprise clients. (Interested in being featured? Reach out to us.) * **Made by creators** – community work. When you set an experience to **Public**, it can appear here. * **Resources** – links to the Discord, tutorials, the documentation, and answers to questions creators ask often. ## Resources & roadmap * **Tutorials** – a series to help you get started building. * **Feature roadmap** – what's in the making, and where you can **request features** to help shape what comes next. --- --- url: /docs/guide/asset-library.md description: >- The Scenery asset library holds everything you can place in an experience – 3D models, images, video, audio, and more – organized in folders. --- # Asset Library & Folders The **asset library** holds everything you can place in an experience – 3D models, images, videos, audio, and more. Open it from the **Assets** tab, or from within the editor at any time. ## Asset types The library is organized by type: * **3D** – 3D models in **USDZ**, including ready-made assets, particle systems, and ShaderGraph materials. (Prepare them in Reality Composer Pro and export as USDZ.) * **Images** – **PNG** (with transparency) and **JPEG**. * **Videos** – standard video, plus **spatial (stereoscopic)** video (marked automatically when the system detects it) and **transparent** video. Transparent video lets you build polished, layered experiences without touching 3D. * **Shader libraries** – custom **Metal** shaders (see [Materials](/editor/materials) and [Post-FX](/editor/post-fx)). * **Audio** – sound files (MP3, WAV, M4A) for spatial, ambient, and effect audio. * **Custom haptics** – **AHAP** patterns for tactile feedback. * **Environment maps** – **HDR / 360°** images for custom lighting and backgrounds (see [Environments & lighting](/editor/environments-lighting)). ## Uploading Upload with the **Upload asset** button, or simply **drag and drop** files into the library – or straight into the editor. ## Folders Keep things organized with **folders**, available here and across the app. Create a folder, give it a **name**, a **colour**, and an **icon**, then **drag and drop** assets into it. Right-click a folder to delete it – the assets move back out, they aren't lost. ::: tip Find assets fast A library gets unwieldy across projects. Lean on folders to stay organized, and use the **search** bar to find an asset by name. ::: --- --- url: /docs/guide/settings.md description: >- Manage your Scenery account and preferences: profile, the two sign-in methods, opening experiences by link, feedback, and account actions. --- # Settings & Account Open **Settings** to manage your account and preferences. ## Account & profile * **Name** – tap your name to change how you appear to others. * **Profile image** – set or change your avatar. **Signing in.** Scenery offers two methods, and you can use either or both: **Sign in with Apple** (quickest, via your Apple Account) and **Email & password**. From Settings you can **Connect Apple Sign-In** (if you started with email), **Add Email & Password** (if you started with Apple), or **Reset Password**. **Why you need an account.** You can browse Scenery without signing in, but to **create, save, and own** experiences – and to **subscribe to a paid plan** – you connect a real account. That ties your work and your subscription to you, so they stay safe and follow you across devices. Scenery prompts you to connect one the first time you create or manage content. ## Tools * **Open experience via link** – paste an experience link to open it directly, handy if it won't open another way. * **Rate Scenery** – leave a star rating and a short note. * **Send feedback** – tell us what's working and what isn't. * **Delete cache** – clear cached data. * **Sign out** and **Delete account**. ## About The **About** screen shows your **Scenery version**, links to **privacy** and **terms**, **beta** info (if you're in a beta), social channels, and a link to **join our Discord** – the place to share work and get first-hand help from other creators. --- --- url: /docs/guide/compatibility.md description: >- Where Scenery features work: build once and deploy to iPhone, iPad, Mac, Apple Vision Pro, and the web, with per-feature compatibility notes. --- # Compatibility Scenery lets you **build once and deploy to many platforms** – but not every feature works everywhere. A small **compatibility** indicator appears throughout the editor next to features, contexts, and settings, telling you at a glance where each one is supported. ## Reading the indicator Tap a compatibility indicator to see per-platform support across **iPhone & iPad**, **Mac**, **Apple Vision Pro**, and the **web (WebXR)**. Each platform shows one of three states: * **Fully compatible** – works as intended. * **Partially compatible** – available with limits. Often this means **preview only**: on Mac, for instance, you can preview content in the simulator but not experience it at full scale, because a Mac has no depth sensing or full world tracking. * **Not compatible** – not available on that platform. ![A compatibility popover for macOS – Partially Compatible Feature, supported in simulator mode](./images/compatibility/compatibility-popover.jpg) *A compatibility popover: Partially Compatible on Mac, supported in simulator mode.* Because you choose a [tracking context](/editor/tracking-contexts) first, the compatibility shown depends partly on that choice – the context picker updates its indicator as you select. ## Tracking-context support How each context is supported, per platform: | Context | iPhone & iPad | Mac | Apple Vision Pro | Web (WebXR) | |---|---|---|---|---| | **World-Tracking** | Full | Preview (simulator) | Full | Full | | **Geo-Tracking** | Full | Preview (simulator) | Preview (diorama) | Coming soon | | **Image-Tracking** | Full | Preview (simulator) | Partial | Partial (needs an experimental Chrome flag) | | **Face-Tracking** | Full | Preview (simulator) | Not supported | Experimental | | **Playspace** | Full | Preview (simulator) | Full | Coming soon | | **Room Bound** | Coming soon | Coming soon | Coming soon | Coming soon | | **Body-Tracking** | Coming soon | Coming soon | Coming soon | Coming soon | For exactly what carries over to the browser, see [WebXR](/webxr/). ::: tip Authoring vs. viewing Experiences are **currently built** on **Mac and iPad** (where the Create button appears). **iPhone** and **Apple Vision Pro** are primarily for **viewing** experiences. ::: --- --- url: /docs/guide/publishing.md description: >- Publish and share Scenery experiences: every experience gets a shareable link, delivered via App Clip, the web, or a QR code by platform and plan. --- # Publishing & Sharing Every experience gets a **shareable link** – the gateway for others to open it. How it's delivered depends on the platform and your plan. ## Sharing on Apple devices On paid plans, the link is an **App Clip** – instant access with **no app install**. Open it from a **QR code or NFC tag** for the smoothest entry, since that skips the browser. A plain browser link instead shows an "open App Clip" prompt (Safari, non-private browsing only), which is why a QR code is recommended. Generate and manage the link in [Experience settings](/editor/experience-settings#sharing-delivery). There you also set **availability** (Public / Private / Link) and whether the experience can be **featured** in Discover. ## Sharing on the web (WebXR) Turn on **WebXR support** to also run the experience in a **browser** – full AR on Android, interactive 3D on desktop. See [WebXR](/webxr/) for what carries over and how to enable it. ## Where it runs Different devices and contexts support different things – see [Compatibility](/guide/compatibility) for the full picture, and use [per-device anchoring](/editor/anchors#per-device-anchoring-device-configuration) to give each kind of device the placement that suits it. --- --- url: /docs/guide/keyboard-shortcuts.md description: >- Keyboard shortcuts for the Scenery editor on Mac and iPad: app, editing, objects and selection, the UI builder, and video controls. --- # Keyboard Shortcuts The Scenery editor is built for **Mac** and **iPad with a hardware keyboard** – that's where these shortcuts apply. iPhone and Apple Vision Pro are mainly for viewing experiences. On Mac, the editor commands also live in the **menu bar**, so you can always rediscover them there. Modifier keys: ⌘ Command, ⇧ Shift, ⌥ Option, ⎋ Escape, ↩ Return, ⌫ Delete. ## App & windows | Shortcut | Action | |---|---| | ⌘N | New window | | ⌘? | Open the documentation | | ⌘F | Search – in Discover, the Asset Library, and your Experiences | | ⎋ | Close or cancel the current panel | | ↩ | Confirm the default button in a dialog | ## Editor – experience | Shortcut | Action | |---|---| | ⌘S | Save the experience | | ⌘W | End editing (close the editor) | | ⌘P | Preview the experience | | ⌘. | Stop the preview | | ⌘⇧P | Preview the selected object | | ⌥A | Run the selected action – press again to stop | | ⌘⌥R | Send to connected devices | ## Editor – objects | Shortcut | Action | |---|---| | ⌘C, ⌘X, ⌘V | Copy, cut, paste the selected object | | ⌘D | Duplicate the selected object | | ⌥ *(hold while dragging)* | Snap the gizmo to a grid | ## Editor – interface builder When an experience has a custom user interface, the UI panel adds: | Shortcut | Action | |---|---| | ↑, ↓ | Select the previous, next element | | ⌘D | Duplicate the selected element | | ⌘⌥C, ⌘⌥V | Copy, paste an element's style | | ↩ | Rename the selected element | | ⌫ | Delete the selected element | | ⌘Z, ⌘⇧Z | Undo, redo | ## Media – video trim When choosing a video's thumbnail or trim point: | Shortcut | Action | |---|---| | ←, → | Step one frame back, forward | | ↩ | Confirm | | ⎋ | Cancel | --- --- url: /docs/guide/faq.md description: >- Answers to common Scenery questions: supported file formats, sharing and links, branding, plans, and migrating from Adobe Aero. --- # FAQ Common questions about building and sharing with Scenery. For anything not covered here, reach us at or on [Discord](https://discord.com/invite/KJCK4d8F9y). ## Which devices and platforms are supported? You **build** on **Mac and iPad** (the editor is made for larger screens), and experiences **play** on **iPhone and iPad** (iOS 18+), **Apple Vision Pro**, and in the **browser** via WebXR. See [Compatibility](/guide/compatibility) for the full per-feature, per-device breakdown. ## Do viewers need to install an app? No. On paid plans, an experience is shared as an **App Clip** – it opens instantly from a **QR code, NFC tag, or link**, with no app download. See [Publishing & Sharing](/guide/publishing). ## Does Scenery work on the web or Android? Yes – through the **WebXR Viewer** (beta): full immersive AR on **Android Chrome**, and an interactive 3D preview on **desktop**. iOS keeps using App Clips for the best native experience. See [Scenery on the Web (WebXR)](/webxr/). ## What 3D and media formats can I use? * **3D models** – USDZ (Apple's AR-ready format: model, materials, and animations in one file). * **Video** – MP4 / MOV, including transparent and spatial video. * **Images** – PNG / JPEG, plus HDR / EXR for environments. * **Audio** – MP3 / WAV / M4A, spatial or ambient. Large media can be streamed (HLS) or pointed at your own URL instead of uploaded. See [Adding Content](/editor/adding-content). ## How do I share an experience? Turn on sharing in [Experience settings](/editor/experience-settings) to generate a link. On Apple devices that's an **App Clip** (QR, NFC, or link); on the web it's a **WebXR** link. Full details in [Publishing & Sharing](/guide/publishing). ## Can I add custom logic or code? Most interactivity is **no-code** – a visual [events and actions](/editor/triggers) system. When you need more, there's a **JavaScript [Scripting API](/scripting/)** (beta) with access to the scene, entities, materials, audio, networking, and more. ## Can I use my own branding? Yes – branding options (logos, colors, custom App Clip displays) are part of the **Agency** plan. Reach out at to discuss what you need. ## I'm coming from Adobe Aero – can I migrate? In most cases, yes – Scenery covers the bulk of what Aero did. Convert your 3D assets to **USDZ** (e.g. with Apple's Reality Converter) and your videos to **MP4**, then rebuild the experience in the editor. If you get stuck, email . ## What plans are there? Scenery has **Free**, **Pro**, and **Agency** tiers that progressively unlock more – Pro adds things like App Clip sharing, custom materials, portals, post-processing, and unlimited experiences; Agency adds commercial use and full branding. The current plans and exactly what each includes live in the app. ## How do I get help? Email , or join the community on **[Discord](https://discord.com/invite/KJCK4d8F9y)** – the fastest way to get answers and share what you're building. --- --- url: /docs/editor/overview.md description: >- A tour of the Scenery editor: the canvas, the hierarchy and selection, viewport tools, the bottom panel, and sending a preview to devices. --- # Editor Overview A quick tour of where things live in the editor. You arrive here after [creating an experience](/guide/first-experience) and choosing its tracking context. ## Around the canvas The numbers below match the numbered points on the screenshot. 1. **Hierarchy** (left) – the objects in the current scene. 2. **Add (+)** (top) – [add content](/editor/adding-content) to the scene. 3. **Undo / Redo** – step back and forth through your changes. 4. **Snapshot** – capture the scene as a preview image for the experience. 5. **Asset Library** – open the library at any time. 6. **Send to Devices** – push your edits to a device in **real time**. While you're actively editing, open the experience on another device (iPhone, iPad, Vision Pro) signed in with the **same Apple ID or connected email address**, and it automatically appears here as a connected device; changes you make are reflected live as you build. Especially powerful on visionOS. 7. **Preview** – run the experience in a **simulator**, or in **AR** on the device you're building on. Choose **full experience** or **selected object only** (very useful for iterating on one element of a complex object, like a single animation). 8. **Settings** – experience and app settings. 9. **Sidebar toggle** – show or hide the side panel. A few of these open something worth seeing up close: ## Moving around the viewport * **Orbit** by click-dragging; **move** with WASD or the arrow keys. With a **trackpad**, you can also navigate the scene with **two-finger swipes** and **pinch** gestures. * The **axis gizmo** (top-right) shows X/Y/Z; click a face to snap the view, or use keys **1–6**. * **⌘F** recenters the view on the selected object. * Switch **view presets** (front, left, top, …) and toggle **perspective / orthographic** camera. * A **background & lighting** button opens the [environment settings](/editor/environments-lighting). ![The viewport view-preset menu open – Perspective, Orthographic, and the Front/Back/Left/Right/Top/Bottom views](./images/overview/viewport-tools.jpg) *The view-preset menu – perspective / orthographic and the standard views.* ## The bottom panel A contextual panel along the bottom, with tabs: * **Events** (default) – object and scene [events](/editor/triggers). * **Materials** – your custom [materials](/editor/materials). * **Post-FX** – [post-processing effects](/editor/post-fx). * **Statistics** – coming soon. Drag the panel up for more room, or use the corner controls to minimize or enlarge it. ![The bottom panel maximized, showing the Events, Materials, Post-FX, Variables, and Statistics tabs](./images/overview/bottom-panel.jpg) *The bottom panel, maximized – Events, Materials, Post-FX, and more.* ## Hierarchy, selection & right-click The **hierarchy** (left) lists the scene's objects. Most elements – in the hierarchy *or* the viewport – respond to **right-click** with the same menu: * **Frame Selected** (or ⌘F) – centre the view on it. * **Duplicate** / **Delete**. * **Lock / Unlock** – stop an object being moved by accident (good for carefully composed containers). * **Show Events** – jump to its events. * **Wrap in Container** – put it inside a new container. * **Add Event** / **Quick Events** – set up a trigger without opening the Events panel (Quick Events are pre-built combos, e.g. remove-on-collision-and-show-next for scavenger hunts). * **Copy Development ID** – the object's unique ID, for referencing it in [scripts](/scripting/). ![The right-click menu on an object – Frame Selected, Duplicate, Delete, Lock, and Copy Development ID](./images/overview/right-click-menu.jpg) *The right-click menu on an object.* Other handy moves: * **Hide / show** – the eye icon (hover an item) temporarily removes it from the scene, like toggling a layer – useful for comparing versions. A hidden object shows an invisible marker in Scene Events. * **Multi-select** – ⌘-click several objects (in the viewport or hierarchy) to get one shared gizmo and transform them together. The same right-click also works in the **Events** panel (copy/duplicate an action or a whole chain). ## Scenes An experience can hold **multiple scenes**. Click the **arrow** next to the current scene for an overview; right-click a scene to **duplicate** it (an exact copy of all its content and events), and double-click / Enter to rename. Move between scenes at runtime with the [Transition to Scene](/editor/actions/experience) action. ![The hierarchy showing more than one scene](./images/overview/scenes-overview.jpg) *Multiple scenes in the hierarchy.* --- --- url: /docs/editor/concepts.md description: >- The core concepts in Scenery and how they nest: experiences, scenes, objects, containers, and the representations that hold their visual content. --- # Experiences, Scenes & Objects A few core ideas show up everywhere in Scenery. Understanding how they nest makes the rest of the editor click. ## Experience An **Experience** is the whole thing you build and share – one project, with its own settings, link, and [tracking context](/editor/tracking-contexts). An experience can hold **one or more Scenes**. ## Scene A **Scene** is a self-contained stage within an experience – its own content, events, lighting, and settings. Simple experiences have a single scene; larger ones use **several**, moving between them at runtime with the [Transition to Scene](/editor/actions/experience) action. Manage scenes from the hierarchy (see [Editor overview](/editor/overview#scenes)). ## Object An **Object** is something you place in a scene – the root node you select and move. Crucially, an object is what **carries the anchor** that decides where and how it attaches to the world (see [Anchors](/editor/anchors)). Inside each object sits its **visual content** – a 3D model, an image or video, a UI panel, and so on. Select the **object** (not the content nested inside it) to anchor and position it; the content moves with it. ## Container A **Container** is an object that **groups other objects**. Anchor and move a container, and everything inside it comes along – handy for arranging several objects as one unit, or anchoring a group together. ::: tip Object vs. its content Tracking comes from the **object's** position, so fine-tune placement by moving the **object**, not the model inside it. See [Anchors](/editor/anchors). ::: --- --- url: /docs/editor/tracking-contexts.md description: >- Tracking contexts decide how a Scenery experience anchors to the world: world tracking, geo-location, image tracking, and face tracking. --- # Tracking Contexts A **tracking context** decides how an experience understands and anchors to the world – whether it tracks the space around the user, a real-world location, a printed image, or a face. You choose it once, right at the start. ::: warning Choose it carefully – it's permanent The context is set when you create the experience and **can't be changed afterwards**. Pick the right one before you build. ::: Each context has its own [device compatibility](/guide/compatibility) – the picker shows which platforms support it as you select. ![The Set Experience Context picker – World-Tracking, Geo-Tracking, Room Bound (coming soon), Playspace, Image-Tracking, Face-Tracking, and Body-Tracking (coming soon), with the compatibility row at the bottom](./images/tracking-contexts/context-picker.jpg) *The Set Experience Context picker, with the compatibility row at the bottom.* ## World-Tracking (default) The most flexible and versatile context, and the right choice for most experiences. It uses full six-degrees-of-freedom tracking and the rear camera to understand the space around the user – floors, walls, and surfaces. When someone opens a world-tracked experience, they're prompted to **place the scene** wherever they are, so the scene's origin is set by the user. Choose World-Tracking unless you have a specific reason not to – you can build almost anything with it, and it's the most broadly compatible. ## Geo-Tracking Anchor a whole experience, or individual objects, to a specific **outdoor location** in the real world. Instead of placing the scene by hand, content appears pinned to its real coordinates when the user arrives. You start from a **3D representation of your chosen location** and place objects directly onto it, so you can see how they'll look on-site. Geo-Tracking uses a **Visual Positioning System (VPS)** – far more accurate than GPS, which on its own can drift enough to make content jump around. VPS localizes the user (and the virtual content) against real-world map data, so content can sit precisely on a building facade or in a plaza. * On **Apple Vision Pro**, a geo experience is shown as a **miniature diorama** – a 3D model of the location with all your content inside it, explorable from a bird's-eye view. Handy for previewing a location-based experience without being on-site. * **App Clip** distribution (instant, no install) uses **Apple's** location-based positioning. The Google-based option is too large to fit in an instant App Clip, so choose the Apple method if no-install delivery matters. See [Publishing & Sharing](/guide/publishing). Geo-Tracking currently localizes using map data from **Google and Apple**. **Custom VPS** – localizing against your own scanned environment – is being added later this summer, which will help indoor and private-location experiences (where public map data isn't available) and extend geo to the web. ![Geo-Tracking selected in the context picker; the Web platform shows as not yet compatible](./images/tracking-contexts/context-picker-geo.jpg) *Geo-Tracking selected; the compatibility row updates per context (Web shows not yet supported here).* ## Image-Tracking Build an experience that lives **on a single image** – a poster, a postcard, an augmented business card. The whole scene anchors to that image. Choose Image-Tracking when the experience genuinely revolves around one image and doesn't need to understand the floor or surfaces around the user. Because the system only has to find the image rather than map the whole space, tracking is **more stable and reliable**. ::: tip Image anchors vs. the Image-Tracking context You don't need this context just to put *some* content on an image. Within a World- or Geo-Tracking experience, you can give an **image anchor** to individual objects (e.g. augment two posters in a walk-around scene). Use the Image-Tracking *context* only when the entire experience is built around one image. See [Anchors](/editor/anchors#image). ::: ## Face-Tracking Anchor an experience to the user's **face**, using the front camera. Good for virtual masks, accessories, and effects that follow the face. Face-Tracking gives you the face as a tracking anchor – the interactive layer is up to you (for example, drive a mask with [blend-shape animation](/editor/actions/animations-appearance#update-blendshape-weights)). It doesn't include higher-level detection such as pupil or eye-movement triggers, so expect to build a little more of the interactivity yourself. ## Playspace Define a **fixed space** in which the experience takes place – a carryover from walk-around scavenger-hunt experiences. Like World-Tracking, the user still places it manually. In most cases we recommend **World-Tracking** instead, as it's more versatile and broadly compatible. ## Coming soon * **Room Bound** – scan a room and lay out content along its walls, ceiling, and floor, with semantic understanding of the space. * **Body-Tracking** – attach objects to hands, legs, and other body parts. ::: info Element tracking is separate Some tracking happens at the **object** level, not the experience level – for example, anchoring an object to a **hand** on Apple Vision Pro. That's set per object (see [Anchors](/editor/anchors)), not chosen here as the experience context, which is why hand tracking doesn't appear in this list. ::: Seven contexts exist; **Room Bound** and **Body-Tracking** are shown as Coming Soon. World-Tracking is the default selection in the picker. --- --- url: /docs/editor/adding-content.md description: >- Add content to a Scenery scene: insert 3D models, media, primitives, user interface, and portals, then anchor and place each object. --- # Adding Content to a Scene Add content from the **+** button. Each element you add becomes an **object** in the scene, holding its visual content inside it. You can give each object its own **[anchor](/editor/anchors)** (how it attaches to the world), and style it in [Asset Appearances](/editor/asset-appearances), [Asset Properties](/editor/asset-properties), and [Materials](/editor/materials). This page covers the **content types** themselves and their type-specific options (set in the object's **Appearance → Model** section). ![The + menu open – Add Container, Add 3D Model, Add Media, Add Primitive, Add Portal](./images/adding-content/insert-menu.jpg) *The + (Insert) menu.* The Insert menu offers Container, 3D Model, Media, Primitive, and Portal to everyone; User Interface appears once your account has the UI-authoring entitlement. ## Container An empty grouping you fill with other content, or use to **anchor several objects together** as one unit. ## Primitive A basic shape – a **horizontal or vertical plane**, a **box**, or a **sphere** – with dimensions, and a **corner radius** on planes and boxes. Good for prototyping and placeholders. ## 3D Model Your own **3D model**, or a ready-made **integrated asset**. * **Custom model** – pick a model from the asset library; the field shows a preview, file format, and size. * **Integrated asset** – assets that ship with the editor (checkpoints, coin, star, ruby…), with optional colour tints; checkpoints can be set to **start / finish / default**. Handy starting points, especially for scavenger hunts. ![The Model section with the asset kind chosen – here an Integrated asset, with its built-in asset list](./images/adding-content/model-kind-picker.jpg) *Choosing the content kind – here an Integrated Asset and its built-in list.* ::: tip Use USDZ for 3D Scenery runs natively on Apple platforms and the WebXR viewer, all using **USDZ**. Prepare models, ShaderGraph materials, and particle systems in Reality Composer Pro and export as USDZ – one format that runs everywhere. ::: ## Media An **image** or a **video**. * **Image** – PNG (with transparency) or JPEG. * **Video** comes in a few flavours: * **Standard video** (MP4 / MOV) – a flat rectangular screen. * **Spatial (stereoscopic) video** – a different image per eye for depth; the depth shows on **headsets** (it plays normally on mobile). * **Transparent video** – alpha-channel video (MOV, HEVC / H.265). Pairs beautifully with the **Billboard** [alignment constraint](/editor/asset-appearances#transform-alignment) for a character that always faces the user and reads as 3D. (These files can be large – compress with tools like Apple Compressor.) * **[HTTP Live Streaming (HLS)](https://developer.apple.com/documentation/HTTP-Live-Streaming)** – stream large or high-res video from a URL instead of uploading it (via manual input, below). * **Immersive video** – 180°, 360°, and wide field-of-view video, plus Apple Immersive Video. On **Apple Vision Pro** these wrap around the viewer – a 360° clip surrounds you completely, 180° fills your forward view, and Apple Immersive Video places you inside the scene. Scenery recognizes an immersive source automatically and plays it natively; deliver it as an HLS stream, like other large video. *(currently Apple Vision Pro only)* Whether an immersive video **surrounds you** or plays inside a **window** follows the Scene's immersion setting – a fully immersive Scene replaces your view with the video, while a windowed Scene keeps your surroundings and places the video alongside your other content. (Standard content can sit in front of a windowed immersive video, but not in front of a fully surrounding one.) Media-specific options (in the object's **Appearance → Model** section): **Shows Loading-Placeholder** (a placeholder while a large asset loads), **Autoplays** and **Loops** (video only), **Renders Double Sided** (also draw the back face), **Renders Immersively** (blends the media softly into the surrounding space – especially nice with spatial video), a **Corner Radius** (rounds the media's corners, when not rendering immersively), and **Volume** for a video's audio. ![A video media object – media type, the source clip, and the loading placeholder, autoplay, and loops toggles](./images/adding-content/media-video-options.jpg) *Media options on a video object.* ::: tip Load assets from your own URL Right-click an asset field → **manual input** to point at an asset **URL** on your own server. The asset is fetched at runtime (never uploaded to Scenery), sidestepping upload size limits and keeping confidential assets off our servers. The same works for **HLS** streams. (Note: file-sharing services like Drive/Dropbox block this by default; self-hosting works.) ::: ::: tip Keep experiences small Everything in an experience downloads on first open (then caches). Aim for a small total – roughly **30–50 MB** for App Clip-delivered mobile experiences. For big or high-res video, prefer an **HLS** stream or a manual asset URL over uploading. ::: ## User Interface Build an interactive **interface panel** – buttons, labels, images, and other controls laid out in 2D, placed in the scene or pinned as a [Screen Space](/editor/anchors#screen-space) overlay. Available with the **UI-authoring entitlement**, a select-access beta feature – to join the beta. ## Portal A **portal world** – a framed window into a self-contained little world that the user looks through, and can step into. You build the world *inside* the portal: add objects to it like any container, and give it its own environment. It's grouped under the object's **Portal World** section. Portal-specific options: * **Background** – a surrounding environment for the world, from an **image** or **video**, projected as **Equirectangular** or **Cubemap** (the two common 360° layouts). * **Lighting** – the portal world's own environment lighting, independent of the main scene. * **Object Crossing** – let the world's contents reach *out* through a chosen boundary – **Front, Back, Left, Right, Top**, or **Bottom** (or **Disabled**) – so an object can break the frame and poke into the room. * **Object Clipping** – clip the contents at a chosen plane (same options), to keep the world neatly bounded by the portal opening. ## Placing what you add New content appears at the **scene origin** (where the axes meet). Move it with the gizmo by dragging an axis handle – **X** (red), **Y** (green), or **Z** (blue) – or type exact values in the object's transform. ![A selected object with the move gizmo – the X (red), Y (green), and Z (blue) axis handles](./images/adding-content/gizmo-placement.jpg) *Moving an object with the gizmo's X / Y / Z handles.* --- --- url: /docs/editor/anchors.md description: >- Anchors decide where and how Scenery content attaches to the real world: planes, images, faces, camera, hands, and per-device overrides. --- # Anchors An **anchor** decides *where and how* your content attaches to the real world – pinned to a spot in front of the user, snapped to a detected table, following the camera, riding along on a hand, or appearing on top of a recognized image. Anchoring is set on the **object** you select – the root node in the hierarchy, which carries the anchor and shows its current **Anchor Type**. An object holds its visual content inside it: a 3D model, visual media, a UI panel, and so on. Select the **object** (not the content nested inside it) and the **Anchoring** panel appears. To anchor several objects together, group them into a **Container** and anchor that. ::: tip Name everything you add Give clear names to your objects, containers, and the models inside them. As scenes grow, good names are what keep everything findable and easy to reference. ::: ## Choosing an anchor The available anchor types depend on the experience's **tracking context** (chosen when you create the experience). Select a context below to see which anchors it offers: ![The Anchor Type menu in a world-tracking experience](./images/anchors/anchor-types.jpg) *The Anchor Type menu in a world-tracking experience.* (App Clip Code appears greyed-out – it isn't author-selectable. **Screen Space** appears only for **User Interface** elements – see [Screen Space](#screen-space).) ::: tip Placing objects well * **Anchor first, then nudge.** An anchor sets an object's **base position**. To fine-tune from there, don't fight the anchor – use the object's **Transform** (position, rotation, scale) to nudge it into place on top of its anchor. * **Nudge the object, not its content.** The object is the root that carries the anchor; its visual content – a 3D model, media, or UI – sits inside it. To fine-tune placement, move the **object** itself rather than the content within it, since tracking comes from the object's position. ::: ## Position The default. The object has a fixed position in the scene. Drag it with the gizmo, or type the X / Y / Z values directly. ## Scene Object Anchor an object to **another object in the scene**, so it tracks that object instead of having a position of its own. * **Target** – the object to follow. * **Lock translation / rotation / scale** – independently per axis (X, Y, Z). * **Offset** – a transform applied relative to the target. * **Update continuously** – when on, it keeps following the target as it moves; when off, it snaps once. * **Transition smoothing** – see [below](#transition-smoothing). ## Horizontal & Vertical Plane Place content on a real surface the device detects. Pick **Horizontal Plane** or **Vertical Plane**, and the options adapt. ![Horizontal Plane anchor with a surface type and minimum size](./images/anchors/plane-classification.jpg) *A Horizontal Plane anchor – surface type and minimum size.* ### Surface type Instead of "any surface," require a specific *kind* of surface. The choices depend on orientation: | Orientation | Surface types | |---|---| | Horizontal | Floor, Ceiling, Table, Seat | | Vertical | Wall, Window, Door | Surface types let content fit its setting – a character that only appears on a **Table**, signage that only appears on a **Wall**. Content with a surface type set won't appear until the device finds a matching surface. **Surface types need a capable device.** Recognizing *what* a surface is (table vs. floor vs. wall) requires an iPhone or iPad from **2018 or later** (A12 chip or newer). On older devices the surface type can't be determined. *(A depth sensor further improves placement accuracy but is not what enables surface types.)* ### Minimum size Require the surface to be at least a certain size (in metres) before content appears. For example, a scene meant to stand on a **tabletop** can require a minimum table size, so it only places on a surface actually big enough to hold it. On iPhone and iPad, **Window** and **Door** currently behave like **Wall**; the finer distinction applies on Apple Vision Pro. ## Camera vs. Current Point of View These look similar but behave differently: * **Camera** – content stays **attached to the camera and follows it continuously**. Good for things that should stay in view, like a heads-up element or a companion character. You can give it an offset. * **Current Point of View** – content is placed at a **one-time snapshot** of where the user is looking *at that moment*, then stays put. You can set an offset and reset its rotation. **Reset rotation** makes the content sit upright, facing the user, ignoring any head tilt at the moment it's placed – so the content isn't skewed by how the user happened to be looking when it appeared. ## Hand Anchor content to the user's hand – a character that follows it, a panel, an object you can move or throw. ![Hand anchor options – chirality, transition smoothing, discovery hint](./images/anchors/hand.jpg) *Hand anchor options.* ::: warning Platform support Hand anchoring is **currently** available on Apple Vision Pro only (shown as "Limited Compatibility" in the editor). Support may expand to other platforms in future. ::: * **Hand** – Left or Right. * **Joint** – one of: Forearm Arm, Wrist, Thumb Tip, Index Finger Tip, Middle Finger Tip, Ring Finger Tip, Little Finger Tip. * **Transition smoothing** – see [below](#transition-smoothing). * **Provides discovery hint** – when on, prompts the user to look at their hand as content appears. Off by default. ## Image Make content appear on top of a recognized image – a poster, a postcard, a sign, a QR code. You can combine an image anchor with other anchoring in the same experience: a walk-around scene that *also* augments specific posters when they're seen. ![Image anchor options – detection image, orientation, physical width](./images/anchors/image.jpg) *Image anchor options.* ::: warning Measure the physical width accurately The **physical width** tells the system how large the image is in the real world. An accurate value is **crucial** – it directly drives how accurately the image is tracked and how well its position is predicted. Even a small error noticeably degrades tracking. ::: * **Detection image** – the reference image, from the asset library. * **Orientation** – Horizontal (flat, e.g. on a table) or Vertical (on a wall, content faces outward). The marker preview updates to match. * **Physical width** – the real-world width of the image, in **centimetres** (see the warning above). * **Provides discovery hint** – prompts the user to look for the image. * **Tracks continuously** – when on, content keeps following the image as it moves; when off, position is set once on first detection. * **Hide if tracking lost** – when on, content disappears if the image goes out of view; when off, it stays where it was last seen. ::: tip Choosing a detection image Use an image with strong contrast and detail, printed flat and non-glossy – avoid shiny, reflective or low-detail images. QR codes work well (they can double as the way into the experience), as do detailed posters and signs. Always test how reliably your image tracks. ::: ## Per-device anchoring (Device Configuration) The same content often works best in different ways on different devices. Per-device anchoring exists for two main reasons: * **Better presentation per device.** A user-interface panel placed in 3D space can work beautifully on a headset, while on a phone the same panel may read better as a flat **Screen Space** overlay. It's case-dependent – give each device whichever placement suits it best. * **Working around device-limited anchors.** Some anchors are only available on certain devices – **Hand** anchoring is currently headset-only, for instance. Per-device anchoring lets you use it where it exists and fall back to something else (e.g. **Position**) where it doesn't. The **Device Configuration** picker lets a single object use a **different anchor per kind of device**: ![The Device Configuration picker – Default, Handheld, Spatial, Desktop, Fallback](./images/anchors/device-configuration.jpg) *The Device Configuration picker.* * **Default** – the anchor used everywhere, unless overridden below. * **Handheld** – phones and tablets. * **Spatial** – headsets (Apple Vision Pro and similar). * **Desktop** – Mac. For each device category you can choose a different anchor, or **Hidden** to leave the object out entirely on that kind of device. A common pattern: **Spatial → Hand**, **Handheld → Position** – so the same object is hand-attached on a headset and placed in front of the user on a phone. * **Fallback** – a fallback position used only when none of the above can be resolved on the running device (for example, the chosen anchor isn't supported there). It keeps content from disappearing by giving it a sensible default placement. ## Screen Space Available **only for User Interface elements** (a UI panel with no nested children). Instead of placing the panel in 3D space, Screen Space pins it as a **non-spatial overlay fixed to the screen** – a 2D HUD that stays in place as the camera moves. Use it for on-screen controls, titles, and other interface that shouldn't live in the world. * **Alignment** – where on screen the panel sits: centre, an edge, or a corner. * **Edge insets** – padding from the screen edges. * **Respects safe area** – keep clear of the notch / home indicator. Because it isn't a 3D anchor, the spatial controls (gizmo, orientation cube) are hidden while a screen-space UI is selected. ## Transition smoothing Several anchors (Scene Object, Hand) offer **transition smoothing**, shown as a **percentage (0–100%)**. At **0%**, content sticks rigidly to its target. As you raise it, content follows with a gentle lag – a more natural, "lazy" follow. Tune it to taste. --- --- url: /docs/editor/asset-appearances.md description: >- Control how an object looks in Scenery: transform and alignment, lighting and shadows, materials, reveal animations, and the hover effect. --- # Asset Appearances Select an object and open its **Appearance** tab to control how it looks and sits in the scene. (Behaviour like gestures and physics lives in [Asset Properties](/editor/asset-properties).) ## Transform & alignment * **Transform** – offset (position), rotation (in degrees), and scale. Scale has a **proportional lock** (closed = scale all axes together; open = scale each axis independently). Tip: drag inside a value field to adjust it smoothly. * **Vertical alignment** – how the object sits vertically: * **None** – stays exactly where you place it. * **Floor** – snaps to the floor, wherever it's placed. * **Eye level** – rises or lowers to the viewer's height. Good for accessibility – e.g. collectibles that stay reachable for both a tall adult and a small child. * **Alignment constraints**: * **None** – fixed orientation. * **Billboard** – always faces the user (great for characters, readable UI). * **Look At** – faces the user *or* another object you choose, with optional **transition smoothing** for a natural, lazy follow. ![Transform and alignment – offset, rotation, scale with the proportional lock, vertical alignment, and the alignment constraint](./images/asset-appearances/transform-alignment.jpg) *The Transform & Alignment section of the Appearance tab.* ## Model The object's **kind** – Primitive, Integrated asset, Custom model, Media, or Web view – and its core look. The kind and its type-specific options live in [Adding Content](/editor/adding-content); here you also set its **opacity** (0 transparent to 1 opaque). ## Render & adjustments * **Render model** – on by default; turn off to keep an object in the scene but invisible until shown by an action. * **Size adjustment (fitting box)** – very large models are auto-fit into a 1 m³ box (preserving proportions) so an accidentally huge import doesn't fill the room. Set to **None** to use the model's real size. * **Pivot adjustment** – where the object's origin sits: None, Top, Center, or Bottom. ## Hover effect On **Apple Vision Pro**, looking at an interactive object highlights it. Set this to **Automatically**, **Always**, or **Never** (turn it off if it clashes with your visual style). When on, choose a **Highlight**, **Spotlight**, or shader-based effect, with a custom colour, strength, and **transparency behaviour**: * **Full** – the effect always renders at full opacity, ignoring the object's transparency. * **Mask** – appears only where the object's opacity is above ~5% (visible parts only). * **Blend** – the effect's opacity follows the object's, fading in and out together. ![The hover effect options – effect type, colour, strength, and transparency behaviour](./images/asset-appearances/hover-effect.jpg) *The Apple Vision Pro Hover Effect options.* ## Lighting & shadows * **Cast dynamic light shadows** – the object casts shadows onto the real environment (iOS 18 / macOS 15 / visionOS; not web yet). * **Cast grounding shadows** – a shadow beneath the object, as if lit from above. * **Receive grounding shadows** – the object receives other objects' grounding shadows. * **Fade behaviour near physical objects** – how a grounding shadow behaves where it falls close to real-world objects: * **Default** – the system's standard handling. * **Constant** – the shadow stays at full strength even right up against nearby real-world objects. * **Fade** – the shadow softens and fades out as it approaches detected real-world objects, so it doesn't read as painted across a surface it shouldn't touch. ![The lighting and shadows controls – cast dynamic light shadows, cast and receive grounding shadows, and the fade behaviour menu](./images/asset-appearances/lighting-shadows.jpg) *The Lighting & Shadows section.* ## Reveal & removal animations How the object animates in and out. Default is **fade and scale**; you can also choose **fade** only, or disable it. Each has a **duration** and **scale factor** – e.g. a playful pop-in (fade + scale from small) or a quiet, slow fade so users barely notice it appear. ![The reveal and removal animation options – kind, duration, and scale factor](./images/asset-appearances/reveal-removal.jpg) *The Reveal and Removal Animation options.* ## Material overrides * **Face culling** – each surface of a model has an **outer** and an **inner** side, and only one is drawn by default (the other is left invisible, which saves work). This controls which: * **Back** (default) – draws the **outer** side; the normal choice for solid objects, whose insides you never see. * **Front** – draws the **inner** side instead, so the object looks hollow or inside-out – handy for seeing inside a shape. * **None** – draws **both** sides; use it for thin, flat, double-sided things like a plane or sheet that would otherwise vanish when seen from behind. ![The default-materials face-culling menu – Back, Front, None](./images/asset-appearances/face-culling.jpg) *Face Culling sits at the top of Material Overrides, above the custom-material reference.* * **Custom material** – reference a material from the [Materials](/editor/materials) section. Materials are created independently there, then referenced here per object. --- --- url: /docs/editor/asset-properties.md description: >- Set how an object behaves in Scenery: visibility, discovery guidance, audio and haptics, drag/rotate/resize gestures, and physics. --- # Asset Properties Select an object and open its **Properties** tab to set how it behaves: visibility, guidance, audio & haptics, gestures, and physics. (Visual styling lives in [Asset Appearances](/editor/asset-appearances); how it attaches to the world is [Anchors](/editor/anchors).) ## General * **Hide by default** – the object is added to the scene but starts hidden, to be revealed later by an action. Off by default (most content is meant to show on add). See [Toggle Object](/editor/actions/scene-content). * **Discovery guidance** – an on-screen arrow that points the user toward this object. **None**, **Initial** (shows until the user has seen the object, then stops), or **Permanent** (keeps pointing). Useful for guiding people through larger, location-based scenes, or directing attention to something that appears behind them. ![The Properties tab with the General section expanded – Hide By Default and Discovery Guidance, above the Audio, Gestures, and Physics sections](./images/asset-properties/general-hide-discovery.jpg) *The General section of the Properties tab.* ## Audio & haptics Attach a sound and a haptic to the object – best for **sound effects tied to the object** (for narration or background music, use the [audio actions](/editor/actions/audio-haptics) instead). * **Audio** – the sound file, with a mode: **Spatial** (3D, positioned at the object – use a **mono** source so it spatializes well), **Non-spatial** (flat, no positioning), or **Ambient** (directional but with no distance falloff). Plus **volume** and **loop**. See [Play Audio](/editor/actions/audio-haptics#play-audio) for how the modes differ. * **Haptics** – attach an **AHAP** haptic file (created in external tooling, ideally alongside the sound) so interactions also have a tactile response. ![The Audio and Haptics section – an attached audio file with Spatial mode, volume and loop, plus the haptics attachment slot](./images/asset-properties/audio-haptics-panel.jpg) *The Audio and Haptics section.* * **Emission behavior** (advanced) – fine control over how the object radiates sound: **overall volume**; the balance of **direct vs. reverb level** (less reverb sounds closer and more intimate, more sits it deeper in the room); **directivity** (the radiation pattern – equal in all directions by default, or focused more in one direction); and **distance attenuation** (how quickly the sound fades as the listener moves away, with a rolloff strength to set the rate). ![The advanced Emission Behavior controls – overall volume, direct and reverb level, directivity, and distance attenuation](./images/asset-properties/audio-emission-behavior.jpg) *The advanced Emission Behavior controls.* The emission controls map to standard spatial-audio properties. They're available on iOS 18 / macOS 15 / visionOS, not on the web. ## Gestures Let users manipulate the object directly. * **Swipe to offset** – swipe to nudge the object left/right (iOS and macOS). * **Drag**, **Rotate**, **Resize** – each can be limited to specific **axes** (X/Y/Z) and given **min/max constraints** – e.g. lock to one axis with a range to make a 3D slider, or cap resize so the object can't be made too large. * **Reset on release** – the object snaps back to its original position and size when the user lets go. Good for "inspect but keep things tidy" (e.g. a product on a pedestal). * **Transform target** (advanced) – the gesture can drive **another object** instead of the one being touched. ![The Gestures section – Swipe to Offset, Drag, Rotate and Resize toggles with per-axis (X/Y/Z) limits and min/max scale fields](./images/asset-properties/gestures-panel.jpg) *The Gestures section, with per-axis Drag, Rotate, and Resize limits.* ::: tip Gestures adapt to the device Drag, Rotate, and Resize work across iPhone, iPad, Mac, and Apple Vision Pro – the input adapts to each. On a touch screen you drag an object with one finger (two fingers moves it up and down), rotate it around the upright axis, and pinch to resize. On Apple Vision Pro the same gestures work directly in 3D space. The per-axis limits and min/max scale you set apply everywhere. ::: ## Physics Give the object a physics body so it can collide, fall, and be pushed. (Physics also needs scene-level setup – see [Physics actions](/editor/actions/physics).) * **Mode**: * **Static** – doesn't move; other objects collide with it (floors, walls, surfaces to land on). * **Dynamic** – moves and responds to gravity, impulses, and collisions. * **Kinematic** – moved by you (script/animation) and affects other bodies, but isn't pushed around by them. * **Collision shape** – Box, Sphere, or Convex (match it to the object's form). * **Material** – default, or custom **friction** (how much the surface resists sliding – static friction to start moving, dynamic friction while already moving) and **restitution** (bounciness: 0 lands with a thud, higher values bounce more). * **Mass** – default, or a custom mass per object (heavier objects are harder to push and fall the same but resist impulses more). * **Dynamic body options** – whether it **reacts to gravity**, plus **linear** and **angular damping** (how quickly its movement and spin slow on their own, like drag – raise them to settle sooner). * **Translation lock / Rotation lock** – constrain movement or rotation to specific axes. ![The Physics section – Mode set to Dynamic with the collision Shape menu open on Box, Sphere, Convex, plus Mass and Dynamic Body options](./images/asset-properties/physics-mode.jpg) *Physics – Mode and the Collision Shape menu.* ![Lower Physics controls – Translation and Rotation locks, Mass Properties, gravity and damping, and the Material's friction and restitution](./images/asset-properties/physics-dynamic-body.jpg) *Physics – Translation/Rotation locks, Mass, Dynamic Body options, and Material.* ::: tip Starting-point values Rough starting points – tune to taste by previewing: | Feel | Restitution (bounce) | Friction | |---|---|---| | Heavy, no bounce (clay, a metal thud) | 0 | 0.6 – 1.0 | | Wood or plastic | 0.2 – 0.4 | 0.4 – 0.6 | | Rubber ball | 0.6 – 0.8 | 0.8 – 1.0 | | Ice or slippery | 0 – 0.2 | 0 – 0.1 | **Damping:** keep **linear** and **angular damping** low (around 0 – 0.1) for free, lively motion; raise them (around 0.3 – 0.8) to make objects settle and stop quickly, as if moving through something thicker than air. ::: Modes, shapes, locks, friction, restitution, mass, damping, and starting-point values are explained from standard rigid-body physics. --- --- url: /docs/editor/materials.md description: >- Create custom materials in Scenery and apply them per object: unlit, video, occlusion, custom shaders, and MaterialX surfaces. --- # Materials Custom materials change how a surface looks. You create them in the **Materials** tab (next to Events), independently of any object, then **apply them per object**. Because they're standalone, one material can be reused across many objects. ## Creating and applying 1. In the **Materials** tab, choose **New material** and pick a kind (below). 2. On an object, open **Appearance → Material overrides**, then **Add reference** to apply a material (or **create a new one** from there). 3. You can apply **several** materials to one object, and remove a reference without deleting the material itself. **Material target** – when applied, you can target **First model**, **All models**, or **selected material slots**, so a material can affect only part of a model (e.g. just the rims of a car, or the glowing ring of a suit). ![Creating a new material – the kind picker with Unlit, Video, Occlusion, Custom Shaders, and MaterialX – Shader Graph (Preset and Physically Based are coming soon)](./images/materials/material-kind-picker.jpg) *Creating a new material – the kind picker.* ## Material kinds * **Unlit** – doesn't respond to scene lighting (no metalness/roughness). Set a **texture** or a **colour tint**, optional **tone mapping** (matches output to the scene), and **opacity** (with an optional opacity texture). * **Video** – a video texture. Pick the video asset, adjust its **audio**, and set **loop / autoplay**. Supports stereoscopic/spatial video and [HTTP Live Streaming (HLS)](https://developer.apple.com/documentation/HTTP-Live-Streaming) (via right-click → manual URL). With UV mapping it can cover a whole model or just part of it (e.g. a curved plane that wraps around the user). * **Occlusion** – invisible; **hides whatever is rendered behind it**. Great for walk-in portals, or for blocking out real buildings so virtual objects can collide with them (give the occluder physics). Options: receive shadows, read depth. *(On Mac it previews as black; black renders as transparent in AR.)* * **Custom Shaders** – your own **Metal**-based shaders. Load from the **asset library**, or use **live editing** (point at a shader file on your Mac and see edits instantly – a live shader preview). Options include the **surface shader function** (a dropdown when the file has several), a **geometry modifier function**, and a **lighting model** – **Lit** (realistic shading that responds to scene lighting), **Unlit** (ignores lighting, drawing at constant brightness from its colours and textures), or **Clearcoat** (Lit plus a glossy clear top layer, like car paint) – plus options to **inherit the original material's properties** and control **opacity**. * **MaterialX – Shader Graph** – a ShaderGraph material authored in **Reality Composer Pro** and imported. Also supports asset-library or live-editing (a USDZ on your Mac). ![An Unlit material – base colour texture and tint, tone mapping, opacity, and the shared rendering options](./images/materials/unlit-material.jpg) *An Unlit material.* ![A Video material with the clip playing in the viewport – video asset, autoplay, loops, and audio volume](./images/materials/video-material.jpg) *A Video material, playing in the viewport.* ![An Occlusion material – invisible, with receive-shadows and read-depth options](./images/materials/occlusion-material.jpg) *An Occlusion material.* ![A MaterialX – Shader Graph material referencing a ShaderGraph asset from the library](./images/materials/materialx-shader-graph.jpg) *A MaterialX – Shader Graph material.* ::: warning Platform support **Custom Shaders** run on iOS and macOS only – they're hidden for visionOS experiences. For cross-platform shaders (including Vision Pro), use **MaterialX – Shader Graph**. (**Physically Based** and **Preset** material kinds are shown as *Coming Soon*.) ::: A custom shader file can define a **surface shader** (sets a surface's colour and properties) and a **geometry modifier** (moves vertices). The editor's **surface shader function** and **geometry modifier function** pickers choose which function in the file to use. **Example: a custom shader (surface shader + geometry modifier).** This draws a UV gradient as the emissive colour and adds a subtle, time-based wiggle. ```cpp #include #include using namespace metal; // Surface shader: sets the surface colour and properties. [[stitchable]] void helloWorldSurfaceShader(realitykit::surface_parameters params) { float2 uv = params.geometry().uv0(); half3 color = half3(1.0 - uv.y, 0.2, uv.y); // gradient, top to bottom params.surface().set_emissive_color(color); } // Geometry modifier: moves vertices. Here, a subtle sine-wave wiggle over time. [[stitchable]] void helloWorldGeometryModifier(realitykit::geometry_parameters params) { float3 position = params.geometry().model_position(); float3 normal = params.geometry().normal(); float time = params.uniforms().time(); float amplitude = 0.02; // wiggle strength float frequency = 6.0; // wave density float speed = 4.0; // animation speed float displacement = amplitude * sin(position.x * frequency + time * speed); params.geometry().set_model_position_offset(position + normal * displacement); } ``` ## Shared rendering options Most material kinds share these: * **Render as wireframe** – draw only the mesh's edges. * **Read depth** – when on, the material does a depth test, so it's hidden behind closer depth-writing objects; when off, it always draws regardless of what's in front (draw-order groups still apply). * **Write depth** – the material writes to the depth buffer, so other objects test against it. * **Face culling** – each surface of a model has an **outer** and an **inner** side, and only one is drawn (the other is left invisible, which saves work). **Back** (default) draws the **outer** side – right for solid objects, whose insides you never see. **Front** draws the **inner** side instead, so the object looks hollow or inside-out. **None** draws **both** sides – use it for thin, flat things like a plane or sheet that should be visible from either side. The five usable kinds are **Unlit**, **Video**, **Occlusion**, **Custom Shaders**, and **MaterialX**. **PBR** and **Preset** are Coming Soon, and **Custom Shaders** isn't available on visionOS. **Lit** uses physically-based shading; **Unlit** applies no lighting; **Clearcoat** adds a clearcoat layer over PBR. --- --- url: /docs/editor/post-fx.md description: >- Add post-processing effects to a Scenery scene: filters and custom shaders that change the look of the whole rendered camera view. --- # Post-FX Post-processing effects change the look of the whole rendered scene (the camera view). Like materials, they live in their own **Post-FX** panel, independent of the scene, and are applied separately. ::: warning Platform support Post-FX currently works on **iOS and macOS** only – not on the web or Apple Vision Pro. ::: ## Creating an effect In the **Post-FX** panel, choose **New effect**, then **Filter** or **Shader**. (A **Preset** kind is shown as *Coming Soon*.) ![The Post-FX panel's empty state with New effect, choosing between Filter and Shader](./images/post-fx/new-effect-picker.jpg) *The Post-FX panel – New Effect, then Filter or Shader.* ### Filter A predefined effect chosen from a built-in list – bloom, blur, distortion, dot screen, photo effects, pixelate, thermal, and more. Most filters expose **parameters** (e.g. a dot screen's angle, width, sharpness). You can **stack several filters** within one effect (e.g. dot screen + bloom). ![A Dot Screen filter applied to the scene, with its angle, width, and sharpness parameters and the post-effect preview toggle](./images/post-fx/dot-screen-filter-applied.jpg) *A Dot Screen Filter applied, with its parameters and the preview toggle.* ### Shader A custom **Metal**-based effect. Load from the **asset library** or use **live editing** (a shader file on your Mac, updated live). A shader can contain multiple **kernel functions** (e.g. a rain effect and an underwater effect in one file). You can also bind **object transforms** (a scene object's transform is passed into the shader) and use the scene's **depth texture**. ![A shader effect – the asset library source, a kernel function selection, object transform bindings, and the depth-texture toggle](./images/post-fx/shader-kernel-and-bindings.jpg) *A Shader effect – kernel function, object transform bindings, and depth texture.* A post-processing shader is a compute **kernel** that reads the rendered frame (`inColor`) and writes a new one (`outColor`). One file can hold several kernels; the editor's **kernel function** picker chooses which to run. **Example: a post-processing kernel.** This minimal kernel blends the frame with a UV gradient – a good starting point for porting effects from [Shadertoy](https://www.shadertoy.com) or [The Book of Shaders](https://thebookofshaders.com). ```cpp #include using namespace metal; [[kernel]] void helloWorldPostProcessing( texture2d inColor [[texture(0)]], texture2d outColor [[texture(1)]], uint2 gid [[thread_position_in_grid]] ) { float2 resolution = float2(inColor.get_width(), inColor.get_height()); float2 uv = float2(gid) / resolution; uv.y = 1 - uv.y; // flip y to match the texture coordinate system // stay within the frame bounds if (gid.x >= inColor.get_width() || gid.y >= inColor.get_height()) { return; } half4 fragmentColor = inColor.read(gid); half4 gradient = half4(uv.x, uv.y, 0, 1); half4 mixedColor = mix(gradient, fragmentColor, 0.5); outColor.write(mixedColor, gid); } ``` ## Previewing and applying * **Enable post-effect preview** – toggle this in the panel to see effects while you tweak them, without committing them to the scene. * **Apply to the whole scene** – Scene settings → Post-processing effects → **custom effects**. * **Apply dynamically** – the [Set Post-Processing Effect](/editor/actions/experience) action turns an effect on/off on a trigger. Handy for changing the whole visual style on user input (e.g. a different era's look per object the user picks). Filter and Shader are the two author-selectable kinds; Preset (built-in) is Coming Soon; all post-FX is hidden for visionOS experiences. --- --- url: /docs/editor/triggers.md description: >- Events and triggers make a Scenery experience interactive: choose when something happens with tap, proximity, schedule, load, and more. --- # Events & Triggers Events are how an experience becomes interactive: a **trigger** decides *when* something happens, and one or more **actions** decide *what* happens. This page covers events and their triggers – for the actions themselves, see [Actions](/editor/actions). You'll find events in the **Events panel** at the bottom of the editor (drag it upward for more room as it grows). Events come in two kinds, split down the middle of the panel: * **Scene events** – apply to the whole scene. * **Object events** – belong to a specific object. ::: tip Keep your events tidy Deleting an object doesn't remove events that referenced it – they're left behind as "unlinked." After deleting content, glance through your events to keep things clean. ::: ![The Events panel at the bottom of the editor, split into Object events on the left and Scene events on the right](./images/triggers/events-panel.jpg) *The Events panel – Object Events and Scene Events.* ## Scene events Scene-wide. When you add your first content, an **Add to scene** event on **On Experience Start** is created for you, so that content appears when the experience starts. (Rename events to describe what they do – it keeps complex scenes readable.) ### Scene triggers * **On Experience Start** – fires once when the experience starts. The one you'll use most. * **On Screen Tap** – fires when the user taps anywhere on the screen (not tied to an object), with an optional screen-area filter. * **On Schedule** – fires on a timer. Either an **interval** (every N seconds, repeating a set number of times or indefinitely) or a specific **date & time** (with a choice of what to do if that time has already passed – skip it, or fire immediately). * **Custom** – fired programmatically; used with scripting. * **On Experience Load** – fires *earlier* than On Experience Start: while the scene is being prepared and assets load, before playback begins. Use it for setup that should happen before the user sees anything. * **On Render** – fires every frame, with an optional **frame throttle** (a minimum time between fires – higher values reduce processing at the cost of responsiveness). ![The scene trigger picker – On Experience Start, On Screen Tap, Custom, On Schedule, On Experience Load, On Render](./images/triggers/scene-trigger-picker.jpg) *The Scene trigger picker.* **On Experience Load vs. On Experience Start:** *Load* runs during preparation (while assets load), *Start* runs when the experience actually begins playing. **Schedule** options are exactly an interval (seconds + repeat count, indefinite supported) or a date & time (with skip-if-passed / fire-immediately fallback). ::: info Scripting triggers **On Experience Load**, **On Render**, and **Custom** are part of the scripting toolset. Scripting is a **beta feature** available to selected accounts – once it's enabled on your account these triggers appear automatically. See [Developer actions](/editor/actions/developer) for how to request access. ::: ## Object events Object events belong to a specific object and are grouped under it in the panel. Nothing is created automatically – it's entirely up to you. Add one with the **+** (or the **Add event** area), choose the **target** (an object can contain several models or media), then pick a trigger. ### Object triggers * **On Will Appear** – just before the object appears. * **On Did Appear** – right after it appears in the scene. * **On Removal** – when it's removed from the scene. * **On Tap Gesture** – when the user taps *this object* specifically (not the screen). * **On Camera Collision** – when the camera passes through the object. Great for walk-through and collection mechanics. * **On Object Collision** – when this object collides with another object you choose; fires on collision **began** or **ended**. Both objects need **physics** enabled – physics is what creates the collider that makes collisions detectable. * **Distance Field** – based on the distance (in metres) between the object and the camera, or another object you choose; fires on **enter** or **exit** of that distance. Useful for proximity effects like fading ambient audio in and out. * **Look Direction** – based on looking: by default, the user looking *at* the object (it can also be one object facing another). Fires on **look at** or **look away**, after a minimum duration. * **Video Playback** – appears only for **video** elements. Fires when playback **begins**, **ends**, or reaches a **timestamp** you pick (with a frame-accurate scrubber to choose the exact moment). Great for syncing content to a video's timeline – e.g. spawn an object at 0:05, or branch a story at a chosen point. * **UI Interaction** – appears only for **User Interface** elements. Fires when the user interacts with a UI control (tap, toggle, value or text change). ![The object trigger picker – On Will Appear, On Did Appear, On Removal, On Tap Gesture, On Camera Collision, On Object Collision, Distance Field, Look Direction](./images/triggers/object-trigger-picker.jpg) *The Object trigger picker.* ## Event options Every event offers: * **Name** – for clarity in busy scenes. * **Target** (object events) – which object / representation it applies to. * **Can repeat** – whether it can fire more than once, or only once. * **Is enabled** – turn the event on or off. Events can be enabled/disabled by *other* events and actions, which is how you build on/off cycles. * **SharePlay synchronization** – whether the trigger also fires for other people in a shared experience. Usually leave this on. ![An event's options – name, target object, and the Is enabled toggle](./images/triggers/event-options.jpg) *An event's options – Name, Object, and Is Enabled.* ::: tip Build back-and-forth interactions Because actions can toggle **Is enabled** on other events, you can make the same object alternate – e.g. tap to open (then disable "open" and enable "close"), tap again to close. ::: ## Triggers vs. actions A trigger decides *when*; **actions** decide *what happens*. Add actions from the **+** on the right of an event, or right-click the action canvas for a quicker browsable list. By default, actions run **sequentially** (one after another); drag one onto another to form a **group**, whose actions run **simultaneously**. See [Actions](/editor/actions) for the full set. --- --- url: /docs/editor/actions.md description: >- Actions are what happens when a Scenery event fires: add, order, and combine actions on an object or scene to build interactivity. --- # Actions Actions are *what happens* when an [event's trigger](/editor/triggers) fires. Each event holds one or more actions, shown on its action canvas. Add an action from the **+** on the right of an event, or **right-click the canvas** for a quicker, browsable list. Actions are grouped into the categories below. ## How actions run * **Sequential by default** – without grouping, actions run one after another (shown by a small arrow between them). * **Group** – drag one action onto another to form a group; actions in a group run **simultaneously**. * **Convert** – right-click a group to convert it into a **Sequence**, **Random**, **Repeat**, or **Cycle**. ::: tip Build complex behaviour by nesting Mix and match groups and sequences to choreograph an experience – e.g. a sequence that waits, plays a sound, then runs a group of animations together. ::: ## Categories * **[Scene Content](/editor/actions/scene-content)** – add, remove, and show/hide objects. * **[Action Ordering](/editor/actions/action-ordering)** – group, sequence, wait, random, repeat, cycle, conditional logic. * **[Animations & Appearance](/editor/actions/animations-appearance)** – animate objects, modify transform, blendshapes, particles. * **[Audio & Haptics](/editor/actions/audio-haptics)** – ambient bed, per-object audio, haptics. * **[Video](/editor/actions/video)** – play / pause and seek video. * **[Physics](/editor/actions/physics)** – impulses and physics resets. * **[Experience](/editor/actions/experience)** – scene transitions, post-FX, links, pause / end. * **[Dynamic Scene Adjustments](/editor/actions/dynamic-scene-adjustments)** – custom events, toggling events and gestures. * **[Developer](/editor/actions/developer)** – debug messages and scripts (appears when scripting is enabled). ::: info Variables Variable actions (Set / Modify Variable) aren't in this menu – variables are managed in the **Variables** panel. ::: --- --- url: /docs/editor/actions/scene-content.md description: >- Scenery actions that bring objects into a scene, take them out, or show and hide them at runtime. --- # Scene Content Actions that bring objects into the scene, take them out, or show and hide them. (For how to add actions and how they run, see [Actions](/editor/actions).) ## Add to Scene Adds an object to the scene when the trigger fires – use it to bring content in **dynamically** (e.g. on a tap) rather than at the start. By default, content you place is added automatically by an **Add to Scene** action on **On Experience Start**. If you'd rather introduce something later, delete that default action and add it from the event you want. **Options** * **Target Object** – the object to add. ## Remove from Scene Removes an object from the scene. When the action sits on the object itself it removes that object; you can also target a different one. **Options** * **Target Object** – the object to remove. ## Toggle Object Shows or hides a specific object. Each Toggle Object action sets a **fixed** state – shown or hidden – rather than flipping the object relative to whatever it currently is. The action's label reads **Show Object** or **Hide Object** to match. **Options** * **Target Object** – the object to show or hide. * **Show** – on shows the object, off hides it. To alternate an object **on each tap** (show, then hide, then show again), don't stack a Show and a Hide action on one trigger – both would run and the object would simply end on the last one. Instead, toggle the **Is enabled** state of two events that show and hide it (see [Events & triggers](/editor/triggers#event-options)). --- --- url: /docs/editor/actions/action-ordering.md description: >- Control how a Scenery event's actions run: sequence, group, delay, randomize, and branch with conditions to build complex interactivity. --- # Action Ordering By default, an event's actions run **sequentially** – one after another, shown by the arrow between them. These actions let you change that flow and structure complex interactivity. You can also build groups and sequences just by dragging actions on top of each other. (See [Actions](/editor/actions) for the basics.) ## Group Runs the actions inside it **simultaneously**. ## Sequence Runs the actions inside it **one after another**. ## Wait Pauses for a set time before the next action runs. **Options** * **Wait Duration** – the pause, in seconds. ::: tip Use Wait sparingly Timing an experience mostly with Wait actions is fragile – change one animation and the whole chain falls out of sync. Prefer to drive timing from the animations and events themselves, and use Wait only for fine-tuning (a beat of breathing room after a video, for instance). ::: ## Random Runs **one** randomly chosen action from the ones you place inside it – useful for surprise and variety in gamified experiences. It only picks from the actions you add, so the randomness stays controlled. ## Repeat Repeats the actions inside it a set number of times. **Options** * **Repeat Count** – how many times (1–9). ## Cycle Steps through several **states** each time it fires – each state is its own set of actions. Use it for multi-step toggles, e.g. cycling a light through off → dim → bright on repeated taps. **Options** * **Number of States** – 2 to 6. * **Mode** – **Loop** (wraps back to the first state after the last), **Ping Pong** (advances to the last state, then reverses), or **One Shot** (advances and stops at the last state). ## Conditional Logic Runs actions only if a condition is met, with an optional "else" branch for when it isn't. **Options** * **Condition** – currently **Platform Is** (run different actions depending on the device platform – e.g. one thing on a headset, another on a phone). The data model also supports value-based conditions (value equal / value less-or-equal), but the editor's condition picker currently offers only **Platform Is**. --- --- url: /docs/editor/actions/animations-appearance.md description: >- Scenery actions that move, animate, and restyle objects: transforms, animations, opacity, and blend-shape weights. --- # Animations & Appearance Actions that move, animate, and change how objects look. (For how actions are added and ordered, see [Actions](/editor/actions).) ## Animate Object Plays an animation on an object. Selecting it opens a dedicated view: a **ghost outline** shows the object's start state, and you drag the model (or type values) to define the end state. ![The Animate Object editor – animation type, a timing curve, and per-property controls, with the animating model in the viewport](../images/animations-appearance/animate-object.jpg) *The Animate Object editor – animation type, timing curve, and per-property controls.* **Animation type** – pick how the object animates: * **From / To** – animate between an explicit **start** and **end** state. You can animate the **transform** (position, rotation, scale), **opacity**, and **blendshape weights** (on a model that has them – set start weights and end weights, together or individually via *apply to all*). * **From / By** – animate by a **relative amount** from wherever the object currently is, rather than to a fixed end state. (Useful when the start isn't known in advance – e.g. "rotate by 90°" regardless of current rotation.) * **Model animation** – play an animation **baked into the 3D model**. A model carries a single baked timeline, so to use several animations, bake them into one and **trim** to a start/end timestamp (plus an optional delay) per action. * **Spin** – continuous rotation around an axis. * **Orbit** – orbit around an axis, with control over axes, number of revolutions, direction (clockwise/counter), and **orient to path** (face the direction of travel). * **Presets** – ready-made motions (pop, blink, bounce, flip, float, jiggle, pulse, spin), each with a **motion style**: basic, playful, or wild. ![The Presets animation type – a motion type with Basic, Playful, and Wild styles](../images/animations-appearance/animation-presets.jpg) *Preset motions, with Basic / Playful / Wild styles.* **Common options** (where applicable): * **Duration** – how long the animation runs, in seconds. * **Transition duration** – eases the change in speed. * **Delay** – wait before the animation starts. * **Repeat mode** – No repeat, a fixed **Count**, **Forever**, **Auto-reverse** (play then reverse), or **Cumulative** (each repeat continues from where the last ended rather than restarting). * **Additive** – blend this animation on top of other concurrent animations instead of overriding them. * **Timing curve** – Linear, Ease In, Ease Out, Ease In Out, or a custom **Cubic Bézier** (drag the control points) to make motion feel natural rather than mechanical. * **Reference space** – the frame the motion is interpreted in: the object's own space (default), **World** (the scene), or **Point of View** (relative to the camera). It matters because "move up" or "rotate" mean different things depending on the frame. ![The custom Cubic Bézier timing curve editor with draggable control points](../images/animations-appearance/timing-curve.jpg) *The custom Cubic Bézier timing curve.* ::: tip Preview a single action Use the **Run action** button to preview just this animation, without entering full preview mode – much faster to iterate. ::: ## Modify Transform Changes an object's **position, rotation, or scale** when the action fires, without a full animation. Choose a **mode** (To or By), a target object, optional **transition smoothing**, and a **reference space** (local, world, or point of view). Use **Modify Transform** for a direct, one-step change; reach for **Animate Object** when you want a timed animation with a duration, repeat, and timing curve. ![The Modify Transform action – mode, target, reference space, and start/end transform values](../images/animations-appearance/modify-transform.jpg) *The Modify Transform action.* ## Update Blendshape Weights Sets the **blendshape weights** (morph targets / shape keys) on a model that has them – the controls that morph a mesh, like facial expressions or a blooming flower. Pick the target, the **weight set** (if the model has more than one), and either set **all** weights together or adjust them individually (the *apply to all* toggle). This **sets** weights directly; to animate them over time, use **Animate Object** with the blendshape-weights property instead. ![Update Blendshape Weights – per-target weight sliders with an apply-to-all toggle](../images/animations-appearance/blendshape-weights.jpg) *Update Blendshape Weights – per-target sliders.* ## Play/Pause Animations Toggles an object's animations – on/off, resume or pause. It affects animations you set up in Scenery, and can also affect a model's **built-in (model-included) animations**. ::: tip Built-in animations autoplay by default A model's baked-in animations play automatically when it's added. To drive one with this action instead, first turn off **Autoplay model animations** in that model's **Appearance** properties. ::: ## Stop Animations Completely **stops and removes** an object's animations (use it when you don't intend to resume them), with an optional **transition duration** for how quickly they stop. ## Burst Particles Emits a one-off burst from a model's **particle system**. The model must actually contain particles – particle systems are created in Reality Composer Pro and imported as USDZ. (In the asset library you can filter for **contains particles**; several particle assets ship with paid plans.) **from-by** = a relative delta vs from-to's absolute end; **cumulative** repeat accumulates across loops; **reference space** = the object's own/local frame (default), World, or Point of View; the preset **motion types** and **styles** match the editor exactly. --- --- url: /docs/editor/actions/audio-haptics.md description: >- Scenery actions for sound and touch: play spatial or ambient audio, control volume and playback, and trigger haptic feedback. --- # Audio & Haptics Actions for sound and touch. (For how actions are added and ordered, see [Actions](/editor/actions).) ## Two kinds of audio: Ambient vs. Play Audio Scenery gives you two distinct tools for sound, each suited to a different job: * **Ambient audio** (Set / Toggle Ambient Audio) is the **scene-wide background bed** – music, or a narrator that guides the whole experience. It lives **independently of any object**. * **Play Audio** plays a sound **emitted from a specific object** (e.g. a plane's engine), tied to that object. Choose by what the sound *is*: background music or narration → **Ambient audio**; a sound that belongs to an object → **Play Audio**. ## Set Ambient Audio Sets the background audio bed for the scene. **Options** * **Audio** – the audio file to play. * **Play instantly** – start immediately, or wait to be started. * **Fade duration** – fade-in length. * **Volume** – overall level, handy for balancing against other sounds without leaving the editor. ![The Set Ambient Audio action – the audio file, Plays Instantly, Fade Duration, and Audio Volume](../images/audio-haptics/set-ambient-audio.jpg) *The Set Ambient Audio action.* ## Toggle Ambient Audio Turns the ambient bed on or off – set to **Automatically**, **Enable playback**, or **Disable playback**. ::: tip Duck the music near an object Pair this with a [Distance Field](/editor/triggers#object-triggers) trigger: disable the ambient bed when the user comes close to a talking object, and enable it again when they leave. ::: ## Play Audio Plays a sound emitted from an object. **Options** * **Audio** – upload a file, or pick an **Integrated Asset** (built-in presets for AR scavenger-hunt moments: checkpoint reached, object collected/found, modifier unlocked). * **Audio mode** – how the sound sits in space: * **Spatial** – fully 3D: the sound comes from the object's location, pans as the listener turns, and grows louder as they move closer. The usual choice for an object's sound. Use a **mono** source so it spatializes cleanly. * **Non-spatial** – flat: it plays the same wherever the listener stands or faces, like a 2D sound effect. (Still attached to the object, unlike the scene-wide Set Ambient Audio.) * **Ambient** – in between: it pans left and right with the listener's facing direction, but doesn't get louder or quieter with distance. * **Volume** – level. * **Loop** – repeat or play once. ![The Play Audio action with its audio mode menu – Spatial, Non-spatial, Ambient](../images/audio-haptics/play-audio-mode.jpg) *The Play Audio action and its Audio Mode menu.* Spatial = directional panning plus distance attenuation; Ambient = directional panning only; Non-spatial = no spatialization. ::: tip Object sounds can also live on the object If a sound is really a permanent sound effect for a model, you can attach it in the model's **Properties** (audio & haptics) instead of via an action, with the same mode and volume controls. ::: ## Fade Audio Fades an object's audio up or down to a target level. **Options** * **Target Object**, **Volume** (0–200), **Fade duration**. ## Seek Audio Jumps an object's audio to a specific **timestamp** (hours / minutes / seconds) – useful for jumping between sections of one longer file based on what the user does. ## Stop Audio Stops the audio on an object, with an optional **fade duration**. ## Emit Haptic Feedback Plays a haptic (a touch / vibration pattern) – a small detail that makes interactions feel more tangible. **Options** * **Custom asset** – upload an **AHAP** haptic file (created in external tooling; also covered under a model's audio & haptics properties). * **Integrated asset** – built-in patterns: impact **Light, Medium, Heavy, Soft, Rigid**, and a selection of others. ![The Emit Haptic Feedback action – an integrated asset with a preset pattern](../images/audio-haptics/emit-haptic-feedback.jpg) *The Emit Haptic Feedback action.* Set Ambient Audio is scene-wide and object-independent; Play Audio is object-emitted with a spatial / non-spatial / ambient input mode. --- --- url: /docs/editor/actions/video.md description: >- Scenery actions for video playback on an object: toggle play and pause, and seek to a timestamp at runtime. --- # Video Actions for controlling video playback on an object. (For how actions are added, see [Actions](/editor/actions).) ## Toggle Video Playback Plays or pauses a video on an object. **Options** * **Target Object** – the object whose video to control. * **Mode** – **Enable playback** (play), **Disable playback** (pause), or **Automatically** (toggle to the opposite of the current state). ## Seek Video Jumps a video to a specific point – useful for branching or non-linear video, where a user's choice jumps to a different part of one file. **Options** * **Target Object**. * **Timestamp** – where to jump to. --- --- url: /docs/editor/actions/physics.md description: >- Scenery actions that drive physics: apply impulses and angular impulses to push and spin objects, and reset physics transforms. --- # Physics Actions that push, spin, and reset objects under physics simulation. (For how actions are added, see [Actions](/editor/actions).) ::: tip Set up physics first Physics needs a few things in place before these actions do anything: * In **Scene settings → Meshing**, enable the **floor mesh** (so objects don't fall through the floor), and **world meshing** if objects should interact with the real environment (LiDAR devices only). * In **Scene settings → Physics**, enable **scene mesh collisions** (so objects react to that mesh), and adjust **gravity** if needed. * On each object, enable **physics** in its **Properties**, and pick the right **collision shape** (sphere, box, or convex). ::: ## Apply Impulse Pushes an object with a one-off force. **Options** * **Target Object**. * **Impulse position** – a fixed **point**, or the **user interaction** point. * **Reference space** – the coordinate frame for the impulse: **Local** (the object's own orientation), **World** (absolute scene axes), or **Point of View** (relative to the camera). * **Impulse** – the force along each axis (X, Y, Z), in newton-seconds. ![The Apply Impulse action – target, impulse position, reference space, and per-axis impulse](../images/physics/apply-impulse.jpg) *The Apply Impulse action.* ## Apply Angular Impulse Like Apply Impulse, but **rotational** – makes the object spin. Same target, reference space, and per-axis impulse. ![The Apply Angular Impulse action – the rotational equivalent, with reference space and per-axis impulse](../images/physics/apply-angular-impulse.jpg) *The Apply Angular Impulse action.* ## Reset Element Physics Resets one object's physics state (position and motion). ## Reset Scene Physics Resets the physics state of the whole scene – handy for switching between physics "states" (e.g. normal gravity vs. moon gravity) on demand. The body physics mechanics these actions drive are explained under [Asset Properties → Physics](/editor/asset-properties#physics). --- --- url: /docs/editor/actions/experience.md description: >- Scenery actions that affect the whole experience: change scenes, set and modify variables, control playback, and open links. --- # Experience Actions that affect the whole experience, not a single object. (For how actions are added, see [Actions](/editor/actions).) ## Transition to Scene Moves to another **scene** in the experience. Each scene keeps the experience's anchoring but is its own canvas, with its own content and events (see [Scenes](/editor/overview#scenes)). **Options** * **Scene** – which scene to transition to. ## Set Post-Processing Effect Applies (or clears) a **post-processing effect** on the camera view – both shader effects and filters. Default is none. Create effects in the **Post-FX** section first, then select one here. Because it's dynamic, you can change the whole visual style on user input – e.g. switch to a black-and-white filter when the user picks a "1920s" object. ## Set Surroundings Effect Dims or tints the user's real surroundings. ::: warning Platform support Set Surroundings Effect works on **Apple Vision Pro** only. ::: **Options** * **Effect** – not applied, semi-dark, dark, ultra-dark, a **custom dimming** value, or a **custom colour tint**. Great for focus and mood – darken the room when the user picks up an object, or tint the space to match a video that's playing. ## Open Link Opens an external link in the browser. **Options** * **Link name** and **URL** (https). * **Open in system browser**. * A confirmation prompt that asks the user before leaving the experience (can be turned off). ## Pause Experience Opens the pause menu. No options. Useful with custom UIs when you want full control over when the experience pauses. ## End Experience Ends the experience (the user can't resume where they left off). On Agency / Enterprise plans you can also add an **outro text** and a **call-to-action button** (title + URL), which Scenery turns into a simple end screen – a way to send people somewhere after the experience. --- --- url: /docs/editor/actions/dynamic-scene-adjustments.md description: >- Scenery actions for reusable interactivity: trigger custom events and toggle an object's drag, rotate, and resize gestures. --- # Dynamic Scene Adjustments Actions for firing reusable events and toggling interactivity. (For how actions are added, see [Actions](/editor/actions).) ## Trigger Custom Event Fires a **custom event** – a reusable bundle of actions you define once (as a scene event with a **Custom** trigger) and trigger from many places. Build complex logic (including scripts) once, then re-run it from any number of triggers instead of rebuilding it each time. **Options** * **Event** – the custom event to fire. (Define it first under scene events; see [Events & triggers](/editor/triggers).) ## Toggle Events Enables or disables other events on the fly – the mechanism behind on/off interaction cycles (tap to show, then disable that event and enable its opposite). See [Event options](/editor/triggers#event-options). **Options** * **Event(s)** to toggle, and whether to **enable** or **disable**. ## Toggle Object Gestures Enables or disables an object's **drag / rotate / resize** gestures (set up in the object's [Properties → Gestures](/editor/asset-properties#gestures)). Reversible – you can turn them back on later. ## End Object Gestures Cancels an object's gestures, **including one already in progress**. Unlike Toggle Object Gestures, this is one-way – it can't re-enable them. ## Cancel Scheduled Actions Cancels all actions that are currently waiting or scheduled – a way to tidy up or stop a chain of interactivity when the story moves on. --- --- url: /docs/editor/actions/developer.md description: >- Developer actions in Scenery: run scripts and log to the console for debugging and custom logic. --- # Developer Actions for debugging and scripting. ::: info Scripting is in beta Scripting is currently a **beta feature**, available to selected accounts. Once it's enabled on your account, the Developer actions – and the scripting triggers ([On Experience Load, On Render, Custom](/editor/triggers#scene-triggers)) – appear in the editor automatically. To request access, to join the beta. ::: ## Show Debug Message Shows a short message **during creation and preview** (not to end users) when the action fires. Handy for troubleshooting – you can see exactly when a particular point in a long chain of actions was reached. **Options** * **Message** – the text to show (supports variable interpolation). ## Run Script Runs a custom **JavaScript** snippet. The editor includes a code editor with autocompletion. Scripting extends an experience beyond the no-code actions – start no-code, then take it further yourself or with developers. See the [Scripting](/scripting/) documentation to learn the API. --- --- url: /docs/editor/scene-settings.md description: >- Settings that apply to a whole Scenery scene: lighting and rendering, meshing and occlusion, physics, draw-order groups, and localization. --- # Scene Settings Settings that apply to the whole scene. Open them with nothing selected (sections can be collapsed with their arrows to keep things tidy). ## General * **Name** – a clear scene name; the foundation of a manageable, larger experience. * **Tracking coaching overlay** – on by default; prompts the user to move their device so it can establish solid tracking. * **Spatial pause controls** (Apple Vision Pro) – the system controls for pausing/repositioning. Disable them when you don't want viewers to reposition a guided experience. (The pause menu can also be opened by long-pressing the surroundings.) * **SharePlay participation mode** – in a shared session, **Presenter only** (only the host interacts) or **All participants** (everyone can interact, simultaneously). Pick by whether the experience is about watching together or interacting together. ![The General scene settings – tracking coaching overlay, spatial pause controls, and the SharePlay participation mode menu](./images/scene-settings/general.jpg) *The General scene settings.* ## Localization markers Add a **marker** – a reference image for the *whole scene* – so content always appears in the same physical spot, and so multiple devices share one coordinate system (great for shared Vision Pro + iPad experiences). Give it an accurate **physical width (cm)**, and a **position/transform** and **heading (azimuth)** to offset it relative to your content. ::: warning Range limits A marker works well near where it was scanned; as the user walks ~50–100 m away, the device struggles to remember it and content can drift. Use markers for anchored, in-place scenes, not long walk-arounds. You can add **multiple markers** (they cycle in the coaching overlay) to re-localize across a space. ::: ![A localization marker – reference image, physical width in centimetres, position, and heading](./images/scene-settings/localization-marker.jpg) *A Localization Marker – reference image, physical width, position, and heading.* ## Meshing & occlusion * **Floor mesh** – on by default; gives physics a floor to rest on (without it, physics objects fall through). * **World mesh** – off by default (performance); builds a mesh of the real environment so content can collide with or be occluded by it. * **Scene mesh lighting** – let the mesh be lit by objects in the scene. * **Object occlusion** – real objects hide virtual ones behind them (**LiDAR devices only**). Big realism win – a virtual dog correctly hidden behind a real desk. * **People occlusion** – real people/pets correctly pass in front of virtual content (recommend **with depth**; iPhone XS and later). Crucial realism for photos and videos taken inside the experience. ![Meshing options – floor mesh and world meshing toggles](./images/scene-settings/meshing-occlusion.jpg) *The Meshing Options.* ## Physics * **Scene mesh collisions** – enable so physics objects can interact with the real environment. * **Gravity** – default −9.81 (Earth); change it to simulate the moon, space, etc. See [Physics actions](/editor/actions/physics) for using physics. ![Scene physics options – scene mesh collisions and the gravity value](./images/scene-settings/physics-gravity.jpg) *The scene Physics Options.* ## Post-processing * **Surroundings effect** – dim/tint the real surroundings (none, semi-dark, dark, custom dim, custom tint). **Apple Vision Pro only.** * **Custom effects** – apply [post-processing effects](/editor/post-fx) (filters/shaders) to the whole scene. iOS / macOS only. ## Lighting & environment These pair with the viewport's [environment controls](/editor/environments-lighting): * **Immersion mode** – **Mixed (AR)** (default), **Full VR** (replace the camera feed with a background), or **Progressive** (Vision Pro: the user dials the virtual environment in/out within a range you set). * **Background** – a custom colour, HDRI, or preset, used by Full VR / Progressive. * **Environment texturing** – on by default (real-world lighting on PBR materials); off to use a custom light map you control. * **Adaptive resolution** – automatically scales the render resolution to the device: a smooth frame rate on older hardware, best quality on newer. When it's on, a **resolution multiplier** (10–100%, default 100%) lets you fine-tune that balance by hand – lower it to render at a lower internal resolution for more performance headroom, or keep it at 100% for maximum sharpness. ![Lighting and rendering settings – immersion mode, environment texturing, adaptive resolution and the resolution multiplier](./images/scene-settings/lighting-environment.jpg) *Lighting & Rendering – immersion mode, environment texturing, and resolution.* ## Draw order groups Control the **order** in which objects render. Normally automatic, but stacked **transparent** elements (overlapping images/videos) can render with artifacts. Create a draw-order group, add the objects, and pick a **depth pass**: * **Pre-pass** – computes depth before colour (better transparency). * **Post-pass** – computes depth after colour (fewer artifacts in complex scenes). * **None**. Try different passes to see which resolves your scene's transparency best. ![A draw order group with its depth pass control](./images/scene-settings/draw-order-groups.jpg) *A Draw Order Group and its Depth Pass.* --- --- url: /docs/editor/experience-settings.md description: >- Settings for a whole Scenery experience: availability, sharing and delivery, presentation mode, branding, and platform options. --- # Experience Settings Settings for the whole experience (not a single scene). Open them with the **Settings** button (top-right) when nothing is selected. ## Information & discovery * **Name** – shows wherever the experience is shared, inside Scenery and out. Make it enticing. * **Description** – a concise hook (max 120 characters). * **Tags** – up to 5; they appear on the preview and help discovery. * **Category** – a primary and an optional secondary category. * **Thumbnail** – upload an image, or use the **Snapshot** button to capture the scene (it uploads to the asset library and sets it as the thumbnail). The thumbnail + name + description are the first thing people see. ![The Information & Discovery settings – name, description, tags, categories, and the experience thumbnail](./images/experience-settings/information-discovery.jpg) *The Information & Discovery settings.* * **Availability**: * **Public** – anyone with the link can open it, and it may appear in the Creators tab. Only make public what's ready to share. * **Private** – only you can open it, even with the link. For confidential / in-progress work. * **Link** – anyone with the link can open it (and it can be featured), but it won't show on your public profile. Good for team / client review. ![The Availability menu – Public, Private, or Link](./images/experience-settings/availability.jpg) *The Availability menu – Public, Private, or Link.* ## Sharing & delivery * **Generate link** – the gateway to your experience. On paid plans the link is an **App Clip** (instant access with no app install) – open it from a **QR code or NFC tag** for the smoothest entry (it skips the browser). Without a QR/NFC tag, a browser link shows an "open App Clip" prompt (Safari, non-private browsing only) – which is why a QR code is recommended. *(Free plan can't share App Clips; Agency/Enterprise can customize the App Clip card.)* * **Allow featuring** – let other creators discover it in Scenery. Turn off for NDA work. * **Enable SharePlay** – share the experience in a SharePlay session (best on Vision Pro; also iOS/iPadOS). * **Scene transition loading indicator** – the spinner shown when moving between scenes; disable it for seamless, immersion-preserving transitions. * **Update link metadata** – regenerates the shareable link and refreshes its preview card (the name, thumbnail, and description shown when the link is opened or shared). The button reads **Generate link** before a link exists, and **Update link metadata** afterwards – use it to refresh the preview after you change the experience's name, thumbnail, or description. ![Sharing and delivery – Generate link, Allow featuring, Enable SharePlay, the scene transition loading indicator, platforms, and the WebXR toggle](./images/experience-settings/sharing-link.jpg) *Sharing & Delivery – Generate Link, featuring, SharePlay, platforms, and WebXR.* ## Platforms & WebXR * **Platforms** – all supported by default; restrict to selected platforms if needed. * **WebXR support** (beta, opt-in) – also run the experience in a browser (Android and other devices) via the [WebXR viewer](/webxr/). A subset of features carries over (native-only features don't). It's available to every creator – just turn on the **WebXR support** toggle to opt in. (Still in beta, so expect rough edges.) ## Presentation mode * **Immersive** (default) – a fully immersive experience around the user. * **Volume** (Apple Vision Pro) – a bounded volume that coexists with other windows/volumes (like a spatial widget). Options: allow resize, allow rotation, show base plate, adjust for viewport, **content scale factor** (shrink the whole scene into a diorama), and **content clipping margins** (let content spill a set distance beyond the volume before it's clipped – e.g. a waterfall falling just past the edge). ![Volume presentation mode – resize, rotation, base plate, adjust for viewport, content scale factor, and clipping margins](./images/experience-settings/presentation-mode.jpg) *Volume presentation mode options.* ## Branding *(Agency / Enterprise only.)* * **Intro text** (max 120 chars) – a short intro card shown when the experience opens. * **Tint** – a custom colour applied to UI like buttons and the pause menu. * **Header image** – shown on the intro screen above the intro text. * **Logo** – watermarks the photos and videos users capture from within the experience (every experience ships with built-in photo/video capture + sharing, even in an App Clip), and appears on the pause screen. ![The Branding controls – tint colour, header image, and logo](./images/experience-settings/branding.jpg) *The Branding controls.* ## Scripting * **Runtime version** – the [scripting](/scripting/) runtime the experience uses (currently one version). --- --- url: /docs/editor/environments-lighting.md description: >- Light your Scenery scene and set its background: lighting presets, image-based lighting, environment texturing, and immersion modes. --- # Environments & Lighting The **background & lighting** button (in the viewport) controls how your scene is lit and what sits behind it. These are creation-time controls; some pair with **Scene settings → Lighting & environment**. ## Lighting How virtual content is lit: * **Custom** (default) – on capable devices, the device captures the **real-world lighting** (an HDR environment light map) and applies it to objects with **PBR (physically based)** materials. Content then feels grounded in the space, and looks different depending on where and when the experience is opened. Great for realism. * **Presets** – pick a fixed lighting environment (indoor, gallery, workshop, urban, street, plaza, …) when you want **full control** over the look regardless of the user's surroundings (e.g. an art-led experience). Also here: a **floor grid** toggle (a creation aid) and **lighting exposure** (brighten / darken the whole scene). ![The Lighting panel – Classic and Custom, indoor presets, the floor grid toggle, lighting exposure, and the acoustic environment](./images/environments-lighting/lighting-panel.jpg) *The Lighting panel.* ![More lighting presets – the Urban set (Park, Square, Plaza, Street)](./images/environments-lighting/lighting-presets.jpg) *More lighting presets – the Urban set.* ## Background By default the real camera feed shows behind your content. Set a **background** (a 360° image, from a preset or your own) to replace the camera feed – turning an AR-tracked scene into a more **VR-like** space the user can still walk around in. To actually show a background, set the scene's **immersion mode** (Scene settings → Lighting & environment): * **Mixed (AR)** – default; content over the real world. * **Progressive** – on Apple Vision Pro, the user can dial the virtual environment in and out. * **Full VR** – uses the background image as a full virtual environment (still with AR tracking, so the user can move around). **Environment texturing** (same section) is on by default (use real-world lighting). Turn it off to rely on your chosen custom light map instead. ![A background applied to the scene, with the Scene settings immersion mode and environment texturing controls](./images/environments-lighting/scene-lighting-rendering.jpg) *A background applied, with the Lighting & Rendering controls.* ## Acoustic environment Sets the reverb used for spatial audio – Medium room (dry), Large room, Outside, Very large room, Concert hall, etc. Use the **tooltips** (the small markers next to options, found throughout the editor) for a description of each. ![The acoustic environment menu – reverb presets from Concert Hall to Anechoic](./images/environments-lighting/acoustic-environment.jpg) *The Acoustic Environment menu.* Immersion mode (Mixed AR / Progressive / Full VR) and environment texturing match the editor's Lighting & Rendering controls. --- --- url: /docs/scripting.md --- # Scripting ::: warning Beta The Scripting API is in **beta** – APIs may change between releases. Scripting is available to selected accounts; see [Developer actions](/editor/actions/developer) for how to request access. ::: Add dynamic behaviour to your experiences with **JavaScript**. Scripts get typed access to the scene graph, entities, materials, audio, networking, animations, and GPU compute – running live in the editor preview and on device. ## Start here * **[Getting Started](/scripting/guide/getting-started)** – your first script, the global objects, and the script lifecycle. * **[Events](/scripting/guide/events)** – run scripts in response to scene and object events. * **[Materials](/scripting/guide/materials)**, **[Live labels](/scripting/guide/labels)**, **[Feature detection](/scripting/guide/feature-detection)**, **[Compute kernels](/scripting/guide/compute)** – focused guides. * **[Examples](/scripting/guide/examples)** – complete, copy-pasteable scripts. ## API reference The [API reference](/scripting/api/) documents every type – the scene, entities, math and geometry, networking, audio, animation, and compute APIs. --- --- url: /docs/scripting/guide/getting-started.md description: >- Get started with Scenery scripting: attach JavaScript to events, find and modify objects, handle rotations in radians, and debug in the console. --- # Getting Started Scenery is a visual editor for spatial experiences. You design scenes, place objects, configure materials, and wire up behaviors – all without code. Scripts handle what the visual editor doesn't: state, logic, external data, real-time connections, procedural behavior. ## How scripts work Scripts attach to events. When something happens – a tap, an object appearing, a frame rendering – Scenery can run your code. To add a script: 1. Create an event (on an object or the scene) 2. Add a "Run Script" action 3. Write your code When the event fires, your script runs with event data in `scriptContext.sourceEvent`: ```javascript // Tap event – scale up the tapped object scriptContext.sourceEvent.objectEntity.animateTo( { scale: Vector3(1.2, 1.2, 1.2) }, 0.2 ); ``` Different events provide different data – position, deltaTime, distance, etc. ::: tip Use `console.log(scriptContext.sourceEvent)` to inspect what data is available for any event. ::: ::: warning Action order matters Actions run sequentially by default. If your script uses `scene.findEntity` to find an object, that object must already be in the scene – so place the "Add to Scene" action **before** "Run Script": **Add to Scene** (MyBox) → **Run Script** (finds MyBox) If the script runs first, the object won't exist yet and `findEntity` returns `null`. Alternatively, attach your script to the object's **On Will Appear** or **On Did Appear** event – then the object is guaranteed to be available. ::: ## Example: Continuous rotation Attach to "On Render": ```javascript // Replace with your object's ID or use { name: "..." } var cube = scene.findEntity("YOUR_OBJECT_ID"); cube.representation.rotation = cube.representation.rotation.multiply( Rotation(0, scriptContext.sourceEvent.deltaTime, 0) ); ``` ## Finding objects ```javascript // By name (set in Identity panel) var box = scene.findEntity({ name: "MyBox" }); // By ID var box = scene.findEntity("ABC123"); // Access the representation (visual content) box.representation.position = Vector3(0, 1, 0); box.representation.opacity = 0.5; ``` To get an ID: right-click an object or representation in the editor → **Copy Development ID**. ::: warning Entity vs Representation An **entity** is the top-level object (the anchor container you see in the scene list). A **representation** is the visual content inside it (model, shape, image, audio, etc.). Use `entity` for finding and referencing objects, use `entity.representation` for visual changes (position, rotation, scale, opacity). `scene.findEntity` finds top-level objects only – not nested representations. If your object contains multiple representations (e.g. a model and an audio source), find the parent entity first, then navigate: ```javascript var entity = scene.findEntity({ name: "MyObject" }); var audio = entity.findRepresentation({ name: "BGMusic" }); audio.isEnabled = true; ``` See [Examples → Entity hierarchy](/scripting/guide/examples#entity-hierarchy-and-lookup) for the full pattern. ::: ## Rotations All rotation values are in **radians**. Use `Math.toRadians()` and `Math.toDegrees()` to convert: ```javascript Rotation(0, Math.PI / 2, 0); // 90° around Y axis Rotation(Math.toRadians(45), 0, 0); // 45° around X axis var degrees = Math.toDegrees(Math.PI); // 180 ``` Other math helpers: `Math.lerp(a, b, t)`, `Math.clamp(value, min, max)`, `Math.map(value, inMin, inMax, outMin, outMax)`, `Math.smoothstep(edge0, edge1, x)`. ## Error handling Script errors are logged to the debug console but don't crash the scene – other scripts and actions continue to run. Use `try/catch` for operations that may fail: ```javascript try { var response = await http.get("https://api.example.com/data"); } catch (error) { console.error("Request failed: " + error); } ``` ::: tip Using await You can use `await` directly at the top level of your script – the runtime wraps async code for you, so there's no need for an `(async function(){…})()` wrapper or a helper function. ```javascript var box = await scene.createEntity(createBox(0.3, 0.3, 0.3)); ``` ::: ## Configuring entities – use the descriptor builder `scene.createEntity(...)` takes **one** descriptor argument. Configure anchor, name, traits, and other settings via the descriptor's chainable builder methods, not as a second options argument. ```javascript // ✅ Correct – chain builder methods on the descriptor const box = createBox(0.3, 0.3, 0.3) .anchor(Anchor.position(0, 0, -1)) .name("hero-box"); const entity = await scene.createEntity(box); // ❌ Wrong – second argument throws at call time await scene.createEntity(box, { anchor: Anchor.position(0, 0, -1) }); ``` Same rule for `scene.runAction(action)` – single argument. Configure inside the action data, not via a second arg. ## Debugging Tap the terminal button in the bottom-right corner to open the **debug console**. It shows `console.log` output and errors from your scripts. You can also type and run scripts directly in the console input field. ```javascript console.log("score:", experience.getVariable("score")); console.error("something went wrong"); ``` ## Next steps * [Events](/scripting/guide/events) – Triggers, event data, the render loop * [API Reference](/scripting/api/) – Full documentation ::: tip Using AI to write scripts Want to use ChatGPT, Claude, or another LLM to help write scripts? Feed it [`llms-full.txt`](/llms-full.txt) – it contains the complete guide and API reference in a single file. ::: --- --- url: /docs/scripting/guide/events.md description: >- Scripting events in Scenery: triggers, event data, and the render loop – run JavaScript when taps, appearances, frames, and other events fire. --- # Events Events trigger scripts. Scenery has two kinds: **object events** that fire for a specific object, and **scene events** that fire globally. ## Object events Attach to individual objects. The triggering object is available in `scriptContext.sourceEvent`. | Event | Fires when | |-------|------------| | On Will Appear | Object is about to be added to scene | | On Did Appear | Object has appeared | | On Removal | Object is removed from scene | | On Tap Gesture | Object is tapped | | On Camera Collision | Camera enters/exits object bounds | | On Object Collision | Physics collision with another object | | Distance Field | Camera or target crosses distance threshold | | Look Direction | User looks at/away from object (or object looks at target) | | Media Playback | Video/audio begins, reaches timestamp, or ends | ```javascript // On Tap Gesture – scale up the tapped object scriptContext.sourceEvent.objectEntity.animateTo( { scale: Vector3(1.2, 1.2, 1.2) }, 0.2 ); ``` ## Scene events Fire regardless of which object is involved. | Event | Fires when | |-------|------------| | On Experience Start | Experience begins | | On Experience Load | Experience finishes loading | | On Screen Tap | User taps anywhere on screen | | On Render | Every frame | | On Schedule | After a delay or at specific time | | On Variable Change | A variable is modified | | On Audio Analysis | Audio levels update (ambient or microphone) | ```javascript // On Render – rotate every frame var cube = scene.findEntity("YOUR_OBJECT_ID"); cube.representation.rotation = cube.representation.rotation.multiply( Rotation(0, scriptContext.sourceEvent.deltaTime, 0) ); ``` ## Subscribing in code You can also subscribe to events programmatically: ```javascript var cube = scene.findEntity("YOUR_OBJECT_ID"); // Object event cube.on('tap', function(e) { e.objectEntity.animateTo({ scale: Vector3(1.2, 1.2, 1.2) }, 0.2); }); // Scene event scene.on('render', function(e) { cube.representation.rotation = cube.representation.rotation.multiply( Rotation(0, e.deltaTime, 0) ); }); // One-time listener scene.once('start', function() { console.log('Experience started'); }); ``` ### Throttling For expensive operations, throttle the render loop: ```javascript scene.on('render', { throttle: 0.1 }, function(e) { // Runs every 100ms instead of every frame }); ``` ### Unsubscribing ```javascript var eventId = cube.on('tap', function() {}); // Later... cube.off(eventId); ``` ::: tip objectEntity vs representationEntity Tap and collision events give you both `objectEntity` (the top-level container) and `representationEntity` (the specific visual that was interacted with). Use `objectEntity` when you need to find sibling representations like audio sources. ::: ## Event data How you access event data depends on how your script runs: **Run Script action** – use `scriptContext.sourceEvent`: ```javascript var entity = scriptContext.sourceEvent.objectEntity; var dt = scriptContext.sourceEvent.deltaTime; ``` **Event subscriptions** – data is passed to your callback: ```javascript scene.on('render', function(e) { var dt = e.deltaTime; }); cube.on('tap', function(e) { var tapped = e.objectEntity; }); ``` **Properties by event type:** | Event | Properties | |-------|------------| | `render` | `deltaTime`, `time` | | `tap` | `objectEntity`, `representationEntity`, `screenPosition` | | `screenTap` | `position` | | `distance` | `objectEntity`, `distance`, `crossed` | | `lookAt` | `objectEntity`, `isLooking` | | `cameraCollision` | `objectEntity`, `representationEntity` | | `physicsCollision` | `objectEntity`, `representationEntity`, `position`, `impulse` | | `variableChange` | `variableId`, `value` | | `mediaPlayback` | `objectEntity`, `status`, `time`, `duration` | | `audioAnalysis` | `kind`, `loudness`, `bass`, `lowMid`, `mid`, `upperMid`, `presence`, `brilliance`, `air`, `treble` | | `add`, `appear`, `remove` | `objectEntity`, `representationEntity` | ::: tip Use `console.log(scriptContext.sourceEvent)` or `console.log(e)` to inspect all available properties. ::: --- --- url: /docs/scripting/guide/materials.md description: >- Create and apply materials from Scenery scripts: build PBR, unlit, and other surfaces and assign them to entities at runtime in JavaScript. --- # Materials A `Material` is what a surface looks like – color, texture, reflectivity, shader. Use the `Material` builder to construct one inline, reference an existing library material by id, or clone a library material and tweak it. ## Kinds Six factories. Pick the one that matches the look you need. | Factory | Use for | |---------|---------| | `Material.unlit({...})` | Flat-shaded surfaces – UI, billboards, vertex-colored point clouds. Renders the same regardless of scene lighting. | | `Material.pbr({...})` | Realistic surfaces – metal, plastic, fabric. Responds to scene lighting. | | `Material.occlusion({...})` | Invisible itself but hides geometry behind it – holdouts, portals. | | `Material.customShader({...})` | Author-supplied Metal shader functions from a compiled `.metal` library. | | `Material.materialX({...})` | ShaderGraph material authored in Reality Composer Pro / `.usda`. | | `Material.video({...})` | Video texture playback. | ## Inline construction Factory + options object reads naturally: ```javascript // Unlit – single flat color const red = Material.unlit({ color: '#FF0000' }); // PBR – color + metalness + roughness, no textures const gold = Material.pbr({ color: '#FFD700', metalness: 1.0, roughness: 0.2 }); // PBR – textured const brick = Material.pbr({ color: '#FFFFFF', // tints the texture map: 'brick-albedo.png', normalMap: 'brick-normal.png', roughnessMap: 'brick-rough.png' }); ``` Same chained, if you prefer: ```javascript const gold = new Material('pbr') .color('#FFD700') .metalness(1.0) .roughness(0.2); ``` ## Input shapes **Colors** – `color`, `emissive`, `sheenColor`, `specularColor`: * `'#RRGGBB'` or `'#RRGGBBAA'` * `{ r, g, b, a }` with values in 0..1 * `{ colorSpace: 'p3', hex: 'FF6600' }` **Textures** – `map`, `emissiveMap`, `normalMap`, etc.: * URL string: `'https://.../texture.png'` * Asset id string: `'libraryTextureId'` * With a scale: `{ texture: 'urlOrId', scale: 0.5 }` **Scalars** – `roughness`, `metalness`, `clearcoat`, `opacity`, `emissiveIntensity`: plain numbers. ## Library reference Materials authored in the experience editor are referenced by id directly – no inline construction needed: ```javascript // Apply a library material to a mesh await scene.createEntity(createMesh({ ..., materials: ['myLibraryMaterialId'] })); ``` Or fetch one with `scene.getMaterial(id)` to inspect: ```javascript const mat = scene.getMaterial('myLibraryMaterialId'); console.log(mat); // → Material(pbr 'My Material' id=..., color=#FFAA00, roughness=0.3) ``` ## Clone → tweak → apply To override a library material for a single entity (without mutating the library entry), clone it first: ```javascript const base = scene.getMaterial('redMatId'); const brighter = base.clone() .emissive('#FF0000') .emissiveIntensity(2); await entity.representation.setMaterial(brighter); ``` `.clone()` deep-copies the material with a fresh `id`. The original library material is untouched. ## ShaderGraph (MaterialX) parameters ShaderGraph materials authored in Reality Composer Pro expose promoted inputs. Set constant values via `setParameter(name, value, typeHint?)`: ```javascript const mat = scene.getMaterial('myShaderGraphMatId').clone() .setParameter('intensity', 0.6) .setParameter('tintColor', '#FF00FF', { type: 'color' }) .setParameter('offset', [0.1, 0.2, 0], { type: 'vector3' }); ``` The same `setParameter` works on `customShader` materials too – the Material auto-routes to the right options blob based on its kind. **Type hints** override inferred types: `{ type: "float" | "int" | "vector2" | "vector3" | "vector4" | "color" | "boolean" | "string" }` For runtime variable bindings (slider drives a parameter), use `entity.representation.bindMaterialParameter(parameterName, variableId)` – different path, lives on the entity, ties into the variable system. ## Runtime swap Replace an entity's material at runtime: ```javascript // Cycle materials every 2 seconds const palette = [ Material.unlit({ color: '#4287F5' }), Material.pbr({ color: '#42F578', metalness: 0.5, roughness: 0.3 }), Material.unlit({ color: '#F54242' }) ]; let i = 0; scene.on('schedule', { interval: 2 }, async function() { i = (i + 1) % palette.length; await entity.representation.setMaterial(palette[i]); }); ``` `setMaterial` accepts a `Material` instance, a library ref-id string, or `{ id, target? }`. `setMaterials([...])` for multiple at once. ## Introspection ```javascript console.log(material); // → Material(pbr id=..., color=#FF6600, roughness=0.3) console.log(JSON.stringify(material, null, 2)); // → full Codable JSON material.kind; // → 'pbr' material.id; // → 'AB12CD34' material.baseColor; // → { color: { colorSpace: 'p3', hex: 'FF6600' } } material.materialXOptions?.parameters; // → { intensity: { ... } } for materialX ``` For setter names that conflict with property names (`roughness`, `opacity`, `clearcoat`, `emissiveIntensity`), read via `material.data.` – e.g. `material.data.roughness?.scale`. ## Target By default a material applies to all models on an entity. Override for multi-model entities: ```javascript Material.pbr({ color: '#FF0000' }) .target({ type: 'firstModel' }); Material.pbr({ color: '#0000FF' }) .target({ type: 'selectedModels', models: ['windshield'], materialSlots: [0] }); ``` --- --- url: /docs/scripting/guide/labels.md description: >- Create and update text labels from Scenery scripts: build, position, and restyle on-screen and in-world text at runtime in JavaScript. --- # Live labels (variable interpolation) Label text supports inline variable references that resolve **live** — the label re-renders automatically whenever the referenced variable changes. No `scene.on('variableChange', …)` wiring needed. ## Basic syntax ```javascript UI.label({ text: 'vol = ${vol}' }) ``` `${vol}` is read from the current segment's variable storage. If `vol` is unset, the label renders `vol = UNDEFINED`. ## Scopes | Form | Reads from | |------|-----------| | `${name}` | Segment-scoped variable (default) | | `${g:name}` | Experience-scoped (global) variable | Set scope on the variable side via `scene.setVariable(name, value, { scope: 'experience' })`. ## Filters Pipe the value through a formatter with `|filter` or `|filter(arg)`: | Filter | Use | Example output (input `0.6789`) | |--------|-----|--------------------------------| | `\|fixed(N)` | Trim a float to N decimals (default 2) | `0.68` | | `\|percent(N)` | Multiply ×100, append `%` (default 0 decimals) | `68%` | | `\|time` | Seconds → `m:ss` (or `h:mm:ss` ≥ 1h) | (for `185`) `3:05` | | `\|int` | Truncate toward zero | `0` | | `\|signed(N)` | Always-signed, N decimals (default 0) | `+1` | Filters are numeric – applied to a non-numeric variable, they pass the raw value through unchanged. Unknown filter names log a console warning and do the same. ## Example: bound slider with live readout The slider drives the variable; the label reads from it. Authors don't write any subscription code – the label re-renders on every variable change automatically. ```javascript // Attach to: On Experience Start scene.setVariable('vol', 0.5); var panel = createPanel( UI.vStack({ spacing: 12, padding: 16, children: [ UI.label({ text: 'Volume: ${vol|fixed(2)}' }), UI.slider({ name: 'volume', value: 0.5 }) ] }), { panelSize: [400, 160] } ); var entity = await scene.createEntity(panel); entity.representation.findView({ name: 'volume' }).bind('value', 'vol'); ``` ## Multiple references in one label A single label can mix any number of references and plain text: ```javascript UI.label({ text: 'Score: ${g:score|int} , Timer: ${countdown|time}' }) ``` Each reference subscribes independently – any one variable changing triggers a re-render with the current values of all of them. --- --- url: /docs/scripting/guide/feature-detection.md description: >- Detect platform capabilities in Scenery scripts: check environment.features before using compute, networking, and other optional APIs. --- # Feature detection Not every Scenery feature is available on every platform or every runtime version. Compute shaders are gated behind a future runtime; hand tracking needs a visionOS device; SharePlay is stripped out of App Clip builds. Authors branch on capability through a single string-keyed registry on `environment.features`: ```javascript if (environment.features.has('compute')) { mesh.runCompute('advect', { uniforms }); } else { stepOnCPU(); } ``` Unknown feature names return `false` rather than throwing – this matches WebGPU's `Set.has` semantic and lets forward-looking scripts query features that may land in a future runtime version without crashing on today's runtime. ## Listing what's available ```javascript console.log(environment.features.list()); // → ['sharedActivities'] ``` `list()` returns every feature string the current runtime + device actually supports. Use it when debugging ("why isn't my branch firing?") or when reflecting capability state into telemetry. ## Current feature strings | Feature | Today | Notes | |---------|-------|-------| | `compute` | Apple-family 7+ devices on iOS 18 / visionOS 2 / macOS 15+ | GPU compute-kernel dispatch. `Kernel.fromSource(...)` / `Kernel.fromAsset(...)` compile a kernel; `mesh.runCompute(kernel, options)` dispatches it against `storage: "compute"` attribute buffers and / or compute textures. | | `sharedActivities` | platform-dependent | Multi-user / SharePlay support. `false` in App Clip and any build without the shared-activities trait. | | `cameraFeed` | iOS / iPadOS | Live camera-feed texture (via `Texture.cameraFeed()`). Wired against ARKit's camera capture; not available on Mac Catalyst (no AR camera) or visionOS (different passthrough surface). | More feature strings land here as their backends wire up. A name not in the table above always returns `false` from `has()` – never throws. ## Pattern Feature detection is the right tool for runtime branching across platforms and capability classes. Use it for: * Hardware capabilities the device may or may not have (compute, future: `handTracking`, `planeDetection`, `sceneReconstruction`). * Build-trait features that strip out of compact targets (`sharedActivities`, future: `gamification`). * Runtime-version features (anything reserved for a later API bump). For pure platform identity – "am I on visionOS?" – read `environment.hostingPlatform` directly: ```javascript if (environment.hostingPlatform === 'visionOS') { enableHandTrackingUI(); } ``` Platform identity is single-valued (you're on exactly one host) and so stays a string property; capabilities form a set and live in the registry. --- --- url: /docs/scripting/guide/compute.md description: >- Run GPU compute kernels from Scenery scripts: Metal shading-language kernels with buffer and texture bindings for dynamic mesh and texture work. --- # Compute Kernels GPU compute lets scripts run a function across thousands of threads in parallel – typical uses are heightmap textures, particle simulations, vertex displacement on a dynamic mesh, or "how many triangles did marching cubes emit this frame" atomic counters. ::: warning Apple GPUs only, for now Compute kernels are **currently available on Apple GPUs only** – web / WebXR support is planned but **not yet available**. Today you author the kernel directly in **Metal Shading Language (MSL)**, which makes this the one corner of the scripting API that isn't fully engine-agnostic. Always gate compute behind `environment.features.has('compute')` ([Feature Detection](/scripting/guide/feature-detection)) so a script degrades cleanly on devices – and platforms – that don't support it yet. ::: ```javascript // Attach to: onStart if (!environment.features.has('compute')) { console.warn('compute not supported – bailing'); return; } const heightMap = await Texture.compute({ pixelFormat: 'rgba8Unorm', width: 256, height: 256 }); const kernel = await Kernel.fromSource({ source: ` #include using namespace metal; struct Uniforms { float time; }; kernel void wave(constant Uniforms& U [[buffer(0)]], texture2d outTex [[texture(0)]], uint2 gid [[thread_position_in_grid]]) { float2 uv = float2(gid) / float2(outTex.get_width(), outTex.get_height()); float r = 0.5 + 0.5 * sin(U.time + uv.x * 6.28); outTex.write(float4(r, uv.y, 0.5, 1.0), gid); }`, functionName: 'wave' }); const uniforms = new Float32Array(1); scene.on('render', function () { uniforms[0] = scene.time; kernel.run({ uniforms: uniforms, outputTextures: { outTex: heightMap.write }, threadGroups: [16, 16, 1] }); }); ``` ## Dispatch entry points Two ways to dispatch a kernel: * **`mesh.runCompute(kernel, options)`** – when the kernel writes mesh attribute buffers (`storage: "compute"` attributes) or the mesh's index buffer. The mesh sits in `buffer(1...)` slots. * **`kernel.run(options)`** – standalone compute. No mesh involvement. Use for texture generation, particle simulation, atomic counters, anything that doesn't displace mesh geometry. Both paths share the same options shape; `kernel.run` simply skips the mesh-related slots. ## MSL slot ordering – strict and positional Author-side bindings map to MSL `[[buffer(N)]]` / `[[texture(N)]]` indices in a fixed order: | Slot | Source | Notes | |---|---|---| | `buffer(0)` | `uniforms: Float32Array` | Always slot 0, packed as raw bytes | | `buffer(1..A)` | `outputs[]` (mesh attributes) | Only for `mesh.runCompute`. Order matches the array – `outputs: ['position', 'normal']` binds `position` at 1, `normal` at 2 | | `buffer(A+1)` | mesh index buffer | Only when `outputIndices: true` and the mesh has `indexCapacity > 0` | | `buffer(A+2..)` | `inputBuffers` then `outputBuffers` | Both maps merged into one continuous block, inputs first | | `texture(0..)` | `inputTextures` then `outputTextures` | Separate slot space from buffers, inputs first | Match your MSL signature to this order. Slot indices the kernel declares (`[[buffer(N)]]`) must match what the runtime binds. ## Common pitfalls ### "No outputs declared" warning ``` kernel.run: no outputs declared (outputs / outputIndices / outputBuffers / outputTextures all empty). Kernel will run but nothing it writes is observable – likely a bug. ``` A dispatch without any write target runs the kernel but has no observable effect – the GPU reads inputs, computes values, then throws them away. Almost always a missed wiring. Add the destination map you meant to (`outputBuffers: { triCount: counter }`, `outputTextures: { outTex: heightMap.write }`, etc.). ### Slot ordering off by one MSL kernels declare slot indices by hand (`constant Uniforms& U [[buffer(0)]]`). If you change the JS dispatch options – add an attribute to `outputs`, toggle `outputIndices`, add an `inputBuffer` – every later slot index shifts. Always re-check the MSL signature when you change the dispatch options. ### Texture token swap (`.read` vs `.write`) A texture handle has two binding tokens: `.read` (sample inside the kernel) and `.write` (write to inside the kernel). They route to different Metal APIs under the hood. Passing `.read` where you meant `.write` (or vice versa) means the kernel binds the texture in the wrong access mode – usually a silent no-op for writes, or undefined sampling for reads. Token name = what the kernel does with it. ### Uniforms must be a `Float32Array` `uniforms` is packed as raw bytes. The runtime only accepts a `Float32Array`. Plain JS arrays and other typed arrays trigger a warn and skip. To pack mixed types (a float and an integer index, say), reinterpret memory inside MSL or use an `inputBuffers` entry instead. ### `outputs` only valid with a mesh `outputs: [...]` and `outputIndices: true` are mesh-attribute writes. They mean nothing for `kernel.run(...)` (no mesh) and are silently ignored there. If you want raw output, use `outputBuffers: { ... }` with a `Buffer` handle. ### Forgetting `await` on factory promises `Texture.compute(...)`, `Kernel.fromSource(...)`, and `Kernel.fromAsset(...)` all return Promises. Without `await`, downstream code touches a Promise object – `kernel.run(...)` fails, `texture.write` is undefined. The `_promisify` wrapper logs nothing helpful here; just remember the `await`. ### Buffer size mismatches on `writeSync` ``` Buffer.writeSync: undersize write – source has 2 elements, buffer holds 4. Copying 2 elements; tail of buffer UNTOUCHED. ``` The warn is loud on purpose. Seeding partial data is usually a bug – match `src.length` to the buffer's declared element count. ## Capability gate Always gate compute calls with `environment.features.has('compute')` – devices below the Apple 7 GPU family or below iOS 18 / visionOS 2 / macOS 15 return `false`. The Swift bridge throws cleanly if you slip past, but the JS-side gate keeps your script structure honest. --- --- url: /docs/scripting/guide/examples.md description: >- Ready-to-paste Scenery scripting examples: rotation, entity lookup and hierarchy, animation, HTTP requests, materials, and more. --- # Examples Complete patterns for common scripting tasks. Each example is self-contained – attach to the indicated event and it works. ## Entity hierarchy and lookup Every object in the scene is an **Entity** (the anchor container). Each entity has one or more **Representations** (the visuals inside it – models, shapes, images, audio, etc.). ``` scene └─ Entity ("MyObject") ← scene.findEntity({ name: "MyObject" }) ├─ representation (primary) ← entity.representation ├─ Representation "Audio1" ← entity.findRepresentation({ name: "Audio1" }) └─ Representation "SubModel" ← entity.findRepresentation({ name: "SubModel" }) ``` ::: warning Common Gotcha `scene.findEntity` only finds **top-level entities** – not nested representations inside them. Find the parent entity first, then use `findRepresentation` to navigate. ::: Find the parent entity first, then navigate to the representation: ```javascript // WRONG – Audio1 is a representation, not a top-level entity var audio = scene.findEntity({ name: "Audio1" }); // null! // RIGHT – find the parent entity, then the nested representation var parent = scene.findEntity({ name: "MyObject" }); var audio = parent.findRepresentation({ name: "Audio1" }); audio.isEnabled = true; ``` Look up by ID or name: ```javascript // Entity lookup var entity = scene.findEntity("ABC123"); // by ID var entity = scene.findEntity({ name: "MyBox" }); // by name // Representation lookup (from entity) var rep = entity.representation; // primary (first) representation var rep = entity.findRepresentation("DEF456"); // by ID var rep = entity.findRepresentation({ name: "Wheel" }); // by name ``` Explore a model's internal hierarchy: ```javascript var entity = scene.findEntity({ name: "Robot" }); var rep = entity.representation; // List all nested children (bones, sub-meshes, etc.) var names = rep.getChildNames(); console.log(names); // ["torso", "arm_left", "arm_right", ...] // Access a specific child var arm = rep.findChild("arm_left"); arm.transform = Transform({ rotation: Rotation(0, 0, Math.PI / 4) }); ``` ### Event context: objectEntity vs representationEntity When a script runs from an event (tap, collision, etc.), the event data includes both: ```javascript // objectEntity = the top-level entity (anchor container) var entity = scriptContext.sourceEvent.objectEntity; // representationEntity = the specific representation that triggered the event var rep = scriptContext.sourceEvent.representationEntity; // Common pattern: use representationEntity for visual changes rep.opacity = 0.5; rep.animateTo({ scale: Vector3(1.2, 1.2, 1.2) }, 0.3); // Use objectEntity to find sibling representations var audio = entity.findRepresentation({ name: "Audio1" }); audio.isEnabled = true; ``` ### Sequential audio with collision counter A practical example combining entity navigation with state: ```javascript // Attach to: On Object Collision (on each trigger object) // Setup: Parent entity "AudioPlayer" contains representations "Audio1", "Audio2", "Audio3" var count = experience.getVariable("hitCount") ?? 0; count = count + 1; experience.setVariable("hitCount", count); if (count <= 3) { var player = scene.findEntity({ name: "AudioPlayer" }); var audio = player.findRepresentation({ name: "Audio" + count }); if (audio) { audio.isEnabled = true; } } ``` ## Create objects dynamically Spawn a box in front of the camera: ```javascript // Attach to: On Screen Tap var box = await scene.createEntity( createBox(0.3, 0.3, 0.3) // width, height, depth in meters .anchor(Anchor.currentPOV(0, 0, -1)) // x, y, z offset from camera ); console.log("Created: " + box.id); ``` Create a sphere at a fixed world position with traits: ```javascript var sphere = await scene.createEntity( createSphere(0.1) // radius in meters .name("Ball") .anchor(Anchor.position(0, 1.5, -2)) // x, y, z world position .traits(function(t) { return t.opacity(0.8).build(); }) ); ``` Place an object on a detected floor: ```javascript var marker = await scene.createEntity( createSphere(0.05) .name("Marker") .anchor(Anchor.horizontalPlane("floor")) ); ``` Load a 3D model from a remote URL: ```javascript // Attach to: On Experience Start var model = await scene.createEntity( createModel("https://developer.apple.com/augmented-reality/quick-look/models/soccerball/ball_soccerball_realistic.usdz") .name("RemoteModel") .anchor(Anchor.position(0, 1, -2)) .traits(function(t) { return t.fittingBox(0.5).build(); // fit into 0.5m bounding box }) ); // Spin on tap model.on('tap', function() { model.play(EntityAnimation.spin(1, 0.8, { axis: [0, 1, 0] })); }); ``` Display an image: ```javascript var photo = await scene.createEntity( createImage("https://picsum.photos/800/600", 0.5, 1.5) // url, width, height in meters .anchor(Anchor.currentPOV(0, 0, -1.5)) ); ``` ## Animate objects Simple property animation: ```javascript var box = scene.findEntity({ name: "MyBox" }); // Animate to target values (fire-and-forget, no await needed) box.animateTo( { position: Vector3(0, 2, -1), opacity: 0.5 }, 1.0, { timingFunction: "easeInOut" } ); // Available: "linear", "easeIn", "easeOut", "easeInOut" ``` Chained animations with async/await: ```javascript var box = scene.findEntity({ name: "MyBox" }); await box.animateTo({ position: Vector3(0, 2, 0) }, 1.0); await box.animateTo({ scale: Vector3(2, 2, 2) }, 0.5); await box.animateTo({ opacity: 0 }, 0.3); ``` Using EntityAnimation with play(): ```javascript var box = scene.findEntity({ name: "MyBox" }); // Spin 2 full revolutions over 3 seconds await box.play(EntityAnimation.spin(2, 3.0, { axis: [0, 1, 0] })); // Keyframe animation await box.play(EntityAnimation.keyframes([ { position: Vector3(0, 1, -2) }, { position: Vector3(1, 2, -2) }, { position: Vector3(0, 3, -2) } ], 2.0, { tweenMode: "linear" })); // Group multiple animations together await box.play(EntityAnimation.group([ EntityAnimation.to({ opacity: 0.5 }, 1.0), EntityAnimation.spin(1, 1.0) ])); ``` ## Value animations ::: tip Spring animations `animateTo` supports `"linear"`, `"easeIn"`, `"easeOut"`, `"easeInOut"` timing. For spring dynamics, use `animateValue` with a `spring` config instead. ::: Animate a number and apply it each frame: ```javascript var box = scene.findEntity({ name: "MyBox" }); animateValue({ from: 0, to: Math.PI * 2, duration: 3.0, curve: "easeInOut", repeatCount: -1, onUpdate: function(value) { box.representation.position = Vector3( Math.sin(value) * 2, 1, Math.cos(value) * 2 ); } }); ``` Spring-based value animation: ```javascript animateValue({ from: 0.6, to: 0.08, spring: { duration: 0.6, bounce: 0 }, onUpdate: function(h) { scene.setVariable("baseColor", Color.hsl(h, 0.8, 0.55)); } }); ``` Repeating pulse animation: ```javascript animateValue({ from: 0.6, to: 0.65, duration: 4, curve: 'easeInOut', repeatCount: -1, reverseOnRepeat: true, onUpdate: function(h) { scene.setVariable("baseColor", Color.hsl(h, 0.5, 0.5)); } }); ``` ## Fetch data from an API GET request – fetch live weather data: ```javascript // Attach to: On Experience Start var response = await http.get( "https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m" ); var temp = response.data.current.temperature_2m; console.log("Berlin: " + temp + "°C"); ``` POST request with JSON: ```javascript var response = await http.post( "https://api.example.com/submit", JSON.stringify({ score: 100, name: "Player" }), { headers: { "Content-Type": "application/json" } } ); console.log("Status: " + response.status); ``` Custom request with auth and headers: ```javascript var response = await http.request("https://api.example.com/data", { method: "POST", token: "YOUR_API_KEY", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: "test" }) }); console.log(response.data.results); ``` ::: tip Authorization Use the `token` shorthand for Bearer auth, or pass headers directly: ```javascript // These are equivalent: http.request(url, { token: "sk-..." }); http.request(url, { headers: { "Authorization": "Bearer sk-..." } }); ``` ::: Binary response as base64 (e.g. text-to-speech): ```javascript var response = await http.request("https://api.openai.com/v1/audio/speech", { method: "POST", token: "sk-...", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: "tts-1", input: "Hello from augmented reality!", voice: "nova", response_format: "pcm" }), responseType: "base64" }); if (response.status === 200) { var entity = scene.findEntity({ name: "Speaker" }); await entity.playAudioBuffer(response.data, { sampleRate: 24000 }); } ``` ## WebSocket real-time connection ```javascript // Attach to: On Experience Start var ws = websocket.connect("wss://echo.websocket.org", { headers: { "Authorization": "Bearer YOUR_TOKEN" } }); ws.onOpen = function() { console.log("Connected"); ws.send(JSON.stringify({ type: "hello" })); }; ws.onMessage = function(event) { var data = JSON.parse(event.data); console.log("Received: " + data.type); }; ws.onClose = function() { console.log("Disconnected"); }; ws.onError = function(event) { console.log("Error: " + event.error); }; ``` ## Shader material parameters Bind a shader parameter to a variable, then toggle it: ```javascript var box = scene.findEntity({ name: "Box" }); // Bind the ShaderGraphMaterial parameter "isOn" to scene variable "isOn" box.representation.bindMaterialParameter("isOn", "isOn", { update: "onChange" }); // Initialize the variable scene.setVariable("isOn", false); // Toggle on tap box.on("tap", function() { var isOn = scene.getVariable("isOn") ?? false; scene.setVariable("isOn", !isOn); }); ``` Animate a material parameter via variables: ```javascript var entity = scriptContext.sourceEvent.representationEntity; // Bind material parameters to variables entity.bindMaterialParameter("baseColor", "baseColor", { update: "onChange" }); entity.bindMaterialParameter("intensity", "intensity", { update: "onChange" }); // Animate color via variable (material auto-updates) scene.setVariable("baseColor", Color.hsl(0.6, 0.5, 0.5)); scene.setVariable("intensity", 0.15); // Smoothly ramp intensity to a target – animateValue paces by // wall-clock so the ramp takes the same time regardless of frame rate. // Hand-rolled `lerp on render tick` looks similar but converges twice // as fast at 120 Hz as at 60 Hz. animateValue({ from: 0.15, to: 0.5, duration: 0.6, curve: "easeOut", onUpdate: function (v) { scene.setVariable("intensity", v); } }); ``` ## Toggle behavior on tap Use a variable to alternate between two actions on each tap: ```javascript // Attach to: On Experience Start experience.setVariable("isOpen", false); var lid = scene.findEntity({ name: "Lid" }); lid.on('tap', function() { var isOpen = experience.getVariable("isOpen"); if (isOpen) { lid.animateTo({ rotation: Rotation(0, 0, 0) }, 0.4, { timingFunction: "easeInOut" }); } else { lid.animateTo({ rotation: Rotation(-Math.PI / 2, 0, 0) }, 0.4, { timingFunction: "easeOut" }); } experience.setVariable("isOpen", !isOpen); }); ``` Or use `on`/`off` to swap between different event handlers: ```javascript // Attach to: On Experience Start var box = scene.findEntity({ name: "MyBox" }); var currentTap = null; function listenShrink() { currentTap = box.on('tap', function() { box.off(currentTap); box.animateTo({ scale: Vector3(0.5, 0.5, 0.5) }, 0.3, { timingFunction: "easeOut" }); listenGrow(); }); } function listenGrow() { currentTap = box.on('tap', function() { box.off(currentTap); box.animateTo({ scale: Vector3(1, 1, 1) }, 0.3, { timingFunction: "easeOut" }); listenShrink(); }); } listenShrink(); ``` ## State management with variables Variables live on two scopes: * **`experience`** – persists across all scenes. Use for global state like scores, settings, or progress. * **`scene`** – cleared on scene transition. Use for local state like counters, toggles, or material bindings. Experience-scoped (persists across scenes): ```javascript // Script A (On Experience Start): initialize state experience.setVariable("score", 0); experience.setVariable("level", 1); // Script B (On Tap Gesture): update state var score = experience.getVariable("score"); experience.setVariable("score", score + 10); // Script C (On Variable Change): react to changes var newScore = experience.getVariable("score"); console.log("Score: " + newScore); ``` Scene-scoped (cleared on scene transition): ```javascript scene.setVariable("localCounter", 0); var count = scene.getVariable("localCounter"); scene.setVariable("localCounter", count + 1); ``` ## Raycasting Cast a ray from the screen center to find objects: ```javascript // Attach to: On Render or On Screen Tap var center = Vector2(0.5, 0.5); var ray = scene.screenToRay(center); if (!ray) return; var hits = scene.raycast(ray.origin, ray.direction); if (hits.length > 0) { var hit = hits[0]; console.log("Hit at: " + hit.position.x.toFixed(2) + ", " + hit.position.y.toFixed(2) + ", " + hit.position.z.toFixed(2)); } ``` Place an object aligned to the surface normal: ```javascript function transformFromNormal(position, normal) { var n = normal.normalize(); var up = Vector3(0, 1, 0); var dot = up.dot(n); if (dot > 0.999) return Transform({ position: position }); if (dot < -0.999) return Transform({ position: position, rotation: Rotation(Math.PI, 0, 0) }); var axis = up.cross(n).normalize(); var angle = Math.acos(dot); var halfAngle = angle / 2; var s = Math.sin(halfAngle); return Transform({ position: position, rotation: Rotation.quaternion(axis.x * s, axis.y * s, axis.z * s, Math.cos(halfAngle)) }); } var ray = scene.screenToRay(Vector2(0.5, 0.5)); if (ray) { var hits = scene.raycast(ray.origin, ray.direction); if (hits.length > 0) { var entity = scene.findEntity({ name: "Marker" }); entity.representation.worldTransform = transformFromNormal(hits[0].position, hits[0].normal); } } ``` Cast against AR planes (real-world surfaces): ```javascript var ray = scene.screenToRay(Vector2(0.5, 0.5)); var arHits = scene.raycastAR(ray.origin, ray.direction, "existingPlaneGeometry", "horizontal"); if (arHits.length > 0) { var hit = arHits[0]; console.log("Surface: " + hit.targetAlignment); scene.createEntity( createSphere(0.05) .anchor(Anchor.position(hit.worldTransform.position)) ); } ``` ## Audio Play spatial audio on an object: ```javascript var speaker = scene.findEntity({ name: "Speaker" }); await speaker.playAudio("https://example.com/sound.mp3", { gain: 0.8, loops: true, inputMode: "spatial" }); ``` Play audio from a generated buffer: ```javascript // Generate a 440Hz sine wave tone (1 second) var sampleRate = 24000; var numSamples = Math.floor(sampleRate * 1.0); var samples = new Int16Array(numSamples); for (var i = 0; i < numSamples; i++) { var t = i / sampleRate; samples[i] = Math.floor(Math.sin(2 * Math.PI * 440 * t) * 32767 * 0.5); } // Convert to base64 var bytes = new Uint8Array(samples.buffer); var binary = ''; for (var j = 0; j < bytes.length; j++) { binary += String.fromCharCode(bytes[j]); } var base64 = btoa(binary); var entity = scene.findEntity({ name: "Speaker" }); await entity.playAudioBuffer(base64, { sampleRate: sampleRate }); ``` Stream audio in real-time: ```javascript var entity = scene.findEntity({ name: "Speaker" }); var stream = entity.createAudioStream({ sampleRate: 24000 }); // Append base64-encoded PCM16 audio chunks as they arrive stream.append(base64AudioData); // Check if buffer has finished playing if (stream.isDrained) { console.log("Playback finished"); } // Stop when done stream.stop(); ``` ## Microphone input ```javascript // Attach to: On Experience Start var mic = scene.microphone; mic.configure({ sampleRate: 24000, silenceThreshold: 0, highpassFrequency: 0 }); mic.onData = function(base64Chunk) { // Process audio chunk (e.g. send to server or WebSocket) ws.send(JSON.stringify({ type: "input_audio_buffer.append", audio: base64Chunk })); }; mic.onError = function(error) { console.log("Mic error: " + error); }; mic.start(); // Later: mic.stop(); ``` ## Face tracking (blend shapes) Drive a 3D face model with live face tracking data: ```javascript // Attach to: On Render // Requires a model with blend shape support (e.g. ARKit-compatible face mesh) var headModel = scene.findEntity({ name: "Head" }).representation; var faces = scene.getAnchors("face"); if (faces.length > 0) { headModel.setBlendShapeWeights(faces[0].blendShapes); } ``` ## Hand tracking (visionOS) Attach an object to the user's hand: ```javascript scene.createEntity( createSphere(0.02) .name("HandMarker") .anchor(Anchor.hand("right", "indexFingerTip", "full", 0.5, true)) ); ``` Read hand joint data: ```javascript // Attach to: On Render var anchors = scene.getAnchors("hand"); for (var i = 0; i < anchors.length; i++) { var hand = anchors[i]; if (hand.chirality === "right") { console.log("Right hand at: " + hand.worldTransform.position); } } ``` Inspect model hierarchy for hand mesh rigging: ```javascript var entity = scene.findEntity({ name: "Glove" }); var rep = entity.representation; var names = rep.getChildNames(); names.forEach(function(name) { var child = rep.findChild(name); console.log(name + " - joints: " + child.jointNames.length); }); ``` ## Image tracking Track a physical image and attach content: ```javascript scene.createEntity( createBox(0.1, 0.1, 0.01) .name("ImageOverlay") .anchor(Anchor.image( "https://example.com/marker.jpg", 0.15, // physical width in meters "myMarker", "any", true, // track continuously true, // hide if tracking lost true // show discovery hint )) ); ``` ## Render loop patterns Smooth oscillation: ```javascript // Attach to: On Render var box = scene.findEntity({ name: "MyBox" }); var t = scriptContext.sourceEvent.time; box.representation.position = Vector3( Math.sin(t) * 0.5, 1 + Math.sin(t * 2) * 0.2, -2 ); ``` Frame-rate independent movement: ```javascript // Attach to: On Render var box = scene.findEntity({ name: "MyBox" }); var dt = scriptContext.sourceEvent.deltaTime; var speed = 2.0; var pos = box.representation.position; box.representation.position = Vector3(pos.x + speed * dt, pos.y, pos.z); ``` Throttled updates for expensive operations: ```javascript scene.on('render', { throttle: 0.1 }, function(e) { // Runs at 10 FPS instead of every frame var anchors = scene.getAnchors("plane"); console.log("Planes: " + anchors.length); }); ``` ## Look at camera (billboard) ```javascript // Attach to: On Render var label = scene.findEntity({ name: "Label" }); var cam = scene.cameraTransform; var dir = cam.position.subtract(label.representation.worldPosition).normalize(); var yaw = Math.atan2(dir.x, dir.z); label.representation.rotation = Rotation(0, yaw, 0); ``` ## Scene transitions ```javascript // Pass data to the next scene via experience variables experience.setVariable("selectedLevel", 3); // Transition (use scene ID from editor) experience.transitionToScene("SCENE_ID"); ``` ## Platform-specific behavior Use `environment` to check the current platform and adapt: ```javascript // Attach to: On Experience Start var platform = environment.hostingPlatform; // "iOS", "macOS", "visionOS", or "web" if (platform === "visionOS") { // Add hand-tracked content scene.createEntity( createSphere(0.02) .name("HandMarker") .anchor(Anchor.hand("right", "indexFingerTip", "full", 0.5, true)) ); } else { console.log("Running on " + platform + " (" + environment.systemOSVersion + ")"); console.log("Locale: " + environment.locale); } ``` ## Arrange objects in a line On screen tap, find multiple entities and animate them into a row: ```javascript // Attach to: On Screen Tap var names = ["BoxA", "BoxB", "BoxC", "BoxD", "BoxE"]; var spacing = 0.4; var startX = -((names.length - 1) * spacing) / 2; for (var i = 0; i < names.length; i++) { var entity = scene.findEntity({ name: names[i] }); if (!entity) continue; var targetPos = Vector3(startX + i * spacing, 1.2, -2); entity.animateTo( { position: targetPos, rotation: Rotation(0, 0, 0), scale: Vector3(1, 1, 1) }, 0.6 + i * 0.1, { timingFunction: "easeOut" } ); } ``` Arrange in a circle: ```javascript // Attach to: On Screen Tap var names = ["Obj1", "Obj2", "Obj3", "Obj4", "Obj5", "Obj6"]; var radius = 1.0; var center = Vector3(0, 1.2, -2.5); for (var i = 0; i < names.length; i++) { var entity = scene.findEntity({ name: names[i] }); if (!entity) continue; var angle = (i / names.length) * Math.PI * 2; var targetPos = Vector3( center.x + Math.sin(angle) * radius, center.y, center.z + Math.cos(angle) * radius ); // Face center var yaw = Math.atan2( center.x - targetPos.x, center.z - targetPos.z ); entity.animateTo( { position: targetPos, rotation: Rotation(0, yaw, 0) }, 0.8, { timingFunction: "easeOut", delay: i * 0.08 } ); } ``` ## Procedural spiral with tap bounce Create boxes in a rainbow spiral. Each box gets an inline unlit material with a unique HSL color. Tap any box to bounce it: ```javascript // Attach to: On Experience Start var count = 24; var baseRadius = 0.8; var heightStep = 0.06; var turns = 2.5; async function buildSpiral() { for (var i = 0; i < count; i++) { var t = i / count; var angle = t * Math.PI * 2 * turns; var radius = baseRadius * (0.3 + t * 0.7); var size = 0.06 + t * 0.06; var x = Math.sin(angle) * radius; var y = 1.0 + i * heightStep; var z = -2.5 + Math.cos(angle) * radius; var box = await scene.createEntity( createBox(size, size, size) .name("Spiral_" + i) .anchor(Anchor.position(x, y, z)) ); // Face outward from center var yaw = Math.atan2(x, z - (-2.5)); box.animateTo( { rotation: Rotation(0, yaw, 0) }, 0.3, { delay: i * 0.03 } ); // Bounce on tap box.on('tap', function(e) { e.representationEntity.animateTo( { scale: Vector3(1.5, 1.5, 1.5) }, 0.3, { timingFunction: "easeOut" } ); }); } } buildSpiral(); ``` ## UI panel with bound slider Build a small control panel, bind a slider to a scene variable, and read the live value back in a label via `${var|fixed(2)}` – the label re-renders automatically whenever the variable changes. See [Live Labels](/scripting/guide/labels) for the full filter reference. ```javascript // Attach to: On Experience Start scene.setVariable("brightness", 0.5); var panel = createPanel( UI.vStack({ spacing: 16, padding: 24, children: [ UI.label({ text: "Brightness: ${brightness|fixed(2)}" }), // Sliders fill the stack's width by default – no `sizing` needed. UI.slider({ name: "brightness", minValue: 0, maxValue: 1, value: 0.5 }) ] }), // Panel size in points (~1360 pt/m). Use 'auto', 'fill', or [w, h]. { panelSize: [400, 200] } ) .name("controls") // Static world anchor in front of the user at eye height. .anchor(Anchor.position(0.5, 1.4, -0.8)); var entity = await scene.createEntity(panel); entity.representation.findView({ name: "brightness" }).bind("value", "brightness"); ``` ## Dynamic mesh ribbon Generate a growing ribbon procedurally: each frame, append two new vertices to the strip, then publish the updated draw range. Vertex writes go through a single `memcpy` from a reused TypedArray. ```javascript // Attach to: On Experience Start var MAX_SEGMENTS = 256; var vertexCapacity = MAX_SEGMENTS * 2; // two verts per segment var ribbon = await scene.createEntity( createMesh({ vertexCapacity: vertexCapacity, indexCapacity: vertexCapacity, attributes: { position: { format: "float3", storage: "dynamic" }, color: { format: "uchar4Normalized", storage: "dynamic" } }, parts: [{ indexCount: 0, topology: "triangleStrip", bounds: { min: [-1, -1, -1], max: [1, 1, 1] } }] }) ); var mesh = ribbon.representation.mesh; // triangleStrip walks the vertex buffer in order, so the index buffer // is just identity. Written once; drawCount expands per-frame. var indices = new Uint32Array(vertexCapacity); for (var i = 0; i < vertexCapacity; i++) { indices[i] = i; } mesh.writeIndices(0, indices); // Reusable scratch buffers – allocated once, extended lazily as the // ribbon grows. var positions = new Float32Array(vertexCapacity * 3); var colors = new Uint8Array(vertexCapacity * 4); var written = 0; // high-water mark – each segment's geometry is deterministic function writeSegment(i) { var t = i * 0.05; var x = Math.sin(t) * 0.4; var y = i * 0.01; var z = Math.cos(t) * 0.4; positions[(i * 2) * 3 + 0] = x; positions[(i * 2) * 3 + 1] = y; positions[(i * 2) * 3 + 2] = z; positions[(i * 2 + 1) * 3 + 0] = x; positions[(i * 2 + 1) * 3 + 1] = y + 0.03; positions[(i * 2 + 1) * 3 + 2] = z; var hue = (i / MAX_SEGMENTS) * 255; colors[(i * 2) * 4 + 0] = hue; colors[(i * 2) * 4 + 1] = 200; colors[(i * 2) * 4 + 2] = 255 - hue; colors[(i * 2) * 4 + 3] = 255; colors[(i * 2 + 1) * 4 + 0] = hue; colors[(i * 2 + 1) * 4 + 1] = 200; colors[(i * 2 + 1) * 4 + 2] = 255 - hue; colors[(i * 2 + 1) * 4 + 3] = 255; } // Wall-clock paced reveal via animateValue – same duration at 60, 120, // 240 Hz. Avoid hand-rolled `segments++` per render tick: that ties the // reveal speed to the frame rate. animateValue({ from: 0, to: MAX_SEGMENTS, duration: 4.0, curve: "linear", onUpdate: function (s) { var target = Math.floor(s); // Extend the buffer to cover any newly-visible segments. for (var i = written; i < target; i++) writeSegment(i); if (target > written) { mesh.writeVertices("position", 0, positions); mesh.writeVertices("color", 0, colors); written = target; } mesh.drawCount = target * 2; mesh.setBounds({ min: [-0.5, 0, -0.5], max: [0.5, Math.max(0.05, target * 0.01), 0.5] }); } }); ``` ## AI voice assistant (OpenAI Realtime) Full conversational AI with voice input/output and tool calling: ```javascript // Attach to: On Did Appear (on the assistant entity) var entity = scriptContext.sourceEvent.representationEntity; var stream = entity.createAudioStream({ sampleRate: 24000 }); var isResponding = false; var ws = websocket.connect("wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview", { headers: { "Authorization": "Bearer YOUR_KEY", "OpenAI-Beta": "realtime=v1" } }); ws.onOpen = function() { ws.send(JSON.stringify({ type: "session.update", session: { modalities: ["text", "audio"], input_audio_format: "pcm16", output_audio_format: "pcm16", voice: "ash", turn_detection: { type: "semantic_vad", eagerness: "medium", create_response: true, interrupt_response: true }, instructions: "You are a helpful AR assistant. Keep responses brief." } })); startListening(); }; ws.onMessage = function(event) { var msg = JSON.parse(event.data); if (msg.type === "response.created") { isResponding = true; scene.microphone.stop(); } if (msg.type === "response.audio.delta") { stream.append(msg.delta); } if (msg.type === "response.done") { isResponding = false; // Resume mic after audio finishes waitForDrain(function() { startListening(); }); } }; function startListening() { if (isResponding) return; scene.microphone.configure({ sampleRate: 24000, silenceThreshold: 0, highpassFrequency: 0 }); scene.microphone.onData = function(base64) { if (!isResponding) { ws.send(JSON.stringify({ type: "input_audio_buffer.append", audio: base64 })); } }; scene.microphone.start(); } function waitForDrain(callback) { if (stream.isDrained) { callback(); return; } setTimeout(function() { waitForDrain(callback); }, 100); } ``` --- --- url: /docs/scripting/api.md --- # API Reference JavaScript API for creating AR experiences. ## Math & Geometry * [Vector2](/scripting/api/vector2) - 2D vector for screen coordinates, UV mapping, and 2D math * [Vector3](/scripting/api/vector3) - 3D vector for positions, directions, and scales * [Vector4](/scripting/api/vector4) - 4D vector for quaternions, colors, and homogeneous coordinates * [Rotation](/scripting/api/rotation) - Rotation represented as a quaternion * [Transform](/scripting/api/transform) - 4x4 transformation matrix representing position, rotation, and scale * [BoundingBox](/scripting/api/boundingbox) - Axis-aligned bounding box for collision and visibility testing * [Ray](/scripting/api/ray) - A ray with origin and direction, useful for raycasting * [Color](/scripting/api/color) - RGBA color with components in 0-1 range ## Scene & Objects * [Scene](/scripting/api/scene) * [ObjectEntity](/scripting/api/objectentity) - Entity in the scene * [RepresentationEntity](/scripting/api/representationentity) - Visual representation of an entity (model, shape, etc.) * [Entity](/scripting/api/entity) - Generic entity within a model hierarchy (bones, groups, nested models) * [Experience](/scripting/api/experience) * [Environment](/scripting/api/environment) * [EnvironmentFeatures](/scripting/api/environmentfeatures) - Capability registry exposed at `environment.features`. * [AnchorData](/scripting/api/anchordata) * [CollisionCastHit](/scripting/api/collisioncasthit) - Result from scene.raycast() when ray intersects an entity or scene mesh * [ARRaycastResult](/scripting/api/arraycastresult) - Result from scene.raycastAR() when ray intersects real-world surfaces * [Kernel](/scripting/api/kernel) - Opaque handle to a compiled GPU compute kernel. Returned by `Kernel.fromSource(...)` / `Kernel.fromAsset(...)`. Two dispatch entry points: `kernel.run({...})` for standalone compute (texture / buffer I/O, no mesh involvement) and `mesh.runCompute(kernel, options)` when the kernel writes mesh-attribute buffers. * [Texture](/scripting/api/texture) - GPU-only / runtime-managed texture. Backed by an `ARXDynamicTexture` (today's only constructible kind from JS is `.computeBacked`; sibling kinds – image, video, renderTarget, cameraFeed – wire up alongside material-parameter binding in a follow-up runtime version). * [TextureBinding](/scripting/api/texturebinding) - Marker token returned by `texture.read` / `texture.write`. Carries the backing texture and the role so dispatch knows whether to bind the texture as a kernel read source or write target. * [Buffer](/scripting/api/buffer) - Raw GPU buffer wrapping a typed scratch region for compute kernels. Use for things meshes + textures don't model: atomic counters, lookup tables (e.g. marching cubes' triangle / edge tables), per-particle state, scratch arrays between kernel passes. ## User Interface * [UIView](/scripting/api/uiview) - A view node inside a UI panel. ## Object Creation * [Anchor](/scripting/api/anchor) - Anchor types for object placement * [ObjectTraits](/scripting/api/objecttraits) - Builder for object traits (transform, materials, adjustments) Use with ObjectDescriptor.traits() to configure object properties * [ObjectDescriptor](/scripting/api/objectdescriptor) - Descriptor for creating scene objects dynamically * [Material](/scripting/api/material) - Chainable builder for an inline material. Six kinds: `unlit`, `pbr`, `occlusion`, `customShader`, `materialX`, `video`. Use the matching factory (`Material.unlit({...})`, etc.) or `new Material(kind)`. Properties map to the underlying material data model with web-standard names: - Colors: `color`, `emissive`, `sheenColor`, `specularColor` – flat tint. - Maps: `map`, `emissiveMap`, `roughnessMap`, `metalnessMap`, `normalMap`, `aoMap`, `specularMap`, `sheenMap`, `clearcoatMap`, `alphaMap` – textures accept either a URL/asset-id string or `{ texture, scale }`. - Scalars: `roughness`, `metalness`, `clearcoat`, `opacity`, `emissiveIntensity`. - Rendering: `opaque`, `transparent`, `opacityThreshold`, `blendMode`, `faceCulling`, `wireframe`, `writesDepth`, `readsDepth`. * [Mesh](/scripting/api/mesh) beta - Scriptable dynamic mesh with CPU-driven vertex and index updates. ## Audio * [AudioStream](/scripting/api/audiostream) - Streaming audio player for real-time audio playback Used for continuous audio streams like OpenAI Realtime API * [Microphone](/scripting/api/microphone) - Microphone audio streaming Access via `scene.microphone` ## Networking * [HTTPClient](/scripting/api/httpclient) - HTTP client for making network requests Supports GET, POST, and custom requests with authentication * [WebSocket](/scripting/api/websocket) - WebSocket connection for real-time communication * [WebSocketManager](/scripting/api/websocketmanager) - WebSocket connection manager Access via `websocket.connect()` ## Animations * [EntityAnimation](/scripting/api/entityanimation) - Entity animation factory - create animation descriptors for use with entity.play() * [ValueAnimation](/scripting/api/valueanimation) - Value animations with spring and curve timing ## Multi-user * [SharedActivity](/scripting/api/sharedactivity) - Shared activity state for multi-user sessions (SharePlay, WebSocket, etc.) ## Functions * [wait()](/scripting/api/functions#wait) - Wait for specified seconds * [animateValue()](/scripting/api/functions#animateValue) - Create and start a value animation Automatically interpolates between from/to values based on their type. Supports numbers, Vector2, Vector3, Vector4, Color, and Rotation. Rotations use spherical interpolation (slerp) automatically. Animations clean up automatically when complete. For infinite animations, call destroy() to stop, or they clean up when the scene ends. * [createBox()](/scripting/api/functions#createBox) - Create a box primitive descriptor * [createSphere()](/scripting/api/functions#createSphere) - Create a sphere primitive * [createPlane()](/scripting/api/functions#createPlane) - Create a plane primitive * [createModel()](/scripting/api/functions#createModel) - Create a 3D model object descriptor * [createMedia()](/scripting/api/functions#createMedia) - Create a visual media object (image, video, or animated GIF) * [createImage()](/scripting/api/functions#createImage) - Create an image * [createVideo()](/scripting/api/functions#createVideo) - Create a video * [createAnimatedGif()](/scripting/api/functions#createAnimatedGif) - Create an animated GIF * [createContainer()](/scripting/api/functions#createContainer) - Create a container/composition * [createPanel()](/scripting/api/functions#createPanel) - Build a UI panel descriptor wrapping the given root view. Pass to `scene.createEntity(panel)` to instantiate. * [createMesh()](/scripting/api/functions#createMesh) beta - Create a scriptable dynamic mesh with CPU-driven vertex/index updates. Returns an ObjectDescriptor – pass to `scene.createEntity(...)`. After the entity loads, reach the mesh via `entity.representation.mesh` and write buffer data with `mesh.writeVertices(...)` / `mesh.writeIndices(...)`. Bytes flow straight from `Float32Array` / `Uint32Array` into the GPU-side buffer – single memcpy per call, no per-element overhead. --- --- url: /docs/scripting/api/vector2.md description: 'Vector2 – 2D vector for screen coordinates, UV mapping, and 2D math' --- # Vector2 2D vector for screen coordinates, UV mapping, and 2D math ## Properties ### x * **Type:** number * X component ### y * **Type:** number * Y component ## Methods ### create() ```javascript create(x: number, y: number): Vector2 ``` Create a Vector2 **Parameters:** * `x` (number) - X component * `y` (number) - Y component **Returns:** [Vector2](/scripting/api/vector2) ### distance() ```javascript distance(v1: Vector2, v2: Vector2): number ``` Distance between two vectors **Parameters:** * `v1` ([Vector2](/scripting/api/vector2)) * `v2` ([Vector2](/scripting/api/vector2)) **Returns:** number ### dot() ```javascript dot(v1: Vector2, v2: Vector2): number ``` Dot product of two vectors **Parameters:** * `v1` ([Vector2](/scripting/api/vector2)) * `v2` ([Vector2](/scripting/api/vector2)) **Returns:** number ### length() ```javascript length(v: Vector2): number ``` Length of a vector **Parameters:** * `v` ([Vector2](/scripting/api/vector2)) **Returns:** number ### lerp() ```javascript lerp(v1: Vector2, v2: Vector2, t: number): Vector2 ``` Linearly interpolate between two vectors **Parameters:** * `v1` ([Vector2](/scripting/api/vector2)) - Start vector * `v2` ([Vector2](/scripting/api/vector2)) - End vector * `t` (number) - Interpolation factor (0-1) **Returns:** [Vector2](/scripting/api/vector2) ### normalize() ```javascript normalize(v: Vector2): Vector2 ``` Normalize a vector (unit length) **Parameters:** * `v` ([Vector2](/scripting/api/vector2)) **Returns:** [Vector2](/scripting/api/vector2) ### one() ```javascript one(): Vector2 ``` One vector (1, 1) **Returns:** [Vector2](/scripting/api/vector2) ### zero() ```javascript zero(): Vector2 ``` Zero vector (0, 0) **Returns:** [Vector2](/scripting/api/vector2) --- --- url: /docs/scripting/api/vector3.md description: 'Vector3 – 3D vector for positions, directions, and scales' --- # Vector3 3D vector for positions, directions, and scales ## Properties ### x * **Type:** number * X component ### y * **Type:** number * Y component ### z * **Type:** number * Z component ## Methods ### add() ```javascript add(other: Vector3): Vector3 ``` Add two vectors **Parameters:** * `other` ([Vector3](/scripting/api/vector3)) - Vector to add **Returns:** [Vector3](/scripting/api/vector3) ### angleBetween() ```javascript angleBetween(v1: Vector3, v2: Vector3): number ``` Angle between two vectors in radians **Parameters:** * `v1` ([Vector3](/scripting/api/vector3)) * `v2` ([Vector3](/scripting/api/vector3)) **Returns:** number ### angleTo() ```javascript angleTo(other: Vector3): number ``` Calculate angle to another vector **Parameters:** * `other` ([Vector3](/scripting/api/vector3)) - Other vector **Returns:** number ### create() ```javascript create(x: number, y: number, z: number): Vector3 ``` Create a Vector3 **Parameters:** * `x` (number) - X component * `y` (number) - Y component * `z` (number) - Z component **Returns:** [Vector3](/scripting/api/vector3) ### cross() ```javascript cross(v1: Vector3, v2: Vector3): Vector3 ``` Cross product of two vectors **Parameters:** * `v1` ([Vector3](/scripting/api/vector3)) * `v2` ([Vector3](/scripting/api/vector3)) **Returns:** [Vector3](/scripting/api/vector3) ### cross() ```javascript cross(other: Vector3): Vector3 ``` Calculate cross product with another vector **Parameters:** * `other` ([Vector3](/scripting/api/vector3)) - Other vector **Returns:** [Vector3](/scripting/api/vector3) ### distance() ```javascript distance(v1: Vector3, v2: Vector3): number ``` Distance between two vectors **Parameters:** * `v1` ([Vector3](/scripting/api/vector3)) * `v2` ([Vector3](/scripting/api/vector3)) **Returns:** number ### distanceTo() ```javascript distanceTo(other: Vector3): number ``` Calculate distance to another vector **Parameters:** * `other` ([Vector3](/scripting/api/vector3)) - Other vector **Returns:** number ### divide() ```javascript divide(scalar: number): Vector3 ``` Divide vector by scalar **Parameters:** * `scalar` (number) - Scalar value **Returns:** [Vector3](/scripting/api/vector3) ### dot() ```javascript dot(v1: Vector3, v2: Vector3): number ``` Dot product of two vectors **Parameters:** * `v1` ([Vector3](/scripting/api/vector3)) * `v2` ([Vector3](/scripting/api/vector3)) **Returns:** number ### dot() ```javascript dot(other: Vector3): number ``` Calculate dot product with another vector **Parameters:** * `other` ([Vector3](/scripting/api/vector3)) - Other vector **Returns:** number ### length() ```javascript length(v: Vector3): number ``` Length of a vector **Parameters:** * `v` ([Vector3](/scripting/api/vector3)) **Returns:** number ### length() ```javascript length(): number ``` Get length of vector **Returns:** number ### lerp() ```javascript lerp(v1: Vector3, v2: Vector3, t: number): Vector3 ``` Linearly interpolate between two vectors **Parameters:** * `v1` ([Vector3](/scripting/api/vector3)) - Start vector * `v2` ([Vector3](/scripting/api/vector3)) - End vector * `t` (number) - Interpolation factor (0-1) **Returns:** [Vector3](/scripting/api/vector3) ### lerp() ```javascript lerp(other: Vector3, t: number): Vector3 ``` Linearly interpolate to another vector **Parameters:** * `other` ([Vector3](/scripting/api/vector3)) - Target vector * `t` (number) - Interpolation factor (0-1) **Returns:** [Vector3](/scripting/api/vector3) ### multiply() ```javascript multiply(scalar: number): Vector3 ``` Multiply vector by scalar **Parameters:** * `scalar` (number) - Scalar value **Returns:** [Vector3](/scripting/api/vector3) ### normalize() ```javascript normalize(v: Vector3): Vector3 ``` Normalize a vector (unit length) **Parameters:** * `v` ([Vector3](/scripting/api/vector3)) **Returns:** [Vector3](/scripting/api/vector3) ### normalize() ```javascript normalize(): Vector3 ``` Get normalized vector **Returns:** [Vector3](/scripting/api/vector3) ### one() ```javascript one(): Vector3 ``` One vector (1, 1, 1) **Returns:** [Vector3](/scripting/api/vector3) ### subtract() ```javascript subtract(other: Vector3): Vector3 ``` Subtract a vector from this vector **Parameters:** * `other` ([Vector3](/scripting/api/vector3)) - Vector to subtract **Returns:** [Vector3](/scripting/api/vector3) ### zero() ```javascript zero(): Vector3 ``` Zero vector (0, 0, 0) **Returns:** [Vector3](/scripting/api/vector3) --- --- url: /docs/scripting/api/vector4.md description: 'Vector4 – 4D vector for quaternions, colors, and homogeneous coordinates' --- # Vector4 4D vector for quaternions, colors, and homogeneous coordinates ## Properties ### w * **Type:** number * W component ### x * **Type:** number * X component ### y * **Type:** number * Y component ### z * **Type:** number * Z component ## Methods ### create() ```javascript create(x: number, y: number, z: number, w: number): Vector4 ``` Create a Vector4 **Parameters:** * `x` (number) - X component * `y` (number) - Y component * `z` (number) - Z component * `w` (number) - W component **Returns:** [Vector4](/scripting/api/vector4) --- --- url: /docs/scripting/api/rotation.md description: Rotation – Rotation represented as a quaternion --- # Rotation Rotation represented as a quaternion Can be created from euler angles or quaternion components. Use `Rotation.slerp()` for smooth interpolation between rotations. **Example:** ```javascript // From euler angles (radians) var rot = Rotation(0, Math.PI / 2, 0); // 90° around Y // From quaternion var rot = Rotation.quaternion(0, 0.707, 0, 0.707); // Interpolate between rotations var mid = Rotation.slerp(rotA, rotB, 0.5); ``` ## Properties ### eulerAngles * **Type:** [Vector3](/scripting/api/vector3) * Euler angles in radians (x, y, z) ### quaternion * **Type:** [Vector4](/scripting/api/vector4) * Quaternion components (x, y, z, w) ## Methods ### create() ```javascript create(): Rotation ``` Create identity rotation (no rotation) **Returns:** [Rotation](/scripting/api/rotation) ### inverse() ```javascript inverse(r: Rotation): Rotation ``` Get the inverse (conjugate) of a rotation **Parameters:** * `r` ([Rotation](/scripting/api/rotation)) - Rotation to invert **Returns:** [Rotation](/scripting/api/rotation) ### inverse() ```javascript inverse(): Rotation ``` Get the inverse of this rotation **Returns:** [Rotation](/scripting/api/rotation) ### multiply() ```javascript multiply(r1: Rotation, r2: Rotation): Rotation ``` Multiply two rotations (compose) **Parameters:** * `r1` ([Rotation](/scripting/api/rotation)) - First rotation * `r2` ([Rotation](/scripting/api/rotation)) - Second rotation (applied after r1) **Returns:** [Rotation](/scripting/api/rotation) ### multiply() ```javascript multiply(other: Rotation): Rotation ``` Multiply (compose) with another rotation **Parameters:** * `other` ([Rotation](/scripting/api/rotation)) - Rotation to apply after this one **Returns:** [Rotation](/scripting/api/rotation) ### slerp() ```javascript slerp(r1: Rotation, r2: Rotation, t: number): Rotation ``` Spherically interpolate between two rotations **Parameters:** * `r1` ([Rotation](/scripting/api/rotation)) - Start rotation * `r2` ([Rotation](/scripting/api/rotation)) - End rotation * `t` (number) - Interpolation factor (0-1) **Returns:** [Rotation](/scripting/api/rotation) ### slerp() ```javascript slerp(other: Rotation, t: number): Rotation ``` Spherically interpolate to another rotation **Parameters:** * `other` ([Rotation](/scripting/api/rotation)) - Target rotation * `t` (number) - Interpolation factor (0-1) **Returns:** [Rotation](/scripting/api/rotation) --- --- url: /docs/scripting/api/transform.md description: >- Transform – 4x4 transformation matrix representing position, rotation, and scale --- # Transform 4x4 transformation matrix representing position, rotation, and scale **Example:** ```javascript // Create with specific values var t = Transform({ position: Vector3(0, 1, 0), rotation: Rotation(0, Math.PI, 0), scale: Vector3(1, 1, 1) }); // Get entity's transform var worldT = entity.worldTransform; // Combine transforms var combined = Transform.multiply(parentT, childT); // Transform a point var worldPoint = Transform.transformPoint(entity.transform, localPoint); ``` ## Properties ### position * **Type:** [Vector3](/scripting/api/vector3) * Position vector ### rotation * **Type:** [Rotation](/scripting/api/rotation) * Rotation quaternion ### scale * **Type:** [Vector3](/scripting/api/vector3) * Scale vector ## Methods ### create() ```javascript create(): Transform ``` Create identity transform (position: 0,0,0, rotation: identity, scale: 1,1,1) **Returns:** [Transform](/scripting/api/transform) ### createWith() ```javascript createWith(position: Vector3, rotation: Rotation, scale: Vector3): Transform ``` Create transform with specific values **Parameters:** * `position` ([Vector3](/scripting/api/vector3)) - Position vector * `rotation` ([Rotation](/scripting/api/rotation)) - Rotation quaternion * `scale` ([Vector3](/scripting/api/vector3)) - Scale vector **Returns:** [Transform](/scripting/api/transform) ### inverse() ```javascript inverse(transform: Transform): Transform ``` Get inverse transform **Parameters:** * `transform` ([Transform](/scripting/api/transform)) - Transform to invert **Returns:** [Transform](/scripting/api/transform) ### inverse() ```javascript inverse(): Transform ``` Get inverse transform **Returns:** [Transform](/scripting/api/transform) ### multiply() ```javascript multiply(t1: Transform, t2: Transform): Transform ``` Multiply two transforms (combine transformations) **Parameters:** * `t1` ([Transform](/scripting/api/transform)) - First transform * `t2` ([Transform](/scripting/api/transform)) - Second transform **Returns:** [Transform](/scripting/api/transform) ### multiply() ```javascript multiply(other: Transform): Transform ``` Multiply with another transform **Parameters:** * `other` ([Transform](/scripting/api/transform)) - Other transform **Returns:** [Transform](/scripting/api/transform) ### transformPoint() ```javascript transformPoint(transform: Transform, point: Vector3): Vector3 ``` Transform a point by this matrix **Parameters:** * `transform` ([Transform](/scripting/api/transform)) - Transform to apply * `point` ([Vector3](/scripting/api/vector3)) - Point to transform **Returns:** [Vector3](/scripting/api/vector3) ### transformPoint() ```javascript transformPoint(point: Vector3): Vector3 ``` Transform a point **Parameters:** * `point` ([Vector3](/scripting/api/vector3)) - Point to transform **Returns:** [Vector3](/scripting/api/vector3) ### transformVector() ```javascript transformVector(transform: Transform, vector: Vector3): Vector3 ``` Transform a direction vector (ignores position) **Parameters:** * `transform` ([Transform](/scripting/api/transform)) - Transform to apply * `vector` ([Vector3](/scripting/api/vector3)) - Direction vector **Returns:** [Vector3](/scripting/api/vector3) ### transformVector() ```javascript transformVector(vector: Vector3): Vector3 ``` Transform a vector **Parameters:** * `vector` ([Vector3](/scripting/api/vector3)) - Vector to transform **Returns:** [Vector3](/scripting/api/vector3) --- --- url: /docs/scripting/api/boundingbox.md description: BoundingBox – Axis-aligned bounding box for collision and visibility testing --- # BoundingBox Axis-aligned bounding box for collision and visibility testing **Example:** ```javascript var box = entity.boundingBox; var center = box.center; var size = box.extents; // Check if point is inside if (BoundingBox.contains(box, point)) { console.log('Point inside!'); } // Check intersection if (BoundingBox.intersects(box1, box2)) { console.log('Boxes overlap!'); } ``` ## Properties ### boundingRadius * **Type:** number * Radius of bounding sphere ### center * **Type:** [Vector3](/scripting/api/vector3) * Center point of the box ### extents * **Type:** [Vector3](/scripting/api/vector3) * Half-size extents from center ### isEmpty * **Type:** boolean * Whether the box has zero volume ### max * **Type:** [Vector3](/scripting/api/vector3) * Maximum corner of the box ### min * **Type:** [Vector3](/scripting/api/vector3) * Minimum corner of the box ## Methods ### contains() ```javascript contains(box: BoundingBox, point: Vector3): boolean ``` Check if a point is inside the box **Parameters:** * `box` ([BoundingBox](/scripting/api/boundingbox)) * `point` ([Vector3](/scripting/api/vector3)) **Returns:** boolean ### contains() ```javascript contains(point: Vector3): boolean ``` Check if point is inside bounding box **Parameters:** * `point` ([Vector3](/scripting/api/vector3)) - Point to check **Returns:** boolean ### containsBox() ```javascript containsBox(box1: BoundingBox, box2: BoundingBox): boolean ``` Check if one box fully contains another **Parameters:** * `box1` ([BoundingBox](/scripting/api/boundingbox)) * `box2` ([BoundingBox](/scripting/api/boundingbox)) **Returns:** boolean ### containsBox() ```javascript containsBox(other: BoundingBox): boolean ``` Check if another bounding box is fully contained **Parameters:** * `other` ([BoundingBox](/scripting/api/boundingbox)) - Other bounding box **Returns:** boolean ### create() ```javascript create(min: Vector3, max: Vector3): BoundingBox ``` Create a bounding box from min/max corners **Parameters:** * `min` ([Vector3](/scripting/api/vector3)) * `max` ([Vector3](/scripting/api/vector3)) **Returns:** [BoundingBox](/scripting/api/boundingbox) ### createEmpty() ```javascript createEmpty(): BoundingBox ``` Create an empty bounding box **Returns:** [BoundingBox](/scripting/api/boundingbox) ### intersects() ```javascript intersects(box1: BoundingBox, box2: BoundingBox): boolean ``` Check if two boxes intersect **Parameters:** * `box1` ([BoundingBox](/scripting/api/boundingbox)) * `box2` ([BoundingBox](/scripting/api/boundingbox)) **Returns:** boolean ### intersects() ```javascript intersects(other: BoundingBox): boolean ``` Check if bounding boxes intersect **Parameters:** * `other` ([BoundingBox](/scripting/api/boundingbox)) - Other bounding box **Returns:** boolean --- --- url: /docs/scripting/api/ray.md description: 'Ray – A ray with origin and direction, useful for raycasting' --- # Ray A ray with origin and direction, useful for raycasting ## Properties ### direction * **Type:** [Vector3](/scripting/api/vector3) * Normalized ray direction ### origin * **Type:** [Vector3](/scripting/api/vector3) * Ray starting point in world space --- --- url: /docs/scripting/api/color.md description: Color – RGBA color with components in 0-1 range --- # Color RGBA color with components in 0-1 range **Example:** ```javascript // From RGBA var c = Color(1, 0, 0, 1); // red // From hex var c = Color.hex("#FF5500"); // Preset colors var c = Color.red(); // Interpolate var mid = Color.lerp(Color.red(), Color.blue(), 0.5); ``` ## Properties ### a * **Type:** number * Alpha component (0-1) ### b * **Type:** number * Blue component (0-1) ### g * **Type:** number * Green component (0-1) ### r * **Type:** number * Red component (0-1) ## Methods ### create() ```javascript create(r: number, g: number, b: number, a: number): Color ``` Create a color from RGBA values **Parameters:** * `r` (number) - Red (0-1) * `g` (number) - Green (0-1) * `b` (number) - Blue (0-1) * `a` (number) - Alpha (0-1) **Returns:** [Color](/scripting/api/color) ### lerp() ```javascript lerp(c1: Color, c2: Color, t: number): Color ``` Linearly interpolate between two colors **Parameters:** * `c1` ([Color](/scripting/api/color)) - Start color * `c2` ([Color](/scripting/api/color)) - End color * `t` (number) - Interpolation factor (0-1) **Returns:** [Color](/scripting/api/color) ### lerp() ```javascript lerp(other: Color, t: number): Color ``` Linearly interpolate to another color **Example:** ```javascript var blended = Color.red().lerp(Color.blue(), 0.5); ``` **Parameters:** * `other` ([Color](/scripting/api/color)) - Target color * `t` (number) - Interpolation factor (0-1) **Returns:** [Color](/scripting/api/color) --- --- url: /docs/scripting/api/scene.md description: 'Scene API reference for Scenery scripting: methods, properties, and examples.' --- # Scene ## Properties ### cameraTransform * **Type:** [Transform](/scripting/api/transform) * Current camera transform in world space ### id * **Type:** string * Scene identifier ### microphone * **Type:** [Microphone](/scripting/api/microphone) * Microphone audio streaming ### time * **Type:** number * Time elapsed since scene started (seconds) ### viewportSize * **Type:** [Vector2](/scripting/api/vector2) * Viewport size in points (iOS only) ## Methods ### \_removeEntity() ```javascript _removeEntity(): void ``` Remove an entity from the scene **Returns:** void ### animateBy() ```javascript animateBy(objectOrId: string | ObjectEntity, properties: Object, duration: number, options?: Object): Promise ``` Animate object by relative values **Parameters:** * `objectOrId` (string | [ObjectEntity](/scripting/api/objectentity)) - ObjectEntity or entity ID * `properties` (Object) - Relative change values * `duration` (number) - Animation duration in seconds * `options` (Object) *(optional)* - Animation options **Returns:** Promise\ ### animateFromTo() ```javascript animateFromTo(objectOrId: string | ObjectEntity, fromProperties: Object, toProperties: Object, duration: number, options?: Object): Promise ``` Animate object from starting to target properties **Parameters:** * `objectOrId` (string | [ObjectEntity](/scripting/api/objectentity)) - ObjectEntity or entity ID * `fromProperties` (Object) - Starting properties * `toProperties` (Object) - Target properties * `duration` (number) - Animation duration in seconds * `options` (Object) *(optional)* - Animation options **Returns:** Promise\ ### animateTo() ```javascript animateTo(objectOrId: string | ObjectEntity, properties: Object, duration: number, options?: Object): Promise ``` Animate object to target properties **Parameters:** * `objectOrId` (string | [ObjectEntity](/scripting/api/objectentity)) - ObjectEntity or entity ID * `properties` (Object) - Target properties (position, rotation, scale, opacity) * `duration` (number) - Animation duration in seconds * `options` (Object) *(optional)* - Animation options (timingFunction, delay, repeatCount, etc.) **Returns:** Promise\ ### createAudioStream() ```javascript createAudioStream(options: Array): AudioStream ``` Create a streaming audio player **Parameters:** * `options` (Array\) - Stream configuration (sampleRate, channels, format, elementId, representationId) **Returns:** [AudioStream](/scripting/api/audiostream) ### createEntity() ```javascript createEntity(descriptor: ObjectDescriptor): Promise ``` Create an object in the scene. **Example:** ```javascript const box = createBox(0.3, 0.3, 0.3) .anchor(Anchor.position(0, 0, -1)) .name('hero-box'); const entity = await scene.createEntity(box); ``` **Parameters:** * `descriptor` ([ObjectDescriptor](/scripting/api/objectdescriptor)) - Object descriptor from createBox, createSphere, createModel, createPlane, createMesh, etc. Configure anchor, traits, name, etc. via the descriptor's builder methods (`.anchor(...)`, `.traits(...)`, `.name(...)`), not as a second argument. **Returns:** Promise\ ### findEntity() ```javascript findEntity(query: any): ObjectEntity ``` Find an entity by ID or name **Example:** ```javascript // By ID var entity = scene.findEntity("ABC123"); // By ID (explicit) var entity = scene.findEntity({ id: "ABC123" }); // By name var entity = scene.findEntity({ name: "My Cool Box" }); ``` **Parameters:** * `query` (any) - Entity ID string, or object with { id: "..." } or { name: "..." } **Returns:** [ObjectEntity](/scripting/api/objectentity) ### getAnchors() ```javascript getAnchors(kind?: string): Array ``` Get detected AR anchors **Parameters:** * `kind` (string) *(optional)* - Filter by anchor type ("plane", "image", "face", etc.), or nil for all **Returns:** Array\ ### getCustomEvents() ```javascript getCustomEvents(): Array ``` List custom-trigger scene events on the current segment. Useful for debug panels that enumerate triggerable events at runtime. Each entry has the event's `id`, optional `name` (omitted when unset), and `isEnabled` flag. System events (tap, render, screenTap, schedule, etc.) are not included – those are handled by the engine, not scripted. **Example:** ```javascript scene.getCustomEvents().forEach(function(evt) { console.log(evt.id, evt.name, evt.isEnabled); }); ``` **Returns:** Array\ ### getVariable() ```javascript getVariable(id: string): any ``` Get a scene-scoped variable **Parameters:** * `id` (string) - Variable identifier **Returns:** any ### off() ```javascript off(eventId: string): void ``` Remove a scene event listener **Example:** ```javascript var eventId = scene.on('render', function() {}); // Later... scene.off(eventId); ``` **Parameters:** * `eventId` (string) - Event ID returned from on() **Returns:** void ### on() ```javascript on(type: string, options?: Object | function, listener?: function): string ``` Register a scene event listener **Example:** ```javascript scene.on('start', function() { console.log('Scene started!'); }); ``` **Parameters:** * `type` (string) - Event type: "start", "render", "screenTap", "variableChange", "schedule" * `options` (Object | function) *(optional)* - Event options or callback * `listener` (function) *(optional)* - Callback if options provided **Returns:** string ### once() ```javascript once(type: string, options?: Object | function, listener?: function): string ``` Register a one-time scene event listener **Example:** ```javascript scene.once('start', function() { console.log('Scene started (once)!'); }); ``` **Parameters:** * `type` (string) - Event type * `options` (Object | function) *(optional)* - Event options or callback * `listener` (function) *(optional)* - Callback if options provided **Returns:** string ### playAnimation() ```javascript playAnimation(objectOrId: string | ObjectEntity, animation: Object, representationId?: string): Promise ``` Play animation on object **Parameters:** * `objectOrId` (string | [ObjectEntity](/scripting/api/objectentity)) - ObjectEntity or entity ID * `animation` (Object) - Animation data * `representationId` (string) *(optional)* - Optional representation ID **Returns:** Promise\ ### playAudioBuffer() ```javascript playAudioBuffer(base64: string, options?: Object): Promise ``` Play audio from PCM buffer data **Example:** ```javascript // Play at user's position (non-spatial) await scene.playAudioBuffer(base64Data, { sampleRate: 24000 }); // Play spatially on an entity await scene.playAudioBuffer(base64Data, { sampleRate: 24000, elementId: box.id }); ``` **Parameters:** * `base64` (string) - Base64-encoded PCM audio data * `options` (Object) *(optional)* - Buffer and playback options * `options.sampleRate` (number) *(optional)* - Sample rate in Hz * `options.channels` (number) *(optional)* - Number of channels (1 = mono, 2 = stereo) * `options.format` (string) *(optional)* - Sample format: "pcm16" or "float32" * `options.gain` (number) *(optional)* - Volume gain (0.0 to 4.0, default 1.0) * `options.elementId` (string) *(optional)* - Target element ID (optional, uses POV if omitted) * `options.representationId` (string) *(optional)* - Target representation ID (optional) **Returns:** Promise\ ### projectToScreen() ```javascript projectToScreen(worldPosition: Vector3): Vector2 ``` Convert world position to normalized screen coordinates (iOS only) **Parameters:** * `worldPosition` ([Vector3](/scripting/api/vector3)) - Position in world space **Returns:** [Vector2](/scripting/api/vector2) ### raycast() ```javascript raycast(origin: Vector3, direction: Vector3): Array ``` Cast ray through scene entities and scene mesh **Parameters:** * `origin` ([Vector3](/scripting/api/vector3)) - Ray origin in world space * `direction` ([Vector3](/scripting/api/vector3)) - Ray direction (will be normalized) **Returns:** Array\ ### raycastAR() ```javascript raycastAR(origin: Vector3, direction: Vector3, target: string, alignment: string): Array ``` Cast ray against AR planes (iOS only) **Parameters:** * `origin` ([Vector3](/scripting/api/vector3)) - Ray origin in world space * `direction` ([Vector3](/scripting/api/vector3)) - Ray direction (will be normalized) * `target` (string) - Target type - "plane", "planeExtended", "surface" * `alignment` (string) - Surface alignment - "horizontal", "vertical", "any" **Returns:** Array\ ### runAction() ```javascript runAction(action: Action): Promise ``` Run an action and wait for completion. **Parameters:** * `action` ([Action](/scripting/api/action)) - Action to run. **Returns:** Promise\ ### screenToRay() ```javascript screenToRay(normalizedPosition: Vector2): Ray ``` Convert normalized screen coordinates to a ray for raycasting (iOS only) **Parameters:** * `normalizedPosition` ([Vector2](/scripting/api/vector2)) - Screen position in normalized coordinates (0-1) **Returns:** [Ray](/scripting/api/ray) ### setVariable() ```javascript setVariable(id: string, value: any, options: any): void ``` Set a scene-scoped variable (cleared on scene transition) **Parameters:** * `id` (string) - Variable identifier * `value` (any) - Value to store (number, string, boolean, Vector3, Color, etc.) * `options` (any) - Optional type hint { type: "vector3" | "color" | ... } **Returns:** void ### triggerCustomEvent() ```javascript triggerCustomEvent(query: any): void ``` Trigger one or more custom-trigger events. or `{ name: "..." }`. Multiple events can share a name – passing `{ name: ... }` fires all matches. **Example:** ```javascript scene.triggerCustomEvent("evt-abc"); // by ID scene.triggerCustomEvent({ id: "evt-abc" }); // by ID, explicit scene.triggerCustomEvent({ name: "spawn-box" }); // by name (fires all matches) ``` **Parameters:** * `query` (any) - Lookup form. Pass a string ID, `{ id: "..." }`, **Returns:** void ## Internal API dev only ::: warning These methods are internal implementation details and not part of the public API. ::: ### buildParameterValue() internal ```javascript buildParameterValue(value: any, typeHint?: any): any ``` Internal: build an `ARXObjectMaterialInputParameter.value(...)` JSON payload from a JS value + optional type hint. Reuses the same `JSValue.toARXValue(typeHint:)` conversion as `setVariable` so the type-inference vocabulary is identical. Authors call `Material.setParameter(name, value, typeHint?)`; the runtime wraps this bridge to stuff the result into `customShadersOptions.parameters` or `materialXOptions.parameters` depending on the material's kind. **Parameters:** * `value` (any) * `typeHint` (any) *(optional)* **Returns:** any ### createAction() internal ```javascript createAction(jsonValue: any): Action ``` **Parameters:** * `jsonValue` (any) **Returns:** [Action](/scripting/api/action) ### createComputeBuffer() internal ```javascript createComputeBuffer(options: { elementType: "float32" | "uint32" | "uint8" | "atomic_uint", length: number, initialValue?: number }): Buffer ``` Internal bridge – JS authors use `Buffer.float32(...)` / `Buffer.uint32(...)` / `Buffer.uint8(...)` / `Buffer.atomic(...)`. Allocates a CPU-visible GPU buffer so `buffer.writeSync(...)` and `buffer.readSync()` work without an explicit copy. Length is element count, not bytes. **Parameters:** * `options` ({ elementType: "float32" | "uint32" | "uint8" | "atomic\_uint", length: number, initialValue?: number }) **Returns:** [Buffer](/scripting/api/buffer) ### createDynamicTexture() internal ```javascript createDynamicTexture(options: { kind, semantic?, ...kind-specific }, callback: (result, error)): void ``` Internal bridge – JS authors use `Texture.compute({...})` (and future sibling factories like `Texture.image(...)`). Callback-based + `_promisify`-wrapped on the JS side so authors get a `Promise`. Async because the asset-provider funnel handles all kinds uniformly (some kinds need real async work – image / video / etc.); compute-backed rides through the same funnel for cache registration so material binding resolves to the same manager later. **Parameters:** * `options` ({ kind, semantic?, ...kind-specific }) * `callback` ((result, error)) - per the `_promisify` contract. **Returns:** void ### createEntity() internal ```javascript createEntity(jsonValue: any, callback: any): void ``` **Parameters:** * `jsonValue` (any) * `callback` (any) **Returns:** void ### createValueAnimation() internal ```javascript createValueAnimation(options: any): ValueAnimation ``` **Parameters:** * `options` (any) **Returns:** [ValueAnimation](/scripting/api/valueanimation) ### loadComputeKernel() internal ```javascript loadComputeKernel(options: { source?: string, assetId?: string, functionName: string }, callback: (result, error)): void ``` Internal bridge – JS authors use `Kernel.fromSource({...})` / `Kernel.fromAsset({...})`. The shape on scene stays minimal: one callback-based entrypoint that the runtime `_promisify` wraps into a Promise. **Parameters:** * `options` ({ source?: string, assetId?: string, functionName: string }) * `callback` ((result, error)) - per the `_promisify` contract. **Returns:** void ### materialDataById() internal ```javascript materialDataById(id: string): any ``` Internal: fetch raw material data from the experience material library by id. The runtime wraps this into `scene.getMaterial(id)` returning a hydrated `Material` instance suitable for `.clone()` / property edits. Authors call `scene.getMaterial(id)`, not this method directly. **Parameters:** * `id` (string) **Returns:** any ### playAudioBuffer() internal ```javascript playAudioBuffer(base64: string, options: Array, callback?: any): void ``` **Parameters:** * `base64` (string) * `options` (Array\) * `callback` (any) *(optional)* **Returns:** void ### runAction() internal ```javascript runAction(proxyAction: Action, callback: any): void ``` **Parameters:** * `proxyAction` ([Action](/scripting/api/action)) * `callback` (any) **Returns:** void ### subscribeToEvent() internal ```javascript subscribeToEvent(jsonValue: any, callback: any): string ``` **Parameters:** * `jsonValue` (any) * `callback` (any) **Returns:** string ### unsubscribeFromEvent() internal ```javascript unsubscribeFromEvent(eventId: string): void ``` **Parameters:** * `eventId` (string) **Returns:** void --- --- url: /docs/scripting/api/objectentity.md description: ObjectEntity – Entity in the scene --- # ObjectEntity Entity in the scene Access properties and representations, listen to events. **Example:** ```javascript var entity = scene.findEntity({ name: "My Box" }); entity.position = Vector3(0, 1, 0); entity.on('tap', function() { console.log('Tapped!'); }); ``` ## Properties ### id * **Type:** string * Unique entity identifier ### representation * **Type:** [RepresentationEntity](/scripting/api/representationentity) * Primary representation (visual/model) ## Methods ### animateBy() ```javascript animateBy(properties: Object, duration: number, options?: Object): Promise ``` Animate this entity's representation by relative values (additive animation) **Parameters:** * `properties` (Object) - Relative change values * `duration` (number) - Animation duration in seconds * `options` (Object) *(optional)* - Animation options **Returns:** Promise\ ### animateFromTo() ```javascript animateFromTo(fromProperties: Object, toProperties: Object, duration: number, options?: Object): Promise ``` Animate this entity's representation from starting to target properties **Parameters:** * `fromProperties` (Object) - Starting property values * `toProperties` (Object) - Target property values * `duration` (number) - Animation duration in seconds * `options` (Object) *(optional)* - Animation options **Returns:** Promise\ ### animateTo() ```javascript animateTo(properties: Object, duration: number, options?: Object): Promise ``` Animate this entity's representation to target properties **Parameters:** * `properties` (Object) - Target properties to animate to * `properties.position` ([Vector3](/scripting/api/vector3)) *(optional)* - Target position * `properties.rotation` ([Rotation](/scripting/api/rotation)) *(optional)* - Target rotation * `properties.scale` ([Vector3](/scripting/api/vector3)) *(optional)* - Target scale * `properties.transform` ([Transform](/scripting/api/transform)) *(optional)* - Target transform (overrides position/rotation/scale) * `properties.opacity` (number) *(optional)* - Target opacity (0-1) * `duration` (number) - Animation duration in seconds * `options` (Object) *(optional)* - Animation options * `options.timingFunction` (string | Object) *(optional)* - Easing: "linear", "easeIn", "easeOut", "easeInOut", or custom * `options.delay` (number) *(optional)* - Delay before animation starts (seconds) * `options.repeatCount` (number) *(optional)* - Number of times to repeat (-1 for infinite) * `options.repeatMode` (string) *(optional)* - "default", "autoReverse", or "cumulative" **Returns:** Promise\ ### createAudioStream() ```javascript createAudioStream(options?: Object): AudioStream ``` Create a streaming audio player on this entity **Parameters:** * `options` (Object) *(optional)* - Stream options **Returns:** [AudioStream](/scripting/api/audiostream) ### findRepresentation() ```javascript findRepresentation(query: any): RepresentationEntity ``` Find a representation by ID or name **Example:** ```javascript // By ID var rep = entity.findRepresentation("ABC123"); // By name var rep = entity.findRepresentation({ name: "Main Model" }); ``` **Parameters:** * `query` (any) - Representation ID string, or object with { id: "..." } or { name: "..." } **Returns:** [RepresentationEntity](/scripting/api/representationentity) ### off() ```javascript off(eventId: string): void ``` Remove an entity event listener **Example:** ```javascript var eventId = entity.on('tap', function() {}); // Later... entity.off(eventId); ``` **Parameters:** * `eventId` (string) - Event ID returned from on() **Returns:** void ### on() ```javascript on(type: string, options?: Object | function, listener?: function): string ``` Register an entity event listener **Example:** ```javascript entity.on('tap', function(event) { console.log('Entity tapped!'); }); ``` **Parameters:** * `type` (string) - Event type: "tap", "add", "collision", "distance", "lookAt", "mediaPlayback" * `options` (Object | function) *(optional)* - Event options or callback * `listener` (function) *(optional)* - Callback if options provided **Returns:** string ### once() ```javascript once(type: string, options?: Object | function, listener?: function): string ``` Register a one-time entity event listener **Example:** ```javascript entity.once('tap', function(event) { console.log('First tap only!'); }); ``` **Parameters:** * `type` (string) - Event type * `options` (Object | function) *(optional)* - Event options or callback * `listener` (function) *(optional)* - Callback if options provided **Returns:** string ### play() ```javascript play(animation: Animation): Promise ``` Play an animation on this entity's representation **Parameters:** * `animation` (Animation) - Animation object from Animation.to(), Animation.spin(), etc. **Returns:** Promise\ ### playAudio() ```javascript playAudio(source: string, options?: Object): Promise ``` Play audio asset on this entity **Parameters:** * `source` (string) - HTTPS URL or asset ID from project * `options` (Object) *(optional)* - Playback options **Returns:** Promise\ ### playAudioBuffer() ```javascript playAudioBuffer(base64: string, options?: Object): Promise ``` Play audio buffer on this entity **Parameters:** * `base64` (string) - Base64-encoded PCM audio data * `options` (Object) *(optional)* - Buffer and playback options **Returns:** Promise\ ### remove() ```javascript remove(): Promise ``` Remove this entity from the scene **Returns:** Promise\ ### setMaterial() ```javascript setMaterial(options?: Object): Promise ``` Set material on this entity's representation **Parameters:** * `options` (Object) *(optional)* - Material options, omit to clear **Returns:** Promise\ ### setMaterials() ```javascript setMaterials(materials: Array): Promise ``` Set materials on this entity's representation **Parameters:** * `materials` (Array\) - Array of material options **Returns:** Promise\ ### stopAnimations() ```javascript stopAnimations(options?: Object): Promise ``` Stop and remove animations from this entity's representation **Parameters:** * `options` (Object) *(optional)* - Options * `options.transitionDuration` (number) *(optional)* - Fade out duration in seconds **Returns:** Promise\ ### stopAudio() ```javascript stopAudio(): Promise ``` Stop audio playback on this entity **Returns:** Promise\ ### toggle() ```javascript toggle(enabled: boolean): Promise ``` Enable or disable this entity's representation **Parameters:** * `enabled` (boolean) - Enable (true) or disable (false) **Returns:** Promise\ ### toggleAnimations() ```javascript toggleAnimations(options?: Object): Promise ``` Toggle (pause/resume) animations on this entity's representation **Parameters:** * `options` (Object) *(optional)* - Options * `options.play` (boolean) *(optional)* - Explicit play (true) or pause (false), toggles if omitted * `options.animationIds` (Array\) *(optional)* - Specific animation IDs to toggle **Returns:** Promise\ ### waitUntilReady() ```javascript waitUntilReady(): Promise ``` Wait until this entity's primary representation has finished loading. Convenience that proxies to `entity.representation.waitUntilReady()`. Resolves immediately if there's no primary representation. **Returns:** Promise\ ## Internal API dev only ::: warning These methods are internal implementation details and not part of the public API. ::: ### subscribeToEvent() internal ```javascript subscribeToEvent(jsonValue: any, callback: any): string ``` **Parameters:** * `jsonValue` (any) * `callback` (any) **Returns:** string ### unsubscribeFromEvent() internal ```javascript unsubscribeFromEvent(eventId: string): void ``` **Parameters:** * `eventId` (string) **Returns:** void --- --- url: /docs/scripting/api/representationentity.md description: 'RepresentationEntity – Visual representation of an entity (model, shape, etc.)' --- # RepresentationEntity Visual representation of an entity (model, shape, etc.) Access via `entity.representation` or `entity.findRepresentation()`. Controls visual properties like opacity, transform, and material parameters. **Example:** ```javascript var rep = entity.representation; rep.opacity = 0.5; rep.position = Vector3(0, 1, 0); rep.bindMaterialParameter("baseColor", "myColor"); ``` ## Properties ### boundingBox * **Type:** [BoundingBox](/scripting/api/boundingbox) * Local bounding box ### entity * **Type:** [ObjectEntity](/scripting/api/objectentity) * Parent entity ### eulerAngles * **Type:** [Vector3](/scripting/api/vector3) * Local rotation as euler angles (radians) ### id * **Type:** string * Unique representation identifier ### isEnabled * **Type:** boolean * Whether this representation is enabled/visible ### mesh * **Type:** [Mesh](/scripting/api/mesh) * Dynamic mesh handle for this representation. Nil unless the kind is `.dynamicMesh`. Use this to write vertex/index buffers and update per-part bounds from a script. Bulk-copy semantics – one call moves many vertices, no per-element overhead. **Example:** ```javascript var mesh = entity.representation.mesh; mesh.writeVertices("position", 0, new Float32Array([ -0.25, 0.0, 0.0, 0.25, 0.0, 0.0, 0.0, 0.4, 0.0 ])); mesh.writeIndices(0, new Uint32Array([0, 1, 2])); mesh.drawCount = 3; // sugar for updatePart(0, { indexCount: 3 }) mesh.setBounds({ min: [-1,-1,-1], max: [1,1,1] }); ``` ### opacity * **Type:** number * Opacity (0-1) ### position * **Type:** [Vector3](/scripting/api/vector3) * Local position relative to parent ### rootView * **Type:** [UIView](/scripting/api/uiview) * Root view of the panel content. Nil unless this representation's kind is `.userInterface`. **Example:** ```javascript var root = rep.rootView; root.setProperty("isHidden", true); ``` ### rotation * **Type:** [Rotation](/scripting/api/rotation) * Local rotation as quaternion ### scale * **Type:** [Vector3](/scripting/api/vector3) * Local scale ### transform * **Type:** [Transform](/scripting/api/transform) * Local transform (position, rotation, scale combined) ### worldBoundingBox * **Type:** [BoundingBox](/scripting/api/boundingbox) * World-space bounding box ### worldEulerAngles * **Type:** [Vector3](/scripting/api/vector3) * World-space rotation as euler angles (radians) ### worldPosition * **Type:** [Vector3](/scripting/api/vector3) * World-space position ### worldRotation * **Type:** [Rotation](/scripting/api/rotation) * World-space rotation as quaternion ### worldScale * **Type:** [Vector3](/scripting/api/vector3) * World-space scale ### worldTransform * **Type:** [Transform](/scripting/api/transform) * World-space transform ## Methods ### animateBy() ```javascript animateBy(properties: Object, duration: number, options?: Object): Promise ``` Animate this representation by relative values **Parameters:** * `properties` (Object) - Relative change properties * `duration` (number) - Animation duration in seconds * `options` (Object) *(optional)* - Animation options **Returns:** Promise\ ### animateFromTo() ```javascript animateFromTo(fromProperties: Object, toProperties: Object, duration: number, options?: Object): Promise ``` Animate this representation from starting to target properties **Parameters:** * `fromProperties` (Object) - Starting properties * `toProperties` (Object) - Target properties * `duration` (number) - Animation duration in seconds * `options` (Object) *(optional)* - Animation options **Returns:** Promise\ ### animateTo() ```javascript animateTo(properties: Object, duration: number, options?: Object): Promise ``` Animate this representation to target properties **Parameters:** * `properties` (Object) - Target properties (position, rotation, scale, opacity) * `duration` (number) - Animation duration in seconds * `options` (Object) *(optional)* - Animation options **Returns:** Promise\ ### bindMaterialParameter() ```javascript bindMaterialParameter(parameterName: string, variableId: string, options?: any): void ``` Bind a shader material parameter to a variable for automatic updates * scope: Variable scope - "segment" (default) or "experience" - materialId: Target specific material by ID (optional, applies to all if omitted) - slots: Target specific material slots as array of integers (optional) **Example:** ```javascript // Initialize variable scene.setVariable("tintColor", Color.red()); // Bind parameter to variable rep.bindMaterialParameter("baseColor", "tintColor"); // Now changing the variable updates the material automatically scene.setVariable("tintColor", Color.blue()); // With experience scope rep.bindMaterialParameter("intensity", "globalIntensity", { scope: "experience" }); // Target specific material rep.bindMaterialParameter("emission", "glowAmount", { materialId: "glowMaterial", slots: [0] }); ``` **Parameters:** * `parameterName` (string) - Name of the material parameter (must match shader graph input) * `variableId` (string) - ID of the variable to bind * `options` (any) *(optional)* - Configuration object (optional) **Returns:** void ### createAudioStream() ```javascript createAudioStream(options?: Object): AudioStream ``` Create a streaming audio player on this representation **Example:** ```javascript var stream = entity.createAudioStream({ sampleRate: 24000 }); stream.append(base64Data); stream.stop(); ``` **Parameters:** * `options` (Object) *(optional)* - Stream options * `options.sampleRate` (number) *(optional)* - Input sample rate in Hz * `options.channels` (number) *(optional)* - Number of channels (1 = mono, 2 = stereo) * `options.format` (string) *(optional)* - Sample format: "pcm16" or "float32" **Returns:** [AudioStream](/scripting/api/audiostream) ### findChild() ```javascript findChild(name: string, recursive?: any): Entity ``` Find a nested entity by name within the model hierarchy **Example:** ```javascript var bone = rep.findChild("thumb_1"); bone.transform = hand.joints.thumbKnuckle.localTransform; ``` **Parameters:** * `name` (string) - Entity name to find * `recursive` (any) *(optional)* - Search all descendants (default: true) **Returns:** [Entity](/scripting/api/entity) ### findRepresentation() ```javascript findRepresentation(query: any): RepresentationEntity ``` Find a child representation by ID or name **Example:** ```javascript var child = rep.findRepresentation({ name: "wheel" }); child.rotation = Rotation(0, Math.PI, 0); ``` **Parameters:** * `query` (any) - Representation ID string, or object with { id: "..." } or { name: "..." } **Returns:** [RepresentationEntity](/scripting/api/representationentity) ### findView() ```javascript findView(query: any): UIView ``` Find a view in this representation's UI tree by ID or name **Example:** ```javascript // By ID var btn = rep.findView("save-btn"); // By name var btn = rep.findView({ name: "Submit" }); ``` **Parameters:** * `query` (any) - View ID string, or object with `{ id: "..." }` or `{ name: "..." }` **Returns:** [UIView](/scripting/api/uiview) ### getChildNames() ```javascript getChildNames(): Array ``` List all nested entity names (for discovery) **Example:** ```javascript var names = rep.getChildNames(); console.log(names); // ["wrist", "thumb_1", "thumb_2", ...] ``` **Returns:** Array\ ### off() ```javascript off(eventId: string): void ``` Remove an event listener from this representation **Parameters:** * `eventId` (string) - Event ID returned from on() **Returns:** void ### on() ```javascript on(type: string, options?: Object | function, listener?: function): string ``` Register an event listener on this representation **Example:** ```javascript entity.representation.on('tap', function(event) { console.log('This representation tapped!'); }); ``` **Parameters:** * `type` (string) - Event type: "tap", "collision", "distance", etc. * `options` (Object | function) *(optional)* - Event options or callback * `listener` (function) *(optional)* - Callback if options provided **Returns:** string ### once() ```javascript once(type: string, options?: Object | function, listener?: function): string ``` Register a one-time event listener on this representation **Example:** ```javascript entity.representation.once('tap', function(event) { console.log('First tap on this representation!'); }); ``` **Parameters:** * `type` (string) - Event type * `options` (Object | function) *(optional)* - Event options or callback * `listener` (function) *(optional)* - Callback if options provided **Returns:** string ### play() ```javascript play(animation: Animation): Promise ``` Play an animation on this representation **Parameters:** * `animation` (Animation) - Animation object from Animation.to(), Animation.spin(), etc. **Returns:** Promise\ ### playAudio() ```javascript playAudio(source: string, options?: Object): Promise ``` Play audio asset on this representation **Example:** ```javascript await entity.playAudio('https://example.com/sound.mp3'); await entity.playAudio('assetId123', { loops: true, gain: 1.5 }); ``` **Parameters:** * `source` (string) - HTTPS URL or asset ID from project * `options` (Object) *(optional)* - Playback options * `options.gain` (number) *(optional)* - Volume gain (0.0 to 4.0, default 1.0) * `options.loops` (boolean) *(optional)* - Loop playback * `options.inputMode` (string) *(optional)* - "spatial", "nonSpatial", or "ambient" **Returns:** Promise\ ### playAudioBuffer() ```javascript playAudioBuffer(base64: string, options?: Object): Promise ``` Play audio buffer spatially on this representation **Example:** ```javascript await entity.playAudioBuffer(base64Data, { sampleRate: 24000 }); ``` **Parameters:** * `base64` (string) - Base64-encoded PCM audio data * `options` (Object) *(optional)* - Buffer and playback options * `options.sampleRate` (number) *(optional)* - Sample rate in Hz * `options.channels` (number) *(optional)* - Number of channels (1 = mono, 2 = stereo) * `options.format` (string) *(optional)* - Sample format: "pcm16" or "float32" * `options.gain` (number) *(optional)* - Volume gain (0.0 to 4.0, default 1.0) **Returns:** Promise\ ### resetBlendShapeWeights() ```javascript resetBlendShapeWeights(options?: Object): void ``` Reset all blend shape weights to zero **Parameters:** * `options` (Object) *(optional)* - Options (modelName, weightSetIndex, weightSetId) **Returns:** void ### setBlendShapeWeights() ```javascript setBlendShapeWeights(weights: Object | number, options?: Object): void ``` Update blend shape weights on this representation (for models with morph targets/blend shapes) **Example:** ```javascript // Make a character smile representation.setBlendShapeWeights({ mouthSmileLeft: 1, mouthSmileRight: 1 }); // Animate a blink representation.setBlendShapeWeights({ eyeBlinkLeft: 1, eyeBlinkRight: 1 }); await wait(0.15); representation.setBlendShapeWeights({ eyeBlinkLeft: 0, eyeBlinkRight: 0 }); // Reset all weights to zero representation.setBlendShapeWeights(0); ``` **Parameters:** * `weights` (Object | number) - Blend shape name-value pairs, or a single number to set all weights * `options` (Object) *(optional)* - Options * `options.modelName` (string) *(optional)* - Target specific model by name (optional) * `options.weightSetIndex` (number) *(optional)* - Weight set index to update * `options.weightSetId` (string) *(optional)* - Weight set ID to update (alternative to index) **Returns:** void ### setMaterial() ```javascript setMaterial(options?: Object): Promise ``` Set material on this representation **Example:** ```javascript // Apply material to all models entity.representation.setMaterial({ id: 'redMaterial' }); // Apply to first model only entity.representation.setMaterial({ id: 'redMaterial', target: { type: 'firstModel' } }); // Clear custom materials (restore originals) entity.representation.setMaterial(); ``` **Parameters:** * `options` (Object) *(optional)* - Material options, omit to clear custom materials * `options.id` (string) - Material reference ID from scene * `options.target` (Object) *(optional)* - Target configuration * `options.target.type` (string) *(optional)* - "allModels", "firstModel", or "selectedModels" * `options.target.models` (Array\) *(optional)* - Model names for selectedModels * `options.target.materialSlots` (Array\) *(optional)* - Material slot indices **Returns:** Promise\ ### setMaterials() ```javascript setMaterials(materials: Array): Promise ``` Set materials on this representation **Example:** ```javascript // Apply multiple materials entity.representation.setMaterials([ { id: 'redMaterial', target: { type: 'firstModel' } }, { id: 'blueMaterial', target: { type: 'selectedModels', models: ['body'] } } ]); // Clear all custom materials (restore originals) entity.representation.setMaterials([]); ``` **Parameters:** * `materials` (Array\) - Array of material options * `materials[].id` (string) - Material reference ID from scene * `materials[].target` (Object) *(optional)* - Target configuration * `materials[].target.type` (string) *(optional)* - "allModels", "firstModel", or "selectedModels" * `materials[].target.models` (Array\) *(optional)* - Model names for selectedModels * `materials[].target.materialSlots` (Array\) *(optional)* - Material slot indices **Returns:** Promise\ ### stopAnimations() ```javascript stopAnimations(options?: Object): Promise ``` Stop and remove animations from this representation **Parameters:** * `options` (Object) *(optional)* - Options * `options.transitionDuration` (number) *(optional)* - Fade out duration in seconds **Returns:** Promise\ ### stopAudio() ```javascript stopAudio(): Promise ``` Stop audio playback on this representation **Example:** ```javascript entity.representation.stopAudio(); ``` **Returns:** Promise\ ### toggle() ```javascript toggle(enabled: boolean): Promise ``` Enable or disable this representation **Parameters:** * `enabled` (boolean) - Enable (true) or disable (false) **Returns:** Promise\ ### toggleAnimations() ```javascript toggleAnimations(options?: Object): Promise ``` Toggle (pause/resume) animations on this representation **Parameters:** * `options` (Object) *(optional)* - Options * `options.play` (boolean) *(optional)* - Explicit play (true) or pause (false), toggles if omitted * `options.animationIds` (Array\) *(optional)* - Specific animation IDs to toggle (optional, toggles all if omitted) **Returns:** Promise\ ### toggleWithAnimation() ```javascript toggleWithAnimation(enable: boolean): void ``` Toggle visibility with fade animation **Example:** ```javascript rep.toggleWithAnimation(false); // Fade out rep.toggleWithAnimation(true); // Fade in ``` **Parameters:** * `enable` (boolean) - Whether to show or hide **Returns:** void ### unbindMaterialParameter() ```javascript unbindMaterialParameter(parameterName: string, options?: any): void ``` Remove a variable binding from a material parameter * materialId: Target specific material by ID (optional) - slots: Target specific material slots (optional) **Example:** ```javascript rep.unbindMaterialParameter("baseColor"); rep.unbindMaterialParameter("emission", { materialId: "glowMaterial" }); ``` **Parameters:** * `parameterName` (string) - Name of the material parameter to unbind * `options` (any) *(optional)* - Configuration object (optional) **Returns:** void ### waitUntilReady() ```javascript waitUntilReady(): Promise ``` Wait until this representation's model assets have finished loading and been attached to the scene. Many setup calls – `bindMaterialParameter`, transform reads, animation triggers – depend on the model being present. On `onWillAppear` the representation proxy exists but the model itself usually hasn't been attached yet, and calling those methods too early silently no-ops. Await this before any model-dependent setup. **Example:** ```javascript // On Cover Will Appear var cover = scriptContext.sourceEvent.objectEntity.findRepresentation({ name: 'Cover' }); await cover.waitUntilReady(); cover.bindMaterialParameter('hueColor', 'myHue'); ``` **Returns:** Promise\ ## Internal API dev only ::: warning These methods are internal implementation details and not part of the public API. ::: ### waitUntilReadyRaw() internal ```javascript waitUntilReadyRaw(callback?: any): void ``` Internal callback-style "wait until the model has finished loading". JS authors use the Promise-returning `rep.waitUntilReady()` exposed by the runtime, which wraps this. Required because JSExport methods can't directly expose async/Promise-returning signatures. **Parameters:** * `callback` (any) *(optional)* **Returns:** void --- --- url: /docs/scripting/api/entity.md description: >- Entity – Generic entity within a model hierarchy (bones, groups, nested models) --- # Entity Generic entity within a model hierarchy (bones, groups, nested models) Access via `representation.findChild()`. Allows manipulation of nested entities within loaded USDZ models. **Example:** ```javascript var bone = rep.findChild("thumb_1"); bone.rotation = hand.joints.thumbKnuckle.localTransform.rotation; // Iterate children rep.findChild("wrist").children.forEach(function(child) { console.log(child.name); }); ``` ## Properties ### children * **Type:** Array\ * Direct child entities ### isEnabled * **Type:** boolean * Whether this entity is enabled/visible ### jointNames * **Type:** Array\ * Joint names for skeletal animation (empty if not a skeletal model) ### jointTransforms * **Type:** Array\ * Joint transforms array (parallel to jointNames) ### name * **Type:** string * Entity name ### opacity * **Type:** number * Opacity (0-1) ### position * **Type:** [Vector3](/scripting/api/vector3) * Local position relative to parent ### rotation * **Type:** [Rotation](/scripting/api/rotation) * Local rotation as quaternion ### scale * **Type:** [Vector3](/scripting/api/vector3) * Local scale ### transform * **Type:** [Transform](/scripting/api/transform) * Local transform (position, rotation, scale combined) ### worldPosition * **Type:** [Vector3](/scripting/api/vector3) * World-space position ### worldRotation * **Type:** [Rotation](/scripting/api/rotation) * World-space rotation ### worldTransform * **Type:** [Transform](/scripting/api/transform) * World-space transform ## Methods ### findChild() ```javascript findChild(name: string, recursive?: any): Entity ``` Find a child entity by name **Example:** ```javascript var bone = entity.findChild("thumb_1"); // recursive var direct = entity.findChild("arm", false); // immediate only ``` **Parameters:** * `name` (string) - Entity name to find * `recursive` (any) *(optional)* - Search all descendants (default: true) **Returns:** [Entity](/scripting/api/entity) --- --- url: /docs/scripting/api/experience.md description: >- Experience API reference for Scenery scripting: methods, properties, and examples. --- # Experience ## Properties ### currentScene * **Type:** [Scene](/scripting/api/scene) * The current scene ### environment * **Type:** [Environment](/scripting/api/environment) * Environment information (platform, OS version, locale) ### sharedActivity * **Type:** [SharedActivity](/scripting/api/sharedactivity) * Shared activity state (SharePlay, WebSocket, or other shared session protocols) ## Methods ### getVariable() ```javascript getVariable(id: string): any ``` Get an experience-scoped variable **Example:** ```javascript var count = experience.getVariable('visitCount'); var name = experience.getVariable('userName'); ``` **Parameters:** * `id` (string) - Variable identifier **Returns:** any ### setVariable() ```javascript setVariable(id: string, value: any, options: any): void ``` Set an experience-scoped variable (persists across scene transitions) **Example:** ```javascript experience.setVariable('visitCount', 3); experience.setVariable('userName', 'Alice'); experience.setVariable('lastPosition', Vector3(0, 1, 0), { type: 'vector3' }); ``` **Parameters:** * `id` (string) - Variable identifier * `value` (any) - Value to store (number, string, boolean, Vector3, Color, etc.) * `options` (any) - Optional type hint { type: "vector3" | "color" | ... } **Returns:** void ### transitionToScene() ```javascript transitionToScene(id: string): void ``` Transition to another scene **Example:** ```javascript experience.transitionToScene('chapter-2'); ``` **Parameters:** * `id` (string) - Scene identifier **Returns:** void --- --- url: /docs/scripting/api/environment.md description: >- Environment API reference for Scenery scripting: methods, properties, and examples. --- # Environment ## Properties ### features * **Type:** [EnvironmentFeatures](/scripting/api/environmentfeatures) * Runtime + device capability registry. Authors query by string – `environment.features.has('compute')` – to branch on whether the current platform supports a given feature. Mirrors WebGPU's `adapter.features.has(...)` shape so portable scripts read the same on both backends. **Example:** ```javascript if (environment.features.has('compute')) { mesh.runCompute('advect', { uniforms }); } ``` ### hostingPlatform * **Type:** string * The platform running the experience: "iOS", "macOS", "visionOS", or "web" ### isEditing * **Type:** boolean * `true` when the experience is running inside the Scenery editor's scene preview, `false` during normal playback. Lets scripts gate debug overlays, skip intros, or auto-fill placeholder state while the author is iterating. **Example:** ```javascript // Show a debug HUD only while editing. if (environment.isEditing) { scene.findEntity('debug-hud').isEnabled = true; } ``` ### locale * **Type:** string * User's locale in BCP 47 format, e.g. "en-US", "de-DE" ### systemOSVersion * **Type:** string * Operating system version string, e.g. "18.0" or "2.0" --- --- url: /docs/scripting/api/environmentfeatures.md description: EnvironmentFeatures – Capability registry exposed at environment.features. --- # EnvironmentFeatures Capability registry exposed at `environment.features`. Set-style membership semantics: unknown names return `false` so forward-looking scripts can query features that may land in a later runtime version without crashing on today's runtime. Today's queryable strings are `'compute'` (GPU compute-kernel dispatch – true on Apple 7+ devices on iOS 18 / visionOS 2 / macOS 15+), `'sharedActivities'` (multi-user / SharePlay support – false in App Clip and trait-stripped builds), and `'cameraFeed'` (live camera-feed texture – iOS / iPadOS only; not Mac Catalyst, not visionOS). More land here as backends wire up; see the Feature Detection guide for the full per-platform matrix. Use `list()` for the live set on the current device/runtime. ## Methods ### has() ```javascript has(name: string): boolean ``` Whether the named feature is supported on the current platform and runtime version. Unknown names return `false` (WebGPU `Set.has` semantic). **Example:** ```javascript if (environment.features.has('compute')) { mesh.runCompute('advect', { uniforms }); } else { stepOnCPU(); } ``` **Parameters:** * `name` (string) - Feature identifier; call `list()` for the set supported on this device/runtime. **Returns:** boolean ### list() ```javascript list(): string[] ``` Every feature string currently supported on this device/runtime. Useful for debugging ("why isn't my branch firing?") and for reflecting the capability set into telemetry. **Example:** ```javascript console.log(environment.features.list()); // → ['sharedActivities'] ``` **Returns:** string\[] --- --- url: /docs/scripting/api/anchordata.md description: >- AnchorData API reference for Scenery scripting: methods, properties, and examples. --- # AnchorData ## Properties ### alignment * **Type:** string * Plane alignment: "horizontal" or "vertical" (plane anchors only) ### altitude * **Type:** number * Altitude in meters (geo anchors only, iOS) ### blendShapes * **Type:** Array\ * Blend shape coefficients for facial expressions **Example:** ```javascript var faces = scene.getAnchors("face"); if (faces.length > 0) { var face = faces[0]; var smile = (face.blendShapes.mouthSmileLeft + face.blendShapes.mouthSmileRight) / 2; var blink = face.blendShapes.eyeBlinkLeft; console.log("Smile:", smile, "Blink:", blink); } ``` ### chirality * **Type:** string * Hand chirality: "left" or "right" (hand anchors only, visionOS) ### classification * **Type:** string * Plane classification: "floor", "ceiling", "wall", "table", "seat", "window", "door" (plane anchors only) ### estimatedScaleFactor * **Type:** number * Estimated scale factor of detected image (image anchors only) ### height * **Type:** number * Plane height in meters (plane anchors only) ### id * **Type:** string * Unique identifier for this anchor ### isTracked * **Type:** boolean * Whether this anchor is currently being tracked (image, face, hand anchors) ### joints * **Type:** Array\ * Hand joint transforms by name **Example:** ```javascript var hand = scene.getAnchors("hand")[0]; if (hand.chirality === "left") { var indexTip = hand.joints.indexFingerTip.worldTransform; console.log("Index finger at:", indexTip.position); } ``` ### kind * **Type:** string * Anchor type: "plane", "hand", "face", "image", "world", "geo", "mesh" ### latitude * **Type:** number * Geographic latitude in degrees (geo anchors only, iOS) ### leftEyeTransform * **Type:** [Transform](/scripting/api/transform) * Left eye transform in face coordinate space (face anchors only) ### longitude * **Type:** number * Geographic longitude in degrees (geo anchors only, iOS) ### lookAtPoint * **Type:** [Vector3](/scripting/api/vector3) * Estimated gaze direction point in face coordinate space (face anchors only) **Example:** ```javascript var face = scene.getAnchors("face")[0]; var lookAt = face.lookAtPoint; // Position object where user is looking object.position = Vector3( face.worldTransform.position.x + lookAt.x, face.worldTransform.position.y + lookAt.y, face.worldTransform.position.z - 0.5 ); ``` ### name * **Type:** string * Optional name for this anchor ### orderedJointTransforms * **Type:** Array\ * Ordered joint transforms for skeletal animation (hand anchors only) **Example:** ```javascript var model = rep.findChild("locator1"); var hand = scene.getAnchors("hand")[0]; var transforms = model.jointTransforms; hand.orderedJointTransforms.forEach(function(t, index) { transforms[index].rotation = t.rotation; }); model.jointTransforms = transforms; ``` ### referenceImageName * **Type:** string * Reference image name from ARReferenceImage (image anchors only) ### rightEyeTransform * **Type:** [Transform](/scripting/api/transform) * Right eye transform in face coordinate space (face anchors only) ### width * **Type:** number * Plane width in meters (plane anchors only) ### worldTransform * **Type:** [Transform](/scripting/api/transform) * World-space transform matrix for anchor position and orientation --- --- url: /docs/scripting/api/collisioncasthit.md description: >- CollisionCastHit – Result from scene.raycast() when ray intersects an entity or scene mesh --- # CollisionCastHit Result from scene.raycast() when ray intersects an entity or scene mesh ## Properties ### distance * **Type:** number * Distance from ray origin to hit point ### faceIndex * **Type:** number | null * Index of the triangle face that was hit (iOS 18+, mesh collisions only) ### normal * **Type:** [Vector3](/scripting/api/vector3) * Surface normal at hit point ### position * **Type:** [Vector3](/scripting/api/vector3) * World-space position where ray hit the entity ### representation * **Type:** [RepresentationEntity](/scripting/api/representationentity) * The entity that was hit (nil for scene mesh hits) ### shapeIndex * **Type:** number | null * Index of the shape within the entity that was hit (iOS 18+) ### type * **Type:** string * Hit type - "representation" or "sceneMesh" ### uv * **Type:** [Vector2](/scripting/api/vector2) * Barycentric UV coordinate on the triangle (iOS 18+, mesh collisions only) --- --- url: /docs/scripting/api/arraycastresult.md description: >- ARRaycastResult – Result from scene.raycastAR() when ray intersects real-world surfaces --- # ARRaycastResult Result from scene.raycastAR() when ray intersects real-world surfaces ## Properties ### anchor * **Type:** [AnchorData](/scripting/api/anchordata) * Associated plane anchor if hit was on a tracked plane (nil for estimated surfaces) ### target * **Type:** string * Type of target that was hit: "existingPlaneGeometry", "existingPlaneInfinite", "estimatedPlane" ### targetAlignment * **Type:** string * Surface alignment: "horizontal", "vertical", "any" ### worldTransform * **Type:** [Transform](/scripting/api/transform) * World transform matrix at hit location --- --- url: /docs/scripting/api/kernel.md description: >- Kernel – Opaque handle to a compiled GPU compute kernel. Returned by Kernel.fromSource(...) / Kernel.fromAsset(...). Two dispatch entry points: kernel.r… --- # Kernel Opaque handle to a compiled GPU compute kernel. Returned by `Kernel.fromSource(...)` / `Kernel.fromAsset(...)`. Two dispatch entry points: `kernel.run({...})` for standalone compute (texture / buffer I/O, no mesh involvement) and `mesh.runCompute(kernel, options)` when the kernel writes mesh-attribute buffers. ## Properties ### functionName * **Type:** string * Display name of the kernel's entry-point function. ### id * **Type:** string * Stable handle id assigned at construction. Surfaces in the scripting manager's live-set; used by `destroy()` to release the experience-lifetime retention. ## Static Methods ### fromAsset() ```javascript Kernel.fromAsset(options: Object): Promise ``` Compile a compute kernel from a shader-library asset declared in the experience's asset library. **Parameters:** * `options` (Object) * `options.assetId` (string) - Shader-library asset id. * `options.functionName` (string) - Kernel entry-point name. **Returns:** Promise\ ### fromSource() ```javascript Kernel.fromSource(options: Object): Promise ``` Compile a compute kernel from inline MSL source. Handy for prototypes and tests; production scripts should prefer `Kernel.fromAsset(...)` so the source lives in the experience's asset library. **Example:** ```javascript const kernel = await Kernel.fromSource({ source: `#include using namespace metal; kernel void wave(...) { ... }`, functionName: 'wave' }); ``` **Parameters:** * `options` (Object) * `options.source` (string) - MSL source string. * `options.functionName` (string) - Kernel entry-point name. **Returns:** Promise\ ## Methods ### destroy() ```javascript destroy(): void ``` Release the kernel's GPU resources (compiled library + pipeline state) immediately. Use in hot loops where JSC's non-deterministic GC would otherwise stretch the lifetime. Normal scripts don't need to call this – ARC handles cleanup when the kernel goes out of scope. Calling `mesh.runCompute(...)` or `kernel.run(...)` against a destroyed kernel warns and aborts the dispatch. **Returns:** void ### run() ```javascript run(options: { uniforms?, inputBuffers?, outputBuffers?, inputTextures?, outputTextures?, threadGroups, threadsPerThreadgroup? }): void ``` Dispatch the kernel against textures and / or `Buffer` handles – no mesh involvement. Use for compute passes that don't write mesh attribute buffers (texture generation, particle simulation pre-pass, any "pure compute" kernel that doesn't displace geometry). MSL slot order: `buffer(0)` uniforms → `buffer(1..)` inputBuffers then outputBuffers → `texture(0..)` inputTextures then outputTextures. **Example:** ```javascript kernel.run({ uniforms: new Float32Array([scene.time]), outputTextures: { heightOut: heightMap.write }, threadGroups: [16, 16, 1] }); ``` **Parameters:** * `options` ({ uniforms?, inputBuffers?, outputBuffers?, inputTextures?, outputTextures?, threadGroups, threadsPerThreadgroup? }) **Returns:** void --- --- url: /docs/scripting/api/texture.md description: >- Texture – GPU-only / runtime-managed texture. Backed by an ARXDynamicTexture (today's only constructible kind from JS is .computeBacked; sibling kinds… --- # Texture GPU-only / runtime-managed texture. Backed by an `ARXDynamicTexture` (today's only constructible kind from JS is `.computeBacked`; sibling kinds – image, video, renderTarget, cameraFeed – wire up alongside material-parameter binding in a follow-up runtime version). For compute-backed textures, kernel dispatch reads / writes the underlying GPU texture; materials sample the same memory through the same texture handle, so author-side updates show up in the next rendered frame without an explicit copy. Authors construct via `Texture.compute(...)` and feed `.read` / `.write` tokens into a kernel's `inputTextures` / `outputTextures` maps. ## Properties ### depth * **Type:** number * Depth (slices) for 3D textures, layer count for arrays. `1` for flat 2D / cubemap-face textures. ### height * **Type:** number ### id * **Type:** string * Texture id – matches `dynamicTexture.id`. Stable for the lifetime of the texture; surfaces in material `TextureReference.dynamic` references. ### pixelFormat * **Type:** string * Engine-agnostic pixel format name (e.g. `"rgba32Float"`). ### read * **Type:** [TextureBinding](/scripting/api/texturebinding) * Token tagged as "read into kernel" – pass to a kernel's `inputTextures` map. ### textureType * **Type:** string * Texture dimensionality: `"2d"`, `"3d"`, `"cube"`, `"2dArray"`, `"cubeArray"`. ### width * **Type:** number ### write * **Type:** [TextureBinding](/scripting/api/texturebinding) * Token tagged as "write from kernel" – pass to a kernel's `outputTextures` map. ## Static Methods ### cameraFeed() ```javascript Texture.cameraFeed(options?: Object): Promise ``` Live camera feed as a sampleable texture. Binds the device's camera capture as a regular texture handle – typical uses are first-person portal effects, augmented-reality color grading on detected planes, or piping the feed into a compute kernel for a real-time filter. Available on iOS / iPadOS only – Mac Catalyst and visionOS don't expose the AR camera capture. Always gate with `environment.features.has('cameraFeed')`. **Example:** ```javascript if (!environment.features.has('cameraFeed')) return; const camTex = await Texture.cameraFeed(); await entity.representation.setMaterial(Material.unlit({ map: camTex })); ``` **Parameters:** * `options` (Object) *(optional)* * `options.semantic` (string) *(optional)* **Returns:** Promise\ ### compute() ```javascript Texture.compute(options: Object): Promise ``` Allocate a GPU-only texture written by a compute kernel. Supports 2D, 3D, cube, and array textures with optional mipmap chains. Async because the dynamic-texture asset-provider funnel allocates the underlying GPU resource and registers it in the runtime's manager cache (so material binding finds the same instance later). Authors `await` once at allocation; subsequent kernel dispatches are sync. **Example:** ```javascript // 2D height map for a wave-grid kernel const heightMap = await Texture.compute({ pixelFormat: 'rgba32Float', width: 256, height: 256 }); // 3D scalar field for volumetric / marching-cubes work const field = await Texture.compute({ textureType: '3d', pixelFormat: 'r32Float', width: 64, height: 64, depth: 64 }); // Compute-driven normal map sampled by a PBR material const normalMap = await Texture.compute({ pixelFormat: 'rgba8Unorm', width: 512, height: 512, semantic: 'normal' }); ``` **Parameters:** * `options` (Object) * `options.pixelFormat` (string) - e.g. `'rgba32Float'`, `'rgba16Float'`, `'r32Float'`. * `options.textureType` (string) *(optional)* - `'2d' | '3d' | 'cube' | '2dArray' | 'cubeArray'`. * `options.width` (number) * `options.height` (number) * `options.depth` (number) *(optional)* - Slice count for 3D textures. * `options.arrayLength` (number) *(optional)* - Layer count for arrays. * `options.mipmapLevelCount` (number) *(optional)* - Set > 1 + call `texture.generateMipmaps()` to populate. * `options.semantic` (string) *(optional)* - `'color' | 'normal' | 'scalar' | 'hdrColor' | 'raw'`. Tells material binding how to sample (sRGB vs linear, etc.). * `options.usage` (Array\) *(optional)* **Returns:** Promise\ ### image() ```javascript Texture.image(urlOrId: string, options?: Object): Promise ``` Static image-asset texture. Reads an image asset (PNG / JPG / etc.) from the experience's asset library or an absolute URL and binds it as a sampleable texture – typical use is a hand-painted normal map or albedo map fed to `Material.pbr({ normalMap: tex })`. **Example:** ```javascript const normalMap = await Texture.image('rocks-normal.png', { semantic: 'normal' }); await entity.representation.setMaterial(Material.pbr({ map: 'rocks-color.png', normalMap: normalMap })); ``` **Parameters:** * `urlOrId` (string) - HTTPS URL or asset id from the project library. * `options` (Object) *(optional)* * `options.semantic` (string) *(optional)* - `'color' | 'normal' | 'scalar' | 'hdrColor' | 'raw'`. Tells material binding how to sample (sRGB vs linear). **Returns:** Promise\ ### renderTarget() ```javascript Texture.renderTarget(_id: string): Promise ``` Render-target texture. Stub for now – wired alongside material binding. **Parameters:** * `_id` (string) **Returns:** Promise\ ### video() ```javascript Texture.video(urlOrId: string, options?: Object): Promise ``` Video-asset texture. Plays a video file (mp4 / mov) as a sampleable texture and binds it to a material – typical use is a looping background, an animated diorama, or a UI-overlay video sticker. The video starts playing automatically by default. Pass `{ autoplays: false }` and call `videoTexture.play()` later when the entity becomes visible. **Example:** ```javascript const intro = await Texture.video('intro-loop.mp4', { loops: true, volume: 0.3 }); await entity.representation.setMaterial(Material.unlit({ map: intro })); ``` **Parameters:** * `urlOrId` (string) - HTTPS URL or asset id from the project library. * `options` (Object) *(optional)* * `options.semantic` (string) *(optional)* - `'color' | 'normal' | 'scalar' | 'hdrColor' | 'raw'`. * `options.loops` (boolean) *(optional)* - Restart from frame 0 on end. * `options.autoplays` (boolean) *(optional)* - Start playback as soon as the asset is ready. * `options.volume` (number) *(optional)* - Linear gain (0..1). Unset = system default. * `options.streamContent` (boolean) *(optional)* - HTTP streaming vs. download-then-play. * `options.isShared` (boolean) *(optional)* - SharePlay sync – when true, playback time syncs across session participants. **Returns:** Promise\ ## Methods ### destroy() ```javascript destroy(): void ``` Release the texture's GPU memory immediately. Textures clean up automatically when the proxy goes out of scope. Call `destroy()` only for early termination in hot allocation / teardown loops where JSC's GC delay matters. Subsequent kernel dispatches binding this texture warn + skip the binding. **Returns:** void ### generateMipmaps() ```javascript generateMipmaps(): void ``` Generate the mipmap chain. Runs a synchronous GPU mipmap-generation pass against the texture's command queue. Requires the texture was allocated with `mipmapLevelCount > 1`. No-op on textures without a mip chain. **Returns:** void --- --- url: /docs/scripting/api/texturebinding.md description: >- TextureBinding – Marker token returned by texture.read / texture.write. Carries the backing texture and the role so dispatch knows whether to bind the t… --- # TextureBinding Marker token returned by `texture.read` / `texture.write`. Carries the backing texture and the role so dispatch knows whether to bind the texture as a kernel read source or write target. --- --- url: /docs/scripting/api/buffer.md description: >- Buffer – Raw GPU buffer wrapping a typed scratch region for compute kernels. Use for things meshes + textures don't model: atomic counters, lookup table… --- # Buffer Raw GPU buffer wrapping a typed scratch region for compute kernels. Use for things meshes + textures don't model: atomic counters, lookup tables (e.g. marching cubes' triangle / edge tables), per-particle state, scratch arrays between kernel passes. Authors construct via `Buffer.float32/uint32/uint8/atomic(...)`. The returned handle binds via `mesh.runCompute(...)`'s `inputBuffers` / `outputBuffers` maps and supports CPU readback via `readSync()` for things like "how many triangles did marching cubes emit this frame?". ## Properties ### byteLength * **Type:** number * Total byte length – `length * elementSize`. ### elementType * **Type:** string * Element type as a scripting string (`"float32"`, `"uint32"`, `"uint8"`, `"atomic_uint"`). ### id * **Type:** string * Stable handle id assigned at construction. Surfaces in the scripting manager's live-set; used by `destroy()` to release the experience-lifetime retention. ### length * **Type:** number * Number of elements (not bytes). ## Static Methods ### atomic() ```javascript Buffer.atomic(initialValue?: number): Buffer | any ``` Allocate a single-element atomic-uint counter – common pattern for marching-cubes' triangle emitter, particle compaction, etc. **Example:** ```javascript const triCounter = Buffer.atomic(0); mesh.runCompute(kernel, { outputBuffers: { triCount: triCounter }, threadGroups: [8, 8, 4] }); const [emitted] = triCounter.readSync(); mesh.drawCount = emitted * 3; ``` **Parameters:** * `initialValue` (number) *(optional)* - Counter seed. **Returns:** [Buffer](/scripting/api/buffer) | any ### float32() ```javascript Buffer.float32(length: number, initialValue?: number): Buffer | any ``` Allocate a Float32-typed GPU buffer. **Parameters:** * `length` (number) - Element count (not bytes). * `initialValue` (number) *(optional)* - Optional seed value for all elements. **Returns:** [Buffer](/scripting/api/buffer) | any ### uint32() ```javascript Buffer.uint32(length: number, initialValue?: number): Buffer | any ``` Allocate a Uint32-typed GPU buffer. **Parameters:** * `length` (number) - Element count (not bytes). * `initialValue` (number) *(optional)* - Optional seed value for all elements. **Returns:** [Buffer](/scripting/api/buffer) | any ### uint8() ```javascript Buffer.uint8(length: number, initialValue?: number): Buffer | any ``` Allocate a Uint8-typed GPU buffer. **Parameters:** * `length` (number) - Element count (not bytes). * `initialValue` (number) *(optional)* - Optional seed value for all elements. **Returns:** [Buffer](/scripting/api/buffer) | any ## Methods ### destroy() ```javascript destroy(): void ``` Release the underlying GPU buffer immediately. Buffers clean up automatically when the proxy goes out of scope. Call `destroy()` only for early termination in hot allocation / teardown loops where JSC's GC delay matters. **Returns:** void ### readSync() ```javascript readSync(): Float32Array | Uint32Array | Uint8Array ``` Read the buffer contents back to JS as a TypedArray. Triggers a command-buffer wait if a compute dispatch is in flight against the buffer – use sparingly (typical pattern: read atomic counters after dispatch to size a downstream draw call). `elementType`. Nil if the buffer is detached. **Returns:** Float32Array | Uint32Array | Uint8Array ### writeSync() ```javascript writeSync(data: Float32Array | Uint32Array | Uint8Array): void ``` Write a TypedArray's contents into the buffer (CPU → GPU). Useful for seeding lookup tables, atomic counter resets, particle initial state. Length must match the buffer's declared element count; shorter writes leave the tail untouched. `elementType`. **Parameters:** * `data` (Float32Array | Uint32Array | Uint8Array) - matching **Returns:** void --- --- url: /docs/scripting/api/uiview.md description: UIView – A view node inside a UI panel. --- # UIView A view node inside a UI panel. Reach via `representation.rootView` for the panel's root content, or `representation.findView(idOrName)` for any descendant. The same class represents every kind (label, button, slider, etc.) – `kind` reports which one, and `setProperty` / `getProperty` accept keys appropriate to that kind. Setting an unsupported key for the kind is a no-op. Mutations apply live to the existing UIKit view (no rebuild – the rendered texture refreshes next frame on iOS; visionOS's view attachment picks up the change automatically). For input controls whose value field is `.reference(...)`, `setProperty` also writes back to the bound variable so observers (label `${var}` templates, other reactive bindings) update – same path the user-drag/tap valueChanged action takes. Literal fields don't write back. **Example:** ```javascript const rep = scene.findEntity({ name: "Settings" }).representation; rep.findView("save-btn").setProperty("title", "Done"); rep.findView({ name: "volume" }).bind("value", "vol"); ``` ## Properties ### id * **Type:** string * Stable view ID set in the experience model. ### isEnabled * **Type:** boolean * Interactive flag. Applies to: any kind. ### isHidden * **Type:** boolean * Visibility flag. Applies to: any kind. ### isOn * **Type:** boolean * On/off state. Applies to: `toggle`. ### kind * **Type:** string * View kind as a string: "label", "button", "slider", "toggle", "textField", "stepper", "segmentedControl", "progressView", "image", "divider", "shape", "hStack", "vStack", "zStack", "spacer", "webView". ### name * **Type:** string * Optional human-readable name (mirrors `representation.name`). ### opacity * **Type:** number * View opacity, 0–1. Applies to: any kind. ### placeholder * **Type:** string * Placeholder text. Applies to: `textField`. ### progress * **Type:** number * Fill progress, 0–1. Applies to: `progressView`. ### selectedIndex * **Type:** number * Currently selected segment. Applies to: `segmentedControl`. ### text * **Type:** string * Text content. Applies to: `label`, `textField`. ### title * **Type:** string * Button title. Applies to: `button`. ### value * **Type:** number * Numeric value. Applies to: `slider`, `stepper`, `progressView`, `segmentedControl`. ## Methods ### animateTo() ```javascript animateTo(properties: Object, duration: number, options?: Object): Promise ``` Animate one or more numeric view properties to target values. **Example:** ```javascript await label.animateTo({ opacity: 0 }, 0.3, { timingFunction: 'easeOut' }); ``` **Parameters:** * `properties` (Object) - Map of propertyName → target numeric value. * `duration` (number) - Seconds. * `options` (Object) *(optional)* * `options.timingFunction` (string) *(optional)* - 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' **Returns:** Promise ### bind() ```javascript bind(propertyName: string, variableName: string, options?: Object): JSUIView ``` Bind a property to an experience variable. Sugar for bindProperty. **Example:** ```javascript panel.findView({ name: 'vol' }).bind('value', 'volume'); ``` **Parameters:** * `propertyName` (string) - Property to bind ('value', 'isOn', 'text', ...). Kind-specific. * `variableName` (string) - Variable identifier in the experience. * `options` (Object) *(optional)* - { scope: 'segment' | 'experience' }. Defaults to 'segment'. **Returns:** JSUIView ### bindProperty() ```javascript bindProperty(propertyName: string, variableName: string, options?: any): void ``` Bind a property to an experience variable at runtime. Variable changes flow into the live view; for input controls, user input flows back into the variable. Calling `bindProperty` again on the same property replaces the existing binding. Does not mutate the saved model – bindings live with this proxy and dissolve when JS releases it (or when the panel rebuilds). **Example:** ```javascript slider.bindProperty("value", "volume"); slider.bindProperty("value", "globalVol", { scope: "experience" }); ``` **Parameters:** * `propertyName` (string) - Property to bind ("value", "isOn", "text", etc.). Kind-specific. * `variableName` (string) - Variable identifier in the experience. * `options` (any) *(optional)* - Optional `{ scope: "segment" | "experience" }`. Defaults to `"segment"`. **Returns:** void ### fadeIn() ```javascript fadeIn(duration?: number): Promise ``` Fade view to fully opaque. **Parameters:** * `duration` (number) *(optional)* **Returns:** Promise ### fadeOut() ```javascript fadeOut(duration?: number): Promise ``` Fade view to fully transparent. **Parameters:** * `duration` (number) *(optional)* **Returns:** Promise ### getProperty() ```javascript getProperty(name: string): any ``` Read a property by key **Example:** ```javascript var title = button.getProperty("title"); var value = slider.getProperty("value"); ``` **Parameters:** * `name` (string) - Property key (e.g. "text", "value", "isHidden"). Keys are kind-specific. **Returns:** any ### off() ```javascript off(subscriptionId: string): void ``` Remove an interaction listener by subscription id. **Parameters:** * `subscriptionId` (string) **Returns:** void ### on() ```javascript on(type: string, listener: function): string ``` Subscribe to a UI interaction on this view. Supported types per kind: - 'tap' → button - 'valueChanged' → slider, segmentedControl - 'toggled' → toggle - 'textChanged' → textField The listener receives `{ uiNodeId, uiInteractionKind, uiValue? }`, matching the payload authored UI-interaction events emit. **Example:** ```javascript slider.on('valueChanged', function(e) { console.log('slider →', e.uiValue); }); button.on('tap', function() { console.log('clicked'); }); ``` **Parameters:** * `type` (string) - Interaction type. * `listener` (function) - Callback receiving the event payload. **Returns:** string ### once() ```javascript once(type: string, listener: function): string ``` Subscribe to a single UI interaction; auto-unsubscribes after first fire. **Parameters:** * `type` (string) * `listener` (function) **Returns:** string ### setProperty() ```javascript setProperty(name: string, value: any): void ``` Write a property by key. Live-updates the rendered view; for input controls bound via `.reference`, also writes back to the bound variable so observers see the change. No view tree rebuild. **Example:** ```javascript label.setProperty("text", "Hello"); slider.setProperty("value", 0.5); view.setProperty("isHidden", true); ``` **Parameters:** * `name` (string) - Property key (e.g. "text", "value", "isHidden"). Keys are kind-specific. * `value` (any) - New property value. Type must match the property; mismatches are no-ops. **Returns:** void ### unbind() ```javascript unbind(propertyName: string): JSUIView ``` Remove a runtime binding installed via bind(). **Parameters:** * `propertyName` (string) **Returns:** JSUIView ### unbindProperty() ```javascript unbindProperty(propertyName: string): void ``` Remove a runtime binding installed via `bindProperty`. The property keeps its current displayed value but no longer reacts to the variable; user input on the property no longer writes back. No-op if no binding exists for `propertyName`. **Example:** ```javascript slider.unbindProperty("value"); ``` **Parameters:** * `propertyName` (string) - Property previously passed to `bindProperty`. **Returns:** void ## Internal API dev only ::: warning These methods are internal implementation details and not part of the public API. ::: ### subscribeToEvent() internal ```javascript subscribeToEvent(eventType: string, callback: any): string ``` Internal: subscribe a JS callback to a UI interaction. Runtime js wraps this as `view.on(type, listener)`. Native attaches a `UIAction` on the matching `UIControl.Event` for the view's kind. (`{ uiNodeId, uiInteractionKind, uiValue? }`). the view's kind. Pass to `_unsubscribeFromEvent` to detach. **Parameters:** * `eventType` (string) - "tap" | "valueChanged" | "toggled" | "textChanged" * `callback` (any) - JS function invoked with an event payload **Returns:** string ### unsubscribeFromEvent() internal ```javascript unsubscribeFromEvent(subscriptionId: string): void ``` Internal: detach a subscription previously created via `_subscribeToEvent`. Runtime js wraps as `view.off(id)`. No-op for unknown ids. **Parameters:** * `subscriptionId` (string) **Returns:** void --- --- url: /docs/scripting/api/anchor.md description: Anchor – Anchor types for object placement --- # Anchor Anchor types for object placement ## Static Methods ### position() ```javascript Anchor.position(xOrVector: Vector3 | number, y?: number, z?: number): Object ``` Anchor at fixed world position **Parameters:** * `xOrVector` ([Vector3](/scripting/api/vector3) | number) - Position vector or X coordinate * `y` (number) *(optional)* - Y coordinate (if first param is number) * `z` (number) *(optional)* - Z coordinate (if first param is number) **Returns:** Object ### geoLocation() ```javascript Anchor.geoLocation(latitude: number, longitude: number, altitude?: number): void ``` Anchor at geographic location **Parameters:** * `latitude` (number) * `longitude` (number) * `altitude` (number) *(optional)* **Returns:** void ### camera() ```javascript Anchor.camera(xOrVector: Vector3 | number, y?: number, z?: number, lerpFactor?: number): void ``` Anchor relative to camera **Parameters:** * `xOrVector` ([Vector3](/scripting/api/vector3) | number) - Offset vector or X coordinate * `y` (number) *(optional)* - Y coordinate (if first param is number) * `z` (number) *(optional)* - Z coordinate (if first param is number) * `lerpFactor` (number) *(optional)* - Smoothing factor **Returns:** void ### currentPOV() ```javascript Anchor.currentPOV(xOrVector?: Vector3 | number, y?: number, z?: number, resetRotation?: boolean): Object ``` Anchor at user's current point of view (captures current camera position) **Parameters:** * `xOrVector` ([Vector3](/scripting/api/vector3) | number) *(optional)* - Offset vector or X coordinate * `y` (number) *(optional)* - Y coordinate (if first param is number) * `z` (number) *(optional)* - Z coordinate (if first param is number) * `resetRotation` (boolean) *(optional)* - If true, object faces forward instead of matching camera **Returns:** Object ### horizontalPlane() ```javascript Anchor.horizontalPlane(classification?: string, minWidth?: number, minHeight?: number): Object ``` Anchor on detected horizontal plane (floor, table, etc.) **Parameters:** * `classification` (string) *(optional)* - Surface type: "floor", "table", "seat", "ceiling" * `minWidth` (number) *(optional)* - Minimum plane width in meters * `minHeight` (number) *(optional)* - Minimum plane depth in meters **Returns:** Object ### verticalPlane() ```javascript Anchor.verticalPlane(classification?: string, minWidth?: number, minHeight?: number): void ``` Anchor on vertical plane **Parameters:** * `classification` (string) *(optional)* - "wall", "window", "door" * `minWidth` (number) *(optional)* * `minHeight` (number) *(optional)* **Returns:** void ### hand() ```javascript Anchor.hand(chirality: string, joint?: string, trackingScope?: string, lerpFactor?: number, providesDiscoveryHint?: boolean): Object ``` Anchor to user's hand (visionOS hand tracking) **Parameters:** * `chirality` (string) - "left" or "right" hand * `joint` (string) *(optional)* - Hand joint: "wrist", "thumbTip", "indexFingerTip", "middleFingerTip", etc. * `trackingScope` (string) *(optional)* - "full", "position", or "orientation" * `lerpFactor` (number) *(optional)* - Smoothing factor (0-1, lower = smoother) * `providesDiscoveryHint` (boolean) *(optional)* - Show visual hint for hand tracking **Returns:** Object ### image() ```javascript Anchor.image(imageUrl: string, physicalWidth: number, identifier?: string, orientation?: string, tracksContinuously?: boolean, hideIfTrackingLost?: boolean, providesDiscoveryHint?: boolean): Object ``` Anchor to image marker (image tracking) **Parameters:** * `imageUrl` (string) - URL to reference image for tracking * `physicalWidth` (number) - Real-world width of the image in meters * `identifier` (string) *(optional)* - Optional unique identifier * `orientation` (string) *(optional)* - Expected orientation: "horizontal" or "vertical" * `tracksContinuously` (boolean) *(optional)* - Continue tracking after initial detection * `hideIfTrackingLost` (boolean) *(optional)* - Hide object when image not visible * `providesDiscoveryHint` (boolean) *(optional)* - Show visual hint to find image **Returns:** Object --- --- url: /docs/scripting/api/objecttraits.md description: >- ObjectTraits – Builder for object traits (transform, materials, adjustments) Use with ObjectDescriptor.traits() to configure object properties --- # ObjectTraits Builder for object traits (transform, materials, adjustments) Use with ObjectDescriptor.traits() to configure object properties ## Instance Methods ### transform() ```javascript transform(transform: Transform): ObjectTraits ``` Set initial transform **Parameters:** * `transform` ([Transform](/scripting/api/transform)) - Transform object **Returns:** [ObjectTraits](/scripting/api/objecttraits) ### fittingBox() ```javascript fittingBox(size: number): ObjectTraits ``` Scale model to fit within a cubic bounding box **Parameters:** * `size` (number) - Maximum dimension in meters (model will fit inside this cube) **Returns:** [ObjectTraits](/scripting/api/objecttraits) ### pivot() ```javascript pivot(pivot: string): ObjectTraits ``` Set pivot adjustment **Parameters:** * `pivot` (string) - "automatic", "top", "center", or "bottom" **Returns:** [ObjectTraits](/scripting/api/objecttraits) ### opacity() ```javascript opacity(opacity: number): ObjectTraits ``` Set opacity adjustment **Parameters:** * `opacity` (number) - Opacity value (0.0 to 1.0) **Returns:** [ObjectTraits](/scripting/api/objecttraits) ### material() ```javascript material(materialOrId: string | Material, target?: Object): ObjectTraits ``` Apply a material – either a library reference by ID or an inline Material instance. **Parameters:** * `materialOrId` (string | [Material](/scripting/api/material)) - Library material ID, or a Material instance. * `target` (Object) *(optional)* - Optional target override. * `target.type` (string) *(optional)* - "allModels", "firstModel", or "selectedModels" * `target.models` (Array) *(optional)* - Array of model names (for selectedModels) * `target.materialSlots` (Array\) *(optional)* - Material slot indices **Returns:** [ObjectTraits](/scripting/api/objecttraits) ### set() ```javascript set(path: string, value: any): ObjectTraits ``` Directly set a property path in traits **Parameters:** * `path` (string) - Dot-separated path (e.g., "modelAdjustments.flatten") * `value` (any) - Value to set **Returns:** [ObjectTraits](/scripting/api/objecttraits) ### build() ```javascript build(): Object ``` Build and return the traits object **Returns:** Object --- --- url: /docs/scripting/api/objectdescriptor.md description: ObjectDescriptor – Descriptor for creating scene objects dynamically --- # ObjectDescriptor Descriptor for creating scene objects dynamically ## Instance Methods ### name() ```javascript name(value: string): ObjectDescriptor ``` Set the object name **Parameters:** * `value` (string) **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) ### anchor() ```javascript anchor(anchor: Object): ObjectDescriptor ``` Set the anchor **Parameters:** * `anchor` (Object) - Anchor descriptor created by Anchor static methods **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) ### traits() ```javascript traits(configureFn: function): ObjectDescriptor ``` Set object traits. Traits already on the descriptor (e.g. set by `createMesh({ materials: [...] })` sugar) are preserved – the builder loads from existing traits and adds on top, so chained `.material(...)` calls append to `customMaterials` instead of replacing. **Parameters:** * `configureFn` (function) - Function that configures traits **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) --- --- url: /docs/scripting/api/material.md description: >- Material – Chainable builder for an inline material. Six kinds: unlit, pbr, occlusion, customShader, materialX, video. Use the matching factory (Materia… --- # Material Chainable builder for an inline material. Six kinds: `unlit`, `pbr`, `occlusion`, `customShader`, `materialX`, `video`. Use the matching factory (`Material.unlit({...})`, etc.) or `new Material(kind)`. Properties map to the underlying material data model with web-standard names: - Colors: `color`, `emissive`, `sheenColor`, `specularColor` – flat tint. - Maps: `map`, `emissiveMap`, `roughnessMap`, `metalnessMap`, `normalMap`, `aoMap`, `specularMap`, `sheenMap`, `clearcoatMap`, `alphaMap` – textures accept either a URL/asset-id string or `{ texture, scale }`. - Scalars: `roughness`, `metalness`, `clearcoat`, `opacity`, `emissiveIntensity`. - Rendering: `opaque`, `transparent`, `opacityThreshold`, `blendMode`, `faceCulling`, `wireframe`, `writesDepth`, `readsDepth`. ## Static Methods ### unlit() ```javascript Material.unlit(opts?: Object): Material ``` Flat-shaded material. Renders the surface color regardless of scene lighting – useful for UI, billboards, vertex-colored point clouds, etc. **Parameters:** * `opts` (Object) *(optional)* - Any chainable setter name as a key: `color`, `map`, `opacity`, `alphaMap`, `disableToneMapping`, `faceCulling`, `blendMode`, `wireframe`, `target`, `name`. **Returns:** [Material](/scripting/api/material) ### pbr() ```javascript Material.pbr(opts?: Object): Material ``` Physically-Based material – the realistic-rendering default for surfaces that should respond to scene lighting. **Parameters:** * `opts` (Object) *(optional)* - `color`, `map`, `emissive`, `emissiveMap`, `emissiveIntensity`, `metalness`, `metalnessMap`, `roughness`, `roughnessMap`, `normalMap`, `aoMap`, `sheenColor`, `sheenMap`, `specularColor`, `specularMap`, `clearcoat`, `clearcoatMap`, `opacity`, `alphaMap`, `opacityThreshold`, plus rendering state. **Returns:** [Material](/scripting/api/material) ### occlusion() ```javascript Material.occlusion(opts?: Object): Material ``` Occlusion material – invisible itself but hides geometry behind it. Use for matte holdouts, real-world geometry masks, portals. **Parameters:** * `opts` (Object) *(optional)* - `receivesDynamicLighting`, `faceCulling`. **Returns:** [Material](/scripting/api/material) ### customShader() ```javascript Material.customShader(opts?: Object): Material ``` Custom shader material – author-supplied surface shader and/or geometry modifier from a compiled shader library. **Parameters:** * `opts` (Object) *(optional)* - `shaderLibrary`, `lightingModel`, `surfaceShader`, `geometryModifier`, plus general rendering state. **Returns:** [Material](/scripting/api/material) ### materialX() ```javascript Material.materialX(opts?: Object): Material ``` ShaderGraph material from a bundled `.usdz`. Reference by file path plus the `/Root/` path inside. **Parameters:** * `opts` (Object) *(optional)* - Typically use `.materialXAsset(urlOrId, name)` followed by `.setParameter(name, value, typeHint?)` calls. **Returns:** [Material](/scripting/api/material) ### video() ```javascript Material.video(): Material ``` Video texture material. Use `.videoSource(urlOrId)` for the playback source and `.videoOptions({ loops, streamContent, volume })` for playback config. **Returns:** [Material](/scripting/api/material) ## Instance Methods ### opacity() ```javascript opacity(): void ``` Material opacity. Values < 1 also flip `isOpaque` to `false` so the blending state matches. **Returns:** void ### materialXAsset() ```javascript materialXAsset(): void ``` MaterialX (ShaderGraph): URL or asset id of the .usdz, plus the path to the material inside it (e.g. `"/Root/MyMaterial"`). **Returns:** void ### setParameter() ```javascript setParameter(name: string, value: any, typeHint?: Object): Material ``` Set a constant value on a shader parameter. Works on `customShader` and `materialX` kinds – routes to the right options blob automatically. Warns and no-ops on kinds without shader parameters. For runtime variable bindings, use `entity.representation.bindMaterialParameter(name, variableId, opts?)`. **Example:** ```javascript Material.materialX('myAsset', '/Root/MyMat') .setParameter('intensity', 0.6) .setParameter('tintColor', '#FF00FF', { type: 'color' }); ``` **Parameters:** * `name` (string) - Shader parameter name (case-sensitive, must match the shader graph input or Metal uniform). * `value` (any) - Number, boolean, string, array (vec2/3/4), or color-like object (`'#RRGGBB'`, `{r,g,b,a?}`, `{colorSpace,hex}`). * `typeHint` (Object) *(optional)* - Override inferred type: `{ type: "float" | "int" | "vector2" | "vector3" | "vector4" | "color" | "boolean" | "string" }`. **Returns:** [Material](/scripting/api/material) ### clone() ```javascript clone(): void ``` Deep-copy the material with a fresh `id`. Use for the get → clone → tweak → apply pattern when overriding a library material for a single entity without mutating the library entry. **Returns:** void ### toString() ```javascript toString(): void ``` Useful per-kind summary for `console.log(mat)` – includes kind, id, name, and the meaningful state set on the material (channel colors, scalar values, shader params, etc.) so authors can see "what's on this material" at a glance. For the full serialized shape, use `JSON.stringify(mat, null, 2)`. **Returns:** void --- --- url: /docs/scripting/api/mesh.md description: Mesh – Scriptable dynamic mesh with CPU-driven vertex and index updates. --- # Mesh beta Scriptable dynamic mesh with CPU-driven vertex and index updates. Reached via `entity.representation.mesh` on representations created with `createMesh(...)`. Returns `null` for any other representation kind. Write methods (`writeVertices`, `writeIndices`) are designed to run every frame: each call issues a single `memcpy` from the JS TypedArray's backing buffer directly into the mesh's GPU-backed storage, with no intermediate copies. Combine with `setBounds` / `drawCount` (single-part shorthand) or `updatePart` (multi-part) to publish the new draw range each tick. ## Properties ### attributes * **Type:** any * Read-only summary of the mesh's attribute layout: an object keyed by attribute name with `{ semantic, format, storage }` fields. Useful for introspection ("does this mesh have a 'speed' attribute?") and for cross-engine devs who reach for `geometry.attributes` reflexively. ### drawCount * **Type:** number * Convenience getter/setter for single-part meshes. ### indexCapacity * **Type:** number * Maximum index count the mesh was allocated for. ### partCount * **Type:** number * Number of parts (draw ranges) on the mesh. ### vertexCapacity * **Type:** number * Maximum vertex count the mesh was allocated for. Read-only – the underlying buffer is sized at creation. To grow, create a new mesh. ## Methods ### recomputeBounds() ```javascript recomputeBounds(partIndex: number): BoundingBox | null ``` Recompute and apply min/max bounds for a part from its written vertex positions. Walks the `position` attribute, writes the computed `BoundingBox` back onto the part, and returns it so callers can inspect the result. Expensive on large meshes – walks `partVertexCount × 3` floats. Most authors should track running bounds incrementally in their own per-frame loop (cheap) and only call `recomputeBounds()` during prototyping or on a sparse cadence. Returns `null` if the mesh has no `position` attribute or `partIndex` is out of range. **Example:** ```javascript mesh.writeVertices("position", 0, scratch); mesh.recomputeBounds(0); // updates part 0's bounds to fit the written verts ``` **Parameters:** * `partIndex` (number) - Zero-based index of the part to recompute. **Returns:** [BoundingBox](/scripting/api/boundingbox) | null ### replaceParts() ```javascript replaceParts(parts: Array<{ indexCount: number, indexOffset?: number, topology?: string, materialIndex?: number, bounds: BoundingBox | { min: number[3], max: number[3] } }>): void ``` Replace the entire parts list. Use for dynamic-part-count meshes where each frame's GPU dispatch produces a variable triangle count (marching cubes, fluid surface, voxel terrain). Pair with an atomic counter in `outputBuffers` to size the part: `updatePart(...)` mutates one part in-place; `replaceParts(...)` swaps the entire list. The latter is the right choice when the part count itself changes between frames. **Example:** ```javascript const [emitted] = triCounter.readSync(); mesh.replaceParts([{ indexCount: emitted * 3, topology: 'triangle', bounds: { min: [-1, -1, -1], max: [1, 1, 1] } }]); ``` **Parameters:** * `parts` (Array<{ indexCount: number, indexOffset?: number, topology?: string, materialIndex?: number, bounds: BoundingBox | { min: number\[3], max: number\[3] } }>) **Returns:** void ### runCompute() ```javascript runCompute(kernel: Kernel, options: { uniforms?: Float32Array, outputs?: string[], outputIndices?: boolean, inputBuffers?: object, outputBuffers?: object, inputTextures?: object, outputTextures?: object, threadGroups: number[3], threadsPerThreadgroup?: number[3] }): void ``` Dispatch a compute kernel against this mesh's `storage: compute` attribute buffers, optionally the index buffer, plus arbitrary input / output buffers and textures. The renderer waits on the dispatch's command buffer before sampling the mesh. Check `environment.features.has('compute')` before calling – returns `false` on devices without GPU compute support. **MSL slot order is deterministic:** `buffer(0)` uniforms → `buffer(1..)` outputs (mesh attributes) → outputIndices → inputBuffers → outputBuffers. Textures live in the separate `texture(0..)` slot space: inputTextures then outputTextures. Match your MSL signature to this order. – `outputs` lists attribute names whose buffers the kernel writes (must be declared `storage: "compute"`); `outputIndices: true` exposes the mesh's index buffer for GPU writes; `inputBuffers` / `outputBuffers` bind `Buffer` handles to kernel slots; textures bind via `.read` / `.write` tokens. `threadsPerThreadgroup` defaults to a SIMD-aligned shape per dispatch dimensionality (1D → `[threadExecutionWidth, 1, 1]`; 2D → `[16, 16, 1]`; 3D → `[8, 8, 4]`). Authors who know their kernel's register / shared-memory budget can override. **Example:** ```javascript const kernel = await Kernel.fromSource({ source: kernelSource, functionName: 'marchCubes' }); const triCounter = Buffer.atomic(0); scene.on('render', function () { mesh.runCompute(kernel, { uniforms: new Float32Array([scene.time, 0.016]), outputs: ['position', 'normal'], outputIndices: true, outputBuffers: { triCount: triCounter }, threadGroups: [8, 8, 4] }); const [emitted] = triCounter.readSync(); mesh.drawCount = emitted * 3; }); ``` **Parameters:** * `kernel` ([Kernel](/scripting/api/kernel)) - handle from `Kernel.fromSource(...)` / `Kernel.fromAsset(...)`. * `options` ({ uniforms?: Float32Array, outputs?: string\[], outputIndices?: boolean, inputBuffers?: object, outputBuffers?: object, inputTextures?: object, outputTextures?: object, threadGroups: number\[3], threadsPerThreadgroup?: number\[3] }) **Returns:** void ### setBounds() ```javascript setBounds(bounds: BoundingBox | { min: number[3], max: number[3] }): void ``` Update the bounds of part 0. Shorthand for `updatePart(0, { bounds })` on single-part meshes. Accepts either a `BoundingBox` instance or a plain object `{ min: [x, y, z], max: [x, y, z] }`. **Example:** ```javascript mesh.setBounds(BoundingBox(Vector3(-1, 0, -1), Vector3(1, 2, 1))); mesh.setBounds({ min: [-1, 0, -1], max: [1, 2, 1] }); // also valid ``` **Parameters:** * `bounds` ([BoundingBox](/scripting/api/boundingbox) | { min: number\[3], max: number\[3] }) - New bounds for part 0. **Returns:** void ### updatePart() ```javascript updatePart(partIndex: number, options: { indexCount?: number, indexOffset?: number, topology?: string, materialIndex?: number, bounds?: BoundingBox | { min: number[3], max: number[3] } }): void ``` Update a part's metadata. Only the keys present in `options` are applied; everything else is left untouched. The typical per-frame call for a growing mesh sends new `bounds` and/or `indexCount` for part 0. For single-part meshes, the shorthands `mesh.drawCount = N` and `mesh.setBounds(b)` are equivalent to passing `{ indexCount: N }` and `{ bounds: b }` here. **Example:** ```javascript mesh.updatePart(0, { indexCount: 1234, bounds: { min: [-1, 0, -1], max: [1, 2, 1] } }); ``` **Parameters:** * `partIndex` (number) - Zero-based index into `parts`. * `options` ({ indexCount?: number, indexOffset?: number, topology?: string, materialIndex?: number, bounds?: BoundingBox | { min: number\[3], max: number\[3] } }) - Only present keys are applied. **Returns:** void ### writeIndices() ```javascript writeIndices(indexOffset: number, data: Uint32Array | Uint16Array): void ``` Write indices starting at slot `indexOffset`. The index count is derived from the TypedArray's length. `data` must be `Uint32Array` (the default index type) or `Uint16Array` if the mesh was created with `indexType: "uint16"`. **Example:** ```javascript mesh.writeIndices(0, new Uint32Array([0, 1, 2, 2, 1, 3])); ``` **Parameters:** * `indexOffset` (number) - First index slot to write into. * `data` (Uint32Array | Uint16Array) - Index data matching the mesh's `indexType`. **Returns:** void ### writeVertices() ```javascript writeVertices(name: string, vertexOffset: number, data: Float32Array | Uint8Array): void ``` Write packed elements of a single named attribute. Starts at vertex index `vertexOffset`. The vertex count is derived from the TypedArray's length (`array.length / componentsPerVertex`). `data` must be a JS TypedArray whose element type matches the attribute's format: `Float32Array` for `float` / `float2` / `float3` / `float4`, `Uint8Array` for `uchar4Normalized`. Bytes flow straight from the JS engine's buffer into the mesh via a single `memcpy` – no intermediate copy, no per-element trips. **Example:** ```javascript mesh.writeVertices("position", 0, new Float32Array([ -0.25, 0.0, 0.0, 0.25, 0.0, 0.0, 0.0, 0.4, 0.0 ])); ``` **Parameters:** * `name` (string) - Attribute name as declared in `createMesh`. * `vertexOffset` (number) - First vertex slot to write into (0 for a full overwrite). * `data` (Float32Array | Uint8Array) - Packed attribute data. The TypedArray must match the attribute's declared format. **Returns:** void --- --- url: /docs/scripting/api/functions.md --- # Global Functions ## wait() ```javascript wait(seconds: number): Promise ``` Wait for specified seconds **Parameters:** * `seconds` (number) - Seconds to wait **Returns:** Promise\ ## animateValue() ```javascript animateValue(options: Object): ValueAnimation ``` Create and start a value animation Automatically interpolates between from/to values based on their type. Supports numbers, Vector2, Vector3, Vector4, Color, and Rotation. Rotations use spherical interpolation (slerp) automatically. Animations clean up automatically when complete. For infinite animations, call destroy() to stop, or they clean up when the scene ends. **Example:** ```javascript // Fade in animateValue({ from: 0, to: 1, duration: 0.3, onUpdate: function(opacity) { entity.opacity = opacity; } }); ``` **Parameters:** * `options` (Object) - Animation configuration * `options.from` (number | [Vector2](/scripting/api/vector2) | [Vector3](/scripting/api/vector3) | [Vector4](/scripting/api/vector4) | [Color](/scripting/api/color) | [Rotation](/scripting/api/rotation)) - Start value * `options.to` (number | [Vector2](/scripting/api/vector2) | [Vector3](/scripting/api/vector3) | [Vector4](/scripting/api/vector4) | [Color](/scripting/api/color) | [Rotation](/scripting/api/rotation)) - End value (must match from type) * `options.duration` (number) *(optional)* - Duration in seconds (for curve timing) * `options.curve` (string) *(optional)* - Easing curve: "linear", "easeIn", "easeOut", "easeInOut" * `options.spring` (Object) *(optional)* - Spring timing (overrides duration/curve) * `options.spring.duration` (number) *(optional)* - Spring settle duration * `options.spring.bounce` (number) *(optional)* - Bounce amount (0 = no bounce, 1 = full bounce) * `options.bezier` (Array\) *(optional)* - Custom bezier curve \[p1x, p1y, p2x, p2y] * `options.delay` (number) *(optional)* - Delay before starting (seconds) * `options.repeatCount` (number) *(optional)* - Number of repeats (0 = play once, -1 = infinite) * `options.reverseOnRepeat` (boolean) *(optional)* - Reverse direction on each repeat (yoyo) * `options.autoStart` (boolean) *(optional)* - Start immediately * `options.onUpdate` (function) - Called each frame with the interpolated value * `options.onComplete` (function) *(optional)* - Called when animation finishes (not called for infinite) **Returns:** [ValueAnimation](/scripting/api/valueanimation) ## createBox() ```javascript createBox(width?: number, height?: number, depth?: number, cornerRadius?: number): ObjectDescriptor ``` Create a box primitive descriptor **Parameters:** * `width` (number) *(optional)* - Width in meters * `height` (number) *(optional)* - Height in meters * `depth` (number) *(optional)* - Depth in meters * `cornerRadius` (number) *(optional)* - Corner radius for rounded edges **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) ## createSphere() ```javascript createSphere(radius?: number): ObjectDescriptor ``` Create a sphere primitive **Parameters:** * `radius` (number) *(optional)* **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) ## createPlane() ```javascript createPlane(orientation: string | number, width?: number, height?: number, cornerRadius?: number): ObjectDescriptor ``` Create a plane primitive **Parameters:** * `orientation` (string | number) - "xz" (horizontal) or "xy" (vertical), or width if orientation omitted * `width` (number) *(optional)* * `height` (number) *(optional)* * `cornerRadius` (number) *(optional)* **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) ## createModel() ```javascript createModel(urlOrId: string): ObjectDescriptor ``` Create a 3D model object descriptor **Parameters:** * `urlOrId` (string) - HTTPS URL to .usdz/.reality file or asset ID from project **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) ## createMedia() ```javascript createMedia(kind: string, urlOrId: string, width?: number, aspectRatio?: number, options?: Object): ObjectDescriptor ``` Create a visual media object (image, video, or animated GIF) **Parameters:** * `kind` (string) - "image", "video", or "animatedGif" * `urlOrId` (string) - HTTPS URL or asset ID * `width` (number) *(optional)* - Width in meters * `aspectRatio` (number) *(optional)* - Aspect ratio (width/height) * `options` (Object) *(optional)* - Additional options * `options.cornerRadius` (number) *(optional)* - Relative corner radius (0-1) * `options.doubleSided` (boolean) *(optional)* - Render both sides * `options.showLoading` (boolean) *(optional)* - Show loading placeholder * `options.immersive` (boolean) *(optional)* - Enable immersive rendering * `options.video` (Object) *(optional)* - Video playback options * `options.video.volume` (number) *(optional)* - Volume (0-1) * `options.video.loops` (boolean) *(optional)* - Loop playback * `options.video.stream` (boolean) *(optional)* - Stream content **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) ## createImage() ```javascript createImage(urlOrId: string, width?: number, aspectRatio?: number, options?: Object): ObjectDescriptor ``` Create an image **Parameters:** * `urlOrId` (string) - HTTPS URL or asset ID * `width` (number) *(optional)* - Width in meters * `aspectRatio` (number) *(optional)* - Aspect ratio (width/height) * `options` (Object) *(optional)* - Additional options (cornerRadius, doubleSided, showLoading, immersive) **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) ## createVideo() ```javascript createVideo(urlOrId: string, width?: number, aspectRatio?: number, options?: Object): ObjectDescriptor ``` Create a video **Parameters:** * `urlOrId` (string) - HTTPS URL or asset ID * `width` (number) *(optional)* - Width in meters * `aspectRatio` (number) *(optional)* - Aspect ratio (width/height) * `options` (Object) *(optional)* - Additional options (cornerRadius, doubleSided, showLoading, immersive, video) **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) ## createAnimatedGif() ```javascript createAnimatedGif(urlOrId: string, width?: number, aspectRatio?: number, options?: Object): ObjectDescriptor ``` Create an animated GIF **Parameters:** * `urlOrId` (string) - HTTPS URL or asset ID * `width` (number) *(optional)* - Width in meters * `aspectRatio` (number) *(optional)* - Aspect ratio (width/height) * `options` (Object) *(optional)* - Additional options (cornerRadius, doubleSided, showLoading, immersive) **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) ## createContainer() ```javascript createContainer(children: Array): ObjectDescriptor ``` Create a container/composition **Parameters:** * `children` (Array<[ObjectDescriptor](/scripting/api/objectdescriptor)>) - Child object descriptors **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) ## createPanel() ```javascript createPanel(content: Object, options?: Object): void ``` Build a UI panel descriptor wrapping the given root view. Pass to `scene.createEntity(panel)` to instantiate. **Parameters:** * `content` (Object) - Root View descriptor (typically a UI.vStack/hStack/zStack). * `options` (Object) *(optional)* * `options.panelSize` (any | any | Array\ | any) *(optional)* - Panel dimensions. Use `'auto'` (default – sized to content), `'fill'` (fill parent), or an explicit size in **points** (≈ 1360 pt/m): either `[width, height]` or `{ width, height }`. E.g. `[400, 240]` ≈ 0.29m × 0.18m. **Returns:** void ## createMesh() beta ```javascript createMesh(opts: Object): ObjectDescriptor ``` Create a scriptable dynamic mesh with CPU-driven vertex/index updates. Returns an ObjectDescriptor – pass to `scene.createEntity(...)`. After the entity loads, reach the mesh via `entity.representation.mesh` and write buffer data with `mesh.writeVertices(...)` / `mesh.writeIndices(...)`. Bytes flow straight from `Float32Array` / `Uint32Array` into the GPU-side buffer – single memcpy per call, no per-element overhead. **Example:** ```javascript // Triangle, single static buffer, no material override (using shorthand). const entity = await scene.createEntity(createMesh({ vertexCapacity: 3, indexCapacity: 3, attributes: { position: "float3" }, parts: [{ indexCount: 3, bounds: { min: [-0.5, -0.1, -0.1], max: [0.5, 0.5, 0.1] } }] })); const mesh = entity.representation.mesh; mesh.writeVertices("position", 0, new Float32Array([ -0.25, 0.0, 0.0, 0.25, 0.0, 0.0, 0.0, 0.4, 0.0 ])); mesh.writeIndices(0, new Uint32Array([0, 1, 2])); ``` **Parameters:** * `opts` (Object) * `opts.vertexCapacity` (number) - Max vertices the mesh allocates space for. Required. * `opts.indexCapacity` (number) - Max indices the mesh allocates space for. Required (must be > 0). For draw orders that would otherwise be non-indexed, set this to `vertexCapacity` and write sequential indices `[0, 1, 2, …]` once. * `opts.indexType` (string) *(optional)* - "uint32" or "uint16". * `opts.attributes` (Object\) - Named attributes. Either a string shorthand or an explicit object. \- **Shorthand** (static, semantic auto-derived from name): `position: "float3"`, `color: "uchar4Normalized"`. \- **Explicit form**: `{ semantic, format, storage }`. `semantic` ∈ "position" | "normal" | "tangent" | "bitangent" | "color" | "uv0" … "uv7" | "unspecified" (defaults to the attribute name when omitted). `format` ∈ "float" | "float2" | "float3" | "float4" | "uchar4Normalized". `storage` ∈ "static" (default – write once at setup) | "dynamic" (CPU-writable via `writeVertices`, per-frame) | "compute" (reserved for a future runtime version; rejected today). * `opts.parts` (Array\) - Draw ranges. Each part: `{ indexOffset?, indexCount, topology?, materialIndex?, bounds }`. `topology` ∈ "triangle" (default) | "triangleStrip" | "line" | "lineStrip" | "point". `bounds`: `{ min: [x,y,z], max: [x,y,z] }`. Must contain every vertex the part draws. * `opts.materials` (Array\) *(optional)* - Material reference ids from the experience's material library. Each part's `materialIndex` indexes into this array. If empty, a default material is used. * `opts.interleavedGroups` (Array\>) *(optional)* - Opt-in attribute grouping. Each inner array is a set of attribute names that share one underlying buffer (interleaved layout). Default is one buffer per attribute. Use case: spatial-drawing-style stroke extension where every vertex carries position+normal+color together — interleaved gives better GPU cache locality. All attributes in a group must share `storage` state (all "static", all "dynamic", etc.). Per-attribute write API (`mesh.writeVertices(name, ...)`) stays the same; the bridge uses a strided memcpy under the hood. **Returns:** [ObjectDescriptor](/scripting/api/objectdescriptor) --- --- url: /docs/scripting/api/audiostream.md description: >- AudioStream – Streaming audio player for real-time audio playback Used for continuous audio streams like OpenAI Realtime API --- # AudioStream Streaming audio player for real-time audio playback Used for continuous audio streams like OpenAI Realtime API Buffers up to 30 seconds of audio internally. For longer responses, playback drains the buffer as new audio arrives. ## Properties ### isDrained * **Type:** boolean * Whether the audio buffer has finished playing Use this to wait for playback to complete before resuming microphone **Example:** ```javascript function waitForPlayback() { if (!stream.isDrained) { setTimeout(waitForPlayback, 100); } else { scene.microphone.start(); } } ``` ## Methods ### append() ```javascript append(base64: string): void ``` Append base64-encoded audio data to the stream buffer **Example:** ```javascript ws.onmessage = function(event) { var msg = JSON.parse(event.data); if (msg.type === 'response.audio.delta') { stream.append(msg.delta); } }; ``` **Parameters:** * `base64` (string) - Base64-encoded PCM16 audio data **Returns:** void ### stop() ```javascript stop(): void ``` Stop playback and release resources **Example:** ```javascript stream.stop(); ``` **Returns:** void --- --- url: /docs/scripting/api/microphone.md description: Microphone – Microphone audio streaming Access via scene.microphone --- # Microphone Microphone audio streaming Access via `scene.microphone` **Example:** ```javascript scene.microphone.configure({ sampleRate: 24000, silenceThreshold: 0.01 }); scene.microphone.onData = function(base64) { // Send to speech API }; scene.microphone.start(); ``` ## Properties ### isStreaming * **Type:** boolean * Whether the microphone is currently streaming ### onData * **Type:** any * Callback for audio data chunks (base64-encoded PCM16) ### onError * **Type:** any * Callback for errors (permission denied, etc.) ## Methods ### configure() ```javascript configure(options: Array): void ``` Configure microphone settings * sampleRate: Output sample rate (default: 24000) - silenceThreshold: RMS threshold to skip quiet audio, 0 to disable (default: 0) - highpassFrequency: Highpass filter cutoff in Hz, 0 to disable (default: 0) **Parameters:** * `options` (Array\) - Configuration object **Returns:** void ### start() ```javascript start(): void ``` Start capturing audio **Returns:** void ### stop() ```javascript stop(): void ``` Stop capturing audio **Returns:** void --- --- url: /docs/scripting/api/httpclient.md description: >- HTTPClient – HTTP client for making network requests Supports GET, POST, and custom requests with authentication --- # HTTPClient HTTP client for making network requests Supports GET, POST, and custom requests with authentication ## Methods ### get() ```javascript get(url: string, callback: any): void ``` Make a GET request **Example:** ```javascript http.get('https://api.example.com/data', function(result, error) { if (error) { console.error(error); return; } console.log(result.status, result.data); }); ``` **Parameters:** * `url` (string) - Request URL * `callback` (any) - Callback function (result, error) **Returns:** void ### post() ```javascript post(url: string, body: string, callback: any): void ``` Make a POST request **Example:** ```javascript http.post('https://api.example.com/data', JSON.stringify({ name: 'test' }), function(result, error) { if (error) { console.error(error); return; } console.log(result.status, result.data); }); ``` **Parameters:** * `url` (string) - Request URL * `body` (string) - Request body string * `callback` (any) - Callback function (result, error) **Returns:** void ### request() ```javascript request(url: string, options: Array, callback: any): void ``` Make a custom HTTP request * method: HTTP method (GET, POST, PUT, DELETE, etc.) - headers: Dictionary of HTTP headers - body: Request body string - token: Bearer token for Authorization header - username: Username for Basic auth - password: Password for Basic auth - responseType: Response format - "json" (default), "text", or "base64" **Example:** ```javascript // JSON response (default) var response = await http.request('https://api.example.com/data', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: 'test' }), token: 'my-api-key' }); console.log(response.data.results); // Binary response as base64 (for audio, images, etc.) var audio = await http.request('https://api.openai.com/v1/audio/speech', { method: 'POST', token: 'sk-...', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'tts-1', input: 'Hello world', voice: 'nova', response_format: 'pcm' }), responseType: 'base64' }); await entity.playAudioBuffer(audio.data, { sampleRate: 24000 }); ``` **Parameters:** * `url` (string) - Request URL * `options` (Array\) - Request options * `callback` (any) - Callback function (result, error) **Returns:** void --- --- url: /docs/scripting/api/websocket.md description: WebSocket – WebSocket connection for real-time communication --- # WebSocket WebSocket connection for real-time communication **Example:** ```javascript var ws = websocket.connect('wss://api.example.com/ws', { headers: { 'Authorization': 'Bearer token' } }); ws.onOpen = function() { console.log('Connected'); ws.send(JSON.stringify({ type: 'hello' })); }; ws.onMessage = function(event) { var data = JSON.parse(event.data); console.log('Received:', data); }; ws.onError = function(event) { console.error('Error:', event.error); }; ws.onClose = function() { console.log('Disconnected'); }; // Later... ws.close(); ``` ## Properties ### onClose * **Type:** any * Called when connection closes ### onError * **Type:** any * Called on error (event.error contains the Error) ### onMessage * **Type:** any * Called when message received (event.data contains the message) ### onOpen * **Type:** any * Called when connection opens ## Methods ### close() ```javascript close(): void ``` Close the connection **Returns:** void ### send() ```javascript send(message: string): void ``` Send a message **Parameters:** * `message` (string) - String message to send **Returns:** void --- --- url: /docs/scripting/api/websocketmanager.md description: WebSocketManager – WebSocket connection manager Access via websocket.connect() --- # WebSocketManager WebSocket connection manager Access via `websocket.connect()` ## Methods ### connect() ```javascript connect(url: string, options: Array): WebSocket ``` Create a new WebSocket connection * protocols: Array of subprotocols - headers: HTTP headers dictionary - reconnectInterval: Seconds between reconnect attempts (default: 2) - maxReconnectAttempts: Maximum reconnection attempts (default: 5) **Parameters:** * `url` (string) - WebSocket URL (ws:// or wss://) * `options` (Array\) - Connection options **Returns:** [WebSocket](/scripting/api/websocket) --- --- url: /docs/scripting/api/entityanimation.md description: >- EntityAnimation – Entity animation factory - create animation descriptors for use with entity.play() --- # EntityAnimation Entity animation factory - create animation descriptors for use with entity.play() ## Static Methods ### to() ```javascript EntityAnimation.to(toProperties: Object, duration: number, options?: Object): EntityAnimation ``` Animate to target properties **Example:** ```javascript // Animate box upward box.play(EntityAnimation.to( { position: Vector3(0, 2, 0) }, 1.0, { timingFunction: "easeOut" } )); ``` **Parameters:** * `toProperties` (Object) - Target properties (position, rotation, scale, opacity) * `duration` (number) - Duration in seconds * `options` (Object) *(optional)* - Animation options (timingFunction, delay, etc.) **Returns:** [EntityAnimation](/scripting/api/entityanimation) ### fromTo() ```javascript EntityAnimation.fromTo(fromProperties: Object, toProperties: Object, duration: number, options?: Object): EntityAnimation ``` Animate from starting to target properties **Parameters:** * `fromProperties` (Object) - Starting properties * `toProperties` (Object) - Target properties * `duration` (number) - Duration in seconds * `options` (Object) *(optional)* - Animation options **Returns:** [EntityAnimation](/scripting/api/entityanimation) ### by() ```javascript EntityAnimation.by(byProperties: Object, duration: number, options?: Object): EntityAnimation ``` Animate by relative values **Parameters:** * `byProperties` (Object) - Relative change values * `duration` (number) - Duration in seconds * `options` (Object) *(optional)* - Animation options **Returns:** [EntityAnimation](/scripting/api/entityanimation) ### fromBy() ```javascript EntityAnimation.fromBy(fromProperties: Object, byProperties: Object, duration: number, options?: Object): EntityAnimation ``` Animate from starting properties by relative values **Parameters:** * `fromProperties` (Object) - Starting properties * `byProperties` (Object) - Relative change values * `duration` (number) - Duration in seconds * `options` (Object) *(optional)* - Animation options **Returns:** [EntityAnimation](/scripting/api/entityanimation) ### spin() ```javascript EntityAnimation.spin(revolutions: number, duration: number, options?: Object): EntityAnimation ``` Spin animation - rotate around local axis **Parameters:** * `revolutions` (number) - Number of full rotations * `duration` (number) - Duration in seconds * `options` (Object) *(optional)* - Animation options * `options.axis` (Array\) *(optional)* - Local rotation axis \[x, y, z] **Returns:** [EntityAnimation](/scripting/api/entityanimation) ### orbit() ```javascript EntityAnimation.orbit(config: Object, duration: number, options?: Object): EntityAnimation ``` Orbit animation - rotate around a point **Parameters:** * `config` (Object) - Orbit configuration * `config.axis` (Array\ | [Vector3](/scripting/api/vector3)) *(optional)* - Rotation axis * `config.rotationCount` (number) *(optional)* - Number of orbits * `config.clockwise` (boolean) *(optional)* - Spin direction * `config.orientToPath` (boolean) *(optional)* - Face movement direction * `config.startTransform` (Object) *(optional)* - Starting offset transform * `duration` (number) - Duration in seconds * `options` (Object) *(optional)* - Animation options **Returns:** [EntityAnimation](/scripting/api/entityanimation) ### keyframes() ```javascript EntityAnimation.keyframes(frames: Array, duration: number, options?: Object): EntityAnimation ``` Keyframe animation - animate through multiple property states **Parameters:** * `frames` (Array\) - Array of property states * `duration` (number) - Total duration in seconds * `options` (Object) *(optional)* - Animation options * `options.tweenMode` (string) *(optional)* - "linear" or "hold" **Returns:** [EntityAnimation](/scripting/api/entityanimation) ### model() ```javascript EntityAnimation.model(name?: string, options?: Object): EntityAnimation ``` Play embedded model animation (USDZ animations) **Parameters:** * `name` (string) *(optional)* - Animation name (optional, plays default if omitted) * `options` (Object) *(optional)* - Options * `options.trimStart` (number) *(optional)* - Trim from start (seconds) * `options.trimEnd` (number) *(optional)* - Trim from end (seconds) * `options.duration` (number) *(optional)* - Override duration **Returns:** [EntityAnimation](/scripting/api/entityanimation) ### group() ```javascript EntityAnimation.group(animations: Array, options?: Object): EntityAnimation ``` Group multiple animations to play together **Parameters:** * `animations` (Array<[EntityAnimation](/scripting/api/entityanimation)>) - Array of EntityAnimation objects * `options` (Object) *(optional)* - Animation options (applied to group) **Returns:** [EntityAnimation](/scripting/api/entityanimation) ### emphasize() ```javascript EntityAnimation.emphasize(style: string, duration: number, options?: Object): EntityAnimation ``` Emphasize animation - attention-grabbing effect **Parameters:** * `style` (string) - "bounce", "pulse", "wiggle", etc. * `duration` (number) - Duration in seconds * `options` (Object) *(optional)* - Animation options **Returns:** [EntityAnimation](/scripting/api/entityanimation) --- --- url: /docs/scripting/api/valueanimation.md description: ValueAnimation – Value animations with spring and curve timing --- # ValueAnimation Value animations with spring and curve timing Supports animating floats, vectors, colors, transforms, and rotations with automatic type detection and appropriate interpolation (lerp for most types, slerp for rotations). **Example:** ```javascript // Animate opacity animateValue({ from: 0, to: 1, duration: 0.5, onUpdate: function(value) { entity.opacity = value; } }); // Spring animation on position animateValue({ from: Vector3(0, 0, 0), to: Vector3(0, 2, 0), spring: { duration: 0.6, bounce: 0.3 }, onUpdate: function(pos) { entity.position = pos; } }); // Transform animation (position + rotation + scale in one) animateValue({ from: entity.transform, to: Transform({ position: Vector3(0, 0.3, 0), rotation: Rotation(0, Math.PI, 0), scale: Vector3(1, 1, 1) }), spring: { duration: 0.8, bounce: 0.5 }, onUpdate: function(t) { entity.transform = t; } }); // Color transition animateValue({ from: Color.red(), to: Color.blue(), duration: 1, curve: 'easeInOut', onUpdate: function(color) { // apply color } }); // Rotation with automatic slerp animateValue({ from: Rotation(0, 0, 0), to: Rotation(0, Math.PI, 0), duration: 0.8, onUpdate: function(rot) { entity.rotation = rot; } }); ``` ## Properties ### isPlaying * **Type:** boolean * Whether the animation is currently playing ## Methods ### destroy() ```javascript destroy(): void ``` Stop and clean up the animation permanently Animations clean up automatically when they complete. Call destroy() only for infinite animations or early termination. **Returns:** void ### start() ```javascript start(): void ``` Start or resume the animation **Returns:** void ### stop() ```javascript stop(): void ``` Pause the animation (can be resumed with start()) **Returns:** void --- --- url: /docs/scripting/api/sharedactivity.md description: >- SharedActivity – Shared activity state for multi-user sessions (SharePlay, WebSocket, etc.) --- # SharedActivity Shared activity state for multi-user sessions (SharePlay, WebSocket, etc.) Always present on `experience.sharedActivity`. When no session is active, `isActive` and `isOwner` return `false`, `participantCount` returns `0`. **Example:** ```javascript if (experience.sharedActivity.isActive && experience.sharedActivity.isOwner) { // Only the owner connects to external services openai.connect(); } experience.sharedActivity.onOwnershipChanged = function(isOwner) { if (isOwner) { openai.connect(); } else { openai.disconnect(); } }; ``` ## Properties ### isActive * **Type:** boolean * Whether a shared activity session is currently active ### isOwner * **Type:** boolean * Whether the local participant is the session owner (presenter) ### onMessage * **Type:** any * Called when a script message is received from another participant **Example:** ```javascript experience.sharedActivity.onMessage = function(data) { console.log('Received:', data.type); }; ``` ### onOwnershipChanged * **Type:** any * Called when session ownership changes (session start, transfer, owner leaves, session end) **Example:** ```javascript experience.sharedActivity.onOwnershipChanged = function(isOwner) { if (isOwner) { startAI(); } else { stopAI(); } }; ``` ### participantCount * **Type:** number * Number of remote participants (excludes local participant) ## Methods ### send() ```javascript send(data: any): true ``` Send data to all participants (reliable delivery). object or array; primitives are rejected). per-message cap, and enqueued for transmission. `false` on any validation failure – check the console for the specific reason. Existing callers that ignore the return value continue to work. **Example:** ```javascript experience.sharedActivity.send({ type: 'orb_state', state: 'speaking' }); ``` **Parameters:** * `data` (any) - JSON-serializable data to send (must be a plain **Returns:** true ### sendFrequent() ```javascript sendFrequent(data: any): true ``` Send data to all participants (unreliable delivery, suitable for high-frequency updates). for the full validation contract. **Example:** ```javascript experience.sharedActivity.sendFrequent({ type: 'audio_level', level: 0.7 }); ``` **Parameters:** * `data` (any) - JSON-serializable data to send. **Returns:** true --- --- url: /docs/webxr.md description: >- Scenery on the web with the WebXR Viewer: open experiences in the browser with no install, plus what works, platform support, and publishing. --- # Scenery on the Web (WebXR) The **WebXR Viewer** brings your experiences to the browser – no app install needed. Every experience with WebXR enabled gets a shareable link that works across devices: * **Android** – full immersive AR in Chrome. * **Desktop** – an interactive 3D preview in any modern browser (orbit the scene from any angle). * **iOS** – continues to use App Clips for the best native experience. ::: warning Beta The WebXR Viewer is in **beta**. Browser-based AR is improving quickly but isn't yet on par with the native experience, so **test before sharing** with your audience. ::: ## Enabling WebXR WebXR is available to every creator – you opt in per experience: 1. In the editor's [Experience settings](/editor/experience-settings#platforms-webxr), turn on **WebXR support**. 2. A viewer URL is generated automatically (`viewer.scenery.app/{experienceId}`). 3. Share it however you like – QR code, social media, email, or embedded on your own site. ![The WebXR Support toggle in the experience settings, with the Beta badge](./images/webxr-support.jpg) *Enable WebXR Support in the experience settings.* On a campaign landing page, visitors see an action that matches their device: Android users get **View in AR**, desktop users get **View in 3D**, and iOS users get the full App Clip. ## What works Most of what you build in the editor carries straight over to the web: * **3D models** with full PBR materials and **animations** (keyframe, skeletal, morph targets / blend shapes). * **Interactions** – tap, proximity, timers, visibility triggers, and the rest of the event system. * **Spatial and ambient audio.** * **Video textures** (MP4, WebM). * **Physics** – gravity, collisions, impulses. * **Face tracking** (Android). * **Image tracking** (Chrome, with an experimental browser flag enabled). * **Variables and conditional logic.** * **Multi-segment experiences** with scene transitions. ## What works differently A few things behave slightly differently from the native iOS / visionOS experience: * **3D model fidelity** – most models render accurately; some advanced surface features (e.g. subdivision surfaces) may look slightly different. This improves with each update. * **Face tracking** – uses a different tracking engine than the native depth camera. Good, but not identical. * **Image tracking** – available in Chrome, but requires enabling an experimental browser flag (off by default). * **Physics** – a different physics engine under the hood; very close, but not identical to the native app. ## Not yet supported These native features don't run in the viewer yet. If your experience relies on one, it may not render as expected: * Custom **scripting**. * **ShaderGraph / custom shader** materials (they use platform-specific shader nodes with no web equivalent yet). * **Particle systems.** * **Location-based** experiences (GPS / visual positioning). * **Play-space-bound** experiences. * **Immersive 180° / 360° video** (renders as flat video). * **Haptic feedback.** * **Audio-reactive effects** (beat detection, frequency-driven animation). * **Transparent video** (alpha channel). * **Draw order groups.** * **In-viewer recording** (screenshots / screen recording). * **Post-processing effects.** ## On the roadmap We're actively expanding the viewer: custom scripting, play-space-bound experiences, Meta Quest support, haptic feedback, immersive 180° / 360° video, transparent video, draw order groups, post-processing effects, and in-viewer recording. ## Where it runs | | AR mode | 3D viewer | |---|---|---| | **Chrome (Android)** | Full AR | Full 3D | | **Chrome (desktop)** | – | Full 3D | | **Safari (iOS / macOS)** | Use the native Scenery app | – | | **Firefox** | – | Limited 3D | | **Meta Quest** | Coming soon | Coming soon | Web and desktop correspond to the **Desktop** device category in [per-device anchoring](/editor/anchors#per-device-anchoring-device-configuration), so you can give web visitors a placement that suits a flat screen.