package com.gushikustudios.rube; import java.util.HashMap; import java.util.Map; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture.TextureWrap; import com.badlogic.gdx.graphics.g2d.PolygonRegion; import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.EarClippingTriangulator; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.physics.box2d.Body; import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer; import com.badlogic.gdx.physics.box2d.Contact; import com.badlogic.gdx.physics.box2d.ContactImpulse; import com.badlogic.gdx.physics.box2d.ContactListener; import com.badlogic.gdx.physics.box2d.Fixture; import com.badlogic.gdx.physics.box2d.Manifold; import com.badlogic.gdx.physics.box2d.PolygonShape; import com.badlogic.gdx.physics.box2d.Shape; import com.badlogic.gdx.physics.box2d.World; import com.badlogic.gdx.utils.Array; import com.gushikustudios.box2d.controllers.B2BuoyancyController; import com.gushikustudios.box2d.controllers.B2Controller; import com.gushikustudios.box2d.controllers.B2GravityController; import com.gushikustudios.rube.loader.RubeSceneLoader; import com.gushikustudios.rube.loader.serializers.utils.RubeImage; /** * Use the left-click to pan. Scroll-wheel zooms. * * @author cvayer, tescott * */ public class RubeLoaderTest implements ApplicationListener, InputProcessor, ContactListener { private OrthographicCamera camera; private RubeSceneLoader loader; private RubeScene scene; private Box2DDebugRenderer debugRender; private Array<SimpleSpatial> spatials; // used for rendering rube images private Array<PolySpatial> polySpatials; private Map<String, Texture> textureMap; private Map<Texture, TextureRegion> textureRegionMap; private static final Vector2 mTmp = new Vector2(); // shared by all objects private SpriteBatch batch; private PolygonSpriteBatch polygonBatch; // used for pan and scanning with the mouse. private Vector3 mCamPos; private Vector3 mCurrentPos; private World mWorld; private Array<B2Controller> mB2Controllers; private float mAccumulator; // time accumulator to fix the physics step. private int mVelocityIter = 8; private int mPositionIter = 3; private float mSecondsPerStep = 1 / 60f; private static final float MAX_DELTA_TIME = 0.25f; @Override public void create() { float w = Gdx.graphics.getWidth(); float h = Gdx.graphics.getHeight(); Gdx.input.setInputProcessor(this); mB2Controllers = new Array<B2Controller>(); mCamPos = new Vector3(); mCurrentPos = new Vector3(); camera = new OrthographicCamera(100, 100 * h / w); camera.position.set(50, 50, 0); camera.zoom = 1.8f; camera.update(); loader = new RubeSceneLoader(); scene = loader.loadScene(Gdx.files.internal("data/palmcontrollers.json")); debugRender = new Box2DDebugRenderer(); batch = new SpriteBatch(); polygonBatch = new PolygonSpriteBatch(); textureMap = new HashMap<String, Texture>(); textureRegionMap = new HashMap<Texture, TextureRegion>(); createSpatialsFromRubeImages(scene); createPolySpatialsFromRubeFixtures(scene); mWorld = scene.getWorld(); // configure simulation settings mVelocityIter = scene.velocityIterations; mPositionIter = scene.positionIterations; if (scene.stepsPerSecond != 0) { mSecondsPerStep = 1f / scene.stepsPerSecond; } mWorld.setContactListener(this); // // example of custom property handling // Array<Body> bodies = scene.getBodies(); if ((bodies != null) && (bodies.size > 0)) { for (int i = 0; i < bodies.size; i++) { Body body = bodies.get(i); String gameInfo = (String) scene.getCustom(body, "GameInfo", null); if (gameInfo != null) { System.out.println("GameInfo custom property: " + gameInfo); } } } // Instantiate any controllers that are in the scene Array<Fixture> fixtures = scene.getFixtures(); if ((fixtures != null) && (fixtures.size > 0)) { for (int i = 0; i < fixtures.size; i++) { Fixture fixture = fixtures.get(i); int controllerType = (Integer) scene.getCustom(fixture, "ControllerType", 0); switch (controllerType) { case B2Controller.BUOYANCY_CONTROLLER: // only allow polygon buoyancy controllers for now.. if (fixture.getShape().getType() == Shape.Type.Polygon) { float bodyHeight = fixture.getBody().getPosition().y; // B2BuoyancyController b2c = new B2BuoyancyController(); // need to calculate the fluid surface height for the buoyancy controller PolygonShape shape = (PolygonShape) fixture.getShape(); shape.getVertex(0, mTmp); float maxHeight = mTmp.y + bodyHeight; // initialize the height, transforming to 'world' // coordinates // find the maxHeight for (int j = 1; j < shape.getVertexCount(); j++) { shape.getVertex(j, mTmp); maxHeight = Math.max(maxHeight, mTmp.y + bodyHeight); // transform to world coordinates } B2BuoyancyController b2c = new B2BuoyancyController( B2BuoyancyController.DEFAULT_SURFACE_NORMAL, // assume up (Vector2)scene.getCustom(fixture, "ControllerVelocity", B2BuoyancyController.DEFAULT_FLUID_VELOCITY), mWorld.getGravity(), maxHeight, fixture.getDensity(), (Float)scene.getCustom(fixture, "LinearDrag", B2BuoyancyController.DEFAULT_LINEAR_DRAG), (Float)scene.getCustom(fixture, "AngularDrag", B2BuoyancyController.DEFAULT_ANGULAR_DRAG)); fixture.setUserData(b2c); // reference back to the controller from the fixture (see // beginContact/endContact) mB2Controllers.add(b2c); // add it to the list so it can be stepped later } break; case B2Controller.GRAVITY_CONTROLLER: { B2GravityController b2c = new B2GravityController(); b2c = new B2GravityController((Vector2)scene.getCustom(fixture, "ControllerVelocity", B2GravityController.DEFAULT_GRAVITY)); fixture.setUserData(b2c); mB2Controllers.add(b2c); } break; } } } scene.printStats(); scene.clear(); // no longer need any scene references } @Override public void dispose() { } @Override public void render() { Gdx.gl.glClearColor(0, 0, 0, 1); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); float delta = Gdx.graphics.getDeltaTime(); if (delta > MAX_DELTA_TIME) { delta = MAX_DELTA_TIME; } mAccumulator += delta; while (mAccumulator >= mSecondsPerStep) { for (int i = 0; i < mB2Controllers.size; i++) { mB2Controllers.get(i).step(mSecondsPerStep); } mWorld.step(mSecondsPerStep, mVelocityIter, mPositionIter); mAccumulator -= mSecondsPerStep; } if ((spatials != null) && (spatials.size > 0)) { batch.setProjectionMatrix(camera.combined); batch.begin(); for (int i = 0; i < spatials.size; i++) { spatials.get(i).render(batch, 0); } batch.end(); } if ((polySpatials != null) && (polySpatials.size > 0)) { polygonBatch.setProjectionMatrix(camera.combined); polygonBatch.begin(); for (int i = 0; i < polySpatials.size; i++) { polySpatials.get(i).render(polygonBatch, 0); } polygonBatch.end(); } debugRender.render(mWorld, camera.combined); } /** * Creates an array of SimpleSpatial objects from RubeImages. * * @param scene2 */ private void createSpatialsFromRubeImages(RubeScene scene) { Array<RubeImage> images = scene.getImages(); if ((images != null) && (images.size > 0)) { spatials = new Array<SimpleSpatial>(); for (int i = 0; i < images.size; i++) { RubeImage image = images.get(i); mTmp.set(image.width, image.height); String textureFileName = "data/" + image.file; Texture texture = textureMap.get(textureFileName); if (texture == null) { texture = new Texture(textureFileName); textureMap.put(textureFileName, texture); } SimpleSpatial spatial = new SimpleSpatial(texture, image.flip, image.body, image.color, mTmp, image.center, image.angleInRads * MathUtils.radiansToDegrees); spatials.add(spatial); } } } /** * Creates an array of PolySpatials based on fixture information from the scene. Note that * fixtures create aligned textures. * * @param scene */ private void createPolySpatialsFromRubeFixtures(RubeScene scene) { Array<Body> bodies = scene.getBodies(); EarClippingTriangulator ect = new EarClippingTriangulator(); if ((bodies != null) && (bodies.size > 0)) { polySpatials = new Array<PolySpatial>(); Vector2 bodyPos = new Vector2(); // for each body in the scene... for (int i = 0; i < bodies.size; i++) { Body body = bodies.get(i); bodyPos.set(body.getPosition()); Array<Fixture> fixtures = body.getFixtureList(); if ((fixtures != null) && (fixtures.size > 0)) { // for each fixture on the body... for (int j = 0; j < fixtures.size; j++) { Fixture fixture = fixtures.get(j); String textureName = (String) scene.getCustom(fixture, "TextureMask", null); if (textureName != null) { String textureFileName = "data/" + textureName; Texture texture = textureMap.get(textureFileName); TextureRegion textureRegion = null; if (texture == null) { texture = new Texture(textureFileName); texture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat); textureMap.put(textureFileName, texture); textureRegion = new TextureRegion(texture); textureRegionMap.put(texture, textureRegion); } else { textureRegion = textureRegionMap.get(texture); } // only handle polygons at this point -- no chain, edge, or circle fixtures. if (fixture.getType() == Shape.Type.Polygon) { PolygonShape shape = (PolygonShape) fixture.getShape(); int vertexCount = shape.getVertexCount(); float[] vertices = new float[vertexCount * 2]; // static bodies are texture aligned and do not get drawn based off of the related body. if (body.getType() == BodyType.StaticBody) { for (int k = 0; k < vertexCount; k++) { shape.getVertex(k, mTmp); mTmp.rotate(body.getAngle() * MathUtils.radiansToDegrees); mTmp.add(bodyPos); // convert local coordinates to world coordinates to that textures are // aligned vertices[k * 2] = mTmp.x * PolySpatial.PIXELS_PER_METER; vertices[k * 2 + 1] = mTmp.y * PolySpatial.PIXELS_PER_METER; } short [] triangleIndices = ect.computeTriangles(vertices).toArray(); PolygonRegion region = new PolygonRegion(textureRegion, vertices, triangleIndices); PolySpatial spatial = new PolySpatial(region, Color.WHITE); polySpatials.add(spatial); } else { // all other fixtures are aligned based on their associated body. for (int k = 0; k < vertexCount; k++) { shape.getVertex(k, mTmp); vertices[k * 2] = mTmp.x * PolySpatial.PIXELS_PER_METER; vertices[k * 2 + 1] = mTmp.y * PolySpatial.PIXELS_PER_METER; } short [] triangleIndices = ect.computeTriangles(vertices).toArray(); PolygonRegion region = new PolygonRegion(textureRegion, vertices, triangleIndices); PolySpatial spatial = new PolySpatial(region, body, Color.WHITE); polySpatials.add(spatial); } } } } } } } } @Override public void resize(int width, int height) { } @Override public void pause() { } @Override public void resume() { } @Override public boolean keyDown(int keycode) { // TODO Auto-generated method stub return false; } @Override public boolean keyUp(int keycode) { // TODO Auto-generated method stub return false; } @Override public boolean keyTyped(char character) { // TODO Auto-generated method stub return false; } @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { mCamPos.set(screenX, screenY, 0); camera.unproject(mCamPos); return true; } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { // TODO Auto-generated method stub return false; } @Override public boolean touchDragged(int screenX, int screenY, int pointer) { mCurrentPos.set(screenX, screenY, 0); camera.unproject(mCurrentPos); camera.position.sub(mCurrentPos.sub(mCamPos)); camera.update(); return true; } @Override public boolean mouseMoved(int screenX, int screenY) { // TODO Auto-generated method stub return false; } @Override public boolean scrolled(int amount) { camera.zoom += (amount * 0.1f); if (camera.zoom < 0.1f) { camera.zoom = 0.1f; } camera.update(); return true; } @Override public void beginContact(Contact contact) { Fixture fixA = contact.getFixtureA(); Fixture fixB = contact.getFixtureB(); if ((fixA.isSensor()) && (fixA.getUserData() != null)) { B2Controller b2c = (B2Controller) fixA.getUserData(); b2c.addBody(fixB.getBody()); } else if ((fixB.isSensor()) && (fixB.getUserData() != null)) { B2Controller b2c = (B2Controller) fixB.getUserData(); b2c.addBody(fixA.getBody()); } } @Override public void endContact(Contact contact) { Fixture fixA = contact.getFixtureA(); Fixture fixB = contact.getFixtureB(); if ((fixA.isSensor()) && (fixA.getUserData() != null)) { B2Controller b2c = (B2Controller) fixA.getUserData(); b2c.removeBody(fixB.getBody()); } else if ((fixB.isSensor()) && (fixB.getUserData() != null)) { B2Controller b2c = (B2Controller) fixB.getUserData(); b2c.removeBody(fixA.getBody()); } } @Override public void preSolve(Contact contact, Manifold oldManifold) { // TODO Auto-generated method stub } @Override public void postSolve(Contact contact, ContactImpulse impulse) { // TODO Auto-generated method stub } }