package com.vitco.core.world.container;
import com.threed.jpct.Object3D;
import com.threed.jpct.PolygonManager;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.TextureInfo;
import com.vitco.core.data.container.Voxel;
import com.vitco.low.hull.HullManager;
import com.vitco.settings.VitcoSettings;
import com.vitco.util.misc.ConversionTools;
import org.poly2tri.triangulation.delaunay.DelaunayTriangle;
import org.poly2tri.triangulation.point.TPoint;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
/**
* A special Object3D plane that has a border that can be enabled/disabled.
*/
public class BorderObject3D extends Object3D {
private static final long serialVersionUID = 1L;
// get the rounded points of a triangle as an int array
private static int[] getRoundedPoints(DelaunayTriangle triangle) {
// rounding is needed since the triangulation can result in non integers
return new int[] {
Math.round(triangle.points[0].getXf()),
Math.round(triangle.points[0].getYf()),
Math.round(triangle.points[1].getXf()),
Math.round(triangle.points[1].getYf()),
Math.round(triangle.points[2].getXf()),
Math.round(triangle.points[2].getYf())
};
}
// get the center direction for a triangle described by points and a center
// true indicates that the point axis is greater than the center
private static boolean[] getCenterDirection(int[] trianglePoints, float centerX, float centerY) {
boolean[] centerDir = new boolean[6];
// check two at a time (x and y)
for (int i = 0; i < 6; i+=2) {
centerDir[i] = (trianglePoints[i] - centerX > 0);
centerDir[i+1] = (trianglePoints[i+1] - centerY > 0);
}
return centerDir;
}
// returns the outside directions for those points that are "outside"
private static byte[] getOutsideDirections(
HashSet<Point> seenTrianglePoints, DelaunayTriangle triangle, int[] roundedTrianglePoints) {
TPoint triangle_center = triangle.centroid();
boolean[] centerDirection =
getCenterDirection(roundedTrianglePoints, triangle_center.getXf(), triangle_center.getYf());
byte[] outsideDirection = new byte[6];
for (int i = 0; i < 3; i++) {
int x = i*2;
int y = x+1;
// how the check is done
// [o] [] | [] [o] | [] [] | [] []
// X | X | X | X
// [] [] | [] [] | [o] [] | [] [o]
if (!seenTrianglePoints.contains(new Point((roundedTrianglePoints[x] - 1), (roundedTrianglePoints[y] - 1)))) {
outsideDirection[x] -= 1;
outsideDirection[y] -= 1;
}
if (!seenTrianglePoints.contains(new Point((roundedTrianglePoints[x]), (roundedTrianglePoints[y] - 1)))) {
outsideDirection[x] += 1;
outsideDirection[y] -= 1;
}
if (!seenTrianglePoints.contains(new Point((roundedTrianglePoints[x] - 1), (roundedTrianglePoints[y])))) {
outsideDirection[x] -= 1;
outsideDirection[y] += 1;
}
if (!seenTrianglePoints.contains(new Point((roundedTrianglePoints[x]), (roundedTrianglePoints[y])))) {
outsideDirection[x] += 1;
outsideDirection[y] += 1;
}
// also adjust for center of the triangle
// Note: boolean center direction will result in less noticeable edges
// (opposed to having or rounded values)
outsideDirection[x] += centerDirection[x]?1:-1;
outsideDirection[y] += centerDirection[y]?1:-1;
// normalize
outsideDirection[x] = (byte) Math.signum(outsideDirection[x]);
outsideDirection[y] = (byte) Math.signum(outsideDirection[y]);
}
return outsideDirection;
}
// the object that is used to refresh the texture of this 3D Object
private TextureObject textureObject;
// refresh the texture of this object
public final void refreshTextureInterpolation() {
assert textureObject != null;
textureObject.refreshTexture(null, this);
}
// called when this object is no longer needed
public final void freeTexture() {
assert textureObject != null;
textureObject.destroy();
}
// generate final interpolated points (to prevent see-through)
private static SimpleVector[] getTrianglePointsInterpolated(Integer axis, Integer plane,
float move, int[] roundedTrianglePoints,
int minx, int miny, byte[] outsideDirection) {
SimpleVector[] triangle = new SimpleVector[3];
for (int i = 0; i < 3; i ++) {
int x = i*2;
int y = x+1;
// only interpolates if this is a corner
float interpolationX = -outsideDirection[x]*VitcoSettings.TRIANGLE_INTERPOLATION_VALUE;
float interpolationY = -outsideDirection[y]*VitcoSettings.TRIANGLE_INTERPOLATION_VALUE;
// generate the point
triangle[i] = new SimpleVector(
(axis == 0 ? plane + move : roundedTrianglePoints[x] + minx - 0.5f - interpolationX) * VitcoSettings.VOXEL_SIZE,
(axis == 1 ? plane + move : (axis == 0 ? roundedTrianglePoints[x] + minx - 0.5f - interpolationX
: roundedTrianglePoints[y] + miny - 0.5f - interpolationY)) * VitcoSettings.VOXEL_SIZE,
(axis == 2 ? plane + move : roundedTrianglePoints[y] + miny - 0.5f - interpolationY) * VitcoSettings.VOXEL_SIZE
);
}
return triangle;
}
// generate final interpolated points (to prevent see-through)
private static SimpleVector[] getTrianglePoints(Integer axis, Integer plane,
float move, int[] roundedTrianglePoints,
int minx, int miny) {
SimpleVector[] triangle = new SimpleVector[3];
for (int i = 0; i < 3; i ++) {
int x = i*2;
int y = x+1;
// generate the point
triangle[i] = new SimpleVector(
(axis == 0 ? plane + move : roundedTrianglePoints[x] + minx - 0.5f) * VitcoSettings.VOXEL_SIZE,
(axis == 1 ? plane + move : (axis == 0 ? roundedTrianglePoints[x] + minx - 0.5f
: roundedTrianglePoints[y] + miny - 0.5f)) * VitcoSettings.VOXEL_SIZE,
(axis == 2 ? plane + move : roundedTrianglePoints[y] + miny - 0.5f) * VitcoSettings.VOXEL_SIZE
);
}
return triangle;
}
// generate triangles and use adjustable edge interpolation
private void generateAdvanced(ArrayList<DelaunayTriangle> triangleList, Collection<Voxel> faceList,
int minx, int miny, int w, int h, Integer orientation, Integer axis,
Integer plane, int side, HullManager<Voxel> hullManager) {
canHaveBorder = side == -1;
textureSizeX = ConversionTools.getTextureSize(w);
textureSizeY = ConversionTools.getTextureSize(h);
// todo: mirror textures if w > h to reduce used memory (!)
// contains seen pixel
HashSet<Point> seenTrianglePoints = new HashSet<Point>();
// generate textureObject
textureObject = new TextureObject(
minx, miny, faceList, orientation,
axis, hullManager, w, h, textureSizeX, textureSizeY
);
// generate the texture (and remember seen triangle points)
textureObject.refreshTexture(seenTrianglePoints, this);
// the texture id
int textureId = textureObject.getTextureId();
// compute values for inversion
int sx0 = 0;
int sx1 = 1;
int x0 = 0;
int y0 = 1;
int x1 = 2;
int y1 = 3;
if (inverted) {
sx0 = 1;
sx1 = 0;
x0 = 2;
y0 = 3;
x1 = 0;
y1 = 1;
}
float move = orientation%2 == 0 ? 0.5f : -0.5f;
// iterate over triangles
for (DelaunayTriangle tri : triangleList) {
// get the rounded points
int[] roundedTrianglePoints = getRoundedPoints(tri);
// get the outside direction
byte[] outside_direction = getOutsideDirections(seenTrianglePoints, tri, roundedTrianglePoints);
// the the interpolated points
SimpleVector[] interpTrianglePoints = getTrianglePointsInterpolated(axis, plane, move, roundedTrianglePoints, minx, miny, outside_direction);
// get the appropriate interpolation for the texture
float textureInterpolation = canHaveBorder && hasBorder
? VitcoSettings.BORDER_INSET_VALUE
: -VitcoSettings.TEXTURE_INTERPOLATION_VALUE;
// add the triangle to this object
this.addTriangle(
interpTrianglePoints[sx0],
(roundedTrianglePoints[x0] + outside_direction[x0]*textureInterpolation + 1)/textureSizeX,
(roundedTrianglePoints[y0] + outside_direction[y0]*textureInterpolation + 1)/textureSizeY,
interpTrianglePoints[sx1],
(roundedTrianglePoints[x1] + outside_direction[x1]*textureInterpolation + 1)/textureSizeX,
(roundedTrianglePoints[y1] + outside_direction[y1]*textureInterpolation + 1)/textureSizeY,
interpTrianglePoints[2],
(roundedTrianglePoints[4] + outside_direction[4]*textureInterpolation + 1)/textureSizeX,
(roundedTrianglePoints[5] + outside_direction[5]*textureInterpolation + 1)/textureSizeY,
textureId
);
if (canHaveBorder) {
// memorize the uv positions
uvPositions.add(new float[] {
roundedTrianglePoints[x0] + 1, outside_direction[x0],
roundedTrianglePoints[y0] + 1, outside_direction[y0],
roundedTrianglePoints[x1] + 1, outside_direction[x1],
roundedTrianglePoints[y1] + 1, outside_direction[y1],
roundedTrianglePoints[4] + 1, outside_direction[4],
roundedTrianglePoints[5] + 1, outside_direction[5]
});
}
}
// set the additional color
this.setAdditionalColor(Color.WHITE);
}
// generate triangles without any interpolation
private void generateSimple(ArrayList<DelaunayTriangle> triangleList, int minx, int miny,
Integer orientation, Integer axis, Integer plane) {
float move = orientation%2 == 0 ? 0.5f : -0.5f;
for (DelaunayTriangle triangle : triangleList) {
int[] roundedTrianglePoints = getRoundedPoints(triangle);
SimpleVector[] simpleVectors = getTrianglePoints(axis, plane, move, roundedTrianglePoints, minx, miny);
this.addTriangle(simpleVectors[inverted?1:0],simpleVectors[inverted?0:1],simpleVectors[2]);
}
}
// ------------------------------------
// true if this plane needs to be inverted for culling)
private final boolean inverted;
// true if this plane can have a border
private boolean canHaveBorder = false;
// true if this plane has a border
private boolean hasBorder = true;
// contains the textureSize for the object and textureSize/32 for objects
// that contain images in their textures
private int textureSizeX = 0;
private int textureSizeY = 0;
// the uv positions of the triangles of this object and the interpolation directions
private final ArrayList<float[]> uvPositions = new ArrayList<float[]>();
// the polygon manager of this object
private final transient PolygonManager polygonManager = this.getPolygonManager();
// set the border state of this object (black outline of the edges)
// IMPORTANT: This only works because we're using the software renderer(!!!)
public final void setBorder(boolean border) {
if (canHaveBorder && hasBorder != border) {
hasBorder = border;
float textureInterpolation = hasBorder
? VitcoSettings.BORDER_INSET_VALUE
: -VitcoSettings.TEXTURE_INTERPOLATION_VALUE;
int textureId = textureObject.getTextureId();
for (int i = 0, size = uvPositions.size(); i < size; i++) {
float[] uvInfo = uvPositions.get(i);
polygonManager.setPolygonTexture(i, new TextureInfo(textureId,
(uvInfo[0] + uvInfo[1]*textureInterpolation)/textureSizeX,
(uvInfo[2] + uvInfo[3]*textureInterpolation)/textureSizeY,
(uvInfo[4] + uvInfo[5]*textureInterpolation)/textureSizeX,
(uvInfo[6] + uvInfo[7]*textureInterpolation)/textureSizeY,
(uvInfo[8] + uvInfo[9]*textureInterpolation)/textureSizeX,
(uvInfo[10] + uvInfo[11]*textureInterpolation)/textureSizeY
));
}
}
}
// ------------------------------------
// constructor
public BorderObject3D(ArrayList<DelaunayTriangle> tris, Collection<Voxel> faceList,
int minx, int miny, int w, int h, Integer orientation, Integer axis,
Integer plane, boolean simpleMode, int side,
boolean culling, boolean hasBorder, HullManager<Voxel> hullManager) {
// construct this object 3D with correct triangle count
super(tris.size());
// compute inversion and store border state
this.inverted = orientation%2 == (orientation/2 == 1 ? 0 : 1);
this.hasBorder = hasBorder;
if (simpleMode) {
// generate triangles without any interpolation
generateSimple(tris, minx, miny, orientation, axis, plane);
} else {
// generate triangles and use adjustable edge interpolation
generateAdvanced(tris, faceList, minx, miny, w, h, orientation, axis,
plane, side, hullManager);
}
// set the correct culling
this.setCulling(culling);
// enable collision checking (needed for both - simpleMode or not)
this.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);
this.setCollisionOptimization(Object3D.COLLISION_DETECTION_OPTIMIZED);
// shift to zero if this is a side view (for true orthogonal view)
this.calcCenter();
this.translate(
side == 2 ? -this.getCenter().x : 0,
side == 1 ? -this.getCenter().y : 0,
side == 0 ? -this.getCenter().z : 0
);
// not really needed since we're using textures for everything
//this.setShadingMode(Object3D.SHADING_FAKED_FLAT);
// build this object
this.build();
}
}