Skip to content

Commit

Permalink
Adding AngleSweep.isEquivalent(to:)
Browse files Browse the repository at this point in the history
  • Loading branch information
LuizZak committed Jul 6, 2024
1 parent e85b5e3 commit 840954a
Show file tree
Hide file tree
Showing 13 changed files with 243 additions and 46 deletions.
9 changes: 9 additions & 0 deletions Sources/Geometria/Angles/Angle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ public struct Angle<Scalar: FloatingPoint & ElementaryFunctions>: Hashable {
self.radians = radians
}

/// Returns `true` if `self` and `other` refer to the same angle, after
/// normalization.
public func isEquivalent(to other: Self) -> Bool {
let normalizedSelf = self.normalized(from: .zero)
let normalizedOther = other.normalized(from: .zero)

return normalizedSelf == normalizedOther
}

/// Returns this angle's normalized representation, starting from a given
/// offset, such that the angle is confined to `[lowerBound, lowerBound + π)`
public func normalized(from lowerBound: Scalar) -> Scalar {
Expand Down
43 changes: 30 additions & 13 deletions Sources/Geometria/Angles/AngleSweep.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,28 @@ public struct AngleSweep<Scalar: FloatingPoint & ElementaryFunctions>: Hashable
self.sweep = sweep
}

/// Returns `true` if `self` and `other` cover to the same angle sweep, after
/// normalization.
///
/// This method ignores the signs of the sweeps, and only compares the covered
/// circular arc of both angle sweeps.
public func isEquivalent(to other: Self) -> Bool {
let (selfStart, selfStop) = self.normalizedStartStop(from: .zero)
let (otherStart, otherStop) = other.normalizedStartStop(from: .zero)

if selfStart == otherStart && selfStop == otherStop {
return true
}

return false
}

/// Returns `true` if this circular arc contains a given angle value within
/// its start + sweep region.
public func contains(_ angle: Angle<Scalar>) -> Bool {
let normalAngle = angle.normalized(from: .zero)
var normalStart = start.normalized(from: .zero)
var normalStop = (start + sweep).normalized(from: .zero)

if sweep.radians < .zero {
swap(&normalStart, &normalStop)
}
let (normalStart, normalStop) = normalizedStartStop(from: .zero)

let normalAngle = angle.normalized(from: .zero)
if normalStart > normalStop {
return normalAngle >= normalStart || normalAngle <= normalStop
}
Expand All @@ -42,12 +53,7 @@ public struct AngleSweep<Scalar: FloatingPoint & ElementaryFunctions>: Hashable
/// Returns the result of clamping a given angle so it is contained within
/// this angle sweep.
public func clamped(_ angle: Angle<Scalar>) -> Angle<Scalar> {
var normalStart = start.normalized(from: .zero)
var normalStop = (start + sweep).normalized(from: .zero)

if sweep.radians < .zero {
swap(&normalStart, &normalStop)
}
let (normalStart, normalStop) = normalizedStartStop(from: .zero)

let nMin = (Angle(radians: normalStart) - angle).normalized(from: -.pi)
let nMax = (Angle(radians: normalStop) - angle).normalized(from: -.pi)
Expand All @@ -61,4 +67,15 @@ public struct AngleSweep<Scalar: FloatingPoint & ElementaryFunctions>: Hashable

return .init(radians: normalStop)
}

func normalizedStartStop(from lowerBound: Scalar) -> (normalStart: Scalar, normalStop: Scalar) {
var normalStart = start.normalized(from: lowerBound)
var normalStop = (start + sweep).normalized(from: lowerBound)

if sweep.radians < .zero {
swap(&normalStart, &normalStop)
}

return (normalStart, normalStop)
}
}
21 changes: 21 additions & 0 deletions Sources/GeometriaPeriodics/2D/CircleArc2Simplex.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Geometria

/// A 2-dimensional simplex composed of a circular arc segment.
public struct CircleArc2Simplex<Vector: Vector2Real>: Periodic2Simplex {
/// The circular arc segment associated with this simplex.
public var circleArc: CircleArc2<Vector>

/// Initializes a new circular arc segment simplex value with a given circular
/// arc segment.
public init(circleArc: CircleArc2<Vector>) {
self.circleArc = circleArc
}
}

extension CircleArc2Simplex {
@inlinable
public var start: Vector { circleArc.startPoint }

@inlinable
public var end: Vector { circleArc.endPoint }
}
20 changes: 20 additions & 0 deletions Sources/GeometriaPeriodics/2D/LineSegment2Simplex.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Geometria

/// A 2-dimensional simplex composed of a line segment.
public struct LineSegment2Simplex<Vector: Vector2Type>: Periodic2Simplex {
/// The line segment associated with this simplex.
public var lineSegment: LineSegment2<Vector>

/// Initializes a new line segment simplex value with a given line segment.
public init(lineSegment: LineSegment2<Vector>) {
self.lineSegment = lineSegment
}
}

extension LineSegment2Simplex {
@inlinable
public var start: Vector { lineSegment.start }

@inlinable
public var end: Vector { lineSegment.end }
}
17 changes: 17 additions & 0 deletions Sources/GeometriaPeriodics/2D/Protocols/Periodic2Geometry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Geometria

/// A 2-dimensional periodic geometry that produces lines and circular arcs as
/// periodic simplexes.
public protocol Periodic2Geometry: PeriodicGeometry {
/// The type of period that this periodic geometry uses to refer to its
/// ordered simplexes.
associatedtype Period: PeriodType

/// The inclusive lower bound period within this geometry.
var startPeriod: Period { get }

/// The exclusive upper bound period within this geometry.
///
/// This value is not part of the addressable period range.
var endPeriod: Period { get }
}
11 changes: 11 additions & 0 deletions Sources/GeometriaPeriodics/2D/Protocols/Periodic2Simplex.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Geometria

/// Protocol for types that describe 2-dimensional simplexes produced by 2-dimensional
/// periodic geometry.
public protocol Periodic2Simplex: PeriodicSimplex where Vector: Vector2Type {
/// Gets the starting point of this simplex.
var start: Vector { get }

/// Gets the ending point of this simplex.
var end: Vector { get }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// Type for periods that can be used to refer to sections of simplexes of periodic
/// geometry.
public protocol PeriodType: Comparable {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Geometria

/// Protocol for types that describe simplexes produced by periodic geometry.
public protocol PeriodicSimplex: GeometricType {
associatedtype Vector: VectorType
}
Empty file.
7 changes: 0 additions & 7 deletions Sources/GeometriaPeriodics/Protocols/Periodic2Geometry.swift

This file was deleted.

61 changes: 61 additions & 0 deletions Tests/GeometriaTests/Angles/AngleSweepTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import XCTest

@testable import Geometria

class AngleSweepTests: XCTestCase {
typealias Sut = AngleSweep<Double>

func testIsEquivalent_equal() {
assertIsEquivalent(
.init(start: .pi, sweep: .pi / 2),
.init(start: .pi, sweep: .pi / 2)
)
assertIsEquivalent(
.init(start: 0.0, sweep: .pi / 2),
.init(start: 0.0, sweep: .pi / 2)
)
assertNotIsEquivalent(
.init(start: .pi, sweep: .pi / 2),
.init(start: .pi / 2, sweep: .pi / 2)
)
}

func testIsEquivalent_equivalentSweep() {
assertIsEquivalent(
.init(start: .pi / 2, sweep: .pi / 2),
.init(start: .pi, sweep: -.pi / 2)
)
assertIsEquivalent(
.init(start: -.pi / 2, sweep: .pi),
.init(start: .pi / 2, sweep: -.pi)
)
}
}

// MARK: - Test internals

private func assertIsEquivalent(
_ lhs: AngleSweep<Double>,
_ rhs: AngleSweep<Double>,
file: StaticString = #file,
line: UInt = #line
) {
guard !lhs.isEquivalent(to: rhs) else {
return
}

XCTFail("\(lhs) is not equivalent to \(rhs)", file: file, line: line)
}

private func assertNotIsEquivalent(
_ lhs: AngleSweep<Double>,
_ rhs: AngleSweep<Double>,
file: StaticString = #file,
line: UInt = #line
) {
guard lhs.isEquivalent(to: rhs) else {
return
}

XCTFail("\(lhs) is equivalent to \(rhs)", file: file, line: line)
}
Loading

0 comments on commit 840954a

Please sign in to comment.