Skip to content

Instantly share code, notes, and snippets.

@drewolbrich
Last active September 18, 2024 23:05
Show Gist options
  • Save drewolbrich/9ecf5ebf3484ffa4610919f03c7d84f6 to your computer and use it in GitHub Desktop.
Save drewolbrich/9ecf5ebf3484ffa4610919f03c7d84f6 to your computer and use it in GitHub Desktop.
A spherical wireframe tessellated icosahedron RealityKit entity
//
// WireframeTessellatedIcosahedronEntity.swift
//
// Created by Drew Olbrich on 9/18/24.
// Copyright © 2024 Lunar Skydiving LLC. All rights reserved.
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//
import RealityKit
import simd
// See https://github.com/maxxfrazer/RealityGeometries
import RealityGeometries
class WireframeTessellatedIcosahedronEntity: Entity {
private static let defaultMaterial = SimpleMaterial(color: .white, isMetallic: false)
private struct Edge: Hashable {
let start: Int
let end: Int
init(_ a: Int, _ b: Int) {
(start, end) = a < b ? (a, b) : (b, a)
}
}
/// Creates an `Entity` representing a wireframe icosahedron, with each face
/// tessellated into four faces, and the edges represented as cylinders.
///
/// The vertices of the tessellated icosahedron are projected onto a sphere.
/// - Parameters:
/// - radius: The radius of the sphere onto which the tessellated icosahedron's
/// vertices are projected.
/// - edgeRadius: The radius of the cylinders representing the tessellated
/// icosahedron's edges.
/// - edgeSides: The number of sides that each cylinder should have.
/// - material: The `Material` to assign to the cylinders.
init(radius: Float = 1, edgeRadius: Float = 0.02, edgeSides: Int = 8, material: Material = defaultMaterial) {
super.init()
let phi: Float = (1 + sqrt(5))/2
var vertices: [SIMD3<Float>] = [
SIMD3(0, 1, phi), SIMD3(0, -1, phi), SIMD3(0, 1, -phi), SIMD3(0, -1, -phi),
SIMD3(1, phi, 0), SIMD3(-1, phi, 0), SIMD3(1, -phi, 0), SIMD3(-1, -phi, 0),
SIMD3(phi, 0, 1), SIMD3(-phi, 0, 1), SIMD3(phi, 0, -1), SIMD3(-phi, 0, -1)
]
let initialFaces: [(Int, Int, Int)] = [
(0, 1, 8), (0, 8, 4), (0, 4, 5), (0, 5, 9), (0, 9, 1),
(1, 6, 8), (8, 6, 10), (8, 10, 4), (4, 10, 2), (4, 2, 5),
(5, 2, 11), (5, 11, 9), (9, 11, 7), (9, 7, 1), (1, 7, 6),
(3, 2, 10), (3, 10, 6), (3, 6, 7), (3, 7, 11), (3, 11, 2)
]
func getMidpoint(_ a: Int, _ b: Int) -> Int {
let midpoint = normalize((vertices[a] + vertices[b])/2)*sqrt(1 + phi*phi)
if let index = vertices.firstIndex(of: midpoint) {
return index
} else {
vertices.append(midpoint)
return vertices.count - 1
}
}
var subdividedFaces: [(Int, Int, Int)] = []
for face in initialFaces {
let (a, b, c) = face
let ab = getMidpoint(a, b)
let bc = getMidpoint(b, c)
let ca = getMidpoint(c, a)
subdividedFaces.append((a, ab, ca))
subdividedFaces.append((b, bc, ab))
subdividedFaces.append((c, ca, bc))
subdividedFaces.append((ab, bc, ca))
}
var edges: Set<Edge> = []
for (a, b, c) in subdividedFaces {
edges.insert(Edge(a, b))
edges.insert(Edge(b, c))
edges.insert(Edge(c, a))
}
let scale = radius/sqrt(1 + phi*phi)
for edge in edges {
let startPoint = vertices[edge.start]*scale
let endPoint = vertices[edge.end]*scale
let cylinderLength: Float = distance(startPoint, endPoint)
let cylinderCenter = (startPoint + endPoint)/2
guard let mesh = try? RealityGeometry.generateCylinder(radius: edgeRadius, height: cylinderLength, sides: edgeSides, splitFaces: false, smoothNormals: true) else {
assertionFailure("cylinder is undefined")
continue
}
let entity = ModelEntity(mesh: mesh, materials: [material])
entity.position = cylinderCenter
entity.look(at: endPoint, from: cylinderCenter, upVector: [0, 1, 0], relativeTo: nil)
let rotation = simd_quatf(angle: radians(fromDegrees: -90), axis: [1, 0, 0])
entity.transform = Transform(matrix: entity.transform.matrix * Transform(rotation: rotation).matrix)
addChild(entity)
}
}
@MainActor @preconcurrency required init() {
fatalError("init() has not been implemented")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment