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>
99 lines
3.0 KiB
Swift
99 lines
3.0 KiB
Swift
//
|
||
// PipeRoute.swift
|
||
// GasTankAR
|
||
//
|
||
// Created by Dennis Nemec on 28.08.26.
|
||
//
|
||
|
||
import Foundation
|
||
import simd
|
||
|
||
/// Eine Rohrführung als geordnete Folge von Wegpunkten (Welt-Koordinaten), die zu einem
|
||
/// zusammenhängenden Pfad verbunden werden – visualisiert die geplante Rohrverlegung.
|
||
struct PipeRoute: Codable {
|
||
private(set) var waypoints: [SIMD3<Float>] = []
|
||
private(set) var removedSegmentIndices: Set<Int> = []
|
||
|
||
var count: Int { waypoints.count }
|
||
|
||
/// Gesamtlänge des Pfads über alle aktiven Segmente in Metern.
|
||
var totalLength: Float {
|
||
guard waypoints.count > 1 else { return 0 }
|
||
return zip(waypoints, waypoints.dropFirst())
|
||
.enumerated()
|
||
.filter { !removedSegmentIndices.contains($0.offset) }
|
||
.reduce(0) { $0 + simd_distance($1.element.0, $1.element.1) }
|
||
}
|
||
|
||
func isSegmentRemoved(at index: Int) -> Bool {
|
||
removedSegmentIndices.contains(index)
|
||
}
|
||
|
||
mutating func removeSegmentAndOrphanedWaypoints(at index: Int) {
|
||
guard index >= 0, index < waypoints.count - 1 else { return }
|
||
removedSegmentIndices.insert(index)
|
||
|
||
var connected = Array(repeating: false, count: waypoints.count)
|
||
for segmentIndex in 0..<(waypoints.count - 1)
|
||
where !removedSegmentIndices.contains(segmentIndex) {
|
||
connected[segmentIndex] = true
|
||
connected[segmentIndex + 1] = true
|
||
}
|
||
|
||
let oldToNew = connected.enumerated().reduce(into: [Int: Int]()) { result, item in
|
||
if item.element {
|
||
result[item.offset] = result.count
|
||
}
|
||
}
|
||
|
||
let oldRemovedSegments = removedSegmentIndices
|
||
waypoints = waypoints.enumerated()
|
||
.filter { connected[$0.offset] }
|
||
.map(\.element)
|
||
|
||
removedSegmentIndices = Set(
|
||
oldRemovedSegments.compactMap { oldIndex in
|
||
guard let newIndex = oldToNew[oldIndex],
|
||
oldToNew[oldIndex + 1] != nil else {
|
||
return nil
|
||
}
|
||
return newIndex
|
||
}
|
||
)
|
||
}
|
||
|
||
var formattedLength: String {
|
||
String(format: "%.2f m", totalLength)
|
||
}
|
||
|
||
mutating func add(_ waypoint: SIMD3<Float>) {
|
||
waypoints.append(waypoint)
|
||
}
|
||
|
||
@discardableResult
|
||
mutating func removeLast() -> SIMD3<Float>? {
|
||
guard !waypoints.isEmpty else { return nil }
|
||
let removedIndex = waypoints.count - 2
|
||
removedSegmentIndices.remove(removedIndex)
|
||
removedSegmentIndices = Set(
|
||
removedSegmentIndices.map { index in
|
||
index > removedIndex ? index - 1 : index
|
||
}
|
||
)
|
||
return waypoints.removeLast()
|
||
}
|
||
|
||
mutating func clear() {
|
||
waypoints.removeAll()
|
||
removedSegmentIndices.removeAll()
|
||
}
|
||
}
|
||
|
||
/// Ablaufphasen beim Erstellen einer Rohrführung.
|
||
enum PipeRoutingPhase: Equatable {
|
||
/// Kein Rohr-Routing aktiv.
|
||
case idle
|
||
/// Wegpunkte werden gesetzt.
|
||
case placing
|
||
}
|