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
Element whose boundary is represented by straight segments.
The same path is available as immutable #points, #segments, and
#lines collections. Closed shapes repeat their first point at the end;
open paths do not. Only closed shapes have a filled interior, so
inside? on an open path is equivalent to testing its boundary.
Instance Method Summary collapse
-
#[](i) ⇒ Sevgi::Geometry::Line
Returns a line by index.
-
#approx(precision = nil) ⇒ Sevgi::Geometry::Element::Lined
Returns an element rebuilt from rounded boundary points.
-
#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
private
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.
-
#length ⇒ Float
Returns the total path length.
-
#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.
-
#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
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Creates a lined element from a geometry-definition block.
280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/sevgi/geometry/element.rb', line 280 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 validate_geometry! end |
Instance Method Details
#[](i) ⇒ Sevgi::Geometry::Line
Returns a line by index.
452 |
# File 'lib/sevgi/geometry/element.rb', line 452 def [](i) = lines[i].tap { |line| Error.("No line exists for index: #{i}") unless line } |
#approx(precision = nil) ⇒ Sevgi::Geometry::Element::Lined
Returns an element rebuilt from rounded boundary points.
Segments are derived from the rounded points so both representations describe the same path. When rounding breaks a concrete shape invariant, the result widens to a less specific Rect, Polygon, or Polyline rather than retaining a misleading concrete class.
305 |
# File 'lib/sevgi/geometry/element.rb', line 305 def approx(precision = nil) = self.class.send(:approximate, *rounded_points(precision)) |
#box ⇒ Sevgi::Geometry::Rect
Returns the bounding rectangle.
456 |
# File 'lib/sevgi/geometry/element.rb', line 456 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.
462 |
# File 'lib/sevgi/geometry/element.rb', line 462 def call(i) = points[i].tap { Error.("No point exists for index: #{i}") unless it } |
#draw(node, **attributes) ⇒ Object
312 313 314 |
# File 'lib/sevgi/geometry/element.rb', line 312 def draw(...) approx.send(:draw!, ...) end |
#eq?(other, precision: nil) ⇒ Boolean
Compares element points with optional numeric precision.
400 401 402 403 404 |
# File 'lib/sevgi/geometry/element.rb', line 400 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.
409 |
# File 'lib/sevgi/geometry/element.rb', line 409 def eql?(other) = other.instance_of?(self.class) && points == other.points |
#equations ⇒ Array<Sevgi::Geometry::Equation::Linear>
Returns immutable boundary equations for all lines.
421 |
# File 'lib/sevgi/geometry/element.rb', line 421 def equations = @equations ||= lines.map(&:equation).freeze |
#hash ⇒ Integer
Returns a hash compatible with strict equality.
413 |
# File 'lib/sevgi/geometry/element.rb', line 413 def hash = [self.class, *points].hash |
#head ⇒ Sevgi::Geometry::Segment
Returns the first segment.
466 |
# File 'lib/sevgi/geometry/element.rb', line 466 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.
502 503 504 505 506 507 508 |
# File 'lib/sevgi/geometry/element.rb', line 502 def inside?(point) point = Tuple[Point, point] return on?(point) unless self.class.send(:close?) on?(point) || pnpoly(points, point) end |
#intersection(equation, precision: nil) ⇒ Array<Sevgi::Geometry::Point>
Intersects the element boundary with an equation.
Precision is applied consistently to boundary-membership tolerance, returned-coordinate rounding, and duplicate collapse. A nil precision uses the current thread's function precision for all three stages.
436 437 438 439 440 441 442 443 444 |
# File 'lib/sevgi/geometry/element.rb', line 436 def intersection(equation, precision: nil) Error.("Must be an equation: #{equation}") unless equation.is_a?(Equation) points = equations.flat_map do |candidate| equation.intersect(candidate).select { |point| boundary_point?(point, precision) } end points.map { |point| point.approx(precision) }.uniq end |
#length ⇒ Float
Returns the total path length.
481 |
# File 'lib/sevgi/geometry/element.rb', line 481 def length = @length ||= segments.sum(&:length) |
#lines ⇒ Array<Sevgi::Geometry::Line>
Returns immutable boundary lines derived from segments and points.
470 471 472 473 474 475 476 477 |
# File 'lib/sevgi/geometry/element.rb', line 470 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.
514 515 516 517 518 |
# File 'lib/sevgi/geometry/element.rb', line 514 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.
524 |
# File 'lib/sevgi/geometry/element.rb', line 524 def outside?(point) = !inside?(point) |
#points(approximate = false) ⇒ Array<Sevgi::Geometry::Point>
Returns immutable element points.
319 320 321 |
# File 'lib/sevgi/geometry/element.rb', line 319 def points(approximate = false) approximate ? rounded_points(nil) : @points end |
#position ⇒ Sevgi::Geometry::Point
Returns the first point.
325 326 327 |
# File 'lib/sevgi/geometry/element.rb', line 325 def position @position ||= points.first end |
#reflect(x: true, y: true) ⇒ Sevgi::Geometry::Element::Lined
Returns an element reflected across the selected axes.
387 388 389 390 391 392 |
# File 'lib/sevgi/geometry/element.rb', line 387 Affinity.public_instance_methods(false).each do |transform| define_method(transform) do |*args, **kwargs, &block| transformed = points.map { it.public_send(transform, *args, **kwargs, &block) } self.class.send(:affine, *transformed) end end |
#rotate(a) ⇒ Sevgi::Geometry::Element::Lined
Returns an element rotated around the origin.
387 388 389 390 391 392 |
# File 'lib/sevgi/geometry/element.rb', line 387 Affinity.public_instance_methods(false).each do |transform| define_method(transform) do |*args, **kwargs, &block| transformed = points.map { it.public_send(transform, *args, **kwargs, &block) } self.class.send(:affine, *transformed) end end |
#scale(sx, sy = Undefined) ⇒ Sevgi::Geometry::Element::Lined
Returns an element scaled from the origin.
387 388 389 390 391 392 |
# File 'lib/sevgi/geometry/element.rb', line 387 Affinity.public_instance_methods(false).each do |transform| define_method(transform) do |*args, **kwargs, &block| transformed = points.map { it.public_send(transform, *args, **kwargs, &block) } self.class.send(:affine, *transformed) end end |
#segments(approximate = false) ⇒ Array<Sevgi::Geometry::Segment>
Returns immutable element segments.
332 333 334 |
# File 'lib/sevgi/geometry/element.rb', line 332 def segments(approximate = false) approximate ? rounded_segments(nil) : @segments end |
#skew(ax, ay = Undefined) ⇒ Sevgi::Geometry::Element::Lined
Returns an element skewed from the origin.
387 388 389 390 391 392 |
# File 'lib/sevgi/geometry/element.rb', line 387 Affinity.public_instance_methods(false).each do |transform| define_method(transform) do |*args, **kwargs, &block| transformed = points.map { it.public_send(transform, *args, **kwargs, &block) } self.class.send(:affine, *transformed) end end |
#skew_x(a) ⇒ Sevgi::Geometry::Element::Lined
Returns an element skewed along x.
387 388 389 390 391 392 |
# File 'lib/sevgi/geometry/element.rb', line 387 Affinity.public_instance_methods(false).each do |transform| define_method(transform) do |*args, **kwargs, &block| transformed = points.map { it.public_send(transform, *args, **kwargs, &block) } self.class.send(:affine, *transformed) end end |
#skew_y(a) ⇒ Sevgi::Geometry::Element::Lined
Returns an element skewed along y.
387 388 389 390 391 392 |
# File 'lib/sevgi/geometry/element.rb', line 387 Affinity.public_instance_methods(false).each do |transform| define_method(transform) do |*args, **kwargs, &block| transformed = points.map { it.public_send(transform, *args, **kwargs, &block) } self.class.send(:affine, *transformed) end end |
#tail ⇒ Sevgi::Geometry::Segment
Returns the last segment.
485 |
# File 'lib/sevgi/geometry/element.rb', line 485 def tail = @tail ||= segments.last |
#translate(dx, dy = Undefined) ⇒ Sevgi::Geometry::Element::Lined
Returns an element translated by offset.
387 388 389 390 391 392 |
# File 'lib/sevgi/geometry/element.rb', line 387 Affinity.public_instance_methods(false).each do |transform| define_method(transform) do |*args, **kwargs, &block| transformed = points.map { it.public_send(transform, *args, **kwargs, &block) } self.class.send(:affine, *transformed) end end |