/*******************************************************************************
* Copyright 2015 Maximilian Stark | Dakror <mail@dakror.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package de.dakror.vloxlands.game.entity;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.ai.msg.Telegram;
import com.badlogic.gdx.ai.msg.Telegraph;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g3d.Environment;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.model.Node;
import com.badlogic.gdx.graphics.g3d.utils.AnimationController;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.utils.Array;
import de.dakror.vloxlands.Vloxlands;
import de.dakror.vloxlands.game.Game;
import de.dakror.vloxlands.game.world.Island;
import de.dakror.vloxlands.game.world.World;
import de.dakror.vloxlands.ui.PinnableWindow;
import de.dakror.vloxlands.ui.RevolverSlot;
import de.dakror.vloxlands.util.CSVReader;
import de.dakror.vloxlands.util.base.EntityBase;
import de.dakror.vloxlands.util.interf.Savable;
import de.dakror.vloxlands.util.math.Bits;
/**
* @author Dakror
*/
public class Entity extends EntityBase implements Telegraph, Savable {
public static final int LINES[][] = { { 0, 1 }, { 0, 3 }, { 0, 4 }, { 6, 7 }, { 6, 5 }, { 6, 2 }, { 1, 5 }, { 2, 3 }, { 4, 5 }, { 3, 7 }, { 1, 2 }, { 7, 4 } };
static HashMap<Byte, Class<?>> idToClassMap = new HashMap<Byte, Class<?>>();
static HashMap<Class<?>, Byte> classToIdMap = new HashMap<Class<?>, Byte>();
protected ModelInstance modelInstance;
protected Array<ModelInstance> subs;
protected byte id;
protected int level;
protected String name;
protected float weight;
protected float uplift;
protected boolean modelVisible;
protected boolean additionalVisible;
protected boolean visible;
protected boolean spawned;
public boolean inFrustum;
public boolean hovered;
public boolean wasSelected;
public boolean selected;
protected boolean markedForRemoval;
protected BoundingBox boundingBox;
protected final Vector3 dimensions = new Vector3();
public final Vector3 blockTrn = new Vector3();
protected Island island;
protected AnimationController animationController;
public final Vector3 posCache = new Vector3();
public final Quaternion rotCache = new Quaternion();
public final Vector3 tmpV = new Vector3();
public final Quaternion tmpQ = new Quaternion();
final Matrix4 tmp = new Matrix4();
public Entity(float x, float y, float z, String model) {
id = classToIdMap.get(getClass());
modelInstance = new ModelInstance(Vloxlands.assets.get("models/" + model, Model.class));
modelInstance.calculateBoundingBox(boundingBox = new BoundingBox());
if (boundingBox.getDimensions().x % 1 != 0 || boundingBox.getDimensions().y % 1 != 0 || boundingBox.getDimensions().z % 1 != 0) {
blockTrn.set(((float) Math.ceil(boundingBox.getDimensions().x) - boundingBox.getDimensions().x) / 2, 1 - boundingBox.getCenter().y, ((float) Math.ceil(boundingBox.getDimensions().z) - boundingBox.getDimensions().z) / 2);
}
blockTrn.add(boundingBox.getDimensions().cpy().scl(0.5f));
modelInstance.transform.translate(x, y, z).translate(blockTrn);
animationController = new AnimationController(modelInstance);
markedForRemoval = false;
subs = new Array<ModelInstance>();
for (Node n : modelInstance.nodes.get(0).children) {
if (n.id.startsWith("model:")) {
subs.add(new ModelInstance(Vloxlands.assets.get("models/" + model.replace(model.substring(model.lastIndexOf("/") + 1), n.id.replace("model:", "")) + ".vxi", Model.class), n.translation));
}
}
modelInstance.transform.getTranslation(posCache);
level = 0;
modelVisible = true;
additionalVisible = true;
visible = true;
dimensions.set(Math.max(Math.round(boundingBox.getDimensions().x), 1), Math.max(Math.round(boundingBox.getDimensions().y), 1), Math.max(Math.round(boundingBox.getDimensions().z), 1));
Game.instance.addListener(this);
}
public void setIsland(Island island) {
this.island = island;
}
public Island getIsland() {
return island;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
public float getUplift() {
return uplift;
}
public void setUplift(float uplift) {
this.uplift = uplift;
}
public BoundingBox getBoundingBox() {
return boundingBox;
}
public AnimationController getAnimationController() {
return animationController;
}
public ModelInstance getModelInstance() {
return modelInstance;
}
public byte getId() {
return id;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public boolean isMarkedForRemoval() {
return markedForRemoval;
}
@Override
public void tick(int tick) {
modelInstance.transform.getTranslation(posCache);
modelInstance.transform.getRotation(rotCache);
inFrustum = Game.camera.frustum.boundsInFrustum(boundingBox.getCenter().x + posCache.x, boundingBox.getCenter().y + posCache.y, boundingBox.getCenter().z + posCache.z, boundingBox.getDimensions().x / 2, boundingBox.getDimensions().y / 2, boundingBox.getDimensions().z / 2);
}
public void getWorldBoundingBox(BoundingBox bb) {
bb.min.set(boundingBox.min).add(posCache);
bb.max.set(boundingBox.max).add(posCache);
bb.set(bb.min, bb.max);
}
public void render(ModelBatch batch, Environment environment, boolean minimapMode) {
if (!visible) return;
if (modelVisible) {
batch.render(modelInstance, environment);
for (ModelInstance mi : subs) {
tmp.set(mi.transform);
modelInstance.transform.getTranslation(posCache);
modelInstance.transform.getRotation(rotCache);
mi.transform.getTranslation(tmpV);
mi.transform.getRotation(tmpQ);
mi.transform.idt();
mi.transform.translate(posCache).rotate(rotCache).translate(tmpV);
mi.transform.rotate(tmpQ);
batch.render(mi, environment);
mi.transform.set(tmp);
}
}
if (additionalVisible) renderAdditional(batch, environment);
if ((hovered || selected) && !minimapMode) {
Gdx.gl.glEnable(GL20.GL_DEPTH_TEST);
Gdx.gl.glLineWidth(selected ? 3 : 2);
Vloxlands.shapeRenderer.setProjectionMatrix(Game.camera.combined);
Vloxlands.shapeRenderer.identity();
Vloxlands.shapeRenderer.translate(posCache.x, posCache.y - boundingBox.getDimensions().y / 2 + boundingBox.getCenter().y + World.gap, posCache.z);
Vloxlands.shapeRenderer.rotate(1, 0, 0, 90);
Vloxlands.shapeRenderer.begin(ShapeType.Line);
Vloxlands.shapeRenderer.setColor(World.SELECTION);
Vloxlands.shapeRenderer.rect(-(float) Math.ceil(boundingBox.getDimensions().x) / 2, -(float) Math.ceil(boundingBox.getDimensions().z) / 2, (float) Math.ceil(boundingBox.getDimensions().x), (float) Math.ceil(boundingBox.getDimensions().z));
Vloxlands.shapeRenderer.end();
Gdx.gl.glLineWidth(1);
}
if (Vloxlands.wireframe && !minimapMode) {
Gdx.gl.glEnable(GL20.GL_DEPTH_TEST);
Vloxlands.shapeRenderer.setProjectionMatrix(Game.camera.combined);
Vloxlands.shapeRenderer.identity();
Vloxlands.shapeRenderer.translate(posCache.x, posCache.y - boundingBox.getDimensions().y / 2 + boundingBox.getCenter().y, posCache.z);
Vloxlands.shapeRenderer.begin(ShapeType.Line);
Vloxlands.shapeRenderer.setColor(Color.RED);
Vloxlands.shapeRenderer.box(-boundingBox.getDimensions().x / 2, 0, boundingBox.getDimensions().z / 2, boundingBox.getDimensions().x, boundingBox.getDimensions().y, boundingBox.getDimensions().z);
Vloxlands.shapeRenderer.end();
}
}
public void renderAdditional(ModelBatch batch, Environment environment) {}
public void update(float delta) {
animationController.update(delta);
}
@Override
public void dispose() {
Game.instance.removeListener(this);
}
public void kill() {
markedForRemoval = true;
}
public boolean isSpawned() {
return spawned;
}
public boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
@Override
public boolean handleMessage(Telegram msg) {
return false;
}
@Override
public void save(ByteArrayOutputStream baos) throws IOException {
baos.write(id);
Bits.putMatrix4(baos, modelInstance.transform);
Bits.putInt(baos, level);
Bits.putBoolean(baos, visible);
Bits.putBoolean(baos, spawned);
Bits.putBoolean(baos, markedForRemoval);
}
public void setUI(PinnableWindow window, Object... params) {}
public void setActions(RevolverSlot parent) {}
public boolean intersects(Entity o) {
float lx = Math.abs(posCache.x - o.posCache.x);
float sumx = (dimensions.x / 2.0f) + (o.dimensions.x / 2.0f);
float ly = Math.abs(posCache.y - o.posCache.y);
float sumy = (dimensions.y / 2.0f) + (o.dimensions.y / 2.0f);
float lz = Math.abs(posCache.z - o.posCache.z);
float sumz = (dimensions.z / 2.0f) + (o.dimensions.z / 2.0f);
return (lx < sumx && ly < sumy && lz < sumz);
}
// -- events -- //
public void onSpawn() {
spawned = true;
}
// -- statics -- //
public static void loadEntities() {
CSVReader csv = new CSVReader(Gdx.files.internal("data/entities.csv"));
csv.readRow(); // headers
String cell;
Class<?> c = null;
boolean hasCell0 = false;
while ((cell = csv.readNext()) != null) {
if (cell.trim().length() == 0) continue;
try {
if (csv.getIndex() == 0) {
hasCell0 = true;
c = Class.forName("de.dakror.vloxlands.game.entity." + cell);
} else {
if (!hasCell0) continue;
byte b = (byte) Integer.parseInt(cell.trim());
idToClassMap.put(b, c);
classToIdMap.put(c, b);
hasCell0 = false;
}
} catch (Exception e) {
e.printStackTrace();
}
}
Gdx.app.log("Entity.loadEntities", idToClassMap.size() + " entities loaded.");
}
public static Entity getForId(byte id, float x, float y, float z) {
Class<?> c = idToClassMap.get(id);
if (c == null) {
Gdx.app.error("Entity.getForId", "No Entity found for id=" + id + "!");
return null;
}
try {
return (Entity) c.getConstructor(float.class, float.class, float.class).newInstance(x, y, z);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}