Created
December 26, 2016 19:53
-
-
Save nixwins/c28987540eb4b13aaa8dd6a2119687db to your computer and use it in GitHub Desktop.
For stackoverlow
This file contains 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
package com.example.android.apis.mycircles; | |
import android.graphics.Canvas; | |
import android.graphics.Color; | |
import android.graphics.Paint; | |
import android.graphics.RectF; | |
import android.util.Log; | |
import com.example.android.apis.mycircles.collisionphysics.CollisionPhysics; | |
import com.example.android.apis.mycircles.collisionphysics.CollisionResponse; | |
import java.util.ArrayList; | |
import java.util.Random; | |
/** | |
* Created by nixwins on 12/26/16. | |
*/ | |
public class Ball { | |
private float ballRadius; | |
private float ballX = this.ballRadius + this.ballRadius/4; | |
private float ballY = this.ballRadius + this.ballRadius/2; | |
private float ballSpeedX; | |
private float ballSpeedY; | |
private RectF ballBounds; | |
private Paint paint; | |
// For collision detection and response | |
// Maintain the response of the earliest collision detected | |
// by this ball instance. (package access) | |
CollisionResponse earliestCollisionResponse = new CollisionResponse(); | |
// Working copy for computing response in intersect(ContainerBox box), | |
// to avoid repeatedly allocating objects. | |
private CollisionResponse tempResponse = new CollisionResponse(); | |
public Ball(float ballX, float ballY, float ballRadius, float speed, float angleInDegree) { | |
this.ballRadius = ballRadius; | |
this.ballX = ballX; | |
this.ballY = ballY; | |
Random random = new Random(); | |
this.ballSpeedX = (float)(speed * Math.cos(Math.toRadians(angleInDegree))); | |
this.ballSpeedY = (float)(-speed * (float)Math.sin(Math.toRadians(angleInDegree))); | |
this.ballBounds = new RectF(); | |
this.paint = new Paint(); | |
this.paint.setColor(Color.BLACK); | |
int r = random.nextInt(256); | |
int g = random.nextInt(256); | |
int b = random.nextInt(256); | |
if(r != 0 && g != 0 && b != 0) paint.setARGB(255, r, g, b); | |
} | |
/** | |
* Check if this ball collides with the container box in the coming time-step. | |
* | |
* @param box: container (obstacle) for this ball | |
*/ | |
public void intersect(Canvas box) { | |
// Call movingPointIntersectsRectangleOuter, which returns the | |
// earliest collision to one of the 4 borders, if collision detected. | |
//Log.d("Collision", "Collide INTER"); | |
CollisionPhysics.pointIntersectsRectangleOuter( | |
this.ballX, this.ballY, this.ballSpeedX, this.ballSpeedY, this.ballRadius, | |
0, 0, box.getWidth(), box.getHeight(), | |
1.0f, tempResponse); | |
if (tempResponse.t < earliestCollisionResponse.t) { | |
earliestCollisionResponse.copy(tempResponse); | |
} | |
} | |
/** | |
* Update the states of this ball for one time-step. | |
* Move for one time-step if no collision occurs; otherwise move up to | |
* the earliest detected collision. | |
*/ | |
public void update() { | |
// Check the earliest collision detected for this ball stored in | |
// earliestCollisionResponse. | |
if (earliestCollisionResponse.t <= 1.0f) { // Collision detected | |
// This ball collided, get the new position and speed | |
this.ballX = earliestCollisionResponse.getNewX(this.ballX, this.ballSpeedX); | |
this.ballY = earliestCollisionResponse.getNewY(this.ballY, this.ballSpeedY); | |
this.ballSpeedX = (float)earliestCollisionResponse.newSpeedX; | |
this.ballSpeedY = (float)earliestCollisionResponse.newSpeedY; | |
// Log.d("Collision", "Collide W H"); | |
} else { // No collision in this coming time-step | |
// Make a complete move | |
this.ballX += this.ballSpeedX; | |
this.ballY += this.ballSpeedY; | |
} | |
// Clear for the next collision detection | |
earliestCollisionResponse.reset(); | |
} | |
public void draw(Canvas canvas){ | |
this.ballBounds.set(this.ballX - this.ballRadius, this.ballY-this.ballRadius, this.ballX+ballRadius, this.ballY + ballRadius); | |
canvas.drawOval(this.ballBounds, paint); | |
} | |
public void collide(ArrayList<Ball> balls){ | |
//Log.d("TEst", "LOG"); | |
// Calculate difference between centres | |
float distX = balls.get(0).getBallX() - balls.get(1).getBallX(); | |
float distY = balls.get(0).getBallY() - balls.get(1).getBallY(); | |
// Get distance with Pythagora | |
double dist = Math.sqrt((distX * distX) + (distY * distY)); | |
float r = ballRadius + ballRadius; | |
if ((float) dist <= r) { | |
Log.d("Collide", "Detected"); | |
this.ballSpeedX = -this.ballSpeedX; | |
this.ballSpeedY = -this.ballSpeedY++; | |
} | |
/*for(int i=0; i < balls.size(); i++) { | |
for(int j=1; j<balls.size(); j++) { | |
// Calculate difference between centres | |
float distX = balls.get(i).getBallX() - balls.get(j).getBallX(); | |
float distY = balls.get(i).getBallY() - balls.get(j).getBallY(); | |
// Get distance with Pythagora | |
double dist = Math.sqrt((distX * distX) + (distY * distY)); | |
*//*double distance = Math.sqrt(((balls.get(0).getBallX() - balls.get(1).getBallX()) * (balls.get(0).getBallX() - balls.get(1).getBallX())) + ((balls.get(0).getBallY() | |
- balls.get(1).getBallY()) * (balls.get(0).getBallY() - balls.get(1).getBallY())));*//* | |
float r = ballRadius + ballRadius; | |
if ((float) dist <= r) { | |
Log.d("Collide", "Detected"); | |
} | |
} | |
}*/ | |
} | |
public void setBallX(float ballX) { | |
this.ballX = ballX; | |
} | |
public void setBallY(float ballY) { | |
this.ballY = ballY; | |
} | |
public void setBallSpeedX(float ballSpeedX) { | |
this.ballSpeedX = ballSpeedX; | |
} | |
public void setBallSpeedY(float ballSpeedY) { | |
this.ballSpeedY = ballSpeedY; | |
} | |
public float getBallSpeedX() { | |
return ballSpeedX; | |
} | |
public float getBallSpeedY() { | |
return ballSpeedY; | |
} | |
public float getBallX() { | |
return ballX; | |
} | |
public float getBallY() { | |
return ballY; | |
} | |
public float getSpeed() { | |
return (float)Math.sqrt(ballSpeedX * ballSpeedX + ballSpeedY * ballSpeedY); | |
} | |
/** Return the direction of movement in degrees (counter-clockwise). */ | |
public float getMoveAngle() { | |
return (float)Math.toDegrees(Math.atan2(-ballSpeedY, ballSpeedX)); | |
} | |
/** Return mass */ | |
public float getMass() { | |
return new Double(Math.ceil(ballRadius * ballRadius * Math.PI / 750)).floatValue(); | |
// return ballRadius * ballRadius * ballRadius / 1000f; | |
} | |
/*Testing */ | |
public void setBallSpeed(float seedX, float speedY){ | |
this.ballSpeedX = seedX; | |
this.ballSpeedY = speedY; | |
} | |
public void update(int xMin, int xMax, int yMin, int yMax){ | |
this.ballX += this.ballSpeedX; | |
this.ballY += this.ballSpeedY; | |
if(this.ballX + this.ballRadius > xMax){ | |
this.ballSpeedX = -this.ballSpeedX; | |
this.ballX = xMax - this.ballRadius; | |
}else if(this.ballX - this.ballRadius < xMin){ | |
this.ballSpeedX = -this.ballSpeedX; | |
this.ballX = xMin + this.ballRadius; | |
} | |
if(this.ballY + this.ballRadius > yMax){ | |
this.ballSpeedY = - this.ballSpeedY; | |
this.ballY = yMax - this.ballRadius; | |
}else if(this.ballY - this.ballRadius < yMin){ | |
this.ballSpeedY = -this.ballSpeedY; | |
this.ballY = yMin + this.ballRadius; | |
} | |
} | |
public void moveOneStepWithCollisionDetection(Canvas box) { | |
// Get the ball's bounds, offset by the radius of the ball | |
float ballMinX = 0 + ballRadius; | |
float ballMinY = 0 + ballRadius; | |
float ballMaxX = box.getWidth() - ballRadius; | |
float ballMaxY = box.getHeight() - ballRadius; | |
// Calculate the ball's new position | |
ballX += ballSpeedX; | |
ballY += ballSpeedY; | |
// Check if the ball moves over the bounds. If so, adjust the position and speed. | |
if (ballX < ballMinX) { | |
ballSpeedX = -ballSpeedX; // Reflect along normal | |
ballX = ballMinX; | |
// Re-position the ball at the edge | |
} else if (ballX > ballMaxX) { | |
ballSpeedX = -ballSpeedX; | |
ballX = ballMaxX; | |
} | |
// May cross both x and y bounds | |
if (ballY < ballMinY) { | |
ballSpeedY = -ballSpeedY; | |
ballY = ballMinY; | |
} else if (ballY > ballMaxY) { | |
ballSpeedY = -ballSpeedY; | |
ballY = ballMaxY; | |
} | |
} | |
} |
This file contains 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
package com.example.android.apis.mycircles; | |
import android.content.Context; | |
import android.graphics.Canvas; | |
import android.util.Log; | |
import android.view.View; | |
import java.util.ArrayList; | |
import java.util.Random; | |
import java.util.concurrent.TimeUnit; | |
/** | |
* Created by nixwins on 12/26/16. | |
*/ | |
public class BouncingBallView extends View { | |
private int xMin = 0; | |
private int xMax; | |
private int yMin = 0; | |
private int yMax; | |
private float radius; | |
private float randX; | |
private float randY; | |
private ArrayList<Ball> balls; | |
public BouncingBallView(Context context) { | |
super(context); | |
this.balls = new ArrayList<>(); | |
for(int i=0; i<2; i++) | |
this.balls.add(addBall()); | |
} | |
public Ball addBall(){ | |
Ball ball; | |
// Init the ball at a random location (inside the box) and moveAngle | |
Random rand = new Random(); | |
int radius = 60; | |
int x = rand.nextInt(500 - radius * 2 - 20) + radius + 10; | |
int y = rand.nextInt(800- radius * 2 - 20) + radius + 10; | |
int speed = 10; | |
int angleInDegree = rand.nextInt(360); | |
ball = new Ball(x, y, radius, speed, angleInDegree); | |
return ball; | |
} | |
@Override | |
protected void onDraw(Canvas canvas) { | |
super.onDraw(canvas); | |
for(int i=0; i < balls.size(); i++) | |
balls.get(i).draw(canvas); | |
for(int i=0; i < balls.size(); i++){ | |
balls.get(i).intersect(canvas); | |
// Update the ball's state with proper collision response if collided. | |
balls.get(i).update(); | |
} | |
for(int i=0; i<balls.size(); i++){ | |
balls.get(i).collide(balls); | |
} | |
invalidate(); | |
} | |
@Override | |
protected void onSizeChanged(int w, int h, int oldw, int oldh) { | |
this.xMax = w - 1; | |
this.yMax = h - 1; | |
} | |
} |
This file contains 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
package com.example.android.apis.mycircles.collisionphysics; | |
/** | |
* Created by nixwins on 12/26/16. | |
*/ | |
public class CollisionPhysics { | |
// Working copy for computing response in intersect(Canvas box), | |
// to avoid repeatedly allocating objects. | |
private static CollisionResponse tempResponse = new CollisionResponse(); | |
/** | |
* Detect collision for a moving point bouncing inside a rectangular container, | |
* within the given timeLimit. | |
* If collision is detected within the timeLimit, compute collision time and | |
* response in the given CollisionResponse object. Otherwise, set collision time | |
* to infinity. | |
* The result is passed back in the given CollisionResponse object. | |
*/ | |
public static void pointIntersectsRectangleOuter( | |
float pointX, float pointY, float speedX, float speedY, float radius, | |
float rectX1, float rectY1, float rectX2, float rectY2, | |
float timeLimit, CollisionResponse response) { | |
response.reset(); // Reset detected collision time to infinity | |
// A outer rectangular container box has 4 borders. | |
// Need to look for the earliest collision, if any. | |
// Right border | |
pointIntersectsLineVertical(pointX, pointY, speedX, speedY, radius, | |
rectX2, timeLimit, tempResponse); | |
if (tempResponse.t < response.t) { | |
response.copy(tempResponse); // Copy into resultant response | |
} | |
// Left border | |
pointIntersectsLineVertical(pointX, pointY, speedX, speedY, radius, | |
rectX1, timeLimit, tempResponse); | |
if (tempResponse.t < response.t) { | |
response.copy(tempResponse); | |
} | |
// Top border | |
pointIntersectsLineHorizontal(pointX, pointY, speedX, speedY, radius, | |
rectY1, timeLimit, tempResponse); | |
if (tempResponse.t < response.t) { | |
response.copy(tempResponse); | |
} | |
// Bottom border | |
pointIntersectsLineHorizontal(pointX, pointY, speedX, speedY, radius, | |
rectY2, timeLimit, tempResponse); | |
if (tempResponse.t < response.t) { | |
response.copy(tempResponse); | |
} | |
} | |
/** | |
* Detect collision for a moving point hitting a horizontal line, | |
* within the given timeLimit. | |
*/ | |
public static void pointIntersectsLineVertical( | |
float pointX, float pointY, float speedX, float speedY, float radius, | |
float lineX, float timeLimit, CollisionResponse response) { | |
response.reset(); // Reset detected collision time to infinity | |
// No collision possible if speedX is zero | |
if (speedX == 0) { | |
return; | |
} | |
// Compute the distance to the line, offset by radius. | |
float distance; | |
if (lineX > pointX) { | |
distance = lineX - pointX - radius; | |
} else { | |
distance = lineX - pointX + radius; | |
} | |
float t = distance / speedX; // speedX != 0 | |
// Accept 0 < t <= timeLimit | |
if (t > 0 && t <= timeLimit) { | |
response.t = t; | |
response.newSpeedX = -speedX; // Reflect horizontally | |
response.newSpeedY = speedY; // No change vertically | |
} | |
} | |
public static void pointIntersectsLineHorizontal( | |
float pointX, float pointY, float speedX, float speedY, float radius, | |
float lineY, float timeLimit, CollisionResponse response) { | |
response.reset(); // Reset detected collision time to infinity | |
// No collision possible if speedY is zero | |
if (speedY == 0) { | |
return; | |
} | |
// Compute the distance to the line, offset by radius. | |
float distance; | |
if (lineY > pointY) { | |
distance = lineY - pointY - radius; | |
} else { | |
distance = lineY - pointY + radius; | |
} | |
float t = distance / speedY; // speedY != 0 | |
// Accept 0 < t <= timeLimit | |
if (t > 0 && t <= timeLimit) { | |
response.t = t; | |
response.newSpeedY = -speedY; // Reflect vertically | |
response.newSpeedX = speedX; // No change horizontally | |
} | |
} | |
} |
This file contains 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
package com.example.android.apis.mycircles.collisionphysics; | |
/** | |
* Created by nixwins on 12/26/16. | |
* | |
* If collision occurs, this object stores the collision time and | |
* the computed responses, new speed (newSpeedX, newSpeedY). | |
*/ | |
public class CollisionResponse { | |
/** Detected collision time, reset to Float.MAX_VALUE */ | |
public float t; | |
// Time threshold to be subtracted from collision time | |
// to prevent moving over the bound. Assume that t <= 1. | |
private static final float T_EPSILON = 0.005f; | |
/** Computed speed in x-direction after collision */ | |
public float newSpeedX; | |
/** Computed speed in y-direction after collision */ | |
public float newSpeedY; | |
/** Constructor which resets the collision time to infinity. */ | |
public CollisionResponse() { | |
reset(); // Reset detected collision time to infinity | |
} | |
/** Reset the detected collision time to infinity. */ | |
public void reset() { | |
this.t = Float.MAX_VALUE; | |
} | |
/** Copy this instance to another, used to find the earliest collision. */ | |
public void copy(CollisionResponse another) { | |
this.t = another.t; | |
this.newSpeedX = another.newSpeedX; | |
this.newSpeedY = another.newSpeedY; | |
} | |
/** Return the x-position after impact. */ | |
public float getNewX(float currentX, float speedX) { | |
// Subtract by a small thread to make sure that it does not cross the bound. | |
if (t > T_EPSILON) { | |
return (float)(currentX + speedX * (t - T_EPSILON)); | |
} else { | |
return currentX; | |
} | |
} | |
/** Return the y-position after impact. */ | |
public float getNewY(float currentY, float speedY) { | |
// Subtract by a small thread to make sure that it does not cross the bound. | |
if (t > T_EPSILON) { | |
return (float)(currentY + speedY * (t - T_EPSILON)); | |
} else { | |
return currentY; | |
} | |
} | |
} |
This file contains 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
package com.example.android.apis.mycircles; | |
import android.support.v7.app.AppCompatActivity; | |
import android.os.Bundle; | |
public class MainActivity extends AppCompatActivity { | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
BouncingBallView bouncingBallView = new BouncingBallView(getApplicationContext()); | |
setContentView(bouncingBallView); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment