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