Custom View in custom ViewGroup not displaying when offset in RelativeLayout - android-custom-view

I have a custom view class (PinView) which is to be positioned within a custom ViewGroup (RightAngleTriangleView) based on some attributes. However, when it is placed relative to another view in a RelativeLayout, the PinView does not display. See code below:
RightAngleTriangleView
public class RightAngleTriangleView extends ViewGroup {
private static final String TAG = "RightAngleTriangleView";
private int pinOrientation;
private boolean isRightFilled, diagonalIsTopRightBottomLeft;
private Paint trianglePaint, pinPaint;
private Path trianglePath, pinPath;
private PointF triStart, triMiddle, triEnd;
private PinView pinView;
private float pinLengthDiff, pinThickness;
public RightAngleTriangleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false); // remove default (non) drawing behaviour for ViewGroup
/*** extract XML attributes ***/
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RightAngleTriangleView);
int fillColour = a.getColor(R.styleable.RightAngleTriangleView_fillColour,
context.getResources().getColor(android.R.color.darker_gray));
int fillPosition = a.getInt(R.styleable.RightAngleTriangleView_fillPosition,
context.getResources().getInteger(
R.integer.RightAngleTriangleView_fillPosition_left));
int diagonal = a.getInt(R.styleable.RightAngleTriangleView_diagonal,
context.getResources().getInteger(
R.integer.RightAngleTriangleView_diagonal_topLeftToBottomRight));
isRightFilled = fillPosition == context.getResources().getInteger(
R.integer.RightAngleTriangleView_fillPosition_right);
diagonalIsTopRightBottomLeft = diagonal == getContext().getResources().getInteger(
R.integer.RightAngleTriangleView_diagonal_topRightToBottomLeft);
pinOrientation = a.getInt(R.styleable.RightAngleTriangleView_pinOrientation,
context.getResources().getInteger(
R.integer.RightAngleTriangleView_pinOrientation_none));
a.recycle();
/*** setup drawing related variables ***/
trianglePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
trianglePaint.setStyle(Paint.Style.FILL_AND_STROKE);
trianglePaint.setColor(fillColour);
trianglePath = new Path();
trianglePath.setFillType(Path.FillType.EVEN_ODD);
triStart = new PointF();
triMiddle = new PointF();
triEnd = new PointF();
pinPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
pinPaint.setStyle(Paint.Style.FILL_AND_STROKE);
pinPaint.setColor(context.getResources().getColor(R.color.pin_color));
pinPath = new Path();
pinPath.setFillType(Path.FillType.EVEN_ODD);
// create pinView (if present)
if(pinOrientation != context.getResources().getInteger(
R.integer.RightAngleTriangleView_pinOrientation_none)){
pinView = new PinView(context, UiUtils.makeAttributeSet(context, getResourceId()));
addView(pinView);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(hasPin()){
// measure child to obtain 'wrapped' valid dimension
measureChild(pinView, widthMeasureSpec, heightMeasureSpec);
}
}
#Override
protected void onLayout (boolean changed, int left, int top, int right, int bottom){
Log.d(TAG, "onLayout() changed = " + changed);
if(hasPin()){
// use 'wrapped' valid dimension as pinThickness
if(pinView.isHorizontal()) {
pinThickness = pinView.getMeasuredHeight() / 2;
pinLengthDiff = (pinThickness * getWidth()) / getHeight();
}
else{
pinThickness = pinView.getMeasuredWidth() / 2;
pinLengthDiff = (pinThickness * getHeight()) / getWidth();
}
placePinView(left, top, right, bottom);
}
}
#Override
protected void onDraw(Canvas canvas){
// draw pin 'edge' if applicable
if(hasPin()){
pinPath.reset(); // remove any previously drawn paths
if(pinView.isHorizontal()){
pinPath.addRect((getWidth() - pinLengthDiff) / 2, (getHeight() - pinThickness) / 2,
(getWidth() + pinLengthDiff) / 2, (getHeight() + pinThickness) / 2,
Path.Direction.CW);
}
else{
pinPath.addRect((getWidth() - pinThickness) / 2, (getHeight() - pinLengthDiff) / 2,
(getWidth() + pinThickness) / 2, (getHeight() + pinLengthDiff) / 2,
Path.Direction.CW);
}
canvas.drawPath(pinPath, pinPaint);
}
// determine triangle vertices
if(diagonalIsTopRightBottomLeft){
// draw diagonal
triStart.set(getWidth(), 0);
triMiddle.set(0, getHeight());
// determine triEnd based on fill position
if(isRightFilled){
triEnd.set(getWidth(), getHeight());
}
else{
triEnd.set(0, 0);
}
}
else{
// draw diagonal
triStart.set(0, 0);
triMiddle.set(getWidth(), getHeight());
// determine triEnd based on fill position
if(isRightFilled){
triEnd.set(getWidth(), 0);
}
else{
triEnd.set(0, getHeight());
}
}
// draw triangle
trianglePath.reset(); // remove any previously drawn paths
trianglePath.moveTo(triStart.x, triStart.y);
trianglePath.lineTo(triMiddle.x, triMiddle.y);
trianglePath.lineTo(triEnd.x, triEnd.y);
trianglePath.close(); // automatically draw third side
canvas.drawPath(trianglePath, trianglePaint);
}
public boolean hasPin(){
return pinView != null;
}
private void placePinView(int left, int top, int right, int bottom){
int l, t, r, b, pinPosition;
int trbl = diagonalIsTopRightBottomLeft ? 1<<2 : 0;
int rightFilled = isRightFilled ? 1<<1 : 0;
int horizontal = pinView.isHorizontal() ? 1 : 0;
int result = trbl + rightFilled + horizontal;
// determine pin size and position
switch (result){
case 0: // diagonal = top-left to bottom-right, left-filled, pin vertical
t = top;
b = t + (int) (getHeight() - pinLengthDiff) / 2;
l = left + (int) (getWidth() - pinThickness)/2;
r = l + pinView.getMeasuredWidth();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_top);
break;
case 1: // diagonal = top-left to bottom-right, left-filled, pin horizontal
l = left + (int) (getWidth() + pinLengthDiff)/2;
r = right;
b = top + (int) (getHeight() + pinThickness)/ 2;
t = b - pinView.getMeasuredHeight();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_right);
break;
case 2: // diagonal = top-left to bottom-right, right-filled, pin vertical
t = top + (int) (getHeight() + pinLengthDiff) / 2;
b = bottom;
r = left + (int) (getWidth() + pinThickness)/2;
l = r - pinView.getMeasuredWidth();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_bottom);
break;
case 3: // diagonal = top-left to bottom-right, right-filled, pin horizontal
l = left;
t = top + (int) (getHeight() - pinThickness)/ 2;
r = l + (int) (getWidth() - pinLengthDiff) / 2;
b = t + pinView.getMeasuredHeight();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_left);
break;
case 4: // diagonal = top-right to bottom-left, left-filled, pin vertical
t = top + (int) (getHeight() + pinLengthDiff) / 2;
b = bottom;
l = left + (int) (getWidth() - pinThickness)/2;
r = l + pinView.getMeasuredWidth();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_bottom);
break;
case 5: // diagonal = top-right to bottom-left, left-filled, pin horizontal
l = left + (int) (getWidth() + pinLengthDiff)/2;
t = top + (int) (getHeight() - pinThickness)/ 2;
r = right;
b = t + pinView.getMeasuredHeight();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_right);
break;
case 6: // diagonal = top-right to bottom-left, right-filled, pin vertical
t = top;
b = t + (int) (getHeight() - pinLengthDiff) / 2;
r = left + (int) (getWidth() + pinThickness)/2;
l = r - pinView.getMeasuredWidth();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_top);
break;
case 7: // diagonal = top-right to bottom-left, right-filled, pin horizontal
l = left;
r = l + (int) (getWidth() - pinLengthDiff) / 2;
b = top + (int) (getHeight() + pinThickness)/2;
t = b - pinView.getMeasuredHeight();
pinPosition = getContext().getResources().getInteger(
R.integer.PinView_position_left);
break;
default:
l = left;
t = top;
r = right;
b = bottom;
pinPosition = -1;
break;
}
// remeasure / resize pinView accounting for correct unspecified dimension
measureChild(pinView, MeasureSpec.makeMeasureSpec(r - l, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(b - t, MeasureSpec.EXACTLY));
pinView.setPosition(pinPosition); // ensure that pinName is in correct position
pinView.layout(l, t, r, b); // position pinView
}
private int getResourceId(){
int trbl = diagonalIsTopRightBottomLeft ? 1<<2 : 0;
int rightFilled = isRightFilled ? 1<<1 : 0;
int horizontal = pinOrientation == getContext().getResources().getInteger(
R.integer.RightAngleTriangleView_pinOrientation_horizontal) ? 1 : 0;
int result = trbl + rightFilled + horizontal;
Log.d(TAG, "getResourceId(): result = " + result);
switch (result){
case 0: // diagonal = top-left to bottom-right, left-filled, pin vertical
return R.xml.pinview_vertical_namebelow;
case 1: // diagonal = top-left to bottom-right, left-filled, pin horizontal
return R.xml.pinview_horizontal;
case 2: // diagonal = top-left to bottom-right, right-filled, pin vertical
return R.xml.pinview_vertical;
case 3: // diagonal = top-left to bottom-right, right-filled, pin horizontal
return R.xml.pinview_horizontal_namebelow;
case 4: // diagonal = top-right to bottom-left, left-filled, pin vertical
return R.xml.pinview_vertical_namebelow;
case 5: // diagonal = top-right to bottom-left, left-filled, pin horizontal
return R.xml.pinview_horizontal_namebelow;
case 6: // diagonal = top-right to bottom-left, right-filled, pin vertical
return R.xml.pinview_vertical;
case 7: // diagonal = top-right to bottom-left, right-filled, pin horizontal
return R.xml.pinview_horizontal;
default:
return -1;
}
}
}
PinView
public class PinView extends RelativeLayout {
private TextView pinNameView, signalTextView;
private boolean isHorizontal;
public PinView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
/*** extract XML attributes ***/
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.PinView);
boolean nameBelow = a.getBoolean(R.styleable.PinView_nameBelow, false);
int orientation = a.getInt(R.styleable.PinView_orientation, 0);
int position = a.getInt(R.styleable.PinView_position, -1);
isHorizontal = orientation == 0;
a.recycle();
/*** create children ***/
LinearLayout backgroundLayout = new LinearLayout(context);
backgroundLayout.setId(R.id.pinview_backgroundlayout);
backgroundLayout.setBackgroundColor(context.getResources().getColor(R.color.pin_color));
if(isHorizontal){
pinNameView = new TextView(context);
signalTextView = new TextView(context);
}
else{
pinNameView = new VerticalTextView(context);
signalTextView = new VerticalTextView(context);
}
pinNameView.setId(R.id.pinview_pinname);
pinNameView.setTextColor(context.getResources().getColor(android.R.color.black));
signalTextView.setId(R.id.pinview_signaltext);
signalTextView.setTextColor(context.getResources().getColor(android.R.color.black));
signalTextView.setBackgroundColor(context.getResources().getColor(R.color.pin_sig_color));
/*** determine children layouts and positions ***/
LayoutParams lpSigText = new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
backgroundLayout.addView(signalTextView, lpSigText); // add signalView to backgroundLayout, NOT this view
LayoutParams lpBackgroundLayout;
if(isHorizontal){
lpBackgroundLayout = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
else{
lpBackgroundLayout = new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
}
LayoutParams lpPinName = new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
// place pin name accordingly
if(nameBelow){
addView(backgroundLayout, lpBackgroundLayout);
if(isHorizontal){
lpPinName.addRule(RelativeLayout.BELOW, backgroundLayout.getId());
}
else{
lpPinName.addRule(RelativeLayout.RIGHT_OF, backgroundLayout.getId());
}
addView(pinNameView, lpPinName);
setPosition(position);
}
else{
addView(pinNameView, lpPinName);
setPosition(position);
if(isHorizontal){
lpBackgroundLayout.addRule(RelativeLayout.BELOW, pinNameView.getId());
}
else{
lpBackgroundLayout.addRule(RelativeLayout.RIGHT_OF, pinNameView.getId());
}
addView(backgroundLayout, lpBackgroundLayout);
}
}
public void setPosition(int position){
// align pin name according to pin position on device
LayoutParams params = (RelativeLayout.LayoutParams) pinNameView.getLayoutParams();
switch(position){ // pin's position relative to parent device
case 2: // top
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
break;
case 3: // bottom
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
break;
case 0: // left
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
break;
case 1: // right
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
break;
default:
break;
}
}
public void setData(DevicePin pin){
pinNameView.setText(pin.name);
signalTextView.setText(pin.data);
// always create new animation, otherwise it will need to be reset
Animation anim = null;
switch (pin.direction){
case LEFT:
anim = AnimationUtils.loadAnimation(getContext(), R.anim.pin_transition_left);
break;
case RIGHT:
anim = AnimationUtils.loadAnimation(getContext(), R.anim.pin_transition_right);
break;
case UP:
anim = AnimationUtils.loadAnimation(getContext(), R.anim.pin_transition_up);
break;
case DOWN:
anim = AnimationUtils.loadAnimation(getContext(), R.anim.pin_transition_down);
break;
}
if(pin.startBehaviour == DevicePin.AnimStartBehaviour.DELAY){
if(pin.animationDelay == -1){
anim.setStartOffset(anim.getDuration());
}
else{
anim.setStartOffset(pin.animationDelay);
}
}
if(pin.action == DevicePin.PinAction.STATIONARY){
anim.setDuration(0);
}
if(pin.animListener != null){
anim.setAnimationListener(pin.animListener);
}
if(anim != null){
signalTextView.setAnimation(anim);
}
}
public boolean isHorizontal(){
return isHorizontal;
}
}
Activity XML
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<view
class="org.ricts.abstractmachine.ui.device.RightAngleTriangleView"
android:id="#+id/edge"
android:layout_width="122px"
android:layout_height="150px"
custom:diagonal = "topLeftToBottomRight"
custom:fillPosition = "right"
custom:fillColour="#color/mux_fill_color"
custom:pinOrientation = "vertical"
/>
<view
class="org.ricts.abstractmachine.ui.device.RightAngleTriangleView"
android:id="#+id/edge2"
android:layout_width="122px"
android:layout_height="150px"
android:layout_toRightOf="#+id/edge"
custom:diagonal = "topLeftToBottomRight"
custom:fillPosition = "right"
custom:fillColour="#color/mux_fill_color"
custom:pinOrientation = "vertical"
/>
<view
class="org.ricts.abstractmachine.ui.device.RightAngleTriangleView"
android:id="#+id/edge3"
android:layout_width="122px"
android:layout_height="150px"
android:layout_below="#+id/edge"
custom:diagonal = "topLeftToBottomRight"
custom:fillPosition = "right"
custom:fillColour="#color/mux_fill_color"
custom:pinOrientation = "vertical"
/>
<view
class="org.ricts.abstractmachine.ui.device.PinView"
android:id="#+id/pin"
android:layout_width="58px"
android:layout_height="64px"
android:layout_below="#+id/edge"
android:layout_toRightOf="#+id/edge3"
custom:orientation="vertical"
custom:position="top"
/>
</RelativeLayout>
The code above is the XML for an Activity to highlight the problem. I have also taken a screenshot of the problem (below). The triangle in the top-most corner is fine and displays the inner PinView. However, the other two (below and at the right of the first one) do not display the PinView even though the code is exactly the same and I have confirmed by Log that the intended PinView positions seem ok.
Problem Screenshot
What am I doing wrong?

It turns out that I was using absolute values when calling View.layout(l,t,r,b) instead of relative ones. I now use the relative values and it works!

Related

Asteroids Game - Buggy Collision Detection

I am making an asteroid game using Processing 3.5.3. As you will see the collision detection is very buggy. When it detects collision between ship/asteroid sometimes it is greater than the asteroid size, sometimes it is smaller. Also, when the asteroids get smaller, the collision detect still seems to be calling the larger size asteroid. The collision between bullet and asteroid seems to only be a hit when the bullet is in the center of the asteroid.
Apologies for all the comments - they are required for my internal documentation.
Here is my code, it is broken up into classes.
Ship
class Ship {
PVector shipAcceleration;
PVector shipVelocity;
PVector shipPosition;
PShape shipShape;
float shipDirection;
int shipLastFire; //holds the time in millis that the last bullet was fired
int shipDelayTime;
Ship() {
shipAcceleration = new PVector();
shipVelocity = new PVector();
shipPosition = new PVector(width/2, height/2); // player starts in middle of screen
shipDirection = 0; // set to 0 to so "up" is a sense of direction
shipLastFire = 0;
shipDelayTime = 300;
keys = new boolean[5];
shipShape = createShape();
shipShape.beginShape();
shipShape.fill(255, 0, 0);
shipShape.vertex(0, -4);
shipShape.vertex(2, 0);
shipShape.vertex(2, 2);
shipShape.vertex(0, 1);
shipShape.vertex(-2, 2);
shipShape.vertex(-2, 0);
shipShape.vertex(0, -4);
shipShape.endShape();
}
void moveShip() {
shipShape.resetMatrix();
// reset.Matrix sourced from https://processing.org/reference/resetMatrix_.html
shipShape.rotate(radians(shipDirection)); // rotates ship
shape(shipShape, shipPosition.x, shipPosition.y, 10, 10);
}
void updateShip() {
// motion sourced from Chapter 22 of 'Processing: A programming handbook
// for visual designers and asrtists' by Casey Reas and Ben Fry
shipAcceleration.x = 0;
shipAcceleration.y = 0;
if (keys[0]) {
shipAcceleration.x = 0.5 * cos(radians(shipDirection) - PI/2);
shipAcceleration.y = 0.5 * sin(radians(shipDirection) - PI/2);
}
if (keys[1] && !keys[2])
{
shipDirection -= 5;
}
if (keys[2] && !keys[1])
{
shipDirection += 5;
}
shipVelocity.add(shipAcceleration);
// add sourced from https://processing.org/reference/PVector_add_.html
shipPosition.add(shipVelocity);
shipVelocity.mult(.95);
// mult sourced from https://processing.org/reference/PVector_mult_.html
shipPosition.x %= width;
if (shipPosition.x < -10)
{
shipPosition.x = width;
}
shipPosition.y %= height;
if (shipPosition.y < -10)
{
shipPosition.y = height;
}
if (keys[4]) {
if (millis() - shipLastFire > shipDelayTime) {
shipLastFire = millis();
fireBullet(shipPosition, shipVelocity, shipDirection);
}
}
}
}
Bullet
class Bullet {
PVector bulletPosition;
PVector bulletVelocity;
boolean bulletHidden; // used if lifespan is max and to help recycle
int bulletSize;
int bulletCreationTime;
int bulletLifespan; //the time in milli seconds that bullets last
int bulletSpeed;
Bullet() {
bulletHidden = true;
bulletSize = 5;
bulletPosition = new PVector();
bulletVelocity = new PVector();
bulletCreationTime = 0;
bulletLifespan = 3000;
bulletSpeed = 5;
}
void updateBullet() {
if (!bulletHidden) {
bulletPosition.add(bulletVelocity);
if (millis() - bulletCreationTime > bulletLifespan)
// millis sourced from https://processing.org/reference/millis_.html
{
bulletHidden = true;
}
bulletPosition.x %= width;
if (bulletPosition.x < -1)
{
bulletPosition.x = width;
}
bulletPosition.y %= height;
if (bulletPosition.y < -1)
{
bulletPosition.y = height;
}
}
}
void drawBullet() {
if (!bulletHidden) {
updateBullet();
ellipse(bulletPosition.x, bulletPosition.y, bulletSize, bulletSize);
}
}
void reset(PVector pos, PVector spe, float direct) {
bulletPosition = new PVector(pos.x + (20 * cos(radians(direct) - PI/2)), pos.y + (20 * sin(radians(direct) - PI/2)));
bulletVelocity.x = bulletSpeed * cos(radians(direct) - PI/2) + spe.x;
bulletVelocity.y = bulletSpeed * sin(radians(direct) - PI/2) + spe.y;
bulletCreationTime = millis();
bulletHidden = false;
}
}
Asteroid
class Asteroid {
float asteroidSize = (width/80*12);
float x;
float y;
float velocityX;
float velocityY;
PVector[] vertices = new PVector[8];
boolean active = true; //false after collision
int level = 1; // how many times has it been shot. Level 1 is not yet shot
Asteroid(float xPos, float yPos, int aLevel) {
if (xPos == 0 && yPos == 0) { //if begin level asteroids
x = random(width) ; // set random start positions
y = random (height);
} else { // if collision generating 2 smaller asteroids
x = xPos; // set from asteroid x, y
y = yPos;
}
velocityX = random(-2, 2);
velocityY = random(-2, 2);
level = aLevel; //sets asteroid level (how many times shot)
//create polygon. /aLevel generates smaller polygons with each collision.
vertices[0] = new PVector(random (width/80*3/aLevel), random(height/80*3/aLevel) );
vertices[1] = new PVector(random((width/80*4/aLevel), (width/80*8/aLevel)), random(height/80*3/aLevel) );
vertices[2] = new PVector(random((width/80*9/aLevel), (width/80*12/aLevel)), random(height/80*3/aLevel) );
vertices[3] = new PVector(random((width/80*9/aLevel), (width/80*12/aLevel)), random((height/80*4/aLevel), (height/80*8/aLevel)) );
vertices[4] = new PVector(random((width/80*9/aLevel), (width/80*12/aLevel)), random((height/80*9/aLevel), (height/80*12/aLevel)) );
vertices[5] = new PVector(random((width/80*4/aLevel), (width/80*8/aLevel)), random((height/80*9/aLevel), (height/80*12/aLevel)) );
vertices[6] = new PVector(random(width/80*3/aLevel), random((height/80*9/aLevel), (height/80*12/aLevel)) );
vertices[7] = new PVector(random(width/80*3/aLevel), random((height/80*4/aLevel), (height/80*8/aLevel)) );
}
void moveAsteroid() {
x = x + velocityX; //asteroids to move with a random velocity
y = y + velocityY;
if ( x < -1 * asteroidSize ) {
x = width + asteroidSize;
} //if off screen left, come back in right
if ( x > width + asteroidSize ) {
x = -1 * asteroidSize;
} // if off screen right, come back in left
if ( y < -1 * asteroidSize ) {
y = height + asteroidSize;
} //if off top of screen, come back in bottom
if ( y > height + asteroidSize ) {
y = -1 * asteroidSize ;
} //if off bottom of screen, come back in top
}
void asteroidDraw() {
if (active == false) { // If not active don't draw
return;
}
stroke(150);
fill(255);
// this was how I orginally coded. Have kept commented out for now, so I can see what I did, but will delete before submission.
/*beginShape();
vertex(vertices[0].x, vertices[0].y );
vertex(vertices[1].x, vertices[1].y );
vertex(vertices[2].x, vertices[2].y );
vertex(vertices[3].x, vertices[3].y );
vertex(vertices[4].x, vertices[4].y );
vertex(vertices[5].x, vertices[5].y );
vertex(vertices[6].x, vertices[6].y );
vertex(vertices[7].x, vertices[7].y );
endShape(CLOSE); */
beginShape();
for (PVector v : vertices) {
vertex(x+v.x, y+v.y);
}
endShape(CLOSE);
}
void manDown() {
active = false; //sets to in active so will stop drawing
// add 2 new asteroids to array
asteroids = (Asteroid[]) append( asteroids, new Asteroid( x+20, y+20, level + 1 ) ); // Appends asteroid to array. Changing level makes the asteroid smaller.
asteroids = (Asteroid[]) append( asteroids, new Asteroid( x-20, y-20, level + 1 ) ); // Appends two smaller asteroids to array.
}
}
Game Manager
class GameManager {
int scoreCount;
boolean gameState = true;
int lifeCount;
void newGame()
{
gameState = true; //sets game state to in play
scoreCount = 0; //set counter of flies killed to 0
lifeCount = 3;
}
void scoreUpdate()
{
textSize(width*3/100);
textAlign(LEFT);
fill(255);
text("Score " + scoreCount, (width*2/100), (height*4/100));
}
void lifeLost()
{
lifeCount = lifeCount - 1;
if (lifeCount <= 0) {
gameState = false;
gameOver();
}
}
void lifeUpdate()
{
textSize(height*3/100);
textAlign(LEFT);
fill(255);
text("Lives " + lifeCount, (width*2/100), ((height*4/100) + (height*3/100)) );
}
void gameOver()
{
background(0);
textSize(height*5/100);
textAlign(CENTER);
fill(255);
text("Game over", width/2, height/2.6);
//play again button
fill(255);
rect(((width/2)-(width/4)), (((height/2)- (height/12))), width/2, height/8);
fill(0);
text("Play Again", width/2, height/2);
//define area for play again button collision
if (mousePressed)
{
if (
(mouseX > width/4) &&
(mouseX < width/4 +width/2) &&
(mouseY > (height/2-height/10.5)) &&
(mouseY < ((height/2-height/10.5) + height/8))
)
{
setup(); //reset game
}
}
}
}
Main
Asteroid[] asteroids; //K Level 1 starts with 6, add 2 each level, 10 levels
Ship myShip;
GameManager gameManager;
ArrayList<Bullet> bullets;
// Array help sourced from chapter 28 of 'Processing: A programming handbook
// for visual designers and asrtists' by Casey Reas and Ben Fry
int bulletIndex; // used to recycle bullets
// index sourced from https://py.processing.org/reference/list_index.html
int startNum = 6; //K begin game with 6 asteroids in the level
boolean[] keys; // boolean for storing keypressed/released
void setup() {
size(800, 800);
gameManager = new GameManager();
gameManager.newGame();
bulletIndex = 0;
bullets = new ArrayList<Bullet>();
keys = new boolean[5];
myShip = new Ship();
asteroids = new Asteroid [startNum]; //K
for (int a = 0; a < startNum; a++) { //K create asteroids in array
asteroids[a] = new Asteroid(0, 0, 1); //K
}
for (int i = 0; i < 20; i++)
{
bullets.add(new Bullet()); // create bullets
}
}
void draw() {
background(0);
collisionDetect();
gameManager.gameState = true;
myShip.updateShip(); // E
myShip.moveShip(); // E
for (int a = 0; a < asteroids.length; a++) { //K for asteroids in array
asteroids[a].moveAsteroid(); //K
asteroids[a].asteroidDraw(); //K
}
gameManager.scoreUpdate();
gameManager.lifeUpdate();
for (int i = 0; i < bullets.size(); i++)
{
bullets.get(i).drawBullet(); // drawing bullets
}
}
void keyPressed() {
if (key == CODED) {
if (keyCode == UP)
keys[0] = true;
if (keyCode == LEFT)
keys[1] = true;
if (keyCode == RIGHT)
keys[2] = true;
if (keyCode == DOWN)
keys[3] = true;
} else {
if (key == 'w')
keys[0] = true;
if (key == 'a')
keys[1] = true;
if (key == 'd')
keys[2] = true;
if (key == 's')
keys[3] = true;
if (key == ' ')
keys[4] = true;
}
}
void keyReleased() {
if (key == CODED) {
if (keyCode == UP)
keys[0] = false;
if (keyCode == LEFT)
keys[1] = false;
if (keyCode == RIGHT)
keys[2] = false;
if (keyCode == DOWN)
keys[3] = false;
} else {
if (key == 'w')
keys[0] = false;
if (key == 'a')
keys[1] = false;
if (key == 'd')
keys[2] = false;
if (key == 's')
keys[3] = false;
if (key == ' ')
keys[4] = false;
}
}
void fireBullet(PVector pos, PVector spe, float dir) {
bullets.get(bulletIndex).reset(pos, spe, dir);
// set attributes of last used bullet
// get sourced from https://processing.org/reference/get_.html
bulletIndex++; //update index
bulletIndex %= bullets.size(); //keep index in range
}
void collisionDetect(){
Asteroid testHolder;
Bullet bulletHolder;
// asteroid and bullet objects to minimize creating new objects
for(int i = 0; i < asteroids.length; i++){
testHolder = asteroids[i];
if(dist(testHolder.x, testHolder.y, myShip.shipPosition.x,
myShip.shipPosition.y) < testHolder.asteroidSize)
// collision of player and the asteroid
{gameManager.gameOver();}
for(int j = 0; j < bullets.size(); j++){
bulletHolder = bullets.get(j);
// pull and store each bullet from the list
if(bulletHolder.bulletHidden){continue;}
// don't calculate anything if it is hidden
if(dist(testHolder.x, testHolder.y, bulletHolder.bulletPosition.x,
bulletHolder.bulletPosition.y) < testHolder.asteroidSize){
testHolder.manDown();
// used to detect collision and split if collided
bulletHolder.bulletHidden = true;
// hide the bullet so it won't go 'through' the asteroids
j++;
}
}
}
}
For the problem with the smaller asteroids, you need to make the asteroidSize dependent on the level. Currently they are all the same: float asteroidSize = (width/80*12);
As the to collision issue, the first thing is that you also have to take the size to the ship/bullet hitting the asteroid into account:
if(dist(testHolder.x, testHolder.y, myShip.shipPosition.x, myShip.shipPosition.y) < (testHolder.asteroidSize + myShip.size))
For clarity: size is in both cases the radius.
Second, there will always be some area's where this basic type of collision detection does not follow the visual, because your shapes are not circles. The randomness that you use for the asteroids does not help in that respect. A way to get more control over this is to define a couple of shapes per level, and pick one of those at random when creating an asteroid. This way you can tweak the shape/radius to make a good trade off between looks and function so it looks 'believable enough'.

mirror image the captured photo

I am clicking the picture and the passing it through intent. But the image that I am displaying on the ImageView is the mirror image of the image captured. I have tried to implement RotateFlip method but the error appears saying Bitmap doesn't contain definition of RotateFlip.
{
var metrics = Resources.DisplayMetrics;
int windowWidth = metrics.WidthPixels;
int windowHeight = metrics.HeightPixels;
int reqWidth = (int)Math.Round(windowWidth*1.0f);
int reqHeight = (int)Math.Round(windowHeight*1.0f);
Bitmap photo = loadBitmapLowResolution(Image, reqWidth, reqHeight);
ExifInterface exif = new ExifInterface(Image);
int orientation = exif.GetAttributeInt(ExifInterface.TagOrientation, 90);
var rotation = exif.GetAttributeInt(ExifInterface.TagOrientation, (int)Orientation.Normal);
orientation = exif.GetAttributeInt(ExifInterface.TagOrientation, (int)Orientation.Rotate90);
// Rotate according to the orientation
Matrix matrix = new Matrix();
switch (orientation)
{
case (int)Orientation.Rotate90: matrix.PostRotate(90); break;
case (int)Orientation.Rotate180: matrix.PostRotate(180); break;
case (int)Orientation.Rotate270: matrix.PostRotate(270); break;
}
Bitmap photoRotated;
if (orientation != (int)Orientation.Normal)
{
photoRotated = Bitmap.CreateBitmap(photo, 0, 0, photo.Width, photo.Height, matrix, true);
}
else
{
photoRotated = photo;
}
// Apply center crop
int cropW = (int)(0.5 * (photoRotated.Width - photoRotated.Width/1.0f ) - 2);
int cropH = (int)(0.5 * (photoRotated.Height - photoRotated.Height/1.0f ) - 2);
cropW = cropW < 0 ? 0 : cropW;
cropH = cropH < 0 ? 0 : cropH;
//Log.Debug(TAG, "Creating a bitmap with size: " + (photoRotated.Width - 2 * cropW) + "x" + (photoRotated.Height - 2 * cropH) + " from a bitmap with size: " + photoRotated.Width + "x" + photoRotated.Height);
Bitmap photoCrop = Bitmap.CreateBitmap(photoRotated, cropW, cropH, photoRotated.Width - 2 * cropW, photoRotated.Height- 2 * cropH);
imageView.SetImageBitmap(photoCrop);
// Create your application here,
}
public static Bitmap loadBitmapLowResolution(string filePath, int reqWidth, int reqHeight)
{
BitmapFactory.Options options = new BitmapFactory.Options();
// First decode with inJustDecodeBounds=true to check dimensions
options.InJustDecodeBounds = true;
BitmapFactory.DecodeFile(filePath, options);
// Calculate inSampleSize
options.InSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
return BitmapFactory.DecodeFile(filePath, options);
}
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
int height = options.OutHeight;
int width = options.OutWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth)
{
// Calculate ratios of height and width to requested height and width
int heightRatio = (int)Math.Round((float)height / (float)reqHeight);
int widthRatio = (int)Math.Round((float)width / (float)reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
The easiest way is to do it is using a Matrix;
public Bitmap HorizontalFlip(Bitmap bInput)
{
if (bInput == null) return null;
Matrix matrix = new Matrix();
matrix.PreScale(-1.0f, 1.0f);
return Bitmap.CreateBitmap(bInput, 0, 0, bInput.Width, bInput.Height, matrix, true);
}
Where bInput is the input bitmap that you wanna flip.

Cant get collisions to work for Mario-like game

so, I've spent a fair few hours on my Mario-like game but recently I've been stuck on the collisions after I changed to an object based collision system that seems like it should work but there is a bug somewhere that i cant find.
currently the box that represents the player just falls straight through the platform rather than sitting on the platform
You wont be able to run this snippet because I'm new to stack overflow and couldn't find anything closer to processing.
Here is the box class that has all my collision detection
//Box class -- THIS IS WHERE I NEED HELP -- everything else is just for context
//Box class
class Box {
private float xpos, ypos, sizeX, sizeY;
private float bottom = ypos + sizeY;
private float top = ypos;
private float left = xpos;
private float right = xpos + sizeX;
Box(float startX, float startY, float xSize, float ySize) {
boxes++;
xpos = startX;
ypos = startY;
sizeX = xSize;
sizeY = ySize;
}
void update() {
rect(xpos, ypos, sizeX, sizeY);
}
void collision() {
//If on box level
if (player.playerY + player.playerHeight >= top && player.playerY <= bottom) {
//Left Side
if ((player.playerX + player.playerWidth) <= left && (player.playerX + player.playerWidth) + speed >= left) {
player.playerX = left - player.playerWidth;
player.canMoveRight = false;
}
//Right Side
if (player.playerX >= right && player.playerX + speed <= right) {
player.playerX = right;
player.canMoveLeft = false;
}
}
//If in box Y coord
if (player.playerX + player.playerWidth >= left && player.playerX <= right) {
//top
if (player.playerY + player.playerHeight <= top && player.playerY + player.yVelocity + gravity >= top) {
player.Gravity = false;
player.playerY = top - player.playerHeight;
player.yVelocity = 0;
player.canMoveDown = false;
}
//bottom
if (player.playerY + player.yVelocity >= bottom && player.playerY + player.yVelocity + gravity <= bottom) {
player.canMoveUp = false;
player.yVelocity = 0;
player.playerY = bottom;
}
}
//onGround
if (player.playerY == ypos - player.playerHeight) {
player.onGround = true;
}
}
}
Control structure for multiple keypresses
//Control structure to enable multiple keypresses
//Control
float speed = 4;
float gravity = 1;
float jump = 10;
boolean isUp, isLeft, isDown, isRight;
void control() {
//Controls
if (isUp) {
if (player.canMoveUp) {
if (player.onGround) {
player.Gravity = true;
player.yVelocity = 0 + jump;
player.onGround = false;
}
}
}
if (isDown) {
if (player.canMoveDown) {
}
}
if (isRight) {
if (player.canMoveRight) {
player.playerX += speed;
}
}
if (isLeft) {
if (player.canMoveLeft) {
player.playerX -= speed;
}
}
if (player.onGround) {
player.Gravity = false;
player.yVelocity = 0;
}
}
void keyPressed() {
setMove(keyCode, true);
}
void keyReleased() {
setMove(keyCode, false);
}
boolean setMove(int k, boolean b) {
switch (k) {
case UP:
return isUp = b;
case DOWN:
return isDown = b;
case LEFT:
return isLeft = b;
case RIGHT:
return isRight = b;
default:
return b;
}
}
Main tab, box/entity updates and player class
//Main tab
float boxes = 0;
//Calls classes
Box b = new Box(0, 700, 800, 100);
Box b1 = new Box(300, 675, 25, 25);
Player player = new Player(50, 400, 20, 20);
void setup() {
size(800, 800);
frameRate(60);
}
void draw() {
background(255, 255, 255);
boxesUpdate();
boxesCollision();
entityUpdate();
control();
println(player.playerX + ", " + player.playerY);
println(player.yVelocity + ", " + player.Gravity + ", " + player.onGround + ", " + player.canMoveDown);
}
//Box collision update method for when i add more boxes
void boxesCollision() {
player.Gravity = true;
player.canMoveUp = true;
player.canMoveDown = true;
player.canMoveLeft = true;
player.canMoveRight = true;
player.onGround = false;
b.collision();
b1.collision();
if (player.Gravity) {
player.playerY += player.yVelocity;
player.yVelocity += gravity;
}
}
//Along with another update method for the other parts of the boxes
void boxesUpdate() {
b.update();
b1.update();
}
//Entity update method to update players and for when i add NPCs and enemies
void entityUpdate() {
player.update();
}
//Player class
class Player {
private float playerX, playerY, playerWidth, playerHeight;
private float yVelocity = 0;
private float xVelocity = 0;
private boolean canMoveRight, canMoveLeft, canMoveUp, canMoveDown;
private boolean onGround, Gravity;
Player(float X, float Y, float xSize, float ySize) {
playerWidth = xSize;
playerHeight = ySize;
playerX = X;
playerY = Y;
}
void update() {
rect(playerX, playerY, playerWidth, playerHeight);
}
}

Save image with Processing

I'm trying to save an image after certain time, the problem is that the image size is bigger than the display so when I use the save or saveFrame function it only saves the image that I can see in the display. There is any other way to save the whole image?
This is my code:
PImage picture, pictureFilter, img;
int total, cont, current;
ArrayList<ArrayList<Position>> columns;
String[] fontList;
public class Position {
public int x;
public int y;
}
void setup() {
fontList = PFont.list();
picture = loadImage("DSC05920b.JPG");
pictureFilter = loadImage("filtrePort2.jpg");
frame.setResizable(true);
size(picture.width, picture.height);
columns = new ArrayList<ArrayList<Position>>();
for(int i = 0; i < picture.width; i++) {
ArrayList<Position> row = new ArrayList<Position>();
for(int j = 0; j < picture.height; j++){
Position p = new Position();
p.x = i;
p.y = j;
row.add(p);
}
columns.add(row);
}
total = picture.width * picture.height;
cont = total;
current = 0;
img = createImage(picture.width, picture.height, RGB);
}
float randomLetter() {
float value = 23;
boolean found = false;
while(!found) {
value = random(48, 122);
if(value >48 && value <58) found = true;
if(value >65 && value <91) found = true;
if(value >97 && value <123) found = true;
}
return value;
}
void draw() {
int x = int(random(0, columns.size()));
ArrayList<Position> rows = columns.get(x);
int y = int(random(0, rows.size()));
Position p = rows.get(y);
color c = pictureFilter.get(p.x, p.y);
int r = (c >> 16) & 0xFF; // Faster way of getting red(argb)
if(r < 240) {
PFont f = createFont(fontList[int(random(0,fontList.length))],random(5, 24),true);
textFont(f);
fill(picture.get(p.x,p.y));
char letter = (char) int(randomLetter());
text(letter, p.x, p.y);
}
if(rows.size() == 1) {
if(columns.size() == 1) {
saveFrame("lol.jpg");
columns.remove(x);
} else {
columns.remove(x);
}
} else {
println(rows.size());
rows.remove(y);
}
--cont;
float percent = float(total-cont)/float(total)*100;
if(int(percent) != current) {
current = int(percent);
save("image_" + current + ".jpg");
}
println("DONE: " + (total-cont) + "/" + total + " Progress: " + percent + "%");
}
The code do a lot of stuff but the part that its not working well is at the final when I check if the percentage have been increased in order to save the image
You can write this into a PGraphics context - aka a graphics buffer.
The buffer can be as big as you need it to be, and you can choose whether to draw it on the screen or not..
// Create the buffer at the size you need, and choose the renderer
PGraphics pg = createGraphics(myImage.width, myImage.height, P2D);
// Wrap all your drawing functions in the pg context - e.g.
PFont f = createFont(fontList[int(random(0,fontList.length))],random(5, 24),true);
textFont(f);
pg.beginDraw();
pg.fill(picture.get(p.x,p.y));
char letter = (char) int(randomLetter());
pg.text(letter, p.x, p.y);
pg.endDraw();
// Draw your PG to the screen and resize the representation of it to the screen bounds
image(pg, 0, 0, width, height); // <-- this wont actually clip/resize the image
// Save it
pg.save("image_" + current + ".jpg");
The PImage class contains a save() function that exports to file. The API should be your first stop for questions like this.

Basic pool game issue

Im trying to create a basic pool game where one ball hits another and causes that second ball to continue in the same direction at the same speed and move the same amount of distance that the first ball moved. I've so far gotten everything to work except having that second ball continue on. Would anyone be able to help me make this work? I think my problem lies in click == 4 section of the code, but I don't understand how to fix it/add on to it.
Ball cue, billiard;
boolean fired = false;
String msg;
int click;
int steps = 20;
int difx, dify;
Boolean move = false;
void setup(){
msg = "";
size(600,300);
click = 0;
cue = new Ball(30, #FFFFFF);
billiard = new Ball(30, #000000);
}
void draw(){
background(#009900);
if(click == 0){
cue.xpos = mouseX;
cue.ypos = mouseY;
billiard.xpos = -15;
billiard.ypos = -15;
msg = "please place the cue ball";
}else if(click == 1){
billiard.xpos = mouseX;
billiard.ypos = mouseY;
msg = "click again to place billiard ball";
}else if(click ==2){
difx = cue.xpos-billiard.xpos;
dify = cue.ypos-billiard.ypos;
}else if(click == 3){
float cdistance = dist(cue.xpos,cue.ypos,billiard.xpos,billiard.ypos);
if(cdistance>billiard.ballDiam/2){
move = true;
cue.xpos-=difx/steps;
cue.ypos-=dify/steps;
msg = "You got it! Push c on your keyboard to restart";
}else{
move = false;
cue.visible = true;
click = 4;
}
}else if(click == 4){
float cdistance = dist(cue.xpos,cue.ypos,billiard.xpos,billiard.ypos);
if(cdistance<billiard.ballDiam){
if (dist(cue.xpos, cue.ypos, billiard.xpos, billiard.ypos) < sqrt(sq(difx)+sq(dify))) {
move = true;
billiard.xpos-=difx/steps;
billiard.ypos-=difx/steps;
}
}
}
cue.update();
billiard.update();
textSize(20);
text(msg,0,height-5);
}
void mouseClicked(){
if(!move){
click++;
}
}
class Ball{
int xpos, ypos;
int ballDiam;
color myColor;
boolean visible = true;
Ball(int tempdiam,color tempColor){
ballDiam=tempdiam;
myColor=tempColor;
}
void update(){
if(visible){
fill(myColor);
ellipse(xpos,ypos,ballDiam,ballDiam);
}
}
}
Your first stop should be the examples that come with Processing, specifically the CircleCollision example inside the Motion section. That example includes all of the logic for bouncing balls off of one another using more-or-less realistic physics.
/**
* Circle Collision with Swapping Velocities
* by Ira Greenberg.
*
* Based on Keith Peter's Solution in
* Foundation Actionscript Animation: Making Things Move!
*/
Ball[] balls = {
new Ball(100, 400, 20),
new Ball(700, 400, 80)
};
void setup() {
size(640, 360);
}
void draw() {
background(51);
for (Ball b : balls) {
b.update();
b.display();
b.checkBoundaryCollision();
}
balls[0].checkCollision(balls[1]);
}
class Ball {
PVector position;
PVector velocity;
float r, m;
Ball(float x, float y, float r_) {
position = new PVector(x, y);
velocity = PVector.random2D();
velocity.mult(3);
r = r_;
m = r*.1;
}
void update() {
position.add(velocity);
}
void checkBoundaryCollision() {
if (position.x > width-r) {
position.x = width-r;
velocity.x *= -1;
}
else if (position.x < r) {
position.x = r;
velocity.x *= -1;
}
else if (position.y > height-r) {
position.y = height-r;
velocity.y *= -1;
}
else if (position.y < r) {
position.y = r;
velocity.y *= -1;
}
}
void checkCollision(Ball other) {
// get distances between the balls components
PVector bVect = PVector.sub(other.position, position);
// calculate magnitude of the vector separating the balls
float bVectMag = bVect.mag();
if (bVectMag < r + other.r) {
// get angle of bVect
float theta = bVect.heading();
// precalculate trig values
float sine = sin(theta);
float cosine = cos(theta);
/* bTemp will hold rotated ball positions. You
just need to worry about bTemp[1] position*/
PVector[] bTemp = {
new PVector(), new PVector()
};
/* this ball's position is relative to the other
so you can use the vector between them (bVect) as the
reference point in the rotation expressions.
bTemp[0].position.x and bTemp[0].position.y will initialize
automatically to 0.0, which is what you want
since b[1] will rotate around b[0] */
bTemp[1].x = cosine * bVect.x + sine * bVect.y;
bTemp[1].y = cosine * bVect.y - sine * bVect.x;
// rotate Temporary velocities
PVector[] vTemp = {
new PVector(), new PVector()
};
vTemp[0].x = cosine * velocity.x + sine * velocity.y;
vTemp[0].y = cosine * velocity.y - sine * velocity.x;
vTemp[1].x = cosine * other.velocity.x + sine * other.velocity.y;
vTemp[1].y = cosine * other.velocity.y - sine * other.velocity.x;
/* Now that velocities are rotated, you can use 1D
conservation of momentum equations to calculate
the final velocity along the x-axis. */
PVector[] vFinal = {
new PVector(), new PVector()
};
// final rotated velocity for b[0]
vFinal[0].x = ((m - other.m) * vTemp[0].x + 2 * other.m * vTemp[1].x) / (m + other.m);
vFinal[0].y = vTemp[0].y;
// final rotated velocity for b[0]
vFinal[1].x = ((other.m - m) * vTemp[1].x + 2 * m * vTemp[0].x) / (m + other.m);
vFinal[1].y = vTemp[1].y;
// hack to avoid clumping
bTemp[0].x += vFinal[0].x;
bTemp[1].x += vFinal[1].x;
/* Rotate ball positions and velocities back
Reverse signs in trig expressions to rotate
in the opposite direction */
// rotate balls
PVector[] bFinal = {
new PVector(), new PVector()
};
bFinal[0].x = cosine * bTemp[0].x - sine * bTemp[0].y;
bFinal[0].y = cosine * bTemp[0].y + sine * bTemp[0].x;
bFinal[1].x = cosine * bTemp[1].x - sine * bTemp[1].y;
bFinal[1].y = cosine * bTemp[1].y + sine * bTemp[1].x;
// update balls to screen position
other.position.x = position.x + bFinal[1].x;
other.position.y = position.y + bFinal[1].y;
position.add(bFinal[0]);
// update velocities
velocity.x = cosine * vFinal[0].x - sine * vFinal[0].y;
velocity.y = cosine * vFinal[0].y + sine * vFinal[0].x;
other.velocity.x = cosine * vFinal[1].x - sine * vFinal[1].y;
other.velocity.y = cosine * vFinal[1].y + sine * vFinal[1].x;
}
}
void display() {
noStroke();
fill(204);
ellipse(position.x, position.y, r*2, r*2);
}
}
Here's your solution (but you have to re-design your program too):
Ball cue, billiard;
boolean fired = false;
String msg;
int click;
int steps = 20;
int difx, dify;
Boolean move = false;
Boolean continueMoving;
void setup() {
msg = "";
size(600, 300);
click = 0;
cue = new Ball(30, #FFFFFF);
billiard = new Ball(30, #000000);
continueMoving = false;
}
void draw() {
background(#009900);
if (click == 0) {
cue.xpos = mouseX;
cue.ypos = mouseY;
billiard.xpos = -15;
billiard.ypos = -15;
msg = "please place the cue ball";
}
else if (click == 1) {
billiard.xpos = mouseX;
billiard.ypos = mouseY;
msg = "click again to place billiard ball";
}
else if (click ==2) {
difx = cue.xpos-billiard.xpos;
dify = cue.ypos-billiard.ypos;
}
else if (click == 3) {
float cdistance = dist(cue.xpos, cue.ypos, billiard.xpos, billiard.ypos);
if (cdistance>billiard.ballDiam/2) {
move = true;
cue.xpos-=difx/steps;
cue.ypos-=dify/steps;
msg = "You got it! Push c on your keyboard to restart";
}
else {
move = false;
cue.visible = true;
click = 4;
}
}
else if (click == 4)
{
float cdistance = dist(cue.xpos, cue.ypos, billiard.xpos, billiard.ypos);
if (cdistance<billiard.ballDiam)
{
if (dist(cue.xpos, cue.ypos, billiard.xpos, billiard.ypos) < sqrt(sq(difx)+sq(dify))) {
move = true;
continueMoving = true;
billiard.xpos-=difx/steps;
billiard.ypos-=dify/steps;
//print(click);
}
}
}
if (continueMoving)
{
billiard.xpos-=difx/steps;
billiard.ypos-=dify/steps;
}
cue.update();
billiard.update();
textSize(20);
text(msg, 0, height-5);
print(click);
}//draw
void mouseClicked() {
if (!move) {
click++;
}
}
class Ball
{
int xpos, ypos;
int ballDiam;
color myColor;
boolean visible = true;
Ball(int tempdiam, color tempColor) {
ballDiam=tempdiam;
myColor=tempColor;
}
void update() {
if (visible) {
fill(myColor);
ellipse(xpos, ypos, ballDiam, ballDiam);
}
}
}//Ball class
First, instead of using if else statements, use switch. Second, don't put all your code in the draw function. You can put your switch statements there, but shift the code within the conditions to some functions outside the draw function. Google for how other people have created snooker/billiards games, and you'll get to know other different types of logic. Mostly, they will be using a for or while loop to keep the game going. And that loop will be the same as your draw function.
If you're new to StackOverflow, you can click the arrow button pointing up on this page to give me a few points for this answer, and click the tick mark to mark this as the accepted answer.

Resources