package net.kennux.cubicworld.entity;
import net.kennux.cubicworld.util.Mathf;
import net.kennux.cubicworld.util.Time;
import net.kennux.cubicworld.voxel.VoxelCollision;
import net.kennux.cubicworld.voxel.VoxelWorld;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.BoundingBox;
/**
* <pre>
* Basic character physics implementation.
* This class is a basic entity implementation for handling very, very simple physics.
* The entity is a box, every frame the box will get moved by velocity (which can be manipulated by move() and impulse()).
* </pre>
*
* @author kennux
*
*/
public abstract class ACharacterEntity extends AEntity
{
/**
* The cubic world instance.
*/
public VoxelWorld voxelWorld;
/**
* The player's velocity.
*/
private Vector3 velocity;
public ACharacterEntity()
{
this.velocity = new Vector3(Vector3.Zero);
}
/**
* You MUST overload this constructor in your own implementation!
*
* @param voxelWorld
*/
public ACharacterEntity(VoxelWorld voxelWorld)
{
this.voxelWorld = voxelWorld;
this.velocity = new Vector3(Vector3.Zero);
}
/**
* Returns the bounding box for the current entity's position.
*
* @return
*/
public BoundingBox getBoundingBox()
{
return this.getBoundingBox(this.getPosition());
}
/**
* Returns the bounding box of this character entity at a given position
* (bottom center).
*
* @return
*/
public BoundingBox getBoundingBox(Vector3 position)
{
// Construct bounding box newPos is the center of the collider, this constructs the minimum position vector and the maximum position vector
Vector3 minimumPos = new Vector3(position.x - (this.getCharacterWidth() / 2.0f), position.y, position.z - (this.getCharacterDepth() / 2.0f));
Vector3 maximumPos = new Vector3(position.x + (this.getCharacterWidth() / 2.0f), position.y + (this.getCharacterHeight()), position.z + (this.getCharacterDepth() / 2.0f));
// Instantiate bounding box
return new BoundingBox(minimumPos, maximumPos);
}
/**
* The depth of the player in voxels.
* Override this in your own implementation if you dont want your character to be 1 depth.
*/
protected float getCharacterDepth()
{
return 1;
}
/**
* The height of the player in voxels.
* Override this in your own implementation if you dont want your character to be 2 height.
*/
protected float getCharacterHeight()
{
return 2;
}
/**
* The width of the player in voxels.
* Override this in your own implementation if you dont want your character to be 1 width.
*/
protected float getCharacterWidth()
{
return 1;
}
/**
* The maximum speed per axis.
* Standard is 3 which means speed can be theoretically between (-3,-3,-3) and (3,3,3).
*
* @return
*/
protected float getMaximumSpeed()
{
return 3f;
}
/**
* Adds a force impulse towards the given direction.
*
* @param direction
*/
public void impulse(Vector3 direction, float strength)
{
Vector3 dirS = new Vector3(direction.nor());
dirS.x *= strength;
dirS.y *= strength;
dirS.z *= strength;
this.velocity.add(dirS);
}
/**
* Performs a grounding check.
*
* @return
*/
public boolean isGrounded()
{
// Perform collision check for y - 0.15
Vector3 nPos = new Vector3(this.getPosition());
nPos.y -= 0.15f;
return this.voxelWorld.intersects(this.getBoundingBox(nPos));
}
/**
* Moves to the given direction.
* normalizes and multiplicates the direction with the delta time
*
* @param direction
*/
public void move(Vector3 direction)
{
direction.y = 0;
Vector3 dirD = new Vector3(direction);
dirD.x *= Time.getDeltaTime();
dirD.z *= Time.getDeltaTime();
this.velocity.add(dirD);
}
/**
* <pre>
* Detects collisions and handles physic movement simulation.
* It does the following things:
*
* - Check if the player is grounded (if no, apply gravitation)
* - Calculate the new position by adding velocity to the current position
* - Check if there are voxels at the new position which could intersect with the player bounding box
* -> if yes, reset position and velocity
* - Calculate deceleration
* - Update position
*
* </pre>
*/
@Override
public void update()
{
// Only serverside
if (!this.master.isServer())
return;
// Physics
if (!this.isGrounded())
this.velocity.y += Time.getDeltaTime() * -2.5f;
// Max check
if (this.velocity.x > 0)
this.velocity.x = Mathf.min(this.velocity.x, this.getMaximumSpeed());
else if (this.velocity.x < 0)
this.velocity.x = Mathf.max(this.velocity.x, -this.getMaximumSpeed());
if (this.velocity.y > 0)
this.velocity.y = Mathf.min(this.velocity.y, this.getMaximumSpeed());
else if (this.velocity.y < 0)
this.velocity.y = Mathf.max(this.velocity.y, -this.getMaximumSpeed());
if (this.velocity.z > 0)
this.velocity.z = Mathf.min(this.velocity.z, this.getMaximumSpeed());
else if (this.velocity.z < 0)
this.velocity.z = Mathf.max(this.velocity.z, -this.getMaximumSpeed());
// Move
Vector3 newPos = new Vector3(this.getPosition()).add(this.velocity);
// Collision check
VoxelCollision collision = this.voxelWorld.collisionCheck(this.getBoundingBox(newPos));
if (collision != null)
{
// Intersection! reset collided axis!
if (collision.collisionX)
this.velocity.x = 0;
else if (collision.collisionY)
this.velocity.y = 0;
else if (collision.collisionZ)
this.velocity.z = 0;
newPos = this.getPosition();
}
// Deceleration
// TODO May reimplement?
if (this.velocity.x > 0)
{
this.velocity.x -= 20.0f * Time.getDeltaTime();
if (this.velocity.x < 0)
this.velocity.x = 0;
}
else if (this.velocity.x < 0)
{
this.velocity.x += 20.0f * Time.getDeltaTime();
if (this.velocity.x > 0)
this.velocity.x = 0;
}
if (this.velocity.y > 0)
{
this.velocity.y -= 9.0f * Time.getDeltaTime();
if (this.velocity.y < 0)
this.velocity.y = 0;
}
else if (this.velocity.y < 0)
{
this.velocity.y += 9.0f * Time.getDeltaTime();
if (this.velocity.y > 0)
this.velocity.y = 0;
}
if (this.velocity.z > 0)
{
this.velocity.z -= 20.0f * Time.getDeltaTime();
if (this.velocity.z < 0)
this.velocity.z = 0;
}
else if (this.velocity.z < 0)
{
this.velocity.z += 20.0f * Time.getDeltaTime();
if (this.velocity.z > 0)
this.velocity.z = 0;
}
this.setPosition(newPos);
}
}