package graphics.model.rails;
import graphics.mesh.Mesh;
import engine.Map;
import static engine.Map.mapHeight;
import static engine.Map.mapWidth;
import java.nio.FloatBuffer;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;
import org.lwjgl.util.vector.Vector4f;
import static engine.Engine.MeshHandler;
import static graphics.Graphics.pointInFrustum;
import graphics.material.Material;
import graphics.model.Model;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Random;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
/**
*
* @author simokr
*/
public class Rails extends Model{
public Rails(){
super();
chunkList = new ArrayList<>();
chunksDrawn = 0;
}
public static final int NS_STRAIGHT = 0;
public static final int EW_STRAIGHT = 1;
public static final int NSEW_CROSS = 2;
public static final int SE_CURVE = 3;
public static final int SW_CURVE = 4;
public static final int NW_CURVE = 5;
public static final int NE_CURVE = 6;
public static final int NES_CROSS = 7;
public static final int ESW_CROSS = 8;
public static final int SWN_CROSS = 9;
public static final int WNE_CROSS = 10;
public static final int NS_STATION = 11;
public static final int EW_STATION = 12;
public static final int NES_STATION = 13;
public static final int ESW_STATION = 14;
public static final int SWN_STATION = 15;
public static final int WNE_STATION = 16;
public static final int TREE = 17;
public static final int UNKNOWN = 18;
private static final int cullingRadius = Map.chunkSize/2;
private ArrayList<Chunk> chunkList;
private static int chunksDrawn;
public boolean create(Map map){
ArrayList<Material> materialList = new ArrayList<>();
HashMap<String, Mesh> objMeshes = new HashMap<>();
objMeshes.put("res/mesh/track.obj", MeshHandler.get("res/mesh/track.obj"));
objMeshes.put("res/mesh/track_90_corner.obj", MeshHandler.get("res/mesh/track_90_corner.obj"));
objMeshes.put("res/mesh/track_x_cross.obj", MeshHandler.get("res/mesh/track_x_cross.obj"));
objMeshes.put("res/mesh/track_t_cross.obj", MeshHandler.get("res/mesh/track_t_cross.obj"));
objMeshes.put("res/mesh/station2.obj", MeshHandler.get("res/mesh/station2.obj"));
objMeshes.put("res/mesh/station2_bin.obj", MeshHandler.get("res/mesh/station2_bin.obj"));
objMeshes.put("res/mesh/station2_bench.obj", MeshHandler.get("res/mesh/station2_bench.obj"));
objMeshes.put("res/mesh/station2_map.obj", MeshHandler.get("res/mesh/station2_map.obj"));
objMeshes.put("res/mesh/cactus_trunk.obj", MeshHandler.get("res/mesh/cactus_trunk.obj"));
objMeshes.put("res/mesh/cactus_branch1.obj", MeshHandler.get("res/mesh/cactus_branch1.obj"));
objMeshes.put("res/mesh/cactus_branch2.obj", MeshHandler.get("res/mesh/cactus_branch2.obj"));
objMeshes.put("res/mesh/cactus_branch3.obj", MeshHandler.get("res/mesh/cactus_branch3.obj"));
for (Entry<String, Mesh> objMesh : objMeshes.entrySet()) {
materialList.add(objMesh.getValue().getDefaultMaterial());
}
/* Build a one compact material from all the meshes */
Material railMaterials = new Material(materialList);
this.material = railMaterials;
materialList = null;
int rowCount = mapHeight/Map.chunkSize;
int colCount = mapWidth/Map.chunkSize;
int totalFloatCount = 0;
for(int chunkY = 0; chunkY < rowCount; chunkY++) {
for(int chunkX = 0; chunkX < colCount; chunkX++) {
ArrayList<Float> buffer = this.createChunk(map, chunkX*Map.chunkSize, chunkY*Map.chunkSize, railMaterials, objMeshes);
if(buffer == null || buffer.size() < 1)
continue;
Chunk chunk = new Chunk(buffer);
if(chunk.isReady()){
this.chunkList.add(chunk);
totalFloatCount += buffer.size();
}
else{
System.err.println("Failed to create rail chunk ("+chunkX+","+chunkY+")");
}
}
}
for (Entry<String, Mesh> objMesh : objMeshes.entrySet()) {
MeshHandler.free(objMesh.getKey());
}
System.out.println("Created rails from map. "+this.chunkList.size()+" chunks with "+(totalFloatCount/9/3)+" triangles.");
return true;
}
private ArrayList<Float> createChunk(Map map, int xStart, int yStart, Material material, HashMap<String, Mesh> objMeshes){
Random rand = new Random();
ArrayList<Float> buffer = new ArrayList<>();
ArrayList<Float> posBuffer = new ArrayList<>();
ArrayList<Float> uvBuffer = new ArrayList<>();
ArrayList<Float> norBuffer = new ArrayList<>();
ArrayList<Float> idBuffer = new ArrayList<>();
Matrix4f transMat = new Matrix4f();
Matrix4f rotMat = new Matrix4f();
Vector4f translatedVertex = new Vector4f();
Vector4f rotatedNormal = new Vector4f();
float[] temp = new float[9];
for(int row = 0; row < Map.chunkSize; row++) {
for(int col = 0; col < Map.chunkSize; col++) {
int x = xStart+col;
int y = yStart+row;
if(map.get(y, x) == Map.EMPTY){
/* Nothing to be added*/
continue;
}
ArrayList<Mesh> msh = new ArrayList<>();
int scan[] = map.scan(y, x);
float mRotation = 0f;
/* Set transform matrix for this piece of track */
transMat.setIdentity();
rotMat.setIdentity();
/* Translate by x,z coordinates */
transMat.translate(new Vector3f(y, 0, x));
int trackType = getTrackType(map.get(y, x), scan, map.scanRideable(y, x));
/* Populate msh list with right .obj meshes */
switch(trackType){
case EW_STRAIGHT:
mRotation -= 90f;
rotMat.rotate((float) Math.toRadians(mRotation), new Vector3f(0, 1, 0));
case NS_STRAIGHT:
//msh.add(trackStraight);
msh.add(objMeshes.get("res/mesh/track.obj"));
break;
case NSEW_CROSS:
//msh.add(trackXCrossing);
msh.add(objMeshes.get("res/mesh/track_x_cross.obj"));
break;
case NE_CURVE:
mRotation -= 90f;
case NW_CURVE:
mRotation -= 90f;
case SW_CURVE:
mRotation -= 90f;
rotMat.rotate((float) Math.toRadians(mRotation), new Vector3f(0, 1, 0));
case SE_CURVE:
//msh.add(track90Corner);
msh.add(objMeshes.get("res/mesh/track_90_corner.obj"));
break;
case WNE_CROSS:
mRotation -= 90f;
case SWN_CROSS:
mRotation -= 90f;
case ESW_CROSS:
mRotation -= 90f;
rotMat.rotate((float) Math.toRadians(mRotation), new Vector3f(0, 1, 0));
case NES_CROSS:
//msh.add(trackTCrossing);
msh.add(objMeshes.get("res/mesh/track_t_cross.obj"));
break;
case EW_STATION:
mRotation -= 90f;
rotMat.rotate((float) Math.toRadians(mRotation), new Vector3f(0, 1, 0));
case NS_STATION:
//msh.add(trackStraight);
//msh.add(station);
msh.add(objMeshes.get("res/mesh/track.obj"));
msh.add(objMeshes.get("res/mesh/station2.obj"));
if(rand.nextFloat() > 0.5f){
//msh.add(station_bin);
msh.add(objMeshes.get("res/mesh/station2_bin.obj"));
}
if(rand.nextFloat() > 0.5f){
//msh.add(station_bench);
msh.add(objMeshes.get("res/mesh/station2_bench.obj"));
}
if(rand.nextFloat() > 0.75f){
//msh.add(station_map);
msh.add(objMeshes.get("res/mesh/station2_map.obj"));
}
break;
case WNE_STATION:
mRotation -= 90f;
case SWN_STATION:
mRotation -= 90f;
case ESW_STATION:
mRotation -= 90f;
rotMat.rotate((float) Math.toRadians(mRotation), new Vector3f(0, 1, 0));
case NES_STATION:
//msh.add(trackTCrossing);
//msh.add(station);
msh.add(objMeshes.get("res/mesh/track_t_cross.obj"));
msh.add(objMeshes.get("res/mesh/station2.obj"));
if(rand.nextFloat() > 0.5f){
//msh.add(station_bin);
msh.add(objMeshes.get("res/mesh/station2_bin.obj"));
}
if(rand.nextFloat() > 0.5f){
//msh.add(station_bench);
msh.add(objMeshes.get("res/mesh/station2_bench.obj"));
}
if(rand.nextFloat() > 0.75f){
//msh.add(station_map);
msh.add(objMeshes.get("res/mesh/station2_map.obj"));
}
break;
case TREE:
transMat.translate(new Vector3f(((float)Math.random()-0.5f)*0.8f, 0, ((float)Math.random()-0.5f)*0.8f));
rotMat.rotate((float) Math.toRadians(Math.random()*360.0), new Vector3f(0, 1, 0));
msh.add(objMeshes.get("res/mesh/cactus_trunk.obj"));
if(rand.nextFloat() > 0.15f){
msh.add(objMeshes.get("res/mesh/cactus_branch1.obj"));
}
if(rand.nextFloat() > 0.15f){
msh.add(objMeshes.get("res/mesh/cactus_branch2.obj"));
}
if(rand.nextFloat() > 0.15f){
msh.add(objMeshes.get("res/mesh/cactus_branch3.obj"));
}
break;
default:
//msh.add(trackStraight);
msh.add(objMeshes.get("res/mesh/track.obj"));
}
/* Stitch together the meshes of current grid */
for(int i=0; i<msh.size(); i++){
FloatBuffer data = msh.get(i).getData();
int indices = msh.get(i).getIndiceCount();
int keyframes = msh.get(i).getKeyframeCount();
int[] offsets = new int[4];
offsets[0] = 0; // always only the first keyframe
offsets[1] = 3*indices*keyframes;
offsets[2] = 2*indices+offsets[1];
offsets[3] = 3*indices*keyframes+offsets[2];
data.rewind();
for(int n=0;n<indices;n++){
data.position(offsets[0]);
data.get(temp, 0, 3);
data.position(offsets[1]);
data.get(temp, 3, 2);
data.position(offsets[2]);
data.get(temp, 5, 3);
data.position(offsets[3]);
data.get(temp, 8, 1);
offsets[0] += 3;
offsets[1] += 2;
offsets[2] += 3;
offsets[3]++;
/* XYZ */
Matrix4f.transform(rotMat, new Vector4f(temp[0], temp[1], temp[2], 1), translatedVertex);
Matrix4f.transform(transMat, translatedVertex, translatedVertex);
posBuffer.add(translatedVertex.x);
posBuffer.add(translatedVertex.y);
posBuffer.add(translatedVertex.z);
/* UV */
uvBuffer.add(temp[3]);
uvBuffer.add(temp[4]);
/* Normals */
Matrix4f.transform(rotMat, new Vector4f(temp[5], temp[6], temp[7], 0), rotatedNormal);
norBuffer.add(rotatedNormal.x);
norBuffer.add(rotatedNormal.y);
norBuffer.add(rotatedNormal.z);
Material meshMat = msh.get(i).getDefaultMaterial();
/* texture index defaults to 0 */
int texIndex = material.getTextureHandleIndex(meshMat.getTextureHandle((int)temp[8]));
texIndex = (texIndex > 0)?texIndex:0;
/* TexID */
idBuffer.add((float)texIndex);
}
}
}
}
buffer.addAll(posBuffer);
buffer.addAll(uvBuffer);
buffer.addAll(norBuffer);
buffer.addAll(idBuffer);
return buffer;
}
private int getTrackType(int thisTile, int[] scan, boolean[] scanR){
switch (thisTile){
/* Track tile */
case Map.TRACK:
if((scanR[0] || scanR[4])
&& (!scanR[2] && !scanR[6])) {
return EW_STRAIGHT;
}
else if((scanR[2] || scanR[6])
&& (!scanR[0] && !scanR[4])) {
return NS_STRAIGHT;
}
else if((scanR[0] || scanR[2])
&& (!scanR[4] && !scanR[6])) {
return SE_CURVE;
}
else if((scanR[2] || scanR[4])
&& (!scanR[6] && !scanR[0])) {
return SW_CURVE;
}
else if((scanR[4] || scanR[6])
&& (!scanR[0] && !scanR[2])) {
return NW_CURVE;
}
else if((scanR[6] || scanR[0])
&& (!scanR[2] && !scanR[4])) {
return NE_CURVE;
}
else if((scanR[2] && scanR[6] && scanR[0])
&& (!scanR[4])) {
return NES_CROSS;
}
else if((scanR[4] && scanR[2] && scanR[0])
&& (!scanR[6])) {
return ESW_CROSS;
}
else if((scanR[2] && scanR[4] && scanR[6])
&& (!scanR[0])) {
return SWN_CROSS;
}
else if((scanR[4] && scanR[6] && scanR[0])
&& (!scanR[2])) {
return WNE_CROSS;
}
else if((scanR[4] && scanR[6] && scanR[0] && scanR[2])) {
return NSEW_CROSS;
}
return UNKNOWN;
/* Station tile */
case Map.STATION:
if((scanR[0] || scanR[4])
&& (!scanR[2] && !scanR[6])) {
return EW_STATION;
}
else if((scanR[2] || scanR[6])
&& (!scanR[0] && !scanR[4])) {
return NS_STATION;
}
else if((scanR[0] || scanR[2])
&& (!scanR[4] && !scanR[6])) {
return SE_CURVE;
}
else if((scanR[2] || scanR[4])
&& (!scanR[6] && !scanR[0])) {
return SW_CURVE;
}
else if((scanR[4] || scanR[6])
&& (!scanR[0] && !scanR[2])) {
return NW_CURVE;
}
else if((scanR[6] || scanR[0])
&& (!scanR[2] && !scanR[4])) {
return NE_CURVE;
}
else if((scanR[2] && scanR[6] && scanR[0])
&& (!scanR[4])) {
return NES_STATION;
}
else if((scanR[4] && scanR[2] && scanR[0])
&& (!scanR[6])) {
return ESW_STATION;
}
else if((scanR[2] && scanR[4] && scanR[6])
&& (!scanR[0])) {
return SWN_STATION;
}
else if((scanR[4] && scanR[6] && scanR[0])
&& (!scanR[2])) {
return WNE_STATION;
}
else if((scanR[4] && scanR[6] && scanR[0] && scanR[2])) {
return NSEW_CROSS;
}
return UNKNOWN;
case Map.TREE:
return TREE;
/* Unkown tile type */
default:
return UNKNOWN;
}
}
@Override
protected boolean preRender(){
if(this.chunkList == null || this.chunkList.isEmpty()|| this.shader == null || !this.shader.isReady())
return false;
this.updateModelMatrix();
this.shader.setModelMatrix(this.modelMatrix);
return true;
}
@Override
public void render(){
if(!this.preRender())
return;
this.shader.enable();
this.material.bindTextures();
chunksDrawn = 0;
for (Chunk chunk : this.chunkList) {
chunk.render();
}
//System.out.println("Chunks on screen: "+chunksDrawn);
this.material.unbindTextures();
this.shader.disable();
}
@Override
public void free(){
for (Chunk chunk : this.chunkList) {
chunk.free();
}
}
private class Chunk{
private Mesh mesh;
Vector3f corner[];
public Chunk(ArrayList<Float> data){
mesh = new Mesh();
mesh.create(data, 9, GL_STATIC_DRAW, false);
corner = new Vector3f[9];
Vector3f topLeftFrontCorner = new Vector3f();
Vector3f bottomRightBackCorner = new Vector3f();
topLeftFrontCorner.x = data.get(0);
bottomRightBackCorner.y = data.get(1);
bottomRightBackCorner.z = data.get(2);
int indices = mesh.getIndiceCount()*3;
for (int i = 0; i < indices; i=i+3) {
topLeftFrontCorner.x = Math.min(data.get(i), topLeftFrontCorner.x);
topLeftFrontCorner.y = Math.max(data.get(i+1), topLeftFrontCorner.y);
topLeftFrontCorner.z = Math.max(data.get(i+2), topLeftFrontCorner.z);
bottomRightBackCorner.x = Math.max(data.get(i), bottomRightBackCorner.x);
bottomRightBackCorner.y = Math.min(data.get(i+1), bottomRightBackCorner.y);
bottomRightBackCorner.z = Math.min(data.get(i+2), bottomRightBackCorner.z);
}
corner[0] = new Vector3f(topLeftFrontCorner);
corner[1] = new Vector3f(topLeftFrontCorner.x, topLeftFrontCorner.y, bottomRightBackCorner.z);
corner[2] = new Vector3f(bottomRightBackCorner.x, topLeftFrontCorner.y, bottomRightBackCorner.z);
corner[3] = new Vector3f(bottomRightBackCorner.x, topLeftFrontCorner.y, topLeftFrontCorner.z);
corner[4] = new Vector3f(bottomRightBackCorner);
corner[5] = new Vector3f(topLeftFrontCorner.x, bottomRightBackCorner.y, bottomRightBackCorner.z);
corner[6] = new Vector3f(bottomRightBackCorner.x, bottomRightBackCorner.y, bottomRightBackCorner.z);
corner[7] = new Vector3f(bottomRightBackCorner.x, bottomRightBackCorner.y, topLeftFrontCorner.z);
corner[8] = new Vector3f((bottomRightBackCorner.x+topLeftFrontCorner.x)/2f,
(bottomRightBackCorner.y+topLeftFrontCorner.y)/2f,
(bottomRightBackCorner.z+topLeftFrontCorner.z)/2f
);
}
public boolean isReady(){
return this.mesh.isReady();
}
public boolean inFrustrum(){
//return pointInFrustum(this.corner[8]) || pointInFrustum(this.corner[0]) || pointInFrustum(this.corner[1]) || pointInFrustum(this.corner[2]) || pointInFrustum(this.corner[3])
// || pointInFrustum(this.corner[4]) || pointInFrustum(this.corner[5]) || pointInFrustum(this.corner[6]) || pointInFrustum(this.corner[7]);
return pointInFrustum(this.corner[8], cullingRadius);
}
public void free(){
this.mesh.free();
}
public void render(){
if(!this.mesh.isReady() || !this.inFrustrum())
return;
chunksDrawn++;
this.mesh.render();
}
}
}