chunkrenderer: generate only one mesh per chunk
parent
61838a93b2
commit
f8a31093cd
|
@ -1,8 +1,10 @@
|
|||
package com.emamaker.voxeltest.intervaltrees.renderer;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
import com.emamaker.voxeltest.intervaltrees.data.IntervalTree;
|
||||
|
@ -16,9 +18,9 @@ import com.jme3.math.ColorRGBA;
|
|||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.scene.shape.Box;
|
||||
import com.jme3.scene.shape.Sphere;
|
||||
import com.jme3.util.BufferUtils;
|
||||
|
||||
public class ChunkRenderer {
|
||||
|
@ -28,7 +30,7 @@ public class ChunkRenderer {
|
|||
// Breadth-first visit each node of the tree
|
||||
chunk.blocks.print();
|
||||
|
||||
Queue<IntervalTree<Blocks>.TreeNode> queue = new ArrayDeque();
|
||||
Queue<IntervalTree<Blocks>.TreeNode> queue = new ArrayDeque<>();
|
||||
queue.add(chunk.blocks.getRoot());
|
||||
|
||||
IntervalTree<Blocks>.TreeNode t;
|
||||
|
@ -39,16 +41,20 @@ public class ChunkRenderer {
|
|||
|
||||
while (!queue.isEmpty()) {
|
||||
t = queue.poll();
|
||||
if (t.getLeft() != null) queue.add(t.getLeft());
|
||||
if (t.getRight() != null) queue.add(t.getRight());
|
||||
if (t.getLeft() != null)
|
||||
queue.add(t.getLeft());
|
||||
if (t.getRight() != null)
|
||||
queue.add(t.getRight());
|
||||
|
||||
if (!t.getValue().equals(Blocks.AIR)) {
|
||||
|
||||
int start = t.getInterval().getLow();
|
||||
for (int i = t.getInterval().getLow(); i <= t.getInterval().getHigh(); i++) {
|
||||
|
||||
if ((i % Config.CHUNK_SIZE == 0 && i != t.getInterval().getLow()) || i == t.getInterval().getHigh()) {
|
||||
if (i == t.getInterval().getHigh()) i++;
|
||||
if ((i % Config.CHUNK_SIZE == 0 && i != t.getInterval().getLow())
|
||||
|| i == t.getInterval().getHigh()) {
|
||||
if (i == t.getInterval().getHigh())
|
||||
i++;
|
||||
b = new Box((i - start) * 0.5f, 0.5f, 0.5f);
|
||||
|
||||
geom = new Geometry("Box", b);
|
||||
|
@ -59,8 +65,10 @@ public class ChunkRenderer {
|
|||
coords1 = Chunk.coord1DTo3D(start);
|
||||
coords2 = Chunk.coord1DTo3D(i);
|
||||
|
||||
geom.getLocalTransform().setTranslation(coords1[0] + b.xExtent, coords1[1] + b.yExtent, coords1[2] + b.zExtent);
|
||||
System.out.println(Arrays.toString(coords1) + "-" + Arrays.toString(coords2) + "\t\tBox with extext " + b.xExtent + " (both directions) with center"
|
||||
geom.getLocalTransform().setTranslation(coords1[0] + b.xExtent, coords1[1] + b.yExtent,
|
||||
coords1[2] + b.zExtent);
|
||||
System.out.println(Arrays.toString(coords1) + "-" + Arrays.toString(coords2)
|
||||
+ "\t\tBox with extext " + b.xExtent + " (both directions) with center"
|
||||
+ geom.getLocalTransform().getTranslation());
|
||||
|
||||
chunk.chunkNode.attachChild(geom);
|
||||
|
@ -81,7 +89,6 @@ public class ChunkRenderer {
|
|||
// + Arrays.toString(coords));
|
||||
|
||||
}
|
||||
if (!mgr.game.getRootNode().hasChild(chunk.chunkNode)) mgr.game.getRootNode().attachChild(chunk.chunkNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,14 +119,13 @@ public class ChunkRenderer {
|
|||
} else if (blocks[idx] == Blocks.GRASS) {
|
||||
mat.setColor("Color", ColorRGBA.Green);
|
||||
}
|
||||
// else {
|
||||
// mat.setColor("Color", ColorRGBA.Red);
|
||||
// else {
|
||||
// mat.setColor("Color", ColorRGBA.Red);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -136,14 +142,35 @@ public class ChunkRenderer {
|
|||
* across different planes everytime I change dimension without having to
|
||||
* write 3 separate 3-nested-for-loops
|
||||
*/
|
||||
/*
|
||||
* It's not feasible to just create a new mesh everytime a quad needs placing.
|
||||
* This ends up creating TONS of meshes the game with just lag out.
|
||||
* As I did in the past, it's better to create a single mesh for each chunk,
|
||||
* containing all the quads. In the future, maybe traslucent blocks and liquids
|
||||
* will need a separate mesh, but still on a per-chunk basis
|
||||
*/
|
||||
Vector3f[] vertices = new Vector3f[Config.CHUNK_3DCOORD_MAX_INDEX + 1];
|
||||
float[] colors = new float[(Config.CHUNK_3DCOORD_MAX_INDEX + 1) * 4];
|
||||
short[] indices = new short[(Config.CHUNK_3DCOORD_MAX_INDEX + 1) * 4];
|
||||
|
||||
short vIndex = 0;
|
||||
short iIndex = 0;
|
||||
short cIndex = 0;
|
||||
|
||||
public void greedyMeshing(WorldManager mgr, Chunk chunk) {
|
||||
HashMap<Vector3f, Sphere> points = new HashMap<>();
|
||||
vIndex = 0;
|
||||
iIndex = 0;
|
||||
cIndex = 0;
|
||||
|
||||
// convert tree to array since it is easier to work with it
|
||||
Blocks[] blocks = chunk.treeTo1DArray();
|
||||
|
||||
// System.out.println(Arrays.toString(blocks));
|
||||
|
||||
if (blocks.length == 0) return;
|
||||
// chunk.chunkNode = new Node();
|
||||
|
||||
if (blocks.length == 0)
|
||||
return;
|
||||
|
||||
int k, l, u, v, w, h, n, j, i;
|
||||
int[] x = { 0, 0, 0 };
|
||||
|
@ -177,15 +204,22 @@ public class ChunkRenderer {
|
|||
for (x[v] = 0; x[v] < Config.CHUNK_SIZE; x[v]++) {
|
||||
for (x[u] = 0; x[u] < Config.CHUNK_SIZE; x[u]++) {
|
||||
Blocks b1 = (x[dim] >= 0) ? blocks[Chunk.coord3DTo1D(x[0], x[1], x[2])] : null;
|
||||
Blocks b2 = (x[dim] < Config.CHUNK_SIZE - 1) ? blocks[Chunk.coord3DTo1D(x[0] + q[0], x[1] + q[1], x[2] + q[2])] : null;
|
||||
Blocks b2 = (x[dim] < Config.CHUNK_SIZE - 1)
|
||||
? blocks[Chunk.coord3DTo1D(x[0] + q[0], x[1] + q[1], x[2] + q[2])]
|
||||
: null;
|
||||
|
||||
// This is the original line taken from rob's code, readapted (replace voxelFace with b1 and b2).
|
||||
// mask[n++] = ((voxelFace != null && voxelFace1 != null && voxelFace.equals(voxelFace1))) ? null : backFace ? voxelFace1 : voxelFace;
|
||||
|
||||
// Additionally checking whether b1 and b2 are AIR or null allows face culling, thus not rendering faces that cannot be seen
|
||||
// This is the original line taken from rob's code, readapted (replace voxelFace
|
||||
// with b1 and b2).
|
||||
// mask[n++] = ((voxelFace != null && voxelFace1 != null &&
|
||||
// voxelFace.equals(voxelFace1))) ? null : backFace ? voxelFace1 : voxelFace;
|
||||
|
||||
// Additionally checking whether b1 and b2 are AIR or null allows face culling,
|
||||
// thus not rendering faces that cannot be seen
|
||||
// Removing the control for null disables chunk borders
|
||||
// This can be surely refactored in something that isn't such a big one-liner
|
||||
mask[n++] = b1 != null && b2 != null && b1.equals(b2) ? null : backFace ? b1 == Blocks.AIR || b1 == null ? b2 : null : b2 == Blocks.AIR || b2 == null ? b1 : null;
|
||||
mask[n++] = b1 != null && b2 != null && b1.equals(b2) ? null
|
||||
: backFace ? b1 == Blocks.AIR || b1 == null ? b2 : null
|
||||
: b2 == Blocks.AIR || b2 == null ? b1 : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,18 +233,21 @@ public class ChunkRenderer {
|
|||
|
||||
if (mask[n] != null) {
|
||||
// First compute the width
|
||||
for (w = 1; i + w < Config.CHUNK_SIZE && mask[n + w] != null && mask[n].equals(mask[n + w]); w++) {
|
||||
for (w = 1; i + w < Config.CHUNK_SIZE && mask[n + w] != null
|
||||
&& mask[n].equals(mask[n + w]); w++) {
|
||||
}
|
||||
|
||||
boolean done = false;
|
||||
for (h = 1; j + h < Config.CHUNK_SIZE; h++) {
|
||||
for (k = 0; k < w; k++) {
|
||||
if (mask[n + k + h * Config.CHUNK_SIZE] == null || !mask[n + k + h * Config.CHUNK_SIZE].equals(mask[n])) {
|
||||
if (mask[n + k + h * Config.CHUNK_SIZE] == null
|
||||
|| !mask[n + k + h * Config.CHUNK_SIZE].equals(mask[n])) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (done) break;
|
||||
if (done)
|
||||
break;
|
||||
}
|
||||
|
||||
if (mask[n] != Blocks.AIR) {
|
||||
|
@ -227,9 +264,12 @@ public class ChunkRenderer {
|
|||
dv[2] = 0;
|
||||
dv[v] = h;
|
||||
|
||||
chunk.chunkNode.attachChild(quad(new Vector3f(x[0], x[1], x[2]), new Vector3f(x[0] + du[0], x[1] + du[1], x[2] + du[2]),
|
||||
new Vector3f(x[0] + du[0] + dv[0], x[1] + du[1] + dv[1], x[2] + du[2] + dv[2]), new Vector3f(x[0] + dv[0], x[1] + dv[1], x[2] + dv[2]),
|
||||
mask[n], chunk, mgr, backFace));
|
||||
quad(new Vector3f(x[0], x[1], x[2]),
|
||||
new Vector3f(x[0] + du[0], x[1] + du[1], x[2] + du[2]),
|
||||
new Vector3f(x[0] + du[0] + dv[0], x[1] + du[1] + dv[1],
|
||||
x[2] + du[2] + dv[2]),
|
||||
new Vector3f(x[0] + dv[0], x[1] + dv[1], x[2] + dv[2]),
|
||||
mask[n], backFace);
|
||||
// System.out.println(Arrays.toString(chunk.chunkNode.getChildren().toArray()));
|
||||
|
||||
}
|
||||
|
@ -255,77 +295,86 @@ public class ChunkRenderer {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vIndex > 0) {
|
||||
// Now, build a single mesh
|
||||
// System.out.println(Arrays.toString(Arrays.copyOf(vertices, vIndex )));
|
||||
// System.out.println(Arrays.toString(Arrays.copyOf(indices, iIndex )));
|
||||
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setBuffer(Type.Position, 3,
|
||||
BufferUtils.createFloatBuffer((Vector3f[]) Arrays.copyOf(vertices, vIndex)));
|
||||
mesh.setBuffer(Type.Color, 4, BufferUtils.createFloatBuffer((float[]) Arrays.copyOf(colors, cIndex)));
|
||||
mesh.setBuffer(Type.Index, 3, BufferUtils.createShortBuffer((short[]) Arrays.copyOf(indices, iIndex)));
|
||||
mesh.updateBound();
|
||||
|
||||
Geometry geo = new Geometry("ColoredMesh", mesh);
|
||||
Material mat = new Material(mgr.game.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
|
||||
mat.setBoolean("VertexColor", true);
|
||||
|
||||
// mat.getAdditionalRenderState().setWireframe(true);
|
||||
// mat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Front);
|
||||
|
||||
geo.setMaterial(mat);
|
||||
chunk.chunkNode.attachChild(geo);
|
||||
}
|
||||
}
|
||||
|
||||
final int VOXEL_SIZE = 1;
|
||||
|
||||
Geometry quad(final Vector3f bottomLeft, final Vector3f topLeft, final Vector3f topRight, final Vector3f bottomRight, final Blocks block, final Chunk chunk,
|
||||
final WorldManager mgr, boolean backFace) {
|
||||
void quad(final Vector3f bottomLeft, final Vector3f topLeft, final Vector3f topRight,
|
||||
final Vector3f bottomRight, final Blocks block, boolean backFace) {
|
||||
|
||||
final Vector3f[] vertices = new Vector3f[4];
|
||||
// System.out.println(vIndex + ", " + iIndex);
|
||||
|
||||
vertices[2] = topLeft.multLocal(VOXEL_SIZE);
|
||||
vertices[3] = topRight.multLocal(VOXEL_SIZE);
|
||||
vertices[0] = bottomLeft.multLocal(VOXEL_SIZE);
|
||||
vertices[1] = bottomRight.multLocal(VOXEL_SIZE);
|
||||
vertices[vIndex] = bottomLeft;
|
||||
vertices[vIndex + 1] = bottomRight;
|
||||
vertices[vIndex + 2] = topLeft;
|
||||
vertices[vIndex + 3] = topRight;
|
||||
|
||||
final int[] indexes = backFace ? new int[] { 2, 0, 1, 1, 3, 2 } : new int[] { 2, 3, 1, 1, 0, 2 };
|
||||
final float[] colorArray = new float[4 * 4];
|
||||
if (backFace) {
|
||||
indices[iIndex] = (short) (vIndex + 2);
|
||||
indices[iIndex + 1] = (short) (vIndex + 0);
|
||||
indices[iIndex + 2] = (short) (vIndex + 1);
|
||||
indices[iIndex + 3] = (short) (vIndex + 1);
|
||||
indices[iIndex + 4] = (short) (vIndex + 3);
|
||||
indices[iIndex + 5] = (short) (vIndex + 2);
|
||||
} else {
|
||||
indices[iIndex] = (short) (vIndex + 2);
|
||||
indices[iIndex + 1] = (short) (vIndex + 3);
|
||||
indices[iIndex + 2] = (short) (vIndex + 1);
|
||||
indices[iIndex + 3] = (short) (vIndex + 1);
|
||||
indices[iIndex + 4] = (short) (vIndex + 0);
|
||||
indices[iIndex + 5] = (short) (vIndex + 2);
|
||||
}
|
||||
|
||||
// System.out.println("A quad delimited by " + Arrays.toString(vertices)
|
||||
// + " for " + block);
|
||||
for (int i = cIndex; i < cIndex + 16; i += 4) {
|
||||
if (block == Blocks.DIRT) {
|
||||
|
||||
for (int i = 0; i < colorArray.length; i += 4) {
|
||||
colors[i] = 1.0f;
|
||||
colors[i + 1] = 0.0f;
|
||||
colors[i + 2] = 0.0f;
|
||||
colors[i + 3] = 1.0f;
|
||||
|
||||
/*
|
||||
* Here I set different colors for quads depending on the "type"
|
||||
* attribute, just so that the different groups of voxels can be
|
||||
* clearly seen.
|
||||
*
|
||||
*/
|
||||
if (block == Blocks.STONE) {
|
||||
} else if (block == Blocks.GRASS) {
|
||||
|
||||
colorArray[i] = 1.0f;
|
||||
colorArray[i + 1] = 0.0f;
|
||||
colorArray[i + 2] = 0.0f;
|
||||
colorArray[i + 3] = 1.0f;
|
||||
|
||||
} else if (block == Blocks.DIRT) {
|
||||
|
||||
colorArray[i] = 0.0f;
|
||||
colorArray[i + 1] = 1.0f;
|
||||
colorArray[i + 2] = 0.0f;
|
||||
colorArray[i + 3] = 1.0f;
|
||||
colors[i] = 0.0f;
|
||||
colors[i + 1] = 1.0f;
|
||||
colors[i + 2] = 0.0f;
|
||||
colors[i + 3] = 1.0f;
|
||||
|
||||
} else {
|
||||
|
||||
colorArray[i] = 0.0f;
|
||||
colorArray[i + 1] = 0.0f;
|
||||
colorArray[i + 2] = 1.0f;
|
||||
colorArray[i + 3] = 1.0f;
|
||||
colors[i] = 0.0f;
|
||||
colors[i + 1] = 0.0f;
|
||||
colors[i + 2] = 1.0f;
|
||||
colors[i + 3] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
Mesh mesh = new Mesh();
|
||||
|
||||
mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
|
||||
mesh.setBuffer(Type.Color, 4, colorArray);
|
||||
mesh.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(indexes));
|
||||
mesh.updateBound();
|
||||
|
||||
Geometry geo = new Geometry("ColoredMesh", mesh);
|
||||
Material mat = new Material(mgr.game.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setBoolean("VertexColor", true);
|
||||
|
||||
/*
|
||||
* To see the actual rendered quads rather than the wireframe, just
|
||||
* comment outthis line.
|
||||
*/
|
||||
mat.getAdditionalRenderState().setWireframe(true);
|
||||
// mat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Front);
|
||||
|
||||
geo.setMaterial(mat);
|
||||
|
||||
return geo;
|
||||
// next time it will be the index of the first vertex that was added
|
||||
vIndex += 4;
|
||||
iIndex += 6;
|
||||
cIndex += 16;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue