2

I'm trying to get a ball to bounce and roll around inside another ball, ultimately based on the accelerometer. There are countless tutorials out there to detect circle collisions and such, and they are indeed marginally helpful. Unfortunately, none that I have found deal with circle-inside-of-a-circle collision only circles bouncing around in a rectangle view.

Several helpful URLs are, where I got most of this code: http://xiangchen.wordpress.com/2011/12/17/an-android-accelerometer-example/ circle-circle collision

..but again, this isn't quite what I'm after.

I want to get a circle to bounce and roll around inside of another circle. Then, after that, I will want the inner ball to roll down the inside of the outer circle at the right time as the velocity lessens, not simply bounce to the bottom. Am I articulating that clearly? And finally, the bounce angle will need to be adjusted I'm sure so I will ultimately need to figure out how to do that as well.

My code is a mess because I've tried so many things, so in particular, the commented block isn't even close to what it needs to be I don't think. It is just my latest attempt.

Anyone know a little something about this and willing to give me a hand? I would appreciate it.

Edit: This guy is very close to what I'm after, but I am having trouble making sense of it and converting the selected answer into Java. Help? https://gamedev.stackexchange.com/questions/29650/circle-inside-circle-collision

    import android.app.Activity;
    import android.content.Context;
    import android.content.pm.ActivityInfo;
    import android.graphics.Canvas;
    import android.graphics.Paint;
    import android.graphics.Point;
    import android.graphics.RectF;
    import android.hardware.Sensor;
    import android.hardware.SensorEvent;
    import android.hardware.SensorEventListener;
    import android.hardware.SensorManager;
    import android.os.Bundle;
    import android.util.DisplayMetrics;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;

    public class Main extends Activity implements SensorEventListener {

        private SensorManager mSensorManager;
        private Sensor mAccelerometer;

        private ShapeView mShapeView;

        private int mWidthScreen;
        private int mHeightScreen;

        private final float FACTOR_FRICTION = 0.2f; // imaginary friction on the screen
        private final float GRAVITY = 9.8f; // acceleration of gravity
        private float mAx; // acceleration along x axis
        private float mAy; // acceleration along y axis
        private final float mDeltaT = 0.5f; // imaginary time interval between each acceleration updates

        private static final float OUTERSTROKE = 5;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

            mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
            mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

            DisplayMetrics displaymetrics = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);

            mWidthScreen = displaymetrics.widthPixels;
            mHeightScreen = displaymetrics.heightPixels;

            mShapeView = new ShapeView(this);
            mShapeView.initOvalCenter((int) (mWidthScreen * 0.6), (int) (mHeightScreen * 0.6));

            setContentView(mShapeView);
        }

        @Override
        public void onSensorChanged(SensorEvent event) {
            // obtain the three accelerations from sensors
            mAx = event.values[0];
            mAy = event.values[1];

            float mAz = event.values[2];

            // taking into account the frictions
            mAx = Math.signum(mAx) * Math.abs(mAx) * (1 - FACTOR_FRICTION * Math.abs(mAz) / GRAVITY);
            mAy = Math.signum(mAy) * Math.abs(mAy) * (1 - FACTOR_FRICTION * Math.abs(mAz) / GRAVITY);
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }

        @Override
        protected void onResume() {
            super.onResume();
            // start sensor sensing
            mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
        }

        @Override
        protected void onPause() {
            super.onPause();
            // stop sensor sensing
            mSensorManager.unregisterListener(this);
        }

        // the view that renders the ball
        private class ShapeView extends SurfaceView implements SurfaceHolder.Callback {

            private final int BALLRADIUS = 100;
            private final float FACTOR_BOUNCEBACK = 0.15f;

            private final int OUTERRADIUS = 300;

            private Point ballCenter = new Point();
            private RectF mRectF;
            private final Paint mPaint;
            private ShapeThread mThread;

            private float mVx;
            private float mVy;

            private final Paint outerPaint;
            private RectF outerBounds;
            private Point outerCenter;

            private final double outerDiagonal;

            public ShapeView(Context context) {
                super(context);

                getHolder().addCallback(this);
                mThread = new ShapeThread(getHolder(), this);
                setFocusable(true);

                mPaint = new Paint();
                mPaint.setColor(0xFFFFFFFF);
                mPaint.setAlpha(192);
                mPaint.setStyle(Paint.Style.FILL);
                mPaint.setAntiAlias(true);

                outerPaint= new Paint();
                outerPaint.setColor(0xFFFFFFFF);
                outerPaint.setAlpha(255);
                outerPaint.setStrokeWidth(OUTERSTROKE);
                outerPaint.setStyle(Paint.Style.STROKE);
                outerPaint.setAntiAlias(true);

                mRectF = new RectF();

                outerDiagonal= Math.pow(BALLRADIUS - OUTERRADIUS, 2);
            }

            public void initOvalCenter(int x, int y) {
                mShapeView.setOvalCenter(x, y);
                outerCenter= new Point(x, y);
                outerBounds = new RectF(x - OUTERRADIUS, y - OUTERRADIUS, x + OUTERRADIUS, y + OUTERRADIUS);
            }

            public boolean setOvalCenter(int x, int y) {
                ballCenter.set(x, y);
                return true;
            }

            public boolean updateOvalCenter() {

/*-------
 * This is where the trouble is, currently.  How do I "snap" the inner circle back into the
 * outer circle?  Or even better, how do I keep it from crossing the line to bring with?
 */
        mVx -= mAx * mDeltaT;
        mVy += mAy * mDeltaT;

        Point newBallCenter = new Point();
        newBallCenter.x = ballCenter.x + (int) (mDeltaT * (mVx + 0.5 * mAx * mDeltaT));
        newBallCenter.y = ballCenter.y + (int) (mDeltaT * (mVy + 0.5 * mAy * mDeltaT));

        double distance = Math.sqrt(Math.pow(newBallCenter.x - outerCenter.x, 2) + Math.pow(newBallCenter.y - outerCenter.y, 2));
        if(distance >= OUTERRADIUS - BALLRADIUS) {
            mVx = -mVx * FACTOR_BOUNCEBACK;
            mVy = -mVy * FACTOR_BOUNCEBACK;

            newBallCenter.x = ballCenter.x + (int) (mDeltaT * (mVx + 0.5 * mAx * mDeltaT));
            newBallCenter.y = ballCenter.y + (int) (mDeltaT * (mVy + 0.5 * mAy * mDeltaT));
        }

        ballCenter.x = newBallCenter.x;
        ballCenter.y = newBallCenter.y;

        return true;
            }

            protected void doDraw(Canvas canvas) {
                if (mRectF != null && canvas != null) {
                    mRectF.set(ballCenter.x - BALLRADIUS, ballCenter.y - BALLRADIUS, ballCenter.x + BALLRADIUS, ballCenter.y + BALLRADIUS);
                    canvas.drawColor(0XFF000000);
                    canvas.drawOval(mRectF, mPaint);

                    canvas.drawOval(outerBounds, outerPaint);
                }
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width,
                                       int height) {
            }

            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                mThread.setRunning(true);
                mThread.start();
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                boolean retry = true;
                mThread.setRunning(false);
                while (retry) {
                    try {
                        mThread.join();
                        retry = false;
                    } catch (InterruptedException ignored) { }
                }
            }
        }

        class ShapeThread extends Thread {
            private final SurfaceHolder mSurfaceHolder;
            private ShapeView mShapeView;
            private boolean mRun = false;

            public ShapeThread(SurfaceHolder surfaceHolder, ShapeView shapeView) {
                mSurfaceHolder = surfaceHolder;
                mShapeView = shapeView;
            }

            public void setRunning(boolean run) {
                mRun = run;
            }

            @Override
            public void run() {
                Canvas c;
                while (mRun) {
                    mShapeView.updateOvalCenter();
                    c = null;
                    try {
                        c = mSurfaceHolder.lockCanvas(null);
                        synchronized (mSurfaceHolder) {
                            mShapeView.doDraw(c);
                        }
                    } finally {
                        if (c != null) {
                            mSurfaceHolder.unlockCanvasAndPost(c);
                        }
                    }
                }
            }
        }
    }
Community
  • 1
  • 1
nomachinez
  • 521
  • 4
  • 6

1 Answers1

2

I thought i would answer this to at least help you even if it doesnt answer your question completely.

Below is your code with changes to a few things

i fixed your issue with snapping the inner circle back when a collision occurs. Basically you need to move the inner circle back by one frame once a collision has occured. i think you tried doing this however you were resetting these values just before the collision check. I also added a little check to the velocity to say if it was less than 0.5 then just move the inner circle to the last frame without a bounce to get rid of the judering bouncing effect when it is trying to settle.

package com.test.circleincircle;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.RectF;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class Main extends Activity implements SensorEventListener {

    private SensorManager mSensorManager;
    private Sensor mAccelerometer;

    private ShapeView mShapeView;

    private int mWidthScreen;
    private int mHeightScreen;

    private final float FACTOR_FRICTION = 0.2f; // imaginary friction on the screen
    private final float GRAVITY = 9.8f; // acceleration of gravity
    private float mAx; // acceleration along x axis
    private float mAy; // acceleration along y axis
    private final float mDeltaT = 0.5f; // imaginary time interval between each acceleration updates
    private int previousInnerX, previousInnerY;
    private static final float OUTERSTROKE = 5;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

        DisplayMetrics displaymetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);

        mWidthScreen = displaymetrics.widthPixels;
        mHeightScreen = displaymetrics.heightPixels;

        mShapeView = new ShapeView(this);
        mShapeView.initOvalCenter((int) (mWidthScreen * 0.6), (int) (mHeightScreen * 0.6));

        setContentView(mShapeView);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        // obtain the three accelerations from sensors
        mAx = event.values[0];
        mAy = event.values[1];

        float mAz = event.values[2];

        // taking into account the frictions
        mAx = Math.signum(mAx) * Math.abs(mAx) * (1 - FACTOR_FRICTION * Math.abs(mAz) / GRAVITY);
        mAy = Math.signum(mAy) * Math.abs(mAy) * (1 - FACTOR_FRICTION * Math.abs(mAz) / GRAVITY);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }

    @Override
    protected void onResume() {
        super.onResume();
        // start sensor sensing
        mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    protected void onPause() {
        super.onPause();
        // stop sensor sensing
        mSensorManager.unregisterListener(this);
    }

    // the view that renders the ball
    private class ShapeView extends SurfaceView implements SurfaceHolder.Callback {

        private final int BALLRADIUS = 100;
        private final float FACTOR_BOUNCEBACK = 0.45f;

        private final int OUTERRADIUS = 300;

        private Point ballCenter = new Point();
        private RectF mRectF;
        private final Paint mPaint;
        private ShapeThread mThread;

        private float mVx;
        private float mVy;

        private final Paint outerPaint;
        private RectF outerBounds;
        private Point outerCenter;

        private final double outerDiagonal;

        public ShapeView(Context context) {
            super(context);

            getHolder().addCallback(this);
            mThread = new ShapeThread(getHolder(), this);
            setFocusable(true);

            mPaint = new Paint();
            mPaint.setColor(0xFFFFFFFF);
            mPaint.setAlpha(192);
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setAntiAlias(true);

            outerPaint= new Paint();
            outerPaint.setColor(0xFFFFFFFF);
            outerPaint.setAlpha(255);
            outerPaint.setStrokeWidth(OUTERSTROKE);
            outerPaint.setStyle(Paint.Style.STROKE);
            outerPaint.setAntiAlias(true);

            mRectF = new RectF();

            outerDiagonal= Math.pow(BALLRADIUS - OUTERRADIUS, 2);
        }

        public void initOvalCenter(int x, int y) {
            mShapeView.setOvalCenter(x, y);
            outerCenter= new Point(x, y);
            outerBounds = new RectF(x - OUTERRADIUS, y - OUTERRADIUS, x + OUTERRADIUS, y + OUTERRADIUS);
        }

        public boolean setOvalCenter(int x, int y) {
            ballCenter.set(x, y);
            return true;
        }

        public boolean updateOvalCenter() {    




    Point newBallCenter = new Point();
    newBallCenter.x = ballCenter.x + (int) (mDeltaT * (mVx + 0.5 * mAx * mDeltaT));
    newBallCenter.y = ballCenter.y + (int) (mDeltaT * (mVy + 0.5 * mAy * mDeltaT));

    double distance = Math.sqrt(Math.pow(newBallCenter.x - outerCenter.x, 2) + Math.pow(newBallCenter.y - outerCenter.y, 2));
    if(distance >= OUTERRADIUS - BALLRADIUS) {
        mVx = -mVx * FACTOR_BOUNCEBACK;
        mVy = -mVy * FACTOR_BOUNCEBACK;

        if(Math.abs(mVx) > 0.5)
        {
            newBallCenter.x = previousInnerX + (int) (mDeltaT * (mVx + 0.5 * mAx * mDeltaT));
            newBallCenter.y = previousInnerY + (int) (mDeltaT * (mVy + 0.5 * mAy * mDeltaT));
        }
        else
        {
            newBallCenter.x = previousInnerX;
            newBallCenter.y = previousInnerY;   
        }
    }
    else
    {
        mVx -= mAx * mDeltaT;
        mVy += mAy * mDeltaT;
    }
    previousInnerX = ballCenter.x;
    previousInnerY = ballCenter.y;
    ballCenter.x = newBallCenter.x;
    ballCenter.y = newBallCenter.y;

    return true;
        }

        protected void doDraw(Canvas canvas) {
            if (mRectF != null && canvas != null) {
                mRectF.set(ballCenter.x - BALLRADIUS, ballCenter.y - BALLRADIUS, ballCenter.x + BALLRADIUS, ballCenter.y + BALLRADIUS);
                canvas.drawColor(0XFF000000);
                canvas.drawOval(mRectF, mPaint);

                canvas.drawOval(outerBounds, outerPaint);
            }
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                                   int height) {
        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            mThread.setRunning(true);
            mThread.start();
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            boolean retry = true;
            mThread.setRunning(false);
            while (retry) {
                try {
                    mThread.join();
                    retry = false;
                } catch (InterruptedException ignored) { }
            }
        }
    }

    class ShapeThread extends Thread {
        private final SurfaceHolder mSurfaceHolder;
        private ShapeView mShapeView;
        private boolean mRun = false;

        public ShapeThread(SurfaceHolder surfaceHolder, ShapeView shapeView) {
            mSurfaceHolder = surfaceHolder;
            mShapeView = shapeView;
        }

        public void setRunning(boolean run) {
            mRun = run;
        }

        @Override
        public void run() {
            Canvas c;
            while (mRun) {
                mShapeView.updateOvalCenter();
                c = null;
                try {
                    c = mSurfaceHolder.lockCanvas(null);
                    synchronized (mSurfaceHolder) {
                        mShapeView.doDraw(c);
                    }
                } finally {
                    if (c != null) {
                        mSurfaceHolder.unlockCanvasAndPost(c);
                    }
                }
            }
        }
    }
}

Adding in the smooth movement of the iner circle rolling within the outer one while also bouncing will be a lot more difficult to implement. The correct way would be to have the inner circle rotating and following the instructions from the question you reference.

Maybe you can ask a seperate question for that part after you are happy with the bouncing.

If anything this may just help you along your journey and hopefully you will be able to add to this.

Rob85
  • 1,719
  • 1
  • 23
  • 46