Skip to content

Instantly share code, notes, and snippets.

@ClickerMonkey
Created October 29, 2014 15:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ClickerMonkey/4a6eeb1be826d969fb9f to your computer and use it in GitHub Desktop.
Save ClickerMonkey/4a6eeb1be826d969fb9f to your computer and use it in GitHub Desktop.
Priori intersection algorithms for circles, rectangles, and planes.
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.event.MouseInputListener;
public class CircleCircle extends JPanel implements MouseInputListener
{
private static final long serialVersionUID = 1L;
public static void main( String[] args )
{
JFrame window = new JFrame();
window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
window.setTitle( "Circle Rectangle" );
window.setLocationRelativeTo( null );
CircleCircle space = new CircleCircle();
window.add( space );
window.setSize( 640, 480 );
window.setResizable( false );
window.setVisible( true );
space.start();
}
public CircleCircle()
{
setBackground( Color.BLACK );
addMouseListener( this );
addMouseMotionListener( this );
}
public static final Font FONT = new Font( "Monospaced", Font.PLAIN, 12 );
private enum DraggingState
{
START, END, RADIUS, NONE, OTHER_CENTER, OTHER_RADIUS;
}
private class Intersection
{
public float cx, cy, time, nx, ny, ix, iy;
public Intersection( float x, float y, float time, float nx, float ny, float ix, float iy )
{
this.cx = x;
this.cy = y;
this.time = time;
this.nx = nx;
this.ny = ny;
this.ix = ix;
this.iy = iy;
}
}
private float pointRadius = 8.0f;
private Vector start;
private Vector end;
private Vector radiusPoint;
private float radius;
private Vector otherCenter;
private Vector otherRadiusPoint;
private float otherRadius;
private DraggingState dragging;
public void start()
{
start = new Vector( 50, 400 );
end = new Vector( 320, 240 );
radius = 40.0f;
radiusPoint = new Vector( start.x, start.y - radius );
otherCenter = new Vector( 320, 240 );
otherRadius = 50.0f;
otherRadiusPoint = new Vector( otherCenter.x, otherCenter.y - otherRadius );
dragging = DraggingState.NONE;
}
public void paint( Graphics g )
{
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
g2d.setColor( getBackground() );
g2d.fillRect( 0, 0, getWidth(), getHeight() );
g2d.setColor( Color.WHITE );
g2d.draw( new Line2D.Float( start.x, start.y, end.x, end.y ) );
g2d.setColor( Color.BLUE );
g2d.draw( new Ellipse2D.Float( otherCenter.x - otherRadius, otherCenter.y - otherRadius, otherRadius * 2, otherRadius * 2 ) );
g2d.setColor( Color.GREEN );
g2d.draw( new Ellipse2D.Float( start.x - pointRadius, start.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.setColor( Color.RED );
g2d.draw( new Ellipse2D.Float( end.x - pointRadius, end.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.setColor( Color.YELLOW );
g2d.draw( new Ellipse2D.Float( radiusPoint.x - pointRadius, radiusPoint.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.draw( new Ellipse2D.Float( otherRadiusPoint.x - pointRadius, otherRadiusPoint.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.draw( new Ellipse2D.Float( otherCenter.x - pointRadius, otherCenter.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.draw( new Ellipse2D.Float( start.x - radius, start.y - radius, radius * 2, radius * 2 ) );
g2d.draw( new Ellipse2D.Float( end.x - radius, end.y - radius, radius * 2, radius * 2 ) );
// Check for intersection
g2d.setColor( Color.LIGHT_GRAY );
g2d.setFont( FONT );
Intersection inter = handleIntersection( otherCenter, otherRadius, start, end, radius );
if (inter != null)
{
g2d.setColor( Color.LIGHT_GRAY );
g2d.drawString( "time: " + inter.time, 10, 20 );
g2d.setColor( Color.GRAY );
g2d.draw( new Ellipse2D.Float( inter.cx - radius, inter.cy - radius, radius * 2, radius * 2 ) );
g2d.draw( new Line2D.Float( inter.cx, inter.cy, inter.cx + inter.nx * 20, inter.cy + inter.ny * 20 ) );
g2d.setColor( Color.RED );
g2d.draw( new Ellipse2D.Float( inter.ix - 2, inter.iy - 2, 4, 4 ) );
// Project Future Position
float remainingTime = 1.0f - inter.time;
float dx = end.x - start.x;
float dy = end.y - start.y;
float dot = dx * inter.nx + dy * inter.ny;
float ndx = dx - 2 * dot * inter.nx;
float ndy = dy - 2 * dot * inter.ny;
float newx = inter.cx + ndx * remainingTime;
float newy = inter.cy + ndy * remainingTime;
g2d.setColor( Color.darkGray );
g2d.draw( new Ellipse2D.Float( newx - radius, newy - radius, radius * 2, radius * 2 ) );
g2d.draw( new Line2D.Float( inter.cx, inter.cy, newx, newy ) );
}
}
private Intersection handleIntersection( Vector fixedCenter, float fixedRadius, Vector start, Vector end, float radius )
{
float dx = end.x - start.x;
float dy = end.y - start.y;
float sq = dx * dx + dy * dy;
// 0 = start, 1 = end, 0.5 = midpoint
float delta = ((fixedCenter.x - start.x) * dx + (fixedCenter.y - start.y) * dy) / sq;
// If it's before the start, no intersection.
if (delta < 0)
{
return null;
}
if (delta > 1) delta = 1;
// The location of the closest point on the line to the center of the fixed circle.
float cx = (start.x + delta * dx);
float cy = (start.y + delta * dy);
// If the distance from the closest point to the line is > radius + fixedRadius,
// there is no intersection.
float cdx = cx - fixedCenter.x;
float cdy = cy - fixedCenter.y;
float cdsq = cdx * cdx + cdy * cdy;
float rsum = radius + fixedRadius;
if (cdsq > rsum * rsum)
{
return null;
}
double cdist = Math.sqrt( cdsq );
double angle1 = Math.asin( cdist / rsum );
double angle2 = Math.PI * 0.5 - angle1;
double side2 = Math.abs( rsum * Math.sin( angle2 ) );
double distance = Math.sqrt( sq );
float time = (float)(delta - side2 / distance);
if (time < 0)
{
return null;
}
float x = time * dx + start.x;
float y = time * dy + start.y;
float nx = (x - fixedCenter.x) / rsum;
float ny = (y - fixedCenter.y) / rsum;
float ix = x + nx * radius;
float iy = y + ny * radius;
return new Intersection( x, y, time, nx, ny, ix, iy );
}
public void mousePressed( MouseEvent e )
{
Vector mouse = new Vector( e.getX(), e.getY() );
if (mouse.distance( start ) <= pointRadius)
{
dragging = DraggingState.START;
}
else if (mouse.distance( end ) <= pointRadius)
{
dragging = DraggingState.END;
}
else if (mouse.distance( radiusPoint ) <= pointRadius)
{
dragging = DraggingState.RADIUS;
}
else if (mouse.distance( otherRadiusPoint ) <= pointRadius)
{
dragging = DraggingState.OTHER_RADIUS;
}
else if (mouse.distance( otherCenter ) <= pointRadius)
{
dragging = DraggingState.OTHER_CENTER;
}
else
{
dragging = DraggingState.NONE;
}
}
public void mouseReleased( MouseEvent e )
{
dragging = DraggingState.NONE;
}
public void mouseDragged( MouseEvent e )
{
Vector mouse = new Vector( e.getX(), e.getY() );
switch (dragging)
{
case END:
end.set( mouse );
break;
case RADIUS:
radiusPoint.set( mouse );
radius = radiusPoint.distance( start );
break;
case OTHER_RADIUS:
otherRadiusPoint.set( mouse );
otherRadius = otherRadiusPoint.distance( otherCenter );
break;
case START:
start.set( mouse );
radiusPoint.set( mouse );
radiusPoint.y -= radius;
break;
case OTHER_CENTER:
otherCenter.set( mouse );
otherRadiusPoint.set( mouse );
otherRadiusPoint.y -= otherRadius;
break;
case NONE:
break;
}
repaint();
}
// Unused Mouse Listener Methods
public void mouseMoved( MouseEvent e )
{
}
public void mouseClicked( MouseEvent e )
{
}
public void mouseEntered( MouseEvent e )
{
}
public void mouseExited( MouseEvent e )
{
}
}
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.event.MouseInputListener;
public class CirclePlane extends JPanel implements MouseInputListener
{
private static final long serialVersionUID = 1L;
public static void main( String[] args )
{
JFrame window = new JFrame();
window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
window.setTitle( "Circle Plane" );
window.setLocationRelativeTo( null );
CirclePlane space = new CirclePlane();
window.add( space );
window.setSize( 640, 480 );
window.setResizable( false );
window.setVisible( true );
space.start();
}
public CirclePlane()
{
setBackground( Color.BLACK );
addMouseListener( this );
addMouseMotionListener( this );
}
public static final Font FONT = new Font( "Monospaced", Font.PLAIN, 12 );
private enum DraggingState
{
START, END, RADIUS, NONE, PLANE_POINT, PLANE_NORMAL;
}
private class Intersection
{
public float cx, cy, time, nx, ny, ix, iy;
public Intersection( float x, float y, float time, float nx, float ny, float ix, float iy )
{
this.cx = x;
this.cy = y;
this.time = time;
this.nx = nx;
this.ny = ny;
this.ix = ix;
this.iy = iy;
}
}
private float pointRadius = 8.0f;
private Vector start;
private Vector end;
private Vector radiusPoint;
private float radius;
private Vector planePoint;
private Vector planeNormal;
private DraggingState dragging;
public void start()
{
planePoint = new Vector( 150, 150 );
planeNormal = new Vector( 120, 120 );
start = new Vector( 50, 400 );
end = new Vector( 320, 240 );
radius = 40.0f;
radiusPoint = new Vector( start.x, start.y - radius );
dragging = DraggingState.NONE;
}
public void paint( Graphics g )
{
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
g2d.setColor( getBackground() );
g2d.fillRect( 0, 0, getWidth(), getHeight() );
Vector normal = planeNormal.sub( planePoint ).normali();
g2d.setColor( Color.BLUE );
g2d.draw( new Line2D.Float( planeNormal.x, planeNormal.y, planePoint.x, planePoint.y ) );
g2d.draw( new Line2D.Float( planePoint.x + normal.y * 300, planePoint.y - normal.x * 300, planePoint.x - normal.y * 300, planePoint.y + normal.x * 300 ) );
g2d.setColor( Color.WHITE );
g2d.draw( new Line2D.Float( start.x, start.y, end.x, end.y ) );
g2d.setColor( Color.GREEN );
g2d.draw( new Ellipse2D.Float( start.x - pointRadius, start.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.setColor( Color.RED );
g2d.draw( new Ellipse2D.Float( end.x - pointRadius, end.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.setColor( Color.YELLOW );
g2d.draw( new Ellipse2D.Float( radiusPoint.x - pointRadius, radiusPoint.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.draw( new Ellipse2D.Float( planePoint.x - pointRadius, planePoint.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.draw( new Ellipse2D.Float( planeNormal.x - pointRadius, planeNormal.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.draw( new Ellipse2D.Float( start.x - radius, start.y - radius, radius * 2, radius * 2 ) );
g2d.draw( new Ellipse2D.Float( end.x - radius, end.y - radius, radius * 2, radius * 2 ) );
// Check for intersection
g2d.setColor( Color.LIGHT_GRAY );
g2d.setFont( FONT );
Intersection inter = handleIntersection( fromPoint( planePoint, normal ), start, end, radius );
if (inter != null)
{
g2d.setColor( Color.LIGHT_GRAY );
g2d.drawString( "time: " + inter.time, 10, 20 );
g2d.setColor( Color.GRAY );
g2d.draw( new Ellipse2D.Float( inter.cx - radius, inter.cy - radius, radius * 2, radius * 2 ) );
g2d.draw( new Line2D.Float( inter.cx, inter.cy, inter.cx + inter.nx * 20, inter.cy + inter.ny * 20 ) );
g2d.setColor( Color.RED );
g2d.draw( new Ellipse2D.Float( inter.ix - 2, inter.iy - 2, 4, 4 ) );
// Project Future Position
float remainingTime = 1.0f - inter.time;
float dx = end.x - start.x;
float dy = end.y - start.y;
float dot = dx * inter.nx + dy * inter.ny;
float ndx = dx - 2 * dot * inter.nx;
float ndy = dy - 2 * dot * inter.ny;
float newx = inter.cx + ndx * remainingTime;
float newy = inter.cy + ndy * remainingTime;
g2d.setColor( Color.darkGray );
g2d.draw( new Ellipse2D.Float( newx - radius, newy - radius, radius * 2, radius * 2 ) );
g2d.draw( new Line2D.Float( inter.cx, inter.cy, newx, newy ) );
}
}
private Intersection handleIntersection( Plane plane, Vector start, Vector end, float radius )
{
// No intersection if start is already intersecting with the plane or
// end is not intersecting with the plane.
if (plane.distance( start ) < radius || plane.distance( end ) > radius)
{
return null;
}
Plane shifted = new Plane( plane.a, plane.b, plane.c - radius );
Plane line = fromLine( start, end );
Vector intersection = new Vector();
shifted.intersection( line, intersection );
float distance = start.distance( intersection );
float time = distance / start.distance( end );
return new Intersection( intersection.x, intersection.y, time, plane.a, plane.b, intersection.x - (plane.a * radius), intersection.y - (plane.b * radius) );
}
public void mousePressed( MouseEvent e )
{
Vector mouse = new Vector( e.getX(), e.getY() );
if (mouse.distance( start ) <= pointRadius)
{
dragging = DraggingState.START;
}
else if (mouse.distance( end ) <= pointRadius)
{
dragging = DraggingState.END;
}
else if (mouse.distance( radiusPoint ) <= pointRadius)
{
dragging = DraggingState.RADIUS;
}
else if (mouse.distance( planeNormal ) <= pointRadius)
{
dragging = DraggingState.PLANE_NORMAL;
}
else if (mouse.distance( planePoint ) <= pointRadius)
{
dragging = DraggingState.PLANE_POINT;
}
else
{
dragging = DraggingState.NONE;
}
}
public void mouseReleased( MouseEvent e )
{
dragging = DraggingState.NONE;
}
public void mouseDragged( MouseEvent e )
{
Vector mouse = new Vector( e.getX(), e.getY() );
switch (dragging)
{
case END:
end.set( mouse );
break;
case RADIUS:
radiusPoint.set( mouse );
radius = radiusPoint.distance( start );
break;
case START:
start.set( mouse );
radiusPoint.set( mouse );
radiusPoint.y -= radius;
break;
case PLANE_NORMAL:
planeNormal.set( mouse );
break;
case PLANE_POINT:
Vector diff = planeNormal.sub( planePoint );
planePoint.set( mouse );
planeNormal.set( mouse );
planeNormal.addi( diff );
break;
case NONE:
break;
}
repaint();
}
// Unused Mouse Listener Methods
public void mouseMoved( MouseEvent e )
{
}
public void mouseClicked( MouseEvent e )
{
}
public void mouseEntered( MouseEvent e )
{
}
public void mouseExited( MouseEvent e )
{
}
public Plane fromPoint( Vector point, Vector normal )
{
return new Plane( normal.x, normal.y, -normal.dot( point ) );
}
public Plane fromLine( Vector s, Vector e )
{
float dx = (e.x - s.x);
float dy = (e.y - s.y);
float d = 1.0f / (float)Math.sqrt( dx * dx + dy * dy );
float a = -dy * d;
float b = dx * d;
float c = -(a * s.x + b * s.y);
return new Plane( a, b, c );
}
public class Plane
{
public float a, b, c;
public Plane( float a, float b, float c )
{
this.a = a;
this.b = b;
this.c = c;
}
public float distance( Vector v )
{
return distance( v.x, v.y );
}
public float distance( float x, float y )
{
return (a * x + b * y + c);
}
public boolean intersection( Plane p, Vector out )
{
float div = (a * p.b - b * p.a);
if (div == 0)
{
return false;
}
div = 1 / div;
out.x = (-c * p.b + b * p.c) * div;
out.y = (-a * p.c + c * p.a) * div;
return true;
}
}
}
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.event.MouseInputListener;
public class CircleRectangle extends JPanel implements MouseInputListener
{
private static final long serialVersionUID = 1L;
public static void main( String[] args )
{
JFrame window = new JFrame();
window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
window.setTitle( "Circle Rectangle" );
window.setLocationRelativeTo( null );
CircleRectangle space = new CircleRectangle();
window.add( space );
window.setSize( 720, 560 );
window.setResizable( false );
window.setVisible( true );
space.start();
}
public CircleRectangle()
{
setBackground( Color.BLACK );
addMouseListener( this );
addMouseMotionListener( this );
}
public static final Font FONT = new Font( "Monospaced", Font.PLAIN, 12 );
private enum DraggingState
{
START, END, RADIUS, NONE;
}
private class Intersection
{
public float cx, cy, time, nx, ny, ix, iy;
public Intersection( float x, float y, float time, float nx, float ny, float ix, float iy )
{
this.cx = x;
this.cy = y;
this.time = time;
this.nx = nx;
this.ny = ny;
this.ix = ix;
this.iy = iy;
}
}
private float pointRadius = 8.0f;
private Vector start;
private Vector end;
private Vector radiusPoint;
private float radius;
private Bounds bounds;
private DraggingState dragging;
public void start()
{
bounds = new Bounds( 300, 300, 400, 400 );
start = new Vector( 132, 316 );
end = new Vector( 348, 465 );
radius = 40.0f;
radiusPoint = new Vector( start.x, start.y - radius );
dragging = DraggingState.NONE;
}
public void paint( Graphics g )
{
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
g2d.setColor( getBackground() );
g2d.fillRect( 0, 0, getWidth(), getHeight() );
g2d.setColor( Color.BLUE );
g2d.draw( new Rectangle2D.Float( bounds.left, bounds.top, bounds.getWidth(), bounds.getHeight() ) );
g2d.setColor( Color.WHITE );
g2d.draw( new Line2D.Float( start.x, start.y, end.x, end.y ) );
g2d.setColor( Color.GREEN );
g2d.draw( new Ellipse2D.Float( start.x - pointRadius, start.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.setColor( Color.RED );
g2d.draw( new Ellipse2D.Float( end.x - pointRadius, end.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.setColor( Color.YELLOW );
g2d.draw( new Ellipse2D.Float( radiusPoint.x - pointRadius, radiusPoint.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.draw( new Ellipse2D.Float( start.x - radius, start.y - radius, radius * 2, radius * 2 ) );
g2d.draw( new Ellipse2D.Float( end.x - radius, end.y - radius, radius * 2, radius * 2 ) );
// Check for intersection
g2d.setColor( Color.LIGHT_GRAY );
g2d.setFont( FONT );
Intersection inter = handleIntersection( bounds, start, end, radius );
if (inter != null)
{
g2d.setColor( Color.LIGHT_GRAY );
g2d.drawString( "time: " + inter.time, 10, 20 );
g2d.setColor( Color.GRAY );
g2d.draw( new Ellipse2D.Float( inter.cx - radius, inter.cy - radius, radius * 2, radius * 2 ) );
g2d.draw( new Line2D.Float( inter.cx, inter.cy, inter.cx + inter.nx * 20, inter.cy + inter.ny * 20 ) );
g2d.setColor( Color.RED );
g2d.draw( new Ellipse2D.Float( inter.ix - 2, inter.iy - 2, 4, 4 ) );
// Project Future Position
float remainingTime = 1.0f - inter.time;
float dx = end.x - start.x;
float dy = end.y - start.y;
float dot = dx * inter.nx + dy * inter.ny;
float ndx = dx - 2 * dot * inter.nx;
float ndy = dy - 2 * dot * inter.ny;
float newx = inter.cx + ndx * remainingTime;
float newy = inter.cy + ndy * remainingTime;
g2d.setColor( Color.darkGray );
g2d.draw( new Ellipse2D.Float( newx - radius, newy - radius, radius * 2, radius * 2 ) );
g2d.draw( new Line2D.Float( inter.cx, inter.cy, newx, newy ) );
}
}
private Intersection handleIntersection( Bounds bounds, Vector start, Vector end, float radius )
{
final float L = bounds.left;
final float T = bounds.top;
final float R = bounds.right;
final float B = bounds.bottom;
// If the bounding box around the start and end points (+radius on all
// sides) does not intersect with the rectangle, definitely not an
// intersection
if ((Math.max( start.x, end.x ) + radius < L) ||
(Math.min( start.x, end.x ) - radius > R) ||
(Math.max( start.y, end.y ) + radius < T) ||
(Math.min( start.y, end.y ) - radius > B))
{
return null;
}
final float dx = end.x - start.x;
final float dy = end.y - start.y;
final float invdx = (dx == 0.0f ? 0.0f : 1.0f / dx);
final float invdy = (dy == 0.0f ? 0.0f : 1.0f / dy);
/*
* HANDLE SIDE INTERSECTIONS
*
* Calculate intersection times with each side's plane, translated by
* radius along normal.
*
* If the intersection point lies between the bounds of the adjacent
* sides AND the line start->end is going in the correct direction...
*/
/** Left Side **/
float ltime = ((L - radius) - start.x) * invdx;
if (ltime >= 0.0f && ltime <= 1.0f)
{
float ly = dy * ltime + start.y;
if (ly >= T && ly <= B && dx > 0)
{
return new Intersection( dx * ltime + start.x, ly, ltime, -1, 0, L, ly );
}
}
/** Right Side **/
float rtime = (start.x - (R + radius)) * -invdx;
if (rtime >= 0.0f && rtime <= 1.0f)
{
float ry = dy * rtime + start.y;
if (ry >= T && ry <= B && dx < 0)
{
return new Intersection( dx * rtime + start.x, ry, rtime, 1, 0, R, ry );
}
}
/** Top Side **/
float ttime = ((T - radius) - start.y) * invdy;
if (ttime >= 0.0f && ttime <= 1.0f)
{
float tx = dx * ttime + start.x;
if (tx >= L && tx <= R && dy > 0)
{
return new Intersection( tx, dy * ttime + start.y, ttime, 0, -1, tx, T );
}
}
/** Bottom Side **/
float btime = (start.y - (B + radius)) * -invdy;
if (btime >= 0.0f && btime <= 1.0f)
{
float bx = dx * btime + start.x;
if (bx >= L && bx <= R && dy < 0)
{
return new Intersection( bx, dy * btime + start.y, btime, 0, 1, bx, B );
}
}
/*
* CALCULATE INTERSECTION CORNER
*
* With the tangent that is perpendicular to the line {start->end}, get
* the corner which tangent's intersection with the line is closest to
* start AND the distance between the line and the corner is <= radius.
*/
float lineLength = (float)Math.sqrt( dx * dx + dy * dy );
float lineLengthInv = 1.0f / lineLength;
// Calculate the plane on start->end. This is used to calculate the
// closeness of the tangent to the starting point and for calculating the
// distance from the line to a corner.
float a = -dy * lineLengthInv;
float b = dx * lineLengthInv;
float c = -(a * start.x + b * start.y);
float min = Float.MAX_VALUE;
float cornerX = 0;
float cornerY = 0;
// @formatter:off
/*
* LT,LB,RT,RB
* 0.0 when the tangent between the corner intersects on
* the start, 1.0 when it intersects the end, and 0.5 when it
* intersects the middle of the line. These are used to calculate
* how close the corner is to the line.
*
* Ldx,Rdx,Tdy,Bdy
* cached values used to calculate LT,LB,RT,RB.
*
* La,Ra,Tb,Bb
* cached values used to calculate the distance between the line
* and the corner.
*/
// @formatter:on
float Ldx = (L - start.x) * dx;
float Rdx = (R - start.x) * dx;
float Tdy = (T - start.y) * dy;
float Bdy = (B - start.y) * dy;
float La = L * a;
float Ra = R * a;
float Tb = T * b;
float Bb = B * b;
// If the top-left corner is closest to start AND the line is <= radius
// away from the top-left, it's the new intersecting corner.
float LT = Ldx + Tdy;
if (LT < min && Math.abs( La + Tb + c ) <= radius)
{
min = LT;
cornerX = L;
cornerY = T;
}
// If the bottom-left corner is closest to start AND the line is <= radius
// away from the bottom-left, it's the new intersecting corner.
float LB = Ldx + Bdy;
if (LB < min && Math.abs( La + Bb + c ) <= radius)
{
min = LB;
cornerX = L;
cornerY = B;
}
// If the top-right corner is closest to start AND the line is <= radius
// away from the top-right, it's the new intersecting corner.
float RT = Rdx + Tdy;
if (RT < min && Math.abs( Ra + Tb + c ) <= radius)
{
min = RT;
cornerX = R;
cornerY = T;
}
// If the bottom-right corner is closest to start AND the line is <= radius
// away from the bottom-right, it's the new intersecting corner.
float RB = Rdx + Bdy;
if (RB < min && Math.abs( Ra + Bb + c ) <= radius)
{
min = RB;
cornerX = R;
cornerY = B;
}
// @formatter:off
/* Solve the triangle between the start, corner, and intersection point.
*
* +-----------T-----------+
* | |
* L| |R
* | |
* C-----------B-----------+
* / \
* / \r _.-E
* / \ _.-'
* / _.-I
* / _.-'
* S-'
*
* S = start of circle's path
* E = end of circle's path
* LTRB = sides of the rectangle
* I = {ix, iY} = point at which the circle intersects with the rectangle
* C = corner of intersection (and collision point)
* C=>I (r) = {nx, ny} = radius and intersection normal
* S=>C = cornerDistance
* S=>I = intersectionDistance
* S=>E = lineLength
* <S = innerAngle
* <I = angle1
* <C = angle2
*/
// @formatter:on
double inverseRadius = 1.0 / radius;
double cornerdx = cornerX - start.x;
double cornerdy = cornerY - start.y;
double cornerDistance = Math.sqrt( cornerdx * cornerdx + cornerdy * cornerdy );
double innerAngle = Math.acos( (cornerdx * dx + cornerdy * dy) / (lineLength * cornerDistance) );
// If the circle is too close, no intersection.
if (cornerDistance < radius)
{
return null;
}
// If inner angle is zero, it's going to hit the corner straight on.
if (innerAngle == 0.0f)
{
float time = (float)((cornerDistance - radius) * lineLengthInv);
// If time is outside the boundaries, return null. This algorithm can
// return a negative time which indicates a previous intersection, and
// can also return a time > 1.0f which can predict a corner
// intersection.
if (time > 1.0f || time < 0.0f)
{
return null;
}
float ix = time * dx + start.x;
float iy = time * dy + start.y;
float nx = (float)(cornerdx / cornerDistance);
float ny = (float)(cornerdy / cornerDistance);
return new Intersection( ix, iy, time, nx, ny, cornerX, cornerY );
}
double innerAngleSin = Math.sin( innerAngle );
double angle1Sin = innerAngleSin * cornerDistance * inverseRadius;
// The angle is too large, there cannot be an intersection
if (Math.abs( angle1Sin ) > 1.0f)
{
return null;
}
double angle1 = Math.PI - Math.asin( angle1Sin );
double angle2 = Math.PI - innerAngle - angle1;
double intersectionDistance = radius * Math.sin( angle2 ) / innerAngleSin;
// Solve for time
float time = (float)(intersectionDistance * lineLengthInv);
// If time is outside the boundaries, return null. This algorithm can
// return a negative time which indicates a previous intersection, and
// can also return a time > 1.0f which can predict a corner intersection.
if (time > 1.0f || time < 0.0f)
{
return null;
}
// Solve the intersection and normal
float ix = time * dx + start.x;
float iy = time * dy + start.y;
float nx = (float)((ix - cornerX) * inverseRadius);
float ny = (float)((iy - cornerY) * inverseRadius);
return new Intersection( ix, iy, time, nx, ny, cornerX, cornerY );
}
public void mousePressed( MouseEvent e )
{
Vector mouse = new Vector( e.getX(), e.getY() );
if (mouse.distance( start ) <= pointRadius)
{
dragging = DraggingState.START;
}
else if (mouse.distance( end ) <= pointRadius)
{
dragging = DraggingState.END;
}
else if (mouse.distance( radiusPoint ) <= pointRadius)
{
dragging = DraggingState.RADIUS;
}
else
{
dragging = DraggingState.NONE;
}
}
public void mouseReleased( MouseEvent e )
{
dragging = DraggingState.NONE;
}
public void mouseDragged( MouseEvent e )
{
Vector mouse = new Vector( e.getX(), e.getY() );
switch (dragging)
{
case END:
end.set( mouse );
break;
case RADIUS:
radiusPoint.set( mouse );
radius = radiusPoint.distance( start );
break;
case START:
start.set( mouse );
radiusPoint.set( mouse );
radiusPoint.y -= radius;
break;
case NONE:
break;
}
repaint();
}
// Unused Mouse Listener Methods
public void mouseMoved( MouseEvent e )
{
}
public void mouseClicked( MouseEvent e )
{
}
public void mouseEntered( MouseEvent e )
{
}
public void mouseExited( MouseEvent e )
{
}
public class Bounds
{
public float left;
public float top;
public float right;
public float bottom;
public Bounds()
{
this( 0, 0, 0, 0 );
}
public Bounds( float left, float top, float right, float bottom )
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
public float getWidth()
{
return right - left;
}
public float getHeight()
{
return bottom - top;
}
}
}
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.event.MouseInputListener;
public class RectanglePlane extends JPanel implements MouseInputListener
{
private static final long serialVersionUID = 1L;
public static void main( String[] args )
{
JFrame window = new JFrame();
window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
window.setTitle( "Rectangle Plane" );
window.setLocationRelativeTo( null );
RectanglePlane space = new RectanglePlane();
window.add( space );
window.setSize( 640, 480 );
window.setResizable( false );
window.setVisible( true );
space.start();
}
public RectanglePlane()
{
setBackground( Color.BLACK );
addMouseListener( this );
addMouseMotionListener( this );
}
public static final Font FONT = new Font( "Monospaced", Font.PLAIN, 12 );
private enum DraggingState
{
START, END, RADIUS, NONE, PLANE_POINT, PLANE_NORMAL;
}
private class Intersection
{
public float cx, cy, time, nx, ny, ix, iy;
public Intersection( float x, float y, float time, float nx, float ny, float ix, float iy )
{
this.cx = x;
this.cy = y;
this.time = time;
this.nx = nx;
this.ny = ny;
this.ix = ix;
this.iy = iy;
}
}
private float pointRadius = 8.0f;
private Vector start;
private Vector end;
private Vector radiusPoint;
private float radius;
private Vector planePoint;
private Vector planeNormal;
private DraggingState dragging;
public void start()
{
planePoint = new Vector( 150, 150 );
planeNormal = new Vector( 120, 120 );
start = new Vector( 50, 400 );
end = new Vector( 320, 240 );
radius = 40.0f;
radiusPoint = new Vector( start.x, start.y - radius );
dragging = DraggingState.NONE;
}
public void paint( Graphics g )
{
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
g2d.setColor( getBackground() );
g2d.fillRect( 0, 0, getWidth(), getHeight() );
Vector normal = planeNormal.sub( planePoint ).normali();
g2d.setColor( Color.BLUE );
g2d.draw( new Line2D.Float( planeNormal.x, planeNormal.y, planePoint.x, planePoint.y ) );
g2d.draw( new Line2D.Float( planePoint.x + normal.y * 300, planePoint.y - normal.x * 300, planePoint.x - normal.y * 300, planePoint.y + normal.x * 300 ) );
g2d.setColor( Color.WHITE );
g2d.draw( new Line2D.Float( start.x, start.y, end.x, end.y ) );
g2d.setColor( Color.GREEN );
g2d.draw( new Rectangle2D.Float( start.x - pointRadius, start.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.setColor( Color.RED );
g2d.draw( new Rectangle2D.Float( end.x - pointRadius, end.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.setColor( Color.YELLOW );
g2d.draw( new Ellipse2D.Float( radiusPoint.x - pointRadius, radiusPoint.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.draw( new Ellipse2D.Float( planePoint.x - pointRadius, planePoint.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.draw( new Ellipse2D.Float( planeNormal.x - pointRadius, planeNormal.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.draw( new Rectangle2D.Float( start.x - radius, start.y - radius, radius * 2, radius * 2 ) );
g2d.draw( new Rectangle2D.Float( end.x - radius, end.y - radius, radius * 2, radius * 2 ) );
// Check for intersection
g2d.setColor( Color.LIGHT_GRAY );
g2d.setFont( FONT );
Bounds extents = new Bounds( -radius, -radius, radius, radius );
Intersection inter = handleIntersection( fromPoint( planePoint, normal ), start, end, extents );
if (inter != null)
{
g2d.setColor( Color.LIGHT_GRAY );
g2d.drawString( "time: " + inter.time, 10, 20 );
g2d.setColor( Color.GRAY );
g2d.draw( new Rectangle2D.Float( inter.cx - radius, inter.cy - radius, radius * 2, radius * 2 ) );
g2d.draw( new Line2D.Float( inter.cx, inter.cy, inter.cx + inter.nx * 20, inter.cy + inter.ny * 20 ) );
g2d.setColor( Color.RED );
g2d.draw( new Ellipse2D.Float( inter.ix - 2, inter.iy - 2, 4, 4 ) );
// Project Future Position
float remainingTime = 1.0f - inter.time;
float dx = end.x - start.x;
float dy = end.y - start.y;
float dot = dx * inter.nx + dy * inter.ny;
float ndx = dx - 2 * dot * inter.nx;
float ndy = dy - 2 * dot * inter.ny;
float newx = inter.cx + ndx * remainingTime;
float newy = inter.cy + ndy * remainingTime;
g2d.setColor( Color.darkGray );
g2d.draw( new Rectangle2D.Float( newx - radius, newy - radius, radius * 2, radius * 2 ) );
g2d.draw( new Line2D.Float( inter.cx, inter.cy, newx, newy ) );
}
}
private Intersection handleIntersection( Plane plane, Vector start, Vector end, Bounds extent )
{
float sL = start.x + extent.left;
float sR = start.x + extent.right;
float sT = start.y + extent.top;
float sB = start.y + extent.bottom;
if (plane.distance( sL, sT ) < 0 ||
plane.distance( sL, sB ) < 0 ||
plane.distance( sR, sT ) < 0 ||
plane.distance( sR, sB ) < 0)
{
return null;
}
float eL = end.x + extent.left;
float eR = end.x + extent.right;
float eT = end.y + extent.top;
float eB = end.y + extent.bottom;
if (!(plane.distance( eL, eT ) < 0 || plane.distance( eL, eB ) < 0 ||
plane.distance( eR, eT ) < 0 || plane.distance( eR, eB ) < 0))
{
return null;
}
float dx = end.x - start.x;
float dy = end.y - start.y;
float sq = dx * dx + dy * dy;
float invsq = 1.0f / sq;
float dist = 1.0f / (float)Math.sqrt( sq );
float a = -dy * dist;
float b = dx * dist;
Vector out = new Vector();
Vector first = new Vector();
float firstTime = 1.0f;
plane.intersection( a, b, sL, sT, out );
float LTtime = ((out.x - sL) * dx + (out.y - sT) * dy) * invsq;
if (LTtime < firstTime)
{
first.set( out );
firstTime = LTtime;
}
plane.intersection( a, b, sL, sB, out );
float LBtime = ((out.x - sL) * dx + (out.y - sB) * dy) * invsq;
if (LBtime < firstTime)
{
first.set( out );
firstTime = LBtime;
}
plane.intersection( a, b, sR, sT, out );
float RTtime = ((out.x - sR) * dx + (out.y - sT) * dy) * invsq;
if (RTtime < firstTime)
{
first.set( out );
firstTime = RTtime;
}
plane.intersection( a, b, sR, sB, out );
float RBtime = ((out.x - sR) * dx + (out.y - sB) * dy) * invsq;
if (RBtime < firstTime)
{
first.set( out );
firstTime = RBtime;
}
if (firstTime == 1.0f)
{
return null;
}
return new Intersection( firstTime * dx + start.x, firstTime * dy + start.y, firstTime, plane.a, plane.b, first.x, first.y );
}
public void mousePressed( MouseEvent e )
{
Vector mouse = new Vector( e.getX(), e.getY() );
if (mouse.distance( start ) <= pointRadius)
{
dragging = DraggingState.START;
}
else if (mouse.distance( end ) <= pointRadius)
{
dragging = DraggingState.END;
}
else if (mouse.distance( radiusPoint ) <= pointRadius)
{
dragging = DraggingState.RADIUS;
}
else if (mouse.distance( planeNormal ) <= pointRadius)
{
dragging = DraggingState.PLANE_NORMAL;
}
else if (mouse.distance( planePoint ) <= pointRadius)
{
dragging = DraggingState.PLANE_POINT;
}
else
{
dragging = DraggingState.NONE;
}
}
public void mouseReleased( MouseEvent e )
{
dragging = DraggingState.NONE;
}
public void mouseDragged( MouseEvent e )
{
Vector mouse = new Vector( e.getX(), e.getY() );
switch (dragging)
{
case END:
end.set( mouse );
break;
case RADIUS:
radiusPoint.set( mouse );
radius = radiusPoint.distance( start );
break;
case START:
start.set( mouse );
radiusPoint.set( mouse );
radiusPoint.y -= radius;
break;
case PLANE_NORMAL:
planeNormal.set( mouse );
break;
case PLANE_POINT:
Vector diff = planeNormal.sub( planePoint );
planePoint.set( mouse );
planeNormal.set( mouse );
planeNormal.addi( diff );
break;
case NONE:
break;
}
repaint();
}
// Unused Mouse Listener Methods
public void mouseMoved( MouseEvent e )
{
}
public void mouseClicked( MouseEvent e )
{
}
public void mouseEntered( MouseEvent e )
{
}
public void mouseExited( MouseEvent e )
{
}
public Plane fromPoint( Vector point, Vector normal )
{
return new Plane( normal.x, normal.y, -normal.dot( point ) );
}
public Plane fromLine( Vector s, Vector e )
{
float dx = (e.x - s.x);
float dy = (e.y - s.y);
float d = 1.0f / (float)Math.sqrt( dx * dx + dy * dy );
float a = -dy * d;
float b = dx * d;
float c = -(a * s.x + b * s.y);
return new Plane( a, b, c );
}
public class Plane
{
public float a, b, c;
public Plane( float a, float b, float c )
{
this.a = a;
this.b = b;
this.c = c;
}
public float distance( Vector v )
{
return distance( v.x, v.y );
}
public float distance( float x, float y )
{
return (a * x + b * y + c);
}
public boolean intersection( Plane p, Vector out )
{
return intersection( p.a, p.b, p.c, out );
}
public boolean intersection( float nx, float ny, float x, float y, Vector out )
{
return intersection( nx, ny, -(nx * x + ny * y), out );
}
public boolean intersection( float pa, float pb, float pc, Vector out )
{
float div = (a * pb - b * pa);
if (div == 0)
{
return false;
}
div = 1 / div;
out.x = (-c * pb + b * pc) * div;
out.y = (-a * pc + c * pa) * div;
return false;
}
}
public class Bounds
{
public float left;
public float top;
public float right;
public float bottom;
public Bounds()
{
this( 0, 0, 0, 0 );
}
public Bounds( float left, float top, float right, float bottom )
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
public float getWidth()
{
return right - left;
}
public float getHeight()
{
return bottom - top;
}
}
}
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.event.MouseInputListener;
public class RectangleRectangle extends JPanel implements MouseInputListener
{
private static final long serialVersionUID = 1L;
public static void main( String[] args )
{
JFrame window = new JFrame();
window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
window.setTitle( "Rectangle Rectangle" );
window.setLocationRelativeTo( null );
RectangleRectangle space = new RectangleRectangle();
window.add( space );
window.setSize( 640, 480 );
window.setResizable( false );
window.setVisible( true );
space.start();
}
public RectangleRectangle()
{
setBackground( Color.BLACK );
addMouseListener( this );
addMouseMotionListener( this );
}
public static final Font FONT = new Font( "Monospaced", Font.PLAIN, 12 );
private enum DraggingState
{
START, END, RADIUS, NONE;
}
private class Intersection
{
public float cx, cy, time, nx, ny, ix, iy;
public Intersection( float x, float y, float time, float nx, float ny, float ix, float iy )
{
this.cx = x;
this.cy = y;
this.time = time;
this.nx = nx;
this.ny = ny;
this.ix = ix;
this.iy = iy;
}
}
private float pointRadius = 8.0f;
private Vector start;
private Vector end;
private Vector radiusPoint;
private float radius;
private Bounds bounds;
private DraggingState dragging;
public void start()
{
bounds = new Bounds( 150, 150, 490, 330 );
start = new Vector( 50, 400 );
end = new Vector( 320, 240 );
radius = 40.0f;
radiusPoint = new Vector( start.x, start.y - radius );
dragging = DraggingState.NONE;
}
public void paint( Graphics g )
{
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
g2d.setColor( getBackground() );
g2d.fillRect( 0, 0, getWidth(), getHeight() );
g2d.setColor( Color.BLUE );
g2d.draw( new Rectangle2D.Float( bounds.left, bounds.top, bounds.getWidth(), bounds.getHeight() ) );
g2d.setColor( Color.WHITE );
g2d.draw( new Line2D.Float( start.x, start.y, end.x, end.y ) );
g2d.setColor( Color.GREEN );
g2d.draw( new Rectangle2D.Float( start.x - pointRadius, start.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.setColor( Color.RED );
g2d.draw( new Rectangle2D.Float( end.x - pointRadius, end.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.setColor( Color.YELLOW );
g2d.draw( new Rectangle2D.Float( radiusPoint.x - pointRadius, radiusPoint.y - pointRadius, pointRadius * 2, pointRadius * 2 ) );
g2d.draw( new Rectangle2D.Float( start.x - radius, start.y - radius, radius * 2, radius * 2 ) );
g2d.draw( new Rectangle2D.Float( end.x - radius, end.y - radius, radius * 2, radius * 2 ) );
// Check for intersection
g2d.setColor( Color.LIGHT_GRAY );
g2d.setFont( FONT );
Bounds extents = new Bounds( -radius, -radius, radius, radius );
Intersection inter = handleIntersection( bounds, start, end, extents );
if (inter != null)
{
g2d.setColor( Color.LIGHT_GRAY );
g2d.drawString( "time: " + inter.time, 10, 20 );
g2d.setColor( Color.GRAY );
g2d.draw( new Rectangle2D.Float( inter.cx - radius, inter.cy - radius, radius * 2, radius * 2 ) );
g2d.draw( new Line2D.Float( inter.cx, inter.cy, inter.cx + inter.nx * 20, inter.cy + inter.ny * 20 ) );
g2d.setColor( Color.RED );
g2d.draw( new Ellipse2D.Float( inter.ix - 2, inter.iy - 2, 4, 4 ) );
// Project Future Position
float remainingTime = 1.0f - inter.time;
float dx = end.x - start.x;
float dy = end.y - start.y;
float dot = dx * inter.nx + dy * inter.ny;
float ndx = dx - 2 * dot * inter.nx;
float ndy = dy - 2 * dot * inter.ny;
float newx = inter.cx + ndx * remainingTime;
float newy = inter.cy + ndy * remainingTime;
g2d.setColor( Color.darkGray );
g2d.draw( new Rectangle2D.Float( newx - radius, newy - radius, radius * 2, radius * 2 ) );
g2d.draw( new Line2D.Float( inter.cx, inter.cy, newx, newy ) );
}
}
private float clamp( float x, float min, float max )
{
return (x < min ? min : (x > max ? max : x));
}
private Intersection handleIntersection( Bounds bounds, Vector start, Vector end, Bounds extents )
{
final float L = bounds.left;
final float T = bounds.top;
final float R = bounds.right;
final float B = bounds.bottom;
// If the bounding box around the start and end points (+extents on all
// sides) does not intersect with the rectangle, definitely not an
// intersection
if ((Math.max( start.x, end.x ) + extents.right < L) ||
(Math.min( start.x, end.x ) + extents.left > R) ||
(Math.max( start.y, end.y ) + extents.bottom < T) ||
(Math.min( start.y, end.y ) + extents.top > B))
{
return null;
}
// If the rectangle around start intersects with the bounds, error
if (!((start.x + extents.right < L) || (start.x + extents.left > R) ||
(start.y + extents.bottom < T) || (start.y + extents.top > B)))
{
return null;
}
final float dx = end.x - start.x;
final float dy = end.y - start.y;
final float invdx = (dx == 0.0f ? 0.0f : 1.0f / dx);
final float invdy = (dy == 0.0f ? 0.0f : 1.0f / dy);
/** Left Side **/
// Does the rectangle go from the left side to the right side of the
// rectangle's left?
if (start.x + extents.left < L && end.x + extents.right > L)
{
float ltime = ((L + extents.left) - start.x) * invdx;
if (ltime >= 0.0f && ltime <= 1.0f)
{
float ly = dy * ltime + start.y;
float lyT = ly + extents.top;
float lyB = ly + extents.bottom;
// Does the collisions point lie on the left side?
if (lyT < B && lyB > T)
{
return new Intersection( dx * ltime + start.x, ly, ltime, -1, 0, L, clamp( ly, T, B ) );
}
// Is the collision directly on the top-left corner?
else if (lyB == T)
{
return new Intersection( dx * ltime + start.x, ly, ltime, -1, -1, L, lyB );
}
// Is the collision directly on the bottom-left corner?
else if (lyT == B)
{
return new Intersection( dx * ltime + start.x, ly, ltime, -1, -1, L, lyT );
}
}
}
/** Right Side **/
// Does the rectangle go from the right side to the left side of the
// rectangle's right?
if (start.x + extents.right > R && end.x + extents.left < R)
{
float rtime = (start.x - (R + extents.right)) * -invdx;
if (rtime >= 0.0f && rtime <= 1.0f)
{
float ry = dy * rtime + start.y;
float ryT = ry + extents.top;
float ryB = ry + extents.bottom;
// Does the collisions point lie on the right side?
if (ryB > T && ryT < B)
{
return new Intersection( dx * rtime + start.x, ry, rtime, 1, 0, R, clamp( ry, T, B ) );
}
// Is the collision directly on the top-right corner?
else if (ryB == T)
{
return new Intersection( dx * rtime + start.x, ry, rtime, 1, -1, L, ryB );
}
// Is the collision directly on the bottom-right corner?
else if (ryT == B)
{
return new Intersection( dx * rtime + start.x, ry, rtime, 1, 1, L, ryT );
}
}
}
/** Top Side **/
// Does the rectangle go from the top side to the bottom side of the
// rectangle's top?
if (start.y + extents.top < T && end.y + extents.bottom > T)
{
float ttime = ((T + extents.top) - start.y) * invdy;
if (ttime >= 0.0f && ttime <= 1.0f)
{
float tx = dx * ttime + start.x;
float txL = tx + extents.left;
float txR = tx + extents.right;
// Does the collisions point lie on the top side?
if (txR > L && txL < R)
{
return new Intersection( tx, dy * ttime + start.y, ttime, 0, -1, clamp( tx, L, R ), T );
}
// Is the collision directly on the top-left corner?
else if (txR == L)
{
return new Intersection( tx, dy * ttime + start.y, ttime, -1, -1, txR, T );
}
// Is the collision directly on the top-right corner?
else if (txL == R)
{
return new Intersection( tx, dy * ttime + start.y, ttime, 1, -1, txL, T );
}
}
}
/** Bottom Side **/
// Does the rectangle go from the bottom side to the top side of the
// rectangle's bottom?
if (start.y + extents.bottom > B && end.y + extents.top < B)
{
float btime = (start.y - (B + extents.bottom)) * -invdy;
if (btime >= 0.0f && btime <= 1.0f)
{
float bx = dx * btime + start.x;
float bxL = bx + extents.left;
float bxR = bx + extents.right;
// Does the collisions point lie on the bottom side?
if (bxR > L && bxL < R)
{
return new Intersection( bx, dy * btime + start.y, btime, 0, 1, clamp( bx, L, R ), B );
}
// Is the collision directly on the top-left corner?
else if (bxR == L)
{
return new Intersection( bx, dy * btime + start.y, btime, -1, 1, bxR, B );
}
// Is the collision directly on the top-right corner?
else if (bxL == R)
{
return new Intersection( bx, dy * btime + start.y, btime, 1, 1, bxL, B );
}
}
}
// No intersection at all!
return null;
}
public void mousePressed( MouseEvent e )
{
Vector mouse = new Vector( e.getX(), e.getY() );
if (mouse.distance( start ) <= pointRadius)
{
dragging = DraggingState.START;
}
else if (mouse.distance( end ) <= pointRadius)
{
dragging = DraggingState.END;
}
else if (mouse.distance( radiusPoint ) <= pointRadius)
{
dragging = DraggingState.RADIUS;
}
else
{
dragging = DraggingState.NONE;
}
}
public void mouseReleased( MouseEvent e )
{
dragging = DraggingState.NONE;
}
public void mouseDragged( MouseEvent e )
{
Vector mouse = new Vector( e.getX(), e.getY() );
switch (dragging)
{
case END:
end.set( mouse );
break;
case RADIUS:
radiusPoint.set( mouse );
radius = radiusPoint.distance( start );
break;
case START:
start.set( mouse );
radiusPoint.set( mouse );
radiusPoint.y -= radius;
break;
case NONE:
break;
}
repaint();
}
// Unused Mouse Listener Methods
public void mouseMoved( MouseEvent e )
{
}
public void mouseClicked( MouseEvent e )
{
}
public void mouseEntered( MouseEvent e )
{
}
public void mouseExited( MouseEvent e )
{
}
public class Bounds
{
public float left;
public float top;
public float right;
public float bottom;
public Bounds()
{
this( 0, 0, 0, 0 );
}
public Bounds( float left, float top, float right, float bottom )
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
public float getWidth()
{
return right - left;
}
public float getHeight()
{
return bottom - top;
}
}
}
/**
*
* Operations in this class are in the following format:
*
* <pre>
* // The operation is performed and the result is set to the out vector which is then returned.
* public Vector operation( <i>parameters</i>, Vector out );
*
* // The operation is performed and the result is set to this vector which is then returned.
* // This is the same as "return operation( <i>parameters</i>, this );"
* public Vector operationi( <i>parameters</i>);
*
* // The operation is performed and the result is set to a new vector which is then returned.
* // This is the same as "return operation( <i>parameters</i>, new Vector() );"
* public Vector operation( <i>parameters</i> );
* </pre>
*
* Properties of unit (normalized) vectors:
* <ol>
* <li>{@link #length()} and {@link #lengthSq()} return 1.0f</li>
* <li>{@link #isUnit()} and {@link #isUnit(float)} return true</li>
* <li>x = cos(A) where A is the angle of the vector (returned by
* {@link #angle()}).</li>
* <li>y = sin(A) where A is the angle of the vector (returned by
* {@link #angle()}).</li>
* <li>passing in {@link #angle()} to {@link #rotatei(float)} is the same as
* passing the vector into {@link #rotatei(Vector)} because of the two
* properties mentioned above except the latter method is quicker since
* {@link Math#cos(double)} and {@link Math#sin(double)} don't need to be
* called.</li>
* <li>It's an efficient way of storing an angle.</li>
* <li>Can be used as the normal parameter in {@link #reflect(Vector)} methods.</li>
* </ol>
*
* @author Philip Diffenderfer
*
*/
public class Vector
{
/**
* Returns a vector with all components set to zero. If this is directly
* modified or passed to a function that may modify it, it will change for
* all references of this value. This should strictly be used as a constant.
*/
public static final Vector ZERO = new Vector( 0, 0 );
/**
* Returns a vector with all components set to one. If this is directly
* modified or passed to a function that may modify it, it will change for
* all references of this value. This should strictly be used as a constant.
*/
public static final Vector ONE = new Vector( 1, 1 );
/**
* Returns a unit vector along the x-axis in the positive direction.
*/
public static final Vector RIGHT = new Vector( 1, 0 );
/**
* Returns a unit vector along the x-axis in the negative direction.
*/
public static final Vector LEFT = new Vector( 1, 0 );
/*
* Returns a unit vector along the y-axis in the positive direction.
*/
public static final Vector TOP = new Vector( 0, 1 );
/**
* Returns a unit vector along the y-axis in the negative direction.
*/
public static final Vector BOTTOM = new Vector( 0, -1 );
/**
* Constant used to fix the angle returned by {@link #angle()} and
* {@link #angleTo(Vector)}.
*/
private static final float ANGLE_FIX = (float)(Math.PI * 2.0f);
/**
* The x-coordinate of the Vector.
*/
public float x;
/**
* The y-coordinate of the Vector.
*/
public float y;
/**
* Instantiates a new Vector at the origin.
*/
public Vector()
{
}
/**
* Instantiates a new Vector at the specified coordinates.
*
* @param x
* The initial x-coordinate of the vector.
* @param y
* The initial y-coordinate of the vector.
*/
public Vector( float x, float y )
{
set( x, y );
}
/**
* Instantiates a new Vector based on another Vector.
*
* @param v
* The vector to copy x and y coordinates from.
*/
public Vector( Vector v )
{
set( v );
}
/**
* Sets the coordinates of this vector and returns this.
*/
public Vector set( float x, float y )
{
this.x = x;
this.y = y;
return this;
}
/**
* Sets the coordinates of this vector and returns this.
*/
public Vector set( Vector v )
{
x = v.x;
y = v.y;
return this;
}
/**
* Clears this vector's components by setting them to zero.
*/
public void clear()
{
x = y = 0.0f;
}
/**
* Negates this vector and returns this.
*/
public Vector negi()
{
return neg( this );
}
/**
* Sets out to the negation of this vector and returns out.
*/
public Vector neg( Vector out )
{
out.x = -x;
out.y = -y;
return out;
}
/**
* Returns a new vector that is the negation to this vector.
*/
public Vector neg()
{
return neg( new Vector() );
}
/**
* Sets this vector to it's absolute value and returns this.
*/
public Vector absi()
{
return abs( this );
}
/**
* Sets out to the absolute value of this vector and returns out.
*/
public Vector abs( Vector out )
{
out.x = x < 0 ? -x : x;
out.y = y < 0 ? -y : y;
return out;
}
/**
* Returns a new vector that is the absolute value of this vector.
*/
public Vector abs()
{
return abs( new Vector() );
}
/**
* Multiplies this vector by s and returns this.
*/
public Vector muli( float s )
{
return mul( s, this );
}
/**
* Sets out to this vector multiplied by s and returns out.
*/
public Vector mul( float s, Vector out )
{
out.x = s * x;
out.y = s * y;
return out;
}
/**
* Returns a new vector that is a multiplication of this vector and s.
*/
public Vector mul( float s )
{
return mul( s, new Vector() );
}
/**
* Divides this vector by s and returns this.
*/
public Vector divi( float s )
{
return div( s, this );
}
/**
* Sets out to the division of this vector and s and returns out.
*/
public Vector div( float s, Vector out )
{
if (s != 0.0f)
{
out.x = x / s;
out.y = y / s;
}
return out;
}
/**
* Returns a new vector that is a division between this vector and s.
*/
public Vector div( float s )
{
return div( s, new Vector() );
}
/**
* Adds s to this vector and returns this.
*/
public Vector addi( float s )
{
return add( s, this );
}
/**
* Sets out to the sum of this vector and s and returns out.
*/
public Vector add( float s, Vector out )
{
out.x = x + s;
out.y = y + s;
return out;
}
/**
* Returns a new vector that is the sum between this vector and s.
*/
public Vector add( float s )
{
return add( s, new Vector() );
}
/**
* Multiplies this vector by v and returns this.
*/
public Vector muli( Vector v )
{
return mul( v, this );
}
/**
* Sets out to the product of this vector and v and returns out.
*/
public Vector mul( Vector v, Vector out )
{
out.x = x * v.x;
out.y = y * v.y;
return out;
}
/**
* Returns a new vector that is the product of this vector and v.
*/
public Vector mul( Vector v )
{
return mul( v, new Vector() );
}
/**
* Divides this vector by v and returns this.
*/
public Vector divi( Vector v )
{
return div( v, this );
}
/**
* Sets out to the division of this vector and v and returns out.
*/
public Vector div( Vector v, Vector out )
{
if (v.x != 0.0f)
{
out.x = x / v.x;
}
if (v.y != 0.0f)
{
out.y = y / v.y;
}
return out;
}
/**
* Returns a new vector that is the division of this vector by v.
*/
public Vector div( Vector v )
{
return div( v, new Vector() );
}
/**
* Adds v to this vector and returns this.
*/
public Vector addi( Vector v )
{
return add( v, this );
}
/**
* Sets out to the addition of this vector and v and returns out.
*/
public Vector add( Vector v, Vector out )
{
out.x = x + v.x;
out.y = y + v.y;
return out;
}
/**
* Returns a new vector that is the addition of this vector and v.
*/
public Vector add( Vector v )
{
return add( v, new Vector() );
}
/**
* Adds v * s to this vector and returns this.
*/
public Vector addsi( Vector v, float s )
{
return adds( v, s, this );
}
/**
* Sets out to the addition of this vector and v * s and returns out.
*/
public Vector adds( Vector v, float s, Vector out )
{
out.x = x + v.x * s;
out.y = y + v.y * s;
return out;
}
/**
* Returns a new vector that is the addition of this vector and v * s.
*/
public Vector adds( Vector v, float s )
{
return adds( v, s, new Vector() );
}
/**
* Subtracts v from this vector and returns this.
*/
public Vector subi( Vector v )
{
return sub( v, this );
}
/**
* Sets out to the subtraction of v from this vector and returns out.
*/
public Vector sub( Vector v, Vector out )
{
out.x = x - v.x;
out.y = y - v.y;
return out;
}
/**
* Returns a new vector that is the subtraction of v from this vector.
*/
public Vector sub( Vector v )
{
return sub( v, new Vector() );
}
/**
* Sets this to the vector starting at origin and ending at target, and
* returns this.
*/
public Vector directi( Vector origin, Vector target )
{
return direct( origin, target, this );
}
/**
* Sets out to the vector starting at origin and ending at target, and
* returns out.
*/
public Vector direct( Vector origin, Vector target, Vector out )
{
out.x = target.x - origin.x;
out.y = target.y - origin.y;
return out;
}
/**
* Returns a new vector starting at origin and ending at target.
*/
public Vector direct( Vector origin, Vector target )
{
return direct( origin, target, new Vector() );
}
/**
* Sets this to the vector between start and end based on delta where delta
* is 0.0 for the start, 0.5 for the middle, and 1.0 for the end, and returns
* this.
*/
public Vector interpolatei( Vector start, Vector end, float delta )
{
return interpolate( start, end, delta, this );
}
/**
* Sets out to the vector between start and end based on delta where delta is
* 0.0 for the start, 0.5 for the middle, and 1.0 for the end, and returns
* out.
*/
public Vector interpolate( Vector start, Vector end, float delta, Vector out )
{
out.x = (end.x - start.x) * delta + start.x;
out.y = (end.y - start.y) * delta + start.y;
return out;
}
/**
* Returns a new vector between start and end based on delta where delta is
* 0.0 for the start, 0.5 for the middle, and 1.0 for the end.
*/
public Vector interpolate( Vector start, Vector end, float delta )
{
return interpolate( start, end, delta, new Vector() );
}
/**
* Sets this to the vector with the given angle in radians with the given
* magnitude, and returns this.
*/
public Vector angle( float radians, float magnitude )
{
x = (float)Math.cos( radians ) * magnitude;
y = (float)Math.sin( radians ) * magnitude;
return this;
}
/**
* Returns the angle in radians of this vector from the x-axis.
*/
public float angle()
{
float a = (float)StrictMath.atan2( y, x );
if (a < 0)
{
a += ANGLE_FIX;
}
return a;
}
/**
* Returns the angle in radians that's between this vector and the given
* vector and the x-axis.
*/
public float angleTo( Vector to )
{
float a = (float)StrictMath.atan2( to.y - y, to.x - x );
if (a < 0)
{
a += ANGLE_FIX;
}
return a;
}
/**
* Determines whether this vector is an exact unit vector.
*/
public boolean isUnit()
{
return lengthSq() == 1.0f;
}
/**
* Determines whether this vector is a unit vector within epsilon.
*/
public boolean isUnit( float epsilon )
{
return Math.abs( lengthSq() - 1.0f ) < epsilon;
}
/**
* Returns the squared length of this vector.
*/
public float lengthSq()
{
return x * x + y * y;
}
/**
* Returns the length of this vector.
*/
public float length()
{
return (float)Math.sqrt( x * x + y * y );
}
/**
* Sets the length of this vector and returns the previous length. If this
* vector has no length, nothing changes.
*/
public float length( float length )
{
float sq = (x * x) + (y * y);
float actual = length;
if (sq != 0.0 && sq != length * length)
{
actual = (float)Math.sqrt( sq );
muli( length / actual );
}
return actual;
}
/**
* Clamps the length of this vector between a minimum and maximum and returns
* this Vector. If this vector has no length, nothing changes.
*/
public Vector clamp( float min, float max )
{
float sq = (x * x) + (y * y);
if (sq != 0)
{
if (sq < min * min)
{
muli( min / (float)Math.sqrt( sq ) );
}
else if (sq > max * max)
{
muli( max / (float)Math.sqrt( sq ) );
}
}
return this;
}
/**
* If the length of this Vector is less than min, it's length will be set to
* min and this will be returned.
*/
public Vector min( float min )
{
float sq = (x * x) + (y * y);
if (sq != 0 && sq < min * min)
{
muli( min / (float)Math.sqrt( sq ) );
}
return this;
}
/**
* If the length of this Vector is greater than max, it's length will be set
* to max and this will be returned.
*/
public Vector max( float max )
{
float sq = (x * x) + (y * y);
if (sq != 0 && sq > max * max)
{
muli( max / (float)Math.sqrt( sq ) );
}
return this;
}
/**
* Rotates this vector by the given radians and returns this.
*/
public Vector rotatei( float radians )
{
return rotate( radians, this );
}
/**
* Sets out to this vector rotated by the given radians and returns out.
*/
public Vector rotate( float radians, Vector out )
{
float c = (float)Math.cos( radians );
float s = (float)Math.sin( radians );
float xp = x * c - y * s;
float yp = x * s + y * c;
out.x = xp;
out.y = yp;
return out;
}
/**
* Returns a new vector that is this vector rotated by the given radians.
*/
public Vector rotate( float radians )
{
return rotate( radians, new Vector() );
}
/**
* Rotates this vector by the given unit vector and returns this.
*/
public Vector rotatei( Vector cossin )
{
return rotate( cossin, this );
}
/**
* Sets out to this vector unit vector by the normal and returns out.
*/
public Vector rotate( Vector cossin, Vector out )
{
final float ox = x, oy = y;
out.x = (cossin.x * ox - cossin.y * oy);
out.y = (cossin.x * oy + cossin.y * ox);
return out;
}
/**
* Returns a new vector that is this vector rotated by the unit vector.
*/
public Vector rotate( Vector cossin )
{
return rotate( cossin, new Vector() );
}
/**
* Rotates this vector around the origin the given number of times and
* returns this.
*/
public Vector rotate90i( int times )
{
return rotate90( times, ZERO, this );
}
/**
* Rotates this vector around the given origin the given number of times and
* returns this.
*/
public Vector rotate90i( int times, Vector origin )
{
return rotate90( times, origin, this );
}
/**
* Sets out to this vector rotated around the given origin a given number of
* times and returns out.
*/
public Vector rotate90( int times, Vector origin, Vector out )
{
float dx = x - origin.x;
float dy = y - origin.y;
switch (times & 3)
{
case 0:
out.x = x;
out.y = y;
break;
case 1:
out.x = x - dy;
out.y = y + dx;
break;
case 2:
out.x = x - dx;
out.y = y - dy;
break;
case 3:
out.x = x + dy;
out.y = y - dy;
break;
}
return out;
}
/**
* Returns a new vector rotated around the given origin a given number of
* times.
*/
public Vector rotate90( int times )
{
return rotate90( times, ZERO, new Vector() );
}
/**
* Returns a new vector rotated around the given origin a given number of
* times.
*/
public Vector rotate90( int times, Vector origin )
{
return rotate90( times, origin, new Vector() );
}
/**
* Reflects this vector across the normal and returns this.
*/
public Vector reflecti( Vector normal )
{
return reflect( normal, this );
}
/**
* Sets out to this vector reflected across the normal and returns out.
*/
public Vector reflect( Vector normal, Vector out )
{
final float scale = 2 * dot( normal );
out.x = x - scale * normal.x;
out.y = y - scale * normal.y;
return out;
}
/**
* Returns a new vector that is this vector reflected across the normal.
*/
public Vector reflect( Vector normal )
{
return reflect( normal, new Vector() );
}
/**
* Reflects this vector across the normal and returns this.
*/
public Vector refracti( Vector normal )
{
return refract( normal, this );
}
/**
* Sets out to this vector reflected across the normal and returns out.
*/
public Vector refract( Vector normal, Vector out )
{
final float scale = 2 * dot( normal );
out.x = scale * normal.x - x;
out.y = scale * normal.y - y;
return out;
}
/**
* Returns a new vector that is this vector reflected across the normal.
*/
public Vector refract( Vector normal )
{
return refract( normal, new Vector() );
}
/**
* Normalizes this vector into a unit vector and returns the length. A unit
* vector has a length of 1.0 and the x-component represents cos(A) and the
* y-component represents sin(A) where A is the angle between this vector and
* the x-axis.
*/
public float normalize()
{
float m = lengthSq();
if (m != 0.0f)
{
divi( m = (float)Math.sqrt( m ) );
}
return m;
}
/**
* Sets this vector to it's normal and returns this.
*/
public Vector normali()
{
return normal( this );
}
/**
* Sets out to the normal of this vector and returns out.
*/
public Vector normal( Vector out )
{
float m = lengthSq();
out.set( x, y );
if (m != 0.0)
{
out.muli( 1.0f / (float)Math.sqrt( m ) );
}
return out;
}
/**
* Returns a new vector which is the normal of this vector.
*/
public Vector normal()
{
return normal( new Vector() );
}
/**
* Sets this vector to the minimum between a and b.
*/
public Vector mini( Vector a, Vector b )
{
return min( a, b, this );
}
/**
* Sets this vector to the maximum between a and b.
*/
public Vector maxi( Vector a, Vector b )
{
return max( a, b, this );
}
/**
* Sets this vector to it's tangent on the left side.
*/
public Vector lefti()
{
return left( this );
}
/**
* Sets out to the tangent of this vector on the left side.
*/
public Vector left( Vector out )
{
float oldx = x;
out.x = -y;
out.y = oldx;
return out;
}
/**
* Returns a new vector that is the tangent of this vector on the left side.
*/
public Vector left()
{
return left( new Vector() );
}
/**
* Sets this vector to it's tangent on the right side.
*/
public Vector righti()
{
return right( this );
}
/**
* Sets out to the tangent of this vector on the right side.
*/
public Vector right( Vector out )
{
float oldx = x;
out.x = y;
out.y = -oldx;
return out;
}
/**
* Returns a new vector that is the tangent of this vector on the right side.
*/
public Vector right()
{
return right( new Vector() );
}
/**
* Returns the dot product between this vector and v.
*/
public float dot( Vector v )
{
return dot( this, v );
}
/**
* Returns the squared distance between this vector and v.
*/
public float distanceSq( Vector v )
{
return distanceSq( this, v );
}
/**
* Returns the distance between this vector and v.
*/
public float distance( Vector v )
{
return distance( this, v );
}
/**
* Sets this vector to the cross between v and a and returns this.
*/
public Vector cross( Vector v, float a )
{
return cross( v, a, this );
}
/**
* Sets this vector to the cross between a and v and returns this.
*/
public Vector cross( float a, Vector v )
{
return cross( a, v, this );
}
/**
* Returns the scalar cross between this vector and v. This is essentially
* the length of the cross product if this vector were 3d. This can also
* indicate which way v is facing relative to this vector (left or right).
*/
public float cross( Vector v )
{
return cross( this, v );
}
/**
* Returns the scalar cross between this vector and v. This is essentially
* the length of the cross product if this vector were 3d. This can also
* indicate which way v is facing relative to this vector (left or right).
*/
public float cross( float vx, float vy )
{
return cross( x, y, vx, vy );
}
/**
* Returns whether the given vector is perfectly parallel to this vector.
*/
public boolean isParallel( Vector v )
{
return isParallel( v, 0.0f );
}
/**
* Returns whether the given vector is parallel to this vector within
* epsilon.
*/
public boolean isParallel( Vector v, float epsilon )
{
return Math.abs( cross( v ) ) < epsilon;
}
/**
* Clones this vector.
*/
public Vector clone()
{
return new Vector( x, y );
}
/**
* Determines whether this vector's components are both exactly zero.
*/
public boolean isZero()
{
return (x == 0f && y == 0f);
}
/**
* Determines whether this vector's components are both at least epsilon away
* from zero.
*/
public boolean isZero( float epsilon )
{
return isEqual( 0, 0, epsilon );
}
/**
* Determines if this vector is equal to v.
*/
public boolean isEqual( Vector v )
{
return (x == v.x && y == v.y);
}
/**
* Determines if this vector is equal to the vector {xx, yy}.
*/
public boolean isEqual( float xx, float yy )
{
return (x == xx && y == yy);
}
/**
* Determines if this vector is equal to v within epsilon.
*/
public boolean isEqual( Vector v, float epsilon )
{
return isEqual( v.x, v.y, epsilon );
}
/**
* Determines if this vector is equal to the vector {xx, yy} within epsilon.
*/
public boolean isEqual( float xx, float yy, float epsilon )
{
return Math.abs( xx - x ) < epsilon && Math.abs( yy - y ) < epsilon;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + Float.floatToIntBits( x );
result = prime * result + Float.floatToIntBits( y );
return result;
}
@Override
public boolean equals( Object obj )
{
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Vector other = (Vector)obj;
if (Float.floatToIntBits( x ) != Float.floatToIntBits( other.x )) return false;
if (Float.floatToIntBits( y ) != Float.floatToIntBits( other.y )) return false;
return true;
}
@Override
public String toString()
{
return "{" + x + "," + y + "}";
}
/**
* Returns and sets out to the minimum x and y coordinates from a and b.
*/
public static Vector min( Vector a, Vector b, Vector out )
{
out.x = StrictMath.min( a.x, b.x );
out.y = StrictMath.min( a.y, b.y );
return out;
}
/**
* Returns and sets out to the maximum x and y coordinates from a and b.
*/
public static Vector max( Vector a, Vector b, Vector out )
{
out.x = StrictMath.max( a.x, b.x );
out.y = StrictMath.max( a.y, b.y );
return out;
}
/**
* Return the dot product between the two vectors.
*/
public static float dot( Vector a, Vector b )
{
return a.x * b.x + a.y * b.y;
}
/**
* Return the distance (squared) between the two points.
*/
public static float distanceSq( Vector a, Vector b )
{
float dx = a.x - b.x;
float dy = a.y - b.y;
return dx * dx + dy * dy;
}
/**
* Return the distance between the two points.
*/
public static float distance( Vector a, Vector b )
{
float dx = a.x - b.x;
float dy = a.y - b.y;
return (float)Math.sqrt( dx * dx + dy * dy );
}
/**
* Returns and sets out to the cross between v and a.
*/
public static Vector cross( Vector v, float a, Vector out )
{
out.x = v.y * a;
out.y = v.x * -a;
return out;
}
/**
* Returns and sets out to the cross between a and v.
*/
public static Vector cross( float a, Vector v, Vector out )
{
out.x = v.y * -a;
out.y = v.x * a;
return out;
}
/**
* Returns the cross product between the two vectors a and b.
*/
public static float cross( Vector a, Vector b )
{
return a.x * b.y - a.y * b.x;
}
/**
* Returns the cross product between the two vectors {ax, ay} and {bx, by}.
*/
public static float cross( float ax, float ay, float bx, float by )
{
return ax * by - ay * bx;
}
/**
* Returns a vector that represents the given angle, where x = cos(angle) and y = sin(angle).
*/
public static Vector fromAngle( float angle )
{
return new Vector( (float)Math.cos( angle ), (float)Math.sin( angle ) );
}
/**
* Returns a new array of instantiated Vectors of the given length.
*/
public static Vector[] arrayOf( int length )
{
Vector[] array = new Vector[length];
while (--length >= 0)
{
array[length] = new Vector();
}
return array;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment