Class: Sevgi::Geometry::Element::Lined
- Inherits:
-
Sevgi::Geometry::Element
- Object
- Sevgi::Geometry::Element
- Sevgi::Geometry::Element::Lined
- Defined in:
- lib/sevgi/geometry/element.rb
Overview
rubocop:disable Metrics/ClassLength Element whose boundary is represented by straight segments.
Constant Summary collapse
- Open =
Open lined element base class.
Class.new(self) do # Draws the element as an SVG polyline. # @param node [Object] graphics node receiving the drawing command # @return [Object] graphics node command result def draw!(node, **) = node.polyline(points: points.map { it.deconstruct.join(",") }, **) end
- Close =
Closed lined element base class.
Class.new(self) do # Creates a closed element from points, appending the first point. # @param points [Array<Sevgi::Geometry::Point, Array<Numeric>>] boundary points # @return [Sevgi::Geometry::Element::Lined] # @raise [Sevgi::Geometry::Error] when any point cannot be coerced def self.new_by_points(*points) = super(*points, points.first) # Draws the element as an SVG polygon. # @param node [Object] graphics node receiving the drawing command # @return [Object] graphics node command result def draw!(node, **) = node.polygon(points: points.map { it.deconstruct.join(",") }, **) end
- SHORTCUTS =
Point shortcut names generated for fixed-size lined elements.
("A".."Z").to_a.freeze
Class Method Summary collapse
-
.[](*segments, position: Origin) ⇒ Sevgi::Geometry::Element::Lined
Builds an element from segments.
-
.build(size = Undefined, open: false) ⇒ Class
Builds a concrete lined element class.
-
.call(*points) ⇒ Sevgi::Geometry::Element::Lined
Builds an element from points.
-
.from_points(*points) ⇒ Sevgi::Geometry::Element::Lined
Builds an element from points.
-
.from_segments(*segments, position: Origin) ⇒ Sevgi::Geometry::Element::Lined
Builds an element from segments.
-
.new_by_points(*points) ⇒ Sevgi::Geometry::Element::Lined
Builds an element from points, applying closed-path behavior where appropriate.
-
.new_by_points!(*points) ⇒ Sevgi::Geometry::Element::Lined
Builds an element from an exact point path.
-
.new_by_segments(*segments, position: Origin) ⇒ Sevgi::Geometry::Element::Lined
Builds an element from segments and a start position.
Instance Method Summary collapse
-
#[](i) ⇒ Sevgi::Geometry::Line
Returns a line by index.
-
#approx ⇒ Sevgi::Geometry::Element::Lined
Returns an element with approximate points and segments.
-
#box ⇒ Sevgi::Geometry::Rect
Returns the bounding rectangle.
-
#call(i) ⇒ Sevgi::Geometry::Point
Returns a point by index.
-
#draw(node, **attributes) ⇒ Object
Draws an approximate element into a graphics node.
-
#eq?(other, precision: nil) ⇒ Boolean
Compares element points with optional numeric precision.
-
#eql?(other) ⇒ Boolean
(also: #==)
Reports strict element equality by class and exact points.
-
#equations ⇒ Array<Sevgi::Geometry::Equation::Linear>
Returns immutable boundary equations for all lines.
-
#hash ⇒ Integer
Returns a hash compatible with strict equality.
-
#head ⇒ Sevgi::Geometry::Segment
Returns the first segment.
-
#initialize(&block) ⇒ Lined
constructor
A new instance of Lined.
-
#inside?(point) ⇒ Boolean
Reports whether a point is inside or on the boundary.
-
#intersection(equation, precision: nil) ⇒ Array<Sevgi::Geometry::Point>
Intersects the element boundary with an equation.
-
#lines ⇒ Array<Sevgi::Geometry::Line>
Returns immutable boundary lines derived from segments and points.
-
#on?(point) ⇒ Boolean
Reports whether a point is on the boundary.
-
#outside?(point) ⇒ Boolean
Reports whether a point is outside the element boundary.
-
#perimeter ⇒ Float
Returns the sum of segment lengths.
-
#points(approximate = false) ⇒ Array<Sevgi::Geometry::Point>
Returns immutable element points.
-
#position ⇒ Sevgi::Geometry::Point
Returns the first point.
-
#reflect(x: true, y: true) ⇒ Sevgi::Geometry::Element::Lined
Returns an element reflected across the selected axes.
-
#rotate(a) ⇒ Sevgi::Geometry::Element::Lined
Returns an element rotated around the origin.
-
#scale(sx, sy = Undefined) ⇒ Sevgi::Geometry::Element::Lined
Returns an element scaled from the origin.
-
#segments(approximate = false) ⇒ Array<Sevgi::Geometry::Segment>
Returns immutable element segments.
-
#skew(ax, ay = Undefined) ⇒ Sevgi::Geometry::Element::Lined
Returns an element skewed from the origin.
-
#skew_x(a) ⇒ Sevgi::Geometry::Element::Lined
Returns an element skewed along x.
-
#skew_y(a) ⇒ Sevgi::Geometry::Element::Lined
Returns an element skewed along y.
-
#tail ⇒ Sevgi::Geometry::Segment
Returns the last segment.
-
#translate(dx, dy = Undefined) ⇒ Sevgi::Geometry::Element::Lined
Returns an element translated by offset.
Methods inherited from Sevgi::Geometry::Element
Constructor Details
#initialize(&block) ⇒ Lined
Returns a new instance of Lined.
206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/sevgi/geometry/element.rb', line 206 def initialize(&block) super() Error.("Constructor block required") unless block instance_exec(&block) @points ||= calculate_points_from_segments @segments ||= calculate_segments_from_points freeze_geometry! sanitize end |
Class Method Details
.[](*segments, position: Origin) ⇒ Sevgi::Geometry::Element::Lined
126 |
# File 'lib/sevgi/geometry/element.rb', line 126 def self.[](...) = from_segments(...) |
.build(size = Undefined, open: false) ⇒ Class
Builds a concrete lined element class.
106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/sevgi/geometry/element.rb', line 106 def self.build(size = Undefined, open: false) Class.new(open ? Open : Close) do define_singleton_method(:close?) { !open } define_singleton_method(:open?) { open } define_singleton_method(:poly?) { size == Undefined } define_singleton_method(:size) { size } Lined.send(:define_shortcuts, self, size, open:) unless size == Undefined end end |
.call(*points) ⇒ Sevgi::Geometry::Element::Lined
133 |
# File 'lib/sevgi/geometry/element.rb', line 133 def self.call(...) = from_points(...) |
.from_points(*points) ⇒ Sevgi::Geometry::Element::Lined
140 |
# File 'lib/sevgi/geometry/element.rb', line 140 def self.from_points(...) = new_by_points(...) |
.from_segments(*segments, position: Origin) ⇒ Sevgi::Geometry::Element::Lined
148 |
# File 'lib/sevgi/geometry/element.rb', line 148 def self.from_segments(...) = new_by_segments(...) |
.new_by_points(*points) ⇒ Sevgi::Geometry::Element::Lined
155 |
# File 'lib/sevgi/geometry/element.rb', line 155 def self.new_by_points(...) = new_by_points!(...) |
.new_by_points!(*points) ⇒ Sevgi::Geometry::Element::Lined
Builds an element from an exact point path.
Closed classes require the closing point to be supplied by the caller.
163 164 165 166 167 |
# File 'lib/sevgi/geometry/element.rb', line 163 def self.new_by_points!(*points) new do @points = Tuples[Point, *points] end end |
.new_by_segments(*segments, position: Origin) ⇒ Sevgi::Geometry::Element::Lined
Builds an element from segments and a start position.
174 175 176 177 178 179 |
# File 'lib/sevgi/geometry/element.rb', line 174 def self.new_by_segments(*segments, position: Origin) new do @position = Tuple[Point, position] @segments = Tuples[Segment, *segments] end end |
Instance Method Details
#[](i) ⇒ Sevgi::Geometry::Line
Returns a line by index.
360 |
# File 'lib/sevgi/geometry/element.rb', line 360 def [](i) = lines[i].tap { |line| Error.("No line exist for index: #{i}") unless line } |
#approx ⇒ Sevgi::Geometry::Element::Lined
Returns an element with approximate points and segments.
224 225 226 227 228 229 |
# File 'lib/sevgi/geometry/element.rb', line 224 def approx points, segments = points(true), segments(true) self.class.send(:new) do @points, @segments = points, segments end end |
#box ⇒ Sevgi::Geometry::Rect
Returns the bounding rectangle.
364 |
# File 'lib/sevgi/geometry/element.rb', line 364 def box = Rect.from_corners([(xs = points.map(&:x)).min, (ys = points.map(&:y)).min], [xs.max, ys.max]) |
#call(i) ⇒ Sevgi::Geometry::Point
Returns a point by index.
370 |
# File 'lib/sevgi/geometry/element.rb', line 370 def call(i) = points[i].tap { Error.("No point exist for index: #{i}") unless it } |
#draw(node, **attributes) ⇒ Object
236 237 238 |
# File 'lib/sevgi/geometry/element.rb', line 236 def draw(...) approx.draw!(...) end |
#eq?(other, precision: nil) ⇒ Boolean
Compares element points with optional numeric precision.
313 314 315 316 317 |
# File 'lib/sevgi/geometry/element.rb', line 313 def eq?(other, precision: nil) other.instance_of?(self.class) && points.size == other.points.size && points.zip(other.points).all? { |left, right| left.eq?(right, precision:) } end |
#eql?(other) ⇒ Boolean Also known as: ==
Reports strict element equality by class and exact points.
322 |
# File 'lib/sevgi/geometry/element.rb', line 322 def eql?(other) = other.instance_of?(self.class) && points == other.points |
#equations ⇒ Array<Sevgi::Geometry::Equation::Linear>
Returns immutable boundary equations for all lines.
334 |
# File 'lib/sevgi/geometry/element.rb', line 334 def equations = @equations ||= lines.map(&:equation).freeze |
#hash ⇒ Integer
Returns a hash compatible with strict equality.
326 |
# File 'lib/sevgi/geometry/element.rb', line 326 def hash = [self.class, *points].hash |
#head ⇒ Sevgi::Geometry::Segment
Returns the first segment.
374 |
# File 'lib/sevgi/geometry/element.rb', line 374 def head = @head ||= segments.first |
#inside?(point) ⇒ Boolean
Reports whether a point is inside or on the boundary.
Open paths have no filled interior; for them this predicate is true only for points on the actual path boundary.
404 405 406 407 408 409 410 |
# File 'lib/sevgi/geometry/element.rb', line 404 def inside?(point) point = Tuple[Point, point] return on?(point) if self.class.open? on?(point) || pnpoly(points, point) end |
#intersection(equation, precision: nil) ⇒ Array<Sevgi::Geometry::Point>
Intersects the element boundary with an equation.
Boundary membership is tested on unrounded candidate points. precision:
only rounds returned coordinates and controls duplicate collapse after
membership has been accepted. When precision is nil, returned points use
the current function precision.
347 348 349 350 351 352 |
# File 'lib/sevgi/geometry/element.rb', line 347 def intersection(equation, precision: nil) equations .flat_map { |candidate| equation.intersect(candidate).select { |point| on?(point) } } .map { |point| point.approx(precision) } .uniq end |
#lines ⇒ Array<Sevgi::Geometry::Line>
Returns immutable boundary lines derived from segments and points.
378 379 380 381 382 383 384 385 |
# File 'lib/sevgi/geometry/element.rb', line 378 def lines @lines ||= segments .zip(points[...segments.size]) .map { |segment, position| segment.line(position) } .freeze end |
#on?(point) ⇒ Boolean
Reports whether a point is on the boundary.
416 417 418 419 420 |
# File 'lib/sevgi/geometry/element.rb', line 416 def on?(point) point = Tuple[Point, point] lines.any? { it.over?(point) } end |
#outside?(point) ⇒ Boolean
Reports whether a point is outside the element boundary.
426 |
# File 'lib/sevgi/geometry/element.rb', line 426 def outside?(point) = !inside?(point) |
#perimeter ⇒ Float
Returns the sum of segment lengths.
389 |
# File 'lib/sevgi/geometry/element.rb', line 389 def perimeter = @perimeter ||= segments.sum(&:length) |
#points(approximate = false) ⇒ Array<Sevgi::Geometry::Point>
Returns immutable element points.
243 244 245 |
# File 'lib/sevgi/geometry/element.rb', line 243 def points(approximate = false) approximate ? @points.map(&:approx).freeze : @points end |
#position ⇒ Sevgi::Geometry::Point
Returns the first point.
249 250 251 |
# File 'lib/sevgi/geometry/element.rb', line 249 def position @position ||= points.first end |
#reflect(x: true, y: true) ⇒ Sevgi::Geometry::Element::Lined
Returns an element reflected across the selected axes.
301 302 303 304 305 |
# File 'lib/sevgi/geometry/element.rb', line 301 Geometry::Affinity.instance_methods.each do |transform| define_method(transform) do |*args, **kwargs, &block| self.class.new_by_points!(*points.map { it.public_send(transform, *args, **kwargs, &block) }) end end |
#rotate(a) ⇒ Sevgi::Geometry::Element::Lined
Returns an element rotated around the origin.
301 302 303 304 305 |
# File 'lib/sevgi/geometry/element.rb', line 301 Geometry::Affinity.instance_methods.each do |transform| define_method(transform) do |*args, **kwargs, &block| self.class.new_by_points!(*points.map { it.public_send(transform, *args, **kwargs, &block) }) end end |
#scale(sx, sy = Undefined) ⇒ Sevgi::Geometry::Element::Lined
Returns an element scaled from the origin.
301 302 303 304 305 |
# File 'lib/sevgi/geometry/element.rb', line 301 Geometry::Affinity.instance_methods.each do |transform| define_method(transform) do |*args, **kwargs, &block| self.class.new_by_points!(*points.map { it.public_send(transform, *args, **kwargs, &block) }) end end |
#segments(approximate = false) ⇒ Array<Sevgi::Geometry::Segment>
Returns immutable element segments.
256 257 258 |
# File 'lib/sevgi/geometry/element.rb', line 256 def segments(approximate = false) approximate ? @segments.map(&:approx).freeze : @segments end |
#skew(ax, ay = Undefined) ⇒ Sevgi::Geometry::Element::Lined
Returns an element skewed from the origin.
301 302 303 304 305 |
# File 'lib/sevgi/geometry/element.rb', line 301 Geometry::Affinity.instance_methods.each do |transform| define_method(transform) do |*args, **kwargs, &block| self.class.new_by_points!(*points.map { it.public_send(transform, *args, **kwargs, &block) }) end end |
#skew_x(a) ⇒ Sevgi::Geometry::Element::Lined
Returns an element skewed along x.
301 302 303 304 305 |
# File 'lib/sevgi/geometry/element.rb', line 301 Geometry::Affinity.instance_methods.each do |transform| define_method(transform) do |*args, **kwargs, &block| self.class.new_by_points!(*points.map { it.public_send(transform, *args, **kwargs, &block) }) end end |
#skew_y(a) ⇒ Sevgi::Geometry::Element::Lined
Returns an element skewed along y.
301 302 303 304 305 |
# File 'lib/sevgi/geometry/element.rb', line 301 Geometry::Affinity.instance_methods.each do |transform| define_method(transform) do |*args, **kwargs, &block| self.class.new_by_points!(*points.map { it.public_send(transform, *args, **kwargs, &block) }) end end |
#tail ⇒ Sevgi::Geometry::Segment
Returns the last segment.
393 |
# File 'lib/sevgi/geometry/element.rb', line 393 def tail = @tail ||= segments.last |
#translate(dx, dy = Undefined) ⇒ Sevgi::Geometry::Element::Lined
Returns an element translated by offset.
301 302 303 304 305 |
# File 'lib/sevgi/geometry/element.rb', line 301 Geometry::Affinity.instance_methods.each do |transform| define_method(transform) do |*args, **kwargs, &block| self.class.new_by_points!(*points.map { it.public_send(transform, *args, **kwargs, &block) }) end end |