Skip to content

Commit

Permalink
Merge pull request #13 from LuizZak/periodic-types
Browse files Browse the repository at this point in the history
2D Geometry Clipping
  • Loading branch information
LuizZak authored Aug 16, 2024
2 parents 8f928f6 + 8e6458b commit 37c6cb5
Show file tree
Hide file tree
Showing 114 changed files with 11,811 additions and 3,208 deletions.
2 changes: 1 addition & 1 deletion .spi.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 1
builder:
configs:
- documentation_targets: [Geometria, GeometriaAlgorithms]
- documentation_targets: [Geometria, GeometriaAlgorithms, GeometriaClipping]
7 changes: 7 additions & 0 deletions .swift-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"version": 1,
"indentation": {
"spaces": 4
},
"lineBreakBeforeEachArgument": true
}
1 change: 1 addition & 0 deletions Geometria-Unix.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@
"Hyperrectangle",
"Intersectable",
"Möller",
"periodics",
"octree",
"Octree",
"Periodics",
Expand Down
76 changes: 76 additions & 0 deletions GeometriaClipping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# GeometriaClipping Overview

This document contains information about concepts used by the `GeometriaClipping` extension library contained within this package.

`GeometriaClipping` is a 2-dimensional parametric clipping package that works on lines and circular arcs, reduced as 'simplexes', which compose larger objects called 'parametric geometries'.

Parametric Simplex
---

- Represents the simplest drawing operation, a line or a circular arc;
- Contains a startPeriod/endPeriod, which defines when it should be stroked
when drawing from say 0-1 in its parent geometry. Simplexes are chained in
sequence, with the end period of a simplex connecting directly into the next
simplex's start period;
- Needs to be 'clampable' between a range (start, end], where the result is
the same stroke operation, but cut to be within range cut to
(max(startPeriod, start), min(endPeriod, end)]. This affects the end points
of the stroke in relative terms, so e.g. a clamp of (startPeriod, (endPeriod - startPeriod) /
2]
results in the same start point, but an ending point halfway around the stroke
path;
- Needs to be 'computable' @ period 'p', effectively computing a global point
on the line/arc at the given period relative to its startPeriod/endPeriod;
- Needs to have an intersection function defined that returns periods on
any input pair of simplexes simplex1/simplex2, as pairs of periods (period1, period2)
that must map to the same global point 'p' when computed with the rule above -
this is required for the intersection process.

Parametric Geometry
---

- Composed of sequential simplexes joined end-to-end, looping back around to
the start at 'endPeriod';
- Has a global startPeriod/endPeriod that matches the one of the simplexes;
- Periods are comparable and wrap around, so endPeriod + n is the same as
startPeriod + n, assuming 'n' is < endPeriod - startPeriod - this only holds
true for geometries and not simplexes as they don't have any notion of 'wrapping'
by themselves;
- Needs a 'contains' function that queries for a global point's containment;
- Needs a 'simplexes in range' that performs a clamp of simplexes within a given
range (start, end] using the 'clampable' property of simplexes, dropping simplexes
that are completely out of the range;
- Has a 'compute at' function that takes a period 'p' and produces an appropriate
global point using the simplexes and their periods.

Intersections
---

- With the intersection function defined for simplexes, intersecting parametric
geometries results in a list of pairs for all possible intersections between
each simplex in geometry1/geometry2.

Example: Union operation
---

- Takes as input two geometries, geometry1/geometry2, and returns up to two geometries;
- Start with collecting all intersections of geometry1/geometry2;
- Query if there are any intersections at all;
- If not, check for containment using each period geometry's 'compute at' and
'contains' function, and the result is the appropriate geometry not contained
within the other;
- If neither geometry is contained within the other, the result is a tuple
of (geometry1, geometry2).
- Start by querying a random starting point on geometry1, and check if it is contained
within geometry2;
- If so, find the next intersection on geometry1 past the starting point;
- If not, find instead the _previous_ intersection on geometry1.
- Loop around the geometries, keeping track of which intersections have been seen, and
repeat the following, starting with 'current' as geometry1 and 'current point'
as the point computed above:
- Compute the 'next' intersection point on the current geometry;
- Store current's 'simplex in range' ('current point', 'next point'];
- At the end, store 'next' as 'current', and flip the current geometry
being stroked from geometry1/geometry2;
- Repeat until 'current' has already been visited.
- The result is then a parametric geometry of all the simplexes collected.
18 changes: 18 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
{
"object": {
"pins": [
{
"package": "MiniDigraph",
"repositoryURL": "https://github.com/LuizZak/MiniDigraph.git",
"state": {
"branch": null,
"revision": "188262a9aaafa42d4d51b369f804edbd57901130",
"version": "0.8.0"
}
},
{
"package": "MiniP5Printer",
"repositoryURL": "https://github.com/LuizZak/MiniP5Printer",
Expand All @@ -10,6 +19,15 @@
"version": "0.0.2"
}
},
{
"package": "swift-collections",
"repositoryURL": "https://github.com/apple/swift-collections.git",
"state": {
"branch": null,
"revision": "3d2dc41a01f9e49d84f0a3925fb858bed64f702d",
"version": "1.1.2"
}
},
{
"package": "swift-numerics",
"repositoryURL": "https://github.com/apple/swift-numerics",
Expand Down
51 changes: 37 additions & 14 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ let reportingSwiftSettings: [SwiftSetting] = [
let testCommons: Target = .target(
name: "TestCommons",
dependencies: [
"Geometria",
.product(name: "MiniP5Printer", package: "MiniP5Printer"),
"Geometria",
"GeometriaAlgorithms",
"GeometriaClipping",
]
)

Expand All @@ -38,24 +40,37 @@ let geometriaTestsTarget: Target = .testTarget(
// GeometriaAlgorithms
let geometriaAlgorithmsTarget: Target = .target(
name: "GeometriaAlgorithms",
dependencies: geometriaDependencies + ["Geometria"],
dependencies: geometriaDependencies + [
"Geometria",
],
swiftSettings: []
)
let geometriaAlgorithmsTestTarget: Target = .testTarget(
name: "GeometriaAlgorithmsTests",
dependencies: geometriaDependencies + ["GeometriaAlgorithms", "TestCommons"],
dependencies: geometriaDependencies + [
"GeometriaAlgorithms",
"TestCommons",
],
swiftSettings: []
)

// GeometriaPeriodics
let geometriaPeriodicsTarget: Target = .target(
name: "GeometriaPeriodics",
dependencies: geometriaDependencies + ["Geometria"],
// GeometriaClipping
let geometriaClippingTarget: Target = .target(
name: "GeometriaClipping",
dependencies: geometriaDependencies + [
"Geometria",
"GeometriaAlgorithms",
.product(name: "MiniDigraph", package: "MiniDigraph"),
.product(name: "OrderedCollections", package: "swift-collections"),
],
swiftSettings: []
)
let geometriaPeriodicsTestTarget: Target = .testTarget(
name: "GeometriaPeriodicsTests",
dependencies: geometriaDependencies + ["GeometriaPeriodics", "TestCommons"],
let geometriaClippingTestTarget: Target = .testTarget(
name: "GeometriaClippingTests",
dependencies: geometriaDependencies + [
"GeometriaClipping",
"TestCommons",
],
swiftSettings: []
)

Expand All @@ -64,22 +79,30 @@ let package = Package(
products: [
.library(
name: "Geometria",
targets: ["Geometria"]),
targets: ["Geometria"]
),
.library(
name: "GeometriaAlgorithms",
targets: ["GeometriaAlgorithms"]),
targets: ["GeometriaAlgorithms"]
),
.library(
name: "GeometriaClipping",
targets: ["GeometriaClipping"]
),
],
dependencies: [
.package(url: "https://github.com/apple/swift-numerics.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.2"),
.package(url: "https://github.com/LuizZak/MiniP5Printer.git", .exactItem("0.0.2")),
.package(url: "https://github.com/LuizZak/MiniDigraph.git", .exactItem("0.8.0")),
],
targets: [
geometriaTarget.applyReportBuildTime(),
geometriaTestsTarget.applyReportBuildTime(),
geometriaAlgorithmsTarget.applyReportBuildTime(),
geometriaAlgorithmsTestTarget.applyReportBuildTime(),
geometriaPeriodicsTarget.applyReportBuildTime(),
geometriaPeriodicsTestTarget.applyReportBuildTime(),
geometriaClippingTarget.applyReportBuildTime(),
geometriaClippingTestTarget.applyReportBuildTime(),
testCommons,
]
)
Expand Down
8 changes: 6 additions & 2 deletions Sources/Geometria/2D/CircleArc2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public typealias CircleArc2F = CircleArc2<Vector2F>
/// Represents a 2D [arc of a circle] as a center, radius, and start+sweep angles.
///
/// [arc of a circle]: https://en.wikipedia.org/wiki/Circular_arc
public struct CircleArc2<Vector: Vector2Real>: GeometricType {
public struct CircleArc2<Vector: Vector2Real>: GeometricType, CustomStringConvertible {
public typealias Scalar = Vector.Scalar

/// The center of the arc's circle.
Expand All @@ -28,6 +28,10 @@ public struct CircleArc2<Vector: Vector2Real>: GeometricType {
/// The sweep angle of this arc, in radians.
public var sweepAngle: Angle<Scalar>

public var description: String {
"\(type(of: self))(center: \(center), radius: \(radius), startAngle: \(startAngle), sweepAngle: \(sweepAngle))"
}

/// Initializes a new circular arc with the given input parameters.
public init(
center: Vector,
Expand Down Expand Up @@ -197,7 +201,7 @@ extension CircleArc2: LineIntersectableType {
with line: Line
) -> LineIntersection<Vector> where Line : LineFloatingPoint, Vector == Line.Vector {
let circle = self.asCircle2
let intersections = circle.intersection(with: line).pointNormals
let intersections = circle.intersection(with: line).lineIntersectionPointNormals

var result = LineIntersection<Vector>(
isContained: false,
Expand Down
35 changes: 25 additions & 10 deletions Sources/Geometria/2D/ClosedShape2Intersection.swift
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
/// The result of a intersection test against two 2-dimensional closed shapes.
public enum ClosedShape2Intersection<Vector: Vector2FloatingPoint> {
/// Represents the case where the convex's boundaries are completely contained
/// within the bounds of the other convex shape.
/// Represents the case where the shape's boundaries are completely contained
/// within the bounds of the other shape.
case contained

/// Represents the case where the other convex's boundaries are completely
/// contained within the bounds of the first convex shape.
/// Represents the case where the other shape's boundaries are completely
/// contained within the bounds of the first shape.
///
/// Is the diametrical opposite of `.contained`.
case contains

/// Represents the case where the convex crosses the bounds of the convex
/// shape on a single vertex, or tangentially, in case of spheroids.
/// Represents the case where the shape crosses the bounds of the shape on a
/// single vertex, or tangentially, in case of spheroids.
case singlePoint(PointNormal<Vector>)

/// A sequence of one or more intersection pairs of points that represent
/// the entrance and exit points of the intersection in relation to one of
/// the convexes.
/// the shapes.
case pairs([Pair])

/// Represents the case where no intersection occurs at any point.
case noIntersection

/// Returns all the point normals associated with this closed shape intersection
/// object.
public var pointNormals: [PointNormal<Vector>] {
switch self {
case .contained, .contains, .noIntersection:
return []

case .singlePoint(let point):
return [point]

case .pairs(let pairs):
return pairs.flatMap({ [$0.enter, $0.exit] })
}
}

/// Convenience for `.pairs([.init(enter: p1, exit: p2)])`.
@inlinable
public static func twoPoints(
Expand All @@ -37,7 +52,7 @@ public enum ClosedShape2Intersection<Vector: Vector2FloatingPoint> {
/// is mapped by a provided closure before being stored back into the same
/// enum case and returned.
public func mappingPointNormals(
_ mapper: (PointNormal<Vector>, PointNormalKind) -> PointNormal<Vector>
_ mapper: (PointNormal<Vector>, LineIntersectionPointNormalKind) -> PointNormal<Vector>
) -> Self {

switch self {
Expand Down Expand Up @@ -67,7 +82,7 @@ public enum ClosedShape2Intersection<Vector: Vector2FloatingPoint> {
/// is replaced by a provided closure before being stored back into the same
/// enum case and returned.
public func replacingPointNormals<NewVector: VectorType>(
_ mapper: (PointNormal<Vector>, PointNormalKind) -> PointNormal<NewVector>
_ mapper: (PointNormal<Vector>, LineIntersectionPointNormalKind) -> PointNormal<NewVector>
) -> ClosedShape2Intersection<NewVector> {

switch self {
Expand Down Expand Up @@ -115,7 +130,7 @@ public enum ClosedShape2Intersection<Vector: Vector2FloatingPoint> {
/// Parameter passed along point normals in ``mappingPointNormals(_:)`` and
/// ``replacingPointNormals(_:)`` to specify to the closure which kind of point
/// normal was provided.
public enum PointNormalKind {
public enum LineIntersectionPointNormalKind {
case singlePoint
case twoPointsFirst
case twoPointsSecond
Expand Down
10 changes: 5 additions & 5 deletions Sources/Geometria/2D/DirectionalRay2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ extension DirectionalRay2: Line2Type {
// NOTE: Doing this in separate statements to ease long compilation times in Xcode 12
let start = Vector(x: x1, y: y1)
let end = Vector(x: x2, y: y2)

self.init(start: start, direction: end - start)
}

/// Initializes a new Directional Ray with a 2D vector for its position and
/// another describing the direction of the ray relative to the position.
///
Expand All @@ -43,9 +43,9 @@ extension DirectionalRay2: Line2Type {
}
}

extension DirectionalRay2: Line2FloatingPoint where Vector: Vector2FloatingPoint {
}
extension DirectionalRay2: Line2Multiplicative where Vector: Vector2Multiplicative { }
extension DirectionalRay2: Line2Signed where Vector: Vector2Signed { }
extension DirectionalRay2: Line2FloatingPoint where Vector: Vector2FloatingPoint { }

extension DirectionalRay2: Line2Real where Vector: Vector2Real {
/// Returns the angle of this directional ray, in radians
Expand Down
11 changes: 4 additions & 7 deletions Sources/Geometria/2D/Line2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ extension Line2: Line2Type {
}
}

extension Line2: Line2FloatingPoint where Vector: Vector2FloatingPoint {

}

extension Line2: Line2Real where Vector: Vector2Real {

}
extension Line2: Line2Multiplicative where Vector: Vector2Multiplicative { }
extension Line2: Line2Signed where Vector: Vector2Signed { }
extension Line2: Line2FloatingPoint where Vector: Vector2FloatingPoint { }
extension Line2: Line2Real where Vector: Vector2Real { }
Loading

0 comments on commit 37c6cb5

Please sign in to comment.