/// AssetLoader - Asset management and loading system /// NO PLATFORM-SPECIFIC IMPORTS ALLOWED IN THIS MODULE /// Handles loading of 3D models, textures, animations, and audio files import Foundation // MARK: - Asset Types /// Asset loading result public enum AssetResult { case success(T) case failure(AssetError) } /// Asset loading errors public enum AssetError: Error, LocalizedError { case fileNotFound(String) case invalidFormat(String) case corruptedData(String) case unsupportedVersion(String) case outOfMemory public var errorDescription: String? { switch self { case .fileNotFound(let path): return "Asset file not found: \(path)" case .invalidFormat(let format): return "Invalid asset format: \(format)" case .corruptedData(let reason): return "Corrupted asset data: \(reason)" case .unsupportedVersion(let version): return "Unsupported asset version: \(version)" case .outOfMemory: return "Out of memory while loading asset" } } } /// Asset handle for reference counting and management public struct AssetHandle: Hashable, Sendable where T: Hashable { public let id: UUID public let path: String public init(id: UUID = UUID(), path: String) { self.id = id self.path = path } public func hash(into hasher: inout Hasher) { hasher.combine(id) } } // MARK: - Mesh Data /// Mesh data structure (vertex and index buffers) public struct MeshData: Sendable, Hashable { public var name: String public var vertices: [MeshVertex] public var indices: [UInt32] public var bounds: BoundingBox public init(name: String, vertices: [MeshVertex], indices: [UInt32], bounds: BoundingBox) { self.name = name self.vertices = vertices self.indices = indices self.bounds = bounds } public func hash(into hasher: inout Hasher) { hasher.combine(name) } } /// Vertex structure for mesh data public struct MeshVertex: Sendable, Hashable { public var position: SIMD3 public var normal: SIMD3 public var uv: SIMD2 public var tangent: SIMD3 public var boneIndices: SIMD4 // For skeletal animation public var boneWeights: SIMD4 // For skeletal animation public init(position: SIMD3, normal: SIMD3, uv: SIMD2, tangent: SIMD3 = .zero, boneIndices: SIMD4 = .zero, boneWeights: SIMD4 = .zero) { self.position = position self.normal = normal self.uv = uv self.tangent = tangent self.boneIndices = boneIndices self.boneWeights = boneWeights } } /// Bounding box for culling public struct BoundingBox: Sendable, Hashable { public var min: SIMD3 public var max: SIMD3 public init(min: SIMD3, max: SIMD3) { self.min = min self.max = max } } // MARK: - Texture Data /// Texture data structure public struct TextureData: Sendable, Hashable { public var name: String public var width: Int public var height: Int public var format: TextureFormat public var data: Data public var mipLevels: Int public init(name: String, width: Int, height: Int, format: TextureFormat, data: Data, mipLevels: Int = 1) { self.name = name self.width = width self.height = height self.format = format self.data = data self.mipLevels = mipLevels } public func hash(into hasher: inout Hasher) { hasher.combine(name) } } /// Texture format public enum TextureFormat: Sendable, Hashable { case rgba8 case rgba16f case rgba32f case dxt1 case dxt5 case bc7 } // MARK: - Animation Data /// Skeletal animation data public struct AnimationData: Sendable, Hashable { public var name: String public var duration: Float public var ticksPerSecond: Float public var channels: [AnimationChannel] public init(name: String, duration: Float, ticksPerSecond: Float, channels: [AnimationChannel]) { self.name = name self.duration = duration self.ticksPerSecond = ticksPerSecond self.channels = channels } public func hash(into hasher: inout Hasher) { hasher.combine(name) } } /// Animation channel (per-bone) public struct AnimationChannel: Sendable, Hashable { public var boneName: String public var positionKeys: [KeyFrame>] public var rotationKeys: [KeyFrame>] public var scaleKeys: [KeyFrame>] public init(boneName: String, positionKeys: [KeyFrame>], rotationKeys: [KeyFrame>], scaleKeys: [KeyFrame>]) { self.boneName = boneName self.positionKeys = positionKeys self.rotationKeys = rotationKeys self.scaleKeys = scaleKeys } } /// Animation keyframe public struct KeyFrame: Sendable, Hashable { public var time: Float public var value: T public init(time: Float, value: T) { self.time = time self.value = value } } // MARK: - Skeleton Data /// Skeletal hierarchy for character animation public struct SkeletonData: Sendable, Hashable { public var bones: [Bone] public var rootBoneIndex: Int public init(bones: [Bone], rootBoneIndex: Int = 0) { self.bones = bones self.rootBoneIndex = rootBoneIndex } } /// Individual bone in skeleton public struct Bone: Sendable, Hashable { public var name: String public var parentIndex: Int? public var offsetMatrix: float4x4 public init(name: String, parentIndex: Int?, offsetMatrix: float4x4) { self.name = name self.parentIndex = parentIndex self.offsetMatrix = offsetMatrix } } // MARK: - Asset Manager /// Main asset management system public actor AssetManager { private var meshCache: [String: MeshData] = [:] private var textureCache: [String: TextureData] = [:] private var animationCache: [String: AnimationData] = [:] private var skeletonCache: [String: SkeletonData] = [:] private var loadedAssets: Set = [] public init() {} // MARK: - Lifecycle public func initialize() { print(" ✓ Asset manager initialized") } public func shutdown() { meshCache.removeAll() textureCache.removeAll() animationCache.removeAll() skeletonCache.removeAll() loadedAssets.removeAll() print(" ✓ Asset manager shutdown (cleared caches)") } // MARK: - Mesh Loading /// Load a 3D mesh from file public func loadMesh(path: String) async -> AssetResult { // Check cache first if let cached = meshCache[path] { return .success(cached) } // Load from disk guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { return .failure(.fileNotFound(path)) } // Parse mesh format (OBJ, FBX, GLTF, etc.) guard let mesh = parseMeshData(data: data, path: path) else { return .failure(.invalidFormat(path)) } // Cache and return meshCache[path] = mesh loadedAssets.insert(path) return .success(mesh) } private func parseMeshData(data: Data, path: String) -> MeshData? { // Simplified placeholder - would parse actual formats // For sports games: support FBX (player models), GLTF (stadiums), custom formats // Create a simple test mesh (placeholder) return createTestMesh(name: path) } private func createTestMesh(name: String) -> MeshData { // Create a simple cube for testing let vertices: [MeshVertex] = [ MeshVertex(position: SIMD3(-1, -1, -1), normal: SIMD3(0, 0, -1), uv: SIMD2(0, 0)), MeshVertex(position: SIMD3(1, -1, -1), normal: SIMD3(0, 0, -1), uv: SIMD2(1, 0)), MeshVertex(position: SIMD3(1, 1, -1), normal: SIMD3(0, 0, -1), uv: SIMD2(1, 1)), MeshVertex(position: SIMD3(-1, 1, -1), normal: SIMD3(0, 0, -1), uv: SIMD2(0, 1)), ] let indices: [UInt32] = [0, 1, 2, 0, 2, 3] let bounds = BoundingBox( min: SIMD3(-1, -1, -1), max: SIMD3(1, 1, 1) ) return MeshData(name: name, vertices: vertices, indices: indices, bounds: bounds) } // MARK: - Texture Loading /// Load a texture from file public func loadTexture(path: String) async -> AssetResult { // Check cache first if let cached = textureCache[path] { return .success(cached) } // Load from disk guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { return .failure(.fileNotFound(path)) } // Parse texture format (PNG, JPG, DDS, KTX, etc.) guard let texture = parseTextureData(data: data, path: path) else { return .failure(.invalidFormat(path)) } // Cache and return textureCache[path] = texture loadedAssets.insert(path) return .success(texture) } private func parseTextureData(data: Data, path: String) -> TextureData? { // Simplified placeholder - would use image decoding libraries // For sports games: support DDS (compressed), PNG, JPG, HDR textures // Create a simple test texture return createTestTexture(name: path) } private func createTestTexture(name: String) -> TextureData { // Create a 2x2 test texture (white) let width = 2 let height = 2 var data = Data(count: width * height * 4) for i in 0..<(width * height * 4) { data[i] = 255 } return TextureData(name: name, width: width, height: height, format: .rgba8, data: data) } // MARK: - Animation Loading /// Load skeletal animation from file public func loadAnimation(path: String) async -> AssetResult { // Check cache first if let cached = animationCache[path] { return .success(cached) } // Load from disk guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { return .failure(.fileNotFound(path)) } // Parse animation format guard let animation = parseAnimationData(data: data, path: path) else { return .failure(.invalidFormat(path)) } // Cache and return animationCache[path] = animation loadedAssets.insert(path) return .success(animation) } private func parseAnimationData(data: Data, path: String) -> AnimationData? { // Simplified placeholder - would parse FBX/GLTF animations // For sports games: running, shooting, passing, tackling animations return createTestAnimation(name: path) } private func createTestAnimation(name: String) -> AnimationData { // Create a simple test animation let channel = AnimationChannel( boneName: "Root", positionKeys: [ KeyFrame(time: 0.0, value: SIMD3(0, 0, 0)), KeyFrame(time: 1.0, value: SIMD3(1, 0, 0)) ], rotationKeys: [ KeyFrame(time: 0.0, value: SIMD4(0, 0, 0, 1)), KeyFrame(time: 1.0, value: SIMD4(0, 0, 0, 1)) ], scaleKeys: [ KeyFrame(time: 0.0, value: SIMD3(1, 1, 1)), KeyFrame(time: 1.0, value: SIMD3(1, 1, 1)) ] ) return AnimationData(name: name, duration: 1.0, ticksPerSecond: 30.0, channels: [channel]) } // MARK: - Skeleton Loading /// Load skeletal hierarchy from file public func loadSkeleton(path: String) async -> AssetResult { // Check cache first if let cached = skeletonCache[path] { return .success(cached) } // Load from disk guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { return .failure(.fileNotFound(path)) } // Parse skeleton format guard let skeleton = parseSkeletonData(data: data, path: path) else { return .failure(.invalidFormat(path)) } // Cache and return skeletonCache[path] = skeleton loadedAssets.insert(path) return .success(skeleton) } private func parseSkeletonData(data: Data, path: String) -> SkeletonData? { // Simplified placeholder return createTestSkeleton() } private func createTestSkeleton() -> SkeletonData { let rootBone = Bone(name: "Root", parentIndex: nil, offsetMatrix: matrix_identity_float4x4) return SkeletonData(bones: [rootBone], rootBoneIndex: 0) } // MARK: - Cache Management public func unloadAsset(path: String) { meshCache.removeValue(forKey: path) textureCache.removeValue(forKey: path) animationCache.removeValue(forKey: path) skeletonCache.removeValue(forKey: path) loadedAssets.remove(path) } public func clearCache() { meshCache.removeAll() textureCache.removeAll() animationCache.removeAll() skeletonCache.removeAll() loadedAssets.removeAll() } public func getCacheSize() -> (meshes: Int, textures: Int, animations: Int, skeletons: Int) { return (meshCache.count, textureCache.count, animationCache.count, skeletonCache.count) } } // MARK: - Matrix Helper extension float4x4 { static var identity: float4x4 { return matrix_identity_float4x4 } }