import LumaCore import SceneKit import SwiftUI struct PatternModelView: View { private let scene: SCNScene private let camera: SCNNode init(model: PatternVisualization.Model) { scene = SCNScene() scene.rootNode.addChildNode(SCNNode(geometry: Self.geometry(for: model))) camera = Self.camera(framing: model.vertices) scene.rootNode.addChildNode(camera) } var body: some View { SceneView(scene: scene, pointOfView: camera, options: [.allowsCameraControl, .autoenablesDefaultLighting]) .frame(width: 400, height: 400) } private static func camera(framing vertices: [Float]) -> SCNNode { let positions = stride(from: 0, to: vertices.count - 2, by: 3).map { SIMD3(vertices[$0], vertices[$0 + 1], vertices[$0 + 2]) } let low = positions.reduce(positions[0], simd_min) let high = positions.reduce(positions[0], simd_max) let center = (low + high) / 2 let radius = max(simd_length(high - center), .leastNormalMagnitude) let camera = SCNCamera() camera.zNear = Double(radius) * 0.01 camera.zFar = Double(radius) * 100 let node = SCNNode() node.camera = camera node.simdPosition = center + simd_normalize(SIMD3(1, 0.8, 1.6)) * radius * 2.8 node.simdLook(at: center) return node } private static let defaultColor: [Float] = [1, 0x7f / 255, 0x33 / 255, 1] private static func geometry(for model: PatternVisualization.Model) -> SCNGeometry { let vertexCount = model.vertices.count / 3 let indices = model.indices ?? (0.. SCNGeometrySource { let stride = components * MemoryLayout.size return SCNGeometrySource( data: floats.withUnsafeBufferPointer { Data(buffer: $0) }, semantic: semantic, vectorCount: floats.count / components, usesFloatComponents: true, componentsPerVector: components, bytesPerComponent: MemoryLayout.size, dataOffset: 0, dataStride: stride) } private static func smoothNormals(_ vertices: [Float], indices: [UInt32]) -> [Float] { let positions = stride(from: 0, to: vertices.count - 2, by: 3).map { SIMD3(vertices[$0], vertices[$0 + 1], vertices[$0 + 2]) } var sums = [SIMD3](repeating: .zero, count: positions.count) for triangle in stride(from: 0, to: indices.count - 2, by: 3) { let corners = (0..<3).map { Int(indices[triangle + $0]) } let face = simd_cross(positions[corners[1]] - positions[corners[0]], positions[corners[2]] - positions[corners[0]]) for corner in corners { sums[corner] += face } } return sums.flatMap { sum -> [Float] in let normal = simd_length(sum) > 0 ? simd_normalize(sum) : SIMD3(0, 0, 1) return [normal.x, normal.y, normal.z] } } }