Class: Sevgi::Geometry::Arc

Inherits:
Element::Arced show all
Defined in:
lib/sevgi/geometry/elements/arc.rb

Overview

Finite, directed portion of an Ellipse, with less than one full turn. Positive extent is clockwise in screen coordinates. Zero extent has a single-point trace and draws nothing.

Examples:

Inspect and reverse an upper semicircle

arc = Sevgi::Geometry::Arc[10, starting_angle: 180, extent: 180]
arc.starting.deconstruct # => [-10.0, 0.0]
arc.reverse.starting == arc.ending

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Element::Arced

#eq?, #eql?, #hash, #outside?

Methods inherited from Element

#at, #closed?, #ignorable?, #intersection, lined

Constructor Details

#initialize(ellipse, starting_angle:, extent:) ⇒ void

Creates a finite arc. Use the bracket constructor or Ellipse#arc.

Parameters:

  • ellipse (Sevgi::Geometry::Ellipse) —

    parent ellipse

  • starting_angle (Numeric) —

    local starting parameter angle in degrees

  • extent (Numeric) —

    signed angular extent in degrees

Raises:



45
46
47
48
49
50
51
# File 'lib/sevgi/geometry/elements/arc.rb', line 45

def initialize(ellipse, starting_angle:, extent:)
  super()
  @ellipse = ellipse
  @starting_angle = Real[:starting_angle, starting_angle]
  @extent = Real[:extent, extent]
  Error.("Arc extent must be between -360 and 360 degrees") unless @extent.abs < 360
end

Instance Attribute Details

#ellipse ⇒ Sevgi::Geometry::Ellipse (readonly)

Returns immutable parent ellipse.

Returns:



33
34
35
# File 'lib/sevgi/geometry/elements/arc.rb', line 33

def ellipse
  @ellipse
end

#extent ⇒ Float (readonly)

Returns signed extent in degrees.

Returns:

  • (Float) —

    signed extent in degrees



35
36
37
# File 'lib/sevgi/geometry/elements/arc.rb', line 35

def extent
  @extent
end

#starting_angle ⇒ Float (readonly)

Returns local starting parameter angle in degrees.

Returns:

  • (Float) —

    local starting parameter angle in degrees



37
38
39
# File 'lib/sevgi/geometry/elements/arc.rb', line 37

def starting_angle
  @starting_angle
end

Class Method Details

.[](rx, ry = rx, extent:, position: Origin, rotation: 0, starting_angle: 0) ⇒ Sevgi::Geometry::Arc

Builds a circular or elliptical arc from dimensions and angles.

Parameters:

  • rx (Numeric) —

    positive local x radius

  • ry (Numeric) (defaults to: rx) —

    positive local y radius, defaulting to rx

  • position (Sevgi::Geometry::Point, Array<Numeric>) (defaults to: Origin) —

    parent ellipse center

  • rotation (Numeric) (defaults to: 0) —

    clockwise ellipse rotation in degrees

  • starting_angle (Numeric) (defaults to: 0) —

    local starting parameter angle in degrees

  • extent (Numeric) —

    signed extent strictly between -360 and 360 degrees

Returns:

Raises:



23
24
25
# File 'lib/sevgi/geometry/elements/arc.rb', line 23

def self.[](rx, ry = rx, extent:, position: Origin, rotation: 0, starting_angle: 0)
  Ellipse[rx, ry, position:, rotation:].arc(starting_angle:, extent:)
end

Instance Method Details

#approx(precision = nil) ⇒ Sevgi::Geometry::Arc

Rebuilds an arc from rounded canonical fields.

Parameters:

  • precision (Integer, nil) (defaults to: nil) —

    decimal precision, or nil for the current function default

Returns:

Raises:

  • (Sevgi::Geometry::Error) —

    when rounding makes a radius zero or extent a full turn

  • (Sevgi::ArgumentError) —

    when precision is invalid



58
59
60
61
62
# File 'lib/sevgi/geometry/elements/arc.rb', line 58

def approx(precision = nil)
  ellipse
    .approx(precision)
    .arc(starting_angle: F.approx(starting_angle, precision), extent: F.approx(extent, precision))
end

#box ⇒ Sevgi::Geometry::Rect

Returns bounds of the finite trace without display rounding.



66
67
68
69
70
# File 'lib/sevgi/geometry/elements/arc.rb', line 66

def box
  points = [starting, ending, *ellipse.send(:extrema).select { contains_angle?(it) }.map { ellipse.point(it) }]
  xs, ys = points.map(&:x), points.map(&:y)
  Rect.from_corners([xs.min, ys.min], [xs.max, ys.max])
end

#circular? ⇒ Boolean

Reports whether the parent radii are exactly equal.

Returns:

  • (Boolean)


74
# File 'lib/sevgi/geometry/elements/arc.rb', line 74

def circular? = ellipse.circular?

#clockwise? ⇒ Boolean

Reports whether the traversal is clockwise.

Returns:

  • (Boolean)


78
# File 'lib/sevgi/geometry/elements/arc.rb', line 78

def clockwise? = extent.positive?

#counterclockwise? ⇒ Boolean

Reports whether the traversal is counterclockwise.

Returns:

  • (Boolean)


82
# File 'lib/sevgi/geometry/elements/arc.rb', line 82

def counterclockwise? = extent.negative?

#draw(node, **attributes) ⇒ Object

Draws the finite trace as an SVG path using unrounded geometry.

Parameters:

  • node (Object) —

    graphics node receiving the path

  • attributes (Hash) —

    SVG attributes

Returns:

  • (Object) —

    graphics command result



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/sevgi/geometry/elements/arc.rb', line 90

def draw(node, **attributes)
  return node.path(d: "", **attributes) if empty?
  return node.path(d: split_path, **attributes) if extent.abs > 180 && starting == ending

  node.ArcTo(
    x1: starting.x,
    y1: starting.y,
    x2: ending.x,
    y2: ending.y,
    rx:,
    ry:,
    rotation:,
    large: extent.abs > 180,
    sweep: clockwise?,
    **attributes
  )
end

#empty? ⇒ Boolean

Reports whether angular extent is exactly zero, independently of numeric precision.

Returns:

  • (Boolean)


112
# File 'lib/sevgi/geometry/elements/arc.rb', line 112

def empty? = extent.zero?

#ending ⇒ Sevgi::Geometry::Point

Returns the ending point.



116
# File 'lib/sevgi/geometry/elements/arc.rb', line 116

def ending = ellipse.point(ending_angle)

#ending_angle ⇒ Float

Returns the unnormalized ending parameter angle.

Returns:

  • (Float)


120
# File 'lib/sevgi/geometry/elements/arc.rb', line 120

def ending_angle = starting_angle + extent

#equation ⇒ Sevgi::Geometry::Equation::Quadratic

Returns the complete parent ellipse carrier.



124
# File 'lib/sevgi/geometry/elements/arc.rb', line 124

def equation = ellipse.equation

#equations ⇒ Array<Sevgi::Geometry::Equation::Quadratic>

Returns the immutable parent carrier collection.



128
# File 'lib/sevgi/geometry/elements/arc.rb', line 128

def equations = ellipse.equations

#inside?(point) ⇒ Boolean

Reports whether a point belongs to the open boundary.

Parameters:

Returns:

  • (Boolean)

Raises:



134
# File 'lib/sevgi/geometry/elements/arc.rb', line 134

def inside?(point) = on?(point)

#length ⇒ Float

Returns the non-negative finite-trace length, independently of thread precision.

Returns:

  • (Float)

Raises:



139
# File 'lib/sevgi/geometry/elements/arc.rb', line 139

def length = @length ||= empty? ? 0.0 : ellipse.send(:arc_length, starting_angle, extent)

#on?(point) ⇒ Boolean

Reports membership using endpoint coordinate tolerance and the finite angular span.

Parameters:

Returns:

  • (Boolean)

Raises:



145
146
147
148
149
150
# File 'lib/sevgi/geometry/elements/arc.rb', line 145

def on?(point)
  point = Tuple[Point, point]
  return true if point.eq?(starting) || point.eq?(ending)

  !empty? && ellipse.on?(point) && contains_angle?(ellipse.send(:parameter, point))
end

#position ⇒ Sevgi::Geometry::Point Also known as: center

Returns the parent ellipse center, not the starting endpoint.



154
# File 'lib/sevgi/geometry/elements/arc.rb', line 154

def position = ellipse.position

#reflect(x: true, y: true) ⇒ Sevgi::Geometry::Arc

Returns an arc reflected across the selected axes.

Parameters:

  • x (Boolean) (defaults to: true) —

    reflect across the x-axis

  • y (Boolean) (defaults to: true) —

    reflect across the y-axis

Returns:

Raises:



161
# File 'lib/sevgi/geometry/elements/arc.rb', line 161

def reflect(x: true, y: true) = affine(:reflect, x:, y:)

#reverse ⇒ Sevgi::Geometry::Arc

Reverses traversal without changing the finite trace.



165
# File 'lib/sevgi/geometry/elements/arc.rb', line 165

def reverse = ellipse.arc(starting_angle: ending_angle, extent: -extent)

#rotate(angle) ⇒ Sevgi::Geometry::Arc

Rotates the arc around the origin without changing local angles.

Parameters:

  • angle (Numeric) —

    clockwise angle in degrees

Returns:

Raises:



171
# File 'lib/sevgi/geometry/elements/arc.rb', line 171

def rotate(angle) = ellipse.rotate(angle).arc(starting_angle:, extent:)

#rotation ⇒ Float

Returns the parent ellipse rotation in degrees.

Returns:

  • (Float)


175
# File 'lib/sevgi/geometry/elements/arc.rb', line 175

def rotation = ellipse.rotation

#rx ⇒ Float

Returns the parent local x radius.

Returns:

  • (Float)


179
# File 'lib/sevgi/geometry/elements/arc.rb', line 179

def rx = ellipse.rx

#ry ⇒ Float

Returns the parent local y radius.

Returns:

  • (Float)


183
# File 'lib/sevgi/geometry/elements/arc.rb', line 183

def ry = ellipse.ry

#scale(sx, sy = Undefined) ⇒ Sevgi::Geometry::Arc

Scales the arc from the origin, preserving its finite trace and traversal.

Parameters:

  • sx (Numeric) —

    x scale factor

  • sy (Numeric, Sevgi::Undefined) (defaults to: Undefined) —

    y factor, defaulting to sx

Returns:

Raises:



190
191
192
193
194
195
# File 'lib/sevgi/geometry/elements/arc.rb', line 190

def scale(sx, sy = Undefined)
  sx, sy = Real[:sx, sx], Real[:sy, Undefined.default(sy, sx)]
  return ellipse.scale(sx, sy).arc(starting_angle:, extent:) if sx == sy

  affine(:scale, sx, sy)
end

#skew(ax, ay = Undefined) ⇒ Sevgi::Geometry::Arc

Skews the arc from the origin.

Parameters:

  • ax (Numeric) —

    x-axis skew angle in degrees

  • ay (Numeric, Sevgi::Undefined) (defaults to: Undefined) —

    y-axis angle, defaulting to ax

Returns:

Raises:



202
# File 'lib/sevgi/geometry/elements/arc.rb', line 202

def skew(ax, ay = Undefined) = affine(:skew, ax, ay)

#skew_x(angle) ⇒ Sevgi::Geometry::Arc

Skews the arc along x.

Parameters:

  • angle (Numeric) —

    skew angle in degrees

Returns:

Raises:



208
# File 'lib/sevgi/geometry/elements/arc.rb', line 208

def skew_x(angle) = affine(:skew_x, angle)

#skew_y(angle) ⇒ Sevgi::Geometry::Arc

Skews the arc along y.

Parameters:

  • angle (Numeric) —

    skew angle in degrees

Returns:

Raises:



214
# File 'lib/sevgi/geometry/elements/arc.rb', line 214

def skew_y(angle) = affine(:skew_y, angle)

#starting ⇒ Sevgi::Geometry::Point

Returns the starting point.



218
# File 'lib/sevgi/geometry/elements/arc.rb', line 218

def starting = ellipse.point(starting_angle)

#translate(dx, dy = Undefined) ⇒ Sevgi::Geometry::Arc

Returns a translated arc without changing local angles.

Parameters:

  • dx (Numeric) —

    x offset

  • dy (Numeric, Sevgi::Undefined) (defaults to: Undefined) —

    y offset, defaulting to dx

Returns:

Raises:



225
# File 'lib/sevgi/geometry/elements/arc.rb', line 225

def translate(dx, dy = Undefined) = ellipse.translate(dx, dy).arc(starting_angle:, extent:)