Skip to content

Instantly share code, notes, and snippets.

@nixwins
Created December 26, 2016 19:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nixwins/c28987540eb4b13aaa8dd6a2119687db to your computer and use it in GitHub Desktop.
Save nixwins/c28987540eb4b13aaa8dd6a2119687db to your computer and use it in GitHub Desktop.
For stackoverlow
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;
}
}
}
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;
}
}
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
}
}
}
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;
}
}
}
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