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 { ... } ⇒ void
constructor
Creates a lined element from a geometry-definition block.
-
#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 { ... } ⇒ void
Creates a lined element from a geometry-definition block.
211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/sevgi/geometry/element.rb', line 211 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.
366 |
# File 'lib/sevgi/geometry/element.rb', line 366 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.
229 230 231 232 233 234 |
# File 'lib/sevgi/geometry/element.rb', line 229 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.
370 |
# File 'lib/sevgi/geometry/element.rb', line 370 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.
376 |
# File 'lib/sevgi/geometry/element.rb', line 376 def call(i) = points[i].tap { Error.("No point exist for index: #{i}") unless it } |
#draw(node, **attributes) ⇒ Object
241 242 243 |
# File 'lib/sevgi/geometry/element.rb', line 241 def draw(...) approx.draw!(...) end |
#eq?(other, precision: nil) ⇒ Boolean
Compares element points with optional numeric precision.
318 319 320 321 322 |
# File 'lib/sevgi/geometry/element.rb', line 318 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.
327 |
# File 'lib/sevgi/geometry/element.rb', line 327 def eql?(other) = other.instance_of?(self.class) && points == other.points |
#equations ⇒ Array<Sevgi::Geometry::Equation::Linear>
Returns immutable boundary equations for all lines.
339 |
# File 'lib/sevgi/geometry/element.rb', line 339 def equations = @equations ||= lines.map(&:equation).freeze |
#hash ⇒ Integer
Returns a hash compatible with strict equality.
331 |
# File 'lib/sevgi/geometry/element.rb', line 331 def hash = [self.class, *points].hash |
#head ⇒ Sevgi::Geometry::Segment
Returns the first segment.
380 |
# File 'lib/sevgi/geometry/element.rb', line 380 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.
410 411 412 413 414 415 416 |
# File 'lib/sevgi/geometry/element.rb', line 410 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.
352 353 354 355 356 357 358 |
# File 'lib/sevgi/geometry/element.rb', line 352 def intersection(equation, precision: nil) points = equations.flat_map do |candidate| equation.intersect(candidate).select { |point| boundary_point?(point, precision) } end points.map { |point| point.approx(precision) }.uniq end |
#lines ⇒ Array<Sevgi::Geometry::Line>
Returns immutable boundary lines derived from segments and points.
384 385 386 387 388 389 390 391 |
# File 'lib/sevgi/geometry/element.rb', line 384 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.
422 423 424 425 426 |
# File 'lib/sevgi/geometry/element.rb', line 422 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.
432 |
# File 'lib/sevgi/geometry/element.rb', line 432 def outside?(point) = !inside?(point) |
#perimeter ⇒ Float
Returns the sum of segment lengths.
395 |
# File 'lib/sevgi/geometry/element.rb', line 395 def perimeter = @perimeter ||= segments.sum(&:length) |
#points(approximate = false) ⇒ Array<Sevgi::Geometry::Point>
Returns immutable element points.
248 249 250 |
# File 'lib/sevgi/geometry/element.rb', line 248 def points(approximate = false) approximate ? @points.map(&:approx).freeze : @points end |
#position ⇒ Sevgi::Geometry::Point
Returns the first point.
254 255 256 |
# File 'lib/sevgi/geometry/element.rb', line 254 def position @position ||= points.first end |
#reflect(x: true, y: true) ⇒ Sevgi::Geometry::Element::Lined
Returns an element reflected across the selected axes.
306 307 308 309 310 |
# File 'lib/sevgi/geometry/element.rb', line 306 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.
306 307 308 309 310 |
# File 'lib/sevgi/geometry/element.rb', line 306 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.
306 307 308 309 310 |
# File 'lib/sevgi/geometry/element.rb', line 306 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.
261 262 263 |
# File 'lib/sevgi/geometry/element.rb', line 261 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.
306 307 308 309 310 |
# File 'lib/sevgi/geometry/element.rb', line 306 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.
306 307 308 309 310 |
# File 'lib/sevgi/geometry/element.rb', line 306 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.
306 307 308 309 310 |
# File 'lib/sevgi/geometry/element.rb', line 306 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.
399 |
# File 'lib/sevgi/geometry/element.rb', line 399 def tail = @tail ||= segments.last |
#translate(dx, dy = Undefined) ⇒ Sevgi::Geometry::Element::Lined
Returns an element translated by offset.
306 307 308 309 310 |
# File 'lib/sevgi/geometry/element.rb', line 306 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 |