Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
xcuserdata/
/.swiftpm

/.claude/settings.local.json
8 changes: 7 additions & 1 deletion Sources/Cadova/Abstract Layer/2D/Circle/Arc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import Foundation
/// let arcWithDiameter = Arc(range: 0°..<90°, diameter: 10)
/// ```
public struct Arc: Shape2D {
/// The angular range of the arc.
public let range: Range<Angle>

/// The radius of the arc.
public let radius: Double

/// Creates a new `Arc` instance with the specified range of angles and radius.
Expand All @@ -30,7 +33,7 @@ public struct Arc: Shape2D {
}

public var body: any Geometry2D {
@Environment(\.segmentation) var segmentation
@Environment(\.scaledSegmentation) var segmentation
Polygon([.zero] + arcPoints(segmentation: segmentation))
}

Expand All @@ -45,6 +48,9 @@ public struct Arc: Shape2D {
}

extension Arc: Area {
/// The angular span of the arc.
public var angularDistance: Angle { range.length }

/// The area of the circular sector.
public var area: Double { radius * radius * .pi * (angularDistance / 360°) }
}
3 changes: 2 additions & 1 deletion Sources/Cadova/Abstract Layer/2D/Circle/Circle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public struct Circle {
/// The diameter of the circle.
public let diameter: Double

/// The radius of the circle (half of the diameter).
public var radius: Double { diameter / 2 }

/// Creates a new `Circle` instance with the specified diameter.
Expand Down Expand Up @@ -50,7 +51,7 @@ public struct Circle {

extension Circle: Shape2D {
public var body: any Geometry2D {
@Environment(\.segmentation) var segmentation
@Environment(\.scaledSegmentation) var segmentation
NodeBasedGeometry(.shape(.circle(radius: radius, segmentCount: segmentation.segmentCount(circleRadius: radius))))
}
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/Cadova/Abstract Layer/2D/Circle/Ring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import Foundation
/// let ring = Ring(outerDiameter: 10, innerDiameter: 4)
/// ```
public struct Ring: Shape2D {
/// The outer diameter of the ring.
public let outerDiameter: Double

/// The inner diameter of the ring (the hole).
public let innerDiameter: Double

/// Creates a new `Ring` instance with the specified outer and inner diameters.
Expand Down Expand Up @@ -84,6 +87,7 @@ public struct Ring: Shape2D {
}

extension Ring: Area {
/// The area of the ring (outer circle minus inner circle).
public var area: Double {
Circle(diameter: outerDiameter).area - Circle(diameter: innerDiameter).area
}
Expand Down
19 changes: 19 additions & 0 deletions Sources/Cadova/Abstract Layer/2D/Metrics2D.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
import Foundation

/// A type that has a measurable area.
///
/// Conforming types provide an `area` property representing the enclosed surface area.
/// Many 2D shapes in Cadova conform to this protocol, including ``Circle``, ``Rectangle``,
/// ``RegularPolygon``, and others.
///
public protocol Area {
/// The enclosed area of the shape.
var area: Double { get }
}

/// A type that has a measurable perimeter.
///
/// Conforming types provide a `perimeter` property representing the total length of
/// the shape's boundary. Many 2D shapes in Cadova conform to this protocol, including
/// ``Circle`` (where it represents circumference), ``Rectangle``, ``RegularPolygon``, and others.
///
public protocol Perimeter {
/// The total length of the shape's boundary.
var perimeter: Double { get }
}

public extension Area {
/// Calculates the volume of a pyramid with this shape as the base.
///
/// - Parameter height: The height of the pyramid from base to apex.
/// - Returns: The volume of the pyramid.
///
func pyramidVolume(height: Double) -> Double {
return (area * height) / 3.0
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal indirect enum PolygonPoints: Sendable, Hashable, Codable {
func points(in environment: EnvironmentValues) -> [Vector2D] {
switch self {
case .literal (let array): array
case .curve (let curve): curve.curve.points(segmentation: environment.segmentation)
case .curve (let curve): curve.curve.points(segmentation: environment.scaledSegmentation)
case .transformed (let polygonPoints, let transform):
polygonPoints.points(in: environment)
.map { transform.apply(to: $0) }
Expand Down
2 changes: 2 additions & 0 deletions Sources/Cadova/Abstract Layer/2D/RegularPolygon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,12 @@ public extension RegularPolygon {
}

extension RegularPolygon: Area, Perimeter {
/// The area of the polygon.
public var area: Double {
Double(sideCount) / 2.0 * pow(circumradius, 2) * sin(360° / Double(sideCount))
}

/// The perimeter of the polygon.
public var perimeter: Double {
Double(sideCount) * sideLength
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/Cadova/Abstract Layer/2D/Stadium.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@ public struct Stadium: Shape2D {
}

extension Stadium: Area, Perimeter {
/// The area of the stadium.
public var area: Double {
let diameter = min(size.x, size.y)
return Double.pi * (diameter / 2) * (diameter / 2) + (max(size.x, size.y) - diameter) * diameter
}

/// The perimeter of the stadium.
public var perimeter: Double {
let diameter = min(size.x, size.y)
return Double.pi * diameter + 2.0 * (max(size.x, size.y) - diameter)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Cadova/Abstract Layer/2D/Text/GlyphRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ internal class GlyphRenderer {
FT_Outline_Decompose(&mutableOutline, &funcs, mutablePointer)
}
guard composeResult == 0 else { return nil }
return SimplePolygonList(paths.map { SimplePolygon($0.points(segmentation: environment.segmentation)) })
return SimplePolygonList(paths.map { SimplePolygon($0.points(segmentation: environment.scaledSegmentation)) })
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Cadova/Abstract Layer/2D/Text/Text.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public struct Text: Shape2D {
public var body: any Geometry2D {
@Environment var environment
@Environment(\.textAttributes) var textAttributes
@Environment(\.segmentation) var segmentation
@Environment(\.scaledSegmentation) var segmentation
let attributes = textAttributes.applyingDefaults()

CachedNode(name: "text", parameters: content, attributes, segmentation) { environment, context in
Expand Down
7 changes: 5 additions & 2 deletions Sources/Cadova/Abstract Layer/2D/Text/TextAttributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ internal struct TextAttributes: Sendable, Hashable, Codable {
var fontFile: URL?
var horizontalAlignment: HorizontalTextAlignment?
var verticalAlignment: VerticalTextAlignment?
var lineSpacingAdjustment: Double?

init(fontFace: FontFace? = nil, fontSize: Double? = nil, fontFile: URL? = nil, horizontalAlignment: HorizontalTextAlignment? = nil, verticalAlignment: VerticalTextAlignment? = nil) {
init(fontFace: FontFace? = nil, fontSize: Double? = nil, fontFile: URL? = nil, horizontalAlignment: HorizontalTextAlignment? = nil, verticalAlignment: VerticalTextAlignment? = nil, lineSpacingAdjustment: Double? = nil) {
self.fontFace = fontFace
self.fontSize = fontSize
self.fontFile = fontFile
self.horizontalAlignment = horizontalAlignment
self.verticalAlignment = verticalAlignment
self.lineSpacingAdjustment = lineSpacingAdjustment
}

func applyingDefaults() -> Self {
Expand All @@ -30,7 +32,8 @@ internal struct TextAttributes: Sendable, Hashable, Codable {
fontSize: fontSize ?? 12,
fontFile: fontFile,
horizontalAlignment: horizontalAlignment ?? .left,
verticalAlignment: verticalAlignment ?? .lastBaseline
verticalAlignment: verticalAlignment ?? .lastBaseline,
lineSpacingAdjustment: lineSpacingAdjustment ?? 0
)
}
}
3 changes: 2 additions & 1 deletion Sources/Cadova/Abstract Layer/2D/Text/TextRendering.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ extension TextAttributes {
FT_Done_Face(face)
FT_Done_FreeType(library)

let lineHeight = Double(metrics.height) / 64.0 / GlyphRenderer.scaleFactor
let baseLineHeight = Double(metrics.height) / 64.0 / GlyphRenderer.scaleFactor
let lineHeight = baseLineHeight + (lineSpacingAdjustment ?? 0)
let ascender = Double(metrics.ascender) / 64.0 / GlyphRenderer.scaleFactor
let descender = Double(metrics.descender) / 64.0 / GlyphRenderer.scaleFactor
let horizontalAdjustment = horizontalAlignment!.adjustmentFactor
Expand Down
3 changes: 2 additions & 1 deletion Sources/Cadova/Abstract Layer/3D/Box.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Foundation

/// A rectangular cuboid shape
/// A rectangular cuboid shape.
public struct Box: Geometry {
/// The dimensions of the box along each axis.
public let size: Vector3D

/// Initializes a new box with specific dimensions and centering options.
Expand Down
7 changes: 6 additions & 1 deletion Sources/Cadova/Abstract Layer/3D/Cylinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ import Foundation
/// ```

public struct Cylinder: Geometry {
/// The height of the cylinder along the Z axis.
public let height: Double

/// The radius at the bottom of the cylinder (at Z = 0).
public let bottomRadius: Double

/// The radius at the top of the cylinder (at Z = height).
public let topRadius: Double

public func build(in environment: EnvironmentValues, context: EvaluationContext) async throws -> D3.BuildResult {
@Environment(\.segmentation) var segmentation
@Environment(\.scaledSegmentation) var segmentation
let segmentCount = segmentation.segmentCount(circleRadius: max(bottomRadius, topRadius))

if height < .ulpOfOne {
Expand Down
148 changes: 0 additions & 148 deletions Sources/Cadova/Abstract Layer/3D/Import.swift

This file was deleted.

Loading
Loading