package com.deftwun.zombiecopter.systems;
import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.core.EntityListener;
import com.badlogic.ashley.core.Family;
import com.badlogic.ashley.systems.IteratingSystem;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
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.Joint;
import com.badlogic.gdx.physics.box2d.Manifold;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.physics.box2d.joints.WeldJointDef;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Logger;
import com.badlogic.gdx.utils.ObjectMap;
import com.deftwun.zombiecopter.App;
import com.deftwun.zombiecopter.box2dJson.PhysicsScene.PhysicsSceneListener;
import com.deftwun.zombiecopter.components.HealthComponent;
import com.deftwun.zombiecopter.components.PhysicsComponent;
import com.deftwun.zombiecopter.components.StickyComponent;
public class PhysicsSystem extends IteratingSystem implements ContactListener, EntityListener, PhysicsSceneListener {
public World world;
private final float gravity = -14;
private Logger logger = new Logger("PhysicsSystem",Logger.INFO);
//Collision Maps
private ObjectMap<Fixture,Array<Fixture>> fixture_fixtures_map = new ObjectMap<Fixture,Array<Fixture>>();
private ObjectMap<Fixture,Array<Body>> fixture_bodies_map = new ObjectMap<Fixture,Array<Body>>();
private ObjectMap<Fixture,Array<PhysicsComponent>> fixture_physics_map = new ObjectMap<Fixture,Array<PhysicsComponent>>();
private ObjectMap<Body,Array<Fixture>> body_fixtures_map = new ObjectMap<Body,Array<Fixture>>();
private ObjectMap<Body,Array<Body>> body_bodies_map = new ObjectMap<Body,Array<Body>>();
private ObjectMap<Body,Array<PhysicsComponent>> body_physics_map = new ObjectMap<Body,Array<PhysicsComponent>>();
private ObjectMap<PhysicsComponent,Array<Fixture>> physics_fixtures_map = new ObjectMap<PhysicsComponent,Array<Fixture>>();
private ObjectMap<PhysicsComponent,Array<Body>> physics_bodies_map = new ObjectMap<PhysicsComponent,Array<Body>>();
private ObjectMap<PhysicsComponent,Array<PhysicsComponent>> physics_physics_map = new ObjectMap<PhysicsComponent,Array<PhysicsComponent>>();
//Collision Methods
public Array<Fixture> getFixturesTouching(Fixture f){if (f==null) return null; return fixture_fixtures_map.get(f,null);}
public Array<Fixture> getFixturesTouching(Body b){if (b==null) return null; return body_fixtures_map.get(b,null);}
public Array<Fixture> getFixturesTouching(PhysicsComponent p){if (p==null) return null;return physics_fixtures_map.get(p,null);}
public Array<Body> getBodiesTouching(Fixture f){if (f==null) return null;return fixture_bodies_map.get(f,null);}
public Array<Body> getBodiesTouching(Body b){if (b==null) return null;return body_bodies_map.get(b,null);}
public Array<Body> getBodiesTouching(PhysicsComponent p){if (p==null) return null;return physics_bodies_map.get(p,null);}
public Array<PhysicsComponent> getPhysicsComponentsTouching(Fixture f){if (f==null) return null;return fixture_physics_map.get(f,null);}
public Array<PhysicsComponent> getPhysicsComponentsTouching(Body b){if (b==null) return null;return body_physics_map.get(b,null);}
public Array<PhysicsComponent> getPhysicsComponentsTouching(PhysicsComponent p){if (p==null) return null;return physics_physics_map.get(p,null);}
public int getContactCount(Fixture f){
if (f == null) return 0;
Array<Fixture> list = fixture_fixtures_map.get(f,null);
return (list == null) ? 0 : list.size;
}
public int getContactCount(Body b){
if (b == null) return 0;
Array<Fixture> list = body_fixtures_map.get(b,null);
return (list == null) ? 0 : list.size;
}
public int getContactCount(PhysicsComponent p){
if (p == null) return 0;
Array<Fixture> list = physics_fixtures_map.get(p,null);
return (list == null) ? 0 : list.size;
}
@SuppressWarnings("unchecked")
public PhysicsSystem(){
super(Family.all(PhysicsComponent.class).get());
logger.debug("initializing");
world = new World(new Vector2(0,gravity),true);
world.setContactListener(this);
}
@Override
public void update(float deltaTime){
super.update(deltaTime);
world.step(deltaTime,4,4);
}
@Override
protected void processEntity(Entity entity, float deltaTime) {
PhysicsComponent physics = App.engine.mappers.physics.get(entity);
HealthComponent health = App.engine.mappers.health.get(entity);
StickyComponent sticky = App.engine.mappers.sticky.get(entity);
float collisionForce = physics.getCollisionNormal();
//Sticky projectiles
if (sticky != null && sticky.enabled){
Fixture stickyFixture = physics.getFixture(sticky.fixtureName);
if (stickyFixture != null){
boolean colliding = getContactCount(stickyFixture) > 0;
Array<Fixture> touchingFixtures = getFixturesTouching(stickyFixture);
for (Fixture f : touchingFixtures){
if (f.isSensor()) colliding = false;
else {
WeldJointDef jd = new WeldJointDef();
Body b1 = f.getBody(),
b2 = stickyFixture.getBody();
jd.initialize(b1,b2,b2.getWorldCenter());
world.createJoint(jd);
sticky.enabled = false;
break;
}
}
if (!colliding) physics.setRotation(physics.getLinearVelocity().angle());
}
}
//Collision Damage
if (health != null && collisionForce >= health.collisionDamageThreshold){
float dmg = collisionForce - health.collisionDamageThreshold;
App.engine.systems.damage.dealDamage(entity,dmg);
logger.debug(String.format("Collision damage: Entity#%d ; Force=%f ; Dmg=%f",
entity.getId(),collisionForce,dmg));
}
physics.clearCollisionNormal();
/*
//Anything outside of the world bounds is destroyed
if (!App.engine.entityBounds.contains(physics.getPosition()) &&
entity != App.engine.getLevel().getEntity()){
logger.debug("Destroy Entity #" + entity.getId() + ": outside of world bounds.");
App.engine.removeEntity(physics.ownerEntity);
}
*/
}
@Override
public void beginContact(Contact contact) {
Fixture fixA = contact.getFixtureA(),
fixB = contact.getFixtureB();
Body bodyA = fixA.getBody(),
bodyB = fixB.getBody();
PhysicsComponent physA = (PhysicsComponent)bodyA.getUserData(),
physB = (PhysicsComponent)bodyB.getUserData();
if ((physA == null) || (physB == null)) {logger.error("BEGIN CONTACT - Physics component is null"); return;}
if (bodyA.getUserData() == physB || bodyB.getUserData() == physA) {logger.debug("BEGIN CONTACT - ignore PhysicsComponent self collisions"); return;}
logger.debug("Begin contact between Entity #" + physA.ownerEntity.getId() + " & #" + physB.ownerEntity.getId());
if (!fixA.isSensor() && !fixB.isSensor()){
if (physA.collisionEffect != "")
App.engine.systems.particle.addEffect(physA.collisionEffect,physA.getPosition(),physB.getRotation());
if (physB.collisionEffect != "")
App.engine.systems.particle.addEffect(physB.collisionEffect,physB.getPosition(),physB.getRotation());
}
fixture_fixtures_map.get(fixA).add(fixB);
fixture_fixtures_map.get(fixB).add(fixA);
fixture_bodies_map.get(fixA).add(bodyB);
fixture_bodies_map.get(fixB).add(bodyA);
fixture_physics_map.get(fixA).add(physB);
fixture_physics_map.get(fixB).add(physA);
body_fixtures_map.get(bodyA).add(fixB);
body_fixtures_map.get(bodyB).add(fixA);
body_bodies_map.get(bodyA).add(bodyB);
body_bodies_map.get(bodyB).add(bodyA);
body_physics_map.get(bodyA).add(physB);
body_physics_map.get(bodyB).add(physA);
physics_fixtures_map.get(physA).add(fixB);
physics_fixtures_map.get(physB).add(fixA);
physics_bodies_map.get(physA).add(bodyB);
physics_bodies_map.get(physB).add(bodyA);
physics_physics_map.get(physA).add(physB);
physics_physics_map.get(physB).add(physA);
}
@Override
public void endContact(Contact contact) {
logger.debug("End contact");
Fixture fixA = contact.getFixtureA(),
fixB = contact.getFixtureB();
Body bodyA = fixA.getBody(),
bodyB = fixB.getBody();
PhysicsComponent physA = (PhysicsComponent)bodyA.getUserData(),
physB = (PhysicsComponent)bodyB.getUserData();
if ((physA == null) || (physB == null)) {logger.error("Physics component is null"); return;}
if (bodyA.getUserData() == physB || bodyB.getUserData() == physA) {logger.debug("END CONTACT - ignore PhysicsComponent self collisions."); return;}
logger.debug("End contact between Entity #" + physA.ownerEntity.getId() + " & #" + physB.ownerEntity.getId());
if (fixture_fixtures_map.get(fixA) == null || fixture_fixtures_map.get(fixB) == null){
//TODO: If the fixtures exist but haven't been added we just return
logger.error("END CONTACT: One or both of the fixtures are no longer registered with the physics system.");
return;
}
fixture_fixtures_map.get(fixA).removeValue(fixB, true);
fixture_fixtures_map.get(fixB).removeValue(fixA, true);
fixture_bodies_map.get(fixA).removeValue(bodyB, true);
fixture_bodies_map.get(fixB).removeValue(bodyA, true);
fixture_physics_map.get(fixA).removeValue(physB, true);
fixture_physics_map.get(fixB).removeValue(physA, true);
body_fixtures_map.get(bodyA).removeValue(fixB, true);
body_fixtures_map.get(bodyB).removeValue(fixA, true);
body_bodies_map.get(bodyA).removeValue(bodyB, true);
body_bodies_map.get(bodyB).removeValue(bodyA, true);
body_physics_map.get(bodyA).removeValue(physB, true);
body_physics_map.get(bodyB).removeValue(physA, true);
physics_fixtures_map.get(physA).removeValue(fixB, true);
physics_fixtures_map.get(physB).removeValue(fixA, true);
physics_bodies_map.get(physA).removeValue(bodyB, true);
physics_bodies_map.get(physB).removeValue(bodyA, true);
physics_physics_map.get(physA).removeValue(physB, true);
physics_physics_map.get(physB).removeValue(physA, true);
}
@Override
public void preSolve(Contact contact, Manifold oldManifold) {
}
@Override
public void postSolve(Contact contact, ContactImpulse impulse) {
Body bodyA = contact.getFixtureA().getBody(), bodyB = contact.getFixtureB().getBody();
PhysicsComponent physA = (PhysicsComponent)bodyA.getUserData(),
physB = (PhysicsComponent)bodyB.getUserData();
if (physA == null || physB == null) return;
physA.addCollisionNormal(impulse.getNormalImpulses()[0]);
physB.addCollisionNormal(impulse.getNormalImpulses()[0]);
}
@Override
public void entityAdded(Entity entity) {
logger.debug("Entity added: " + entity.getId());
PhysicsComponent physics = App.engine.mappers.physics.get(entity);
if (physics == null) return;
physics.ownerEntity = entity;
this.physicsAdded(physics);
}
@Override
public void entityRemoved(Entity entity) {
logger.debug("Entity removed: " + entity.getId());
PhysicsComponent physics = App.engine.mappers.physics.get(entity);
if (physics == null) return;
physics.destroy();
this.physicsRemoved(physics);
}
//Should be called when a new physics component is created
public void physicsAdded(PhysicsComponent physics){
logger.debug("Physics Component added");
physics_fixtures_map.put(physics,new Array<Fixture>());
physics_bodies_map.put(physics,new Array<Body>());
physics_physics_map.put(physics,new Array<PhysicsComponent>());
}
//Should be called when a new fixture is created
@Override
public void fixtureAdded(Fixture fixture){
logger.debug("Fixture Added");
logger.debug("Fixture_Fixture_Map size = " + fixture_fixtures_map.size);
fixture_fixtures_map.put(fixture,new Array<Fixture>());
fixture_bodies_map.put(fixture,new Array<Body>());
fixture_physics_map.put(fixture,new Array<PhysicsComponent>());
}
//Should be called when a new body is created
@Override
public void bodyAdded(Body body){
logger.debug("Body Added");
body_fixtures_map.put(body,new Array<Fixture>());
body_bodies_map.put(body,new Array<Body>());
body_physics_map.put(body,new Array<PhysicsComponent>());
}
//Should be called when an existing physics component is destroyed
public void physicsRemoved(PhysicsComponent physics){
logger.debug("Physics Component Removed");
physics_fixtures_map.remove(physics);
physics_bodies_map.remove(physics);
physics_physics_map.remove(physics);
}
//Should be called when an existing fixture is destroyed
@Override
public void fixtureRemoved(Fixture fixture){
logger.debug("Fixture Removed");
fixture_fixtures_map.remove(fixture);
fixture_bodies_map.remove(fixture);
fixture_physics_map.remove(fixture);
}
//Should be called when an existing body is destroyed
@Override
public void bodyRemoved(Body body){
logger.debug("Body Removed");
body_fixtures_map.remove(body);
body_bodies_map.remove(body);
body_physics_map.remove(body);
}
@Override
public void jointAdded(Joint j) {
}
@Override
public void jointRemoved(Joint j) {
}
}