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
852 changes: 20 additions & 832 deletions Sources/UntoldEngine/Mesh/Mesh.swift

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Sources/UntoldEngine/Scenes/Builder/MeshNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class MeshNode: Node, NodeAnimations, NodeKinetics {

if name == nil { setEntityName(entityId: self.entityID, name: resource) }

setEntityMesh(entityId: self.entityID, filename: resource.filename, withExtension: resource.extensionName)
setEntityMeshAsync(entityId: self.entityID, filename: resource.filename, withExtension: resource.extensionName)
}

public func materialData(
Expand Down
134 changes: 104 additions & 30 deletions Sources/UntoldEngine/Scenes/SceneSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public struct SceneData: Codable {
var chromaticAberration: ChromaticAberrationData? = nil
var depthOfField: DepthOfFieldData? = nil
var ssao: SSAOData? = nil
var antiAliasing: AntiAliasingData? = nil
}

enum SceneAssetKind: String, Codable {
Expand Down Expand Up @@ -100,6 +101,28 @@ struct SSAOData: Codable {
var enabled: Bool? = false
}

enum AntiAliasingModeData: String, Codable {
case none
case fxaa
case smaa
}

struct FXAAData: Codable {
var subpixelQuality: Float = 0.75
var edgeThreshold: Float = 0.125
var edgeThresholdMin: Float = 0.0625
}

struct SMAAData: Codable {
var edgeThreshold: Float = 0.1
}

struct AntiAliasingData: Codable {
var mode: AntiAliasingModeData = .fxaa
var fxaa: FXAAData = .init()
var smaa: SMAAData = .init()
}

struct LightData: Codable {
var color: simd_float3 = .one
var radius: Float = 1.0
Expand Down Expand Up @@ -354,6 +377,24 @@ private func collectDerivedAnimationSourceURLs(rootEntityId: EntityID, currentEn
}
}

private func collectDerivedAssetNodeIds(rootEntityId: EntityID, currentEntityId: EntityID, derivedEntityIds: inout [EntityID]) {
for childId in getEntityChildren(parentId: currentEntityId) {
if let derivedComp = scene.get(component: DerivedAssetNodeComponent.self, for: childId),
derivedComp.assetRootEntityId == rootEntityId
{
derivedEntityIds.append(childId)
}

collectDerivedAssetNodeIds(rootEntityId: rootEntityId, currentEntityId: childId, derivedEntityIds: &derivedEntityIds)
}
}

private func derivedAssetNodeIds(rootEntityId: EntityID) -> [EntityID] {
var derivedEntityIds: [EntityID] = []
collectDerivedAssetNodeIds(rootEntityId: rootEntityId, currentEntityId: rootEntityId, derivedEntityIds: &derivedEntityIds)
return derivedEntityIds
}

private func animationSourceURLsForSerialization(entityId: EntityID) -> [URL] {
var urls: [URL] = []
var seen = Set<String>()
Expand Down Expand Up @@ -743,9 +784,8 @@ public func serializeScene() -> SceneData {
if let assetInstanceComp = scene.get(component: AssetInstanceComponent.self, for: entityId) {
// Collect overrides from derived descendants
var overrides: [AssetOverrideData] = []
let children = getEntityChildren(parentId: entityId)

for childId in children {
for childId in derivedAssetNodeIds(rootEntityId: entityId) {
if let derivedComp = scene.get(component: DerivedAssetNodeComponent.self, for: childId) {
// Only collect overrides if the derived node belongs to this asset instance
if derivedComp.assetRootEntityId == entityId {
Expand Down Expand Up @@ -880,6 +920,25 @@ public func serializeScene() -> SceneData {

sceneData.ssao = SSAOData(radius: SSAOParams.shared.radius, bias: SSAOParams.shared.bias, intensity: SSAOParams.shared.intensity, enabled: SSAOParams.shared.enabled)

let antiAliasingModeData: AntiAliasingModeData
switch antiAliasingMode {
case .none:
antiAliasingModeData = .none
case .fxaa:
antiAliasingModeData = .fxaa
case .smaa:
antiAliasingModeData = .smaa
}
sceneData.antiAliasing = AntiAliasingData(
mode: antiAliasingModeData,
fxaa: FXAAData(
subpixelQuality: FXAAParams.shared.subpixelQuality,
edgeThreshold: FXAAParams.shared.edgeThreshold,
edgeThresholdMin: FXAAParams.shared.edgeThresholdMin
),
smaa: SMAAData(edgeThreshold: SMAAParams.shared.edgeThreshold)
)

// save asset base path
sceneData.assetBasePath = assetBasePath

Expand Down Expand Up @@ -1014,7 +1073,7 @@ public func deserializeScene(
if let env = sceneData.environment {
applyIBL = env.applyIBL ?? false
renderEnvironment = env.renderEnvironment ?? false
ambientIntensity = env.ambientIntensity ?? 0.44
ambientIntensity = env.ambientIntensity ?? 0.4

// Only generate HDR if IBL is explicitly enabled and HDR is specified
if applyIBL, let hdr = env.hdr, !hdr.isEmpty {
Expand Down Expand Up @@ -1079,6 +1138,22 @@ public func deserializeScene(
}
}

if let antiAliasing = sceneData.antiAliasing {
switch antiAliasing.mode {
case .none:
antiAliasingMode = .none
case .fxaa:
antiAliasingMode = .fxaa
case .smaa:
antiAliasingMode = .smaa
}

FXAAParams.shared.subpixelQuality = antiAliasing.fxaa.subpixelQuality
FXAAParams.shared.edgeThreshold = antiAliasing.fxaa.edgeThreshold
FXAAParams.shared.edgeThresholdMin = antiAliasing.fxaa.edgeThresholdMin
SMAAParams.shared.edgeThreshold = antiAliasing.smaa.edgeThreshold
}

withWorldMutationGate {
for sourceEntityData in sceneData.entities {
var sceneDataEntity = sourceEntityData
Expand Down Expand Up @@ -1135,20 +1210,19 @@ public func deserializeScene(

switch meshLoadingMode {
case .sync:
setEntityMesh(entityId: entityId, filename: filename, withExtension: withExtension, assetName: nil)
applyDeserializedLocalTransform(entityId: entityId, entityData: sceneDataEntity)

// Restore Static Batch Component (sync mode - mesh already loaded)
if sceneDataEntity.hasStaticBatchComponent == true {
setEntityStaticBatchComponent(entityId: entityId)
}
// Apply overrides synchronously after import (must run after static restore so
// per-node static opt-outs can remove static from selected children).
applyAssetInstanceOverrides(entityId: entityId, overrides: assetInstance.overrides)

// Setup animations (skeleton is now available)
if sceneDataEntity.hasAnimationComponent == true {
applyDeserializedAnimations(entityId: entityId, entityData: sceneDataEntity)
loadTracker.registerLoad()
setEntityMeshAsync(entityId: entityId, filename: filename, withExtension: withExtension, assetName: nil) { success in
applyDeserializedLocalTransform(entityId: entityId, entityData: sceneDataEntity)
if success {
if sceneDataEntity.hasStaticBatchComponent == true {
setEntityStaticBatchComponent(entityId: entityId)
}
applyAssetInstanceOverrides(entityId: entityId, overrides: assetInstance.overrides)
if sceneDataEntity.hasAnimationComponent == true {
applyDeserializedAnimations(entityId: entityId, entityData: sceneDataEntity)
}
}
loadTracker.completeLoad()
}
case .asyncDefault:
loadTracker.registerLoad()
Expand Down Expand Up @@ -1191,19 +1265,20 @@ public func deserializeScene(
setEntityStaticBatchComponent(entityId: entityId)
}
} else {
setEntityMesh(entityId: entityId, filename: filename, withExtension: withExtension, assetName: sceneDataEntity.assetName)
applyDeserializedLocalTransform(entityId: entityId, entityData: sceneDataEntity)

// Restore Static Batch Component (sync mode - mesh already loaded)
if sceneDataEntity.hasStaticBatchComponent == true {
setEntityStaticBatchComponent(entityId: entityId)
loadTracker.registerLoad()
setEntityMeshAsync(entityId: entityId, filename: filename, withExtension: withExtension, assetName: sceneDataEntity.assetName) { success in
applyDeserializedLocalTransform(entityId: entityId, entityData: sceneDataEntity)
if success {
if sceneDataEntity.hasStaticBatchComponent == true {
setEntityStaticBatchComponent(entityId: entityId)
}
if sceneDataEntity.hasAnimationComponent == true {
applyDeserializedAnimations(entityId: entityId, entityData: sceneDataEntity)
}
}
loadTracker.completeLoad()
}
}

// Setup animations (skeleton is now available)
if sceneDataEntity.hasAnimationComponent == true {
applyDeserializedAnimations(entityId: entityId, entityData: sceneDataEntity)
}
case .asyncDefault:
if isProcedural {
let meshes = createProceduralMeshes(assetName: sceneDataEntity.assetName)
Expand Down Expand Up @@ -1653,9 +1728,8 @@ public extension Notification.Name {
private func applyAssetInstanceOverrides(entityId: EntityID, overrides: [AssetOverrideData]) {
// Build nodePath -> derived entity map
var nodePathMap: [String: EntityID] = [:]
let children = getEntityChildren(parentId: entityId)

for childId in children {
for childId in derivedAssetNodeIds(rootEntityId: entityId) {
if let derivedComp = scene.get(component: DerivedAssetNodeComponent.self, for: childId) {
nodePathMap[derivedComp.nodePath] = childId
}
Expand Down
Loading
Loading