Hey Everyone!!
A recent thread (http://www.kirupa.com/forum/showthread.php?t=335736) got me thinking about line/point collisions, so I put together some experimental code to test some of the math. But, I’ve run into a problem… I hope someone out there can help me solve it…??
The code (with the SWF in the zip file attached) creates a line and point. The point falls toward the line, and bounces away at the correct angle. This image shows what it does:
It works fine, I have no problem with the mechanics or the math behind it. The point is deflected properly even at high speed.
The problem is this: As the point’s velocity decreases, it eventually settles on the line. But, after a certain minimum threshold, it falls through. I’d like to find a solution where the ball comes to rest on the line and doesn’t fall through, not matter what other forces might be acting on it.
I’m not sure why this happens, but I think I know where in the code the problem lies. I think it has something to do with the way the code calculates the intersection point in relation to the current velocity.
There’s a lot of code, but most it is used for setting up, some calculation methods, getters and setters used to calulate velocity and the actual Verlet integration (Sorry the code isn’t the prettiest and not optimized at all… I’m still in the R&D stage!) So you can safely ignore the 99% of it that’s working fine. But, I think the problem is right here (I’ve used “ratio” instead of the usual “t”):
[AS]
var ratio:Number = findIntersection(vx, vy, _point, _lineA, _lineB);
if (ratio > 0 && ratio < 1)
{
//… calculates the angle of deflection and new velocity
//Move the point to the intersection with the line
setX = xPos + vx * ratio;
setY = yPos + vy * ratio;
//xPos += newVx * ratio;
//yPos += newVy * ratio;
//apply new vx and vy and add bounce dampening
vx = newVx * 0.6;
vy = newVy * 0.6;
}
[/AS]
I think the problem is that the ratio between 0 and 1 (which determines a collision with the line) is being returned as 0 when the point’s velocity appraoches 0. This makes sense to me because obviously it means the point’s motion vector is almost zero too. But… what’s the solution to preventing or overriding this?
I have found one possible solution. You can find out out whether the point is above or below (to the left or right of) the line like this: extend a vector from the point to the line’s left and right normals (lx,y and rx,ry). Whichever is shorter determines which side of the line the point is on. I’ve tested this, and it works just fine (the point won’t fall through if this is checked). But, I needed a lot of extra code to manage tracking which side of the line the point was one, and I wasn’t sure that it was going to be robust enough for production level code.
I also discovered that you can use the dot product between the point’s motion vector and the line’s normal (dotProduct_2) to find out which side of the line you’re on:
[AS]
var lineSide:String;
if(dotProduct_2 < 0)
{
lineSide = “Left”
}
else
{
lineSide = “Right”
}
trace(lineSide);
[/AS]
My instincts tell me that this is part of the solution, but so far I haven’t been able to put the final pieces together. Any thoughts?
Anyway, I know this is pretty advanced stuff (it is for me!) so I hope some of you are up for a challenge!!
Here’s the full code (it’s in the attached ZIP too), and thanks so much for taking your time with this!
[AS]
package
{
import flash.events.Event;
import flash.display.Sprite;
import flash.display.Shape;
import flash.geom.Point;
public class MiniBounce extends Sprite
{
private var _circle:Shape;
private var _point:Point;
private var _lineA:Point;
private var _lineB:Point;
private var _lineView:Shape;
private var _previousX:Number;
private var _previousY:Number;
private var _temporaryX:Number;
private var _temporaryY:Number;
private var _xPos:Number;
private var _yPos:Number;
private var _vx:Number;
private var _vy:Number;
private var _accelerationX:Number;
private var _accelerationY:Number;
private var _gravity:Number;
[SWF(width="550", height="400", backgroundColor="#FFFFFF", frameRate="60")]
public function MiniBounce():void
{
//A and B points of the vector the point will hit
_lineA = new Point(0, 250);
_lineB = new Point(550, 300);
//A line to display the vector
_lineView = new Shape();
_lineView.graphics.lineStyle(1)
_lineView.graphics.moveTo(_lineA.x, _lineA.y);
_lineView.graphics.lineTo(_lineB.x, _lineB.y);
addChild(_lineView);
//The point model that collides with the line
_point = new Point(150, 150)
//Values to calculate the point's velocity
_xPos = _point.x;
_yPos = _point.y;
_previousX = _point.x;
_previousY = _point.y;
_accelerationX = 0;
_accelerationY = 0;
_vx = 0;
_vy = 0;
//Circle shape that displays the points position
_circle = new Shape();
_circle.graphics.beginFill(0x000000);
_circle.graphics.drawCircle(0, 0, 3);
_circle.graphics.endFill();
addChild(_circle);
//Gravity
_gravity = 0.1;
addEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
private function enterFrameHandler(event:Event):void
{
//Integrate (Calculate velocity based on
//the difference betweencurrent and previous positions)
_temporaryX = xPos;
_temporaryY = yPos;
_accelerationX = vx;
_accelerationY = vy;
xPos += _accelerationX;
yPos += _accelerationY + _gravity;
_previousX = temporaryX;
_previousY = temporaryY;
_point.x = xPos;
_point.y = yPos;
//Find the line's vx, vy, dx, dy
var line_Vx:Number = (_lineB.x - _lineA.x);
var line_Vy:Number = (_lineB.y - _lineA.y);
var line_Length:Number = Math.sqrt(line_Vx * line_Vx + line_Vy * line_Vy);
var line_Dx:Number = line_Vx / line_Length;
var line_Dy:Number = line_Vy / line_Length;
//Find out if the point intersects the line
var ratio:Number = findIntersection(vx, vy, _point, _lineA, _lineB);
if (ratio > 0 && ratio < 1)
{
//We have a collision!
//Calculate the collision angle
//1. Project the motion vector onto the line vector
var dotProduct_1:Number = dotProduct(vx, vy, line_Vx, line_Vy);
//2. Find the vx and vy of the new projected vector
var projection_1_Vx:Number = dotProduct_1 * line_Dx;
var projection_1_Vy:Number = dotProduct_1 * line_Dy;
//3. Project the motion vector onto line's normal
var dotProduct_2:Number = dotProductNormal(vx, vy, line_Vx, line_Vy);
//4. Find the vx and vy of the new projected vector
var projection_2_Vx:Number = dotProduct_2 * (line_Vy / line_Length);
var projection_2_Vy:Number = dotProduct_2 * (-line_Vx / line_Length);
//Reverse the velocity and calculate the new vx and vy
projection_2_Vx *= -1;
projection_2_Vy *= -1;
var newVx:Number = projection_1_Vx + projection_2_Vx;
var newVy:Number = projection_1_Vy + projection_2_Vy;
//Move the point to the intersection with the line
setX = xPos + vx * ratio;
setY = yPos + vy * ratio;
//xPos += newVx * ratio;
//yPos += newVy * ratio;
//apply new vx and vy and add bounce dampening
vx = newVx * 0.6;
vy = newVy * 0.6;
//What about gravity?
//_gravity = 0;
//Find out which side of the line the collision
//occurs on
var lineSide:String;
if(dotProduct_2 < 0)
{
lineSide = "Left"
}
else
{
lineSide = "Right"
}
trace(lineSide);
}
//Update the circle's position on the stage
_circle.x = xPos;
_circle.y = yPos;
//wrap
if(yPos > 400)
{
setY = 0;
}
if(xPos > stage.stageWidth)
{
setX = 0;
}
if(xPos < 0)
{
setX = stage.stageWidth;
}
}
public function findIntersection(vx:Number, vy:Number, v1A:Point, v2A:Point, v2B:Point):Number
{
//Calcuate the vx and vy
var v1_Vx:Number = vx;
var v1_Vy:Number = vy;
var v2_Vx:Number = v2B.x - v2A.x;
var v2_Vy:Number = v2B.y - v2A.y;
//Calculate 2 new vectors used to find the intersection point
//and to make sure that the point intersects within the length
//of the line
var v3_Vx:Number = v2A.x - v1A.x;
var v3_Vy:Number = v2A.y - v1A.y;
var v4_Vx:Number = v1A.x - v2A.x;
var v4_Vy:Number = v1A.y - v2A.y;
var ratio1:Number = perProduct(v3_Vx, v3_Vy, v2_Vx, v2_Vy ) / perProduct(v1_Vx, v1_Vy, v2_Vx, v2_Vy);
var ratio2:Number = perProduct(v4_Vx, v4_Vy, v1_Vx, v1_Vy) / perProduct(v2_Vx, v2_Vy, v1_Vx, v1_Vy);
if (ratio1 > 0 && ratio1 <= 1 && ratio2 > 0 && ratio2 <= 1)
{
//Return the ratio only if the point intersects within
//the length of the line
return ratio1;
}
else
{
return 0;
}
}
public function dotProductNormal(vx1:Number, vy1:Number, vx2:Number, vy2:Number):Number
{
//dot product between v1 and v2's normal
var dotProduct:Number = ((vx1 * (vy2 / Math.sqrt(vx2 * vx2 + vy2 * vy2)))
+ (vy1 * (-vx2 / Math.sqrt(vx2 * vx2 + vy2 * vy2))));
return dotProduct;
}
public function dotProduct(vx1:Number, vy1:Number, vx2:Number, vy2:Number):Number
{
var dotProduct:Number = ((vx1 * (vx2/ Math.sqrt(vx2 * vx2 + vy2 * vy2)))
+ (vy1 * (vy2 / Math.sqrt(vx2 * vx2 + vy2 * vy2))));
return dotProduct;
}
public function perProduct(vx1:Number, vy1:Number, vx2:Number, vy2:Number):Number
{
var perProduct:Number = vx1 * vy2 - vy1 * vx2;
return perProduct;
}
//temporaryX
public function get temporaryX():Number
{
return _temporaryX;
}
public function set temporaryX(value:Number):void
{
_temporaryX = value;
}
//temporaryY
public function get temporaryY():Number
{
return _temporaryY;
}
public function set temporaryY(value:Number):void
{
_temporaryY = value;
}
//previousX
public function get previousX():Number
{
return _previousX;
}
public function set previousX(value:Number):void
{
previousX = value;
}
//previousY
public function get previousY():Number
{
return _previousY;
}
public function set previousY(value:Number):void
{
previousY = value;
}
//vx
public function get vx():Number
{
return _xPos - _previousX;
}
public function set vx(value:Number):void
{
_previousX = _xPos - value;
}
//vy
public function get vy():Number
{
return _yPos - _previousY;
}
public function set vy(value:Number):void
{
_previousY = _yPos - value;
}
//xPos
public function get xPos():Number
{
return _xPos;
}
public function set xPos(value:Number):void
{
_xPos = value;
}
//yPos
public function get yPos():Number
{
return _yPos;
}
public function set yPos(value:Number):void
{
_yPos = value;
}
//setX
public function set setX(value:Number):void
{
_previousX = value - vx;
_xPos = value;
}
//setY
public function set setY(value:Number):void
{
_previousY = value - vy;
_yPos = value;
}
}
}
[/AS]