Add help view and refine AR tank placement

This commit is contained in:
Dennis Nemec
2026-11-07 18:38:35 +01:00
parent 7fa162c910
commit a343bde741
25 changed files with 250 additions and 49 deletions

View File

@ -26,6 +26,8 @@ final class ARSessionController: NSObject, ARSessionDelegate, ARCoachingOverlayV
}
/// Ob die Sicherheitsabstände (Ringe um den Tank) eingeblendet sind.
private(set) var showSafetyZones = false
/// Nur für Debug-Builds mit eingeschalteter Entwickler-Option.
let allowsTankResizing: Bool
/// Wird bei jedem erfolgreichen Foto erhöht – die UI nutzt das für ein kurzes Aufblitzen.
private(set) var snapshotFlashCounter = 0
/// Angelegte Strecken (nur für die laufende Sitzung gehalten).
@ -127,9 +129,15 @@ final class ARSessionController: NSObject, ARSessionDelegate, ARCoachingOverlayV
private static let anchorDetectionTimeout: Duration = .seconds(20)
private static let relocationTimeout: Duration = .seconds(15)
init(persistence: ARProjectPersisting, useLiDAR: Bool, arExposure: Double = 1.0) {
init(
persistence: ARProjectPersisting,
useLiDAR: Bool,
arExposure: Double = 1.0,
devTankResizingEnabled: Bool = false
) {
self.persistence = persistence
self.useLiDAR = useLiDAR
self.allowsTankResizing = TankResizePolicy.isEnabled(devFlag: devTankResizingEnabled)
self.showsPit = persistence.tank.placement == .belowGround && !persistence.tank.showLidOnly
self.entityFactory = TankEntityFactory(aboveGroundLightMultiplier: Float(arExposure))
self.configFactory = SessionConfigurationFactory(
@ -255,7 +263,7 @@ final class ARSessionController: NSObject, ARSessionDelegate, ARCoachingOverlayV
/// Setzt die Tank-Skalierung auf die Originalgröße zurück.
func resetTankScale() {
guard let tankEntity else { return }
guard allowsTankResizing, let tankEntity else { return }
tankEntity.scale = SIMD3<Float>(repeating: 1)
updateSafetyZones()
updateTankDimensionValues()
@ -967,6 +975,10 @@ final class ARSessionController: NSObject, ARSessionDelegate, ARCoachingOverlayV
}
container.transform = Transform(matrix: worldTransform)
// Ältere Projekte können eine gespeicherte Skalierung enthalten.
if !allowsTankResizing {
container.scale = SIMD3<Float>(repeating: 1)
}
updateDepthFadeGroundY()
updateSceneOcclusion()
updateSafetyZones()
@ -992,30 +1004,21 @@ final class ARSessionController: NSObject, ARSessionDelegate, ARCoachingOverlayV
safetyZoneEntity = entity
}
// Der Katalogwert ist im Modellraum gemessen. w = 0 vermeidet eine
// zweite Translation; Rotation und Skalierung des Tanks bleiben erhalten.
let domeCenterX = entityFactory.domeCenterX(for: persistence.tank)
let localOffset = SIMD4<Float>(domeCenterX, 0, 0, 0)
let transformedOffset = tankEntity.transformMatrix(relativeTo: tankAnchor) * localOffset
let tankPosition = tankEntity.position(relativeTo: tankAnchor)
let localDomeOffset = SIMD4<Float>(0, 0, safetyZoneDomeOffset, 0)
let tankTransform = tankEntity.transformMatrix(relativeTo: tankAnchor)
let domeOffset = tankTransform * localDomeOffset
entity.position = tankPosition + SIMD3(domeOffset.x, domeOffset.y, domeOffset.z)
}
/// Abstand vom Tankmittelpunkt zum seitlichen Domschacht entlang der lokalen
/// Tank-Längsachse (+Z), abgeleitet aus den Vorgabemaßen.
private var safetyZoneDomeOffset: Float {
switch persistence.tank.size {
case .t1_2:
return 2.460 / 2
case .t2_1:
return 4.255 / 2
case .t2_9:
return 5.500 / 2
}
entity.position = tankPosition + SIMD3(transformedOffset.x, 0, transformedOffset.z)
}
private func installManipulationGestures(on entity: ModelEntity) {
guard let arView else { return }
let recognizers = arView.installGestures([.translation, .rotation, .scale], for: entity)
let gestures: ARView.EntityGestures = allowsTankResizing
? [.translation, .rotation, .scale]
: [.translation, .rotation]
let recognizers = arView.installGestures(gestures, for: entity)
for recognizer in recognizers {
recognizer.addTarget(self, action: #selector(handleManipulationChanged(_:)))
}