Initial commit for first version of project

This commit is contained in:
Dennis Nemec
2026-09-02 20:54:36 +02:00
parent 3d9f1ab6bc
commit 992bb32025
56 changed files with 5139 additions and 31 deletions

View File

@ -0,0 +1,128 @@
//
// SafetyZone.swift
// GasTankAR
//
// Created by Dennis Nemec on 27.08.26.
//
import RealityKit
import UIKit
/// Ein Sicherheitsabstand rund um den Tank: ein Kreis mit gegebenem Radius und Farbe,
/// dessen Mittelpunkt der Tank ist.
///
/// Modular gehalten: Ein weiterer Sicherheitsabstand wird schlicht durch Hinzufügen
/// eines Eintrags zu ``SafetyZone/all`` ergänzt – die Darstellung folgt automatisch.
struct SafetyZone: Identifiable {
let id = UUID()
/// Radius des Kreises in Metern (Abstand vom Tank-Mittelpunkt).
let radius: Float
/// Farbe des Rings.
let color: UIColor
/// Anzeigename, z. B. für Barrierefreiheit oder eine spätere Legende.
let label: String
/// Alle darzustellenden Sicherheitsabstände. Zum Ergänzen genügt ein weiterer Eintrag.
static let all: [SafetyZone] = [
SafetyZone(radius: 3, color: .systemRed, label: "3 m"),
SafetyZone(radius: 5, color: .systemOrange, label: "5 m"),
]
}
/// Baut die flachen, auf dem Boden liegenden Ringe für die Sicherheitsabstände.
@MainActor
enum SafetyZoneFactory {
/// Halbe Linienbreite des Rings in Metern (Ring = Radius ± Halbbreite).
private static let lineHalfWidth: Float = 0.03
/// Anzahl der Segmente pro Ring (höher = runder).
private static let segments = 96
/// Deckkraft der Ringe.
private static let opacity: Float = 0.85
/// Texthöhe der Meter-Beschriftung in Metern.
private static let labelHeight: Float = 0.25
/// Winkelpositionen (im Bogenmaß), an denen die Beschriftung auf dem Ring liegt.
/// Mehrere Positionen sorgen dafür, dass die Meterangabe aus jeder Richtung lesbar ist.
private static let labelAngles: [Float] = [0, .pi / 2, .pi, 3 * .pi / 2]
/// Erzeugt eine Entity mit je einem Ring pro Sicherheitsabstand, zentriert im Ursprung
/// und flach in der XZ-Ebene (y = 0) liegend.
static func makeEntity(for zones: [SafetyZone] = SafetyZone.all) -> Entity {
let root = Entity()
root.name = "SafetyZones"
for zone in zones {
root.addChild(makeRing(for: zone))
}
return root
}
private static func makeRing(for zone: SafetyZone) -> Entity {
let container = Entity()
container.name = "SafetyZone-\(zone.label)"
let mesh = annulusMesh(radius: zone.radius, halfWidth: lineHalfWidth, segments: segments)
var material = UnlitMaterial(color: zone.color.withAlphaComponent(CGFloat(opacity)))
material.blending = .transparent(opacity: .init(floatLiteral: opacity))
// Beidseitig sichtbar – unabhängig von der Blickrichtung auf den Boden.
material.faceCulling = .none
container.addChild(ModelEntity(mesh: mesh, materials: [material]))
for angle in labelAngles {
container.addChild(makeLabel(for: zone, at: angle))
}
return container
}
/// Erzeugt eine flach auf dem Boden liegende Meter-Beschriftung, tangential zum Ring
/// ausgerichtet (Oberkante zeigt zur Mitte, damit sie von außen aufrecht lesbar ist).
private static func makeLabel(for zone: SafetyZone, at angle: Float) -> Entity {
let mesh = MeshResource.generateText(
zone.label,
extrusionDepth: 0.002,
font: .systemFont(ofSize: CGFloat(labelHeight), weight: .bold)
)
var material = UnlitMaterial(color: zone.color)
material.faceCulling = .none
let text = ModelEntity(mesh: mesh, materials: [material])
// Auf den eigenen Ursprung zentrieren.
let bounds = text.visualBounds(relativeTo: nil)
text.position = -bounds.center
let holder = Entity()
holder.addChild(text)
holder.position = SIMD3(sin(angle) * zone.radius, 0.01, cos(angle) * zone.radius)
holder.orientation = simd_quatf(angle: angle, axis: SIMD3(0, 1, 0))
* simd_quatf(angle: -.pi / 2, axis: SIMD3(1, 0, 0))
return holder
}
/// Erzeugt einen flachen Kreisring (Annulus) in der XZ-Ebene.
private static func annulusMesh(radius: Float, halfWidth: Float, segments: Int) -> MeshResource {
var positions: [SIMD3<Float>] = []
var indices: [UInt32] = []
let inner = max(0, radius - halfWidth)
let outer = radius + halfWidth
for i in 0...segments {
let angle = Float(i) / Float(segments) * 2 * .pi
let cosA = cos(angle)
let sinA = sin(angle)
positions.append(SIMD3(inner * cosA, 0, inner * sinA))
positions.append(SIMD3(outer * cosA, 0, outer * sinA))
}
for i in 0..<segments {
let base = UInt32(i * 2)
indices.append(contentsOf: [base, base + 1, base + 2])
indices.append(contentsOf: [base + 2, base + 1, base + 3])
}
var descriptor = MeshDescriptor(name: "safetyRing")
descriptor.positions = MeshBuffers.Positions(positions)
descriptor.primitives = .triangles(indices)
return (try? MeshResource.generate(from: [descriptor])) ?? .generatePlane(width: 0, depth: 0)
}
}