package com.bitwaffle.spaceout.entities.dynamic;
import java.util.Random;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Quaternion;
import org.lwjgl.util.vector.Vector3f;
import com.bitwaffle.spaceguts.entities.DynamicEntity;
import com.bitwaffle.spaceguts.entities.Entities;
import com.bitwaffle.spaceguts.entities.Entity;
import com.bitwaffle.spaceguts.graphics.render.Render3D;
import com.bitwaffle.spaceguts.physics.CollisionTypes;
import com.bitwaffle.spaceguts.physics.Physics;
import com.bitwaffle.spaceguts.util.QuaternionHelper;
import com.bitwaffle.spaceout.entities.passive.AsteroidField;
import com.bitwaffle.spaceout.interfaces.Health;
import com.bitwaffle.spaceout.interfaces.Projectile;
import com.bitwaffle.spaceout.resources.Models;
import com.bulletphysics.collision.dispatch.CollisionObject;
import com.bulletphysics.collision.shapes.SphereShape;
import com.bulletphysics.linearmath.Transform;
/**
* A la Atari's 1979 classic, Asteroids.
* @author TranquilMarmot
*/
public class Asteroid extends DynamicEntity implements Health, Projectile{
final static short COL_GROUP = CollisionTypes.PLANET;
final static short COL_WITH = (short)(CollisionTypes.SHIP | CollisionTypes.WALL | CollisionTypes.PLANET | CollisionTypes.PICKUP | CollisionTypes.PROJECTILE);
/** Used for scaling the modelview when drawing */
private static Matrix4f oldModelView = new Matrix4f();
/** The fastest the asteroid can spin */
final static float ANGVEC_CAP = 5.0f;
/** How fast the asteroids this asteroid creates will be spinning when they're created */
final static float SPAWN_ANGVEC_FACTOR = 25.0f;
/** How much damage the asteroid does when it hits the player */
final static int DAMAGE = 10;
/** How bouncy the asteroid is */
final static float ASTEROID_RESTITUTION = 0.5f;
/** Size at which the asteroid will drop loot instead of splitting into smaller asteroids*/
final static float LOOT_SIZE = 20.0f;
/** How many items an asteroid drops when it's destroyed and is smaller than LOOT_SIZE */
final static int LOOT_AMOUNT = 25;
/** How many asteroids are created when the asteroid is destroyed */
final static int NUMBER_OF_DIVISIONS = 3;
/** How heavy an asteroid is based on it's size (mass = size * MASS_FACTOR) */
final static float MASS_FACTOR = 2;
/** How much health the asteroid has */
int health = 80;
/** How big the asteroid is */
private float size;
/** Which asteroid field this asteroid belongs to */
private AsteroidField field;
/**
* ASS-teroid heh heh heh
* @param location Location of asteroid
* @param rotation Rotation of asteroid
* @param size How big the asteroid is
*/
public Asteroid(Vector3f location, Quaternion rotation, float size, AsteroidField field) {
super(location, rotation, new SphereShape(size), size * MASS_FACTOR, ASTEROID_RESTITUTION, COL_GROUP, COL_WITH);
rigidBody.setActivationState(CollisionObject.DISABLE_DEACTIVATION);
rigidBody.setAngularVelocity(new javax.vecmath.Vector3f(0.0f, 0.015f, 0.0f));
this.type = "Asteroid (size " + size + " health " + health + ")";
this.model = Models.ASTEROID.getModel();
this.size = size;
if(field != null){
this.field = field;
field.addAsteroidToField(this);
}
}
@Override
public void draw(){
// scale the modelview before drawing
oldModelView.load(Render3D.modelview);
Render3D.modelview.scale(new org.lwjgl.util.vector.Vector3f(size, size, size));
Render3D.program.setUniform("ModelViewMatrix", Render3D.modelview);
super.draw();
Render3D.modelview.load(oldModelView);
}
@Override
/**
* Draws the physics debug info for this entity. Should be called before
* rotations are applied.
*/
public void drawPhysicsDebug() {
Transform worldTransform = new Transform();
rigidBody.getWorldTransform(worldTransform);
Physics.dynamicsWorld.debugDrawObject(worldTransform, Models.ASTEROID.getModel().getCollisionShape(),
new javax.vecmath.Vector3f(0.0f, 0.0f, 0.0f));
}
@Override
public void update(float timeStep){
super.update(timeStep);
capAngularVelocity();
}
/**
* Keep the asteroid from spinning too fast
*/
private void capAngularVelocity(){
javax.vecmath.Vector3f angVec = new javax.vecmath.Vector3f();
rigidBody.getAngularVelocity(angVec);
float speed = angVec.length();
if(speed > ANGVEC_CAP){
angVec.x *= ANGVEC_CAP / speed;
angVec.y *= ANGVEC_CAP / speed;
angVec.z *= ANGVEC_CAP / speed;
rigidBody.setAngularVelocity(angVec);
}
}
@Override
public int getCurrentHealth() {
return health;
}
@Override
public void hurt(int amount) {
health -= amount;
this.type = "Asteroid (size " + size + " health " + health + ")";
if(health <= 0){
explode();
} else {
size -= amount / 2.0f;
if(size <= LOOT_SIZE)
explode();
}
}
/**
* Cause the asteroid to explode, either leaving behind more asteroids or some diamonds
*/
private void explode(){
removeFlag = true;
if(size <= LOOT_SIZE){
for(int i = 0; i < 25; i++){
addRandomDiamond();
}
} else{
for(int i = 0; i < NUMBER_OF_DIVISIONS; i++){
addRandomAsteroid(this.size / NUMBER_OF_DIVISIONS);
}
}
}
/**
* Adds a random asteroid at this asteroid's location
* @param newSize New size of asteroid
*/
private void addRandomAsteroid(float newSize){
Random randy = new Random();
float asteroidX = randy.nextFloat() * 10.0f;
float asteroidY = randy.nextFloat() * 10.0f;
float asteroidZ = randy.nextFloat() * 10.0f;
// randomly go positive or negative on each axis (+/- size of parent asteroid)
asteroidX = randy.nextBoolean() ? -asteroidX - size : asteroidX + size;
asteroidY = randy.nextBoolean() ? -asteroidY - size : asteroidY + size;
asteroidZ = randy.nextBoolean() ? -asteroidZ - size : asteroidZ + size;
Vector3f asteroidLocation = new Vector3f(asteroidX, asteroidY, asteroidZ);
Vector3f.add(this.location, asteroidLocation, asteroidLocation);
// make the new asteroid face a random direction
Quaternion asteroidRotation = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
float xRot = randy.nextFloat() * 100.0f;
float yRot = randy.nextFloat() * 100.0f;
float zRot = randy.nextFloat() * 100.0f;
asteroidRotation = QuaternionHelper.rotate(asteroidRotation, new Vector3f(xRot,yRot, zRot));
Asteroid a = new Asteroid(asteroidLocation, asteroidRotation, newSize, field);
// randomly go +/- the parent asteroid's speed
javax.vecmath.Vector3f linVec = new javax.vecmath.Vector3f();
this.rigidBody.getLinearVelocity(linVec);
if(randy.nextBoolean()) linVec.x = -linVec.x / 2;
if(randy.nextBoolean()) linVec.y = -linVec.y / 2;
if(randy.nextBoolean()) linVec.z = -linVec.z / 2;
a.rigidBody.setLinearVelocity(linVec);
// randomly go +/- the parent asteroid's angular speed
javax.vecmath.Vector3f angVec = new javax.vecmath.Vector3f();
this.rigidBody.getAngularVelocity(angVec);
if(randy.nextBoolean()) angVec.x = -angVec.x;
if(randy.nextBoolean()) angVec.y = -angVec.y;
if(randy.nextBoolean()) angVec.y = -angVec.y;
a.rigidBody.setAngularVelocity(angVec);
Entities.addDynamicEntity(a);
}
// TODO find a better way to do loot drops
/**
* Add some random diamonds
*/
private void addRandomDiamond() {
Random randy = new Random();
float diamondX = randy.nextFloat() * 10.0f;
float diamondY = randy.nextFloat() * 10.0f;
float diamondZ = randy.nextFloat() * 10.0f;
if(randy.nextBoolean()) diamondX = -diamondX;
if(randy.nextBoolean()) diamondY = -diamondY;
if(randy.nextBoolean()) diamondZ = -diamondZ;
Vector3f diamondLocation = new Vector3f();
Vector3f downInFront = QuaternionHelper.rotateVectorByQuaternion(
new Vector3f(diamondX, diamondY, diamondZ), this.rotation);
Vector3f.add(this.location, downInFront, diamondLocation);
Quaternion diamondRotation = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
float xRot = randy.nextFloat() * 100.0f;
float yRot = randy.nextFloat() * 100.0f;
float zRot = randy.nextFloat() * 100.0f;
diamondRotation = QuaternionHelper.rotate(diamondRotation, new Vector3f(xRot,yRot, zRot));
float diamondStopSpeed = 0.1f;
Diamond d = new Diamond(diamondLocation, diamondRotation, diamondStopSpeed);
Entities.addDynamicEntity(d);
}
@Override
public void heal(int amount) {
health += amount;
}
@Override
public int getDamage() {
return DAMAGE;
}
@Override
public Entity getOwner() {
return this;
}
public float getSize(){
return size;
}
}