Collision with inclined plane.

hello guys

I trying to implement collision with an inclined plane.
I have some balls that fall within a cube which is already implemented collision with the ball and the cube, gravity and loss of energy.
What I want is to implement collision with an inclined plane and making the balls slip in the plane.As larger and heavier balls should just slip, the smallers should bounce and slipping.
Any idea?

This is the class that draws everything:


package coreEngine;

import geometries.Cubo;
import geometries.Esfera;
import geometries.Rectangle;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Random;

import javax.imageio.ImageIO;

import matematcbase.Matrix4x4;
import matematcbase.Vector3f;

import obj.ObjModel;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
import org.lwjgl.util.vector.Vector2f;

import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureLoader;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.util.glu.GLU.*;

/*
 * Created on 21/03/2010
 * Atualizado Para Ver��o 1.0
 * Desenvolvido Por Dennis Kerr Coelho
 * PalmSoft Tecnologia
 */

public class CanvasGame extends PS_3DCanvas {

	private final static double CAMERA_Z = -8.0;
	private final static double FOVY = 45.0; // field-of-view angle around Y

	private final static double NEAR = 0.1; // Z values < NEAR are clipped
	private final static double FAR = 30.0; // Z values > FAR are clipped

	// private GLU glu = new GLU ();

	// Graphics2D dbg = null;

	// Do not generate a rotation increment greater than MAX_ROT_INC degrees.

	private final static float MAX_ROT_INC = 15.0f;

	// Total and increment rotation variables.

	public static float rotAngleX, rotAngleY, rotAngleZ;
	private float incX, incY, incZ;
	private float angTX, angTY, angTZ;
	private float Px, Py, Pz;

	public static Texture[] textures;

	private Vector2f quadPosition;
	private Vector2f quadVelocity;
	private float angle;
	private float angleRotation = 1.0f;
	private static final float MAX_SPEED = 20.0f;

	// Vector3f v = new Vector3f(0,0,1);
	// Vector3f v1 = new Vector3f(0,1,0);
	// Vector3f v2 = new Vector3f(1,0,0);

	public static ArrayList<objeto3d> listadeobjetos = new ArrayList<objeto3d>();

	boolean MatrixOpengl = true;

	ObjModel model1;

	Random rnd = new Random();

	float vel = 0.5f;
	Cubo cubo = new Cubo();
	Rectangle rectangle = new Rectangle(1.0f, 2.5f);

	public CanvasGame() {

		// for(int i = 0; i < 100;i++){
		// listadeobjetos.add(new
		// meuObjeto((float)((Math.random()*8)-4),(float)((Math.random()*8)-4),(float)((Math.random()*8)-4)));
		// }

		model1 = new ObjModel();
		model1.loadObj("/tank.obj");

		for (int i = 0; i < 20; i++) {
			Esfera esfera = new Esfera((float) (-2 + (Math.random() * 4)),
					(float) (-2 + (Math.random() * 4)),
					(float) (-2 + (Math.random() * 4)),
					0.1f + ((float) (0.2 * Math.random())));
			boolean colidiu = false;
			do {
				colidiu = false;
				esfera.X = (float) (-2 + (Math.random() * 4));
				esfera.Y = (float) (-2 + (Math.random() * 4));
				esfera.Z = (float) (-2 + (Math.random() * 4));
				esfera.setCorR((float) Math.random());
				esfera.setCorG((float) Math.random());
				esfera.setCorB((float) Math.random());
				esfera.setVx((vel + rnd.nextFloat() * vel)
						* (rnd.nextFloat() > 0.5 ? 1 : -1));
				// esfera.vy =
				// (vel+rnd.nextFloat()*vel)*(rnd.nextFloat()>0.5?1:-1);
				esfera.setVy((-9.8f));
				esfera.setVz((vel + rnd.nextFloat() * vel)
						* (rnd.nextFloat() > 0.5 ? 1 : -1));

				for (int j = 0; j < listadeobjetos.size(); j++) {
					Esfera es = (Esfera) CanvasGame.listadeobjetos.get(j);
					if (esfera.colideesfera(es)) {
						colidiu = true;
						break;
					}
				}
			} while (colidiu);
			listadeobjetos.add(esfera);
		}

	}

	@Override
	public void init() {

		Px = 0;
		Py = 0;
		Pz = 0;

		glEnable(GL_DEPTH_TEST);

		glEnable(GL_LIGHTING);

		// Initialize rotation accumulator and increment variables.

		rotAngleX = rotAngleY = rotAngleZ = 0.0f;

		rotAngleX = 0.0f;

		Random rand = new Random();
		incX = 1;// rand.nextFloat ()*MAX_ROT_INC;
		incY = 1;// rand.nextFloat ()*MAX_ROT_INC;
		incZ = 1;// rand.nextFloat ()*MAX_ROT_INC;

		angTX = angTY = angTZ = 0;

		// Load six 2D textures to decal the cube. If the image file on which
		// the
		// texture is based does not exist, load() returns null.
		textures = new Texture[2];
		// try {
		//
		// textures [0] = TextureLoader.getTexture("PNG",
		// this.getClass().getResourceAsStream("LogoUNIVALI.png"));
		// textures [1] = TextureLoader.getTexture("PNG",
		// this.getClass().getResourceAsStream("textura-tanque.png"));
		// } catch (FileNotFoundException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// } catch (IOException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }
		//
		// textures [0].bind();
		//

		// int twidth = (int)textures [0].getImageWidth();
		// int theight = (int)textures [0].getImageHeight();
		//
		// System.out.println("twidth "+twidth+" theight "+theight+" texdata ");

		// ByteBuffer buffer = BufferUtils.createByteBuffer((textures
		// [0].hasAlpha() ? 4 : 3) * twidth * theight);
		// //System.out.println("buffer.capacity() "+buffer.capacity());
		// glGetTexImage(GL_TEXTURE_2D, 0, textures [0].hasAlpha() ? GL_RGBA :
		// GL_RGB, GL_UNSIGNED_BYTE, buffer);

		// int components = textures [0].hasAlpha() ? 4 : 3;
		//
		// System.out.println("components "+components);
		// gluBuild2DMipmaps(GL_TEXTURE_2D, components, twidth, theight,
		// components==3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE,buffer);
		//
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);// GL_LINEAR_MIPMAP_LINEAR);
		// glTexParameteri(GL_TEXTURE_2D,
		// EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, 8);

		// glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

		int width = GameMain.instance.display_parent.getWidth();
		int height = GameMain.instance.display_parent.getHeight();

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		float aspect = (float) width / (float) height;
		gluPerspective((float) FOVY, (float) aspect, (float) NEAR, (float) FAR);
		glMatrixMode(GL_MODELVIEW);

		// glMatrixMode(GL_PROJECTION);
		// glLoadIdentity();
		// gluOrtho2D(0, GameMain.instance.display_parent.getWidth(), 0,
		// GameMain.instance.display_parent.getHeight());
		// glMatrixMode(GL_MODELVIEW);
		// glLoadIdentity();
		// glViewport(0, 0, GameMain.instance.display_parent.getWidth(),
		// GameMain.instance.display_parent.getHeight());
		// //set clear color to black
		// glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		// //sync frame (only works on windows)
		// Display.setVSyncEnabled(true);
		// //System.out.println("Inicio");

	}

	int keytimer = 0;

	@Override
	public void SimulaSe(double diftime) {

		keytimer += diftime;

		if (Keyboard.isKeyDown(Keyboard.KEY_UP)) {
			rotAngleX += (diftime / 10.0f);
		}
		if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)) {
			rotAngleX -= (diftime / 10.0f);
		}
		if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)) {
			rotAngleY += (diftime / 10.0f);
		}
		if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) {
			rotAngleY -= (diftime / 10.0f);
		}

		if (Keyboard.isKeyDown(Keyboard.KEY_W)) {
			Py += (diftime / 100.0f);
		}
		if (Keyboard.isKeyDown(Keyboard.KEY_S)) {
			Py -= (diftime / 100.0f);
		}
		if (Keyboard.isKeyDown(Keyboard.KEY_A)) {
			Px += (diftime / 100.0f);
		}
		if (Keyboard.isKeyDown(Keyboard.KEY_D)) {
			Px -= (diftime / 100.0f);
		}

		if (Keyboard.isKeyDown(Keyboard.KEY_F1) && keytimer > 200) {
			MatrixOpengl = !MatrixOpengl;
			keytimer = 0;
		}

		for (int i = 0; i < listadeobjetos.size(); i++) {
			listadeobjetos.get(i).simulaSe((int) diftime);
		}

		// if(UP){
		// rotAngleX = (float)(90.0f*diftime/1000.0f);
		// Matrix4x4 m = new Matrix4x4();
		// m.setRotate(rotAngleX, v2.x, v2.y, v2.z);
		// m.transform(v);
		// m.transform(v1);
		// m.transform(v2);
		// v.Normalize();
		// v1.Normalize();
		// v2.Normalize();
		// }
		// if(DOWN){
		// rotAngleX = -(float)(90.0f*diftime/1000.0f);
		// Matrix4x4 m = new Matrix4x4();
		// m.setRotate(rotAngleX, v2.x, v2.y, v2.z);
		// m.transform(v);
		// m.transform(v1);
		// m.transform(v2);
		// v.Normalize();
		// v1.Normalize();
		// v2.Normalize(); }
		//
		//
		//
		// if(RIGHT){
		// rotAngleY =(float)(90.0f*diftime/1000.0f);
		// Matrix4x4 m = new Matrix4x4();
		// m.setRotate(rotAngleY, v1.x, v1.y, v1.z);
		// m.transform(v);
		// m.transform(v1);
		// m.transform(v2);
		// v.Normalize();
		// v1.Normalize();
		// v2.Normalize(); }
		// if(LEFT){
		// rotAngleY = -(float)(90.0f*diftime/1000.0f);
		// Matrix4x4 m = new Matrix4x4();
		// m.setRotate(rotAngleY, v1.x, v1.y, v1.z);
		// m.transform(v);
		// m.transform(v1);
		// m.transform(v2);
		// v.Normalize();
		// v1.Normalize();
		// v2.Normalize();
		// }
		//
		//
		// if(TQ){
		// rotAngleZ =(float)(90.0f*diftime/1000.0f);
		// Matrix4x4 m = new Matrix4x4();
		// m.setRotate(rotAngleZ, v.x, v.y, v.z);
		// m.transform(v);
		// m.transform(v1);
		// m.transform(v2);
		// v.Normalize();
		// v1.Normalize();
		// v2.Normalize(); }
		// if(TE){
		// rotAngleZ = -(float)(90.0f*diftime/1000.0f);
		// Matrix4x4 m = new Matrix4x4();
		// m.setRotate(rotAngleZ, v.x, v.y, v.z);
		// m.transform(v);
		// m.transform(v1);
		// m.transform(v2);
		// v.Normalize();
		// v1.Normalize();
		// v2.Normalize();
		// }
		//
		// if(TW){
		// X+=v.x*4*diftime/1000.0f;
		// Y+=v.y*4*diftime/1000.0f;
		// Z+=v.z*4*diftime/1000.0f;
		// }
		// if(TS){
		// X-=v.x*4*diftime/1000.0f;
		// Y-=v.y*4*diftime/1000.0f;
		// Z-=v.z*4*diftime/1000.0f;
		// }

		// if(TA){
		// X+=v1.x*4*diftime/1000.0f;
		// Y+=v1.y*4*diftime/1000.0f;
		// Z+=v1.z*4*diftime/1000.0f;
		// }
		// if(TD){
		// X-=v1.x*4*diftime/1000.0f;
		// Y-=v1.y*4*diftime/1000.0f;
		// Z-=v1.z*4*diftime/1000.0f;
		// }

	}

	float soma = 0;

	@Override
	public void DesenhaSe() {
		// Compute total rotations around X, Y, and Z axes.
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glLoadIdentity();

		soma += 1.0;

		float[] lightDiffuse = { 1.0f, 1.0f, 1.0f, 1.0f }; // yellow diffuse :
															// color where light
															// hit directly the
															// object's surface
		float[] lightAmbient = { 1.0f, 1.0f, 1.0f, 1.0f }; // red ambient :
															// color applied
															// everywhere
		float[] lightPosition = { 0.0f, 0.0f, (float) 1.0, 1.0f };

		ByteBuffer temp = ByteBuffer.allocateDirect(16);
		temp.order(ByteOrder.nativeOrder());
		glLight(GL_LIGHT0, GL_AMBIENT,
				(FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip());
		glLight(GL_LIGHT0, GL_DIFFUSE,
				(FloatBuffer) temp.asFloatBuffer().put(lightDiffuse).flip());
		glLight(GL_LIGHT0, GL_POSITION,
				(FloatBuffer) temp.asFloatBuffer().put(lightPosition).flip());

		glEnable(GL_LIGHT0);

		gluLookAt(0.0f, 0.0f, (float) CAMERA_Z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
				0.0f);

		// Perform the rotations.

		if (MatrixOpengl) {
			glRotatef(rotAngleX, 1.0f, 0.0f, 0.0f);
			glTranslatef(0.0f, 0.0f, -Py);
		} else {
			Matrix4x4 m = new Matrix4x4();
			// m.setRotateX(rotX);
			m.setRotate(rotAngleX, 1.0f, 0.0f, 0.0f);
			glMultMatrix(m.toFloatBuffer());
			System.out.println("rodando na unha");
		}
		glRotatef(rotAngleY, 0.0f, 1.0f, 0.0f);
		glRotatef(rotAngleZ, 0.0f, 0.0f, 1.0f);

		// clear background
		// glEnable(GL_COLOR_MATERIAL);
		// glClear(GL_COLOR_BUFFER_BIT);
		// // draw white quad

		glEnable(GL_TEXTURE_2D);

		Texture texture = textures[1];

		// texture.bind ();

		// glPushMatrix();
		// {
		// glScaled(0.04, 0.04, 0.04);
		// model1.desenhase();
		// } glPopMatrix();

		texture = textures[0];

		// texture.bind ();

		// glPushMatrix();
		// {
		//
		// if(MatrixOpengl){
		// glTranslatef(Px, Py, Pz);
		// }else{
		// Matrix4x4 m = new Matrix4x4();
		// m.setIdentity();
		// //m.setRotateX(rotX);
		// m.setTranslate(new Vector3f(Px, Py, Pz));
		// glMultMatrix(m.toFloatBuffer());
		// System.out.println("rodando na unha");
		// }
		//
		//

		glDisable(GL_TEXTURE_2D);
		glEnable(GL_COLOR_MATERIAL);
		// glClear(GL_COLOR_BUFFER_BIT);

		FloatBuffer fb = ByteBuffer.allocateDirect(64).asFloatBuffer();
		glGetFloat(GL_MODELVIEW_MATRIX, fb);

		glLoadIdentity();

		glMultMatrix(fb);

		for (int i = 0; i < listadeobjetos.size(); i++) {
			listadeobjetos.get(i).desenhaSe();
		}

		cubo.desenhase(2.5f, 2.5f, 2.5f);
		glPushMatrix();
		glColor3f(0, 1, 0);
		glTranslatef(0, 0, 1);
		
		glRotatef(60, 0, 1, 0);
		glRotatef(40, 1, 0, 0);
		glTranslatef(0, -0.7f, 0);
		rectangle.drawRectangle2D();
		glPopMatrix();
		glColor3f(1.0f, 1.0f, 1.0f);
		glDisable(GL_COLOR_MATERIAL);
		// glClear(GL_COLOR_BUFFER_BIT);

		// System.out.println("pinta");
	}

	@Override
	public void reshape(int x, int y, int width, int height) {
		// TODO Auto-generated method stub

	}

	// Texture load (String filename,GL gl)
	// {
	// Texture texture = null;
	//
	// try
	// {
	//
	// System.out.println(" "+new
	// File(getClass().getResource(filename).toString()));
	//
	// InputStream stream = getClass().getResourceAsStream(filename);
	// TextureData data = TextureIO.newTextureData(stream, false, "png");
	//
	//
	// data.setMipmap(true);
	//
	// texture = TextureIO.newTexture (data);
	//
	//
	// texture.setTexParameteri (GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	//
	//
	// texture.setTexParameteri (GL_TEXTURE_MIN_FILTER,
	// GL_LINEAR_MIPMAP_NEAREST);
	//
	// }
	// catch (Exception e)
	// {
	// System.out.println ("error loading texture from "+filename);
	// }
	//
	// return texture;
	// }

}

and this is the Sphere class


package geometries;

import static org.lwjgl.opengl.GL11.GL_COLOR_MATERIAL;
import static org.lwjgl.opengl.GL11.glColor3f;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glPopMatrix;
import static org.lwjgl.opengl.GL11.glPushMatrix;
import static org.lwjgl.opengl.GL11.glTranslatef;

import org.lwjgl.util.glu.Sphere;

import coreEngine.CanvasGame;
import coreEngine.objeto3d;

public class Esfera extends objeto3d {

	float R;

	private float vx = 0;
	private float vy = 0;
	private float vz = 0;

	public float dy = 0;

	private float corR = 1;
	private float corG = 1;
	private float corB = 1;

	public float getVx() {
		return vx;
	}

	public void setVx(float vx) {
		this.vx = vx;
	}

	public float getVy() {
		return vy;
	}

	public void setVy(float vy) {
		this.vy = vy;
	}

	public float getVz() {
		return vz;
	}

	public void setVz(float vz) {
		this.vz = vz;
	}

	public float getCorR() {
		return corR;
	}

	public void setCorR(float corR) {
		this.corR = corR;
	}

	public float getCorG() {
		return corG;
	}

	public void setCorG(float corG) {
		this.corG = corG;
	}

	public float getCorB() {
		return corB;
	}

	public void setCorB(float corB) {
		this.corB = corB;
	}

	Sphere sphere;

	float grav = 9.8f;
	float perdaDeEnergia = 20;
	float energia = 0;

	public Esfera(float X, float Y, float Z, float R) {
		this.X = X;
		this.Y = Y;
		this.Z = Z;

		this.R = R;

		sphere = new Sphere();

		this.dy = this.energia = 50;

	}

	@Override
	public void desenhaSe() {
		glEnable(GL_COLOR_MATERIAL);
		glColor3f(corR, corG, corB);
		glPushMatrix();
		glTranslatef(X, Y, Z);
		sphere.draw(R, 10, 10);
		glPopMatrix();
	}

	@Override
	public void simulaSe(int diftime) {
		float oldx = X;
		float oldy = Y;
		float oldz = Z;

		// TENTATIVA FRACASSADA
		X += vx * diftime / 1000.0f;
		// Y += vy*diftime/1000.0f;
		Z += vz * diftime / 1000.0f;
		// Y += (vy*diftime/1000.0f)*grav;
		/*
		 * if(energia > 0){ if (this.dy > 0) { Y += (vy*diftime/1000.0f); dy--;
		 * }else{ Y -= (vy*diftime/1000.0f); } }
		 */

		// Gravidade
		float tempo = (float) (diftime / 1000.0f);
		Y -= (vy * tempo) + (grav * (float) Math.pow(tempo, 2)) / 2.0f;
		vy += grav * tempo;
		vy = (vy > 40.0f) ? 40.0f : vy;

		vx = (vx > 40.0f) ? 40.0f : vx;
		vz = (vz > 40.0f) ? 40.0f : vz;

		for (int i = 0; i < CanvasGame.listadeobjetos.size(); i++) {
			Esfera es = (Esfera) CanvasGame.listadeobjetos.get(i);
			if (es != this) {
				if (colideesfera(es)) {
					X = oldx;
					Y = oldy;
					Z = oldz;

					if (vx > 0) {
						vx = vx - (perdaDeEnergia * R);
						vx = -vx;
					} else {
						vx = vx + (perdaDeEnergia * R);
						vx = -vx;
					}

					vy = vy - (perdaDeEnergia * R);
					vy = -vy;

					if (vz > 0) {
						vz = vz - (perdaDeEnergia * R);
						vz = -vz;
					} else {
						vx = vz + (perdaDeEnergia * R);
						vz = -vz;
					}

					break;
				}
			}
		}

		if (X < -2) {
			X = oldx;
			vx = -vx;
		}

		if (X > 2) {
			X = oldx;
			vx = -vx;
		}

		if (Y < -2.2f) {
			Y = oldy;
			vy = vy - (perdaDeEnergia * R);
			vy = -vy;
		}

		if (Y > 2) {
			// Y = oldy;
			// vy = vy - (perdaDeEnergia*R);
			// vy = -vy;
			// energia -= (perdaDeEnergia*R);
			// System.out.println(vy);
		}

		if (Z < -2) {
			Z = oldz;
			vz = -vz;
		}

		if (Z > 2) {
			Z = oldz;
			vz = -vz;
		}
	}

	public boolean colideesfera(Esfera esf) {

		float difx = esf.X - X;
		float dify = esf.Y - Y;
		float difz = esf.Z - Z;

		float dist = difx * difx + dify * dify + difz * difz;

		if (((esf.R + R) * (esf.R + R)) > dist) {
			return true;
		}

		return false;
	}
}


Firstly, that is far too much code to post. You should be posting small sections of relevant code. This code is both long and not relevant and also (and I mean no offence here) but it is in what looks like Spanish and that isn’t helping the members of an English speaking forum either.

Now your question. Firstly, are you talking about just the maths of the collision, or the collision response. If you’re talking about the collision then a Google search for “plane-sphere collision test” will yield all the results you should need. Come back if you have more specific questions.

If you’re talking about the collision response, then you need to think about normal response, the sphere’s stretching (and hence bouncing) ability and elasticity of collision.

The normal response is the force the plane exerts on the sphere to stop it falling through and it is called normal response because it acts in the direction of the plane’s normal, ie perpendicular to the plane.

Bouncing occurs when the top of the sphere continues to fall whilst the bottom remains at rest, compressing the ball and giving it elastic potential which then converts to kinetic and pushes the ball up. You can cheat this by simply scaling up the normal reaction based on a coefficient of stretchiness (no idea what if any this’s name is. Partly Young’s modulus but something else as well must be).

Elasticity of a collision determines how much of the energy remains as kinetic and how much dissipates. This will combine with the above so that balls don’t bounce infinitely.

To implement sliding properly, you will need to do friction as well.

In terms of answering implementational questions, you will need to post more succinct code. I can deal with the Spanish but English comments would be appreciated.

Its portugues not spanish but this is not important and i post here because i have no idea how to implement this
All I need Is detect the collision and make the spheres rolling dow, so if someone already implement this please show.me the code or give me an example.
Sorry for the long post.