import java.util.Random; import com.jme.app.SimpleGame; import com.jme.bounding.BoundingBox; import com.jme.light.LightNode; import com.jme.light.PointLight; import com.jme.math.FastMath; import com.jme.math.Quaternion; import com.jme.math.Vector3f; import com.jme.renderer.Camera; import com.jme.renderer.ColorRGBA; import com.jme.renderer.Renderer; import com.jme.scene.Node; import com.jme.scene.Spatial; import com.jme.scene.shape.Box; import com.jme.scene.shape.RoundedBox; import com.jme.scene.state.LightState; import com.jme.scene.state.MaterialState; import com.jme.scene.state.ZBufferState; import com.jme.system.DisplaySystem; /** * RubiksCube implementation with the jMonkey Engine * * This implementation is non interactive and made just for fun * * @author Thomas Åhlén 2007-11-28 */ public class RubiksCube extends SimpleGame { private final static int LEFT = 0; private final static int RIGHT = 1; private final static int DOWN = 2; private final static int UP = 3; private final static int FRONT = 4; private final static int BACK = 5; private final static int GREEN = 0; private final static int BLUE = 1; private final static int YELLOW = 2; private final static int WHITE = 3; private final static int RED = 4; private final static int ORANGE = 5; RubikRotator m_rotator; private Node m_cubeNode; private Node[][][] m_cubeNodes; private Node[] m_rotationNodes; private Node[][][] m_rotationSubNodes; private float m_scale = 100f; private float m_boxSize = m_scale*1f; private float m_gap = m_scale*0.05f; private float m_plateThickness = m_scale*0.05f; private float m_plateSize = m_boxSize*0.9f; private float m_plateEdge = m_scale*0.02f; private float m_rotationSpeed = FastMath.PI/3.0f; private MaterialState[] m_mStates; private int m_counter = 0; private Vector3f m_camPosition; private Renderer m_renderer; private Random m_rand; public static void main(String[] args) { RubiksCube app = new RubiksCube(); app.setDialogBehaviour(NEVER_SHOW_PROPS_DIALOG); app.start(); } protected void simpleInitGame() { Camera cam; display.setTitle("RubikCube"); m_renderer = DisplaySystem.getDisplaySystem().getRenderer(); m_rand = new Random(); ZBufferState zbuf = m_renderer.createZBufferState(); zbuf.setWritable( true ); zbuf.setEnabled( true ); zbuf.setFunction( ZBufferState.CF_LEQUAL ); m_cubeNode = new Node("TheCube"); m_cubeNode.setRenderState( zbuf ); rootNode.attachChild( m_cubeNode ); createMaterialStates(); createCubeNodes(); createRotationNodes(); m_rotator = new RubikRotator(); Vector3f left = new Vector3f( 1, 0, 0 ); Vector3f up = new Vector3f( 0, 1, 0 ); Vector3f dir = new Vector3f( 0, 0, 1 ); m_camPosition = Vector3f.ZERO.clone(); m_camPosition.addLocal( m_boxSize*4, m_boxSize*4, m_boxSize*4 ); float frustumFar = 100*m_scale; cam = m_renderer.getCamera(); cam.setFrustumPerspective( 45.0f, 1, 1, frustumFar ); cam.setFrame( m_camPosition, left, up, dir ); cam.lookAt( Vector3f.ZERO, Vector3f.UNIT_Y ); LightState ls = m_renderer.createLightState(); LightNode lightNode = new LightNode( "CamLight", ls ); PointLight light = new PointLight(); light.setDiffuse( new ColorRGBA( 0.8f, 0.8f, 0.8f, 1f ) ); light.setAmbient( new ColorRGBA( 0.3f, 0.3f, 0.3f, 1f ) ); light.setLocation(m_camPosition); light.setEnabled( true ); lightNode.setLight( light ); lightNode.setTarget( m_cubeNode ); } protected void simpleUpdate() { if( m_counter%100 == 0 && m_rotator.isDone() ) { int type, turns; type = m_rand.nextInt( 9 ); do { turns = -2 + m_rand.nextInt( 5 ); } while( turns == 0 ); m_rotator.init( type, turns ); } m_rotator.rotate( tpf ); m_counter++; } private void createMaterialStates() { MaterialState ms; m_mStates = new MaterialState[6]; ms = m_renderer.createMaterialState(); ms.setDiffuse( new ColorRGBA( 1, 0, 0, 0.5f ) ); ms.setEmissive( new ColorRGBA( 0.5f, 0, 0, 1 ) ); m_mStates[RED] = ms; ms = m_renderer.createMaterialState(); ms.setDiffuse( new ColorRGBA( 0, 0, 1, 0.5f ) ); ms.setEmissive( new ColorRGBA( 0, 0, 0.5f, 1 ) ); m_mStates[BLUE] = ms; ms = m_renderer.createMaterialState(); ms.setDiffuse( new ColorRGBA( 0, 1, 0, 0.5f ) ); ms.setEmissive( new ColorRGBA( 0, 0.5f, 0, 1 ) ); m_mStates[GREEN] = ms; ms = m_renderer.createMaterialState(); ms.setDiffuse( new ColorRGBA( 1, 1, 0, 0.5f ) ); ms.setEmissive( new ColorRGBA( 0.5f, 0.5f, 0, 1 ) ); m_mStates[YELLOW] = ms; ms = m_renderer.createMaterialState(); ms.setDiffuse( new ColorRGBA( 1, 1, 1, 0.5f ) ); ms.setEmissive( new ColorRGBA( 0.5f, 0.5f, 0.5f, 1 ) ); m_mStates[WHITE] = ms; ms = m_renderer.createMaterialState(); ms.setDiffuse( new ColorRGBA( 251f/255f, 130f/255f, 0, 0.5f ) ); ms.setEmissive( new ColorRGBA( 251f/255f*0.5f, 130f/255f*0.5f, 0, 1 ) ); m_mStates[ORANGE] = ms; } /** * Create 27 cube node where our cubes will reside when not rotating */ private void createCubeNodes() { Node node; m_cubeNodes = new Node[3][3][3]; for( int i = 0; i < 3; i++ ) { for( int j = 0; j < 3; j++ ) { for( int k = 0; k < 3; k++ ) { node = new Node( "cn" + i + j + k ); node.getLocalTranslation().addLocal( (i-1)*(m_boxSize+m_gap), (j-1)*(m_boxSize+m_gap), (k-1)*(m_boxSize+m_gap) ); m_cubeNodes[i][j][k] = node; node.attachChild( createBox(i,j,k) ); m_cubeNode.attachChild( node ); } } } } private Spatial createBox( int i, int j, int k ) { Node boxNode; Spatial box; MaterialState ms; boxNode = new Node("bn"); boxNode.setModelBound(new BoundingBox()); box = new Box( "b" + i + j + k, new Vector3f(0,0,0), m_boxSize/2,m_boxSize/2,m_boxSize/2 ); ms = m_renderer.createMaterialState(); ms.setDiffuse( new ColorRGBA( 0.1f, 0.1f, 0.1f, 0.5f ) ); box.setRenderState(ms); boxNode.attachChild( box ); if( i == 0 ) { boxNode.attachChild( createBoxPlate( LEFT ) ); } else if( i == 2) { boxNode.attachChild( createBoxPlate( RIGHT ) ); } if( j == 0 ) { boxNode.attachChild( createBoxPlate( DOWN ) ); } else if( j == 2) { boxNode.attachChild( createBoxPlate( UP ) ); } if( k == 0 ) { boxNode.attachChild( createBoxPlate( FRONT ) ); } else if( k == 2) { boxNode.attachChild( createBoxPlate( BACK ) ); } return boxNode; } private RoundedBox createBoxPlate( int side ) { RoundedBox rb; MaterialState ms; float xDim,yDim,zDim; float dx,dy,dz; float translation; dx = dy = dz = 0; xDim = yDim = zDim = m_plateSize/2; ms = m_mStates[GREEN]; translation = m_boxSize/2 + m_plateThickness/2; switch( side ) { case LEFT: ms = m_mStates[GREEN]; xDim = m_plateThickness; dx = -translation; break; case RIGHT: ms = m_mStates[BLUE]; xDim = m_plateThickness; dx = translation; break; case DOWN: ms = m_mStates[YELLOW]; yDim = m_plateThickness; dy = -translation; break; case UP: ms = m_mStates[WHITE]; yDim = m_plateThickness; dy = translation; break; case FRONT: ms = m_mStates[RED]; zDim = m_plateThickness; dz = -translation; break; case BACK: ms = m_mStates[ORANGE]; zDim = m_plateThickness; dz = translation; break; } rb = new RoundedBox( "plate", new Vector3f( xDim, yDim, zDim ), new Vector3f( m_plateEdge, m_plateEdge, m_plateEdge ), new Vector3f( m_plateEdge, m_plateEdge, m_plateEdge ) ); rb.getLocalTranslation().addLocal( dx, dy, dz ); rb.setRenderState(ms); return rb; } /** * Create 9 rotation nodes since that is what we need to have a fully * functional RubiksCube * * We also create 9*3*3 sub-nodes where we will place 9 of our cubes when we * need to perform a rotation. */ private void createRotationNodes() { Node node; Node subNode; m_rotationNodes = new Node[9]; m_rotationSubNodes = new Node[9][3][3]; for( int i = 0; i < 9; i++ ) { node = new Node( "rn" + i ); m_rotationNodes[i] = node; if( i < 3) { node.getLocalTranslation().addLocal( (i-1)*(m_boxSize+m_gap), 0, 0 ); } else if( i < 6 ) { node.getLocalTranslation().addLocal( 0, (i%3-1)*(m_boxSize+m_gap), 0 ); } else { node.getLocalTranslation().addLocal( 0, 0, (i%3-1)*(m_boxSize+m_gap) ); } for( int j = 0; j < 3; j++ ) { for( int k = 0; k < 3; k++ ) { subNode = new Node( "rn" + i + "s" + j ); m_rotationSubNodes[i][j][k] = subNode; node.attachChild( subNode ); m_cubeNode.attachChild( node ); if( i < 3) { subNode.getLocalTranslation().addLocal( 0, (j-1)*(m_boxSize+m_gap), (k-1)*(m_boxSize+m_gap) ); } else if( i < 6 ) { subNode.getLocalTranslation().addLocal( (j-1)*(m_boxSize+m_gap), 0, (k-1)*(m_boxSize+m_gap) ); } else { subNode.getLocalTranslation().addLocal( (j-1)*(m_boxSize+m_gap), (k-1)*(m_boxSize+m_gap), 0 ); } } } } } /** * Handles all rotations */ class RubikRotator { private Node rotationNode; Quaternion fromQuat = new Quaternion(); Quaternion toQuat = new Quaternion(); private Vector3f posX = Vector3f.UNIT_X; private Vector3f negX = Vector3f.UNIT_X.clone().multLocal( -1 ); private Vector3f posY = Vector3f.UNIT_Y; private Vector3f negY = Vector3f.UNIT_Y.clone().multLocal( -1 ); private Vector3f posZ = Vector3f.UNIT_Z; private Vector3f negZ = Vector3f.UNIT_Z.clone().multLocal( -1 ); private float rotationAngle; private float deltaAngle; private float rotationSpeed; private int type; private int turns; public RubikRotator() { deltaAngle = 0; } /** * @param type Decides which rotation we are going to do * @param turns A value between -2 and 2 that decide direction and * number of 90 degree turn we want to do */ public void init( int type, int turns ) { if( turns == 0 ) return; this.type = type; this.turns = turns; rotationSpeed = m_rotationSpeed; rotationAngle = FastMath.abs( turns )*FastMath.HALF_PI; deltaAngle = rotationAngle; if( type < 0 || type > 8 ) { type=8; } moveCubesToRotationNode(); initRotation(); } public void rotate( float dT ) { float dA; if( !isDone() ) { dA = rotationSpeed * dT; if( dA < deltaAngle ) { deltaAngle -= dA; rotationNode.getLocalRotation().slerp( fromQuat, toQuat, ( 1 - deltaAngle / rotationAngle ) ); } else { deltaAngle = 0; rotationNode.getLocalRotation().slerp( fromQuat, toQuat, 1 ); rotationNode.updateGeometricState( dT, true ); moveCubesFromRotationNode(); } } } /** * Just setup the two Quaternions we are going use for this rotation */ private void initRotation() { if( type < 3) { if( turns < 0 ) { fromQuat.fromAngleNormalAxis( 0, negX ); toQuat.fromAngleNormalAxis( rotationAngle, negX ); } else { fromQuat.fromAngleNormalAxis( 0, posX ); toQuat.fromAngleNormalAxis( rotationAngle, posX ); } } else if( type < 6 ) { if( turns < 0 ) { fromQuat.fromAngleNormalAxis( 0, negY ); toQuat.fromAngleNormalAxis( rotationAngle, negY ); } else { fromQuat.fromAngleNormalAxis( 0, posY ); toQuat.fromAngleNormalAxis( rotationAngle, posY ); } } else { if( turns < 0 ) { fromQuat.fromAngleNormalAxis( 0, negZ ); toQuat.fromAngleNormalAxis( rotationAngle, negZ ); } else { fromQuat.fromAngleNormalAxis( 0, posZ ); toQuat.fromAngleNormalAxis( rotationAngle, posZ ); } } } /** * We are about to rotate part of the cube move those cube to the * right rotation node */ private void moveCubesToRotationNode() { Node node; rotationNode = m_rotationNodes[type]; for( int j = 0; j < 3; j++ ) { for( int k = 0; k < 3; k++ ) { node = m_rotationSubNodes[type][j][k]; if( type < 3) { node.attachChild( m_cubeNodes[type][j][k].getChild(0) ); } else if( type < 6 ) { node.attachChild( m_cubeNodes[j][type-3][k].getChild(0) ); } else { node.attachChild( m_cubeNodes[j][k][type-6].getChild(0) ); } } } } /** * Rotation is done move the cubes from the rotation node to cube node */ private void moveCubesFromRotationNode() { Node node; Spatial box = null; for( int j = 0; j < 3; j++ ) { for( int k = 0; k < 3; k++ ) { if( type < 3) { node = m_cubeNodes[type][j][k]; if( turns == -1 ) { box = m_rotationSubNodes[type][2-k][j].getChild(0); } else if( turns == 1 ) { box = m_rotationSubNodes[type][k][2-j].getChild(0); } else if( turns == 2 || turns == -2 ) { box = m_rotationSubNodes[type][2-j][2-k].getChild(0); } } else if( type < 6 ) { node = m_cubeNodes[j][type-3][k]; if( turns == -1 ) { box = m_rotationSubNodes[type][k][2-j].getChild(0); } else if( turns == 1 ) { box = m_rotationSubNodes[type][2-k][j].getChild(0); } else if( turns == 2 || turns == -2 ) { box = m_rotationSubNodes[type][2-j][2-k].getChild(0); } } else { node = m_cubeNodes[j][k][type-6]; if( turns == -1 ) { box = m_rotationSubNodes[type][2-k][j].getChild(0); } else if( turns == 1 ) { box = m_rotationSubNodes[type][k][2-j].getChild(0); } else if( turns == 2 || turns == -2 ) { box = m_rotationSubNodes[type][2-j][2-k].getChild(0); } } // There should be a possiblity of a tiny error accumulating // here that might have to be fixed. box.getLocalRotation().set( box.getWorldRotation() ); node.attachChild( box ); } } } public boolean isDone() { if( deltaAngle == 0 ) { return true; } return false; } } }