com.laex.cg2d.render.impl.EntityManager.java Source code

Java tutorial

Introduction

Here is the source code for com.laex.cg2d.render.impl.EntityManager.java

Source

/*
 * Copyright (c) 2012, 2013 Hemanta Sapkota.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * Hemanta Sapkota (laex.pearl@gmail.com)
 */
package com.laex.cg2d.render.impl;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import aurelienribon.bodyeditor.BodyEditorLoader;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Rectangle;
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;
import com.badlogic.gdx.physics.box2d.CircleShape;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.utils.Array;
import com.laex.cg2d.model.ScreenModel.CGBounds;
import com.laex.cg2d.model.ScreenModel.CGEditorShapeType;
import com.laex.cg2d.model.ScreenModel.CGEntity;
import com.laex.cg2d.model.ScreenModel.CGEntityAnimation;
import com.laex.cg2d.model.ScreenModel.CGEntityCollisionType;
import com.laex.cg2d.model.ScreenModel.CGEntitySpritesheetItem;
import com.laex.cg2d.model.ScreenModel.CGLayer;
import com.laex.cg2d.model.ScreenModel.CGShape;
import com.laex.cg2d.model.ScreenModel.CGVector2;
import com.laex.cg2d.render.BodyVisitor;
import com.laex.cg2d.render.ScreenScaffold;
import com.laex.cg2d.render.impl.bodies.CircleBody;
import com.laex.cg2d.render.util.RunnerUtil;
import com.laex.cg2d.render.util.ScreenToWorld;

/**
 * The Class EntityManager.
 */
public class EntityManager implements ScreenScaffold {

    /** The batch. */
    private SpriteBatch batch;

    /** The state time. */
    float stateTime = 0;

    /** The shape to sprite map. */
    private Map<CGShape, Sprite> shapeToSpriteMap;

    /** The shape to entity map. */
    private Map<CGShape, CGEntity> shapeToEntityMap;

    /** The shape to animation map. */
    private Map<CGShape, Animation> shapeToAnimationMap;

    // x = origin x, y = origin y, z = radius
    /** The shape to animation origin map. */
    private Map<CGShape, Vector3> shapeToAnimationOriginMap;

    /** The draw entities. */
    private boolean drawEntities;

    /** The manipulator. */
    private ScreenManagerImpl manipulator;

    /**
     * Instantiates a new entity manager.
     * 
     * @param manipulator
     *          the manipulator
     * @param batch
     *          the batch
     */
    public EntityManager(ScreenManagerImpl manipulator, SpriteBatch batch) {
        this.batch = batch;
        this.manipulator = manipulator;

        this.drawEntities = manipulator.model().getScreenPrefs().getDebugDrawPrefs().getDrawEntities();

        this.shapeToSpriteMap = new HashMap<CGShape, Sprite>();
        this.shapeToEntityMap = new HashMap<CGShape, CGEntity>();
        this.shapeToAnimationMap = new HashMap<CGShape, Animation>();
        this.shapeToAnimationOriginMap = new HashMap<CGShape, Vector3>();
    }

    /**
     * Update state time.
     * 
     * @param stateTime
     *          the state time
     */
    public void updateStateTime(float stateTime) {
        this.stateTime = stateTime;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.laex.cg2d.render.IGameComponentManager#create()
     */
    @Override
    public void create() {

        try {
            createBodies();
        } catch (IOException e) {
            e.printStackTrace();
        }

        createJoints();
    }

    /**
     * Joints must be created after all shapes are created including entities,
     * boxes, circles. Therefore, we create joints here in EntityManager and not
     * ShapeManager or BackgroundManager.
     */
    private void createJoints() {
        for (CGLayer layer : manipulator.model().getLayersList()) {
            for (CGShape shape : layer.getShapeList()) {
                manipulator.createJoints(shape);
            }
        }
    }

    /**
     * Creates the entity.
     * 
     * @param shape
     *          the shape
     * @param animationName
     *          the animation name
     * @return the body
     * @throws IOException
     *           Signals that an I/O exception has occurred.
     */
    public Body createEntity(CGShape shape, String animationName) throws IOException {
        CGEditorShapeType eType = shape.getEditorShapeType();

        if (eType == CGEditorShapeType.BACKGROUND_SHAPE) {
            return null;
        }

        /* We alo create shapes */
        if (eType == CGEditorShapeType.SIMPLE_SHAPE_BOX || eType == CGEditorShapeType.SIMPLE_SHAPE_CIRCLE
                || eType == CGEditorShapeType.SIMPLE_SHAPE_HEDGE || eType == CGEditorShapeType.SIMPLE_SHAPE_VEDGE) {
            Body b = manipulator.createBody(shape, null, null);
            b.setUserData(shape);
            return b;
        }

        /* For Entities */
        String entPath = shape.getEntityRefFile().getResourceFileAbsolute();
        final CGEntity entity = CGEntity.parseFrom(Gdx.files.absolute(entPath).read());

        shapeToEntityMap.put(shape, entity);

        /* Entity Animation */
        CGEntityAnimation ea = null;

        /* If name is not provided, get default animation */
        if (animationName == null) {
            ea = RunnerUtil.getDefaultAnimation(entity);
        } else {
            ea = RunnerUtil.getAnimationFrom(entity, animationName);
        }

        Body b = manipulator.createBody(shape, entity, ea);
        b.setUserData(shape);

        // Resource file empty indicates this entity might not have
        // image & collision shape defined.
        if (ea.getSpritesheetFile().getResourceFileAbsolute().trim().isEmpty()) {
            manipulator.world().destroyBody(b);
            return null;
        }

        FileHandle handle = Gdx.files.absolute(ea.getSpritesheetFile().getResourceFileAbsolute());

        Texture tex = new Texture(handle);
        tex.setFilter(TextureFilter.Linear, TextureFilter.Linear);

        Array<TextureRegion> walkFrames = new Array<TextureRegion>();

        for (CGEntitySpritesheetItem cgEi : ea.getSpritesheetItemsList()) {
            CGBounds bnds = cgEi.getExtractBounds();
            TextureRegion tr = new TextureRegion(tex, (int) bnds.getX(), (int) bnds.getY(), (int) bnds.getWidth(),
                    (int) bnds.getHeight());
            walkFrames.add(tr);
        }

        Animation spriteAnimation = new Animation(ea.getAnimationDuration(), walkFrames);
        shapeToAnimationMap.put(shape, spriteAnimation);

        Sprite spr = new Sprite(spriteAnimation.getKeyFrame(stateTime, true));

        // Set the position & size
        float x = shape.getBounds().getX();
        float y = shape.getBounds().getY();
        float width = shape.getBounds().getWidth();
        float height = shape.getBounds().getHeight();

        Vector2 scrPos = new Vector2(x, y);
        Vector2 worldPos = ScreenToWorld.inst(manipulator.model()).screenToWorldFlipped(scrPos, height);
        spr.setPosition(worldPos.x, worldPos.y);

        // the position circle (collision shape) will vary with that of
        // a box. so
        // we need to check and set position for each types
        if (ea.getCollisionType() == CGEntityCollisionType.CIRCLE) {
            Vector3 origin = shapeToAnimationOriginMap.get(shape);

            float radius = origin.z;

            float x1 = (worldPos.x - radius);
            float y1 = (worldPos.y - radius);
            spr.setPosition(x1, y1);
        }

        float w = ((float) width / manipulator.ptmRatio());
        float h = ((float) height / manipulator.ptmRatio());

        spr.setSize(w, h);

        shapeToSpriteMap.put(shape, spr);

        return b;
    }

    /**
     * Removes the entity.
     * 
     * @param shape
     *          the shape
     */
    public void removeEntity(CGShape shape) {
        shapeToAnimationMap.remove(shape);
        shapeToSpriteMap.remove(shape);
        shapeToEntityMap.remove(shape);
    }

    /**
     * Creates the bodies.
     * 
     * @throws IOException
     *           Signals that an I/O exception has occurred.
     */
    private void createBodies() throws IOException {
        for (CGLayer layer : manipulator.model().getLayersList()) {
            for (final CGShape shape : layer.getShapeList()) {
                /* Create entity with default animation */
                createEntity(shape, null);
            }
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.laex.cg2d.render.IGameComponentManager#render()
     */
    @Override
    public void render() {
        if (!drawEntities) {
            return;
        }

        batch.begin();

        manipulator.acceptBodyVisitor(new BodyVisitor() {

            @Override
            public void visit(Body b, CGShape shape) {
                if (!(shape.getEditorShapeType() == CGEditorShapeType.ENTITY_SHAPE)) {
                    return;
                }

                Vector2 pos = b.getPosition();

                // position for circle collision shape & box collision shape differ.
                CGEntity e = shapeToEntityMap.get(shape);
                CGEntityAnimation ea = RunnerUtil.getDefaultAnimation(e);

                // Position of circle should be adjusted of radius.
                if (ea.getCollisionType() == CGEntityCollisionType.CIRCLE) {
                    float radius = shapeToAnimationOriginMap.get(shape).z;
                    pos.x = pos.x - radius;
                    pos.y = pos.y - radius;
                }

                Sprite spr = shapeToSpriteMap.get(shape);
                if (spr == null) {
                    return;
                }

                spr.setPosition(pos.x, pos.y);
                // setting origin important for rotations to work properly
                Vector3 origin = shapeToAnimationOriginMap.get(shape);
                spr.setOrigin(origin.x, origin.y);
                spr.setRotation(b.getAngle() * MathUtils.radiansToDegrees);

                Animation anim = shapeToAnimationMap.get(shape);

                TextureRegion tr = anim.getKeyFrame(stateTime, true);
                spr.setRegion(tr);
                spr.draw(batch);
            }
        });

        batch.end();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.laex.cg2d.render.IGameComponentManager#dispose()
     */
    @Override
    public void dispose() {
        manipulator.acceptBodyVisitor(new BodyVisitor() {
            @Override
            public void visit(Body b, CGShape shape) {

                Sprite spr = shapeToSpriteMap.get(shape);

                if (spr != null) {

                    spr.getTexture().dispose();

                }
            }
        });

    }

    public Vector2[] normalizeVertices(List<CGVector2> vertices, float height) {
        Vector2[] vss = new Vector2[vertices.size()];

        for (int i = 0; i < vertices.size(); i++) {
            CGVector2 vi = vertices.get(i);
            Vector2 v = new Vector2(vi.getX(), vi.getY());
            v.y = height - v.y;
            vss[i] = ScreenToWorld.inst(manipulator.model()).screenToWorld(v);
        }

        // after flipping, reverse the vertices
        Vector2[] inverse = new Vector2[vertices.size()];
        int len = vertices.size() - 1;
        for (int i = 0; i < vertices.size(); i++) {
            inverse[i] = vss[len--];
        }

        return inverse;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.laex.cg2d.render.AbstractGameComponentManager#createEntityCollisionShape
     * (com.laex.cg2d.protobuf.CGScreenModel.CGShape,
     * com.laex.cg2d.protobuf.CGScreenModel.CGEntity,
     * com.badlogic.gdx.physics.box2d.BodyDef,
     * com.badlogic.gdx.physics.box2d.FixtureDef,
     * com.badlogic.gdx.physics.box2d.Body, com.badlogic.gdx.math.Vector2)
     */
    /**
     * Creates the entity collision shape.
     * 
     * @param shape
     *          the shape
     * @param entity
     *          the entity
     * @param ea
     *          the ea
     * @param bodyDef
     *          the body def
     * @param fixDef
     *          the fix def
     * @param b
     *          the b
     */
    public void createEntityCollisionShape(CGShape shape, CGEntity entity, CGEntityAnimation ea, BodyDef bodyDef,
            FixtureDef fixDef, Body b) {

        CGEntityCollisionType shapeType = ea.getCollisionType();
        PolygonShape polyShape = new PolygonShape();
        Vector2[] va = null;

        // check if shape type is NONE. NONE shape type means that this entity
        // has no collision parameters defined.
        // We simple ignore this and do not create any shape collision for this
        // object
        if (shapeType == CGEntityCollisionType.NONE) {
            // /set empty origin vertex so that other codes do not have to check null
            // pointer exception on its part
            shapeToAnimationOriginMap.put(shape, new Vector3(0, 0, 0));
            return;
        }

        switch (shapeType) {
        case BOX:
            va = normalizeVertices(ea.getVerticesList(), shape.getBounds().getHeight());
            polyShape.set(va);

            shapeToAnimationOriginMap.put(shape, new Vector3(0, 0, 0));

            fixDef.shape = polyShape;
            b.createFixture(fixDef);
            break;

        case CIRCLE: {
            manipulator.world().destroyBody(b);

            CircleShape circShape = new CircleShape();

            /* Decode x,y, width, height of collision shape from the vertices */
            CGVector2 v1 = ea.getVertices(0);
            CGVector2 v2 = ea.getVertices(2);

            Rectangle r = new Rectangle(v1.getX(), v1.getY(), v2.getX(), v2.getY());
            r.width = r.width - r.x;
            r.height = r.height - r.y;

            // this radius is calculated for circle's collision shape data not
            // the sprite width/height data
            float radius = CircleBody.calculateRadiusOfCircleShape(r.width, manipulator.ptmRatio());

            Vector2 cpos = new Vector2(r.x, r.y);
            cpos.y = shape.getBounds().getHeight() - r.height - r.y;
            cpos = ScreenToWorld.inst(manipulator.model()).screenToWorld(cpos);

            /* Calculate origin */
            int x = (int) r.x;
            int y = (int) r.y;
            int w = (int) r.width;
            int h = (int) r.height;

            /*
             * ptmRatioSquared should be casted to integer to avoid floating point
             * calculation division. If not, the orirgin will actully diverge and will
             * be clearly seen in the rotation of the circle shape
             */
            int ptmRatioSquared = (int) (manipulator.ptmRatio() * manipulator.ptmRatio());
            float ox = (x + w) / (ptmRatioSquared) + radius;
            float oy = (y + h) / (ptmRatioSquared) + radius;
            /* End origin calculatio */

            b = manipulator.world().createBody(bodyDef);

            circShape.setRadius(radius);
            circShape.setPosition(cpos);

            shapeToAnimationOriginMap.put(shape, new Vector3(ox, oy, radius));

            fixDef.shape = circShape;
            b.createFixture(fixDef);
        }
            break;

        case CUSTOM:
            BodyEditorLoader bel = new BodyEditorLoader(
                    Gdx.files.absolute(ea.getFixtureFile().getResourceFileAbsolute()));
            // scale = image width or image height / ptmRatio
            /*
             * bodyScale: Consider this: scale is a factor which scales the vertices
             * created by Physcis Editor. This scale is not equals to ptmRatio or
             * scaleFactor. bodyScale is the width or height of the image / ptmRatio
             * imageWidth = 32, scale = imageWidth / ptmRatio = 32 / 16 = 2 imageWidth
             * = 32, scale = imageWidth / ptmRatio = 32 / 32 = 1
             */
            float bodyScale = (shape.getBounds().getWidth() / manipulator.ptmRatio());
            bel.attachFixture(b, ea.getAnimationName(), fixDef, bodyScale);
            Vector2 or = bel.getOrigin(ea.getAnimationName(), bodyScale);
            shapeToAnimationOriginMap.put(shape, new Vector3(or.x, or.y, 0));
            break;

        case NONE:
        default:
            break;
        }

    }
}