Created
October 29, 2014 15:07
-
-
Save ClickerMonkey/4a6eeb1be826d969fb9f to your computer and use it in GitHub Desktop.
Priori intersection algorithms for circles, rectangles, and planes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ) | |
{ | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* 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