diff --git a/README.md b/README.md index ecea7011..0efbcc22 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Untold Engine is built for developers and teams who: Creator & Lead Developer: https://www.haroldserrano.com -[![untoldengine-image](/docs/images/engine-highlight-5.png)](https://vimeo.com/1176995991?fl=ip&fe=ec) +[![untoldengine-image](/docs/images/UntoldEngine-features.png)](https://vimeo.com/1176995991?fl=ip&fe=ec) Click to Play @@ -130,8 +130,22 @@ Engine as a package dependency. ## Getting Started -To create your own XR, 3D, or spatial visualization app using Untold Engine, see -[Getting Started](https://untoldengine.github.io/UntoldEngine/API/GettingStarted/). +To create your own XR, 3D, or spatial visualization app using Untold Engine, +start with the documentation path that matches where you are: + +- **Build a complete Vision Pro app:** follow the + [Archviz To Vision Pro learning path](https://untoldengine.github.io/UntoldEngine/LearningPaths/ArchvizToVisionPro/) + to take a Blender archviz model into a standalone visionOS project. +- **Learn the engine API through focused demos:** use the + [Tutorials](https://untoldengine.github.io/UntoldEngine/Tutorials/) + to understand Starter Demo, lighting, rendering quality, exporter workflows, + scene channels, light portals, XR input, and performance diagnostics. +- **Create your own project from scratch:** see + [Getting Started](https://untoldengine.github.io/UntoldEngine/API/GettingStarted/) + and [Create A New Xcode Project](https://untoldengine.github.io/UntoldEngine/Tutorials/CreateXcodeProjectTutorial/). + +If your team is evaluating Untold Engine and needs an engine capability that is +not currently available, see [Commercial Use, Sponsored Features, and Support](COMMERCIAL.md). ## Core Direction @@ -145,8 +159,6 @@ Untold Engine is built around three focused goals: --- -![untoldengine-image-2](/docs/images/engine-highlight-7.png) - ## Example Use Cases Untold Engine is well-suited for: @@ -307,6 +319,9 @@ modifications, sponsored engine features, priority support, or custom terms. - **Priority support / retainers** — get focused help with engine integration, rendering issues, performance, and production use. +If your team needs an engine feature that is not currently available, contact +[Harold Serrano](https://www.haroldserrano.com/contact) to discuss sponsored feature development, private engine work, commercial licensing, or ongoing support. + See [COMMERCIAL.md](COMMERCIAL.md) for commercial licensing details. --- diff --git a/docs/API/GettingStarted.md b/docs/API/GettingStarted.md index 50a1c912..ea1de0f8 100644 --- a/docs/API/GettingStarted.md +++ b/docs/API/GettingStarted.md @@ -141,9 +141,10 @@ private func configureEngineSystems() { gameMode = true // Register XR input gestures - InputSystem.shared.registerXREvents() - InputSystem.shared.setXRSpatialPickingBackendPreference(.octreeGPUPreferred) - InputSystem.shared.setXRTwoHandRotateAxisMode(.dynamicSnapped) + registerXREvents() + setInput(.xr(.pickingBackend(.octreeGPUPreferred))) + setInput(.xr(.twoHandRotateAxisMode(.dynamicSnapped))) + setInput(.xr(.sceneReady(true))) // Enable post processing and anti-aliasing setRendering(.postProcessing(.enabled)) diff --git a/docs/API/UsingPhysicsSystem.md b/docs/API/UsingPhysicsSystem.md index 4e550a1e..6061486a 100644 --- a/docs/API/UsingPhysicsSystem.md +++ b/docs/API/UsingPhysicsSystem.md @@ -1,96 +1,155 @@ -# Enabling Physics System in Untold Engine +# Physics System -The physics system in the Untold Engine enables realistic simulations such as gravity, forces, and dynamic interactions. While collision support is still under development, this guide will walk you through adding physics to your entities. +Untold Engine has two physics layers: -## How to Enable Physics +- The built-in kinetics system handles mass, gravity scale, velocity, forces, impulses, damping, and steering-style movement. +- The pluggable physics backend layer adds rigid bodies, colliders, contacts, triggers, activation events, and backend-owned simulation when a backend plugin is installed. -### Step 1: Create an Entity +Use the built-in kinetics helpers for lightweight motion and gameplay steering. Use `RigidBodyComponent` and `ColliderComponent` when an external backend should own collision-aware simulation. -Start by creating an entity that represents the object you want to add physics to. +## Built-In Kinetics + +Create an entity, load its mesh, and enable kinetics: ```swift -let redPlayer = createEntity() +let player = createEntity() + +setEntityMeshAsync(entityId: player, filename: "redplayer", withExtension: "untold") { success in + guard success else { return } + setEntityKinetics(entityId: player) + setMass(entityId: player, mass: 0.5) + setGravityScale(entityId: player, gravityScale: 1.0) +} ``` ---- -### Step 2: Link a Mesh to the Entity -Next, load your model’s mesh file and link it to the entity. This step visually represents your entity in the scene. +Apply forces, impulses, or direct velocity changes from update code: ```swift -setEntityMesh(entityId: redPlayer, filename: "redplayer", withExtension: "untold") +applyForce(entityId: player, force: simd_float3(0.0, 0.0, 5.0)) +applyLinearImpulse(entityId: player, direction: simd_float3(0.0, 1.0, 0.0), magnitude: 3.0) +setLinearVelocity(entityId: player, velocity: simd_float3(0.0, 0.0, -2.0)) ``` ---- -### Step 3: Enable Physics on the Entity -Activate the physics simulation for your entity using the setEntityKinetics function. This function prepares the entity for movement and dynamic interaction. +Useful runtime helpers include: ```swift -setEntityKinetics(entityId: redPlayer) +getMass(entityId: player) +getVelocity(entityId: player) +setVelocity(entityId: player, velocity: simd_float3.zero) +clearForces(entityId: player) +clearVelocity(entityId: player) +clearAngularVelocity(entityId: player) +pausePhysicsComponent(entityId: player, isPaused: true) +isPhysicsComponentPaused(entityId: player) ``` ---- -#### Step 4: Configure Physics Properties -You can customize the entity’s physics behavior by defining its mass and gravity scale: +Forces accumulate until the physics update consumes them. Apply forces only when gameplay intends to push the entity. + +## Damping And Speed Limits -- Mass: Determines the force needed to move the object. Heavier objects require more force. -- Gravity Scale: Controls how strongly gravity affects the entity (default is 0.0). +The physics system includes helpers for controlling velocity without writing the integration loop yourself. ```swift -setMass(entityId: redPlayer, mass: 0.5) -setGravityScale(entityId: redPlayer, gravityScale: 1.0) +clampLinearSpeed(entityId: player, minSpeed: 0.0, maxSpeed: 4.0) +applyLinearDamping(entityId: player, dampingFactor: 0.92, deltaTime: deltaTime) + +setAngularVelocity(entityId: player, angularVelocity: simd_float3(0.0, 2.0, 0.0)) +clampAngularSpeed(entityId: player, maxAngularSpeed: 3.0) +applyAngularDamping(entityId: player, dampingFactor: 0.9, deltaTime: deltaTime) ``` ---- -#### Step 5: Apply Forces (Optional) -You can apply a custom force to the entity for dynamic movement. This is useful for simulating actions like jumps or pushes. +## Steering + +The steering system calculates movement forces on top of the physics layer: ```swift -applyForce(entityId: redPlayer, force: simd_float3(0.0, 0.0, 5.0)) +steerSeek( + entityId: player, + targetPosition: simd_float3(0.0, 0.0, 5.0), + maxSpeed: 2.0, + deltaTime: deltaTime +) ``` -> Note: Forces are applied per frame. To avoid unintended behavior, only apply forces when necessary. +Additional helpers include: + +- `steerFlee(entityId:threatPosition:maxSpeed:deltaTime:)` +- `steerArrive(entityId:targetPosition:maxSpeed:slowingRadius:deltaTime:)` +- `steerPursuit(entityId:targetEntity:maxSpeed:deltaTime:)` +- `steerFollowPath(entityId:path:maxSpeed:deltaTime:)` +- `steerAvoidObstacles(entityId:obstacles:avoidanceRadius:maxSpeed:deltaTime:)` ---- +See [Steering System](UsingSteeringSystem.md) for details. -#### Step 6: Use the Steering System -For advanced movement behaviors, use the Steering System helpers to steer entities toward or away from targets. The high-level helpers calculate steering forces and apply them through the physics system. +## Backend Rigid Bodies And Colliders -Example: Steering Toward a Position +External physics backends consume engine-owned ECS components. An entity must have `RigidBodyComponent`, `ColliderComponent`, and a transform to be owned by the backend coordinator. ```swift -steerSeek(entityId: redPlayer, targetPosition: simd_float3(0.0, 0.0, 5.0), maxSpeed: 2.0, deltaTime: deltaTime) +let crate = createEntity() +setEntityMeshAsync(entityId: crate, filename: "crate", withExtension: "untold") + +registerComponent(entityId: crate, componentType: RigidBodyComponent.self) +registerComponent(entityId: crate, componentType: ColliderComponent.self) + +if let body = scene.get(component: RigidBodyComponent.self, for: crate) { + body.motionType = .dynamic + body.mass = 2.0 + body.gravityScale = 1.0 + body.layer = 0 + body.collisionMask = .max +} + +if let collider = scene.get(component: ColliderComponent.self, for: crate) { + collider.shape = .box(halfExtents: simd_float3(0.5, 0.5, 0.5)) + collider.friction = 0.6 + collider.restitution = 0.1 + collider.isTrigger = false +} ``` ---- +Supported collider shapes are: -#### Additional Steering Functions +- `.sphere(radius:)` +- `.box(halfExtents:)` +- `.capsule(radius:height:)` +- `.cylinder(radius:height:)` +- `.convexHull(vertices:)` -The Steering System includes other useful behaviors, such as: +Supported motion types are `.static`, `.kinematic`, and `.dynamic`. -- `steerFlee(entityId:threatPosition:maxSpeed:deltaTime:)` -- `steerArrive(entityId:targetPosition:maxSpeed:slowingRadius:deltaTime:)` -- `steerPursuit(entityId:targetEntity:maxSpeed:deltaTime:)` -- `steerFollowPath(entityId:path:maxSpeed:deltaTime:)` -- `steerAvoidObstacles(entityId:obstacles:avoidanceRadius:maxSpeed:deltaTime:)` +## World Configuration -These functions simplify complex movement patterns, making them easy to implement. +`PhysicsCoordinator` owns world-level settings shared by the backend and the built-in integrator. ---- +```swift +var config = PhysicsWorldConfiguration() +config.gravity = simd_float3(0.0, -9.8, 0.0) +PhysicsCoordinator.shared.setWorldConfiguration(config) +``` + +If a backend is installed later, the coordinator applies the current configuration to it. -### What Happens Behind the Scenes? +## Backend Events -1. Physics Simulation: -- Entities with physics enabled are updated each frame to account for forces, gravity, and other dynamic factors. -- Transformations are recalculated based on velocity, acceleration, and forces applied. -2. Realistic Motion: -- The system ensures consistent, physics-based movement without manual updates to the transform. +When an installed backend emits events, subscribe through `PhysicsEvents`. ---- +```swift +let contactSubscription = PhysicsEvents.shared.onContact { event in + if event.phase == .began { + print("Contact:", event.entityA, event.entityB) + } +} + +let triggerSubscription = PhysicsEvents.shared.onTrigger { event in + print("Trigger:", event.phase, event.triggerEntity, event.otherEntity) +} + +let activationSubscription = PhysicsEvents.shared.onActivation { event in + print("Active:", event.entity, event.isActive) +} +``` -### Running the Simulation -Once you've set up physics, run the project to see it in action: +Keep the returned `EventSubscription` alive for as long as you want to receive events. Call `cancel()` on the subscription when you no longer need it. -1. Launch the project: Your model will appear in the game window. -2. Press "P" to enter Game Mode: -- Gravity and forces will affect the entity. -- If forces are applied, you’ll see dynamic motion in real time. +With no external backend installed, contact and trigger events are dormant because the built-in kinetics integrator does not perform collision detection. diff --git a/docs/API/UsingRegistrationSystem.md b/docs/API/UsingRegistrationSystem.md index 4a005648..ed677b47 100644 --- a/docs/API/UsingRegistrationSystem.md +++ b/docs/API/UsingRegistrationSystem.md @@ -28,10 +28,13 @@ registerComponent(entityId: entity, componentType: RenderComponent.self) ``` Example: -When you load a mesh for rendering, the system automatically registers the required components: +When you load a mesh for rendering, the system automatically registers the required components. For normal runtime code, use the async path: ```swift -setEntityMesh(entityId: entity, filename: "model", withExtension: "untold") +setEntityMeshAsync(entityId: entity, filename: "model", withExtension: "untold") { success in + guard success else { return } + // RenderComponent, TransformComponent, material data, and mesh resources are ready. +} ``` This function: @@ -39,16 +42,16 @@ This function: - Loads the mesh from the specified `.untold` file. - Associates the mesh with the entity. - Registers default components like RenderComponent and TransformComponent. -- Uses the immediate path; the mesh is GPU-resident when the function returns. +- Calls the completion handler when the mesh has been registered. -For asynchronous always-resident loading, use: +For immediate loading, use: ```swift -setEntityMeshAsync(entityId: entity, filename: "model", withExtension: "untold") { success in - // Mesh is registered on success. -} +setEntityMesh(entityId: entity, filename: "model", withExtension: "untold") ``` +The immediate path is useful for tools and tests that need the mesh to be GPU-resident when the function returns. + For large streamed scenes, use `setEntityStreamScene(...)`. The streaming/OCC path is owned by the tile manifest pipeline, not by direct `StreamingComponent` authoring. --- diff --git a/docs/API/UsingRenderingSystem.md b/docs/API/UsingRenderingSystem.md index 4a9acf77..41b1b7a9 100644 --- a/docs/API/UsingRenderingSystem.md +++ b/docs/API/UsingRenderingSystem.md @@ -1,6 +1,6 @@ -# Enabling Rendering System in Untold Engine +# Rendering System -The Rendering System in the Untold Engine is responsible for displaying your models on the screen. It supports advanced features such as Physically Based Rendering (PBR), tile-based deferred lighting, screen-space ambient occlusion, and multiple types of lights to illuminate your scenes. +The rendering system displays your entities through Metal. It supports runtime `.untold` assets, PBR materials, tile-based deferred lighting, screen-space ambient occlusion, anti-aliasing, scene-channel render modes, light portals, XR lighting, and debug views. ## How to Enable the Rendering System @@ -13,33 +13,66 @@ let entity = createEntity() ``` --- -### Step 2: Link a Mesh to the Entity +### Step 2: Load A Mesh -To display a model, load its `.untold` runtime asset and link it to the entity using `setEntityMesh`. +To display a model, load its `.untold` runtime asset and link it to the entity. For normal app and game code, use `setEntityMeshAsync`: ```swift -setEntityMesh(entityId: entity, filename: "entity", withExtension: "untold") +setEntityMeshAsync(entityId: entity, filename: "robot", withExtension: "untold") { success in + guard success else { return } + translateTo(entityId: entity, position: simd_float3(0.0, 0.0, -2.0)) +} ``` Parameters: -- entityId: The ID of the entity created earlier. -- filename: The name of the `.untold` file without the extension. -- withExtension: The file extension, typically `"untold"` for runtime assets. +- `entityId`: The ID of the entity created earlier. +- `filename`: The name of the `.untold` file without the extension. +- `withExtension`: The file extension, typically `"untold"` for runtime assets. +- `completion`: Called when the mesh has been loaded and registered. -> Note: If PBR textures (e.g., albedo, normal, roughness, metallic maps) are included, the rendering system will automatically use the appropriate PBR shader to render the model with realistic lighting and material properties. +`setEntityMeshAsync` registers the render and transform data needed by the renderer. If the asset includes PBR textures, the renderer uses the material maps automatically. + +For tooling, tests, or cases that require immediate GPU residency, `setEntityMesh` is still available: + +```swift +setEntityMesh(entityId: entity, filename: "robot", withExtension: "untold") +``` --- -### Running the Rendering System +## Scene-Authored Data -Once everything is set up: +`setEntityMesh` and `setEntityMeshAsync` load geometry and materials. Scene-authored lights, cameras, and baked color grading are loaded separately: -1. Run the project. -2. Your model will appear in the game window, illuminated by the configured lights. -3. If the model is not visible or appears flat, revisit the lighting and texture setup to ensure everything is loaded correctly. +```swift +loadSceneAuthored(filename: "office", withExtension: "untold") { success in + // Scene-authored lights/cameras and any baked color LUT are registered. +} +``` ---- +For streamed tile manifests: + +```swift +loadSceneAuthored(url: manifestURL) { success in + // Scene-authored data from the manifest is registered. +} +``` + +See [Registration System](UsingRegistrationSystem.md) and [Color Management](UsingColorManagement.md) for the full scene-authored workflow. + +## Lighting + +Renderable assets need lighting unless their material is emissive or the scene is using a specialized debug view. The most common setup is a directional light: + +```swift +let sun = createEntity() +createDirLight(entityId: sun) +setLight(entityId: sun, .intensity(1.4)) +setLight(entityId: sun, .color(simd_float3(1.0, 0.95, 0.85))) +``` + +See [Lighting System](UsingLightingSystem.md) and [Light Portals](UsingLightPortals.md) for the full lighting API. ## Common Issues and Fixes @@ -53,7 +86,7 @@ Once everything is set up: - Cause: PBR textures are missing or not linked properly. - Solution: Ensure the `.untold` asset references the correct PBR textures, and verify their paths during the loading process. -#### Debugging Tip: +#### Debugging Tip - Log the addition of lights and entities to verify the scene setup. - Ensure the position of the point light is within the visible range of the camera and the objects it is meant to illuminate. @@ -120,7 +153,6 @@ Restore normal rendering with `setRendering(.debugView(.lit))`. For the broader settings style, see [Engine Settings API](UsingEngineAPI.md). -For optional renderer features that add their own graph passes or resources, -see [Rendering Extensions](UsingRenderingExtensions.md). +For optional renderer features that add their own graph passes or resources, see [Rendering Extensions](UsingRenderingExtensions.md). --- diff --git a/docs/API/UsingSceneBuilder.md b/docs/API/UsingSceneBuilder.md new file mode 100644 index 00000000..52640232 --- /dev/null +++ b/docs/API/UsingSceneBuilder.md @@ -0,0 +1,172 @@ +# Scene Builder / UntoldView + +Untold Engine supports two SwiftUI entry points: + +- `SceneView` hosts an `UntoldRenderer` and lets you set up the scene imperatively with the engine facade APIs. +- `UntoldView` hosts the same renderer, but lets you declare scene content with `SceneBuilder` nodes. + +Both paths create normal Untold Engine entities and components. Use the builder when you want compact SwiftUI-style scene setup, and use the lower-level facade APIs when a demo or game needs explicit control over loading, registration, or runtime systems. + +## SceneView + +`SceneView` is the direct host for the renderer's `MTKView`. + +```swift +import SwiftUI +import UntoldEngine + +struct GameView: View { + let renderer = UntoldRenderer.create() + + var body: some View { + SceneView(renderer: renderer) + .onInit { + let entity = createEntity() + setEntityMeshAsync(entityId: entity, filename: "robot", withExtension: "untold") + translateTo(entityId: entity, position: simd_float3(0.0, 0.0, -2.0)) + + let light = createEntity() + createDirLight(entityId: light) + setLight(entityId: light, .intensity(1.5)) + + cameraLookAt( + entityId: findGameCamera(), + eye: simd_float3(0.0, 1.2, 4.0), + target: simd_float3(0.0, 0.8, 0.0) + ) + } + } +} +``` + +`onInit` runs once after the renderer exists, so resource loading calls have a Metal device available. It is the right place for mesh loading, entity creation, camera setup, light setup, input registration, and scene-channel configuration. + +## UntoldView + +`UntoldView` wraps `SceneView` and runs a `SceneBuilder` block once when the platform view is created. + +```swift +import SwiftUI +import UntoldEngine + +struct GameView: View { + var body: some View { + UntoldView { + CameraNode(name: "Main Camera") + .lookAt( + eye: simd_float3(0.0, 1.2, 4.0), + target: simd_float3(0.0, 0.8, 0.0) + ) + + DirectionalLightNode(name: "Sun") + .intensity(1.5) + + MeshNode(resource: "robot.untold", name: "Robot") + .translateTo(x: 0.0, y: 0.0, z: -2.0) + .scaleTo(x: 1.0, y: 1.0, z: 1.0) + .roughness(0.45) + } + } +} +``` + +`MeshNode` uses `setEntityMeshAsync` internally. Primitive nodes such as `CubeNode`, `SphereNode`, `PlaneNode`, `CylinderNode`, and `ConeNode` generate their meshes immediately. + +## Node Hierarchies + +Nodes can contain child nodes. The builder creates normal scene-graph relationships by calling `setParent(childId:parentId:)` for each child. + +```swift +MeshNode(resource: "vehicle.untold", name: "Vehicle") { + CubeNode(size: 0.25, name: "Marker") + .translateTo(x: 0.0, y: 1.25, z: 0.0) + .baseColor(1.0, 0.2, 0.1) +} +``` + +The child transform is local to the parent. You can still access the generated entity through `node.entityID` if you need to pass it to another engine API. + +## Common Node Modifiers + +Transform modifiers are available on all nodes: + +```swift +.translateTo(x: 0.0, y: 0.0, z: -2.0) +.translateBy(x: 0.0, y: 0.1, z: 0.0) +.rotateTo(angle: 45.0, axis: [.y]) +.rotateBy(angle: 10.0, axis: [.y]) +.scaleTo(x: 1.0, y: 1.0, z: 1.0) +``` + +Material modifiers are available on `MeshNode` and primitive nodes: + +```swift +.baseColor(0.8, 0.2, 0.1, 1.0) +.roughness(0.6) +.metallic(0.0) +.emissive(0.0, 0.0, 0.0) +.materialData( + roughness: 0.45, + metallic: 0.0, + baseColorResource: "robot_albedo.png", + normalResource: "robot_normal.png" +) +``` + +Light nodes expose light-specific modifiers: + +```swift +DirectionalLightNode() + .color(1.0, 0.95, 0.85) + .intensity(1.4) + +PointLightNode() + .radius(4.0) + .falloff(2.0) + .attenuation(constant: 1.0, linear: 0.2, quadratic: 0.05) + +SpotLightNode() + .coneAngle(30.0) + .radius(8.0) +``` + +## Per-Frame Updates + +Use `onUpdate` for frame-driven game logic. Keep per-frame state in the ECS or in a reference type. Avoid mutating SwiftUI state that would rebuild the scene content every frame. + +```swift +UntoldView { + MeshNode(resource: "drone.untold", name: "Drone") +} +.onUpdate { event in + if let drone = findEntity(name: "Drone") { + rotateBy(entityId: drone, angle: 30.0 * Float(event.deltaTime), axis: simd_float3(0, 1, 0)) + } +} +``` + +## Runtime View Options + +`UntoldViewOptions` controls settings that can be applied to the live `MTKView` without recreating the renderer or scene. + +```swift +UntoldView(options: UntoldViewOptions( + preferredFramesPerSecond: 90, + isPaused: false, + clearColor: simd_float4(0.02, 0.02, 0.025, 1.0) +)) { + MeshNode(resource: "robot.untold") +} +``` + +You can also use modifiers: + +```swift +UntoldView { + MeshNode(resource: "robot.untold") +} +.preferredFramesPerSecond(90) +.paused(false) +``` + +Renderer or engine settings such as anti-aliasing, post effects, LOD, lighting, and scene channels should still be configured through the engine settings APIs. diff --git a/docs/API/UsingScenegraph.md b/docs/API/UsingScenegraph.md index 2770e0d6..ebfe6923 100644 --- a/docs/API/UsingScenegraph.md +++ b/docs/API/UsingScenegraph.md @@ -1,33 +1,103 @@ -# Adding Parent-Child Relationships in Untold Engine +# Scene Graph -The Untold Engine includes a Scene Graph data structure, designed to manage hierarchical transformations efficiently. This enables parent-child relationships between entities, where a child's transformation (position, rotation, scale) is relative to its parent. For example, a car's wheels (children) move and rotate relative to the car body (parent). +The scene graph manages parent-child relationships between entities. A child keeps its own local transform, and the engine composes it with the parent's world transform during scene-graph updates. -## Why Use Parent-Child Relationships? +Use parent-child relationships when entities should move as a group: -Parent-child relationships are useful when you want multiple entities to move or transform together. When a parent entity changes its position, rotation, or scale, its child entities inherit those changes automatically. This is ideal for scenarios like: +- A vehicle body with wheel children. +- A hand-held tool attached to a character hand. +- A marker, label, or interaction proxy attached to a loaded model. +- A group of objects that should keep a fixed relative layout. -- A car (parent) and its wheels (children) -- A robot (parent) with movable arms and legs (children) -- A group of objects that should remain in a fixed configuration relative to each other +## Create A Parent Relationship -## Assigning Parent-Child Relationships +```swift +let parent = createEntity() +let child = createEntity() + +setEntityMeshAsync(entityId: parent, filename: "vehicle", withExtension: "untold") +setEntityMeshAsync(entityId: child, filename: "wheel", withExtension: "untold") -To assign a parent to an entity, use the setParent function. This function establishes a hierarchical relationship between the specified entities. +setParent(childId: child, parentId: parent) +``` + +After this call, transforms applied to `parent` affect `child`. ```swift -// Create child and parent entities -let childEntity = createEntity() -let parentEntity = createEntity() +translateTo(entityId: parent, position: simd_float3(0.0, 0.0, -2.0)) +rotateBy(entityId: parent, angle: 45.0, axis: simd_float3(0.0, 1.0, 0.0)) +``` -// Set parent-child relationship -setParent(childId: childEntity, parentId: parentEntity) +The child's transform remains local to the parent. + +```swift +translateTo(entityId: child, position: simd_float3(0.6, -0.4, 0.8)) ``` -## What Happens Behind the Scenes? +## Parent With An Offset + +`setParent` accepts an optional local offset. This is useful when you want to attach an entity and place it relative to the parent in one call. + +```swift +setParent( + childId: child, + parentId: parent, + offset: simd_float3(0.6, -0.4, 0.8) +) +``` + +## Remove A Parent + +Use `removeParent` when an entity should stop inheriting from its parent. + +```swift +removeParent(childId: child) +``` + +The entity remains alive; only the scene-graph relationship is removed. + +## Query Relationships + +Use the query helpers when gameplay code needs to inspect the hierarchy. -1. Transformation Inheritance: -- Once the relationship is established, any transformation applied to the parent entity (e.g., movement, rotation) will automatically affect the child entity. -- The child’s transformation is expressed relative to the parent. +```swift +let children = getEntityChildren(parentId: parent) +let currentParent = getEntityParent(entityId: child) +``` + +`getEntityParent` returns `nil` when the entity is not parented. + +## Loaded Assets + +`.untold` assets can create multiple entities when they preserve hierarchy. You can still parent the root entity to another gameplay entity: + +```swift +let roomAnchor = createEntity() +let room = createEntity() + +setEntityMeshAsync(entityId: room, filename: "room", withExtension: "untold") { success in + guard success else { return } + setParent(childId: room, parentId: roomAnchor) +} +``` + +If you need to find a named child exported from Blender, use entity names: + +```swift +if let door = findEntity(name: "Door_Main") { + setParent(childId: door, parentId: roomAnchor) +} +``` + +## Scene Builder + +`UntoldView` and `SceneBuilder` create parent relationships automatically for nested nodes: + +```swift +MeshNode(resource: "vehicle.untold", name: "Vehicle") { + CubeNode(size: 0.25, name: "Marker") + .translateTo(x: 0.0, y: 1.25, z: 0.0) +} +``` -2. Independent Local Transformations: -- While the child inherits the parent's transformations, it can also have its own independent local transformations, such as offset positions or rotations relative to the parent. +See [Scene Builder / UntoldView](UsingSceneBuilder.md) for the declarative scene setup workflow. diff --git a/docs/API/UsingScriptingSystem.md b/docs/API/UsingScriptingSystem.md new file mode 100644 index 00000000..a8699ce5 --- /dev/null +++ b/docs/API/UsingScriptingSystem.md @@ -0,0 +1,212 @@ +# Scripting System + +Untold Engine includes a USC scripting layer for serializable entity behavior. It is useful when you want gameplay actions to be built in Swift, saved as data, loaded later, and executed by the engine. + +The scripting API has three main pieces: + +- `USCBuilder` builds scripts with a Swift fluent API. +- `USCScript` is the codable runtime script data. +- `USCInterpreter` executes a script for an entity and event. + +Call `initScriptingSystem()` during startup before loading or executing scripts. This registers the script component merge behavior and the built-in math actions. + +```swift +initScriptingSystem() +``` + +## Build A Script + +Use `buildScript` for inline script creation. + +```swift +let spinScript = buildScript(name: "SpinOnUpdate") { script in + script + .onUpdate() + .rotateBy(degrees: 45.0, axis: simd_float3(0.0, 1.0, 0.0)) +} +``` + +The builder stores instructions as data. The script can be executed immediately or saved to disk. + +## Export And Load Scripts + +Use `exportScript` to write a `.usc` file and `loadUSCScript(from:)` to load it later. + +```swift +let url = projectScriptsURL.appendingPathComponent("SpinOnUpdate.usc") + +try exportScript(name: "SpinOnUpdate", to: url) { script in + script + .onUpdate() + .rotateBy(degrees: 45.0, axis: simd_float3(0.0, 1.0, 0.0)) +} + +let loadedScript = loadUSCScript(from: url) +``` + +You can also save an existing script: + +```swift +try saveUSCScript(spinScript, to: url) +``` + +## Execute A Script + +`USCInterpreter` executes a script against a specific entity through a `USCContext`. + +```swift +let entity = createEntity() +setEntityMeshAsync(entityId: entity, filename: "robot", withExtension: "untold") + +let context = USCContext(entityId: entity, script: spinScript) +USCInterpreter().execute(script: spinScript, context: context, forEvent: "OnUpdate") +``` + +For production gameplay, prefer attaching scripts through the engine's script component workflow or deserializing scene-authored scripts. Direct interpreter calls are useful for tools, tests, and custom dispatch. + +## Events + +Scripts are organized around events: + +```swift +buildScript(name: "RobotBehavior") { script in + script + .onStart() + .log("Robot spawned") + + script + .onUpdate() + .rotateBy(degrees: 30.0, axis: simd_float3(0.0, 1.0, 0.0)) + + script + .onCollision(tag: "Player") + .log("Robot touched player") + + script + .onEvent("OpenDoor") + .translateBy(x: 0.0, y: 2.0, z: 0.0) +} +``` + +Physics backend events also fan out to USC events. Contact begin events fire `OnCollision`, and named variants such as `OnCollision:Player` are fired when the other entity has a name. Trigger events fire `OnTriggerEnter` and `OnTriggerExit`. + +## Input Conditions + +Scripts can read keyboard state and branch on key transitions: + +```swift +buildScript(name: "KeyboardMove") { script in + script + .onUpdate() + .ifKeyPressed("w") { block in + block.translateBy(x: 0.0, y: 0.0, z: -0.05) + } + .ifKeyReleased("space") { block in + block.log("Jump released") + } +} +``` + +You can also store key state in a variable: + +```swift +script.getKeyState("space", as: "spaceDown") +``` + +## Entity Commands + +The builder includes common transform, animation, camera, and physics actions: + +```swift +script.translateTo(x: 0.0, y: 1.0, z: -2.0) +script.translateBy(x: 0.0, y: 0.0, z: -0.1) +script.rotateTo(degrees: 90.0, axis: simd_float3(0.0, 1.0, 0.0)) +script.rotateBy(degrees: 10.0, axis: simd_float3(0.0, 1.0, 0.0)) +script.lookAt("Target") + +script.playAnimation("Walk", loop: true, transitionHalflife: 0.08) +script.stopAnimation() + +script.cameraMoveTo(simd_float3(0.0, 1.5, 4.0)) + +script.applyForce(force: simd_float3(0.0, 5.0, 0.0)) +script.clearVelocity() +script.clearForces() +script.setGravityScale(1.0) +``` + +These commands call the same runtime systems documented elsewhere in the API guide. + +## Variables And Properties + +Use variables for temporary script state: + +```swift +script.setVariable("speed", to: 2.0) +script.setVariable("direction", to: simd_float3(0.0, 0.0, -1.0)) +script.logVariable("speed") +``` + +Use property access when a script needs to read or write entity state: + +```swift +script.getProperty(.position, as: "currentPosition") +script.setProperty(.mass, to: 2.0) +script.setProperty(of: "Door", .position, to: simd_float3(0.0, 2.0, 0.0)) +``` + +String key paths are also supported for lower-level access: + +```swift +script.getProperty("position", as: "position") +script.getProperty("velocity.x", as: "horizontalVelocity") +script.setProperty("mass", to: 0.5) +``` + +Use `ScriptProperty` where possible because it avoids typos in common property names. + +## Conditions And Math + +The builder supports conditionals and math instructions: + +```swift +buildScript(name: "MassCheck") { script in + script + .onUpdate() + .getProperty(.mass, as: "mass") + .ifLess("mass", than: 0.5) { block in + block.log("Light object") + } +} +``` + +Vector and scalar helpers include addition, subtraction, multiplication, division, normalization, dot product, cross product, lerp, reflection, projection, angle checks, and clamps. + +```swift +script + .setVariable("a", to: simd_float3(1.0, 0.0, 0.0)) + .setVariable("b", to: simd_float3(0.0, 0.0, 1.0)) + .addVec3("a", "b", as: "moveDirection") + .normalizeVec3("moveDirection", as: "moveDirection") +``` + +## Custom Actions + +Register custom actions through `USCActionRegistry` when a script needs to call game-specific behavior. + +```swift +USCActionRegistry.shared.register(name: "Game.spawnEffect") { context, args in + guard let effectName = args["effectName"], case let .string(name) = effectName else { + return nil + } + + spawnEffect(named: name, at: context.entityId) + return nil +} + +script + .setVariable("effectName", to: "Spark") + .callAction("Game.spawnEffect", args: ["effectName"]) +``` + +Custom actions are a good boundary between data-driven scripts and game-specific Swift code. diff --git a/docs/API/UsingTheExporter.md b/docs/API/UsingTheExporter.md index 12cb02d1..fc8dd565 100644 --- a/docs/API/UsingTheExporter.md +++ b/docs/API/UsingTheExporter.md @@ -352,7 +352,7 @@ This lets you keep background geometry (walls, floors, ceilings) optimized while To change the prefix or disable selective merging, edit `NO_MERGE_PREFIX` at the top of `scripts/tilestreamingpartition.py`. Set it to `""` to merge all objects regardless of name. -At runtime, `NM_` objects default to `.selectableGeometry` and `.preserveIdentity` scene channels. Regular render/streaming geometry defaults to `.contextGeometry`. This lets an app hide context geometry with `setSceneChannelVisible(.contextGeometry, false)` while keeping `NM_` objects visible and selectable. See [Scene Channels](UsingSceneChannels.md). +At runtime, `NM_` objects default to `.selectableGeometry` and `.preserveIdentity` scene channels. Regular render/streaming geometry defaults to `.contextGeometry`. This lets an app hide context geometry with `setSceneChannel(.contextGeometry, .renderMode(.hidden))` while keeping `NM_` objects visible and selectable. See [Scene Channels](UsingSceneChannels.md). ## Optimization Workflows diff --git a/docs/Architecture/animationPoseLayer.md b/docs/Architecture/animationPoseLayer.md index e45e9cf7..adff39fa 100644 --- a/docs/Architecture/animationPoseLayer.md +++ b/docs/Architecture/animationPoseLayer.md @@ -156,7 +156,7 @@ public func changeAnimation(entityId: EntityID, name: String, Opt-in per entity (default off — existing content behaves exactly as today): ```swift -public func setRootMotionEnabled(entityId: EntityID, _ enabled: Bool) +public func setRootMotionEnabled(entityId: EntityID, enabled: Bool, rootJointPath: String? = nil) ``` When enabled, for the designated root joint (the first joint with a `nil` diff --git a/docs/Architecture/sceneChannels.md b/docs/Architecture/sceneChannels.md index 22a0b12f..a82fa3b5 100644 --- a/docs/Architecture/sceneChannels.md +++ b/docs/Architecture/sceneChannels.md @@ -48,20 +48,20 @@ The built-in `NM_` prefix is evaluated first to preserve selectable-object compa ## Rendering -Channel rendering is controlled globally. The compatibility visibility API maps to render modes: +Channel rendering is controlled globally through `setSceneChannel(_:_:)`: ```swift -setSceneChannelVisible(.contextGeometry, false) // .hidden -setSceneChannelVisible(.contextGeometry, true) // .normal +setSceneChannel(.contextGeometry, .renderMode(.normal)) +setSceneChannel(.contextGeometry, .renderMode(.hidden)) +setSceneChannel(.contextGeometry, .renderMode(.wireframe)) +setSceneChannel(.ghostGeometry, .renderMode(.passthroughGhost(opacity: 0.35))) ``` -New code can set the render mode directly: +The older compatibility APIs map to the same render-mode state and should only be used while migrating older code: ```swift -setSceneChannelRenderMode(.contextGeometry, .normal) -setSceneChannelRenderMode(.contextGeometry, .hidden) -setSceneChannelRenderMode(.contextGeometry, .wireframe) -setSceneChannel(.ghostGeometry, .renderMode(.passthroughGhost(opacity: 0.35))) +setSceneChannelVisible(.contextGeometry, false) // Deprecated; use .renderMode(.hidden) +setSceneChannelVisible(.contextGeometry, true) // Deprecated; use .renderMode(.normal) ``` The render passes call `shouldHideSceneEntity(entityId:)` for individual entities. Hidden entities are skipped before draw encoding. This is different from opacity: no transparent draw is submitted, so the feature avoids transparency sorting issues. diff --git a/docs/LearningPaths/ArchvizToVisionPro.md b/docs/LearningPaths/ArchvizToVisionPro.md new file mode 100644 index 00000000..5f1eabcd --- /dev/null +++ b/docs/LearningPaths/ArchvizToVisionPro.md @@ -0,0 +1,253 @@ +# Archviz To Vision Pro + +This learning path takes an architectural visualization scene from Blender and turns it into a Vision Pro app with Untold Engine. + +You will start with this archviz model in Blender: + +![archviz-image](../images/archviz-model.png) + +Then you will load the exported scene on Apple Vision Pro: + +[![untoldengine-image](../images/engine-highlight-5.png)](https://vimeo.com/1176995991?fl=ip&fe=ec) + +By the end of the path, you will have: + +- A standalone visionOS Xcode project. +- An archviz `.untold` asset inside the project's `GameData` folder. +- A scene that loads the model asynchronously. +- Blender-authored scene data such as lights, cameras, and color management loaded at runtime. +- Vision Pro input configured so the user can move and rotate the scene root with spatial gestures. + +## What You Will Build + +The goal is a simple but complete archviz viewer: + +1. Create a new visionOS project with the Untold Engine CLI. +2. Install the starter archviz asset pack. +3. Load the `Bedroom.untold` model in `GameScene.swift`. +4. Configure rendering and XR input. +5. Use spatial manipulation so the scene can be moved and rotated in mixed reality. +6. Build and run on Apple Vision Pro or the Vision Pro Simulator. + +This path uses the starter archviz asset so you can focus on the engine workflow first. After this is working, you can replace the starter asset with your own Blender export. + +## Prerequisites + +You need: + +- Xcode with visionOS support. +- A local Untold Engine checkout. +- The `untoldengine` CLI installed from the repository. +- An Apple Vision Pro device or the Vision Pro Simulator. + +Clone the engine and use a stable release: + +```bash +git clone https://github.com/untoldengine/UntoldEngine.git +cd UntoldEngine +git checkout v0.16.0 +``` + +Install the CLI: + +```bash +./scripts/install-untoldengine-create.sh +``` + +Verify the command is available: + +```bash +untoldengine --help +``` + +## Create The Vision Pro Project + +Create a standalone Xcode project: + +```bash +cd ~/Projects +untoldengine create ArchvizViewer --platform visionos +open ArchvizViewer/ArchvizViewer.xcodeproj +``` + +The generated project already depends on the correct engine products for visionOS: + +- `UntoldEngineXR` +- `UntoldEngineAR` + +The project also includes a bundled `GameData` folder. Runtime assets should live there. + +```text +ArchvizViewer/ + Sources/ + ArchvizViewer/ + GameScene.swift + GameData/ + Models/ + StreamModels/ + Textures/ + Scenes/ + Scripts/ +``` + +## Install The Starter Archviz Asset + +From the generated project folder, install the starter asset pack: + +```bash +cd ~/Projects/ArchvizViewer +untoldengine assets install starter-archviz +``` + +The CLI finds the project's `GameData` folder and merges the asset files into it. + +The important file for this path is the `Bedroom.untold` model. The generated project should now contain the model and its supporting textures and resources under `GameData`. + +## Open GameScene.swift + +In Xcode, open: + +```text +Sources/ArchvizViewer/GameScene.swift +``` + +This is where the generated project sets up the engine scene. The two important places are: + +- `init()` for one-time scene setup and asset loading. +- `configureEngineSystems()` for rendering, input, and runtime settings. + +## Load The Archviz Model + +In `init()`, after `configureEngineSystems()`, create an entity and load the archviz model with `setEntityMeshAsync`. + +```swift +let entity = createEntity() +setEntityName(entityId: entity, name: "Bedroom") + +setEntityMeshAsync(entityId: entity, filename: "Bedroom", withExtension: "untold") { success in + guard success else { + setSceneReady(false) + return + } + + loadSceneAuthored(filename: "Bedroom", withExtension: "untold") + + setRendering(.environment(.ibl(true))) + setRendering(.environment(.asset("forest.exr"))) + setRendering(.environment(.intensity(0.9))) + setRendering(.environment(.visible(true))) + + setSceneReady(true) +} +``` + +What this does: + +- `createEntity()` creates the root entity for the archviz model. +- `setEntityName(...)` gives the entity a stable runtime name. +- `setEntityMeshAsync(...)` loads the `.untold` model without blocking the app. +- `loadSceneAuthored(...)` loads Blender-authored lights, cameras, and color-management data from the asset. +- `setSceneReady(true)` tells the rest of the app that the scene can now respond to input. + +`setEntityMeshAsync` looks for the asset by name in `GameData`, so the filename is `"Bedroom"` and the extension is `"untold"`. + +## Configure Rendering And XR Input + +In `configureEngineSystems()`, enable game mode, register XR input, and configure the rendering defaults: + +```swift +private func configureEngineSystems() { + gameMode = true + + registerXREvents() + setInput(.xr(.pickingBackend(.octreeGPUPreferred))) + setInput(.xr(.twoHandRotateAxisMode(.dynamicSnapped))) + setInput(.xr(.sceneReady(true))) + + setRendering(.postProcessing(.enabled)) + setRendering(.antiAliasing(.msaa)) + setPostFX(.ssao(.enabled(false))) + + TextureStreamingSystem.shared.apply(.superdetailed) +} +``` + +The important calls are: + +- `registerXREvents()` enables Vision Pro gesture input. +- `setInput(.xr(.pickingBackend(.octreeGPUPreferred)))` asks the engine to use the GPU-preferred spatial picking path. +- `setInput(.xr(.twoHandRotateAxisMode(.dynamicSnapped)))` configures two-hand scene rotation behavior. +- `setRendering(...)` and `setPostFX(...)` configure the render path. +- `TextureStreamingSystem.shared.apply(.superdetailed)` asks the texture system to use high-detail texture settings. + +## Add Spatial Manipulation + +In the generated input handling function, use the spatial manipulation system to move and rotate the scene root. + +```swift +func handleInput() { + if gameMode == false { return } + if isSceneReady() == false { return } + + let state = getXRSpatialInputState() + + SpatialManipulationSystem.shared.processAnchoredSceneManipulationLifecycle( + from: state, + dragSensitivity: 10.0, + rotateSensitivity: 1.0 + ) +} +``` + +This lets the user: + +- Pinch and drag to move the scene. +- Use two hands to rotate the scene. + +The scene-readiness check matters because spatial input should not manipulate the scene before the model has finished loading. + +## Build And Run + +In Xcode: + +1. Select the Vision Pro Simulator or a connected Apple Vision Pro. +2. Build and run. +3. Start the experience. +4. Confirm the archviz scene appears. +5. Pinch and drag to move the scene. +6. Use two hands to rotate the scene. + +## Replace The Starter Asset With Your Own Blender Scene + +Once the starter asset works, the next step is to export your own Blender archviz model. + +At a high level: + +1. Prepare the model in Blender. +2. Export it to `.untold` with the Blender add-on or CLI exporter. +3. Place the exported asset under the project's `GameData` folder. +4. Update the filename passed to `setEntityMeshAsync`. + +For example, if your exported model is `ModernHouse.untold`, load it like this: + +```swift +setEntityMeshAsync(entityId: entity, filename: "ModernHouse", withExtension: "untold") { success in + guard success else { + setSceneReady(false) + return + } + + loadSceneAuthored(filename: "ModernHouse", withExtension: "untold") + setSceneReady(true) +} +``` + +## Where To Go Next + +- [Blender Add-On Workflow](../Tutorials/BlenderAddonTutorial.md) +- [Export Assets With The CLI](../Tutorials/CLIExporterTutorial.md) +- [Create A New Xcode Project](../Tutorials/CreateXcodeProjectTutorial.md) +- [XR App Basics](../Tutorials/XRTutorial.md) +- [Spatial Input And Manipulation](../Tutorials/SpatialInputTutorial.md) +- [Materials, Textures, And Color Management](../Tutorials/MaterialsPipelineTutorial.md) +- [Light Portals](../Tutorials/LightPortalsTutorial.md) diff --git a/docs/LearningPaths/index.md b/docs/LearningPaths/index.md new file mode 100644 index 00000000..83fd9f89 --- /dev/null +++ b/docs/LearningPaths/index.md @@ -0,0 +1,18 @@ +# Learning Paths + +Learning paths are project-based guides that take you from a real-world goal to a working Untold Engine app. + +Use the tutorials when you want to understand a specific engine demo or API. Use learning paths when you want to build a complete result and see how the engine systems fit together. + +## Available Paths + +| Path | What You Build | Main Systems | +| --- | --- | --- | +| [Archviz To Vision Pro](ArchvizToVisionPro.md) | A Blender architectural visualization scene running on Apple Vision Pro | Xcode project creation, `GameData`, async asset loading, scene-authored data, XR input, spatial manipulation | + +## Planned Paths + +- Digital Twin Walkthrough +- City Streaming +- Interactive Product Viewer + diff --git a/docs/images/archviz-model.png b/docs/images/archviz-model.png new file mode 100644 index 00000000..b0b5b923 Binary files /dev/null and b/docs/images/archviz-model.png differ diff --git a/docs/images/engine-highlight-5.png b/docs/images/engine-highlight-5.png index 6e4ee271..e5faee19 100644 Binary files a/docs/images/engine-highlight-5.png and b/docs/images/engine-highlight-5.png differ diff --git a/docs/index.md b/docs/index.md index a0c71c35..73e30171 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,7 +32,7 @@ Untold Engine is built for developers and teams who: Creator & Lead Developer: [Harold Serrano](http://www.haroldserrano.com) -![untoldengine-image](images/engine-highlight-5.png) +[![untoldengine-image](images/engine-highlight-5.png)](https://vimeo.com/1176995991?fl=ip&fe=ec) --- @@ -121,8 +121,6 @@ Untold Engine is built around three focused goals: --- -![untoldengine-image-2](images/engine-highlight-7.jpg) - ## Example Use Cases Untold Engine is well-suited for: @@ -164,6 +162,10 @@ modifications, sponsored engine features, priority support, or custom terms. - **Priority support / retainers** — get focused help with engine integration, rendering issues, performance, and production use. +If your team needs an engine feature that is not currently available, contact +[Harold Serrano](https://www.haroldserrano.com/contact) to discuss sponsored feature development, private engine +work, commercial licensing, or ongoing support. + See [COMMERCIAL.md](https://github.com/untoldengine/UntoldEngine/blob/main/COMMERCIAL.md) for commercial licensing details. --- diff --git a/mkdocs.yml b/mkdocs.yml index e49e64a5..35711af8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -43,6 +43,9 @@ markdown_extensions: nav: - Introduction: index.md + - Learning Paths: + - Overview: LearningPaths/index.md + - Archviz To Vision Pro: LearningPaths/ArchvizToVisionPro.md - Tutorials: - Overview: Tutorials/index.md - Starter Demo: Tutorials/StarterDemo.md @@ -65,6 +68,7 @@ nav: - Getting Started: API/GettingStarted.md - Untold Engine CLI: API/UsingUntoldEngineCLI.md - Usage Examples: API/UsageExamples.md + - Scene Builder / UntoldView: API/UsingSceneBuilder.md - Engine Settings: API/UsingEngineAPI.md - Registration System: API/UsingRegistrationSystem.md - Transform System: API/UsingTransformSystem.md @@ -75,8 +79,11 @@ nav: - Materials: API/UsingMaterials.md - Lighting System: API/UsingLightingSystem.md - Animation System: API/UsingAnimationSystem.md + - Animation Transitions: API/UsingAnimationTransitions.md + - Root Motion: API/UsingRootMotion.md - Physics System: API/UsingPhysicsSystem.md - Steering System: API/UsingSteeringSystem.md + - Scripting System: API/UsingScriptingSystem.md - Post Effects: API/UsingPostFX.md - Scene Channels: API/UsingSceneChannels.md - LOD System: API/UsingLODSystem.md @@ -102,6 +109,7 @@ nav: - Rendering System: Architecture/renderingSystem.md - Rendering Extensions: Architecture/RenderingExtensions.md - XR Rendering System: Architecture/xrRenderingSystem.md + - Animation Pose Layer: Architecture/animationPoseLayer.md - Asset Format: Architecture/assetFormat.md - Scene Channels: Architecture/sceneChannels.md - Tile-Based Streaming: Architecture/tilebasedstreaming.md