package com.vitco.core.world;
import com.threed.jpct.SimpleVector;
import com.vitco.core.data.container.Voxel;
import com.vitco.core.world.container.BorderObject3D;
import com.vitco.core.world.container.VoxelManager;
import com.vitco.low.hull.HullManagerExt;
import com.vitco.low.triangulate.Grid2TriPolyFast;
import com.vitco.low.triangulate.util.Grid2PolyHelper;
import com.vitco.settings.VitcoSettings;
import com.vitco.util.graphic.SharedImageFactory;
import org.poly2tri.Poly2Tri;
import org.poly2tri.triangulation.delaunay.DelaunayTriangle;
import java.awt.*;
import java.util.*;
/**
* This is a world wrapper that provides easy voxel interaction
* (allows for adding and removing of voxels)
*/
public class CWorld extends AbstractCWorld {
private static final long serialVersionUID = 1L;
// constructor
public CWorld(boolean culling, Integer side, boolean simpleMode) {
super(culling, side, simpleMode);
}
// ----------------
// static constructor
static {
// jvm will convert this to native code
// => so there is no lag later on
Poly2Tri.warmup();
// initialize the buffered images (for textures)
for (int e1 = 2; e1 <= 4; e1++) { // from 4 (=2^2) to 16 (=2^4)
int d1 = (int)Math.pow(2, e1);
for (int e2 = 2; e2 <= 4; e2++) { // from 4 (=2^2) to 16 (=2^4)
int d2 = (int)Math.pow(2, e2);
SharedImageFactory.getBufferedImage(d1, d2);
}
}
for (int e1 = 7; e1 <= 9; e1++) { // from 128 (=2^7) to 512 (=2^9)
int d1 = (int)Math.pow(2, e1);
for (int e2 = 7; e2 <= 9; e2++) { // from 128 (=2^7) to 512 (=2^9)
int d2 = (int)Math.pow(2, e2);
SharedImageFactory.getBufferedImage(d1, d2);
}
}
}
// manages the voxel "hull" (allows for easy querying of hull changes)
private final HullManagerExt<Voxel> hullManager = new HullManagerExt<Voxel>();
// manages the voxels that are in this world, allows for easy detection
// of changed areas (combined faces of neighbouring voxels)
private final VoxelManager voxelManager = new VoxelManager(hullManager, side);
// true if the world needs a clear (this flag is necessary to do the
// clearing in sync with the rendering)
private boolean worldNeedsClear = false;
// add or update a voxel
@Override
public void updateVoxel(Voxel voxel) {
hullManager.update(voxel.posId, voxel);
}
// erase the entire content of this world
@Override
public void clear() {
// the hull manager needs to be cleared here (since it is not in sync
// with the rendering thread)
hullManager.clear();
// this flag is necessary to do the clearing in sync with the rendering
worldNeedsClear = true;
}
// clear field by voxel
@Override
public boolean clearPosition(Voxel voxel) {
return hullManager.clearPosition(voxel.posId);
}
// ====================================
// used to retrieve which world objects belong to which side (0-5, i.e. direction)
private final HashMap<Integer, Integer> worldId2Side = new HashMap<Integer, Integer>();
// stores side/plane/area combination to object world id
private final HashMap<String, Integer> plane2WorldId = new HashMap<String, Integer>();
// enable/disable the border on all objects in the world (main view)
private boolean hasBorder = true;
@Override
public final void setBorder(boolean border) {
hasBorder = border;
for (Integer worldId : worldId2Side.keySet()) {
((BorderObject3D)this.getObject(worldId)).setBorder(border);
}
}
// the maximum amount of areas that are drawn in one call
private final static int maxAreaDraw = 10;
// handle all planes (in one direction, determined by side)
// returns true iff all areas are handled
private boolean handleOrientedPlane(int orientation) {
int axis = orientation/2;
// processed entries are cleaned here in this function (!)
HashMap<Integer, HashMap<Point, Boolean>> outdatedPlanes = voxelManager.getInvalidPlanes(orientation);
int progressCounter = 0;
for (Iterator<Map.Entry<Integer, HashMap<Point, Boolean>>> planeIterator = outdatedPlanes.entrySet().iterator(); planeIterator.hasNext() && progressCounter < maxAreaDraw;) {
Map.Entry<Integer, HashMap<Point, Boolean>> entry = planeIterator.next();
Integer outdatedPlane = entry.getKey();
HashMap<Point, Boolean> outdatedAreas = entry.getValue();
// loop over all outdated areas
for (Iterator<Map.Entry<Point,Boolean>> areaIterator = outdatedAreas.entrySet().iterator(); areaIterator.hasNext() && progressCounter < maxAreaDraw; progressCounter++) {
Map.Entry<Point,Boolean> outdatedAreaEntry = areaIterator.next();
Point outdatedArea = outdatedAreaEntry.getKey();
Boolean fullRefresh = outdatedAreaEntry.getValue();
// id for this particular area
String areaKey = orientation + "_" + outdatedPlane + "_" + outdatedArea.x + "_" + outdatedArea.y;
if (fullRefresh) { // full refresh (recreate triangulation)
// handle the triangle building
Collection<Voxel> faceList = voxelManager.getFaces(orientation, outdatedPlane, outdatedArea);
if (faceList != null) {
// this should never happen as the faceManager deletes unused faceLists
assert !faceList.isEmpty();
// determine size of rect that contains all voxel faces
boolean first = true;
int min1 = 0;
int max1 = 0;
int min2 = 0;
int max2 = 0;
for (Voxel face : faceList) {
int[] pos2D = VoxelManager.convert3D2D(face, axis);
if (first) {
min1 = pos2D[0];
max1 = pos2D[0];
min2 = pos2D[1];
max2 = pos2D[1];
first = false;
} else {
min1 = Math.min(min1,pos2D[0]);
max1 = Math.max(max1, pos2D[0]);
min2 = Math.min(min2,pos2D[1]);
max2 = Math.max(max2, pos2D[1]);
}
}
int w = max1 - min1 + 1;
int h = max2 - min2 + 1;
// --------------
ArrayList<DelaunayTriangle> tris = new ArrayList<DelaunayTriangle>();
boolean[][] data = new boolean[w][h];
for (Voxel face : faceList) {
int[] pos2D = VoxelManager.convert3D2D(face, axis);
data[pos2D[0] - min1][pos2D[1] - min2] = true;
// // consider textured faces separately (needed?)
// if (face.getTexture() == null) {
// data[pos2D[0] - min1][pos2D[1] - min2] = true;
// } else {
// int pX = pos2D[0] - min1;
// int pY = pos2D[1] - min2;
// // add the textured faces separately
// tris.add(new DelaunayTriangle(new PolygonPoint(pX, pY), new PolygonPoint(pX + 1, pY), new PolygonPoint(pX, pY + 1)));
// tris.add(new DelaunayTriangle(new PolygonPoint(pX + 1, pY), new PolygonPoint(pX + 1, pY + 1), new PolygonPoint(pX, pY + 1)));
// }
}
tris.addAll(Grid2TriPolyFast.triangulate(Grid2PolyHelper.convert(data)));
// tris.addAll(Grid2TriGreedyOptimal.triangulate(data));
// --------------
// todo: remove
// // build image to compute triangle overlay
// TiledImage src = SharedImageFactory.getTiledImage(w, h);
// for (Voxel face : faceList) {
// int[] pos2D = VoxelManager.convert3D2D(face, axis);
// src.setSample(pos2D[0] - min1, pos2D[1] - min2, 0, 1);
// }
// // triangulate the image
// ArrayList<DelaunayTriangle> tris = Grid2Tri.triangulate(Grid2Tri.doVectorize(src));
// // reset image
// for (Voxel face : faceList) {
// int[] pos2D = VoxelManager.convert3D2D(face, axis);
// src.setSample(pos2D[0] - min1, pos2D[1] - min2, 0, 0);
// }
// // --------------
// build the plane
BorderObject3D box = new BorderObject3D(
tris, faceList,
min1, min2, w, h, orientation, axis,
outdatedPlane, simpleMode, side, culling,
hasBorder, hullManager
);
// remove old version of this side (if exists)
Integer oldId = plane2WorldId.get(areaKey);
if (oldId != null) {
// only remove texture in non-wireframe world
if (!simpleMode) {
BorderObject3D obj = (BorderObject3D) getObject(oldId);
// remove other information
removeObject(oldId);
obj.freeTexture();
} else {
// remove other information
removeObject(oldId);
}
worldId2Side.remove(oldId);
}
// add new plane
int newWorldId = addObject(box);
plane2WorldId.put(areaKey, newWorldId);
worldId2Side.put(newWorldId, orientation);
} else {
// remove old version of this side (if exists)
Integer oldId = plane2WorldId.remove(areaKey);
if (oldId != null) {
// only remove texture in non-wireframe world
if (!simpleMode) {
BorderObject3D obj = (BorderObject3D) getObject(oldId);
// remove other information
removeObject(oldId);
obj.freeTexture();
} else {
// remove other information
removeObject(oldId);
}
worldId2Side.remove(oldId);
}
}
} else if (!simpleMode) {
// only do texture refresh (soft)
Integer objId = plane2WorldId.get(areaKey);
if (objId != null) {
((BorderObject3D) getObject(objId)).refreshTextureInterpolation();
}
}
// this area was processed
areaIterator.remove();
}
// clear this plane entry if all areas are processed
if (outdatedAreas.isEmpty()) {
planeIterator.remove();
}
}
//faceManager.clearInvalidAreas(orientation);
return progressCounter < maxAreaDraw;
}
// refresh world (partially) - returns true if fully refreshed
@Override
public boolean refreshWorld() {
// if this counter is six, the world is ready
int ready = 0;
// clear the voxel manager if necessary (needs to be done in sync!)
if (worldNeedsClear) {
worldNeedsClear = false;
// clear the voxel manager
voxelManager.clear();
// remove world objects
for (Integer objId : worldId2Side.keySet()) {
// only remove texture in non-wireframe world
if (!simpleMode) {
BorderObject3D obj = (BorderObject3D) getObject(objId);
// remove other information
removeObject(objId);
obj.freeTexture();
} else {
// remove other information
removeObject(objId);
}
}
worldId2Side.clear();
plane2WorldId.clear();
}
// handle the updating
if (side == -1) {
for (int i = 0; i < 6; i++) {
for (Voxel voxel : hullManager.getHullAdditions(i)) {
voxelManager.addFace(i, voxel);
}
for (Voxel voxel : hullManager.getHullRemovals(i)) {
voxelManager.removeFace(i, voxel);
}
if (handleOrientedPlane(i)) {
ready++;
}
}
} else {
int orientation = side == 0 ? 5 : (side == 1 ? 3 : 1);
for (Voxel voxel : hullManager.getHullAdditions(orientation)) {
voxelManager.addFace(orientation, voxel);
}
for (Voxel voxel : hullManager.getHullRemovals(orientation)) {
voxelManager.removeFace(orientation, voxel);
}
if (handleOrientedPlane(orientation)) {
ready = 6;
}
}
return ready == 6;
}
// get voxel by hit position
@Override
public final int[] getVoxelPos(Integer objectId, float posx, float posy, float posz) {
Integer side = worldId2Side.get(objectId);
int[] result = null;
if (side != null) {
Integer axis = side/2;
result = new int[] {
Math.round((posx/VitcoSettings.VOXEL_SIZE) + (axis == 0 ? (side == 0 ? -0.5f : 0.5f) : 0)),
Math.round((posy/VitcoSettings.VOXEL_SIZE) + (axis == 1 ? (side == 2 ? -0.5f : 0.5f) : 0)),
Math.round((posz/VitcoSettings.VOXEL_SIZE) + (axis == 2 ? (side == 4 ? -0.5f : 0.5f) : 0))
};
}
return result;
}
// get side for world object
@Override
public Integer getSide(Integer objectId) {
return worldId2Side.get(objectId);
}
// do a hit test against the voxels in this world
@Override
public short[] hitTest(SimpleVector position, SimpleVector dir) {
return hullManager.hitTest(position, dir);
}
}