From 19c4582866c61dde6783df5350f5cae44780dfd3 Mon Sep 17 00:00:00 2001 From: Max Cobb Date: Sat, 12 Jun 2021 13:18:04 +0100 Subject: [PATCH] Added cone and cylinder geometries --- .../RealityGeometries/CompleteVertex.swift | 38 +++++ .../RealityGeometries/MeshResource+Cone.swift | 125 +++++++++++++++ .../MeshResource+Cylinder.swift | 143 ++++++++++++++++++ .../MeshResource+Planes.swift | 21 ++- 4 files changed, 321 insertions(+), 6 deletions(-) create mode 100644 Sources/RealityGeometries/CompleteVertex.swift create mode 100644 Sources/RealityGeometries/MeshResource+Cone.swift create mode 100644 Sources/RealityGeometries/MeshResource+Cylinder.swift diff --git a/Sources/RealityGeometries/CompleteVertex.swift b/Sources/RealityGeometries/CompleteVertex.swift new file mode 100644 index 0000000..764e124 --- /dev/null +++ b/Sources/RealityGeometries/CompleteVertex.swift @@ -0,0 +1,38 @@ +// +// File.swift +// +// +// Created by Max Cobb on 12/06/2021. +// + +import RealityKit + +internal struct CompleteVertex { + var position: SIMD3 + var normal: SIMD3 + var uv: SIMD2 +} + +internal extension Array where Element == CompleteVertex { + func generateMeshDescriptor( + with indices: [UInt32], materials: [UInt32] = [] + ) -> MeshDescriptor { + var meshDescriptor = MeshDescriptor() + var positions: [SIMD3] = [] + var normals: [SIMD3] = [] + var uvs: [SIMD2] = [] + for vx in self { + positions.append(vx.position) + normals.append(vx.normal) + uvs.append(vx.uv) + } + meshDescriptor.positions = MeshBuffers.Positions(positions) + meshDescriptor.normals = MeshBuffers.Normals(normals) + meshDescriptor.textureCoordinates = MeshBuffers.TextureCoordinates(uvs) + meshDescriptor.primitives = .triangles(indices) + if !materials.isEmpty { + meshDescriptor.materials = MeshDescriptor.Materials.perFace(materials) + } + return meshDescriptor + } +} diff --git a/Sources/RealityGeometries/MeshResource+Cone.swift b/Sources/RealityGeometries/MeshResource+Cone.swift new file mode 100644 index 0000000..fed6ea0 --- /dev/null +++ b/Sources/RealityGeometries/MeshResource+Cone.swift @@ -0,0 +1,125 @@ +// +// File.swift +// +// +// Created by Max Cobb on 12/06/2021. +// + +import RealityKit + +extension MeshResource { + fileprivate static func coneIndices(_ sides: Int, _ lowerCenterIndex: UInt32, _ splitFaces: Bool) -> ([UInt32], [UInt32]) { + var indices: [UInt32] = [] + var materialIndices: [UInt32] = [] + for side in 0.. ([CompleteVertex], [CompleteVertex], [CompleteVertex]) { + var theta: Float = 0 + let thetaInc = 2 * .pi / Float(sides) + let uStep: Float = 1 / Float(sides); + // first vertices added will be bottom edges + var vertices = [CompleteVertex]() + // all top edge vertices of the cylinder + var upperEdgeVertices = [CompleteVertex]() + // bottom edge vertices + var lowerCapVertices = [CompleteVertex]() + + // create vertices for all sides of the cylinder + for side in 0...sides { + let cosTheta = cos(theta) + let sinTheta = sin(theta) + + let lowerPosition: SIMD3 = [ + radius * cosTheta, -height / 2, radius * sinTheta + ] + + let lowerNormal = cross( + SIMD3(-sinTheta, 0, cosTheta), + SIMD3(0, 1, 0) - SIMD3(cosTheta, 0, sinTheta) + ) + let bottomVertex = CompleteVertex( + position: lowerPosition, + normal: lowerNormal, + uv: [uStep * Float(side), 0] + ) + + // add vertex for bottom side of cylinder, facing out + vertices.append(bottomVertex) + + // add vertex for bottom side facing down + lowerCapVertices.append(CompleteVertex( + position: bottomVertex.position, + normal: [0, -1, 0], uv: [cosTheta + 1, sinTheta + 1] / 2) + ) + + // add vertex for top side facing out + let topVertex = CompleteVertex( + position: [0, height / 2, 0], + normal: lowerNormal, uv: [0.5, 1] + ) + upperEdgeVertices.append(topVertex) + + theta += thetaInc; + } + return (vertices, upperEdgeVertices, lowerCapVertices) + } + + /// Creates a new cone mesh with the specified values + /// - Parameters: + /// - radius: Radius of the code base + /// - height: Height of the code from base to tip + /// - sides: How many sides the cone should have, default is 24, minimum is 3 + /// - splitFaces: A Boolean you set to true to indicate that vertices shouldn’t be merged. + /// - Returns: A cone mesh + public static func generateCone( + radius: Float, height: Float, sides: Int = 24, splitFaces: Bool = false + ) throws -> MeshResource { + assert(sides > 2, "Sides must be an integer above 2") + let halfHeight = height / 2 + + // first vertices added to vertices will be bottom edges + // upperEdgeVertices are all top edge vertices of the cylinder + // lowerCapVertices are the bottom edge vertices + var ( + vertices, upperEdgeVertices, lowerCapVertices + ) = coneVertices(sides, radius, height) + + vertices.append(contentsOf: upperEdgeVertices) + + let lowerCenterIndex = UInt32(vertices.count) + vertices.append(CompleteVertex( + position: [0, -halfHeight, 0], normal: [0, -1, 0], uv: [0.5, 0.5] + )) + + vertices.append(contentsOf: lowerCapVertices) + + let (indices, materialIndices) = coneIndices( + sides, lowerCenterIndex, splitFaces + ) + + let meshDescr = vertices.generateMeshDescriptor( + with: indices, materials: materialIndices + ) + return try MeshResource.generate(from: [meshDescr]) + } +} + diff --git a/Sources/RealityGeometries/MeshResource+Cylinder.swift b/Sources/RealityGeometries/MeshResource+Cylinder.swift new file mode 100644 index 0000000..6063c5f --- /dev/null +++ b/Sources/RealityGeometries/MeshResource+Cylinder.swift @@ -0,0 +1,143 @@ +// +// File.swift +// +// +// Created by Max Cobb on 12/06/2021. +// + +import RealityKit + +extension MeshResource { + fileprivate static func cylinderIndices(_ sides: Int, _ lowerCenterIndex: UInt32, _ upperCenterIndex: UInt32, _ splitFaces: Bool) -> ([UInt32], [UInt32]) { + var indices: [UInt32] = [] + var materialIndices: [UInt32] = [] + for side in 0.. ([CompleteVertex], [CompleteVertex], [CompleteVertex], [CompleteVertex]) { + var theta: Float = 0 + let normalizeMult = 1 / sqrt(radius) + let thetaInc = 2 * .pi / Float(sides) + let uStep: Float = 1 / Float(sides); + // first vertices added will be bottom edges + var vertices = [CompleteVertex]() + // all top edge vertices of the cylinder + var upperEdgeVertices = [CompleteVertex]() + // bottom edge vertices + var lowerCapVertices = [CompleteVertex]() + // top edge vertices + var upperCapVertices = [CompleteVertex]() + + // create vertices for all sides of the cylinder + for side in 0...sides { + let cosTheta = cos(theta) + let sinTheta = sin(theta) + + let lowerPosition: SIMD3 = [ + radius * cosTheta, -height / 2, radius * sinTheta + ] + + let bottomVertex = CompleteVertex( + position: lowerPosition, + normal: [lowerPosition.x, 0, lowerPosition.z] * normalizeMult, + uv: [uStep * Float(side), 0] + ) + + // add vertex for bottom side of cylinder, facing out + vertices.append(bottomVertex) + + // add vertex for bottom side facing down + lowerCapVertices.append(CompleteVertex( + position: bottomVertex.position, + normal: [0, -1, 0], uv: [cosTheta + 1, sinTheta + 1] / 2) + ) + + // add vertex for top side facing out + let topVertex = CompleteVertex( + position: bottomVertex.position + [0, height, 0], + normal: bottomVertex.normal, uv: [uStep * Float(side), 1] + ) + upperEdgeVertices.append(topVertex) + + upperCapVertices.append(CompleteVertex( + position: topVertex.position, + normal: [0, 1, 0], uv: [cosTheta + 1, sinTheta + 1] / 2) + ) + + theta += thetaInc; + } + return (vertices, upperEdgeVertices, lowerCapVertices, upperCapVertices) + } + + /// Creates a new cylinder mesh with the specified values + /// - Parameters: + /// - radius: Radius of the cylinder + /// - height: Height of the cylinder + /// - sides: How many sides the cone should have, default is 24, minimum is 3 + /// - splitFaces: A Boolean you set to true to indicate that vertices shouldn’t be merged. + /// - Returns: A cylinder mesh. + public static func generateCylinder( + radius: Float, height: Float, sides: Int = 24, splitFaces: Bool = false + ) throws -> MeshResource { + assert(sides > 2, "Sides must be an integer above 2") + + let halfHeight = height / 2 + + // first vertices added to vertices will be bottom edges + // upperEdgeVertices are all top edge vertices of the cylinder + // lowerCapVertices are the bottom edge vertices + // upperCapVertices are the top edge vertices + var ( + vertices, upperEdgeVertices, lowerCapVertices, upperCapVertices + ) = cylinderVertices(sides, radius, height) + + vertices.append(contentsOf: upperEdgeVertices) + + let lowerCenterIndex = UInt32(vertices.count) + vertices.append(CompleteVertex( + position: [0, -halfHeight, 0], normal: [0, -1, 0], uv: [0.5, 0.5] + )) + + vertices.append(contentsOf: lowerCapVertices) + let upperCenterIndex = UInt32(vertices.count) + vertices.append(CompleteVertex( + position: [0, halfHeight, 0], normal: [0, 1, 0], uv: [0.5, 0.5] + )) + vertices.append(contentsOf: upperCapVertices) + + let (indices, materialIndices) = cylinderIndices( + sides, lowerCenterIndex, upperCenterIndex, splitFaces + ) + + let meshDescr = vertices.generateMeshDescriptor( + with: indices, materials: materialIndices + ) + return try MeshResource.generate(from: [meshDescr]) + } +} diff --git a/Sources/RealityGeometries/MeshResource+Planes.swift b/Sources/RealityGeometries/MeshResource+Planes.swift index 90b4b0a..6d96d5d 100644 --- a/Sources/RealityGeometries/MeshResource+Planes.swift +++ b/Sources/RealityGeometries/MeshResource+Planes.swift @@ -8,19 +8,29 @@ import RealityKit extension MeshResource { - static func generateDetailedPlane( + /// Creates a new plane mesh with the specified values. + /// - Parameters: + /// - width: Width of the output plane + /// - depth: Depth of the output plane + /// - vertices: Vertex count in the x and z axis + /// - Returns: A plane mesh + public static func generateDetailedPlane( width: Float, depth: Float, vertices: (Int, Int) ) throws -> MeshResource { var descr = MeshDescriptor() var meshPositions: [SIMD3] = [] var indices: [UInt32] = [] - for x_v in 0..<(vertices.0) { // 5 + var textureMap: [SIMD2] = [] + for x_v in 0..<(vertices.0) { let vertexCounts = meshPositions.count print(vertexCounts) - for y_v in 0..<(vertices.1) { // 4 + for y_v in 0..<(vertices.1) { meshPositions.append([ - (Float(x_v) / Float(vertices.0 - 1) - 0.5) * width, 0, (0.5 - Float(y_v) / Float(vertices.1 - 1)) * depth + (Float(x_v) / Float(vertices.0 - 1) - 0.5) * width, + 0, + (0.5 - Float(y_v) / Float(vertices.1 - 1)) * depth ]) + textureMap.append([Float(x_v) / Float(vertices.0 - 1), Float(y_v) / Float(vertices.1 - 1)]) if x_v > 0 && y_v > 0 { indices.append( contentsOf: [ @@ -32,8 +42,7 @@ extension MeshResource { } descr.primitives = .triangles(indices) descr.positions = MeshBuffer(meshPositions) - // - TODO: Add Texture Map -// descr.textureCoordinates = MeshBuffers.TextureCoordinates([]) + descr.textureCoordinates = MeshBuffers.TextureCoordinates(textureMap) return try .generate(from: [descr]) } }