NEED HELP WITH MY GAME "FREEFALL"
Hello everyone. I am making this game called 'freefall' and I am stuck. I am new to Java, so is not fully aware of its capabilities. The code of the game is given below. The problem that I am facing as you would have noticed is that when I increase the angle of the stick the ball sinks into it. I know its because of certain approximations I have taken due to which the ball moves vertically down more, than is required. Can you guys tell me of any better methods to use to solve this issue. The stick can be rotated clockwise and counter clockwise by holding the left and right mouse buttons respectively. Also my code is really raw and unedited. Sorry for that. If you have any doubts let me know. Thank you for your time and suggestions in advance :)
import java.awt.*;
import java.awt.event.*;
import java.awt.Robot;
import javax.swing.*;
import java.awt.geom.*;
import java.awt.geom.Line2D.Float;
import java.lang.Object.*;
class BallDraw extends JFrame
{
static ImageIcon icon;
BallDraw()
{
super("Ball Dynamics");
//icon=new ImageIcon("FORD.JPG");
this.setCursor(Cursor.HAND_CURSOR);
setSize(800,600);
BallDynamics BDX=new BallDynamics();
//BDX.setOpaque(false);
//BallDynamics2 BDX2=new BallDynamics2();
getContentPane().add(BDX);
setVisible(true);
addMouseListener(new BallDynamics());
addMouseMotionListener(new BallDynamics());
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
}
}
public class BallDynamics extends JPanel implements Runnable,MouseListener,MouseMotionListener
{
private boolean running=false;
Line2D.Double line;
static Point2D linePt1;
static Point2D linePt2;
static Point2D linePt1TF;
static Point2D linePt2TF;
static AffineTransform aT;
int rebounce=0;
static int i=0;
int s=20;
static int rotDir;
static int HIT;
static int dropFlag=0;
static double newLineX1;
static double newLineY1;
static double newLineX2;
static double newLineY2;
static int angleMotion=0;
static int x1=240,x0=390,y1=330,y0=376;
static double y,a,b,c,slope;
static int landed;
static double height;
static double HB;
static double base;
static double newX1;
static double newY1;
static double newX2;
static double newY2;
static double dy;
static double dx;
static int intersects;
static int brot=0;
Thread ballThread1;
Ellipse2D.Double dCircle;
//simultaion instance variables
static double V,Vp=0,Vn=0,G=9.8,deltaT=0.85,YDn=0,Dp=0,XDn=390;
Robot PIX;
static double x;
public static void main(String args[])
{
BallDraw bdFrame=new BallDraw();
}
public void paint(Graphics g)
{
super.paint(g);
setDoubleBuffered(true);
System.out.println("paint here");
Graphics2D g2d=(Graphics2D)g;
g2d.setColor(Color.BLACK);
//g2d.drawImage(BallDraw.icon.getImage(),0,0,null);
setBackground(Color.black);
BasicStroke bs=new BasicStroke(.1f,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND);
g2d.setStroke(bs);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
System.out.println("Vel="+(Vn)+",Height="+(YDn));
dCircle=new Ellipse2D.Double(XDn,YDn,20,20);
g2d.draw(dCircle);
g2d.setPaint(new GradientPaint((float)XDn,(float)YDn,Color.white,(float)XDn+15,(float)YDn+15,Color.DARK_GRAY));
g2d.fill(dCircle);
BasicStroke bs1=new BasicStroke(5f,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND);
g2d.setStroke(bs1);
line=new Line2D.Double(300,400,500,400);
aT=new AffineTransform();
System.out.println("The value of i="+ i);
aT=AffineTransform.getRotateInstance((Math.PI*i/180),400,400);
g2d.transform(aT);
newLineDim(aT);
g2d.setPaint(new GradientPaint((float)XDn,(float)YDn,Color.white.darker(),(float)XDn,(float)YDn,Color.DARK_GRAY));
g2d.draw(line);
startBallAnim();
}
public void startBallAnim()
{
if(running==false && ballThread1==null)
{
System.out.println("ball thread started");
ballThread1=new Thread(this);
running=true;
ballThread1.start();
}
}
public void stopBallAnim()
{
ballThread1=null;
}
public void run()
{
/*try
{
PIX=new Robot();
//System.out.println(PIX.getPixelColor(400,(int)(BallDynamics.YDn+1)));
//System.out.println(Color.);
System.out.println("PIX object created after");
}
catch (AWTException e)
{
System.out.println("PIX object created after EX");
}*/
try
{
while(ballThread1.isAlive())
{
//pixelPos();
step();
repaint();
try
{
ballThread1.sleep(1000/30);
//ballThread1.sleep(1000/50);
}
catch (InterruptedException e)
{
return;
}
}
}
catch(NullPointerException e)
{
System.out.println("Ball animation stopped");
}
}
public void step()
{
if(rotDir==1)
i++;
else if(rotDir==2)
i--;
if(landed==0)
{
YDn +=0.025*Vn;
Vn += G;
}
/*if(dCircle.intersects(line))
yPix=(int)(BallDynamics.YDn+50);
xPix=400;
Color PixCo=PIX.getPixelColor(xPix,yPix);
//System.out.println(PIX.getPixelColor(400,(int)(BallDynamics.YDn-20)));
System.out.println(Color.BLACK);*/
if(line.intersects(XDn,YDn+5,20,20)==true && rebounce<3)
{
System.out.println("am in rebounce");
Vn =-.44*(Vn);
rebounce+=1;
System.out.println("rebounce="+rebounce);
}
if(rebounce>=3)
{
//stopBallAnim();
//if(landed==0)
//{
landed=1;
newLineX1=linePt1TF.getX();
newLineY1=linePt1TF.getY();
newLineX2=linePt2TF.getX();
newLineY2=linePt2TF.getY();
//dy=newLineY1-newLineY2;
//dx=newLineX1-newLineX2;
slope=Math.tan((Math.PI*i/180));
//slope=dy/dx;
b=YDn - slope*XDn;
//}
if(slope>0) //slope value is inverted
{
System.out.println("negative slope");
XDn=XDn+.00026*i*XDn;
}
else if(slope<0)
{
System.out.println("positive slope");
XDn=XDn-.00026*i*(-1)*XDn;
}
YDn=slope*XDn + b;
System.out.println(slope);
System.out.println("ball x="+(int)XDn);
System.out.println("ball y="+(int)YDn);
System.out.println("line x="+newX1);
System.out.println("line y="+newY1);
System.out.println("the end");
System.out.println("new="+linePt1TF);
System.out.println("new="+linePt2TF);
System.out.println("rebounce="+rebounce);
if((int)newLineX2+2<=(int)XDn)
{
Vn=50;
rebounce=0;
landed=0;
}
if((int)newLineX1-22>=(int)XDn)
{
Vn=50;
rebounce=0;
landed=0;
}
System.out.println("rebounce="+rebounce);
//System.out.println(line.getP1().distance(line.getP2()));
//if(line.intersects(XDn,YDn+5,20,20)==false)
//{
//stopBallAnim();
//}
}
}
public void mouseClicked(MouseEvent arg0)
{
}
public void mousePressed(MouseEvent arg0)
{
System.out.println("Mouse Pressed");
if(SwingUtilities.isLeftMouseButton(arg0))
{
System.out.println("left clicked");
rotDir=1;
}
else
{
System.out.println("right clicked");
rotDir=2;
}
System.out.println(rotDir);
}
public void mouseReleased(MouseEvent arg0)
{
System.out.println("Mouse released");
rotDir=0;
}
public void mouseEntered(MouseEvent arg0)
{
System.out.println("Mouse entered");
}
public void mouseExited(MouseEvent arg0)
{
System.out.println("Mouse exited");
}
public void newLineDim(AffineTransform aTXFER)
{
linePt1 = line.getP1();
linePt2 = line.getP2();
Point2D Pt1Xfer = new Point2D.Double();
Point2D Pt2Xfer = new Point2D.Double();
aTXFER.transform(linePt1, Pt1Xfer);
aTXFER.transform(linePt2, Pt2Xfer);
linePt1TF=Pt1Xfer;
linePt2TF=Pt2Xfer;
}
public void mouseDragged(MouseEvent arg0)
{
// TODO Auto-generated method stub
}
public void mouseMoved(MouseEvent arg0)
{
BallDynamics BDXS=new BallDynamics();
System.out.println("Mouse moved="+Cursor.HAND_CURSOR);
// TODO Auto-generated method stub
}
}
of course since you have so many variables and your code isnt the easiest to follow, all i can do is tell you what should work reguardless of what your variables mean.
after your ball hits your paddle, you should have the ball follow a parrallel line to that of the paddle where y = mx+(b+ball width+paddle width), recreate the line by taking the 4 vars you are using to draw your paddle find slope = Math.abs(y1-y2)/Math.abs(x1-x2) then solve for b by pugging in a pair on the line b = y1 - x1*slope, and then add the radius of the circle + the thickness of the line to b, and as the y value decreases, plug that into your parallel equation, as the paddle rotates, change the equation the y values are being plugged into
also rewite whatever makes the ball decide to go left or right based upon the slope of the line and not the direction of rotation, if the slope is negitive it will roll right, if the slope is positive it will roll left, if you rotate the paddle 91-179 degrees clockwise with your current code, it will still move to the right even though the slope should move it to the left
I showed you how to do that in reply 9 of your [url= http://forum.java.sun.com/thread.jspa?threadID=762521]Moving a ball over a rotateable line[/url] thread.
hey! is this Philip? well yes i did check that out but you see I am having the problem when the ball is moving over the inclined line. In the code you had produced the ball remains stationary after the ball is done with the bouncing.
When the ball starts moving down the line the x and y axis increments are not accurate enough for the ball to move along the line. Can you tell me how i can achieve this. If you run my code you ll see the issues with the game.
is this Philip?
Yes.
When the ball starts moving down the line the x and y axis increments are not accurate enough for the ball to move along the line. Can you tell me how i can achieve this.
The inclined plane part is a little different than the falling ball. But the locating of the ball is pretty close in both cases.
Try this:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.*;
public class TC extends JPanel implements ActionListener, ChangeListener {
DropController dropController = new DropController(this);
Ellipse2D.Double ball;
Line2D.Doubleline;
doubleinitialX;
doublefloor;
doublelastTheta = 0;
doubleg = 3.0;
doubledLast;
doublevLast;
doublexLast;
doublexVelocity;
doublerestitution = 0.75;
boolean isFalling;
boolean isRolling;
public void actionPerformed(ActionEvent e) {
String ac = e.getActionCommand();
if(ac.equals("drop")) {
isFalling = true;
dropController.start();
} else if(ac.equals("reset")) {
dropController.stop();
double pivotX = line.getX1() + (line.getX2() - line.getX1())/2;
double pivotY = line.getY1() + (line.getY2() - line.getY1())/2;
double verticalOffset = (ball.height/2)*(1.0 - 1.0/Math.cos(lastTheta));
double dx = pivotX - initialX - ball.getWidth()/2;
double rotationOffset = dx*Math.tan(lastTheta);
floor = pivotY + verticalOffset - rotationOffset;
ball.setFrame(initialX, 0, ball.width, ball.height);
vLast = 0;
dLast = 0;
isFalling = false;
isRolling = false;
repaint();
}
}
public void stateChanged(ChangeEvent e) {
JSlider slider = (JSlider)e.getSource();
int value = slider.getValue();
tilt(Math.toRadians(value));
repaint();
}
public void step() {
if(isFalling)
dropBall();
else if(isRolling) {
if(!hasClearedPlane())
rollBall();
else {
isRolling = false;
xVelocity = vLast*Math.cos(lastTheta);
vLast *=Math.sin(lastTheta);
xLast = 0;
dLast = 0;
freeFall();
}
} else
freeFall();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if(ball == null)
initGeom();
g2.setPaint(Color.green.darker());
g2.draw(line);
g2.setPaint(Color.red);
g2.fill(ball);
g2.setPaint(Color.blue);
int w = getWidth();
g2.draw(new Line2D.Double(w/4, floor, w*3/4, floor));
}
private void initGeom() {
int w = getWidth();
int h = getHeight();
int DIA = 30;
line = new Line2D.Double(w/4, h*3/4, w*3/4, h*3/4);
floor = line.getY1();
// Near the center is best for initial x value.
initialX = w*7/12.0;
double y = 0;
ball = new Ellipse2D.Double(initialX, y, DIA, DIA);
vLast = 0;
dLast = 0;
}
private void tilt(double theta) {
// Rotate line at its center.
Point2D p1 = line.getP1();
Point2D p2 = line.getP2();
double length = p1.distance(p2);
Point2D.Double pivot = new Point2D.Double();
pivot.x = p1.getX() + (p2.getX() - p1.getX())/2;
pivot.y = p1.getY() + (p2.getY() - p1.getY())/2;
double x = pivot.getX() + (length/2)*Math.cos(theta);
double y = pivot.getY() + (length/2)*Math.sin(theta);
p2.setLocation(x, y);
x = pivot.x + (length/2)*Math.cos(theta+Math.PI);
y = pivot.y + (length/2)*Math.sin(theta+Math.PI);
p1.setLocation(x, y);
line.setLine(p1, p2);
// Adjust floor/ball height on tilted line in two parts:
// Locate ball center above the intersection of the tilted line
// and the vertical line that passes through the ball center.
double verticalOffset = -(ball.height/2)/Math.cos(theta) + ball.height/2;
// Find vertical displacement of intersection of tilted line
// and vertical line caused by rotation angle of tilted line.
double dx = pivot.x - ball.getCenterX();
double rotationOffset = dx*Math.tan(theta);
// Find floor == point of contact between ball and the horizontal.
floor = pivot.y + verticalOffset - rotationOffset;
// If the animation is not running we can adjust the
// vertical position of ball to rest on floor.
if(!dropController.isRunning())
ball.y = floor - ball.height;
lastTheta = theta;
}
public void dropBall() {
// v = v0t + gt; t = 1
// d = d0 + v0t + (v - v0)t/2
double v = vLast + g;
double d = dLast + (v + vLast)/2.0;
double x = ball.getX();
double y = checkForContact(d);
ball.setFrameFromDiagonal(x, y, x + ball.width, y + ball.height);
repaint();
// If there was no contact with line, update state variables.
if(y == d) {
dLast = d;
vLast = v;
}
}
private double checkForContact(double y) {
if(y + ball.height >= floor) { // contact
// Find velocity at contact.
// v^2 = v0^2 + 2gd
// v = sqrt(v0*v0 + 2gd)
double distanceToContact = floor - ball.height - dLast;
double contactVelocity = Math.sqrt(vLast*vLast + 2.0*g*distanceToContact);
// Apply coefficient of restitution to kinetic energy.
// ke = (mv^2)/2
double ke = (contactVelocity*contactVelocity)/2.0;
double keAfter = restitution*ke;
// Calculate new rebound velocity.
double reboundVelocity = -Math.sqrt(2.0*keAfter);
// Reset variables to reflect the contact with line.
y = floor - ball.height;
vLast = reboundVelocity;
dLast = y;
// Determine the rebound height after contact.
// v2^2 = v1^2 + 2gd
// d = (v1^2)/2g
double reboundHeight = (reboundVelocity*reboundVelocity)/(2.0*g);
// Switch from falling to rolling/stop if rebound height is small.
if(reboundHeight < 1.0) {
isFalling = false;
if(lastTheta == 0.0)
dropController.stop();
else {
vLast = 0;
dLast = 0;
isRolling = true;
}
y = floor - ball.height;
}
}
return y;
}
private void rollBall() {
// v = v0t + gt*sin(theta); t = 1
// d = d0 + v0t + (v - v0)t/2
double v = vLast + g*Math.sin(lastTheta);
double d = dLast + (v + vLast)/2.0;
double x = ball.getX() + d*Math.cos(lastTheta);
double y = ball.getY() + d*Math.sin(lastTheta);
ball.setFrameFromDiagonal(x, y, x + ball.width, y + ball.height);
floor = ball.getY() + ball.getHeight();
repaint();
dLast = d;
vLast = v;
}
private boolean hasClearedPlane() {
if(lastTheta < 0.0)
return ball.getCenterX() < line.getX1();
else
return ball.getCenterX() > line.getX2();
}
private void freeFall() {
// v = v0t + gt; t = 1
// d = d0 + v0t + (v - v0)t/2
double v = vLast + g;
double d = dLast + (v + vLast)/2.0;
xLast += xVelocity;
double x = ball.getX() + xLast;
double y = ball.getY() + d;
ball.setFrameFromDiagonal(x, y, x + ball.width, y + ball.height);
floor = ball.getY() + ball.getHeight();
repaint();
if(ball.getY() > getHeight())
dropController.stop();
else {
dLast = d;
vLast = v;
}
}
private JPanel getControls() {
JSlider slider = new JSlider(JSlider.HORIZONTAL, -45, 45, 0);
slider.setPaintTicks(true);
slider.setMinorTickSpacing(5);
slider.setMajorTickSpacing(15);
slider.setPaintLabels(true);
slider.addChangeListener(this);
JButton drop = new JButton("drop");
drop.setActionCommand("drop");
drop.addActionListener(this);
JButton reset = new JButton("reset");
reset.setActionCommand("reset");
reset.addActionListener(this);
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(1,0,1,0);
gbc.weightx = 1.0;
panel.add(drop, gbc);
gbc.gridwidth = gbc.REMAINDER;
panel.add(reset, gbc);
gbc.gridwidth = 2;
gbc.fill = gbc.HORIZONTAL;
panel.add(slider, gbc);
return panel;
}
public static void main(String[] args) {
TC test = new TC();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(test);
f.getContentPane().add(test.getControls(), "Last");
f.setSize(500,500);
f.setLocation(200,100);
f.setVisible(true);
}
}
class DropController implements Runnable {
TC trueContact;
Thread thread;
boolean animating;
public DropController(TC tc) {
trueContact = tc;
animating = false;
}
public void run() {
while(animating) {
try {
Thread.sleep(100);
} catch(InterruptedException ie) {
System.out.printf("sleep interrupted%n");
animating = false;
}
trueContact.step();
}
}
public void start() {
if(!animating) {
animating = true;
thread = new Thread(this);
thread.setPriority(Thread.NORM_PRIORITY);
thread.start();
}
}
public void stop() {
if(animating) {
animating = false;
thread.interrupt();
thread = null;
System.out.printf("thread stopped%n");
}
}
public boolean isRunning() { return animating; }
}
Hey Philip I really appreciate doing so much for me man...Thanks! But when i ran your code i found the same issue i am facing i.e. when the ball is rolling down the inclined plane just rotate the line so that the slope is inverted and then invert the slope again...like trying to balance the ball on the stick and then you will notice that the ball sinks into the stick. Think there is solution for that?
rotate the line so that the slope is inverted and then invert the slope again...
Think there is solution for that?
To change the angle of rotation of the line while the ball is moving. To do this I
started with a new app that focuses on the ball rolling on an inclined plane. Along
the way I found some errors in TC above; I'll try to list them in an edit to that reply.
This is a bit choppy at times but I think you will be able to work with it.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.*;
public class RollingBall extends JPanel implements ActionListener, ChangeListener {
RollController rollController = new RollController(this);
Ellipse2D.Double ball;
Line2D.Doubleline;
Point2D.Doublepivot;
doubleinitialX;
doubleinitialY;
doublefloor;
doubleg = 1.25;
doubletheta = 0;
doublevLast;
public void actionPerformed(ActionEvent e) {
String ac = e.getActionCommand();
if(ac.equals("roll")) {
rollController.start();
} else if(ac.equals("reset")) {
rollController.stop();
double verticalOffset = (ball.height/2)*(1.0 - 1.0/Math.cos(theta));
double dx = pivot.x - initialX - ball.getWidth()/2;
double rotationOffset = dx*Math.tan(theta);
floor = pivot.y + verticalOffset - rotationOffset;
ball.setFrame(initialX, initialY, ball.width, ball.height);
initialize();
repaint();
}
}
public void stateChanged(ChangeEvent e) {
JSlider slider = (JSlider)e.getSource();
theta = Math.toRadians(slider.getValue());
tilt();
repaint();
}
public void roll() {
if(isOverLine())
rollBall();
else {
rollController.stop();
initialize();
}
}
private void initialize() {
positionBall();
vLast = 0;
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if(ball == null)
initGeom();
g2.setPaint(Color.green.darker());
g2.draw(line);
g2.setPaint(Color.red);
g2.fill(ball);
g2.setPaint(Color.blue);
double cx = ball.getCenterX();
g2.draw(new Line2D.Double(cx-50, floor, cx+50, floor));
}
private void initGeom() {
int w = getWidth();
int h = getHeight();
int DIA = 30;
Point2D p1 = new Point2D.Double(w/16, h*7/12);
Point2D p2 = new Point2D.Double(w*15/16, h*7/12);
line = new Line2D.Double(p1, p2);
double x = p1.getX() + (p2.getX() - p1.getX())/2;
double y = p1.getY() + (p2.getY() - p1.getY())/2;
pivot = new Point2D.Double(x, y);
floor = line.getY1();
initialX = w/2.0;
initialY = line.getY1() - DIA;
ball = new Ellipse2D.Double(initialX, initialY, DIA, DIA);
initialize();
}
private void tilt() {
// Rotate line at its center.
Point2D p1 = line.getP1();
Point2D p2 = line.getP2();
double length = p1.distance(p2);
double x = pivot.getX() + (length/2)*Math.cos(theta);
double y = pivot.getY() + (length/2)*Math.sin(theta);
p2.setLocation(x, y);
x = pivot.x + (length/2)*Math.cos(theta+Math.PI);
y = pivot.y + (length/2)*Math.sin(theta+Math.PI);
p1.setLocation(x, y);
line.setLine(p1, p2);
positionBall();
}
private void positionBall() {
setFloor();
// Make sure ball contact is between ends points of line.
if(!isOverLine())
moveBallOverLine();
// If the animation is not running we can adjust the
// vertical position of ball to rest on floor.
if(!rollController.isRunning())
ball.y = floor - ball.height;
}
private void setFloor() {
// Adjust floor/ball height on tilted line in two parts:
// Locate ball center above the intersection of the tilted line
// and the vertical line that passes through the ball center.
double verticalOffset = (ball.height/2)*(1.0 - 1.0/Math.cos(theta));
// Find vertical displacement of intersection of tilted line
// and vertical line caused by rotation angle of tilted line.
double dx = pivot.x - ball.getCenterX();
double rotationOffset = dx*Math.tan(theta);
// Find floor == point of contact between ball and the horizontal.
floor = pivot.y + verticalOffset - rotationOffset;
}
private void rollBall() {
// v = v0t + gt*sin(theta); t = 1
// d = d0 + v0t + (v - v0)t/2
double v = vLast + g*Math.sin(theta);
double d = (v + vLast)/2.0;
double x = ball.getX() + d*Math.cos(theta);
double y = getBallY() + d*Math.sin(theta);
ball.setFrameFromDiagonal(x, y, x + ball.width, y + ball.height);
floor = ball.getY() + ball.getHeight();
repaint();
vLast = v;
}
private double getBallY() {
double phi = theta + Math.PI/2;
double contactX = ball.getCenterX() + (ball.getWidth()/2)*Math.cos(phi);
double contactY = ball.getCenterY() + (ball.getHeight()/2)*Math.sin(phi);
double normalX = ball.getCenterX() + 200*Math.cos(phi);
double normalY = ball.getCenterY() + 200*Math.sin(phi);
Point2D.Double intxn = getIntersection(normalX, normalY);
double dy = intxn.y - contactY;
return ball.getY() + dy;
}
private Point2D.Double getIntersection(double endX, double endY) {
double x1 = line.getX1();
double y1 = line.getY1();
double x2 = line.getX2();
double y2 = line.getY2();
double x3 = ball.getCenterX();
double y3 = ball.getCenterY();
double x4 = endX;
double y4 = endY;
double aDividend = (x4 - x3)*(y1 - y3) - (y4 - y3)*(x1 - x3);
double aDivisor = (y4 - y3)*(x2 - x1) - (x4 - x3)*(y2 - y1);
double ua = aDividend / aDivisor;
double bDividend = (x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3);
double bDivisor = (y4 - y3)*(x2 - x1) - (x4 - x3)*(y2 - y1);
double ub = bDividend / bDivisor;
Point2D.Double p = new Point2D.Double();
p.x = x1 + ua * (x2 - x1);
p.y = y1 + ua * (y2 - y1);
return p;
}
private boolean isOverLine() {
// find contact point of ball with (infinite) line
double phi = theta + Math.PI/2;
double contactX = ball.getCenterX() + (ball.getWidth()/2)*Math.cos(phi);
double contactY = ball.getCenterY() + (ball.getHeight()/2)*Math.sin(phi);
double d1 = pivot.distance(contactX, contactY);
double d2 = pivot.distance(contactX, contactY);
double pivotToEnd = pivot.distance(line.getP1());
return pivotToEnd > (d1 < d2 ? d1 : d2);
}
private void moveBallOverLine() {
double d1 = line.getP1().distance(ball.getCenterX(), ball.getCenterY());
double d2 = line.getP2().distance(ball.getCenterX(), ball.getCenterY());
double phi = theta - Math.PI/2;
if(d1 < d2) {
ball.x = line.getX1()+1;
ball.y = line.getY1()+1;
} else if(d1 > d2) {
ball.x = line.getX2()-1;
ball.y = line.getY2()-1;
}
ball.x += (ball.getWidth()/2)*Math.cos(phi) - ball.getWidth()/2;
ball.y += (ball.getHeight()/2)*Math.sin(phi) - ball.getHeight()/2;
setFloor();
}
private JPanel getControls() {
JSlider slider = new JSlider(JSlider.HORIZONTAL, -45, 45, 0);
slider.setPaintTicks(true);
slider.setMinorTickSpacing(5);
slider.setMajorTickSpacing(15);
slider.setPaintLabels(true);
slider.addChangeListener(this);
JButton roll = new JButton("roll");
roll.setActionCommand("roll");
roll.addActionListener(this);
JButton reset = new JButton("reset");
reset.setActionCommand("reset");
reset.addActionListener(this);
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(1,0,1,0);
gbc.weightx = 1.0;
panel.add(roll, gbc);
gbc.gridwidth = gbc.REMAINDER;
panel.add(reset, gbc);
gbc.gridwidth = 2;
gbc.fill = gbc.HORIZONTAL;
panel.add(slider, gbc);
return panel;
}
public static void main(String[] args) {
RollingBall test = new RollingBall();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(test);
f.getContentPane().add(test.getControls(), "Last");
f.setSize(500,500);
f.setLocation(200,100);
f.setVisible(true);
}
}
class RollController implements Runnable {
RollingBall rollingBall;
Thread thread;
boolean animating;
public RollController(RollingBall br) {
rollingBall = br;
animating = false;
}
public void run() {
while(animating) {
try {
Thread.sleep(50);
} catch(InterruptedException ie) {
System.out.printf("sleep interrupted%n");
animating = false;
}
rollingBall.roll();
}
}
public void start() {
if(!animating) {
animating = true;
thread = new Thread(this);
thread.setPriority(Thread.NORM_PRIORITY);
thread.start();
}
}
public void stop() {
if(animating) {
animating = false;
thread.interrupt();
thread = null;
System.out.printf("thread stopped%n");
}
}
public boolean isRunning() { return animating; }
}
found some errors in TC above; I'll try to list them in an edit to that reply
Changes to improve the TC class above:
Remove dLast from the rollBall method.
Remove xlast member variable declaration.
Remove xLast from step method.
In freeFall remove the line
xLast += xVelocity;
and replace
double x = ball.getX() + xLast;
with
double x = ball.getX() + xVelocity;
Hey Philip!!! awesome man!! Your code is perfect!! I am not able to assign you the remaining duke dollars cause the icon is not showing. Anyways thanks a lot. I have a few doubts which you could probably help me out with. Can you give me a detailed explanation of what is beiing done in the following part of your code. I know its the key to solving the problem I faced in my code. I see a lot of calculations. Are these to get rid of all the offsets. It would be really helpful if you just give me an explanation on the technique you have used. Specially the use of variables aDividend, aDivisor, bDivident and bDivisor. Thanks again.
private void rollBall() {
// v = v0t + gt*sin(theta); t = 1
// d = d0 + v0t + (v - v0)t/2
double v = vLast + g*Math.sin(theta);
double d = (v + vLast)/2.0;
double x = ball.getX() + d*Math.cos(theta);
double y = getBallY() + d*Math.sin(theta);
ball.setFrameFromDiagonal(x, y, x + ball.width, y + ball.height);
floor = ball.getY() + ball.getHeight();
repaint();
vLast = v;
}
private double getBallY() {
double phi = theta + Math.PI/2;
double contactX = ball.getCenterX() + (ball.getWidth()/2)*Math.cos(phi);
double contactY = ball.getCenterY() + (ball.getHeight()/2)*Math.sin(phi);
double normalX = ball.getCenterX() + 200*Math.cos(phi);
double normalY = ball.getCenterY() + 200*Math.sin(phi);
Point2D.Double intxn = getIntersection(normalX, normalY);
double dy = intxn.y - contactY;
return ball.getY() + dy;
}
private Point2D.Double getIntersection(double endX, double endY) {
double x1 = line.getX1();
double y1 = line.getY1();
double x2 = line.getX2();
double y2 = line.getY2();
double x3 = ball.getCenterX();
double y3 = ball.getCenterY();
double x4 = endX;
double y4 = endY;
double aDividend = (x4 - x3)*(y1 - y3) - (y4 - y3)*(x1 - x3);
double aDivisor = (y4 - y3)*(x2 - x1) - (x4 - x3)*(y2 - y1);
double ua = aDividend / aDivisor;
double bDividend = (x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3);
double bDivisor = (y4 - y3)*(x2 - x1) - (x4 - x3)*(y2 - y1);
double ub = bDividend / bDivisor;
Point2D.Double p = new Point2D.Double();
p.x = x1 + ua * (x2 - x1);
p.y = y1 + ua * (y2 - y1);
return p;
}
It would be really helpful if you just give me an explanation on the technique you have used.
See comments in code below.
Specially the use of variables aDividend, aDivisor, bDivident and bDivisor.
See this [url=http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/]line intersection technique[/url].
private void rollBall() {
// v = v0t + gt*sin(theta); t = 1
// d = d0 + v0t + (v - v0)t/2
double v = vLast + g*Math.sin(theta);
double d = (v + vLast)/2.0;
double x = ball.getX() + d*Math.cos(theta);
// Since the user can change the line angle while the ball is
// rolling we need to find out where the ball contacts the
// line in this moment of time.
double y = getBallY() + d*Math.sin(theta);
ball.setFrameFromDiagonal(x, y, x + ball.width, y + ball.height);
floor = ball.getY() + ball.getHeight();
repaint();
vLast = v;
}
private double getBallY() {
// The angle of the line that passes between the center of the
// ball and through the intersection of ball and inclined plane,
// the point of contact. Angles are measured positive clockwise.
double phi = theta + Math.PI/2;
// Find the point of contact between ball and inclined plane at
// its angle of rotation that existed during the last call to
// rollBall. The ball has kept the location for us.
double contactX = ball.getCenterX() + (ball.getWidth()/2)*Math.cos(phi);
double contactY = ball.getCenterY() + (ball.getHeight()/2)*Math.sin(phi);
// Imagine a line of (arbitrary) length 200 with origin at the
// center of the ball. Position it with angle phi and find its
// end coordinates. This line is normal to the inclined plane.
double normalX = ball.getCenterX() + 200*Math.cos(phi);
double normalY = ball.getCenterY() + 200*Math.sin(phi);
// Find the intersection of the inclined plane with this new
// construction line. This is the point where the ball contacts
// the inclined plane at its current (line) position (which the
// user may have changed since the last call to rollBall).
Point2D.Double intxn = getIntersection(normalX, normalY);
// Adjust the ball vertically by the amount of the difference
// between the current ball position (before last line rotation)
// and new contact point of ball and line. If the user has not
// changed the angle of rotation of line since the last call to
// rollBall we can expect dy to be zero.
double dy = intxn.y - contactY;
return ball.getY() + dy;
}
