diff --git a/Sources/UntoldEngine/Scenes/SceneSerializer.swift b/Sources/UntoldEngine/Scenes/SceneSerializer.swift index 1d0106ade..a434e7d38 100644 --- a/Sources/UntoldEngine/Scenes/SceneSerializer.swift +++ b/Sources/UntoldEngine/Scenes/SceneSerializer.swift @@ -1311,21 +1311,24 @@ public func serializeScene() -> SceneData { var shouldApplyIBL = applyIBL if hdrURL.isEmpty == false { - var hdrExists = false - - // Check in user's asset base path (HDR folder) - if let basePath = assetBasePath { - let hdrPath = basePath.appendingPathComponent("HDR").appendingPathComponent(hdrURL) - hdrExists = FileManager.default.fileExists(atPath: hdrPath.path) + // Resolve through the same search LoadingSystem/generateHDR already use + // (assetBasePath/HDR, app bundle, engine bundle) rather than only checking + // assetBasePath/HDR directly. The active HDR may legitimately be one of the + // engine's built-in defaults (e.g. teatro_massimo_2k.hdr), which lives in the + // engine's resource bundle, not the project's asset folder -- checking only + // assetBasePath/HDR falsely reported those as "not found" and disabled IBL. + let nsName = hdrURL as NSString + let providedExtension = nsName.pathExtension + let candidates: [(base: String, ext: String)] = providedExtension.isEmpty + ? ["hdr", "exr", "png"].map { (hdrURL, $0) } + : [(nsName.deletingPathExtension, providedExtension)] + + let hdrExists = candidates.contains { candidate in + LoadingSystem.shared.resourceURL(forResource: candidate.base, withExtension: candidate.ext, subResource: nil) != nil + } - if !hdrExists { - Logger.logWarning(message: "[SceneSerializer] HDR file not found in assets: \(hdrURL), disabling IBL") - validatedHDR = nil - shouldApplyIBL = false - } - } else { - // No asset base path set, can't validate - Logger.logWarning(message: "[SceneSerializer] No asset base path set, cannot validate HDR") + if !hdrExists { + Logger.logWarning(message: "[SceneSerializer] HDR file not found in assets: \(hdrURL), disabling IBL") validatedHDR = nil shouldApplyIBL = false } @@ -1623,12 +1626,14 @@ public func deserializeScene( hdrURL = hdr } else { hdrURL = "" + hdrDirectoryURL = nil } } else { applyIBL = false renderEnvironment = false ambientIntensity = 0.4 hdrURL = "" + hdrDirectoryURL = nil } if let colorGrading = sceneData.colorGrading { diff --git a/Sources/UntoldEngine/Systems/LoadingSystem.swift b/Sources/UntoldEngine/Systems/LoadingSystem.swift index 780a0068e..afd6e00a5 100644 --- a/Sources/UntoldEngine/Systems/LoadingSystem.swift +++ b/Sources/UntoldEngine/Systems/LoadingSystem.swift @@ -109,9 +109,16 @@ public func getResourceURL(resourceName: String, ext: String, subName: String?) // Flat layout (no top-level "Assets") var searchPaths: [[String]] = [ + // Nested form (Models//.) is checked first, then the flat + // form (Models/.) the Editor's asset exporter actually writes -- + // it cooks the runtime asset into the category root, beside the per-model + // support folder, rather than inside it. See RuntimeExportLocation.swift. ["Models", resourceName, "\(resourceName).\(ext)"], + ["Models", "\(resourceName).\(ext)"], ["StreamModels", resourceName, "\(resourceName).\(ext)"], + ["StreamModels", "\(resourceName).\(ext)"], ["Animations", resourceName, "\(resourceName).\(ext)"], + ["Animations", "\(resourceName).\(ext)"], ["HDR", "\(resourceName).\(ext)"], ["Gaussians", "\(resourceName).\(ext)"], ["Scripts", "\(resourceName).\(ext)"], @@ -132,8 +139,11 @@ public func getResourceURL(resourceName: String, ext: String, subName: String?) // Then try structured subdirectories let searchPaths: [[String]] = [ ["Models", resourceName, "\(resourceName).\(ext)"], + ["Models", "\(resourceName).\(ext)"], ["StreamModels", resourceName, "\(resourceName).\(ext)"], + ["StreamModels", "\(resourceName).\(ext)"], ["Animations", resourceName, "\(resourceName).\(ext)"], + ["Animations", "\(resourceName).\(ext)"], ["HDR", "\(resourceName).\(ext)"], ["Gaussians", "\(resourceName).\(ext)"], ["Scripts", "\(resourceName).\(ext)"], diff --git a/Sources/UntoldEngine/Utils/FuncUtils.swift b/Sources/UntoldEngine/Utils/FuncUtils.swift index 33107a41e..2b1cc09ad 100644 --- a/Sources/UntoldEngine/Utils/FuncUtils.swift +++ b/Sources/UntoldEngine/Utils/FuncUtils.swift @@ -53,7 +53,7 @@ private func makeHDRTexture(width: Int, height: Int, label: String) throws -> MT 1 + floorf(log2f(fmaxf(Float(width), Float(height)))) ) - guard let texture = renderInfo.device.makeTexture(descriptor: descriptor) else { + guard let device = renderInfo.device, let texture = device.makeTexture(descriptor: descriptor) else { throw LoadError.textureCreationFailed } texture.label = label @@ -154,6 +154,10 @@ public func loadTexture( } public func loadImage(_ textureName: String, from directory: URL? = nil) throws -> URL { + guard !textureName.isEmpty else { + throw LoadError.urlCreationFailed(textureName) + } + if let directory { let fileURL = directory.appendingPathComponent(textureName) if FileManager.default.fileExists(atPath: fileURL.path) {