Skip to content

Commit

Permalink
[GeometriaClipping] Adding Capsule2Parametric shape
Browse files Browse the repository at this point in the history
  • Loading branch information
LuizZak committed Aug 18, 2024
1 parent edf742a commit 82a84f3
Show file tree
Hide file tree
Showing 10 changed files with 274 additions and 1 deletion.
39 changes: 39 additions & 0 deletions Sources/Geometria/2D/CircleArc2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,45 @@ public struct CircleArc2<Vector: Vector2Real>: GeometricType, CustomStringConver
sweepAngle: sweepAngle1
)
}

/// Creates a new circular arc that fits the given start/end points on the
/// circumference of the arc, and a center point.
///
/// The sweep angle is chosen to be the clockwise sweep angle that connects
/// startAngle to endAngle.
///
/// - note: The initializer assumes that `center` is equally distant to both
/// `startPoint` and `endPoint`.
public init(
clockwiseAngleToCenter center: Vector,
startPoint: Vector,
endPoint: Vector
) {
let radius = center.distance(to: startPoint)
let startAngle = center.angle(to: startPoint)
let endAngle = center.angle(to: endPoint)

let (sweepAngle1, sweepAngle2) = startAngle.relativeAngles(to: endAngle)

let sweepAngle: Angle<Scalar>
switch (sweepAngle1.radians > 0, sweepAngle2.radians > 0) {
case (false, true):
sweepAngle = sweepAngle2

case (true, false):
sweepAngle = sweepAngle1

case (true, true), (false, false):
sweepAngle = sweepAngle1
}

self.init(
center: center,
radius: radius,
startAngle: startAngle,
sweepAngle: sweepAngle
)
}
}

extension CircleArc2: Equatable where Vector: Equatable { }
Expand Down
2 changes: 1 addition & 1 deletion Sources/Geometria/2D/Protocols/Vector/Vector2Signed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public protocol Vector2Signed: Vector2Multiplicative & VectorSigned where SubVec
mutating func formLeftRotated()

/// Returns a vector that represents this vector's point, rotated 90º
/// clockwise clockwise relative to the origin.
/// clockwise relative to the origin.
func rightRotated() -> Self

/// Rotates this vector 90º clockwise relative to the origin.
Expand Down
92 changes: 92 additions & 0 deletions Sources/GeometriaClipping/2D/Geometry/Capsule2Parametric.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import Geometria

/// A parametric geometry that is defined by line segments connecting two circular
/// arcs at the end-points.
public struct Capsule2Parametric<Vector: Vector2Real>: ParametricClip2Geometry, Equatable {
public typealias Scalar = Vector.Scalar
public typealias Simplex = Parametric2GeometrySimplex<Vector>
public typealias Contour = Parametric2Contour<Vector>

public var start: Vector
public var startRadius: Scalar
public var end: Vector
public var endRadius: Scalar

public var startPeriod: Period
public var endPeriod: Period

public var isReversed: Bool

internal init(
start: Vector,
end: Vector,
radius: Scalar,
startPeriod: Period,
endPeriod: Period
) {
self.init(
start: start,
startRadius: radius,
end: end,
endRadius: radius,
startPeriod: startPeriod,
endPeriod: endPeriod
)
}

internal init(
start: Vector,
startRadius: Scalar,
end: Vector,
endRadius: Scalar,
startPeriod: Period,
endPeriod: Period
) {
self.start = start
self.startRadius = startRadius
self.end = end
self.endRadius = endRadius
self.startPeriod = startPeriod
self.endPeriod = endPeriod
self.isReversed = false
}

public func allContours() -> [Contour] {
let startCircle = Circle2(center: start, radius: startRadius)
let endCircle = Circle2(center: end, radius: endRadius)

let tangents = startCircle.outerTangents(to: endCircle)

let startArc = CircleArc2(clockwiseAngleToCenter: start, startPoint: tangents.1.start, endPoint: tangents.0.start)
let endArc = CircleArc2(clockwiseAngleToCenter: end, startPoint: tangents.0.end, endPoint: tangents.1.end)

let simplexes: [Simplex] = [
.circleArc2(.init(circleArc: startArc, startPeriod: 0, endPeriod: 0)),
.lineSegment2(.init(lineSegment: tangents.0, startPeriod: 0, endPeriod: 0)),
.circleArc2(.init(circleArc: endArc, startPeriod: 0, endPeriod: 0)),
.lineSegment2(.init(lineSegment: tangents.1.reversed, startPeriod: 0, endPeriod: 0)),
]

let contour = Contour(
normalizing: simplexes,
startPeriod: startPeriod,
endPeriod: endPeriod
)

if isReversed {
return [
contour.reversed()
]
} else {
return [
contour
]
}
}

public func reversed() -> Capsule2Parametric {
var copy = self
copy.isReversed = !isReversed
return copy
}
}
142 changes: 142 additions & 0 deletions Tests/GeometriaClippingTests/2D/Geometry/Capsule2ParametricTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import Geometria
import TestCommons
import XCTest

@testable import GeometriaClipping

class Capsule2ParametricTests: XCTestCase {
typealias Sut = Capsule2Parametric<Vector2D>
let accuracy: Double = 1e-12

func testEphemeral() {
let sut = Sut(
start: .init(x: -150, y: -30),
startRadius: 60,
end: .init(x: 70, y: 80),
endRadius: 100,
startPeriod: 0.0,
endPeriod: 1.0
)

TestFixture.beginFixture { fixture in
fixture.add(sut, category: "input")

fixture.assertions(on: sut)
.assertSimplexes(
accuracy: accuracy,
[
.circleArc2(
.init(
circleArc: .init(
center: .init(x: -150.0, y: -30.0),
radius: 60.00000000000001,
startAngle: Angle(radians: 2.1977925247947656),
sweepAngle: Angle(radians: 2.8148954755916673)
),
startPeriod: 0.0,
endPeriod: 0.16870660664537054
)
),
.lineSegment2(
.init(
lineSegment: .init(
start: .init(x: -132.2516485101565, y: -87.31488479786879),
end: .init(x: 99.5805858164058, y: -15.524807996447976)
),
startPeriod: 0.16870660664537054,
endPeriod: 0.41113094230800334
)
),
.circleArc2(
.init(
circleArc: .init(
center: .init(x: 70.0, y: 80.0),
radius: 100.0,
startAngle: Angle(radians: -1.2704973067931533),
sweepAngle: Angle(radians: 3.468289831587919)
),
startPeriod: 0.41113094230800334,
endPeriod: 0.7575756643373672
)
),
.lineSegment2(
.init(
lineSegment: .init(
start: .init(x: 11.328505092685084, y: 160.97935345099341),
end: .init(x: -185.20289694438895, y: 18.58761207059606)
),
startPeriod: 0.7575756643373672,
endPeriod: 1.0
)
),
]
)
}
}

func testReversed() {
let sut = Sut(
start: .init(x: -150, y: -30),
startRadius: 60,
end: .init(x: 70, y: 80),
endRadius: 100,
startPeriod: 0.0,
endPeriod: 1.0
)

TestFixture.beginFixture { fixture in
fixture.add(sut, category: "input")

fixture.assertions(on: sut.reversed())
.assertSimplexes(
accuracy: accuracy,
[
.lineSegment2(
.init(
lineSegment: .init(
start: .init(x: -185.20289694438895, y: 18.58761207059606),
end: .init(x: 11.328505092685084, y: 160.97935345099341)
),
startPeriod: 0.0,
endPeriod: 0.24242433566263277
)
),
.circleArc2(
.init(
circleArc: .init(
center: .init(x: 70.0, y: 80.0),
radius: 100.0,
startAngle: Angle(radians: 2.1977925247947656),
sweepAngle: Angle(radians: -3.468289831587919)
),
startPeriod: 0.24242433566263277,
endPeriod: 0.5888690576919966
)
),
.lineSegment2(
.init(
lineSegment: .init(
start: .init(x: 99.5805858164058, y: -15.524807996447976),
end: .init(x: -132.2516485101565, y: -87.31488479786879)
),
startPeriod: 0.5888690576919966,
endPeriod: 0.8312933933546295
)
),
.circleArc2(
.init(
circleArc: .init(
center: .init(x: -150.0, y: -30.0),
radius: 60.00000000000001,
startAngle: Angle(radians: 5.012688000386433),
sweepAngle: Angle(radians: -2.8148954755916673)
),
startPeriod: 0.8312933933546295,
endPeriod: 1.0
)
),
]
)
}
}
}

0 comments on commit 82a84f3

Please sign in to comment.