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
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,48 @@
# Changelog
## v0.15.0 - 2026-08-06
### 🐞 Fixes
- [Patch] Added material fidelity report to the exporter (a36166f…)
- [Patch] Added untoldengine export-tiles CLI command with RAM-aware parallel export (50221e3…)
- [Patch] Fix HDR IBL energy calibration (fcda745…)
- [Patch] Fix iOS simulator Metal kernel build output (2ccc688…)
- [Patch] fixed abs path in serializer (7f8dd45…)
- [Patch] Improve directional shadow softness for XR (ac5a1ff…)
- [Patch] Default quaternion rotations to identity instead of zero (06651fa…)
- [Patch] Improve Blender-style light units, attenuation, and shadow behavior (a08fded…)
- [Patch] Added debug views for hdr (95a9c6f…)
- [Patch] Stage world/studio-light HDR environments and transcode EXR to ZIP for engine compatibility (b782fd4…)
- [Patch] Re-encode all file-backed material textures through Blender instead of raw-copying (6332c8f…)
- [Patch] Stage world/studio-light HDR environments in export-tiles too (2e351f2…)
- [Patch] Fixed bake resolution (ee582e4…)
- [Patch] Added new variable to setRendering (990520d…)
- [Patch] Fix joint transforms buffer CPU/GPU race with per-frame ring (f07c0fd…)
- [Patch] Restore encoder safety net with a closed flag (e8f9607…)
- [Patch] Persist scene-authored color management and environment state (8980058…)
- [Patch] Aggregate getAnimationPolicy over descendants (bee8546…)
- [Patch] Fix MPS -destination must be writable- crash on sRGB texture resample (7924e4a…)
- [Patch] Create the camera component in CameraNode when missing (aeefa4c…)
- [Patch] Address review: redundant changeAnimation is a no-op (5f61789…)
### 📚 Docs
- [Docs] Reorder topics in docs (30cf39e…)
- [Docs] Updated usage example (5c1bc45…)
- [Docs] Updated engine documentation (f9a88b0…)
- [Docs] Documented bake-materials and bake-color-management (a2696cf…)
- [Docs] Updated IBL usage documentation (845039d…)
- [Docs] Update the readme docs (96d0340…)
- [Docs] Updated xr images (6ac8f82…)
- [Docs] Added scene authored documentation (f9f0ea9…)
- [Docs] add plugin architecture and authoring guidelines (e363a83…)
- [Docs] Added community links (140edc0…)
### 🚀 Features
- [Feature] Add compiled animation clip sampling and pose layer foundation (8e3892b…)
- [Feature] Add per-frame update event with UntoldView onUpdate modifier (6c3dd20…)
- [Feature] Add simulator fallback for the TBDR deferred renderer (d78c8c1…)
- [Feature] Add per-entity animation policy (inherit/forceOn/forceOff) (24e9ad8…)
- [Feature] Add primitive, light, and camera nodes to the scene builder DSL (3b8bc92…)
- [Feature] Add physics backend plugin interface (260b23f…)
- [Feature] Add inertialized animation transitions to changeAnimation (08be3a6…)
- [Feature] Drive external physics backends via an EngineExtension coordinator (c3b8b0e…)
- [Feature] Mesh and Gaussian share streaming path (0e088a9…)
## v0.14.3 - 2026-07-17
### 🐞 Fixes
- [Patch] Added the new public debug view - position (3dccbad…)
Expand Down
48 changes: 30 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
<a href="https://github.com/untoldengine/UntoldEngine/issues/new?assignees=&labels=enhancement&template=02_FEATURE_REQUEST.md&title=feat%3A+">Request a Feature</a>
·
<a href="https://github.com/untoldengine/UntoldEngine/issues/new?assignees=&labels=question&template=04_SUPPORT_QUESTION.md&title=support%3A+">Ask a Question</a>
·
<a href="https://discord.gg/pSckCPFxj">Join Our Discord</a>
</div>

<div align="center">
Expand Down Expand Up @@ -45,7 +47,9 @@ Untold Engine is built for developers and teams who:
Creator & Lead Developer:
https://www.haroldserrano.com

![untoldengine-image](/docs/images/engine-highlight-5.png)
[![untoldengine-image](/docs/images/engine-highlight-5.png)](https://vimeo.com/1176995991?fl=ip&fe=ec)

Click to Play

---

Expand Down Expand Up @@ -78,6 +82,13 @@ workflow, product experience, and customer-specific behavior.
</tr>
</table>

## Requirements

- macOS 26 or newer
- Swift 6 / Xcode 26 or newer
- Apple Silicon Mac
- Supported platforms: macOS, iOS, and visionOS

## Try the Engine Right Now

The best first step is to run the Starter Demo. It is intentionally small and
Expand All @@ -93,7 +104,7 @@ Clone the repository and launch the Starter Demo:
```bash
git clone https://github.com/untoldengine/UntoldEngine.git
cd UntoldEngine
git checkout v0.14.3
git checkout v0.15.0
swift run StarterDemo
```

Expand Down Expand Up @@ -164,22 +175,7 @@ Untold Engine is well-suited for:

---

# Commercial Use, Sponsored Features, and Support

Untold Engine is open source under MPL-2.0 and can be used in commercial apps.
Paid commercial options are available for teams that need private engine
modifications, sponsored engine features, priority support, or custom terms.

- **Sponsored open-source features** — fund roadmap-aligned engine work that is
released into the public MPL engine.
- **Commercial license** — keep private modifications to engine internals closed
under commercial terms.
- **Priority support / retainers** — get focused help with engine integration,
rendering issues, performance, and production use.

See [COMMERCIAL.md](COMMERCIAL.md) for commercial licensing details.

---

# Engine API

Expand Down Expand Up @@ -298,6 +294,23 @@ Full license: https://www.mozilla.org/MPL/2.0/

---

# Commercial Use, Sponsored Features, and Support

Untold Engine is open source under MPL-2.0 and can be used in commercial apps.
Paid commercial options are available for teams that need private engine
modifications, sponsored engine features, priority support, or custom terms.

- **Sponsored open-source features** — fund roadmap-aligned engine work that is
released into the public MPL engine.
- **Commercial license** — keep private modifications to engine internals closed
under commercial terms.
- **Priority support / retainers** — get focused help with engine integration,
rendering issues, performance, and production use.

See [COMMERCIAL.md](COMMERCIAL.md) for commercial licensing details.

---

# Trademark

"Untold Engine" and the Untold Engine logo are trademarks of **Untold Engine Studios**. Forks may not use the name in a way that implies official endorsement. See [TRADEMARKS.md](TRADEMARKS.md).
Expand All @@ -308,4 +321,3 @@ Full license: https://www.mozilla.org/MPL/2.0/

- [GitHub Discussions](https://github.com/untoldengine/UntoldEngine/discussions) — ideas and questions
- [GitHub Issues](https://github.com/untoldengine/UntoldEngine/issues) — bugs and tasks
- [Discord](https://discord.gg/pSckCPFxj) — ask questions, share projects, and discuss engine development
4 changes: 4 additions & 0 deletions Sources/CShaderTypes/ShaderTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,10 @@ typedef enum{
gaussianTBDRRenderLocalCameraIndex,
}GaussianTBDRRenderBufferIndices;

typedef enum{
gaussianTBDRDrawOpaqueDepthTextureIndex = 0,
}GaussianTBDRDrawTextureIndices;

typedef enum{
outputTransformPassEncodingModeIndex
}OutputTransformBufferIndices;
Expand Down
2 changes: 1 addition & 1 deletion Sources/Demos/ShowcaseDemo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate {
private enum Constants {
static let appVersion = "0.14.3"
static let appVersion = "0.15.0"
static let defaultWindowSize = NSSize(width: 1920, height: 1080)
static let minimumWindowSize = NSSize(width: 640, height: 480)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Sandbox/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate {
private enum Constants {
static let appVersion = "0.14.3"
static let appVersion = "0.15.0"
static let windowSize = NSSize(width: 1600, height: 900)
}

Expand Down
6 changes: 0 additions & 6 deletions Sources/Sandbox/GameScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,6 @@
*/

// Uncomment to render a streamed scene
/*
let sceneRoot = createEntity()
setEntityStreamScene(entityId: sceneRoot, url: URL(string: "https://d8pyi1c08k1w.cloudfront.net/dungeon3/dungeon3.json")!){ success in
setSceneReady(success)
}
*/
}

private func configureEngineSystems() {
Expand Down
21 changes: 19 additions & 2 deletions Sources/UntoldEngine/ECS/Components.swift
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,18 @@ public enum MeshStreamingState: String {
case unloading // Being unloaded
}

/// Kind of asset a StreamingComponent loads/unloads.
///
/// Determines which loader `GeometryStreamingSystem.loadMesh`/`unloadMesh` dispatch to.
/// `.mesh` goes through the existing `.untold`/OCC mesh pipeline; `.gaussianSplat` goes
/// through `setEntityGaussianAsync`/`unloadGaussian`. The distance/radius scheduling,
/// concurrency, and memory-budget eviction logic in `GeometryStreamingSystem` is shared
/// across both kinds.
public enum StreamingAssetKind {
case mesh
case gaussianSplat
}

/// Component that marks an entity for geometry streaming
public class StreamingComponent: Component {
/// Distance from camera at which mesh starts loading
Expand All @@ -794,12 +806,15 @@ public class StreamingComponent: Component {
/// Filename of the asset (without extension)
public var assetFilename: String = ""

/// File extension (e.g., "usdz")
/// File extension (e.g., "usdz", or "ply" for gaussian splats)
public var assetExtension: String = ""

/// Optional: specific mesh name within the asset
public var assetName: String?

/// Which loader this entity streams through. See `StreamingAssetKind`.
public var assetKind: StreamingAssetKind = .mesh

/// Current streaming state
public var state: MeshStreamingState = .unloaded

Expand All @@ -817,14 +832,16 @@ public class StreamingComponent: Component {
withExtension ext: String,
streamingRadius: Float = 100.0,
unloadRadius: Float = 150.0,
priority: Int = 0
priority: Int = 0,
assetKind: StreamingAssetKind = .mesh
) {
self.init()
assetFilename = filename
assetExtension = ext
self.streamingRadius = streamingRadius
self.unloadRadius = unloadRadius
self.priority = priority
self.assetKind = assetKind
}
}

Expand Down
21 changes: 17 additions & 4 deletions Sources/UntoldEngine/Physics/PhysicsBackendRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,9 @@ private struct InstalledPhysicsBackendPlugin {
/// active is rejected — uninstall first. When no plugin is installed the engine
/// uses its built-in integrator, exactly as before this registry existed.
///
/// Install before `UntoldRenderer.create(...)`. Once the engine locks the registry
/// for runtime, install and uninstall are rejected.
/// Install before `UntoldRenderer.create(...)`. The registry locks for runtime on
/// the first fixed substep the coordinator simulates with the installed backend;
/// from then on install and uninstall are rejected.
public final class PhysicsBackendRegistry: @unchecked Sendable {
public static let shared = PhysicsBackendRegistry()

Expand Down Expand Up @@ -244,35 +245,44 @@ public final class PhysicsBackendRegistry: @unchecked Sendable {
}

lock.lock()
defer { lock.unlock() }

guard !lockedForRuntime else {
let failure = PhysicsBackendPluginFailure(registryLocked: true)
failuresByPluginID[manifest.id] = failure
lock.unlock()
return .rejected(failure)
}
if let active = installedPlugin, active.manifest.id != manifest.id {
let failure = PhysicsBackendPluginFailure(conflictingPluginID: active.manifest.id)
failuresByPluginID[manifest.id] = failure
lock.unlock()
return .rejected(failure)
}

let replacing = installedPlugin != nil
installedPlugin = InstalledPhysicsBackendPlugin(manifest: manifest, backend: backend)
failuresByPluginID.removeValue(forKey: manifest.id)
lock.unlock()

// Outside the lock: configures the backend and schedules the coordinator
// in EngineExtensionRegistry (idempotent for a replace of the same ID).
PhysicsCoordinator.shared.backendDidInstall(backend)
return replacing ? .replaced : .installed
}

/// Removes the backend plugin. Returns false if it is not installed or the registry is locked.
@discardableResult
public func uninstall(id: String) -> Bool {
lock.lock()
defer { lock.unlock() }
guard !lockedForRuntime, installedPlugin?.manifest.id == id else {
lock.unlock()
return false
}
installedPlugin = nil
failuresByPluginID.removeValue(forKey: id)
lock.unlock()

PhysicsCoordinator.shared.backendDidUninstall()
return true
}

Expand Down Expand Up @@ -314,5 +324,8 @@ public final class PhysicsBackendRegistry: @unchecked Sendable {
failuresByPluginID.removeAll()
lockedForRuntime = false
lock.unlock()

PhysicsCoordinator.shared.backendDidUninstall()
PhysicsCoordinator.shared.resetForTesting()
}
}
Loading
Loading