com.strategames.engine.utils.BodyEditorLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.strategames.engine.utils.BodyEditorLoader.java

Source

/**
 * 
 * Copyright 2013 Martijn Brekhof
 *
 * This file is part of Catch Da Stars.
 *
 * Catch Da Stars is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Catch Da Stars is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Catch Da Stars.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package com.strategames.engine.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.CircleShape;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.utils.JsonReader;
import com.badlogic.gdx.utils.JsonValue;

/**
 * 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
 * 
 * Martijn Brekhof: Updated to support breaking objects 
 * TODO: create list of fixtures in advance to increase performance when recreating objects
 */
public class BodyEditorLoader {

    // Model
    private final Model model;

    // Reusable stuff
    private final List<Vector2> vectorPool = new ArrayList<Vector2>();
    private final PolygonShape polygonShape = new PolygonShape();
    private final CircleShape circleShape = new CircleShape();
    private final Vector2 vec = new Vector2();

    // -------------------------------------------------------------------------
    // Ctors
    // -------------------------------------------------------------------------

    public BodyEditorLoader(FileHandle file) {
        if (file == null)
            throw new NullPointerException("file is null");
        model = readJson(file.readString());
    }

    public BodyEditorLoader(String str) {
        if (str == null)
            throw new NullPointerException("str is null");
        model = readJson(str);
    }

    // -------------------------------------------------------------------------
    // Public API
    // -------------------------------------------------------------------------

    public void setupVertices(String name, float scale) throws RuntimeException {
        RigidBodyModel rbModel = model.rigidBodies.get(name);
        if (rbModel == null)
            throw new RuntimeException("Name '" + name + "' was not found.");

        Vector2 origin = vec.set(rbModel.origin).scl(scale);

        for (int i = 0, n = rbModel.polygons.size(); i < n; i++) {
            PolygonModel polygon = rbModel.polygons.get(i);
            Vector2[] vertices = polygon.buffer;

            for (int ii = 0, nn = vertices.length; ii < nn; ii++) {
                vertices[ii] = new Vector2().set(polygon.vertices.get(ii)).scl(scale);
                vertices[ii].sub(origin);
            }
        }

        for (int i = 0, n = rbModel.circles.size(); i < n; i++) {
            CircleModel circle = rbModel.circles.get(i);
            circle.centerBuffer = newVec().set(circle.center).scl(scale);
            circle.radiusBuffer = circle.radius * scale;
        }
    }

    /**
     * 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(java.lang.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 loadSync.
     * @param uniqueIdentifier will be added to the fixture. Retrieve using fixture.getUserData()
     * @param fd The fixture parameters to apply to the created body fixture.
     */
    public void attachFixture(Body body, String name, int uniqueIdentifier, FixtureDef fd) throws RuntimeException {
        RigidBodyModel rbModel = model.rigidBodies.get(name);
        if (rbModel == null)
            throw new RuntimeException("Name '" + name + "' was not found.");

        for (int i = 0, n = rbModel.polygons.size(); i < n; i++) {
            PolygonModel polygon = rbModel.polygons.get(i);
            polygonShape.set(polygon.buffer);
            fd.shape = polygonShape;
            Fixture fixture = body.createFixture(fd); // Added to support breakable objects
            fixture.setUserData(new Integer(uniqueIdentifier)); // Added to support breakable objects
        }

        for (int i = 0, n = rbModel.circles.size(); i < n; i++) {
            CircleModel circle = rbModel.circles.get(i);

            circleShape.setPosition(circle.centerBuffer);
            circleShape.setRadius(circle.radiusBuffer);
            fd.shape = circleShape;
            Fixture fixture = body.createFixture(fd); // Added to support breakable objects
            fixture.setUserData(new Integer(uniqueIdentifier)); // Added to support breakable objects
        }
    }

    /**
     * Gets the image path attached to the given name.
     */
    public String getImagePath(String name) throws RuntimeException {
        RigidBodyModel rbModel = model.rigidBodies.get(name);
        if (rbModel == null)
            throw new RuntimeException("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)
            throw new RuntimeException("Name '" + name + "' was not found.");

        return vec.set(rbModel.origin).scl(scale);
    }

    /**
     * <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;
    }

    // -------------------------------------------------------------------------
    // 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> polygons = 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[] buffer; // used to avoid recalculation in attachFixture()
    }

    public static class CircleModel {
        public final Vector2 center = new Vector2();
        public float radius;
        private Vector2 centerBuffer; // used to avoid recalculation in attachFixture()
        private float radiusBuffer; // used to avoid recalculation in attachFixture()
    }

    // -------------------------------------------------------------------------
    // 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) {
        RigidBodyModel rbModel = new RigidBodyModel();
        rbModel.name = (String) bodyElem.get("name").asString();
        rbModel.imagePath = (String) bodyElem.get("imagePath").asString();

        JsonValue originElem = bodyElem.get("origin");
        rbModel.origin.x = originElem.get("x").asFloat();
        rbModel.origin.y = originElem.get("y").asFloat();

        // polygons

        JsonValue polygonsElem = bodyElem.get("polygons");

        for (int i = 0; i < polygonsElem.size; i++) {
            PolygonModel polygon = new PolygonModel();
            rbModel.polygons.add(polygon);

            JsonValue verticesElem = polygonsElem.get(i);
            for (int ii = 0; ii < verticesElem.size; ii++) {
                JsonValue vertexElem = verticesElem.get(ii);
                float x = vertexElem.get("x").asFloat();
                float y = vertexElem.get("y").asFloat();
                polygon.vertices.add(new Vector2(x, y));
            }

            polygon.buffer = new Vector2[polygon.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.get("cx").asFloat();
            circle.center.y = circleElem.get("cy").asFloat();
            circle.radius = circleElem.get("r").asFloat();
        }

        return rbModel;
    }

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    private Vector2 newVec() {
        return vectorPool.isEmpty() ? new Vector2() : vectorPool.remove(0);
    }
}