Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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:
Expand Down Expand Up @@ -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.

---
Expand Down
7 changes: 4 additions & 3 deletions docs/API/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
167 changes: 113 additions & 54 deletions docs/API/UsingPhysicsSystem.md
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 10 additions & 7 deletions docs/API/UsingRegistrationSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,30 @@ 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:

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

---
Expand Down
Loading
Loading