Introduce TankDefinition/TankCatalog loaded from Tanks.json, bundle the Drachengas USDZ tank models, add tank selection and preview views, and update the AR experience, editor and project views to use the catalog. Includes tests for catalog loading and model selection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
2.9 KiB
Swift
77 lines
2.9 KiB
Swift
//
|
|
// TankDepthFadeMaterial.swift
|
|
// GasTankAR
|
|
//
|
|
// Created by Dennis Nemec on 26.08.26.
|
|
//
|
|
|
|
import RealityKit
|
|
import Metal
|
|
import UIKit
|
|
|
|
/// Kapselt das Höhen-Fade-CustomMaterial für den unterirdischen Tank:
|
|
/// verblasst die Opacity anhand der Welt-Höhe (Kopf opak, vergrabener Teil transparent).
|
|
@MainActor
|
|
enum TankDepthFadeMaterial {
|
|
/// Fade-Band (Meter) unterhalb der Bodenkante, über das die Transparenz einsetzt.
|
|
static let band: Float = 0.15
|
|
/// Minimale Opacity des vergrabenen Teils.
|
|
static let belowAlpha: Float = 0.3
|
|
|
|
private static let shader: CustomMaterial.SurfaceShader? = {
|
|
guard let library = MTLCreateSystemDefaultDevice()?.makeDefaultLibrary() else { return nil }
|
|
return CustomMaterial.SurfaceShader(named: "tankDepthFadeSurface", in: library)
|
|
}()
|
|
|
|
/// Ersetzt die Originalmaterialien rekursiv durch CustomMaterial-Versionen,
|
|
/// wobei Texturen und Farben des USDZ erhalten bleiben. Schatten werden für
|
|
/// transparente Model-Entities separat deaktiviert.
|
|
static func apply(to entity: Entity) {
|
|
guard let shader else { return }
|
|
forEachModelEntity(entity) { modelEntity in
|
|
guard var component = modelEntity.model else { return }
|
|
component.materials = component.materials.map { material in
|
|
guard var custom = try? CustomMaterial(from: material, surfaceShader: shader) else {
|
|
return material
|
|
}
|
|
custom.blending = .transparent(opacity: 1.0)
|
|
custom.custom.value = SIMD4<Float>(0, band, belowAlpha, 0)
|
|
return custom
|
|
}
|
|
modelEntity.model = component
|
|
modelEntity.components.set(GroundingShadowComponent(castsShadow: false))
|
|
modelEntity.components.set(DynamicLightShadowComponent(castsShadow: false))
|
|
}
|
|
}
|
|
|
|
/// Aktualisiert die Boden-Höhe (Welt-Y) in bereits angewandten Fade-Materialien.
|
|
static func updateGroundY(
|
|
_ groundY: Float,
|
|
belowAlpha: Float = 0.3,
|
|
on entity: Entity
|
|
) {
|
|
forEachModelEntity(entity) { modelEntity in
|
|
guard var component = modelEntity.model else { return }
|
|
component.materials = component.materials.map { material in
|
|
guard var custom = material as? CustomMaterial else { return material }
|
|
var customValue = custom.custom.value
|
|
customValue.x = groundY
|
|
customValue.y = band
|
|
customValue.z = belowAlpha
|
|
custom.custom.value = customValue
|
|
return custom
|
|
}
|
|
modelEntity.model = component
|
|
}
|
|
}
|
|
|
|
private static func forEachModelEntity(_ entity: Entity, _ body: (ModelEntity) -> Void) {
|
|
if let modelEntity = entity as? ModelEntity {
|
|
body(modelEntity)
|
|
}
|
|
for child in entity.children {
|
|
forEachModelEntity(child, body)
|
|
}
|
|
}
|
|
}
|