sevgi-geometry 0.95.0 → 0.98.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +125 -0
- data/README.md +7 -7
- data/lib/sevgi/geometry/element.rb +222 -117
- data/lib/sevgi/geometry/elements/line.rb +60 -15
- data/lib/sevgi/geometry/elements/parallelogram.rb +110 -29
- data/lib/sevgi/geometry/elements/polygon.rb +41 -1
- data/lib/sevgi/geometry/elements/polyline.rb +36 -1
- data/lib/sevgi/geometry/elements/rect.rb +159 -29
- data/lib/sevgi/geometry/elements/triangle.rb +60 -11
- data/lib/sevgi/geometry/equation/linear.rb +131 -69
- data/lib/sevgi/geometry/equation.rb +36 -13
- data/lib/sevgi/geometry/internal.rb +3 -3
- data/lib/sevgi/geometry/operation/align.rb +1 -0
- data/lib/sevgi/geometry/operation/sweep.rb +20 -4
- data/lib/sevgi/geometry/operation.rb +28 -14
- data/lib/sevgi/geometry/point.rb +115 -13
- data/lib/sevgi/geometry/segment.rb +76 -13
- data/lib/sevgi/geometry/version.rb +1 -1
- data/lib/sevgi/geometry.rb +25 -2
- metadata +6 -6
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Geometry
|
|
5
|
-
#
|
|
5
|
+
# Stateless operations that relate or derive geometry values.
|
|
6
|
+
#
|
|
7
|
+
# `alignment` returns a translation offset, while `align` applies that
|
|
8
|
+
# offset to a copy. Center alignment works on both axes; edge alignments
|
|
9
|
+
# change only the named axis and preserve the other coordinate. `sweep`
|
|
10
|
+
# derives boundary-to-boundary spans from a closed lined element, and
|
|
11
|
+
# `sweep!` additionally requires at least one span.
|
|
6
12
|
module Operation
|
|
7
13
|
extend self
|
|
8
14
|
|
|
@@ -15,24 +21,36 @@ module Sevgi
|
|
|
15
21
|
# @!parse
|
|
16
22
|
# class << self
|
|
17
23
|
# # Returns an element translated to align with another element.
|
|
24
|
+
# # Center alignment changes both axes. Edge alignments change only the named axis.
|
|
18
25
|
# # @param element [Sevgi::Geometry::Element] element to move
|
|
19
26
|
# # @param other [Sevgi::Geometry::Element] reference element
|
|
20
27
|
# # @param alignment [Symbol] one of :center, :left, :right, :top, or :bottom
|
|
21
28
|
# # @return [Sevgi::Geometry::Element] translated element
|
|
22
29
|
# # @raise [Sevgi::Geometry::Operation::OperationInapplicableError] when an argument is not a geometry element
|
|
23
30
|
# # @raise [Sevgi::ArgumentError] when alignment is unknown
|
|
31
|
+
# # @example Center one rectangle inside another
|
|
32
|
+
# # inner = Sevgi::Geometry::Rect[4, 2]
|
|
33
|
+
# # outer = Sevgi::Geometry::Rect[20, 10, position: [5, 5]]
|
|
34
|
+
# # Sevgi::Geometry::Operation.align(inner, outer).position.deconstruct # => [13.0, 9.0]
|
|
24
35
|
# def align(element, other, alignment = :center); end
|
|
25
36
|
#
|
|
26
37
|
# # Returns the offset needed to align one element with another.
|
|
38
|
+
# # Center alignment includes both axes. Edge alignments return zero on the other axis.
|
|
27
39
|
# # @param element [Sevgi::Geometry::Element] element to move
|
|
28
40
|
# # @param other [Sevgi::Geometry::Element] reference element
|
|
29
41
|
# # @param alignment [Symbol] one of :center, :left, :right, :top, or :bottom
|
|
30
42
|
# # @return [Sevgi::Geometry::Point] translation offset
|
|
31
43
|
# # @raise [Sevgi::Geometry::Operation::OperationInapplicableError] when an argument is not a geometry element
|
|
32
44
|
# # @raise [Sevgi::ArgumentError] when alignment is unknown
|
|
45
|
+
# # @example Calculate an offset without moving the element
|
|
46
|
+
# # inner = Sevgi::Geometry::Rect[4, 2]
|
|
47
|
+
# # outer = Sevgi::Geometry::Rect[20, 10, position: [5, 5]]
|
|
48
|
+
# # Sevgi::Geometry::Operation.alignment(inner, outer, :bottom).approx.deconstruct # => [0.0, 13.0]
|
|
33
49
|
# def alignment(element, other, alignment = :center); end
|
|
34
50
|
#
|
|
35
51
|
# # Sweeps parallel lines across a lined element in both directions.
|
|
52
|
+
# # `angle` is the direction of the returned lines; `step` is their signed perpendicular spacing.
|
|
53
|
+
# # Open paths yield no interior spans.
|
|
36
54
|
# # @param element [Sevgi::Geometry::Element::Lined] element to intersect
|
|
37
55
|
# # @param initial [Sevgi::Geometry::Point, Array<Numeric>] point on the initial sweep line
|
|
38
56
|
# # @param angle [Numeric] clockwise sweep line angle in degrees
|
|
@@ -43,11 +61,17 @@ module Sevgi
|
|
|
43
61
|
# # @yieldreturn [void]
|
|
44
62
|
# # @return [Array<Sevgi::Geometry::Line>] generated sweep lines
|
|
45
63
|
# # @raise [Sevgi::Geometry::Operation::OperationInapplicableError] when element is not sweepable
|
|
46
|
-
# # @raise [Sevgi::Geometry::Error] when initial
|
|
64
|
+
# # @raise [Sevgi::Geometry::Error] when initial, angle, step, or limit is invalid
|
|
47
65
|
# # @raise [Sevgi::Geometry::Operation::OperationError] when iteration reaches the limit
|
|
66
|
+
# # @example Generate horizontal spans through a rectangle
|
|
67
|
+
# # rect = Sevgi::Geometry::Rect[10, 6]
|
|
68
|
+
# # lines = Sevgi::Geometry::Operation.sweep(rect, initial: [0, 0], angle: 0, step: 2)
|
|
69
|
+
# # lines.size # => 4
|
|
70
|
+
# # lines.map(&:length).uniq # => [10.0]
|
|
48
71
|
# def sweep(element, initial:, angle:, step:, limit: Sweep::LIMIT); end
|
|
49
72
|
#
|
|
50
73
|
# # Sweeps parallel lines across a lined element and requires at least one result.
|
|
74
|
+
# # It has the same geometry as {sweep}, but raises when the result would be empty.
|
|
51
75
|
# # @param element [Sevgi::Geometry::Element::Lined] element to intersect
|
|
52
76
|
# # @param initial [Sevgi::Geometry::Point, Array<Numeric>] point on the initial sweep line
|
|
53
77
|
# # @param angle [Numeric] clockwise sweep line angle in degrees
|
|
@@ -58,19 +82,9 @@ module Sevgi
|
|
|
58
82
|
# # @yieldreturn [void]
|
|
59
83
|
# # @return [Array<Sevgi::Geometry::Line>] generated sweep lines
|
|
60
84
|
# # @raise [Sevgi::Geometry::Operation::OperationInapplicableError] when element is not sweepable
|
|
61
|
-
# # @raise [Sevgi::Geometry::Error] when initial
|
|
85
|
+
# # @raise [Sevgi::Geometry::Error] when initial, angle, step, or limit is invalid
|
|
62
86
|
# # @raise [Sevgi::Geometry::Operation::OperationError] when no lines are found or iteration reaches the limit
|
|
63
87
|
# def sweep!(element, initial:, angle:, step:, limit: Sweep::LIMIT); end
|
|
64
|
-
#
|
|
65
|
-
# # Sweeps parallel lines in one signed direction from an equation.
|
|
66
|
-
# # @param element [Sevgi::Geometry::Element::Lined] element to intersect
|
|
67
|
-
# # @param equation [Sevgi::Geometry::Equation] initial sweep equation
|
|
68
|
-
# # @param step [Numeric] signed distance between sweep lines
|
|
69
|
-
# # @param limit [Integer] maximum iterations
|
|
70
|
-
# # @return [Array<Sevgi::Geometry::Line>] generated sweep lines
|
|
71
|
-
# # @raise [Sevgi::Geometry::Operation::OperationInapplicableError] when element is not sweepable
|
|
72
|
-
# # @raise [Sevgi::Geometry::Operation::OperationError] when iteration reaches the limit
|
|
73
|
-
# def unisweep(element, equation, step, limit: Sweep::LIMIT); end
|
|
74
88
|
# end
|
|
75
89
|
# Registers one or more public operation methods.
|
|
76
90
|
# @api private
|
|
@@ -87,7 +101,7 @@ module Sevgi
|
|
|
87
101
|
define_singleton_method(operation) do |element, *args, **kwargs, &block|
|
|
88
102
|
OperationInapplicableError.("Not a Geometric Element: #{element}") unless element.is_a?(Element)
|
|
89
103
|
unless handler.applicable?(element)
|
|
90
|
-
OperationInapplicableError.("
|
|
104
|
+
OperationInapplicableError.("Operation not applicable to #{element}: #{handler}")
|
|
91
105
|
end
|
|
92
106
|
|
|
93
107
|
handler.public_send(operation, element, *args, **kwargs, &block)
|
data/lib/sevgi/geometry/point.rb
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Geometry
|
|
5
|
-
#
|
|
5
|
+
# Implementation owner and registry for public affine transformations on Point and lined elements.
|
|
6
|
+
# @api private
|
|
6
7
|
module Affinity
|
|
7
8
|
# Reflects a point across the selected axes.
|
|
8
9
|
#
|
|
@@ -11,45 +12,141 @@ module Sevgi
|
|
|
11
12
|
# @param x [Boolean] reflect across the x-axis
|
|
12
13
|
# @param y [Boolean] reflect across the y-axis
|
|
13
14
|
# @return [Sevgi::Geometry::Point]
|
|
14
|
-
|
|
15
|
+
# @raise [Sevgi::Geometry::Error] when a flag is not Boolean
|
|
16
|
+
def reflect(x: true, y: true)
|
|
17
|
+
Error.("Reflection x flag must be Boolean") unless x.equal?(true) || x.equal?(false)
|
|
18
|
+
Error.("Reflection y flag must be Boolean") unless y.equal?(true) || y.equal?(false)
|
|
19
|
+
|
|
20
|
+
with(x: (y ? -1 : 1) * self.x(), y: (x ? -1 : 1) * self.y())
|
|
21
|
+
end
|
|
15
22
|
|
|
16
23
|
# Rotates a point around the origin using screen-space degrees.
|
|
17
24
|
# @param a [Numeric] clockwise angle in degrees
|
|
18
25
|
# @return [Sevgi::Geometry::Point]
|
|
19
|
-
|
|
26
|
+
# @raise [Sevgi::Geometry::Error] when angle is not a finite real number
|
|
27
|
+
def rotate(a)
|
|
28
|
+
a = Real[:angle, a]
|
|
29
|
+
with(x: (x * F.cos(a)) - (y * F.sin(a)), y: (x * F.sin(a)) + (y * F.cos(a)))
|
|
30
|
+
end
|
|
20
31
|
|
|
21
32
|
# Scales a point from the origin.
|
|
22
33
|
# @param sx [Numeric] x scale factor
|
|
23
34
|
# @param sy [Numeric, Sevgi::Undefined] y scale factor, defaulting to sx
|
|
24
35
|
# @return [Sevgi::Geometry::Point]
|
|
25
|
-
|
|
36
|
+
# @raise [Sevgi::Geometry::Error] when a scale is not a finite real number
|
|
37
|
+
def scale(sx, sy = Undefined)
|
|
38
|
+
sx = Real[:sx, sx]
|
|
39
|
+
sy = Real[:sy, Undefined.default(sy, sx)]
|
|
40
|
+
with(x: sx * x, y: sy * y)
|
|
41
|
+
end
|
|
26
42
|
|
|
27
43
|
# Skews a point from the origin.
|
|
28
44
|
# @param ax [Numeric] x-axis skew angle in degrees
|
|
29
45
|
# @param ay [Numeric, Sevgi::Undefined] y-axis skew angle in degrees, defaulting to ax
|
|
30
46
|
# @return [Sevgi::Geometry::Point]
|
|
31
|
-
|
|
47
|
+
# @raise [Sevgi::Geometry::Error] when an angle is not a finite real number
|
|
48
|
+
def skew(ax, ay = Undefined)
|
|
49
|
+
ax = Real[:ax, ax]
|
|
50
|
+
ay = Real[:ay, Undefined.default(ay, ax)]
|
|
51
|
+
with(x: x + (y * F.tan(ax)), y: y + (x * F.tan(ay)))
|
|
52
|
+
end
|
|
32
53
|
|
|
33
54
|
# Skews a point along x.
|
|
34
55
|
# @param a [Numeric] skew angle in degrees
|
|
35
56
|
# @return [Sevgi::Geometry::Point]
|
|
36
|
-
|
|
57
|
+
# @raise [Sevgi::Geometry::Error] when angle is not a finite real number
|
|
58
|
+
def skew_x(a) = with(x: x + (y * F.tan(Real[:angle, a])))
|
|
37
59
|
|
|
38
60
|
# Skews a point along y.
|
|
39
61
|
# @param a [Numeric] skew angle in degrees
|
|
40
62
|
# @return [Sevgi::Geometry::Point]
|
|
41
|
-
|
|
63
|
+
# @raise [Sevgi::Geometry::Error] when angle is not a finite real number
|
|
64
|
+
def skew_y(a) = with(y: y + (x * F.tan(Real[:angle, a])))
|
|
42
65
|
|
|
43
66
|
# Translates a point.
|
|
44
67
|
# @param dx [Numeric] x offset
|
|
45
68
|
# @param dy [Numeric, Sevgi::Undefined] y offset, defaulting to dx
|
|
46
69
|
# @return [Sevgi::Geometry::Point]
|
|
47
|
-
|
|
70
|
+
# @raise [Sevgi::Geometry::Error] when an offset is not a finite real number
|
|
71
|
+
def translate(dx, dy = Undefined)
|
|
72
|
+
dx = Real[:dx, dx]
|
|
73
|
+
dy = Real[:dy, Undefined.default(dy, dx)]
|
|
74
|
+
with(x: x + dx, y: y + dy)
|
|
75
|
+
end
|
|
48
76
|
end
|
|
49
77
|
|
|
50
78
|
# Immutable point in SVG/screen coordinates.
|
|
51
79
|
#
|
|
52
|
-
# Use `Point[x, y]` to create a point from two coordinates.
|
|
80
|
+
# Use `Point[x, y]` to create a point from two coordinates. Public geometry
|
|
81
|
+
# methods that expect a point also accept `[x, y]`; explicit Point values are
|
|
82
|
+
# most useful when a result will be transformed, compared, or reused.
|
|
83
|
+
# @example Measure and rotate a point in screen coordinates
|
|
84
|
+
# point = Sevgi::Geometry::Point[3, 4]
|
|
85
|
+
# Sevgi::Geometry::Point.length(Sevgi::Geometry::Origin, point) # => 5.0
|
|
86
|
+
# point.rotate(90).approx.deconstruct # => [-4.0, 3.0]
|
|
87
|
+
# @example Compare positions in screen coordinates
|
|
88
|
+
# upper = Sevgi::Geometry::Point[4, 2]
|
|
89
|
+
# lower = Sevgi::Geometry::Point[4, 8]
|
|
90
|
+
# upper.above?(lower) # => true
|
|
91
|
+
# upper.left?([6, 2]) # => true
|
|
92
|
+
# @see Sevgi::Geometry::Segment
|
|
93
|
+
# @!parse
|
|
94
|
+
# class Point
|
|
95
|
+
# # Creates a point from two coordinates.
|
|
96
|
+
# # @param x [Numeric] x coordinate
|
|
97
|
+
# # @param y [Numeric] y coordinate
|
|
98
|
+
# # @return [Sevgi::Geometry::Point]
|
|
99
|
+
# # @raise [Sevgi::Geometry::Error] when a coordinate is not a finite Numeric
|
|
100
|
+
# # @example Create a point with mathematical notation
|
|
101
|
+
# # Sevgi::Geometry::Point[3, 5]
|
|
102
|
+
# def self.[](x, y); end
|
|
103
|
+
#
|
|
104
|
+
# # Returns a point reflected across the selected axes.
|
|
105
|
+
# # @param x [Boolean] reflect across the x-axis
|
|
106
|
+
# # @param y [Boolean] reflect across the y-axis
|
|
107
|
+
# # @return [Sevgi::Geometry::Point]
|
|
108
|
+
# # @raise [Sevgi::Geometry::Error] when a flag is not Boolean
|
|
109
|
+
# def reflect(x: true, y: true); end
|
|
110
|
+
#
|
|
111
|
+
# # Returns a point rotated around the origin.
|
|
112
|
+
# # @param a [Numeric] clockwise angle in degrees
|
|
113
|
+
# # @return [Sevgi::Geometry::Point]
|
|
114
|
+
# # @raise [Sevgi::Geometry::Error] when angle is not a finite real number
|
|
115
|
+
# def rotate(a); end
|
|
116
|
+
#
|
|
117
|
+
# # Returns a point scaled from the origin.
|
|
118
|
+
# # @param sx [Numeric] x scale factor
|
|
119
|
+
# # @param sy [Numeric, Sevgi::Undefined] y scale factor, defaulting to sx
|
|
120
|
+
# # @return [Sevgi::Geometry::Point]
|
|
121
|
+
# # @raise [Sevgi::Geometry::Error] when a scale is not a finite real number
|
|
122
|
+
# def scale(sx, sy = Undefined); end
|
|
123
|
+
#
|
|
124
|
+
# # Returns a point skewed from the origin.
|
|
125
|
+
# # @param ax [Numeric] x-axis skew angle in degrees
|
|
126
|
+
# # @param ay [Numeric, Sevgi::Undefined] y-axis skew angle in degrees, defaulting to ax
|
|
127
|
+
# # @return [Sevgi::Geometry::Point]
|
|
128
|
+
# # @raise [Sevgi::Geometry::Error] when an angle is not a finite real number
|
|
129
|
+
# def skew(ax, ay = Undefined); end
|
|
130
|
+
#
|
|
131
|
+
# # Returns a point skewed along x.
|
|
132
|
+
# # @param a [Numeric] skew angle in degrees
|
|
133
|
+
# # @return [Sevgi::Geometry::Point]
|
|
134
|
+
# # @raise [Sevgi::Geometry::Error] when angle is not a finite real number
|
|
135
|
+
# def skew_x(a); end
|
|
136
|
+
#
|
|
137
|
+
# # Returns a point skewed along y.
|
|
138
|
+
# # @param a [Numeric] skew angle in degrees
|
|
139
|
+
# # @return [Sevgi::Geometry::Point]
|
|
140
|
+
# # @raise [Sevgi::Geometry::Error] when angle is not a finite real number
|
|
141
|
+
# def skew_y(a); end
|
|
142
|
+
#
|
|
143
|
+
# # Returns a translated point.
|
|
144
|
+
# # @param dx [Numeric] x offset
|
|
145
|
+
# # @param dy [Numeric, Sevgi::Undefined] y offset, defaulting to dx
|
|
146
|
+
# # @return [Sevgi::Geometry::Point]
|
|
147
|
+
# # @raise [Sevgi::Geometry::Error] when an offset is not a finite real number
|
|
148
|
+
# def translate(dx, dy = Undefined); end
|
|
149
|
+
# end
|
|
53
150
|
Point = Data.define(:x, :y) do
|
|
54
151
|
include Comparable
|
|
55
152
|
include Affinity
|
|
@@ -104,10 +201,13 @@ module Sevgi
|
|
|
104
201
|
def initialize(x:, y:) = super(x: Real[:x, x], y: Real[:y, y])
|
|
105
202
|
|
|
106
203
|
# Compares points by x, then y.
|
|
107
|
-
# @param other [
|
|
108
|
-
# @return [Integer, nil]
|
|
109
|
-
|
|
110
|
-
|
|
204
|
+
# @param other [Object] point or two-item coordinate array to compare
|
|
205
|
+
# @return [Integer, nil] comparison result, or nil when other is not a valid point value
|
|
206
|
+
def <=>(other)
|
|
207
|
+
deconstruct <=> Tuple[Point, other].deconstruct
|
|
208
|
+
rescue Error
|
|
209
|
+
nil
|
|
210
|
+
end
|
|
111
211
|
|
|
112
212
|
# Reports whether this point is at or above another point in screen coordinates.
|
|
113
213
|
# @param other [Sevgi::Geometry::Point, Array<Numeric>] point to compare
|
|
@@ -165,6 +265,8 @@ module Sevgi
|
|
|
165
265
|
alias_method :==, :eql?
|
|
166
266
|
end
|
|
167
267
|
|
|
268
|
+
private_constant :Affinity
|
|
269
|
+
|
|
168
270
|
# Origin point in SVG/screen coordinates.
|
|
169
271
|
Origin = Point.origin
|
|
170
272
|
end
|
|
@@ -2,10 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Geometry
|
|
5
|
-
# Immutable polar
|
|
5
|
+
# Immutable polar displacement in SVG/screen coordinates.
|
|
6
6
|
#
|
|
7
|
-
# `length` is a distance and `angle` is a
|
|
8
|
-
# Use
|
|
7
|
+
# A Segment has no position: `length` is a distance and `angle` is a
|
|
8
|
+
# clockwise direction. Use {#ending} to apply it to a starting point or
|
|
9
|
+
# {#line} when a placed, finite line is required. `Segment[length, angle]`
|
|
10
|
+
# starts from polar components; `Segment.(starting, ending)` derives them
|
|
11
|
+
# from two points.
|
|
12
|
+
# @example Derive polar components from two points
|
|
13
|
+
# segment = Sevgi::Geometry::Segment.([1, 2], [4, 6])
|
|
14
|
+
# segment.length # => 5.0
|
|
15
|
+
# segment.ending([1, 2]).deconstruct # => [4.0, 6.0]
|
|
16
|
+
# @example Use a cardinal direction
|
|
17
|
+
# Sevgi::Geometry::Segment.upward(3).ending([5, 5]).deconstruct # => [5.0, 2.0]
|
|
18
|
+
# @see Sevgi::Geometry::Line
|
|
19
|
+
# @!parse
|
|
20
|
+
# class Segment
|
|
21
|
+
# # Creates a segment from polar components.
|
|
22
|
+
# # @param length [Numeric] non-negative segment length
|
|
23
|
+
# # @param angle [Numeric] clockwise angle in degrees
|
|
24
|
+
# # @return [Sevgi::Geometry::Segment]
|
|
25
|
+
# # @raise [Sevgi::Geometry::Error] when a component is not finite or length is negative
|
|
26
|
+
# # @example Create a segment with mathematical notation
|
|
27
|
+
# # Sevgi::Geometry::Segment[5, 30]
|
|
28
|
+
# def self.[](length, angle); end
|
|
29
|
+
# end
|
|
9
30
|
Segment = Data.define(:length, :angle) do
|
|
10
31
|
include Comparable
|
|
11
32
|
|
|
@@ -56,9 +77,15 @@ module Sevgi
|
|
|
56
77
|
def self.upward(length) = self[length, -90.0]
|
|
57
78
|
|
|
58
79
|
class << self
|
|
59
|
-
# @
|
|
80
|
+
# @overload horizontal(length)
|
|
81
|
+
# Returns a rightward segment.
|
|
82
|
+
# @param length [Numeric] segment length
|
|
83
|
+
# @return [Sevgi::Geometry::Segment]
|
|
60
84
|
alias_method :horizontal, :rightward
|
|
61
|
-
# @
|
|
85
|
+
# @overload vertical(length)
|
|
86
|
+
# Returns a downward segment.
|
|
87
|
+
# @param length [Numeric] segment length
|
|
88
|
+
# @return [Sevgi::Geometry::Segment]
|
|
62
89
|
alias_method :vertical, :downward
|
|
63
90
|
end
|
|
64
91
|
|
|
@@ -66,14 +93,22 @@ module Sevgi
|
|
|
66
93
|
# @param length [Numeric] segment length
|
|
67
94
|
# @param angle [Numeric] clockwise angle in degrees
|
|
68
95
|
# @return [void]
|
|
69
|
-
# @raise [Sevgi::Geometry::Error] when a component is not
|
|
70
|
-
def initialize(length:, angle:)
|
|
96
|
+
# @raise [Sevgi::Geometry::Error] when a component is not finite or length is negative
|
|
97
|
+
def initialize(length:, angle:)
|
|
98
|
+
length = Real[:length, length]
|
|
99
|
+
Error.("Segment length cannot be negative") if length.negative?
|
|
100
|
+
|
|
101
|
+
super(length:, angle: Real[:angle, angle])
|
|
102
|
+
end
|
|
71
103
|
|
|
72
104
|
# Compares segments by length.
|
|
73
|
-
# @param other [
|
|
74
|
-
# @return [Integer, nil]
|
|
75
|
-
|
|
76
|
-
|
|
105
|
+
# @param other [Object] segment or two-item length/angle array to compare
|
|
106
|
+
# @return [Integer, nil] comparison result, or nil when other is not a valid segment value
|
|
107
|
+
def <=>(other)
|
|
108
|
+
length <=> Tuple[Segment, other].length
|
|
109
|
+
rescue Error
|
|
110
|
+
nil
|
|
111
|
+
end
|
|
77
112
|
|
|
78
113
|
# Returns a segment rounded to precision.
|
|
79
114
|
# @param precision [Integer, nil] decimal precision, or nil for the current function default
|
|
@@ -121,7 +156,35 @@ module Sevgi
|
|
|
121
156
|
def y = length * F.sin(angle)
|
|
122
157
|
end
|
|
123
158
|
|
|
124
|
-
#
|
|
125
|
-
|
|
159
|
+
# Immutable length/angle constraint used when a full segment is not implied.
|
|
160
|
+
#
|
|
161
|
+
# @!attribute [r] length
|
|
162
|
+
# @return [Float] non-negative target measure
|
|
163
|
+
# @!attribute [r] angle
|
|
164
|
+
# @return [Float] direction used to derive a segment
|
|
165
|
+
# @!parse
|
|
166
|
+
# class LengthAngle
|
|
167
|
+
# # Creates a length-and-angle constraint.
|
|
168
|
+
# # @param length [Numeric] finite non-negative target width or height
|
|
169
|
+
# # @param angle [Numeric] finite direction in degrees
|
|
170
|
+
# # @return [Sevgi::Geometry::LengthAngle]
|
|
171
|
+
# # @raise [Sevgi::Geometry::Error] when a component is not finite or length is negative
|
|
172
|
+
# # @example Create a constraint with mathematical notation
|
|
173
|
+
# # Sevgi::Geometry::LengthAngle[3, 90]
|
|
174
|
+
# def self.[](length, angle); end
|
|
175
|
+
# end
|
|
176
|
+
LengthAngle = Data.define(:length, :angle) do
|
|
177
|
+
# Creates a target-measure constraint.
|
|
178
|
+
# @param length [Numeric] finite non-negative target width or height
|
|
179
|
+
# @param angle [Numeric] finite direction in degrees
|
|
180
|
+
# @return [void]
|
|
181
|
+
# @raise [Sevgi::Geometry::Error] when a component is not finite or length is negative
|
|
182
|
+
def initialize(length:, angle:)
|
|
183
|
+
length = Real[:length, length]
|
|
184
|
+
Error.("LengthAngle length cannot be negative") if length.negative?
|
|
185
|
+
|
|
186
|
+
super(length:, angle: Real[:angle, angle])
|
|
187
|
+
end
|
|
188
|
+
end
|
|
126
189
|
end
|
|
127
190
|
end
|
data/lib/sevgi/geometry.rb
CHANGED
|
@@ -14,10 +14,33 @@ require_relative "geometry/operation"
|
|
|
14
14
|
require_relative "geometry/version"
|
|
15
15
|
|
|
16
16
|
module Sevgi
|
|
17
|
-
#
|
|
17
|
+
# Immutable screen-space geometry values used by Sevgi layout and drawing helpers.
|
|
18
18
|
#
|
|
19
19
|
# Coordinates follow SVG screen conventions: +x points right, +y points down,
|
|
20
|
-
# and positive angles turn clockwise.
|
|
20
|
+
# and positive angles turn clockwise. Constructors accept Point and Segment
|
|
21
|
+
# objects or their two-number Array forms. Transformations return new values;
|
|
22
|
+
# they do not mutate their receiver.
|
|
23
|
+
#
|
|
24
|
+
# Shape constructors have two complementary notations: `Shape[...]` accepts
|
|
25
|
+
# dimensions or segments, while `Shape.(...)` accepts points. Named factories
|
|
26
|
+
# such as `Rect.from_size` and `Rect.from_corners` expose the same distinction
|
|
27
|
+
# when a call site benefits from spelling it out.
|
|
28
|
+
#
|
|
29
|
+
# Trigonometric construction can retain ordinary floating-point noise. Use
|
|
30
|
+
# `approx` for presentation values and `eq?` for precision-aware comparison;
|
|
31
|
+
# strict `==` intentionally compares the exact immutable value.
|
|
32
|
+
#
|
|
33
|
+
# @example Measure and move a line
|
|
34
|
+
# line = Sevgi::Geometry::Line.([0, 0], [3, 4])
|
|
35
|
+
# line.length #=> 5.0
|
|
36
|
+
# line.translate(2, 1).starting #=> Point[2.0, 1.0]
|
|
37
|
+
# @example Follow SVG screen directions
|
|
38
|
+
# origin = Sevgi::Geometry::Point.origin
|
|
39
|
+
# Sevgi::Geometry::Point.angle(origin, [0, 10]) #=> 90.0
|
|
40
|
+
# origin.translate(0, 10).below?(origin) #=> true
|
|
41
|
+
# @see Sevgi::Geometry::Element::Lined
|
|
42
|
+
# @see Sevgi::Geometry::Operation
|
|
43
|
+
# @see https://sevgi.roktas.dev/geometry/ Geometry guide
|
|
21
44
|
module Geometry
|
|
22
45
|
end
|
|
23
46
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sevgi-geometry
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.98.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Recai Oktaş
|
|
@@ -15,15 +15,15 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.98.2
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
26
|
-
description:
|
|
25
|
+
version: 0.98.2
|
|
26
|
+
description: Models the points, lines, shapes, and transforms used by the DSL.
|
|
27
27
|
email: roktas@gmail.com
|
|
28
28
|
executables: []
|
|
29
29
|
extensions: []
|
|
@@ -73,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
74
|
version: '0'
|
|
75
75
|
requirements: []
|
|
76
|
-
rubygems_version: 4.0.
|
|
76
|
+
rubygems_version: 4.0.16
|
|
77
77
|
specification_version: 4
|
|
78
|
-
summary:
|
|
78
|
+
summary: Geometry values and operations for Sevgi drawings.
|
|
79
79
|
test_files: []
|