Files
Gas-Tank-AR-App/GasTankAR/Views/TankAR/MeasurementEntityFactory.swift
2026-09-02 20:54:36 +02:00

99 lines
3.8 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.

//
// MeasurementEntityFactory.swift
// GasTankAR
//
// Created by Dennis Nemec on 28.08.26.
//
import RealityKit
import UIKit
/// Baut die AR-Visualisierung einer Strecke: zwei Endpunkt-Kugeln, die verbindende Linie
/// sowie ein Text-Label (Bezeichnung + Länge) am Mittelpunkt der Strecke.
///
/// Enthält außerdem wiederverwendbare Bausteine für die *Live*-Vorschau während des Messens
/// (Cursor-Kugel, skalierbare Linie, aktualisierbares Label).
@MainActor
enum MeasurementEntityFactory {
/// Name des Label-Halters – wird zum Wiederfinden fürs Billboarding genutzt.
static let labelName = "MeasurementLabel"
private static let color = UIColor.systemYellow
private static let pointRadius: Float = 0.012
private static let lineThickness: Float = 0.006
private static let labelHeight: Float = 0.06
private static let labelYOffset: Float = 0.03
/// Kleine Kugel für einen (auch vorläufigen) Messpunkt.
static func makePoint(at position: SIMD3<Float>) -> ModelEntity {
let sphere = ModelEntity(
mesh: .generateSphere(radius: pointRadius),
materials: [UnlitMaterial(color: color)]
)
sphere.position = position
return sphere
}
/// Vollständige Strecken-Visualisierung (Punkte + Linie + Label).
static func makeMeasurement(_ measurement: Measurement) -> Entity {
let container = Entity()
container.name = "Measurement-\(measurement.id.uuidString)"
container.addChild(makePoint(at: measurement.start))
container.addChild(makePoint(at: measurement.end))
let line = makeLineBase()
configureLine(line, from: measurement.start, to: measurement.end)
container.addChild(line)
let label = makeLabelHolder()
label.position = labelPosition(from: measurement.start, to: measurement.end)
label.addChild(makeText("\(measurement.name) (\(measurement.formattedDistance))"))
container.addChild(label)
return container
}
/// Basis-Linie der Länge 1 m (entlang +Z). Für die Live-Linie wird sie via ``configureLine`` skaliert/rotiert.
static func makeLineBase() -> ModelEntity {
ModelEntity(
mesh: .generateBox(size: SIMD3(lineThickness, lineThickness, 1)),
materials: [UnlitMaterial(color: color)]
)
}
/// Richtet eine per ``makeLineBase`` erzeugte Linie zwischen zwei Punkten aus (Länge über Z-Skalierung).
static func configureLine(_ line: Entity, from start: SIMD3<Float>, to end: SIMD3<Float>) {
let length = max(simd_distance(start, end), 0.0001)
line.transform = Transform(
scale: SIMD3(1, 1, length),
rotation: simd_quatf(from: SIMD3(0, 0, 1), to: normalize(end - start)),
translation: (start + end) / 2
)
}
/// Leerer Label-Halter (Billboard-Anker). Der Text wird als zentriertes Kind eingehängt.
static func makeLabelHolder() -> Entity {
let holder = Entity()
holder.name = labelName
return holder
}
/// Zentriertes Text-Entity für einen Label-Halter.
static func makeText(_ string: String) -> ModelEntity {
let mesh = MeshResource.generateText(
string,
extrusionDepth: 0.001,
font: .systemFont(ofSize: CGFloat(labelHeight), weight: .semibold)
)
var material = UnlitMaterial(color: .white)
material.faceCulling = .none
let text = ModelEntity(mesh: mesh, materials: [material])
text.position = -text.visualBounds(relativeTo: nil).center
return text
}
/// Position des Labels (Streckenmitte, leicht angehoben).
static func labelPosition(from start: SIMD3<Float>, to end: SIMD3<Float>) -> SIMD3<Float> {
(start + end) / 2 + SIMD3(0, labelYOffset, 0)
}
}