Class: Sevgi::Geometry::Parallelogram
- Inherits:
-
ParallelogramBase
- Object
- Sevgi::Geometry::Parallelogram
- Defined in:
- lib/sevgi/geometry/elements/parallelogram.rb
Overview
Closed four-sided element whose opposite sides are equal and parallel. Every construction path rejects degenerate or unrelated side pairs; affine operations preserve the class while that invariant holds.
Instance Attribute Summary collapse
-
#A ⇒ Sevgi::Geometry::Point
readonly
First vertex.
-
#AB ⇒ Sevgi::Geometry::Line
readonly
Side from A to B.
-
#B ⇒ Sevgi::Geometry::Point
readonly
Second vertex.
-
#BC ⇒ Sevgi::Geometry::Line
readonly
Side from B to C.
-
#C ⇒ Sevgi::Geometry::Point
readonly
Third vertex.
-
#CD ⇒ Sevgi::Geometry::Line
readonly
Side from C to D.
-
#D ⇒ Sevgi::Geometry::Point
readonly
Fourth vertex.
-
#DA ⇒ Sevgi::Geometry::Line
readonly
Side from D to A.
Class Method Summary collapse
-
.[](base, side, position: Origin) ⇒ Sevgi::Geometry::Parallelogram
Builds a parallelogram from adjacent base and side segments.
-
.call(*points) ⇒ Sevgi::Geometry::Parallelogram
Builds a parallelogram from four boundary points.
-
.from_points(*points) ⇒ Sevgi::Geometry::Parallelogram
Builds a parallelogram from four boundary points.
-
.from_segments(base, side, position: Origin) ⇒ Sevgi::Geometry::Parallelogram
Builds a parallelogram from two adjacent segments and derives their opposites.
-
.new_by_height(base:, constraint:, position: Origin) ⇒ Sevgi::Geometry::Parallelogram
Builds a parallelogram from a base and bounding-height constraint.
-
.new_by_width(side:, constraint:, position: Origin) ⇒ Sevgi::Geometry::Parallelogram
Builds a parallelogram from a side and bounding-width constraint.
Instance Method Summary collapse
-
#perimeter ⇒ Float
Returns the closed path perimeter.
Instance Attribute Details
#A ⇒ Sevgi::Geometry::Point (readonly)
Returns first vertex.
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 58 class Parallelogram < ParallelogramBase # Builds a parallelogram from adjacent base and side segments. Both segments originate at `position`; `base` # defines AB and `side` defines AD, regardless of their angles. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end # Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; # its signed angle is retained as the direction of the derived side while the component magnitude determines that # side's non-negative length. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target height and side direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the height constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_height(base: [4, 0], constraint: [3, -90]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 3, angle: 90) # base = Sevgi::Geometry::Segment[4, 0] # Sevgi::Geometry::Parallelogram.new_by_height(base:, constraint:) def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end # Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its # signed angle is retained as the direction of the derived base while the component magnitude determines that # base's non-negative length. # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target width and base direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the width constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_width(side: [3, 90], constraint: [4, 180]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 4, angle: 0) # side = Sevgi::Geometry::Segment[3, 90] # Sevgi::Geometry::Parallelogram.new_by_width(side:, constraint:) def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end private def validate_geometry! a, b, c, d = segments valid = opposite?(a, c) && opposite?(b, d) && !F.zero?(cross(a, b)) Error.("Parallelogram sides must be non-degenerate opposite pairs") unless valid end def cross(a, b) = (a.x * b.y) - (a.y * b.x) def opposite?(a, b) = F.zero?(a.x + b.x) && F.zero?(a.y + b.y) end |
#AB ⇒ Sevgi::Geometry::Line (readonly)
Returns side from A to B.
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 58 class Parallelogram < ParallelogramBase # Builds a parallelogram from adjacent base and side segments. Both segments originate at `position`; `base` # defines AB and `side` defines AD, regardless of their angles. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end # Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; # its signed angle is retained as the direction of the derived side while the component magnitude determines that # side's non-negative length. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target height and side direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the height constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_height(base: [4, 0], constraint: [3, -90]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 3, angle: 90) # base = Sevgi::Geometry::Segment[4, 0] # Sevgi::Geometry::Parallelogram.new_by_height(base:, constraint:) def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end # Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its # signed angle is retained as the direction of the derived base while the component magnitude determines that # base's non-negative length. # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target width and base direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the width constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_width(side: [3, 90], constraint: [4, 180]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 4, angle: 0) # side = Sevgi::Geometry::Segment[3, 90] # Sevgi::Geometry::Parallelogram.new_by_width(side:, constraint:) def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end private def validate_geometry! a, b, c, d = segments valid = opposite?(a, c) && opposite?(b, d) && !F.zero?(cross(a, b)) Error.("Parallelogram sides must be non-degenerate opposite pairs") unless valid end def cross(a, b) = (a.x * b.y) - (a.y * b.x) def opposite?(a, b) = F.zero?(a.x + b.x) && F.zero?(a.y + b.y) end |
#B ⇒ Sevgi::Geometry::Point (readonly)
Returns second vertex.
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 58 class Parallelogram < ParallelogramBase # Builds a parallelogram from adjacent base and side segments. Both segments originate at `position`; `base` # defines AB and `side` defines AD, regardless of their angles. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end # Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; # its signed angle is retained as the direction of the derived side while the component magnitude determines that # side's non-negative length. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target height and side direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the height constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_height(base: [4, 0], constraint: [3, -90]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 3, angle: 90) # base = Sevgi::Geometry::Segment[4, 0] # Sevgi::Geometry::Parallelogram.new_by_height(base:, constraint:) def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end # Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its # signed angle is retained as the direction of the derived base while the component magnitude determines that # base's non-negative length. # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target width and base direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the width constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_width(side: [3, 90], constraint: [4, 180]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 4, angle: 0) # side = Sevgi::Geometry::Segment[3, 90] # Sevgi::Geometry::Parallelogram.new_by_width(side:, constraint:) def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end private def validate_geometry! a, b, c, d = segments valid = opposite?(a, c) && opposite?(b, d) && !F.zero?(cross(a, b)) Error.("Parallelogram sides must be non-degenerate opposite pairs") unless valid end def cross(a, b) = (a.x * b.y) - (a.y * b.x) def opposite?(a, b) = F.zero?(a.x + b.x) && F.zero?(a.y + b.y) end |
#BC ⇒ Sevgi::Geometry::Line (readonly)
Returns side from B to C.
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 58 class Parallelogram < ParallelogramBase # Builds a parallelogram from adjacent base and side segments. Both segments originate at `position`; `base` # defines AB and `side` defines AD, regardless of their angles. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end # Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; # its signed angle is retained as the direction of the derived side while the component magnitude determines that # side's non-negative length. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target height and side direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the height constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_height(base: [4, 0], constraint: [3, -90]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 3, angle: 90) # base = Sevgi::Geometry::Segment[4, 0] # Sevgi::Geometry::Parallelogram.new_by_height(base:, constraint:) def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end # Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its # signed angle is retained as the direction of the derived base while the component magnitude determines that # base's non-negative length. # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target width and base direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the width constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_width(side: [3, 90], constraint: [4, 180]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 4, angle: 0) # side = Sevgi::Geometry::Segment[3, 90] # Sevgi::Geometry::Parallelogram.new_by_width(side:, constraint:) def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end private def validate_geometry! a, b, c, d = segments valid = opposite?(a, c) && opposite?(b, d) && !F.zero?(cross(a, b)) Error.("Parallelogram sides must be non-degenerate opposite pairs") unless valid end def cross(a, b) = (a.x * b.y) - (a.y * b.x) def opposite?(a, b) = F.zero?(a.x + b.x) && F.zero?(a.y + b.y) end |
#C ⇒ Sevgi::Geometry::Point (readonly)
Returns third vertex.
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 58 class Parallelogram < ParallelogramBase # Builds a parallelogram from adjacent base and side segments. Both segments originate at `position`; `base` # defines AB and `side` defines AD, regardless of their angles. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end # Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; # its signed angle is retained as the direction of the derived side while the component magnitude determines that # side's non-negative length. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target height and side direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the height constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_height(base: [4, 0], constraint: [3, -90]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 3, angle: 90) # base = Sevgi::Geometry::Segment[4, 0] # Sevgi::Geometry::Parallelogram.new_by_height(base:, constraint:) def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end # Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its # signed angle is retained as the direction of the derived base while the component magnitude determines that # base's non-negative length. # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target width and base direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the width constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_width(side: [3, 90], constraint: [4, 180]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 4, angle: 0) # side = Sevgi::Geometry::Segment[3, 90] # Sevgi::Geometry::Parallelogram.new_by_width(side:, constraint:) def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end private def validate_geometry! a, b, c, d = segments valid = opposite?(a, c) && opposite?(b, d) && !F.zero?(cross(a, b)) Error.("Parallelogram sides must be non-degenerate opposite pairs") unless valid end def cross(a, b) = (a.x * b.y) - (a.y * b.x) def opposite?(a, b) = F.zero?(a.x + b.x) && F.zero?(a.y + b.y) end |
#CD ⇒ Sevgi::Geometry::Line (readonly)
Returns side from C to D.
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 58 class Parallelogram < ParallelogramBase # Builds a parallelogram from adjacent base and side segments. Both segments originate at `position`; `base` # defines AB and `side` defines AD, regardless of their angles. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end # Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; # its signed angle is retained as the direction of the derived side while the component magnitude determines that # side's non-negative length. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target height and side direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the height constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_height(base: [4, 0], constraint: [3, -90]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 3, angle: 90) # base = Sevgi::Geometry::Segment[4, 0] # Sevgi::Geometry::Parallelogram.new_by_height(base:, constraint:) def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end # Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its # signed angle is retained as the direction of the derived base while the component magnitude determines that # base's non-negative length. # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target width and base direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the width constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_width(side: [3, 90], constraint: [4, 180]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 4, angle: 0) # side = Sevgi::Geometry::Segment[3, 90] # Sevgi::Geometry::Parallelogram.new_by_width(side:, constraint:) def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end private def validate_geometry! a, b, c, d = segments valid = opposite?(a, c) && opposite?(b, d) && !F.zero?(cross(a, b)) Error.("Parallelogram sides must be non-degenerate opposite pairs") unless valid end def cross(a, b) = (a.x * b.y) - (a.y * b.x) def opposite?(a, b) = F.zero?(a.x + b.x) && F.zero?(a.y + b.y) end |
#D ⇒ Sevgi::Geometry::Point (readonly)
Returns fourth vertex.
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 58 class Parallelogram < ParallelogramBase # Builds a parallelogram from adjacent base and side segments. Both segments originate at `position`; `base` # defines AB and `side` defines AD, regardless of their angles. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end # Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; # its signed angle is retained as the direction of the derived side while the component magnitude determines that # side's non-negative length. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target height and side direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the height constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_height(base: [4, 0], constraint: [3, -90]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 3, angle: 90) # base = Sevgi::Geometry::Segment[4, 0] # Sevgi::Geometry::Parallelogram.new_by_height(base:, constraint:) def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end # Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its # signed angle is retained as the direction of the derived base while the component magnitude determines that # base's non-negative length. # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target width and base direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the width constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_width(side: [3, 90], constraint: [4, 180]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 4, angle: 0) # side = Sevgi::Geometry::Segment[3, 90] # Sevgi::Geometry::Parallelogram.new_by_width(side:, constraint:) def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end private def validate_geometry! a, b, c, d = segments valid = opposite?(a, c) && opposite?(b, d) && !F.zero?(cross(a, b)) Error.("Parallelogram sides must be non-degenerate opposite pairs") unless valid end def cross(a, b) = (a.x * b.y) - (a.y * b.x) def opposite?(a, b) = F.zero?(a.x + b.x) && F.zero?(a.y + b.y) end |
#DA ⇒ Sevgi::Geometry::Line (readonly)
Returns side from D to A.
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 58 class Parallelogram < ParallelogramBase # Builds a parallelogram from adjacent base and side segments. Both segments originate at `position`; `base` # defines AB and `side` defines AD, regardless of their angles. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end # Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; # its signed angle is retained as the direction of the derived side while the component magnitude determines that # side's non-negative length. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target height and side direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the height constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_height(base: [4, 0], constraint: [3, -90]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 3, angle: 90) # base = Sevgi::Geometry::Segment[4, 0] # Sevgi::Geometry::Parallelogram.new_by_height(base:, constraint:) def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end # Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its # signed angle is retained as the direction of the derived base while the component magnitude determines that # base's non-negative length. # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target width and base direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the width constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_width(side: [3, 90], constraint: [4, 180]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 4, angle: 0) # side = Sevgi::Geometry::Segment[3, 90] # Sevgi::Geometry::Parallelogram.new_by_width(side:, constraint:) def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end private def validate_geometry! a, b, c, d = segments valid = opposite?(a, c) && opposite?(b, d) && !F.zero?(cross(a, b)) Error.("Parallelogram sides must be non-degenerate opposite pairs") unless valid end def cross(a, b) = (a.x * b.y) - (a.y * b.x) def opposite?(a, b) = F.zero?(a.x + b.x) && F.zero?(a.y + b.y) end |
Class Method Details
.[](base, side, position: Origin) ⇒ Sevgi::Geometry::Parallelogram
Builds a parallelogram from adjacent base and side segments. Both segments originate at position; base
defines AB and side defines AD, regardless of their angles.
66 67 68 69 70 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 66 def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end |
.call(*points) ⇒ Sevgi::Geometry::Parallelogram
Builds a parallelogram from four boundary points.
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 58 class Parallelogram < ParallelogramBase # Builds a parallelogram from adjacent base and side segments. Both segments originate at `position`; `base` # defines AB and `side` defines AD, regardless of their angles. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end # Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; # its signed angle is retained as the direction of the derived side while the component magnitude determines that # side's non-negative length. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target height and side direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the height constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_height(base: [4, 0], constraint: [3, -90]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 3, angle: 90) # base = Sevgi::Geometry::Segment[4, 0] # Sevgi::Geometry::Parallelogram.new_by_height(base:, constraint:) def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end # Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its # signed angle is retained as the direction of the derived base while the component magnitude determines that # base's non-negative length. # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target width and base direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the width constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_width(side: [3, 90], constraint: [4, 180]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 4, angle: 0) # side = Sevgi::Geometry::Segment[3, 90] # Sevgi::Geometry::Parallelogram.new_by_width(side:, constraint:) def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end private def validate_geometry! a, b, c, d = segments valid = opposite?(a, c) && opposite?(b, d) && !F.zero?(cross(a, b)) Error.("Parallelogram sides must be non-degenerate opposite pairs") unless valid end def cross(a, b) = (a.x * b.y) - (a.y * b.x) def opposite?(a, b) = F.zero?(a.x + b.x) && F.zero?(a.y + b.y) end |
.from_points(*points) ⇒ Sevgi::Geometry::Parallelogram
Builds a parallelogram from four boundary points.
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 58 class Parallelogram < ParallelogramBase # Builds a parallelogram from adjacent base and side segments. Both segments originate at `position`; `base` # defines AB and `side` defines AD, regardless of their angles. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end # Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; # its signed angle is retained as the direction of the derived side while the component magnitude determines that # side's non-negative length. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target height and side direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the height constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_height(base: [4, 0], constraint: [3, -90]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 3, angle: 90) # base = Sevgi::Geometry::Segment[4, 0] # Sevgi::Geometry::Parallelogram.new_by_height(base:, constraint:) def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end # Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its # signed angle is retained as the direction of the derived base while the component magnitude determines that # base's non-negative length. # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target width and base direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the width constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_width(side: [3, 90], constraint: [4, 180]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 4, angle: 0) # side = Sevgi::Geometry::Segment[3, 90] # Sevgi::Geometry::Parallelogram.new_by_width(side:, constraint:) def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end private def validate_geometry! a, b, c, d = segments valid = opposite?(a, c) && opposite?(b, d) && !F.zero?(cross(a, b)) Error.("Parallelogram sides must be non-degenerate opposite pairs") unless valid end def cross(a, b) = (a.x * b.y) - (a.y * b.x) def opposite?(a, b) = F.zero?(a.x + b.x) && F.zero?(a.y + b.y) end |
.from_segments(base, side, position: Origin) ⇒ Sevgi::Geometry::Parallelogram
Builds a parallelogram from two adjacent segments and derives their opposites.
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 58 class Parallelogram < ParallelogramBase # Builds a parallelogram from adjacent base and side segments. Both segments originate at `position`; `base` # defines AB and `side` defines AD, regardless of their angles. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end # Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; # its signed angle is retained as the direction of the derived side while the component magnitude determines that # side's non-negative length. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target height and side direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the height constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_height(base: [4, 0], constraint: [3, -90]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 3, angle: 90) # base = Sevgi::Geometry::Segment[4, 0] # Sevgi::Geometry::Parallelogram.new_by_height(base:, constraint:) def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end # Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its # signed angle is retained as the direction of the derived base while the component magnitude determines that # base's non-negative length. # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target width and base direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the width constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_width(side: [3, 90], constraint: [4, 180]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 4, angle: 0) # side = Sevgi::Geometry::Segment[3, 90] # Sevgi::Geometry::Parallelogram.new_by_width(side:, constraint:) def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end private def validate_geometry! a, b, c, d = segments valid = opposite?(a, c) && opposite?(b, d) && !F.zero?(cross(a, b)) Error.("Parallelogram sides must be non-degenerate opposite pairs") unless valid end def cross(a, b) = (a.x * b.y) - (a.y * b.x) def opposite?(a, b) = F.zero?(a.x + b.x) && F.zero?(a.y + b.y) end |
.new_by_height(base:, constraint:, position: Origin) ⇒ Sevgi::Geometry::Parallelogram
Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; its signed angle is retained as the direction of the derived side while the component magnitude determines that side's non-negative length.
86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 86 def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end |
.new_by_width(side:, constraint:, position: Origin) ⇒ Sevgi::Geometry::Parallelogram
Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its signed angle is retained as the direction of the derived base while the component magnitude determines that base's non-negative length.
113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 113 def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end |
Instance Method Details
#perimeter ⇒ Float
Returns the closed path perimeter.
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/sevgi/geometry/elements/parallelogram.rb', line 58 class Parallelogram < ParallelogramBase # Builds a parallelogram from adjacent base and side segments. Both segments originate at `position`; `base` # defines AB and `side` defines AD, regardless of their angles. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when segments or position cannot be coerced def self.[](base, side, position: Origin) base, side = Tuples[Segment, base, side] new_by_segments(base, side.reverse, base.reverse, side, position:) end # Builds a parallelogram from a base and bounding-height constraint. The constraint length is the target height; # its signed angle is retained as the direction of the derived side while the component magnitude determines that # side's non-negative length. # @param base [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to B # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target height and side direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the height constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_height(base: [4, 0], constraint: [3, -90]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 3, angle: 90) # base = Sevgi::Geometry::Segment[4, 0] # Sevgi::Geometry::Parallelogram.new_by_height(base:, constraint:) def self.new_by_height(base:, constraint:, position: Origin) base = Tuple[Segment, base] constraint = Tuple[LengthAngle, constraint] height = constraint.length - base.y.abs angle = constraint.angle sine = F.sin(angle) Error.("Parallelogram height is smaller than its base span") if height.negative? Error.("Parallelogram height constraint must have a vertical component") if F.zero?(sine) self[base, Segment[height / sine.abs, angle], position:] end # Builds a parallelogram from a side and bounding-width constraint. The constraint length is the target width; its # signed angle is retained as the direction of the derived base while the component magnitude determines that # base's non-negative length. # @param side [Sevgi::Geometry::Segment, Array<Numeric>] segment from A to D # @param constraint [Sevgi::Geometry::LengthAngle, Array<Numeric>] target width and base direction # @param position [Sevgi::Geometry::Point, Array<Numeric>] starting point # @return [Sevgi::Geometry::Parallelogram] # @raise [Sevgi::Geometry::Error] when inputs cannot be coerced or the width constraint is infeasible # @example Use an array constraint # Sevgi::Geometry::Parallelogram.new_by_width(side: [3, 90], constraint: [4, 180]) # @example Use a LengthAngle constraint # constraint = Sevgi::Geometry::LengthAngle.new(length: 4, angle: 0) # side = Sevgi::Geometry::Segment[3, 90] # Sevgi::Geometry::Parallelogram.new_by_width(side:, constraint:) def self.new_by_width(side:, constraint:, position: Origin) side = Tuple[Segment, side] constraint = Tuple[LengthAngle, constraint] width = constraint.length - side.x.abs angle = constraint.angle cosine = F.cos(angle) Error.("Parallelogram width is smaller than its side span") if width.negative? Error.("Parallelogram width constraint must have a horizontal component") if F.zero?(cosine) self[Segment[width / cosine.abs, angle], side, position:] end private def validate_geometry! a, b, c, d = segments valid = opposite?(a, c) && opposite?(b, d) && !F.zero?(cross(a, b)) Error.("Parallelogram sides must be non-degenerate opposite pairs") unless valid end def cross(a, b) = (a.x * b.y) - (a.y * b.x) def opposite?(a, b) = F.zero?(a.x + b.x) && F.zero?(a.y + b.y) end |