sevgi-geometry 0.98.2 → 1.0.0
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 +96 -2
- data/README.md +26 -1
- data/lib/sevgi/geometry/element.rb +115 -61
- data/lib/sevgi/geometry/elements/arc.rb +256 -0
- data/lib/sevgi/geometry/elements/circle.rb +28 -0
- data/lib/sevgi/geometry/elements/ellipse/affine.rb +72 -0
- data/lib/sevgi/geometry/elements/ellipse/length.rb +80 -0
- data/lib/sevgi/geometry/elements/ellipse.rb +269 -0
- data/lib/sevgi/geometry/elements/line.rb +12 -12
- data/lib/sevgi/geometry/elements/parallelogram.rb +5 -7
- data/lib/sevgi/geometry/elements/polyline.rb +9 -0
- data/lib/sevgi/geometry/elements/rect.rb +5 -1
- data/lib/sevgi/geometry/elements/triangle.rb +6 -9
- data/lib/sevgi/geometry/equation/quadratic.rb +98 -16
- data/lib/sevgi/geometry/equation.rb +34 -16
- data/lib/sevgi/geometry/internal.rb +8 -0
- data/lib/sevgi/geometry/operation/align.rb +1 -4
- data/lib/sevgi/geometry/operation/box.rb +50 -0
- data/lib/sevgi/geometry/operation/sweep.rb +13 -15
- data/lib/sevgi/geometry/operation.rb +9 -8
- data/lib/sevgi/geometry/point.rb +12 -2
- data/lib/sevgi/geometry/predicate.rb +176 -0
- data/lib/sevgi/geometry/segment.rb +4 -2
- data/lib/sevgi/geometry/version.rb +1 -1
- data/lib/sevgi/geometry.rb +4 -3
- metadata +11 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5541e9628216ffd94f9d305d00419f6d7d306c6c0250cdece01c04907cf2e504
|
|
4
|
+
data.tar.gz: 0d1f0f9ea6bb347e8b98e9472c66ac87ddc9a2e512160c73848788ec5b36dc7b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e5c264c3685c2b0034ab38c0ff64d36b53de4adec41df40d98609ce18841cca60922c24d1e0b331b931456a40e9ed519ec806ad566a35f47c68ffd6b4c7ead74
|
|
7
|
+
data.tar.gz: f053552d2951ab8147cdfb5cbb974b080fd0780885a316f148386fce0d6d2680da3021853772e41b12e84832c742c3536e9df3f9b710c25b838646457846ee03
|
data/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,101 @@
|
|
|
3
3
|
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## 1.0.0 - 2026-09-16
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Added Hatch and Orb Showcase examples and displayed both arc lengths in the Arc example.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Matched documentation page widths to the homepage Showcase.
|
|
15
|
+
- Aligned Derender user guidance with UTF-8 normalization and safe subtree selection rules.
|
|
16
|
+
- Clarified the packaged skill's script invocation, subtree copying, and Derender selection and encoding rules.
|
|
17
|
+
- Allowed Canvas and Paper values as the first `SVG` argument when using the default document profile.
|
|
18
|
+
- Made element `dup` and `clone` own independent subtrees while retaining IDs and Ruby copy semantics.
|
|
19
|
+
- Made Segment equality compare length and direction; ordering still compares length only.
|
|
20
|
+
- Limited Tile cell, row, and column queries to the requested geometry and accelerated XML character validation.
|
|
21
|
+
- Simplified the first-run guide and aligned library constructor examples.
|
|
22
|
+
- Updated the packaged skill for canvas origins, alignment, Paper lookup, curved geometry, and export constraints.
|
|
23
|
+
- Revised DSL catalog examples and packaged Sevgi skill guidance to use statement-style calls and show inspection results.
|
|
24
|
+
- Refined Showcase example tabs, made Ruby and XML views fill their panels, and hid executable preambles from Ruby views.
|
|
25
|
+
- Documented non-rendering element data and clarified the related DSL catalog examples.
|
|
26
|
+
- Refined Showcase heading spacing, page-title colors, and DSL catalog cards across light and dark themes.
|
|
27
|
+
- Removed an obsolete ruler example and unused site code.
|
|
28
|
+
- Kept the Squared and Copperplate guidesheet examples together at the end of the Showcase.
|
|
29
|
+
- Updated the packaged skill to avoid duplicate semantic shapes and unnecessary paint passes.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Kept horizontal scrollbars visible in Showcase example code panels.
|
|
34
|
+
- Read Derender input in its declared XML encoding and normalized generated source, XML declarations, and markup to UTF-8.
|
|
35
|
+
- Preserved open-path endpoints and derived edges independently of display precision.
|
|
36
|
+
- Kept near-axis and near-parallel line equations and intersections independent of angle and slope rounding.
|
|
37
|
+
- Corrected the Showcase social image dimensions.
|
|
38
|
+
- Added SVG namespaces to standalone drawing examples and regenerated their outputs.
|
|
39
|
+
- Made failed Sevgi Rake tasks stop dependent tasks and captured required-library failures in executor results.
|
|
40
|
+
- Removed executor signal-handler replacement so the host and shell retain SIGINT ownership.
|
|
41
|
+
- Escaped late export CSS as XML text and inserted it only at a supported final root closing tag.
|
|
42
|
+
- Preserved Derender processing instructions and rejected custom entity references and unsupported trailing document nodes before inclusion.
|
|
43
|
+
- Included both box origins in `Align`, unified Tile index validation, and isolated custom numeric attribute snapshots.
|
|
44
|
+
- Prevented avoidable overflow and underflow in point distances.
|
|
45
|
+
- Corrected browser source-panel expectations and added executable documentation, YARD, and packaged-skill checks.
|
|
46
|
+
- Rendered inline code in DSL catalog summaries and kept linked task themes visible below the sticky header.
|
|
47
|
+
- Restored Showcase tab icons with embedded Font Awesome SVGs.
|
|
48
|
+
|
|
49
|
+
## 0.100.0 - 2026-09-09
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- Added `ArcTo` and `ArcBy` for SVG-native elliptical arc paths.
|
|
54
|
+
- Added Geometry `Arc`, `Ellipse`, and `Circle` with bounds, length, predicates, affine transformations, and
|
|
55
|
+
linear/quadratic intersections. Closed ellipses and circles support sweeps and hatching.
|
|
56
|
+
- Added Geometry `Point.midpoint`, `Element#closed?`, lined `vertices`, open-path traversal, and `Rect#center`.
|
|
57
|
+
- Added Geometry aggregate bounding boxes through `Operation.box`, point collinearity through `Point.collinear?`, and
|
|
58
|
+
polygon `simple?`, `convex?`, and `concave?` predicates.
|
|
59
|
+
- Added Protractor and Arc examples and documented the drawing and calculation APIs.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- Removed redundant parent scans while duplicating element subtrees.
|
|
64
|
+
- Reused source bounds across Tile cells to reduce repeated calculations and object allocations.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- Preserved Showcase card HTML through Markdown rendering so all examples and their tabs remain visible.
|
|
69
|
+
- Preserved XML declarations, pre-root comments, CDATA sections, inline comments, and namespace-only leaves in
|
|
70
|
+
Derender output and evaluation.
|
|
71
|
+
- Limited PDF stamp recoloring to replaced text and carried graphics state across a page's content streams.
|
|
72
|
+
- Corrected decimal interval fitting and made Grid point queries honor the active numeric precision.
|
|
73
|
+
- Prevented implicit shell interpretation of single executable arguments in `F.sh` and `F.sh!`, including paths with spaces or shell metacharacters.
|
|
74
|
+
- Preserved inherited namespaces on detached Derender nodes in generated source and direct evaluation, including imports through `EvaluateChildren` and `IncludeChildren`.
|
|
75
|
+
- Prevented sweep and Hatch lines from crossing the exterior between two tangent vertices of a concave polygon.
|
|
76
|
+
- Aligned Tile cells with their declared bounds for circles, rotated shapes, and polygons whose first vertex differs from their bounding-box origin.
|
|
77
|
+
- Limited `Load` chains to 128 active sources with `Executor::LoadDepthError`, preserving cycle detection and nested load diagnostics.
|
|
78
|
+
- Rejected nil and false aggregate-box inputs with the documented geometry error.
|
|
79
|
+
- Restored precision-aware rejection of zero-length Triangle sides.
|
|
80
|
+
- Made point-set collinearity independent of input order and removed eager polygon edge-pair storage.
|
|
81
|
+
- Applied the collinearity tolerance to every three-point subset, removing coordinate-axis-dependent baseline selection.
|
|
82
|
+
|
|
83
|
+
## 0.99.0 - 2026-08-09
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
|
|
87
|
+
- Added `SVG.Module` for building anonymous callable drawing modules with their contract installed before definition.
|
|
88
|
+
- Added responsive Mermaid diagrams for the Derender round trip and SVG validation lifecycle.
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
|
|
92
|
+
- Reorganized the documentation around getting started, usage, composition, documents, layout, output, and examples.
|
|
93
|
+
- Documented how the `igves` and `igsev` command names reflect their conversion directions.
|
|
94
|
+
- Normalized DSL catalog provider labels to lowercase and simplified polyfill-backed editor entries to `inkscape`.
|
|
95
|
+
|
|
96
|
+
### Fixed
|
|
97
|
+
|
|
98
|
+
- Allowed A5-on-A4 pdfcpu conversion to replace an existing output file.
|
|
99
|
+
- Pre-rendered Mermaid diagrams as inline SVG instead of parsing them in site visitors' browsers.
|
|
100
|
+
- Removed duplicate provider and context labels from DSL catalog entries.
|
|
7
101
|
|
|
8
102
|
## 0.98.2 - 2026-07-25
|
|
9
103
|
|
|
@@ -212,7 +306,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
212
306
|
|
|
213
307
|
### Changed
|
|
214
308
|
|
|
215
|
-
- Added the
|
|
309
|
+
- Added the checkerboard example to the documentation showcase flow.
|
|
216
310
|
- Consolidated Victor Book attribution for adapted showcase examples into a single documentation note.
|
|
217
311
|
|
|
218
312
|
### Fixed
|
data/README.md
CHANGED
|
@@ -18,9 +18,34 @@ require "sevgi/geometry"
|
|
|
18
18
|
|
|
19
19
|
```ruby
|
|
20
20
|
rect = Sevgi::Geometry::Rect[3, 5]
|
|
21
|
-
rect.
|
|
21
|
+
rect.center # => Point[1.5, 2.5]
|
|
22
|
+
rect.closed? # => true
|
|
23
|
+
rect.vertices.size # => 4
|
|
24
|
+
rect.points.size # => 5
|
|
25
|
+
rect.translate(2, 1).position # => Point[2.0, 1.0]
|
|
26
|
+
|
|
27
|
+
Sevgi::Geometry::Point.midpoint([0, 0], [3, 5]) # => Point[1.5, 2.5]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Geometry uses SVG screen coordinates. Positive x goes right, positive y goes down, and positive angles turn clockwise.
|
|
31
|
+
Operations return new immutable values.
|
|
32
|
+
|
|
33
|
+
Open lined paths expose `starting`, `ending`, and `reverse`. Use `closed?` to distinguish open and closed boundaries.
|
|
34
|
+
Closed lined paths repeat their first vertex at the end of `points`. `vertices` omits that path-closing repetition.
|
|
35
|
+
|
|
36
|
+
`Ellipse` and `Circle` describe closed boundaries. `Arc` selects a finite, directed part of an ellipse:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
ellipse = Sevgi::Geometry::Ellipse[40, 20, position: [50, 30]]
|
|
40
|
+
arc = ellipse.arc starting_angle: 180, extent: 120
|
|
41
|
+
arc.box
|
|
42
|
+
arc.length
|
|
43
|
+
arc.intersection Sevgi::Geometry::Equation.vertical(50)
|
|
22
44
|
```
|
|
23
45
|
|
|
46
|
+
Positive extent turns clockwise. Its absolute value must be less than 360 degrees.
|
|
47
|
+
Bounds and length do not depend on display precision. Closed ellipses and circles also support line sweeps.
|
|
48
|
+
|
|
24
49
|
## Ruby compatibility
|
|
25
50
|
|
|
26
51
|
Requires Ruby 3.4.0 or newer. CI verifies the current Ruby 3.4 release and the development Ruby from `.ruby-version`.
|
|
@@ -12,7 +12,7 @@ module Sevgi
|
|
|
12
12
|
|
|
13
13
|
# @overload lined(size = Undefined, open: false)
|
|
14
14
|
# Builds a lined element subclass.
|
|
15
|
-
# Instances expose total path `length
|
|
15
|
+
# Instances expose total path `length`. Closed classes also expose `perimeter`.
|
|
16
16
|
# @param size [Integer, Sevgi::Undefined] segment count for fixed-size elements, or Undefined for variable size
|
|
17
17
|
# @param open [Boolean] true for an open path, false for a closed path
|
|
18
18
|
# @return [Class] subclass of {Sevgi::Geometry::Element::Lined}
|
|
@@ -22,16 +22,6 @@ module Sevgi
|
|
|
22
22
|
# Path.([0, 0], [1, 0], [1, 1])
|
|
23
23
|
def self.lined(...) = Lined.send(:build, ...)
|
|
24
24
|
|
|
25
|
-
# @overload arced(*args)
|
|
26
|
-
# Builds an arced element subclass.
|
|
27
|
-
# @api private
|
|
28
|
-
# @param args [Array<Object>] arced factory arguments
|
|
29
|
-
# @return [Class]
|
|
30
|
-
# @raise [NoMethodError] until arced elements are implemented
|
|
31
|
-
def self.arced(...) = Arced.build(...)
|
|
32
|
-
|
|
33
|
-
private_class_method :arced
|
|
34
|
-
|
|
35
25
|
# Core API
|
|
36
26
|
|
|
37
27
|
# Returns a copy moved to a point and optional offset.
|
|
@@ -62,7 +52,12 @@ module Sevgi
|
|
|
62
52
|
# @raise [Sevgi::PanicError] when a subclass does not implement box
|
|
63
53
|
def box = PanicError.("#{self.class}#box must be implemented")
|
|
64
54
|
|
|
65
|
-
#
|
|
55
|
+
# Reports whether the element boundary forms a closed path.
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def closed? = self.class.send(:close?)
|
|
58
|
+
|
|
59
|
+
# Returns carrier equations for candidate boundary intersections.
|
|
60
|
+
# A finite element can represent only part of each carrier.
|
|
66
61
|
# @abstract Subclasses implement element-specific equations.
|
|
67
62
|
# @return [Array<Sevgi::Geometry::Equation>]
|
|
68
63
|
# @raise [Sevgi::PanicError] when a subclass does not implement equations
|
|
@@ -73,6 +68,29 @@ module Sevgi
|
|
|
73
68
|
# @return [Boolean]
|
|
74
69
|
def ignorable?(precision: nil) = F.zero?(box.width, precision:) && F.zero?(box.height, precision:)
|
|
75
70
|
|
|
71
|
+
# Intersects the element boundary with an equation.
|
|
72
|
+
#
|
|
73
|
+
# Precision controls boundary membership, returned-coordinate rounding, and duplicate collapse.
|
|
74
|
+
# A nil precision uses the current thread's function precision for all three stages.
|
|
75
|
+
# @example Intersect a rectangle with a vertical line
|
|
76
|
+
# rect = Sevgi::Geometry::Rect[8, 4]
|
|
77
|
+
# axis = Sevgi::Geometry::Equation.vertical(3)
|
|
78
|
+
# rect.intersection(axis).map(&:deconstruct) # => [[3.0, 0.0], [3.0, 4.0]]
|
|
79
|
+
# @param equation [Sevgi::Geometry::Equation] equation to intersect with
|
|
80
|
+
# @param precision [Integer, nil] coordinate precision, or nil for the current function default
|
|
81
|
+
# @return [Array<Sevgi::Geometry::Point>] unique boundary intersection points
|
|
82
|
+
# @raise [Sevgi::Geometry::Error] when equation is not an equation
|
|
83
|
+
# @raise [Sevgi::PanicError] when the equation combination is not implemented
|
|
84
|
+
def intersection(equation, precision: nil)
|
|
85
|
+
Error.("Must be an equation: #{equation}") unless equation.is_a?(Equation)
|
|
86
|
+
|
|
87
|
+
points = equations.flat_map do |candidate|
|
|
88
|
+
equation.intersect(candidate).select { |point| boundary_point?(point, precision) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
points.map { |point| point.approx(precision) }.uniq
|
|
92
|
+
end
|
|
93
|
+
|
|
76
94
|
# Returns the element position.
|
|
77
95
|
# @abstract Subclasses implement element-specific positioning.
|
|
78
96
|
# @return [Sevgi::Geometry::Point]
|
|
@@ -87,14 +105,24 @@ module Sevgi
|
|
|
87
105
|
# @raise [Sevgi::PanicError] when a subclass does not implement translate
|
|
88
106
|
def translate(_x, _y) = PanicError.("#{self.class}#translate must be implemented")
|
|
89
107
|
|
|
108
|
+
def boundary_point?(point, precision)
|
|
109
|
+
return on?(point) if precision.nil?
|
|
110
|
+
|
|
111
|
+
F.with_precision(precision) { on?(point) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private :boundary_point?
|
|
115
|
+
|
|
90
116
|
# Element whose boundary is represented by straight segments.
|
|
91
117
|
#
|
|
92
118
|
# The same path is available as immutable {#points}, {#segments}, and
|
|
93
|
-
# {#lines} collections.
|
|
94
|
-
#
|
|
95
|
-
# `inside?` on an open path
|
|
96
|
-
#
|
|
119
|
+
# {#lines} collections. {#vertices} omits the repeated closing point from
|
|
120
|
+
# closed shapes. Open paths have the same values in `vertices` and `points`.
|
|
121
|
+
# Only closed shapes have a filled interior, so `inside?` on an open path
|
|
122
|
+
# is equivalent to testing its boundary.
|
|
123
|
+
# @example Inspect path and geometric views
|
|
97
124
|
# rect = Sevgi::Geometry::Rect[8, 4]
|
|
125
|
+
# rect.vertices.size # => 4
|
|
98
126
|
# rect.points.size # => 5
|
|
99
127
|
# rect.segments.size # => 4
|
|
100
128
|
# rect.lines.size # => 4
|
|
@@ -103,6 +131,18 @@ module Sevgi
|
|
|
103
131
|
# Open lined element base class.
|
|
104
132
|
# @api private
|
|
105
133
|
Open = Class.new(self) do
|
|
134
|
+
# Returns the first point in the directed path.
|
|
135
|
+
# @return [Sevgi::Geometry::Point]
|
|
136
|
+
def starting = points.first
|
|
137
|
+
|
|
138
|
+
# Returns the last point in the directed path.
|
|
139
|
+
# @return [Sevgi::Geometry::Point]
|
|
140
|
+
def ending = points.last
|
|
141
|
+
|
|
142
|
+
# Returns the same trace with opposite traversal.
|
|
143
|
+
# @return [Sevgi::Geometry::Element::Lined]
|
|
144
|
+
def reverse = self.class.send(:new_by_points!, *points.reverse)
|
|
145
|
+
|
|
106
146
|
# Draws the element as an SVG polyline.
|
|
107
147
|
# @param node [Object] graphics node receiving the drawing command
|
|
108
148
|
# @return [Object] graphics node command result
|
|
@@ -313,13 +353,23 @@ module Sevgi
|
|
|
313
353
|
approx.send(:draw!, ...)
|
|
314
354
|
end
|
|
315
355
|
|
|
316
|
-
# Returns immutable element points.
|
|
356
|
+
# Returns immutable element points in path order.
|
|
357
|
+
# Closed elements repeat the first vertex at the end.
|
|
317
358
|
# @param approximate [Boolean] true to round points with the current function precision
|
|
318
359
|
# @return [Array<Sevgi::Geometry::Point>] frozen point collection
|
|
319
360
|
def points(approximate = false)
|
|
320
361
|
approximate ? rounded_points(nil) : @points
|
|
321
362
|
end
|
|
322
363
|
|
|
364
|
+
# Returns immutable geometric vertices in path order.
|
|
365
|
+
# Closed elements omit the repeated closing point. Open elements return {#points}.
|
|
366
|
+
# @return [Array<Sevgi::Geometry::Point>] frozen vertex collection
|
|
367
|
+
def vertices
|
|
368
|
+
return points unless closed?
|
|
369
|
+
|
|
370
|
+
@vertices ||= points[...-1].freeze
|
|
371
|
+
end
|
|
372
|
+
|
|
323
373
|
# Returns the first point.
|
|
324
374
|
# @return [Sevgi::Geometry::Point]
|
|
325
375
|
def position
|
|
@@ -420,29 +470,6 @@ module Sevgi
|
|
|
420
470
|
# @return [Array<Sevgi::Geometry::Equation::Linear>] frozen equation collection
|
|
421
471
|
def equations = @equations ||= lines.map(&:equation).freeze
|
|
422
472
|
|
|
423
|
-
# Intersects the element boundary with an equation.
|
|
424
|
-
#
|
|
425
|
-
# Precision is applied consistently to boundary-membership tolerance, returned-coordinate rounding, and
|
|
426
|
-
# duplicate collapse. A nil precision uses the current thread's function precision for all three stages.
|
|
427
|
-
# @example Intersect a rectangle with a vertical line
|
|
428
|
-
# rect = Sevgi::Geometry::Rect[8, 4]
|
|
429
|
-
# axis = Sevgi::Geometry::Equation.vertical(3)
|
|
430
|
-
# rect.intersection(axis).map(&:deconstruct) # => [[3.0, 0.0], [3.0, 4.0]]
|
|
431
|
-
# @param equation [Sevgi::Geometry::Equation] equation to intersect with
|
|
432
|
-
# @param precision [Integer, nil] decimal precision for returned points, or nil for the current function default
|
|
433
|
-
# @return [Array<Sevgi::Geometry::Point>] unique boundary intersection points
|
|
434
|
-
# @raise [Sevgi::Geometry::Error] when equation is not an equation
|
|
435
|
-
# @raise [Sevgi::PanicError] when the equation combination is not implemented
|
|
436
|
-
def intersection(equation, precision: nil)
|
|
437
|
-
Error.("Must be an equation: #{equation}") unless equation.is_a?(Equation)
|
|
438
|
-
|
|
439
|
-
points = equations.flat_map do |candidate|
|
|
440
|
-
equation.intersect(candidate).select { |point| boundary_point?(point, precision) }
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
points.map { |point| point.approx(precision) }.uniq
|
|
444
|
-
end
|
|
445
|
-
|
|
446
473
|
# Properties
|
|
447
474
|
|
|
448
475
|
# Returns a line by index.
|
|
@@ -465,14 +492,19 @@ module Sevgi
|
|
|
465
492
|
# @return [Sevgi::Geometry::Segment]
|
|
466
493
|
def head = @head ||= segments.first
|
|
467
494
|
|
|
468
|
-
# Returns immutable boundary lines
|
|
495
|
+
# Returns immutable boundary lines with the stored endpoints and segments.
|
|
469
496
|
# @return [Array<Sevgi::Geometry::Line>] frozen line collection
|
|
470
497
|
def lines
|
|
471
|
-
@lines ||=
|
|
472
|
-
.
|
|
473
|
-
.
|
|
474
|
-
|
|
475
|
-
|
|
498
|
+
@lines ||= points
|
|
499
|
+
.each_cons(2)
|
|
500
|
+
.zip(segments)
|
|
501
|
+
.map do |points, segment|
|
|
502
|
+
# Rebuilding from either view alone loses stored endpoints or input angles.
|
|
503
|
+
Line.send(:new) do
|
|
504
|
+
@points = points
|
|
505
|
+
@segments = [segment]
|
|
506
|
+
end
|
|
507
|
+
end
|
|
476
508
|
.freeze
|
|
477
509
|
end
|
|
478
510
|
|
|
@@ -488,7 +520,7 @@ module Sevgi
|
|
|
488
520
|
|
|
489
521
|
# Reports whether a point is inside or on the boundary.
|
|
490
522
|
#
|
|
491
|
-
# Open paths have no filled interior
|
|
523
|
+
# Open paths have no filled interior. For them this predicate is true
|
|
492
524
|
# only for points on the actual path boundary.
|
|
493
525
|
# @example Compare closed and open path containment
|
|
494
526
|
# rect = Sevgi::Geometry::Rect[8, 4]
|
|
@@ -502,7 +534,7 @@ module Sevgi
|
|
|
502
534
|
def inside?(point)
|
|
503
535
|
point = Tuple[Point, point]
|
|
504
536
|
|
|
505
|
-
return on?(point) unless
|
|
537
|
+
return on?(point) unless closed?
|
|
506
538
|
|
|
507
539
|
on?(point) || pnpoly(points, point)
|
|
508
540
|
end
|
|
@@ -525,18 +557,11 @@ module Sevgi
|
|
|
525
557
|
|
|
526
558
|
private
|
|
527
559
|
|
|
528
|
-
def boundary_point?(point, precision)
|
|
529
|
-
return on?(point) if precision.nil?
|
|
530
|
-
|
|
531
|
-
F.with_precision(precision) { on?(point) }
|
|
532
|
-
end
|
|
533
|
-
|
|
534
560
|
def calculate_points_from_segments
|
|
535
561
|
Error.("No segments found") unless segments
|
|
536
562
|
|
|
537
563
|
[point = position, *segments.map { point = it.ending(point) }].tap do |points|
|
|
538
|
-
|
|
539
|
-
points[-1] = points.first if points.first.eq?(points.last)
|
|
564
|
+
points[-1] = points.first if closed? && points.first.eq?(points.last)
|
|
540
565
|
end
|
|
541
566
|
end
|
|
542
567
|
|
|
@@ -578,7 +603,7 @@ module Sevgi
|
|
|
578
603
|
|
|
579
604
|
def rounded_points(precision)
|
|
580
605
|
rounded = @points.map { it.approx(precision) }
|
|
581
|
-
rounded[-1] = rounded.first if
|
|
606
|
+
rounded[-1] = rounded.first if closed?
|
|
582
607
|
rounded.freeze
|
|
583
608
|
end
|
|
584
609
|
|
|
@@ -592,7 +617,7 @@ module Sevgi
|
|
|
592
617
|
|
|
593
618
|
Error.("Wrong number of points; expected #{np} where found #{points.size}") unless points.size == np
|
|
594
619
|
Error.("Wrong number of segments; expected #{ns} where found #{segments.size}") unless segments.size == ns
|
|
595
|
-
return unless
|
|
620
|
+
return unless closed? && !points.first.eq?(points.last)
|
|
596
621
|
|
|
597
622
|
Error.("Element points must form a closed path")
|
|
598
623
|
end
|
|
@@ -600,12 +625,38 @@ module Sevgi
|
|
|
600
625
|
def validate_geometry! = nil
|
|
601
626
|
end
|
|
602
627
|
|
|
603
|
-
#
|
|
604
|
-
#
|
|
628
|
+
# Abstract family of circular and elliptical boundary elements.
|
|
629
|
+
# Arc is open, while Ellipse and Circle have closed boundaries. Concrete values share exact and precision-aware comparisons.
|
|
605
630
|
class Arced < self
|
|
631
|
+
# Compares canonical fields with coordinate and numeric precision.
|
|
632
|
+
# @param other [Object] comparison target
|
|
633
|
+
# @param precision [Integer, nil] decimal precision, or nil for the current function default
|
|
634
|
+
# @return [Boolean]
|
|
635
|
+
def eq?(other, precision: nil)
|
|
636
|
+
other.instance_of?(self.class) &&
|
|
637
|
+
state.zip(other.send(:state)).all? do |left, right|
|
|
638
|
+
left.is_a?(::Numeric) ? F.eq?(left, right, precision:) : left.eq?(right, precision:)
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
# Reports strict equality by concrete class and canonical fields.
|
|
643
|
+
# @param other [Object] comparison target
|
|
644
|
+
# @return [Boolean]
|
|
645
|
+
def eql?(other) = other.instance_of?(self.class) && state == other.send(:state)
|
|
646
|
+
|
|
647
|
+
# Returns a hash independent of numeric precision.
|
|
648
|
+
# @return [Integer]
|
|
649
|
+
def hash = [self.class, *state].hash
|
|
650
|
+
|
|
651
|
+
# Reports whether a point is outside the element.
|
|
652
|
+
# @param point [Sevgi::Geometry::Point, Array<Numeric>] point to test
|
|
653
|
+
# @return [Boolean]
|
|
654
|
+
# @raise [Sevgi::Geometry::Error] when point cannot be coerced
|
|
655
|
+
def outside?(point) = !inside?(point)
|
|
656
|
+
|
|
657
|
+
alias == eql?
|
|
606
658
|
end
|
|
607
659
|
|
|
608
|
-
private_constant :Arced
|
|
609
660
|
end
|
|
610
661
|
|
|
611
662
|
require_relative "elements/line"
|
|
@@ -614,5 +665,8 @@ module Sevgi
|
|
|
614
665
|
require_relative "elements/polyline"
|
|
615
666
|
require_relative "elements/rect"
|
|
616
667
|
require_relative "elements/triangle"
|
|
668
|
+
require_relative "elements/ellipse"
|
|
669
|
+
require_relative "elements/circle"
|
|
670
|
+
require_relative "elements/arc"
|
|
617
671
|
end
|
|
618
672
|
end
|