Class: PerfectShape::Line
- Includes:
- MultiPoint
- Defined in:
- lib/perfect_shape/line.rb
Overview
Mostly ported from java.awt.geom: docs.oracle.com/javase/8/docs/api/java/awt/geom/Line2D.html
Instance Attribute Summary
Attributes included from MultiPoint
Class Method Summary collapse
-
.point_crossings(x1, y1, x2, y2, px, py) ⇒ Object
Calculates the number of times the line from (x1,y1) to (x2,y2) crosses the ray extending to the right from (px,py).
-
.point_distance(x1, y1, x2, y2, px, py) ⇒ Object
Returns the distance from a point to a line segment.
-
.point_distance_square(x1, y1, x2, y2, px, py) ⇒ Object
Returns the square of the distance from a point to a line segment.
-
.relative_counterclockwise(x1, y1, x2, y2, px, py) ⇒ Object
Returns an indicator of where the specified point (px,py) lies with respect to the line segment from (x1,y1) to (x2,y2).
Instance Method Summary collapse
-
#contain?(x_or_point, y = nil, outline: true, distance_tolerance: 0) ⇒ @code true
Checks if line contains point (two-number Array or x, y args), with distance tolerance (0 by default).
-
#point_crossings(x_or_point, y = nil) ⇒ Object
Calculates the number of times the line crosses the ray extending to the right from (px,py).
- #point_distance(x_or_point, y = nil) ⇒ Object
- #relative_counterclockwise(x_or_point, y = nil) ⇒ Object
Methods included from MultiPoint
#initialize, #max_x, #max_y, #min_x, #min_y
Methods inherited from Shape
#==, #bounding_box, #center_point, #center_x, #center_y, #height, #max_x, #max_y, #min_x, #min_y, #normalize_point, #width
Class Method Details
.point_crossings(x1, y1, x2, y2, px, py) ⇒ Object
Calculates the number of times the line from (x1,y1) to (x2,y2) crosses the ray extending to the right from (px,py). If the point lies on the line, then no crossings are recorded. +1 is returned for a crossing where the Y coordinate is increasing -1 is returned for a crossing where the Y coordinate is decreasing
191 192 193 194 195 196 197 198 199 200 |
# File 'lib/perfect_shape/line.rb', line 191 def point_crossings(x1, y1, x2, y2, px, py) return 0 if (py < y1 && py < y2) return 0 if (py >= y1 && py >= y2) # assert(y1 != y2); return 0 if (px >= x1 && px >= x2) return ((y1 < y2) ? 1 : -1) if (px < x1 && px < x2) xintercept = x1 + (py - y1) * (x2 - x1) / (y2 - y1); return 0 if (px >= xintercept) (y1 < y2) ? 1 : -1 end |
.point_distance(x1, y1, x2, y2, px, py) ⇒ Object
Returns the distance from a point to a line segment. The distance measured is the distance between the specified point and the closest point between the specified end points. If the specified point intersects the line segment in between the end points, this method returns 0.0.
180 181 182 183 184 |
# File 'lib/perfect_shape/line.rb', line 180 def point_distance(x1, y1, x2, y2, px, py) BigDecimal(::Math.sqrt(point_distance_square(x1, y1, x2, y2, px, py)).to_s) end |
.point_distance_square(x1, y1, x2, y2, px, py) ⇒ Object
Returns the square of the distance from a point to a line segment. The distance measured is the distance between the specified point and the closest point between the specified end points. If the specified point intersects the line segment in between the end points, this method returns 0.0.
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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/perfect_shape/line.rb', line 107 def point_distance_square(x1, y1, x2, y2, px, py) x1 = BigDecimal(x1.to_s) y1 = BigDecimal(y1.to_s) x2 = BigDecimal(x2.to_s) y2 = BigDecimal(y2.to_s) px = BigDecimal(px.to_s) py = BigDecimal(py.to_s) # Adjust vectors relative to x1,y1 # x2,y2 becomes relative vector from x1,y1 to end of segment x2 -= x1 y2 -= y1 # px,py becomes relative vector from x1,y1 to test point px -= x1 py -= y1 dot_product = px * x2 + py * y2; if dot_product <= 0.0 # px,py is on the side of x1,y1 away from x2,y2 # distance to segment is length of px,py vector # "length of its (clipped) projection" is now 0.0 projected_length_square = BigDecimal('0.0'); else # switch to backwards vectors relative to x2,y2 # x2,y2 are already the negative of x1,y1=>x2,y2 # to get px,py to be the negative of px,py=>x2,y2 # the dot product of two negated vectors is the same # as the dot product of the two normal vectors px = x2 - px py = y2 - py dot_product = px * x2 + py * y2 if dot_product <= 0.0 # px,py is on the side of x2,y2 away from x1,y1 # distance to segment is length of (backwards) px,py vector # "length of its (clipped) projection" is now 0.0 projected_length_square = BigDecimal('0.0') else # px,py is between x1,y1 and x2,y2 # dot_product is the length of the px,py vector # projected on the x2,y2=>x1,y1 vector times the # length of the x2,y2=>x1,y1 vector projected_length_square = dot_product * dot_product / (x2 * x2 + y2 * y2) end end # Distance to line is now the length of the relative point # vector minus the length of its projection onto the line # (which is zero if the projection falls outside the range # of the line segment). length_square = px * px + py * py - projected_length_square length_square = BigDecimal('0.0') if length_square < 0 length_square end |
.relative_counterclockwise(x1, y1, x2, y2, px, py) ⇒ Object
Returns an indicator of where the specified point (px,py) lies with respect to the line segment from (x1,y1) to (x2,y2).
The return value can be either 1, -1, or 0 and indicates in which direction the specified line must pivot around its first end point, (x1,y1), in order to point at the specified point (px,py). A return value of 1 indicates that the line segment must turn in the direction that takes the positive X axis towards the negative Y axis. In the default coordinate system used by Java 2D, this direction is counterclockwise.
A return value of -1 indicates that the line segment must turn in the direction that takes the positive X axis towards the positive Y axis. In the default coordinate system, this direction is clockwise.
A return value of 0 indicates that the point lies exactly on the line segment. Note that an indicator value of 0 is rare and not useful for determining collinearity because of floating point rounding issues.
If the point is colinear with the line segment, but not between the end points, then the value will be -1 if the point lies “beyond (x1,y1)” or 1 if the point lies “beyond (x2,y2)”.
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 |
# File 'lib/perfect_shape/line.rb', line 56 def relative_counterclockwise(x1, y1, x2, y2, px, py) x2 -= x1; y2 -= y1; px -= x1; py -= y1; ccw = px * y2 - py * x2; if ccw == 0.0 # The point is colinear, classify based on which side of # the segment the point falls on. We can calculate a # relative value using the projection of px,py onto the # segment - a negative value indicates the point projects # outside of the segment in the direction of the particular # endpoint used as the origin for the projection. ccw = px * x2 + py * y2; if ccw > 0.0 # Reverse the projection to be relative to the original x2,y2 # x2 and y2 are simply negated. # px and py need to have (x2 - x1) or (y2 - y1) subtracted # from them (based on the original values) # Since we really want to get a positive answer when the # point is "beyond (x2,y2)", then we want to calculate # the inverse anyway - thus we leave x2 & y2 negated. px -= x2; py -= y2; ccw = px * x2 + py * y2; ccw = 0.0 if ccw < 0.0 end end (ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0); end |
Instance Method Details
#contain?(x_or_point, y = nil, outline: true, distance_tolerance: 0) ⇒ @code true
Checks if line contains point (two-number Array or x, y args), with distance tolerance (0 by default)
the line, false if the point lies outside of the line’s bounds.
215 216 217 218 219 220 |
# File 'lib/perfect_shape/line.rb', line 215 def contain?(x_or_point, y = nil, outline: true, distance_tolerance: 0) x, y = normalize_point(x_or_point, y) return unless x && y distance_tolerance = BigDecimal(distance_tolerance.to_s) point_distance(x, y) <= distance_tolerance end |
#point_crossings(x_or_point, y = nil) ⇒ Object
Calculates the number of times the line crosses the ray extending to the right from (px,py). If the point lies on the line, then no crossings are recorded. +1 is returned for a crossing where the Y coordinate is increasing -1 is returned for a crossing where the Y coordinate is decreasing
239 240 241 242 243 |
# File 'lib/perfect_shape/line.rb', line 239 def point_crossings(x_or_point, y = nil) x, y = normalize_point(x_or_point, y) return unless x && y Line.point_crossings(points[0][0], points[0][1], points[1][0], points[1][1], x, y) end |
#point_distance(x_or_point, y = nil) ⇒ Object
222 223 224 225 226 |
# File 'lib/perfect_shape/line.rb', line 222 def point_distance(x_or_point, y = nil) x, y = normalize_point(x_or_point, y) return unless x && y Line.point_distance(points[0][0], points[0][1], points[1][0], points[1][1], x, y) end |
#relative_counterclockwise(x_or_point, y = nil) ⇒ Object
228 229 230 231 232 |
# File 'lib/perfect_shape/line.rb', line 228 def relative_counterclockwise(x_or_point, y = nil) x, y = normalize_point(x_or_point, y) return unless x && y Line.relative_counterclockwise(points[0][0], points[0][1], points[1][0], points[1][1], x, y) end |