Files
Gas-Tank-AR-App/GasTankAR/Views/TankAR/PipeRoute.swift
Dennis Nemec b8536d4182 Add tank catalog with USDZ models and tank selection
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>
2026-09-26 19:12:39 +02:00

99 lines
3.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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
}