diff --git a/Sources/Bounds.swift b/Sources/Bounds.swift index ca9a0e58..390aba17 100644 --- a/Sources/Bounds.swift +++ b/Sources/Bounds.swift @@ -56,6 +56,12 @@ public protocol Bounded { var bounds: Bounds { get } } +extension Bounds: CustomStringConvertible { + public var description: String { + "Bounds(min: [\(min.components)], max: [\(max.components)])" + } +} + extension Bounds: Codable { private enum CodingKeys: CodingKey { case min, max diff --git a/Sources/Color.swift b/Sources/Color.swift index 5ebd19a5..92f1454a 100644 --- a/Sources/Color.swift +++ b/Sources/Color.swift @@ -102,6 +102,18 @@ extension Color: RGBARepresentable { } } +extension Color: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: Double...) { + self.init(unchecked: elements) + } +} + +extension Color: CustomStringConvertible { + public var description: String { + "Color(\(r), \(g), \(b)\(a == 1 ? "" : ", \(a)"))" + } +} + extension Color: Codable { private enum CodingKeys: String, CodingKey { case r, g, b, a diff --git a/Sources/PathPoint.swift b/Sources/PathPoint.swift index f0b6f0d8..6aa43ddd 100644 --- a/Sources/PathPoint.swift +++ b/Sources/PathPoint.swift @@ -48,6 +48,25 @@ public struct PathPoint: Hashable, Sendable { public var isCurved: Bool } +extension PathPoint: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: Double...) { + self = .point(Vector(elements)) + } +} + +extension PathPoint: CustomStringConvertible { + public var description: String { + let p = "\(position.x), \(position.y)\(position.z == 0 ? "" : ", \(position.z)")" + let t = texcoord.map { + ", texcoord: [\($0.x), \($0.y)\($0.z == 0 ? "" : ", \($0.z)")]" + } ?? "" + let c = color.map { + ", color: [\($0.r), \($0.g), \($0.b)\($0.a == 1 ? "" : ", \($0.a)")]" + } ?? "" + return "PathPoint.\(isCurved ? "curve" : "point")(\(p)\(t)\(c))" + } +} + extension PathPoint: Codable { /// Creates a new path point by decoding from the given decoder. /// - Parameter decoder: The decoder to read data from. @@ -57,15 +76,15 @@ extension PathPoint: Codable { let y = try container.decode(Double.self) switch container.count { case 2: - self.init(Vector(x, y), texcoord: nil, color: nil, isCurved: false) + self = [x, y] case 3: let isCurved: Bool, position: Vector do { isCurved = try container.decodeIfPresent(Bool.self) ?? false - position = Vector(x, y) + position = [x, y] } catch { isCurved = false - position = try Vector(x, y, container.decode(Double.self)) + position = try [x, y, container.decode(Double.self)] } self.init(position, texcoord: nil, color: nil, isCurved: isCurved) case 4: @@ -73,12 +92,12 @@ extension PathPoint: Codable { let isCurved: Bool, position: Vector, texcoord: Vector? do { isCurved = try container.decodeIfPresent(Bool.self) ?? false - position = Vector(x, y, zOrU) + position = [x, y, zOrU] texcoord = nil } catch { isCurved = false - position = Vector(x, y) - texcoord = try Vector(zOrU, container.decode(Double.self)) + position = [x, y] + texcoord = try [zOrU, container.decode(Double.self)] } self.init(position, texcoord: texcoord, color: nil, isCurved: isCurved) case 5: @@ -87,12 +106,12 @@ extension PathPoint: Codable { let isCurved: Bool, position: Vector, texcoord: Vector? do { isCurved = try container.decodeIfPresent(Bool.self) ?? false - position = Vector(x, y) - texcoord = Vector(zOrU, uOrV) + position = [x, y] + texcoord = [zOrU, uOrV] } catch { isCurved = false - position = Vector(x, y, zOrU) - texcoord = try Vector(uOrV, container.decode(Double.self)) + position = [x, y, zOrU] + texcoord = try [uOrV, container.decode(Double.self)] } self.init(position, texcoord: texcoord, color: nil, isCurved: isCurved) case 6: diff --git a/Sources/Quaternion.swift b/Sources/Quaternion.swift index 4140ab5e..c4dbecda 100644 --- a/Sources/Quaternion.swift +++ b/Sources/Quaternion.swift @@ -409,6 +409,12 @@ extension Quaternion: Codable { } } +extension Quaternion: CustomStringConvertible { + public var description: String { + "Quaternion(\(x), \(y), \(z), \(w))" + } +} + public extension Quaternion { /// The zero quaternion. static let zero = Quaternion(unchecked: 0, 0, 0, 0) diff --git a/Sources/Vector.swift b/Sources/Vector.swift index 0c235705..628a7c2c 100644 --- a/Sources/Vector.swift +++ b/Sources/Vector.swift @@ -96,6 +96,18 @@ extension Vector: XYZRepresentable { } } +extension Vector: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: Double...) { + self.init(elements) + } +} + +extension Vector: CustomStringConvertible { + public var description: String { + "Vector(\(x), \(y)\(z == 0 ? "" : ", \(z)"))" + } +} + extension Vector: Comparable { /// Returns whether the leftmost vector has the lower value. /// This provides a stable order when sorting collections of vectors. diff --git a/Sources/Vertex.swift b/Sources/Vertex.swift index b3fdea9a..bb2a8141 100644 --- a/Sources/Vertex.swift +++ b/Sources/Vertex.swift @@ -93,6 +93,24 @@ extension Vertex: Comparable { } } +extension Vertex: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: Double...) { + self.init(Vector(elements)) + } +} + +extension Vertex: CustomStringConvertible { + public var description: String { + let p = "[\(position.x), \(position.y)\(position.z == 0 ? "" : ", \(position.z)")]" + let t = texcoord == .zero ? "" : + ", [\(texcoord.x), \(texcoord.y)\(texcoord.z == 0 ? "" : ", \(texcoord.z)")]" + let n = texcoord == .zero && normal == .zero ? "" : + ", [\(normal.x), \(normal.y), \(normal.z)]" + let c = color == .white ? "" : ", \(color)" + return "Vertex(\(p)\(n)\(t)\(c))" + } +} + extension Vertex: Codable { private enum CodingKeys: CodingKey { case position, normal, texcoord, color diff --git a/Tests/PolygonTests.swift b/Tests/PolygonTests.swift index 54146e7e..a4ec57ca 100644 --- a/Tests/PolygonTests.swift +++ b/Tests/PolygonTests.swift @@ -9,8 +9,8 @@ @testable import Euclid import XCTest -extension Euclid.Polygon { - /// Convenience constructor for testing +extension Euclid.Polygon: ExpressibleByArrayLiteral { + // Convenience constructor for testing init(unchecked vertices: [Vertex], plane: Plane? = nil) { self.init( unchecked: vertices, @@ -26,6 +26,10 @@ extension Euclid.Polygon { let normal = faceNormalForPolygonPoints(points, convex: nil, closed: nil) self.init(unchecked: points.map { Vertex($0, normal) }) } + + public init(arrayLiteral elements: Vector...) { + self.init(unchecked: elements) + } } class PolygonTests: XCTestCase { @@ -927,14 +931,13 @@ class PolygonTests: XCTestCase { } func testPolygonWithCollinearPointsCorrectlyDetessellated() { - let normal = -Vector.unitZ - let polygon = Polygon(unchecked: [ - Vertex(Vector(0, 0), normal), - Vertex(Vector(0.5, 0), normal), - Vertex(Vector(0.5, 1), normal), - Vertex(Vector(-0.5, 1), normal), - Vertex(Vector(-0.5, 0), normal), - ]) + let polygon: Euclid.Polygon = [ + [0, 0], + [0.5, 0], + [0.5, 1], + [-0.5, 1], + [-0.5, 0], + ] let triangles = polygon.triangulate() XCTAssertEqual(triangles.count, 3) let result = triangles.detessellate() @@ -944,53 +947,33 @@ class PolygonTests: XCTestCase { } func testHouseShapedPolygonCorrectlyDetessellated() { - let normal = -Vector.unitZ - let polygon = Polygon(unchecked: [ - Vertex(Vector(0, 0.5), normal), - Vertex(Vector(1, 0), normal), - Vertex(Vector(0.5, 0), normal), - Vertex(Vector(0.5, -1), normal), - Vertex(Vector(-0.5, -1), normal), - Vertex(Vector(-0.5, 0), normal), - Vertex(Vector(-1, 0), normal), - ]) + let polygon: Euclid.Polygon = [ + [0, 0.5], + [1, 0], + [0.5, 0], + [0.5, -1], + [-0.5, -1], + [-0.5, 0], + [-1, 0], + ] let triangles = polygon.triangulate() XCTAssertEqual(triangles.count, 5) let result = triangles.detessellate() XCTAssertEqual(result.count, 1) XCTAssertEqual(result.first?.undirectedEdges, polygon.undirectedEdges) + XCTAssert(result.flatMap { $0.vertices }.allSatisfy { $0.normal == -.unitZ }) XCTAssertEqual(Set(result.first?.vertices ?? []), Set(polygon.vertices)) } func testNonWatertightPolygonsCorrectlyDetessellated() { - let normal = -Vector.unitZ - let triangles = [ - Polygon(unchecked: [ - Vertex(Vector(0, -1), normal), - Vertex(Vector(-2, 0), normal), - Vertex(Vector(2, 0), normal), - ]), - Polygon(unchecked: [ - Vertex(Vector(-2, 0), normal), - Vertex(Vector(0, 1), normal), - Vertex(Vector(0, 0), normal), - ]), - Polygon(unchecked: [ - Vertex(Vector(2, 0), normal), - Vertex(Vector(0, 0), normal), - Vertex(Vector(0, 1), normal), - ]), + let triangles: [Euclid.Polygon] = [ + [[0, -1], [-2, 0], [2, 0]], + [[-2, 0], [0, 1], [0, 0]], + [[2, 0], [0, 0], [0, 1]], ] let result = triangles.detessellate() XCTAssertEqual(result.count, 1) - XCTAssertEqual(result, [ - Polygon(unchecked: [ - Vertex(Vector(0, -1), normal), - Vertex(Vector(-2, 0), normal), - Vertex(Vector(0, 1), normal), - Vertex(Vector(2, 0), normal), - ]), - ]) + XCTAssertEqual(result, [[[0, -1], [-2, 0], [0, 1], [2, 0]]]) } // MARK: area