//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/** @author Yulong Wang
* @version 2.0
* @date Sun May 28 19:05:16 EDT 2023
* @see LICENSE (MIT style license file).
*
* @note Find Points where Lines Intersect Shapes
*/
package scalation
package animation
import scalation.mathstat.VectorD
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/* The `pointOnRect` top level method finds where a line intersects a rectangle.
* Translated from
*
* @see https://stackoverflow.com/questions/1585525/how-to-find-the-intersection-point-between-a-line-and-a-rectangle
*
* "Finds the intersection point between the rectangle with parallel sides to the x and y axes
* the half-line pointing towards (x,y) originating from the middle of the rectangle
*
* Note: the function works given min[XY] <= max[XY], even though minY may not be the "top" of the rectangle
* because the coordinate system is flipped. Note: if the input is inside the rectangle,
* the line segment wouldn't have an intersection with the rectangle, but the projected half-line does.
* Warning: passing in the middle of the rectangle will return the midpoint itself there are infinitely
* many half-lines projected in all directions, so let's just shortcut to midpoint (GIGO)."
*
* @author TWiStErRob
* @licence Dual CC0/WTFPL/Unlicence, whatever floats your boat
* @see source
* @see based on
*
* @param x the x coordinate of point to build the half-line from
* @param y the y coordinate of point to build the half-line from
* @param minX the the "left" side of the rectangle
* @param minY the the "top" side of the rectangle
* @param maxX the the "right" side of the rectangle
* @param maxY the the "bottom" side of the rectangle
* @return an object with x and y members for the intersection
* @throws if validate == true and (x,y) is inside the rectangle
*/
def pointOnRect (x: Double, y: Double, minX: Double, minY: Double, maxX: Double, maxY: Double): VectorD =
// assert minX <= maxX
// assert minY <= maxY
if (minX < x && x < maxX) && (minY < y && y < maxY) then
println (s"Point ($x, $y) cannot be inside rectangle: ($minX, $minY) - ($maxX, $maxY)")
val midX = (minX + maxX) / 2
val midY = (minY + maxY) / 2
// if (midX - x == 0) -> m == ±Inf -> minYx/maxYx == x (because value / ±Inf = ±0)
val m = (midY - y) / (midX - x)
if x <= midX then // check "left" side
val minXy = m * (minX - x) + y
if minY <= minXy && minXy <= maxY then return VectorD (minX, minXy)
if x >= midX then // check "right" side
val maxXy = m * (maxX - x) + y
if minY <= maxXy && maxXy <= maxY then return VectorD (maxX, maxXy)
if y <= midY then // check "top" side
val minYx = (minY - y) / m + x
if minX <= minYx && minYx <= maxX then return VectorD (minYx, minY)
if y >= midY then // check "bottom" side
val maxYx = (maxY - y) / m + x
if minX <= maxYx && maxYx <= maxX then return VectorD (maxYx, maxY)
// edge case when finding midpoint intersection: m = 0/0 = NaN
if x == midX && y == midY then return VectorD (x, y)
// should never happen
println (s"Cannot find intersection for ($x, $y) cannot be inside rectangle: ($minX, $minY) - ($maxX, $maxY)")
VectorD (0, 0)
end pointOnRect