Java tutorial
/* * Copyright 2015 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.destinationsol.game; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.physics.box2d.*; import com.badlogic.gdx.utils.JsonReader; import com.badlogic.gdx.utils.JsonValue; import org.destinationsol.Const; import org.destinationsol.common.SolColor; import org.destinationsol.common.SolMath; import org.destinationsol.files.FileManager; import org.destinationsol.files.HullConfigManager; import org.destinationsol.game.dra.Dra; import org.destinationsol.game.dra.DraLevel; import org.destinationsol.game.dra.RectSprite; import org.destinationsol.game.ship.hulls.HullConfig; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Loads the collision fixtures defined with the Physics Body Editor * application. You only need to give it a body and the corresponding fixture * name, and it will attach these fixtures to your body. * * @author Aurelien Ribon | http://www.aurelienribon.com */ public class PathLoader { // Model private final Model model; // Reusable stuff private final List<Vector2> vectorPool = new ArrayList<Vector2>(); private final PolygonShape tmpPolyShape = new PolygonShape(); private final CircleShape tmpCircleShape = new CircleShape(); private final Vector2 tmpV = new Vector2(); // ------------------------------------------------------------------------- // Ctors // ------------------------------------------------------------------------- /** * * @param fileName * @deprecated this constructor uses hardcoded file paths; use the constructor that just accepts the jsonValue (node) to load from. */ public PathLoader(String fileName) { FileHandle file = FileManager.getInstance().getAssetsDirectory().child("paths").child(fileName + ".json"); if (file.exists()) { model = readJson(file.readString()); } else { model = new Model(); } } public PathLoader() { model = new Model(); } // ------------------------------------------------------------------------- // Public API // ------------------------------------------------------------------------- /** * Creates and applies the fixtures defined in the editor. The name * parameter is used to retrieve the right fixture from the loaded file. * <br/><br/> * * The body reference point (the red cross in the tool) is by default * located at the bottom left corner of the image. This reference point * will be put right over the BodyDef position point. Therefore, you should * place this reference point carefully to let you place your body in your * world easily with its BodyDef.position point. Note that to draw an image * at the position of your body, you will need to know this reference point * (see {@link #getOrigin(String, float)}. * <br/><br/> * * Also, saved shapes are normalized. As shown in the tool, the width of * the image is considered to be always 1 meter. Thus, you need to provide * a scale factor so the polygons get resized according to your needs (not * every body is 1 meter large in your game, I guess). * * @param body The Box2d body you want to attach the fixture to. * @param name The name of the fixture you want to load. * @param fd The fixture parameters to apply to the created body fixture. * @param scale The desired scale of the body. The default width is 1. */ public boolean attachFixture(Body body, String name, FixtureDef fd, float scale) { RigidBodyModel rbModel = model.rigidBodies.get(name); if (rbModel == null) { return false; } Vector2 origin = tmpV.set(rbModel.origin).scl(scale); int polyCount = rbModel.polys.size(); for (int i = 0; i < polyCount; i++) { PolygonModel poly = rbModel.polys.get(i); Vector2[] points = poly.tmpArray; int pointCount = points.length; for (int ii = 0; ii < pointCount; ii++) { Vector2 origPoint = poly.vertices.get(pointCount - ii - 1); points[ii] = newVec(origPoint).scl(scale); points[ii].sub(origin); } tmpPolyShape.set(points); fd.shape = tmpPolyShape; body.createFixture(fd); for (Vector2 point : points) free(point); } int circleCount = rbModel.circles.size(); for (int i = 0; i < circleCount; i++) { CircleModel circle = rbModel.circles.get(i); Vector2 center = newVec(circle.center).scl(scale).sub(origin); float radius = circle.radius * scale; tmpCircleShape.setPosition(center); tmpCircleShape.setRadius(radius); fd.shape = tmpCircleShape; body.createFixture(fd); free(center); } return true; } /** * Gets the image path attached to the given name. */ public String getImagePath(String name) { RigidBodyModel rbModel = model.rigidBodies.get(name); if (rbModel == null) throw new AssertionError("Name '" + name + "' was not found."); return rbModel.imagePath; } /** * Gets the origin point attached to the given name. Since the point is * normalized in [0,1] coordinates, it needs to be scaled to your body * size. Warning: this method returns the same Vector2 object each time, so * copy it if you need it for later use. */ public Vector2 getOrigin(String name, float scale) { RigidBodyModel rbModel = model.rigidBodies.get(name); if (rbModel == null) { tmpV.set(.5f, .5f); } else { tmpV.set(rbModel.origin); } tmpV.scl(scale); return tmpV; } /** * <b>For advanced users only.</b> Lets you access the internal model of * this loader and modify it. Be aware that any modification is permanent * and that you should really know what you are doing. */ public Model getInternalModel() { return model; } public void readJson(JsonValue rigidBodyNode, HullConfig hullConfig) { RigidBodyModel rigidBodyModel = readRigidBody(rigidBodyNode, hullConfig); model.rigidBodies.put(rigidBodyModel.name, rigidBodyModel); } // ------------------------------------------------------------------------- // Json Models // ------------------------------------------------------------------------- public static class Model { public final Map<String, RigidBodyModel> rigidBodies = new HashMap<String, RigidBodyModel>(); } public static class RigidBodyModel { public String name; public String imagePath; public final Vector2 origin = new Vector2(); public final List<PolygonModel> polys = new ArrayList<PolygonModel>(); public final List<PolygonModel> shapes = new ArrayList<PolygonModel>(); public final List<CircleModel> circles = new ArrayList<CircleModel>(); } public static class PolygonModel { public final List<Vector2> vertices = new ArrayList<Vector2>(); private Vector2[] tmpArray; // used to avoid allocation in attachFixture() } public static class CircleModel { public final Vector2 center = new Vector2(); public float radius; } // ------------------------------------------------------------------------- // Json reading process // ------------------------------------------------------------------------- private Model readJson(String str) { Model m = new Model(); JsonValue rootElem = new JsonReader().parse(str); JsonValue bodiesElems = rootElem.get("rigidBodies"); for (int i = 0; i < bodiesElems.size; i++) { JsonValue bodyElem = bodiesElems.get(i); RigidBodyModel rbModel = readRigidBody(bodyElem); m.rigidBodies.put(rbModel.name, rbModel); } return m; } private RigidBodyModel readRigidBody(JsonValue bodyElem, HullConfig hullConfig) { RigidBodyModel rbModel = new RigidBodyModel(); rbModel.name = hullConfig.getInternalName(); rbModel.imagePath = FileManager.getInstance().getHullsDirectory().child(hullConfig.getInternalName()) .child(HullConfigManager.TEXTURE_FILE_NAME).path(); JsonValue originElem = bodyElem.get("origin"); rbModel.origin.x = originElem.getFloat("x"); rbModel.origin.y = 1 - originElem.getFloat("y"); // polygons JsonValue polygonsElem = bodyElem.get("polygons"); for (int i = 0; i < polygonsElem.size; i++) { PolygonModel polygon = new PolygonModel(); rbModel.polys.add(polygon); JsonValue verticesElem = polygonsElem.get(i); for (int ii = 0; ii < verticesElem.size; ii++) { JsonValue vertexElem = verticesElem.get(ii); float x = vertexElem.getFloat("x"); float y = 1 - vertexElem.getFloat("y"); polygon.vertices.add(new Vector2(x, y)); } polygon.tmpArray = new Vector2[polygon.vertices.size()]; } // shapes JsonValue shapeElems = bodyElem.get("shapes"); for (int i = 0; i < shapeElems.size; i++) { JsonValue shapeElem = shapeElems.get(i); String type = shapeElem.getString("type"); if (!"POLYGON".equals(type)) continue; PolygonModel shape = new PolygonModel(); rbModel.shapes.add(shape); JsonValue verticesElem = shapeElem.get("vertices"); for (int ii = 0; ii < verticesElem.size; ii++) { JsonValue vertexElem = verticesElem.get(ii); float x = vertexElem.getFloat("x"); float y = 1 - vertexElem.getFloat("y"); shape.vertices.add(new Vector2(x, y)); } shape.tmpArray = new Vector2[shape.vertices.size()]; } // circles JsonValue circlesElem = bodyElem.get("circles"); for (int i = 0; i < circlesElem.size; i++) { CircleModel circle = new CircleModel(); rbModel.circles.add(circle); JsonValue circleElem = circlesElem.get(i); circle.center.x = circleElem.getFloat("cx"); circle.center.y = 1 - circleElem.getFloat("cy"); circle.radius = circleElem.getFloat("r"); } return rbModel; } private RigidBodyModel readRigidBody(JsonValue bodyElem) { RigidBodyModel rbModel = new RigidBodyModel(); rbModel.name = bodyElem.getString("name"); rbModel.imagePath = bodyElem.getString("imagePath"); JsonValue originElem = bodyElem.get("origin"); rbModel.origin.x = originElem.getFloat("x"); rbModel.origin.y = 1 - originElem.getFloat("y"); // polygons JsonValue polygonsElem = bodyElem.get("polygons"); for (int i = 0; i < polygonsElem.size; i++) { PolygonModel polygon = new PolygonModel(); rbModel.polys.add(polygon); JsonValue verticesElem = polygonsElem.get(i); for (int ii = 0; ii < verticesElem.size; ii++) { JsonValue vertexElem = verticesElem.get(ii); float x = vertexElem.getFloat("x"); float y = 1 - vertexElem.getFloat("y"); polygon.vertices.add(new Vector2(x, y)); } polygon.tmpArray = new Vector2[polygon.vertices.size()]; } // shapes JsonValue shapeElems = bodyElem.get("shapes"); for (int i = 0; i < shapeElems.size; i++) { JsonValue shapeElem = shapeElems.get(i); String type = shapeElem.getString("type"); if (!"POLYGON".equals(type)) continue; PolygonModel shape = new PolygonModel(); rbModel.shapes.add(shape); JsonValue verticesElem = shapeElem.get("vertices"); for (int ii = 0; ii < verticesElem.size; ii++) { JsonValue vertexElem = verticesElem.get(ii); float x = vertexElem.getFloat("x"); float y = 1 - vertexElem.getFloat("y"); shape.vertices.add(new Vector2(x, y)); } shape.tmpArray = new Vector2[shape.vertices.size()]; } // circles JsonValue circlesElem = bodyElem.get("circles"); for (int i = 0; i < circlesElem.size; i++) { CircleModel circle = new CircleModel(); rbModel.circles.add(circle); JsonValue circleElem = circlesElem.get(i); circle.center.x = circleElem.getFloat("cx"); circle.center.y = 1 - circleElem.getFloat("cy"); circle.radius = circleElem.getFloat("r"); } return rbModel; } // ------------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------------- private Vector2 newVec() { return newVec(null); } private Vector2 newVec(Vector2 v) { Vector2 res = vectorPool.isEmpty() ? new Vector2() : vectorPool.remove(0); if (v != null) res.set(v); return res; } private void free(Vector2 v) { vectorPool.add(v); } /** * This needs refactoring... * @param dras a texture will be added here * @param tex pass if you already have a texture.. So hacky! */ public Body getBodyAndSprite(SolGame game, HullConfig hullConfig, float scale, BodyDef.BodyType type, Vector2 pos, float angle, List<Dra> dras, float density, DraLevel level, TextureAtlas.AtlasRegion tex) { final String name = hullConfig.getInternalName(); final String pathName = FileManager.getInstance().getHullsDirectory().child(hullConfig.getInternalName()) .child(HullConfigManager.TEXTURE_FILE_NAME).path(); BodyDef bd = new BodyDef(); bd.type = type; bd.angle = angle * SolMath.degRad; bd.angularDamping = 0; bd.position.set(pos); bd.linearDamping = 0; Body body = game.getObjMan().getWorld().createBody(bd); FixtureDef fd = new FixtureDef(); fd.density = density; fd.friction = Const.FRICTION; Vector2 orig; boolean found = attachFixture(body, name, fd, scale); if (!found) { DebugOptions.MISSING_PHYSICS_ACTION.handle("Could not find physics data for " + name); fd.shape = new CircleShape(); fd.shape.setRadius(scale / 2); body.createFixture(fd); fd.shape.dispose(); } orig = hullConfig.getShipBuilderOrigin(); if (tex == null) { String imgName = pathName; tex = hullConfig.getTexture(); } RectSprite s = new RectSprite(tex, scale, orig.x - .5f, orig.y - .5f, new Vector2(), level, 0, 0, SolColor.W, false); dras.add(s); return body; } /** * This needs refactoring... * @param texDirName used only to load a texture * @param texName used both to load a texture and to load a path from the path file. should be just a file name without a path or extension * @param dras a texture will be added here * @param tex pass if you already have a texture.. So hacky! */ public Body getBodyAndSprite(SolGame game, String texDirName, String texName, float scale, BodyDef.BodyType type, Vector2 pos, float angle, List<Dra> dras, float density, DraLevel level, TextureAtlas.AtlasRegion tex) { BodyDef bd = new BodyDef(); bd.type = type; bd.angle = angle * SolMath.degRad; bd.angularDamping = 0; bd.position.set(pos); bd.linearDamping = 0; Body body = game.getObjMan().getWorld().createBody(bd); FixtureDef fd = new FixtureDef(); fd.density = density; fd.friction = Const.FRICTION; String pathName = texName + ".png"; Vector2 orig; boolean found = attachFixture(body, pathName, fd, scale); if (!found) { DebugOptions.MISSING_PHYSICS_ACTION .handle("Could not find physics data for " + texDirName + "/" + texName); fd.shape = new CircleShape(); fd.shape.setRadius(scale / 2); body.createFixture(fd); fd.shape.dispose(); } orig = getOrigin(pathName, 1); if (tex == null) { String imgName = texDirName + "/" + texName; tex = game.getTexMan().getTex(imgName, null); } RectSprite s = new RectSprite(tex, scale, orig.x - .5f, orig.y - .5f, new Vector2(), level, 0, 0, SolColor.W, false); dras.add(s); return body; } }