Class: Sevgi::Geometry::Triangle

Inherits:
TriangleBase
  • Object
show all
Defined in:
lib/sevgi/geometry/elements/triangle.rb

Overview

Closed three-sided element built from non-collinear segments or points. Every construction path rejects degenerate triangles; affine operations retain Triangle when the transformed points remain non-degenerate.

Examples:

Pair mathematical notation with English conveniences

Sevgi::Geometry::Triangle[[2, 0], [2, 90]] == Sevgi::Geometry::Triangle.from_segments([2, 0], [2, 90])
Sevgi::Geometry::Triangle.([0, 0], [2, 0], [2, 2]) == Sevgi::Geometry::Triangle.from_points([0, 0], [2, 0], [2, 2])

Use named vertices and sides

triangle = Sevgi::Geometry::Triangle.([0, 0], [3, 0], [3, 4])
triangle.C.deconstruct # => [3.0, 4.0]
triangle.AB.length     # => 3.0
triangle.perimeter     # => 12.0

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#ASevgi::Geometry::Point (readonly)

Returns first vertex.

Returns:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/sevgi/geometry/elements/triangle.rb', line 52

class Triangle < TriangleBase
  # Builds a triangle from two adjacent segments.
  #
  # The closing segment is the direct vector from the end of `segment_b`
  # back to `position`. Segment order controls orientation; reversing the
  # inputs returns the corresponding opposite orientation. Zero-length or
  # collinear inputs are rejected using the current numeric precision.
  # @param segment_a [Sevgi::Geometry::Segment, Array<Numeric>] first segment
  # @param segment_b [Sevgi::Geometry::Segment, Array<Numeric>] second segment
  # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point
  # @return [Sevgi::Geometry::Triangle]
  # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced, or the segments are degenerate
  def self.[](segment_a, segment_b, position: Origin)
    a, b = Tuples[Segment, segment_a, segment_b]

    validate!(a, b)
    new_by_segments(a, b, closing_segment(a, b), position:)
  end

  class << self
    private

    def closing_segment(a, b)
      Segment.(b.ending(a.ending(Origin)), Origin)
    end

    def cross(a, b) = (a.x * b.y) - (a.y * b.x)

    def validate!(a, b)
      if F.zero?(a.length) ||
          F.zero?(b.length) ||
          F.zero?(cross(a, b))
        Error.("Triangle segments must form a non-degenerate triangle")
      end
    end
  end

  private

  def validate_geometry!
    self.class.send(:validate!, segments[0], segments[1])
  end
end

#ABSevgi::Geometry::Line (readonly)

Returns side from A to B.

Returns:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/sevgi/geometry/elements/triangle.rb', line 52

class Triangle < TriangleBase
  # Builds a triangle from two adjacent segments.
  #
  # The closing segment is the direct vector from the end of `segment_b`
  # back to `position`. Segment order controls orientation; reversing the
  # inputs returns the corresponding opposite orientation. Zero-length or
  # collinear inputs are rejected using the current numeric precision.
  # @param segment_a [Sevgi::Geometry::Segment, Array<Numeric>] first segment
  # @param segment_b [Sevgi::Geometry::Segment, Array<Numeric>] second segment
  # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point
  # @return [Sevgi::Geometry::Triangle]
  # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced, or the segments are degenerate
  def self.[](segment_a, segment_b, position: Origin)
    a, b = Tuples[Segment, segment_a, segment_b]

    validate!(a, b)
    new_by_segments(a, b, closing_segment(a, b), position:)
  end

  class << self
    private

    def closing_segment(a, b)
      Segment.(b.ending(a.ending(Origin)), Origin)
    end

    def cross(a, b) = (a.x * b.y) - (a.y * b.x)

    def validate!(a, b)
      if F.zero?(a.length) ||
          F.zero?(b.length) ||
          F.zero?(cross(a, b))
        Error.("Triangle segments must form a non-degenerate triangle")
      end
    end
  end

  private

  def validate_geometry!
    self.class.send(:validate!, segments[0], segments[1])
  end
end

#BSevgi::Geometry::Point (readonly)

Returns second vertex.

Returns:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/sevgi/geometry/elements/triangle.rb', line 52

class Triangle < TriangleBase
  # Builds a triangle from two adjacent segments.
  #
  # The closing segment is the direct vector from the end of `segment_b`
  # back to `position`. Segment order controls orientation; reversing the
  # inputs returns the corresponding opposite orientation. Zero-length or
  # collinear inputs are rejected using the current numeric precision.
  # @param segment_a [Sevgi::Geometry::Segment, Array<Numeric>] first segment
  # @param segment_b [Sevgi::Geometry::Segment, Array<Numeric>] second segment
  # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point
  # @return [Sevgi::Geometry::Triangle]
  # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced, or the segments are degenerate
  def self.[](segment_a, segment_b, position: Origin)
    a, b = Tuples[Segment, segment_a, segment_b]

    validate!(a, b)
    new_by_segments(a, b, closing_segment(a, b), position:)
  end

  class << self
    private

    def closing_segment(a, b)
      Segment.(b.ending(a.ending(Origin)), Origin)
    end

    def cross(a, b) = (a.x * b.y) - (a.y * b.x)

    def validate!(a, b)
      if F.zero?(a.length) ||
          F.zero?(b.length) ||
          F.zero?(cross(a, b))
        Error.("Triangle segments must form a non-degenerate triangle")
      end
    end
  end

  private

  def validate_geometry!
    self.class.send(:validate!, segments[0], segments[1])
  end
end

#BCSevgi::Geometry::Line (readonly)

Returns side from B to C.

Returns:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/sevgi/geometry/elements/triangle.rb', line 52

class Triangle < TriangleBase
  # Builds a triangle from two adjacent segments.
  #
  # The closing segment is the direct vector from the end of `segment_b`
  # back to `position`. Segment order controls orientation; reversing the
  # inputs returns the corresponding opposite orientation. Zero-length or
  # collinear inputs are rejected using the current numeric precision.
  # @param segment_a [Sevgi::Geometry::Segment, Array<Numeric>] first segment
  # @param segment_b [Sevgi::Geometry::Segment, Array<Numeric>] second segment
  # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point
  # @return [Sevgi::Geometry::Triangle]
  # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced, or the segments are degenerate
  def self.[](segment_a, segment_b, position: Origin)
    a, b = Tuples[Segment, segment_a, segment_b]

    validate!(a, b)
    new_by_segments(a, b, closing_segment(a, b), position:)
  end

  class << self
    private

    def closing_segment(a, b)
      Segment.(b.ending(a.ending(Origin)), Origin)
    end

    def cross(a, b) = (a.x * b.y) - (a.y * b.x)

    def validate!(a, b)
      if F.zero?(a.length) ||
          F.zero?(b.length) ||
          F.zero?(cross(a, b))
        Error.("Triangle segments must form a non-degenerate triangle")
      end
    end
  end

  private

  def validate_geometry!
    self.class.send(:validate!, segments[0], segments[1])
  end
end

#CSevgi::Geometry::Point (readonly)

Returns third vertex.

Returns:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/sevgi/geometry/elements/triangle.rb', line 52

class Triangle < TriangleBase
  # Builds a triangle from two adjacent segments.
  #
  # The closing segment is the direct vector from the end of `segment_b`
  # back to `position`. Segment order controls orientation; reversing the
  # inputs returns the corresponding opposite orientation. Zero-length or
  # collinear inputs are rejected using the current numeric precision.
  # @param segment_a [Sevgi::Geometry::Segment, Array<Numeric>] first segment
  # @param segment_b [Sevgi::Geometry::Segment, Array<Numeric>] second segment
  # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point
  # @return [Sevgi::Geometry::Triangle]
  # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced, or the segments are degenerate
  def self.[](segment_a, segment_b, position: Origin)
    a, b = Tuples[Segment, segment_a, segment_b]

    validate!(a, b)
    new_by_segments(a, b, closing_segment(a, b), position:)
  end

  class << self
    private

    def closing_segment(a, b)
      Segment.(b.ending(a.ending(Origin)), Origin)
    end

    def cross(a, b) = (a.x * b.y) - (a.y * b.x)

    def validate!(a, b)
      if F.zero?(a.length) ||
          F.zero?(b.length) ||
          F.zero?(cross(a, b))
        Error.("Triangle segments must form a non-degenerate triangle")
      end
    end
  end

  private

  def validate_geometry!
    self.class.send(:validate!, segments[0], segments[1])
  end
end

#CASevgi::Geometry::Line (readonly)

Returns side from C to A.

Returns:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/sevgi/geometry/elements/triangle.rb', line 52

class Triangle < TriangleBase
  # Builds a triangle from two adjacent segments.
  #
  # The closing segment is the direct vector from the end of `segment_b`
  # back to `position`. Segment order controls orientation; reversing the
  # inputs returns the corresponding opposite orientation. Zero-length or
  # collinear inputs are rejected using the current numeric precision.
  # @param segment_a [Sevgi::Geometry::Segment, Array<Numeric>] first segment
  # @param segment_b [Sevgi::Geometry::Segment, Array<Numeric>] second segment
  # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point
  # @return [Sevgi::Geometry::Triangle]
  # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced, or the segments are degenerate
  def self.[](segment_a, segment_b, position: Origin)
    a, b = Tuples[Segment, segment_a, segment_b]

    validate!(a, b)
    new_by_segments(a, b, closing_segment(a, b), position:)
  end

  class << self
    private

    def closing_segment(a, b)
      Segment.(b.ending(a.ending(Origin)), Origin)
    end

    def cross(a, b) = (a.x * b.y) - (a.y * b.x)

    def validate!(a, b)
      if F.zero?(a.length) ||
          F.zero?(b.length) ||
          F.zero?(cross(a, b))
        Error.("Triangle segments must form a non-degenerate triangle")
      end
    end
  end

  private

  def validate_geometry!
    self.class.send(:validate!, segments[0], segments[1])
  end
end

Class Method Details

.[](segment_a, segment_b, position: Origin) ⇒ Sevgi::Geometry::Triangle

Builds a triangle from two adjacent segments.

The closing segment is the direct vector from the end of segment_b back to position. Segment order controls orientation; reversing the inputs returns the corresponding opposite orientation. Zero-length or collinear inputs are rejected using the current numeric precision.

Parameters:

Returns:

Raises:



64
65
66
67
68
69
# File 'lib/sevgi/geometry/elements/triangle.rb', line 64

def self.[](segment_a, segment_b, position: Origin)
  a, b = Tuples[Segment, segment_a, segment_b]

  validate!(a, b)
  new_by_segments(a, b, closing_segment(a, b), position:)
end

.call(*points) ⇒ Sevgi::Geometry::Triangle

Builds a triangle from three boundary points.

Parameters:

Returns:

Raises:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/sevgi/geometry/elements/triangle.rb', line 52

class Triangle < TriangleBase
  # Builds a triangle from two adjacent segments.
  #
  # The closing segment is the direct vector from the end of `segment_b`
  # back to `position`. Segment order controls orientation; reversing the
  # inputs returns the corresponding opposite orientation. Zero-length or
  # collinear inputs are rejected using the current numeric precision.
  # @param segment_a [Sevgi::Geometry::Segment, Array<Numeric>] first segment
  # @param segment_b [Sevgi::Geometry::Segment, Array<Numeric>] second segment
  # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point
  # @return [Sevgi::Geometry::Triangle]
  # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced, or the segments are degenerate
  def self.[](segment_a, segment_b, position: Origin)
    a, b = Tuples[Segment, segment_a, segment_b]

    validate!(a, b)
    new_by_segments(a, b, closing_segment(a, b), position:)
  end

  class << self
    private

    def closing_segment(a, b)
      Segment.(b.ending(a.ending(Origin)), Origin)
    end

    def cross(a, b) = (a.x * b.y) - (a.y * b.x)

    def validate!(a, b)
      if F.zero?(a.length) ||
          F.zero?(b.length) ||
          F.zero?(cross(a, b))
        Error.("Triangle segments must form a non-degenerate triangle")
      end
    end
  end

  private

  def validate_geometry!
    self.class.send(:validate!, segments[0], segments[1])
  end
end

.from_points(*points) ⇒ Sevgi::Geometry::Triangle

Builds a triangle from three boundary points.

Parameters:

Returns:

Raises:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/sevgi/geometry/elements/triangle.rb', line 52

class Triangle < TriangleBase
  # Builds a triangle from two adjacent segments.
  #
  # The closing segment is the direct vector from the end of `segment_b`
  # back to `position`. Segment order controls orientation; reversing the
  # inputs returns the corresponding opposite orientation. Zero-length or
  # collinear inputs are rejected using the current numeric precision.
  # @param segment_a [Sevgi::Geometry::Segment, Array<Numeric>] first segment
  # @param segment_b [Sevgi::Geometry::Segment, Array<Numeric>] second segment
  # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point
  # @return [Sevgi::Geometry::Triangle]
  # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced, or the segments are degenerate
  def self.[](segment_a, segment_b, position: Origin)
    a, b = Tuples[Segment, segment_a, segment_b]

    validate!(a, b)
    new_by_segments(a, b, closing_segment(a, b), position:)
  end

  class << self
    private

    def closing_segment(a, b)
      Segment.(b.ending(a.ending(Origin)), Origin)
    end

    def cross(a, b) = (a.x * b.y) - (a.y * b.x)

    def validate!(a, b)
      if F.zero?(a.length) ||
          F.zero?(b.length) ||
          F.zero?(cross(a, b))
        Error.("Triangle segments must form a non-degenerate triangle")
      end
    end
  end

  private

  def validate_geometry!
    self.class.send(:validate!, segments[0], segments[1])
  end
end

.from_segments(segment_a, segment_b, position: Origin) ⇒ Sevgi::Geometry::Triangle

Builds a triangle from two adjacent segments and derives the closing side.

Parameters:

Returns:

Raises:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/sevgi/geometry/elements/triangle.rb', line 52

class Triangle < TriangleBase
  # Builds a triangle from two adjacent segments.
  #
  # The closing segment is the direct vector from the end of `segment_b`
  # back to `position`. Segment order controls orientation; reversing the
  # inputs returns the corresponding opposite orientation. Zero-length or
  # collinear inputs are rejected using the current numeric precision.
  # @param segment_a [Sevgi::Geometry::Segment, Array<Numeric>] first segment
  # @param segment_b [Sevgi::Geometry::Segment, Array<Numeric>] second segment
  # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point
  # @return [Sevgi::Geometry::Triangle]
  # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced, or the segments are degenerate
  def self.[](segment_a, segment_b, position: Origin)
    a, b = Tuples[Segment, segment_a, segment_b]

    validate!(a, b)
    new_by_segments(a, b, closing_segment(a, b), position:)
  end

  class << self
    private

    def closing_segment(a, b)
      Segment.(b.ending(a.ending(Origin)), Origin)
    end

    def cross(a, b) = (a.x * b.y) - (a.y * b.x)

    def validate!(a, b)
      if F.zero?(a.length) ||
          F.zero?(b.length) ||
          F.zero?(cross(a, b))
        Error.("Triangle segments must form a non-degenerate triangle")
      end
    end
  end

  private

  def validate_geometry!
    self.class.send(:validate!, segments[0], segments[1])
  end
end

Instance Method Details

#perimeterFloat

Returns the closed path perimeter.

Returns:

  • (Float)


52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/sevgi/geometry/elements/triangle.rb', line 52

class Triangle < TriangleBase
  # Builds a triangle from two adjacent segments.
  #
  # The closing segment is the direct vector from the end of `segment_b`
  # back to `position`. Segment order controls orientation; reversing the
  # inputs returns the corresponding opposite orientation. Zero-length or
  # collinear inputs are rejected using the current numeric precision.
  # @param segment_a [Sevgi::Geometry::Segment, Array<Numeric>] first segment
  # @param segment_b [Sevgi::Geometry::Segment, Array<Numeric>] second segment
  # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point
  # @return [Sevgi::Geometry::Triangle]
  # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced, or the segments are degenerate
  def self.[](segment_a, segment_b, position: Origin)
    a, b = Tuples[Segment, segment_a, segment_b]

    validate!(a, b)
    new_by_segments(a, b, closing_segment(a, b), position:)
  end

  class << self
    private

    def closing_segment(a, b)
      Segment.(b.ending(a.ending(Origin)), Origin)
    end

    def cross(a, b) = (a.x * b.y) - (a.y * b.x)

    def validate!(a, b)
      if F.zero?(a.length) ||
          F.zero?(b.length) ||
          F.zero?(cross(a, b))
        Error.("Triangle segments must form a non-degenerate triangle")
      end
    end
  end

  private

  def validate_geometry!
    self.class.send(:validate!, segments[0], segments[1])
  end
end