initial commit

This is a _mostly_ working port of my original C++ code. Some parts
aren't too optimized yet and some stuff may actually not work. But it's
_mostly_ there. :)
This commit is contained in:
Gered 2013-07-15 19:13:30 -04:00
parent 913ec916ce
commit 382da2985f
15 changed files with 2109 additions and 0 deletions

View file

@ -0,0 +1,194 @@
package com.blarg.gdx.tilemap3d;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.g3d.utils.MeshBuilder;
import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector3;
import java.nio.FloatBuffer;
public class ChunkVertexGenerator {
protected final MeshBuilder.VertexInfo vertex = new MeshPartBuilder.VertexInfo();
final MeshBuilder builder = new MeshBuilder();
final MeshBuilder alphaBuilder = new MeshBuilder();
final TileCoord tmpPosition = new TileCoord();
final Color tmpColor = new Color();
final Vector3 tmpOffset = new Vector3();
public void generate(TileChunk chunk) {
TileMap tileMap = chunk.tileMap;
builder.begin(
VertexAttributes.Usage.Position |
VertexAttributes.Usage.Color |
VertexAttributes.Usage.Normal |
VertexAttributes.Usage.TextureCoordinates
);
alphaBuilder.begin(
VertexAttributes.Usage.Position |
VertexAttributes.Usage.Color |
VertexAttributes.Usage.Normal |
VertexAttributes.Usage.TextureCoordinates
);
for (int y = 0; y < chunk.getHeight(); ++y) {
for (int z = 0; z < chunk.getDepth(); ++z) {
for (int x = 0; x < chunk.getWidth(); ++x) {
Tile tile = chunk.get(x, y, z);
if (tile.tile == Tile.NO_TILE)
continue;
TileMesh mesh = chunk.tileMap.tileMeshes.get(tile);
// "world/tilemap space" position that this tile is at
tmpPosition.x = x + (int)chunk.getPosition().x;
tmpPosition.y = y + (int)chunk.getPosition().y;
tmpPosition.z = z + (int)chunk.getPosition().z;
Matrix4 transform = Tile.getTransformationFor(tile);
// tile color
if (tile.hasCustomColor())
tmpColor.set(tile.color);
else
tmpColor.set(mesh.color);
if (mesh instanceof CubeTileMesh) {
CubeTileMesh cubeMesh = (CubeTileMesh)mesh;
// determine what's next to each cube face
Tile left = chunk.getWithinSelfOrNeighbourSafe(x - 1, y, z);
Tile right = chunk.getWithinSelfOrNeighbourSafe(x + 1, y, z);
Tile forward = chunk.getWithinSelfOrNeighbourSafe(x, y, z - 1);
Tile backward = chunk.getWithinSelfOrNeighbourSafe(x, y, z + 1);
Tile down = chunk.getWithinSelfOrNeighbourSafe(x, y - 1, z);
Tile up = chunk.getWithinSelfOrNeighbourSafe(x, y + 1, z);
// evaluate each face's visibility and add it's vertices if needed one at a time
if ((left == null || left.tile == Tile.NO_TILE || !tileMap.tileMeshes.get(left).isOpaque(TileMesh.SIDE_RIGHT)) && cubeMesh.hasFace(TileMesh.SIDE_LEFT)) {
// left face is visible
if (cubeMesh.alpha)
addMesh(alphaBuilder, cubeMesh, chunk, tmpPosition, transform, tmpColor, cubeMesh.leftFaceVertexOffset, TileMesh.CUBE_VERTICES_PER_FACE);
else
addMesh(builder, cubeMesh, chunk, tmpPosition, transform, tmpColor, cubeMesh.leftFaceVertexOffset, TileMesh.CUBE_VERTICES_PER_FACE);
}
if ((right == null || right.tile == Tile.NO_TILE || !tileMap.tileMeshes.get(right).isOpaque(TileMesh.SIDE_LEFT)) && cubeMesh.hasFace(TileMesh.SIDE_RIGHT)) {
// right face is visible
if (cubeMesh.alpha)
addMesh(alphaBuilder, cubeMesh, chunk, tmpPosition, transform, tmpColor, cubeMesh.rightFaceVertexOffset, TileMesh.CUBE_VERTICES_PER_FACE);
else
addMesh(builder, cubeMesh, chunk, tmpPosition, transform, tmpColor, cubeMesh.rightFaceVertexOffset, TileMesh.CUBE_VERTICES_PER_FACE);
}
if ((forward == null || forward.tile == Tile.NO_TILE || !tileMap.tileMeshes.get(forward).isOpaque(TileMesh.SIDE_BACK)) && cubeMesh.hasFace(TileMesh.SIDE_FRONT)) {
// front face is visible
if (cubeMesh.alpha)
addMesh(alphaBuilder, cubeMesh, chunk, tmpPosition, transform, tmpColor, cubeMesh.frontFaceVertexOffset, TileMesh.CUBE_VERTICES_PER_FACE);
else
addMesh(builder, cubeMesh, chunk, tmpPosition, transform, tmpColor, cubeMesh.frontFaceVertexOffset, TileMesh.CUBE_VERTICES_PER_FACE);
}
if ((backward == null || backward.tile == Tile.NO_TILE || !tileMap.tileMeshes.get(backward).isOpaque(TileMesh.SIDE_FRONT)) && cubeMesh.hasFace(TileMesh.SIDE_BACK)) {
// back face is visible
if (cubeMesh.alpha)
addMesh(alphaBuilder, cubeMesh, chunk, tmpPosition, transform, tmpColor, cubeMesh.backFaceVertexOffset, TileMesh.CUBE_VERTICES_PER_FACE);
else
addMesh(builder, cubeMesh, chunk, tmpPosition, transform, tmpColor, cubeMesh.backFaceVertexOffset, TileMesh.CUBE_VERTICES_PER_FACE);
}
if ((down == null || down.tile == Tile.NO_TILE || !tileMap.tileMeshes.get(down).isOpaque(TileMesh.SIDE_TOP)) && cubeMesh.hasFace(TileMesh.SIDE_BOTTOM)) {
// bottom face is visible
if (cubeMesh.alpha)
addMesh(alphaBuilder, cubeMesh, chunk, tmpPosition, transform, tmpColor, cubeMesh.bottomFaceVertexOffset, TileMesh.CUBE_VERTICES_PER_FACE);
else
addMesh(builder, cubeMesh, chunk, tmpPosition, transform, tmpColor, cubeMesh.bottomFaceVertexOffset, TileMesh.CUBE_VERTICES_PER_FACE);
}
if ((up == null || up.tile == Tile.NO_TILE || !tileMap.tileMeshes.get(up).isOpaque(TileMesh.SIDE_BOTTOM)) && cubeMesh.hasFace(TileMesh.SIDE_TOP)) {
// top face is visible
if (cubeMesh.alpha)
addMesh(alphaBuilder, cubeMesh, chunk, tmpPosition, transform, tmpColor, cubeMesh.topFaceVertexOffset, TileMesh.CUBE_VERTICES_PER_FACE);
else
addMesh(builder, cubeMesh, chunk, tmpPosition, transform, tmpColor, cubeMesh.topFaceVertexOffset, TileMesh.CUBE_VERTICES_PER_FACE);
}
} else {
boolean visible = false;
// visibility determination. we check for at least one
// adjacent empty space / non-opaque tile
Tile left = chunk.getWithinSelfOrNeighbourSafe(x - 1, y, z);
Tile right = chunk.getWithinSelfOrNeighbourSafe(x + 1, y, z);
Tile forward = chunk.getWithinSelfOrNeighbourSafe(x, y, z - 1);
Tile backward = chunk.getWithinSelfOrNeighbourSafe(x, y, z + 1);
Tile down = chunk.getWithinSelfOrNeighbourSafe(x, y - 1, z);
Tile up = chunk.getWithinSelfOrNeighbourSafe(x, y + 1, z);
// null == empty space (off the edge of the entire map)
if (
(left == null || left.tile == Tile.NO_TILE || !tileMap.tileMeshes.get(left).isOpaque(TileMesh.SIDE_RIGHT)) ||
(right == null || right.tile == Tile.NO_TILE || !tileMap.tileMeshes.get(right).isOpaque(TileMesh.SIDE_LEFT)) ||
(forward == null || forward.tile == Tile.NO_TILE || !tileMap.tileMeshes.get(forward).isOpaque(TileMesh.SIDE_BACK)) ||
(backward == null || backward.tile == Tile.NO_TILE || !tileMap.tileMeshes.get(backward).isOpaque(TileMesh.SIDE_FRONT)) ||
(up == null || up.tile == Tile.NO_TILE || !tileMap.tileMeshes.get(up).isOpaque(TileMesh.SIDE_BOTTOM)) ||
(down == null || down.tile == Tile.NO_TILE || !tileMap.tileMeshes.get(down).isOpaque(TileMesh.SIDE_TOP))
)
visible = true;
if (visible) {
if (mesh.alpha)
addMesh(alphaBuilder, mesh, chunk, tmpPosition, transform, tmpColor, 0, mesh.getMesh().getNumVertices());
else
addMesh(builder, mesh, chunk, tmpPosition, transform, tmpColor, 0, mesh.getMesh().getNumVertices());
}
}
}
}
}
chunk.mesh.mesh = builder.end();
chunk.mesh.meshPartSize = chunk.mesh.mesh.getNumVertices();
chunk.alphaMesh.mesh = alphaBuilder.end();
chunk.alphaMesh.meshPartSize = chunk.alphaMesh.mesh.getNumVertices();
}
private void addMesh(MeshBuilder builder, TileMesh sourceMesh, TileChunk chunk, TileCoord position, Matrix4 transform, Color color, int firstVertex, int numVertices) {
// adjust position by the tilemesh offset. TileMesh's are modeled using the
// origin (0,0,0) as the center and are 1 unit wide/deep/tall. So, their
// max extents are from -0.5,-0.5,-0.5 to 0.5,0.5,0.5. For rendering
// purposes in a chunk, we want the extents to be 0,0,0 to 1,1,1. This
// adjustment fixes that
tmpOffset.set(TileMesh.OFFSET);
tmpOffset.x += (float)position.x;
tmpOffset.y += (float)position.y;
tmpOffset.z += (float)position.z;
// copy vertices
for (int i = 0, j = firstVertex; i < numVertices; ++i, ++j)
copyVertex(builder, sourceMesh, j, tmpOffset, transform, color, chunk);
}
private void copyVertex(MeshBuilder builder, TileMesh sourceMesh, int sourceVertexIndex, Vector3 positionOffset, Matrix4 transform, Color color, TileChunk chunk) {
FloatBuffer buffer = sourceMesh.getMesh().getVerticesBuffer();
int strideInFloats = sourceMesh.getMesh().getVertexSize() / (Float.SIZE / 8);
int offset = (sourceVertexIndex * strideInFloats);
vertex.setPos(buffer.get(offset), buffer.get(offset + 1), buffer.get(offset + 2));
offset += 3;
vertex.setCol(buffer.get(offset), buffer.get(offset + 1), buffer.get(offset + 2), buffer.get(offset + 3));
offset += 4;
vertex.setNor(buffer.get(offset), buffer.get(offset + 1), buffer.get(offset + 2));
offset += 3;
vertex.setUV(buffer.get(offset), buffer.get(offset + 1));
// transform if applicable... (this will probably just be per-tile rotation)
if (transform != null) {
vertex.position.mul(transform);
vertex.normal.mul(transform);
}
// translate vertex into "world/tilemap space"
vertex.position.add(positionOffset);
builder.vertex(vertex);
}
}

View file

@ -0,0 +1,373 @@
package com.blarg.gdx.tilemap3d;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g3d.utils.MeshBuilder;
import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.blarg.gdx.Bitfield;
import com.blarg.gdx.graphics.TextureAtlas;
import com.blarg.gdx.math.MathHelpers;
import java.nio.FloatBuffer;
public class CubeTileMesh extends TileMesh {
static final Vector3 A = new Vector3(-0.5f, -0.5f, -0.5f);
static final Vector3 B = new Vector3(0.5f, 0.5f, 0.5f);
final Mesh mesh;
final Vector3[] collisionVertices;
public final byte faces;
public final int topFaceVertexOffset;
public final int bottomFaceVertexOffset;
public final int frontFaceVertexOffset;
public final int backFaceVertexOffset;
public final int leftFaceVertexOffset;
public final int rightFaceVertexOffset;
public boolean hasFace(byte side) {
return Bitfield.isSet(side, faces);
}
@Override
public Mesh getMesh() {
return mesh;
}
@Override
public Vector3[] getCollisionVertices() {
return collisionVertices;
}
public CubeTileMesh(
TextureRegion topTexture,
TextureRegion bottomTexture,
TextureRegion frontTexture,
TextureRegion backTexture,
TextureRegion leftTexture,
TextureRegion rightTexture,
byte faces, byte opaqueSides, byte lightValue, boolean alpha, float translucency, Color color) {
super(opaqueSides, alpha, translucency, lightValue, color);
this.faces = faces;
if (faces == 0)
throw new IllegalArgumentException();
int numVertices = 0;
if (hasFace(SIDE_TOP)) {
topFaceVertexOffset = numVertices;
numVertices += CUBE_VERTICES_PER_FACE;
} else
topFaceVertexOffset = 0;
if (hasFace(SIDE_BOTTOM)) {
bottomFaceVertexOffset = numVertices;
numVertices += CUBE_VERTICES_PER_FACE;
} else
bottomFaceVertexOffset = 0;
if (hasFace(SIDE_FRONT)) {
frontFaceVertexOffset = numVertices;
numVertices += CUBE_VERTICES_PER_FACE;
} else
frontFaceVertexOffset = 0;
if (hasFace(SIDE_BACK)) {
backFaceVertexOffset = numVertices;
numVertices += CUBE_VERTICES_PER_FACE;
} else
backFaceVertexOffset = 0;
if (hasFace(SIDE_LEFT)) {
leftFaceVertexOffset = numVertices;
numVertices += CUBE_VERTICES_PER_FACE;
} else
leftFaceVertexOffset = 0;
if (hasFace(SIDE_RIGHT)) {
rightFaceVertexOffset = numVertices;
numVertices += CUBE_VERTICES_PER_FACE;
} else
rightFaceVertexOffset = 0;
mesh = setupFaceVertices(topTexture, bottomTexture, frontTexture, backTexture, leftTexture, rightTexture);
collisionVertices = setupCollisionVertices();
}
private Mesh setupFaceVertices(
TextureRegion topTexture,
TextureRegion bottomTexture,
TextureRegion frontTexture,
TextureRegion backTexture,
TextureRegion leftTexture,
TextureRegion rightTexture
) {
MeshPartBuilder.VertexInfo vertex = new MeshPartBuilder.VertexInfo();
MeshBuilder builder = new MeshBuilder();
builder.begin(
VertexAttributes.Usage.Position |
VertexAttributes.Usage.Color |
VertexAttributes.Usage.Normal |
VertexAttributes.Usage.TextureCoordinates
);
if (hasFace(SIDE_TOP)) {
vertex.setPos(A.x, B.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.UP_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, topTexture), TextureAtlas.scaleTexCoordV(1.0f, topTexture));
builder.vertex(vertex);
vertex.setPos(B.x, B.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.UP_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, topTexture), TextureAtlas.scaleTexCoordV(1.0f, topTexture));
builder.vertex(vertex);
vertex.setPos(A.x, B.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.UP_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, topTexture), TextureAtlas.scaleTexCoordV(0.0f, topTexture));
builder.vertex(vertex);
vertex.setPos(B.x, B.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.UP_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, topTexture), TextureAtlas.scaleTexCoordV(1.0f, topTexture));
builder.vertex(vertex);
vertex.setPos(B.x, B.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.UP_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, topTexture), TextureAtlas.scaleTexCoordV(0.0f, topTexture));
builder.vertex(vertex);
vertex.setPos(A.x, B.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.UP_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, topTexture), TextureAtlas.scaleTexCoordV(0.0f, topTexture));
builder.vertex(vertex);
}
if (hasFace(SIDE_BOTTOM)) {
vertex.setPos(B.x, A.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.DOWN_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, bottomTexture), TextureAtlas.scaleTexCoordV(1.0f, bottomTexture));
builder.vertex(vertex);
vertex.setPos(A.x, A.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.DOWN_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, bottomTexture), TextureAtlas.scaleTexCoordV(1.0f, bottomTexture));
builder.vertex(vertex);
vertex.setPos(B.x, A.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.DOWN_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, bottomTexture), TextureAtlas.scaleTexCoordV(0.0f, bottomTexture));
builder.vertex(vertex);
vertex.setPos(A.x, A.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.DOWN_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, bottomTexture), TextureAtlas.scaleTexCoordV(1.0f, bottomTexture));
builder.vertex(vertex);
vertex.setPos(A.x, A.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.DOWN_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, bottomTexture), TextureAtlas.scaleTexCoordV(0.0f, bottomTexture));
builder.vertex(vertex);
vertex.setPos(B.x, A.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.DOWN_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, bottomTexture), TextureAtlas.scaleTexCoordV(0.0f, bottomTexture));
builder.vertex(vertex);
}
if (hasFace(SIDE_FRONT)) {
vertex.setPos(B.x, A.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.FORWARD_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, frontTexture), TextureAtlas.scaleTexCoordV(1.0f, frontTexture));
builder.vertex(vertex);
vertex.setPos(A.x, A.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.FORWARD_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, frontTexture), TextureAtlas.scaleTexCoordV(1.0f, frontTexture));
builder.vertex(vertex);
vertex.setPos(B.x, B.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.FORWARD_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, frontTexture), TextureAtlas.scaleTexCoordV(0.0f, frontTexture));
builder.vertex(vertex);
vertex.setPos(A.x, A.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.FORWARD_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, frontTexture), TextureAtlas.scaleTexCoordV(1.0f, frontTexture));
builder.vertex(vertex);
vertex.setPos(A.x, B.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.FORWARD_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, frontTexture), TextureAtlas.scaleTexCoordV(0.0f, frontTexture));
builder.vertex(vertex);
vertex.setPos(B.x, B.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.FORWARD_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, frontTexture), TextureAtlas.scaleTexCoordV(0.0f, frontTexture));
builder.vertex(vertex);
}
if (hasFace(SIDE_BACK)) {
vertex.setPos(A.x, A.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.BACKWARD_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, backTexture), TextureAtlas.scaleTexCoordV(1.0f, backTexture));
builder.vertex(vertex);
vertex.setPos(B.x, A.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.BACKWARD_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, backTexture), TextureAtlas.scaleTexCoordV(1.0f, backTexture));
builder.vertex(vertex);
vertex.setPos(A.x, B.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.BACKWARD_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, backTexture), TextureAtlas.scaleTexCoordV(0.0f, backTexture));
builder.vertex(vertex);
vertex.setPos(B.x, A.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.BACKWARD_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, backTexture), TextureAtlas.scaleTexCoordV(1.0f, backTexture));
builder.vertex(vertex);
vertex.setPos(B.x, B.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.BACKWARD_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, backTexture), TextureAtlas.scaleTexCoordV(0.0f, backTexture));
builder.vertex(vertex);
vertex.setPos(A.x, B.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.BACKWARD_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, backTexture), TextureAtlas.scaleTexCoordV(0.0f, backTexture));
builder.vertex(vertex);
}
if (hasFace(SIDE_LEFT)) {
vertex.setPos(A.x, A.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.LEFT_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, leftTexture), TextureAtlas.scaleTexCoordV(1.0f, leftTexture));
builder.vertex(vertex);
vertex.setPos(A.x, A.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.LEFT_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, leftTexture), TextureAtlas.scaleTexCoordV(1.0f, leftTexture));
builder.vertex(vertex);
vertex.setPos(A.x, B.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.LEFT_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, leftTexture), TextureAtlas.scaleTexCoordV(0.0f, leftTexture));
builder.vertex(vertex);
vertex.setPos(A.x, A.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.LEFT_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, leftTexture), TextureAtlas.scaleTexCoordV(1.0f, leftTexture));
builder.vertex(vertex);
vertex.setPos(A.x, B.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.LEFT_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, leftTexture), TextureAtlas.scaleTexCoordV(0.0f, leftTexture));
builder.vertex(vertex);
vertex.setPos(A.x, B.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.LEFT_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, leftTexture), TextureAtlas.scaleTexCoordV(0.0f, leftTexture));
builder.vertex(vertex);
}
if (hasFace(SIDE_RIGHT)) {
vertex.setPos(B.x, A.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.RIGHT_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, rightTexture), TextureAtlas.scaleTexCoordV(1.0f, rightTexture));
builder.vertex(vertex);
vertex.setPos(B.x, A.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.RIGHT_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, rightTexture), TextureAtlas.scaleTexCoordV(1.0f, rightTexture));
builder.vertex(vertex);
vertex.setPos(B.x, B.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.RIGHT_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, rightTexture), TextureAtlas.scaleTexCoordV(0.0f, rightTexture));
builder.vertex(vertex);
vertex.setPos(B.x, A.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.RIGHT_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, rightTexture), TextureAtlas.scaleTexCoordV(1.0f, rightTexture));
builder.vertex(vertex);
vertex.setPos(B.x, B.y, A.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.RIGHT_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(1.0f, rightTexture), TextureAtlas.scaleTexCoordV(0.0f, rightTexture));
builder.vertex(vertex);
vertex.setPos(B.x, B.y, B.z);
vertex.setCol(color);
vertex.setNor(MathHelpers.RIGHT_VECTOR3);
vertex.setUV(TextureAtlas.scaleTexCoordU(0.0f, rightTexture), TextureAtlas.scaleTexCoordV(0.0f, rightTexture));
builder.vertex(vertex);
}
return builder.end();
}
private Vector3[] setupCollisionVertices() {
Vector3[] vertices = new Vector3[mesh.getNumVertices()];
FloatBuffer meshVertices = mesh.getVerticesBuffer();
int strideInFloats = mesh.getVertexSize() / (Float.SIZE / 8);
for (int i = 0; i < mesh.getNumVertices(); ++i) {
int offset = i * strideInFloats;
// NOTE: we assume position is always at the start of each vertex... I believe libgdx will _always_ do this
Vector3 v = new Vector3();
v.x = meshVertices.get(offset);
v.y = meshVertices.get(offset + 1);
v.z = meshVertices.get(offset + 2);
vertices[i] = v;
}
return vertices;
}
@Override
public void dispose() {
mesh.dispose();
}
}

View file

@ -0,0 +1,159 @@
package com.blarg.gdx.tilemap3d;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.model.MeshPart;
import com.badlogic.gdx.graphics.g3d.model.Node;
import com.badlogic.gdx.graphics.g3d.model.NodePart;
import com.badlogic.gdx.graphics.g3d.utils.MeshBuilder;
import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.blarg.gdx.graphics.TextureAtlas;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.Map;
public class ModelTileMesh extends TileMesh {
final MeshPartBuilder.VertexInfo vertex = new MeshPartBuilder.VertexInfo();
final Vector3 tmpPosition = new Vector3();
final Vector3 tmpNormal = new Vector3();
Mesh mesh;
Array<Vector3> collisionVertices;
@Override
public Mesh getMesh() {
return mesh;
}
@Override
public Vector3[] getCollisionVertices() {
return collisionVertices.items;
}
public ModelTileMesh(Model model, Map<String, TextureRegion> textures, byte opaqueSides, byte lightValue, boolean alpha, float translucency, Color color) {
super(opaqueSides, alpha, translucency, lightValue, color);
setupMesh(model, textures);
setupCollisionVertices(model);
}
public ModelTileMesh(Model model, Model collisionModel, Map<String, TextureRegion> textures, byte opaqueSides, byte lightValue, boolean alpha, float translucency, Color color) {
super(opaqueSides, alpha, translucency, lightValue, color);
setupMesh(model, textures);
setupCollisionVertices(collisionModel);
}
private void setupMesh(Model model, Map<String, TextureRegion> textures) {
MeshBuilder builder = new MeshBuilder();
builder.begin(
VertexAttributes.Usage.Position |
VertexAttributes.Usage.Color |
VertexAttributes.Usage.Normal |
VertexAttributes.Usage.TextureCoordinates
);
for (int i = 0; i < model.nodes.size; ++i)
addModelNodeVertices(model.nodes.get(i), builder, textures);
mesh = builder.end();
}
private void addModelNodeVertices(Node node, MeshBuilder builder, Map<String, TextureRegion> textures) {
final Matrix4 transform = node.globalTransform; // TODO: test that this is the right transform to use?
for (int i = 0; i < node.parts.size; ++i) {
NodePart nodePart = node.parts.get(i);
TextureRegion texture = textures.get(nodePart.material.id);
MeshPart meshPart = nodePart.meshPart;
ShortBuffer indices = meshPart.mesh.getIndicesBuffer();
FloatBuffer vertices = meshPart.mesh.getVerticesBuffer();
final int strideInFloats = meshPart.mesh.getVertexSize() / (Float.SIZE / 8);
for (int j = 0; j < meshPart.numVertices; ++j) {
int index = indices.get(meshPart.indexOffset + j);
int offset = index * strideInFloats;
tmpPosition.set(vertices.get(offset), vertices.get(offset + 1), vertices.get(offset + 2));
tmpPosition.mul(transform);
vertex.setPos(tmpPosition);
offset += 3;
if (meshPart.mesh.getVertexAttribute(VertexAttributes.Usage.Color) != null) {
vertex.setCol(vertices.get(offset), vertices.get(offset + 1), vertices.get(offset + 2), vertices.get(offset + 3));
offset += 4;
} else
vertex.setCol(Color.WHITE);
// TODO: better to throw exception (or check beforehand) if this is missing? setting zero's doesn't feel like the best solution
if (meshPart.mesh.getVertexAttribute(VertexAttributes.Usage.Normal) != null) {
tmpNormal.set(vertices.get(offset), vertices.get(offset + 1), vertices.get(offset + 2));
tmpNormal.mul(transform);
vertex.setNor(tmpNormal);
offset += 3;
} else
vertex.setNor(Vector3.Zero);
// TODO: better to throw exception (or check beforehand) if this is missing? setting zero's doesn't feel like the best solution
if (meshPart.mesh.getVertexAttribute(VertexAttributes.Usage.TextureCoordinates) != null) {
vertex.setUV(
TextureAtlas.scaleTexCoordU(vertices.get(offset), texture),
TextureAtlas.scaleTexCoordV(vertices.get(offset + 1), texture)
);
offset += 3;
} else
vertex.setUV(Vector2.Zero);
builder.vertex(vertex);
}
}
for (int i = 0; i < node.children.size; ++i)
addModelNodeVertices(node.children.get(i), builder, textures);
}
private void setupCollisionVertices(Model collisionModel) {
int numVertices = 0;
for (int i = 0; i < collisionModel.meshParts.size; ++i)
numVertices += collisionModel.meshParts.get(i).numVertices;
collisionVertices = new Array<Vector3>(true, numVertices, Vector3.class);
for (int i = 0; i < collisionModel.nodes.size; ++i)
addModelNodeCollisionVertices(collisionModel.nodes.get(i));
}
private void addModelNodeCollisionVertices(Node node) {
final Matrix4 transform = node.globalTransform; // TODO: test that this is the right transform to use?
for (int i = 0; i < node.parts.size; ++i) {
NodePart nodePart = node.parts.get(i);
MeshPart meshPart = nodePart.meshPart;
ShortBuffer indices = meshPart.mesh.getIndicesBuffer();
FloatBuffer vertices = meshPart.mesh.getVerticesBuffer();
final int strideInFloats = meshPart.mesh.getVertexSize() / (Float.SIZE / 8);
for (int j = 0; j < meshPart.numVertices; ++j) {
int index = indices.get(meshPart.indexOffset + j);
int offset = index * strideInFloats;
tmpPosition.set(vertices.get(offset), vertices.get(offset + 1), vertices.get(offset + 2));
tmpPosition.mul(transform);
collisionVertices.add(new Vector3(tmpPosition));
}
}
for (int i = 0; i < node.children.size; ++i)
addModelNodeCollisionVertices(node.children.get(i));
}
@Override
public void dispose() {
mesh.dispose();
}
}

View file

@ -0,0 +1,154 @@
package com.blarg.gdx.tilemap3d;
public class PositionAndSkyTileMapLighter extends SimpleTileMapLighter {
public PositionAndSkyTileMapLighter() {
}
private void applyLighting(TileMap tileMap) {
// for each light source (sky or not), recursively go through and set
// appropriate lighting for each adjacent tile
for (int y = 0; y < tileMap.getHeight(); ++y)
{
for (int z = 0; z < tileMap.getDepth(); ++z)
{
for (int x = 0; x < tileMap.getWidth(); ++x)
{
Tile tile = tileMap.get(x, y, z);
if (tile.isEmptySpace())
{
if (tile.isSkyLit())
spreadSkyLight(x, y, z, tile, tile.skyLight, tileMap);
}
else
{
TileMesh mesh = tileMap.tileMeshes.get(tile);
if (mesh.isLightSource())
spreadTileLight(x, y, z, tile, mesh.lightValue, tileMap);
}
}
}
} }
private void spreadSkyLight(int x, int y, int z, Tile tile, byte light, TileMap tileMap) {
if (light > 0)
{
tile.skyLight = light;
--light;
Tile left = tileMap.getSafe(x - 1, y, z);
Tile right = tileMap.getSafe(x + 1, y, z);
Tile forward = tileMap.getSafe(x, y, z - 1);
Tile backward = tileMap.getSafe(x, y, z + 1);
Tile up = tileMap.getSafe(x, y + 1, z);
Tile down = tileMap.getSafe(x, y - 1, z);
if (left != null && (left.isEmptySpace() || !tileMap.tileMeshes.get(left).isOpaque(TileMesh.SIDE_RIGHT)) && left.skyLight < light)
{
byte spreadLight = light;
if (!left.isEmptySpace())
spreadLight = Tile.adjustLightForTranslucency(spreadLight, tileMap.tileMeshes.get(left).translucency);
spreadSkyLight(x - 1, y, z, left, spreadLight, tileMap);
}
if (right != null && (right.isEmptySpace() || !tileMap.tileMeshes.get(right).isOpaque(TileMesh.SIDE_LEFT)) && right.skyLight < light)
{
byte spreadLight = light;
if (!right.isEmptySpace())
spreadLight = Tile.adjustLightForTranslucency(spreadLight, tileMap.tileMeshes.get(right).translucency);
spreadSkyLight(x + 1, y, z, right, spreadLight, tileMap);
}
if (forward != null && (forward.isEmptySpace() || !tileMap.tileMeshes.get(forward).isOpaque(TileMesh.SIDE_BACK)) && forward.skyLight < light)
{
byte spreadLight = light;
if (!forward.isEmptySpace())
spreadLight = Tile.adjustLightForTranslucency(spreadLight, tileMap.tileMeshes.get(forward).translucency);
spreadSkyLight(x, y, z - 1, forward, spreadLight, tileMap);
}
if (backward != null && (backward.isEmptySpace() || !tileMap.tileMeshes.get(backward).isOpaque(TileMesh.SIDE_FRONT)) && backward.skyLight < light)
{
byte spreadLight = light;
if (!backward.isEmptySpace())
spreadLight = Tile.adjustLightForTranslucency(spreadLight, tileMap.tileMeshes.get(backward).translucency);
spreadSkyLight(x, y, z + 1, backward, spreadLight, tileMap);
}
if (up != null && (up.isEmptySpace() || !tileMap.tileMeshes.get(up).isOpaque(TileMesh.SIDE_BOTTOM)) && up.skyLight < light)
{
byte spreadLight = light;
if (!up.isEmptySpace())
spreadLight = Tile.adjustLightForTranslucency(spreadLight, tileMap.tileMeshes.get(up).translucency);
spreadSkyLight(x, y + 1, z, up, spreadLight, tileMap);
}
if (down != null && (down.isEmptySpace() || !tileMap.tileMeshes.get(down).isOpaque(TileMesh.SIDE_TOP)) && down.skyLight < light)
{
byte spreadLight = light;
if (!down.isEmptySpace())
spreadLight = Tile.adjustLightForTranslucency(spreadLight, tileMap.tileMeshes.get(down).translucency);
spreadSkyLight(x, y - 1, z, down, spreadLight, tileMap);
}
}
}
private void spreadTileLight(int x, int y, int z, Tile tile, byte light, TileMap tileMap) {
if (light > 0)
{
tile.tileLight = light;
--light;
Tile left = tileMap.getSafe(x - 1, y, z);
Tile right = tileMap.getSafe(x + 1, y, z);
Tile forward = tileMap.getSafe(x, y, z - 1);
Tile backward = tileMap.getSafe(x, y, z + 1);
Tile up = tileMap.getSafe(x, y + 1, z);
Tile down = tileMap.getSafe(x, y - 1, z);
if (left != null && (left.isEmptySpace() || !tileMap.tileMeshes.get(left).isOpaque(TileMesh.SIDE_RIGHT)) && left.tileLight < light)
{
byte spreadLight = light;
if (!left.isEmptySpace())
spreadLight = Tile.adjustLightForTranslucency(spreadLight, tileMap.tileMeshes.get(left).translucency);
spreadTileLight(x - 1, y, z, left, spreadLight, tileMap);
}
if (right != null && (right.isEmptySpace() || !tileMap.tileMeshes.get(right).isOpaque(TileMesh.SIDE_LEFT)) && right.tileLight < light)
{
byte spreadLight = light;
if (!right.isEmptySpace())
spreadLight = Tile.adjustLightForTranslucency(spreadLight, tileMap.tileMeshes.get(right).translucency);
spreadTileLight(x + 1, y, z, right, spreadLight, tileMap);
}
if (forward != null && (forward.isEmptySpace() || !tileMap.tileMeshes.get(forward).isOpaque(TileMesh.SIDE_BACK)) && forward.tileLight < light)
{
byte spreadLight = light;
if (!forward.isEmptySpace())
spreadLight = Tile.adjustLightForTranslucency(spreadLight, tileMap.tileMeshes.get(forward).translucency);
spreadTileLight(x, y, z - 1, forward, spreadLight, tileMap);
}
if (backward != null && (backward.isEmptySpace() || !tileMap.tileMeshes.get(backward).isOpaque(TileMesh.SIDE_FRONT)) && backward.tileLight < light)
{
byte spreadLight = light;
if (!backward.isEmptySpace())
spreadLight = Tile.adjustLightForTranslucency(spreadLight, tileMap.tileMeshes.get(backward).translucency);
spreadTileLight(x, y, z + 1, backward, spreadLight, tileMap);
}
if (up != null && (up.isEmptySpace() || !tileMap.tileMeshes.get(up).isOpaque(TileMesh.SIDE_BOTTOM)) && up.tileLight < light)
{
byte spreadLight = light;
if (!up.isEmptySpace())
spreadLight = Tile.adjustLightForTranslucency(spreadLight, tileMap.tileMeshes.get(up).translucency);
spreadTileLight(x, y + 1, z, up, spreadLight, tileMap);
}
if (down != null && (down.isEmptySpace() || !tileMap.tileMeshes.get(down).isOpaque(TileMesh.SIDE_TOP)) && down.tileLight < light)
{
byte spreadLight = light;
if (!down.isEmptySpace())
spreadLight = Tile.adjustLightForTranslucency(spreadLight, tileMap.tileMeshes.get(down).translucency);
spreadTileLight(x, y - 1, z, down, spreadLight, tileMap);
}
}
}
@Override
public void light(TileMap tileMap) {
resetLightValues(tileMap);
applySkyLight(tileMap);
applyLighting(tileMap);
}
}

View file

@ -0,0 +1,70 @@
package com.blarg.gdx.tilemap3d;
import com.blarg.gdx.Bitfield;
public class SimpleTileMapLighter implements TileMapLighter {
public SimpleTileMapLighter() {
}
protected void resetLightValues(TileMap tileMap) {
for (int y = 0; y < tileMap.getHeight(); ++y)
{
for (int z = 0; z < tileMap.getDepth(); ++z)
{
for (int x = 0; x < tileMap.getWidth(); ++x)
{
Tile tile = tileMap.get(x, y, z);
// sky lighting will be recalculated, and other types of light sources
// info stays as they were
tile.flags = Bitfield.clear(Tile.FLAG_LIGHT_SKY, tile.flags);
tile.skyLight = 0;
tile.tileLight = tileMap.ambientLightValue;
}
}
}
}
protected void applySkyLight(TileMap tileMap) {
// NOTE: resetLightValues() clears sky light data in such a way that
// doesn't require us to flood-fill 0 light values here for everything
// that isn't sky-lit
// go through each vertical column one at a time from top to bottom
for (int x = 0; x < tileMap.getWidth(); ++x)
{
for (int z = 0; z < tileMap.getDepth(); ++z)
{
boolean stillSkyLit = true;
byte currentSkyLightValue = tileMap.skyLightValue;
for (int y = tileMap.getHeight() - 1; y >= 0 && stillSkyLit; --y)
{
Tile tile = tileMap.get(x, y, z);
TileMesh mesh = tileMap.tileMeshes.get(tile);
if (mesh == null || (mesh != null && !mesh.isOpaque(TileMesh.SIDE_TOP) && !mesh.isOpaque(TileMesh.SIDE_BOTTOM)))
{
// tile is partially transparent or this tile is empty space
tile.flags = Bitfield.set(Tile.FLAG_LIGHT_SKY, tile.flags);
if (mesh != null)
currentSkyLightValue = Tile.adjustLightForTranslucency(currentSkyLightValue, mesh.translucency);
tile.skyLight = currentSkyLightValue;
}
else
{
// tile is present and is fully solid, sky lighting stops
// at the tile above this one
stillSkyLit = false;
}
}
}
}
}
@Override
public void light(TileMap tileMap) {
resetLightValues(tileMap);
applySkyLight(tileMap);
}
}

View file

@ -0,0 +1,130 @@
package com.blarg.gdx.tilemap3d;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector3;
import com.blarg.gdx.Bitfield;
public final class Tile {
static final Matrix4 faceNorthRotation = new Matrix4().setToRotation(Vector3.Y, 0.0f);
static final Matrix4 faceEastRotation = new Matrix4().setToRotation(Vector3.Y, 90.0f);
static final Matrix4 faceSouthRotation = new Matrix4().setToRotation(Vector3.Y, 180.0f);
static final Matrix4 faceWestRotation = new Matrix4().setToRotation(Vector3.Y, 270.0f);
public static final short NO_TILE = 0;
public static final byte LIGHT_VALUE_MAX = 15;
public static final byte LIGHT_VALUE_SKY = LIGHT_VALUE_MAX;
public static final short FLAG_COLLIDEABLE = 1;
public static final short FLAG_FACE_NORTH = 2;
public static final short FLAG_FACE_EAST = 4;
public static final short FLAG_FACE_SOUTH = 8;
public static final short FLAG_FACE_WEST = 16;
public static final short FLAG_CUSTOM_COLOR = 32;
public static final short FLAG_FRICTION_SLIPPERY = 64;
public static final short FLAG_LIGHT_SKY = 128;
public static final short FLAG_WALKABLE_SURFACE = 256;
public short tile;
public short flags;
public byte tileLight;
public byte skyLight;
public int color;
public Tile() {
tile = NO_TILE;
}
public Tile set(short tileIndex) {
this.tile = tileIndex;
return this;
}
public Tile set(short tileIndex, short flags) {
this.tile = tileIndex;
this.flags = flags;
return this;
}
public Tile set(short tileIndex, short flags, final Color color) {
return set(tileIndex, flags, color.toIntBits());
}
public Tile set(short tileIndex, short flags, int color) {
flags = Bitfield.set(FLAG_CUSTOM_COLOR, flags);
this.tile = tileIndex;
this.flags = flags;
this.color = color;
return this;
}
public Tile setCustomColor(final Color color) {
return setCustomColor(color.toIntBits());
}
public Tile setCustomColor(int color) {
flags = Bitfield.set(FLAG_CUSTOM_COLOR, flags);
this.color = color;
return this;
}
public Tile clearCustomColor() {
flags = Bitfield.clear(FLAG_CUSTOM_COLOR, flags);
color = 0;
return this;
}
public float getBrightness() {
if (tileLight > skyLight)
return getBrightness(tileLight);
else
return getBrightness(skyLight);
}
public boolean isEmptySpace() {
return tile == NO_TILE;
}
public boolean isCollideable() {
return Bitfield.isSet(FLAG_COLLIDEABLE, flags);
}
public boolean hasCustomColor() {
return Bitfield.isSet(FLAG_CUSTOM_COLOR, flags);
}
public boolean isSlippery() {
return Bitfield.isSet(FLAG_FRICTION_SLIPPERY, flags);
}
public boolean isSkyLit() {
return Bitfield.isSet(FLAG_LIGHT_SKY, flags);
}
public static float getBrightness(byte light) {
// this is a copy of the brightness formula listed here:
// http://gamedev.stackexchange.com/a/21247
final float BASE_BRIGHTNESS = 0.086f;
float normalizedLightValue = (float)light / (float)(LIGHT_VALUE_MAX + 1);
return (float)Math.pow((float)normalizedLightValue, 1.4f) + BASE_BRIGHTNESS;
}
public static byte adjustLightForTranslucency(byte light, float translucency) {
return (byte)Math.round((float)light * translucency);
}
public static Matrix4 getTransformationFor(Tile tile) {
if (Bitfield.isSet(FLAG_FACE_NORTH, tile.flags))
return faceNorthRotation;
else if (Bitfield.isSet(FLAG_FACE_EAST, tile.flags))
return faceEastRotation;
else if (Bitfield.isSet(FLAG_FACE_SOUTH, tile.flags))
return faceSouthRotation;
else if (Bitfield.isSet(FLAG_FACE_WEST, tile.flags))
return faceWestRotation;
else
return null;
}
}

View file

@ -0,0 +1,190 @@
package com.blarg.gdx.tilemap3d;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.utils.Disposable;
public class TileChunk extends TileContainer implements Disposable {
final int x;
final int y;
final int z;
final int width;
final int height;
final int depth;
final Tile[] data;
final BoundingBox bounds;
final BoundingBox tmpBounds = new BoundingBox();
final Vector3 position;
final Vector3 tmpPosition = new Vector3();
public TileChunkMesh mesh;
public TileChunkMesh alphaMesh;
public final TileMap tileMap;
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public int getDepth() {
return depth;
}
@Override
public int getMinX() {
return x;
}
@Override
public int getMinY() {
return y;
}
@Override
public int getMinZ() {
return z;
}
@Override
public int getMaxX() {
return x + width - 1;
}
@Override
public int getMaxY() {
return y + height - 1;
}
@Override
public int getMaxZ() {
return z + depth - 1;
}
@Override
public Vector3 getPosition() {
tmpPosition.set(position);
return tmpPosition;
}
@Override
public BoundingBox getBounds() {
tmpBounds.set(bounds);
return tmpBounds;
}
public int getNumVertices() {
return mesh.mesh != null ? mesh.mesh.getNumVertices() : 0;
}
public int getNumAlphaVertices() {
return alphaMesh.mesh != null ? alphaMesh.mesh.getNumVertices() : 0;
}
public TileChunk(int x, int y, int z, int width, int height, int depth, TileMap tileMap) {
if (tileMap == null)
throw new IllegalArgumentException();
this.tileMap = tileMap;
this.x = x;
this.y = y;
this.z = z;
this.width = width;
this.height = height;
this.depth = depth;
this.position = new Vector3(x, y, z);
bounds = new BoundingBox();
bounds.min.set(x, y, z);
bounds.max.set(x + width, y + height, z + depth);
int numTiles = width * height * depth;
data = new Tile[numTiles];
for (int i = 0; i < numTiles; ++i)
data[i] = new Tile();
mesh = new TileChunkMesh(this);
alphaMesh = new TileChunkMesh(this);
}
public void updateVertices(ChunkVertexGenerator generator) {
generator.generate(this);
}
public Tile getWithinSelfOrNeighbour(int x, int y, int z) {
int checkX = x + this.x;
int checkY = y + this.y;
int checkZ = z + this.z;
return tileMap.get(checkX, checkY, checkZ);
}
public Tile getWithinSelfOrNeighbourSafe(int x, int y, int z) {
int checkX = x + this.x;
int checkY = y + this.y;
int checkZ = z + this.z;
if (!tileMap.isWithinBounds(checkX, checkY, checkZ))
return null;
else
return tileMap.get(checkX, checkY, checkZ);
}
@Override
public Tile get(int x, int y, int z) {
int index = getIndexOf(x, y, z);
return data[index];
}
@Override
public Tile getSafe(int x, int y, int z) {
if (!isWithinLocalBounds(x, y, z))
return null;
else
return get(x, y, z);
}
private int getIndexOf(int x, int y, int z) {
return (y * width * depth) + (z * width) + x;
}
@Override
public void dispose() {
mesh.dispose();
alphaMesh.dispose();
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
TileChunk tileChunk = (TileChunk)o;
if (x != tileChunk.x)
return false;
if (y != tileChunk.y)
return false;
if (z != tileChunk.z)
return false;
if (tileMap != null ? !tileMap.equals(tileChunk.tileMap) : tileChunk.tileMap != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
result = 31 * result + z;
result = 31 * result + (tileMap != null ? tileMap.hashCode() : 0);
return result;
}
}

View file

@ -0,0 +1,29 @@
package com.blarg.gdx.tilemap3d;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g3d.Renderable;
import com.badlogic.gdx.graphics.g3d.materials.Material;
import com.badlogic.gdx.graphics.g3d.materials.TextureAttribute;
import com.badlogic.gdx.utils.Disposable;
import com.blarg.gdx.graphics.TextureAtlas;
public class TileChunkMesh extends Renderable implements Disposable {
public TileChunkMesh(TileChunk chunk) {
meshPartOffset = 0;
meshPartSize = 0;
primitiveType = GL20.GL_TRIANGLES;
bones = null;
lights = null;
shader = null;
userData = null;
TextureAtlas tileMapAtlas = chunk.tileMap.tileMeshes.atlas;
material = new Material(TextureAttribute.createDiffuse(tileMapAtlas.texture));
}
@Override
public void dispose() {
if (mesh != null)
mesh.dispose();
}
}

View file

@ -0,0 +1,281 @@
package com.blarg.gdx.tilemap3d;
import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.math.collision.Ray;
import com.blarg.gdx.math.IntersectionTester;
import com.blarg.gdx.math.MathHelpers;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
public abstract class TileContainer {
static final Vector3 tmp1 = new Vector3();
static final Vector3 tmpTMax = new Vector3();
static final Vector3 tmpTDelta = new Vector3();
static final TileCoord tmpCoords = new TileCoord();
public abstract int getWidth();
public abstract int getHeight();
public abstract int getDepth();
public abstract int getMinX();
public abstract int getMinY();
public abstract int getMinZ();
public abstract int getMaxX();
public abstract int getMaxY();
public abstract int getMaxZ();
public abstract Tile get(int x, int y, int z);
public abstract Tile getSafe(int x, int y, int z);
public abstract Vector3 getPosition();
public abstract BoundingBox getBounds();
public boolean isWithinBounds(int x, int y, int z) {
if (x < getMinX() || x > getMaxX())
return false;
else if (y < getMinY() || y > getMaxY())
return false;
else if (z < getMinZ() || z > getMaxZ())
return false;
else
return true;
}
public boolean isWithinLocalBounds(int x, int y, int z) {
if (x < 0 || x >= getWidth())
return false;
else if (y < 0 || y >= getHeight())
return false;
else if (z < 0 || z >= getDepth())
return false;
else
return true;
}
public void getBoundingBoxFor(int x, int y, int z, BoundingBox result) {
// local "TileContainer space"
result.min.set(x, y, z);
result.max.set(x + 1.0f, y + 1.0f, z + 1.0f); // 1.0f = tile width
// move to "world/tilemap space"
result.min.add(getBounds().min);
result.max.add(getBounds().min);
}
public boolean getOverlappedTiles(BoundingBox box, TileCoord min, TileCoord max) {
// make sure the given box actually intersects with this TileContainer in the first place
if (!IntersectionTester.overlaps(getBounds(), box))
return false;
// convert to tile coords (these will be in "world/tilemap space")
// HACK: ceil() calls and "-1"'s keep us from picking up too many/too few
// tiles. these were arrived at through observation
int minX = (int)box.min.x;
int minY = (int)box.min.y;
int minZ = (int)box.min.z;
int maxX = MathUtils.ceil(box.max.x);
int maxY = MathUtils.ceil(box.max.y - 1.0f);
int maxZ = MathUtils.ceil(box.max.z);
// trim off the excess bounds so that we end up with a min-to-max area
// that is completely within the bounds of this TileContainer
// HACK: "+1"'s ensure we pick up just the right amount of tiles. these were arrived
// at through observation
minX = MathUtils.clamp(minX, getMinX(), getMaxX() + 1);
minY = MathUtils.clamp(minY, getMinY(), getMaxY());
minZ = MathUtils.clamp(minZ, getMinZ(), getMaxZ() + 1);
maxX = MathUtils.clamp(maxX, getMinX(), getMaxX() + 1);
maxY = MathUtils.clamp(maxY, getMinY(), getMaxY());
maxZ = MathUtils.clamp(maxZ, getMinZ(), getMaxZ() + 1);
// return the leftover area, converted to the local coordinate space of the TileContainer
min.x = minX - getMinX();
min.y = minY - getMinY();
min.z = minZ - getMinZ();
max.x = maxX - getMinX();
max.y = maxY - getMinY();
max.z = maxZ - getMinZ();
return true;
}
public boolean checkForCollision(Ray ray, TileCoord collisionCoords) {
// make sure that the ray and this TileContainer can actually collide in the first place
if (!Intersector.intersectRayBounds(ray, getBounds(), tmp1))
return false;
// convert initial collision point to tile coords (this is in "world/tilemap space")
int currentX = (int)tmp1.x;
int currentY = (int)tmp1.y;
int currentZ = (int)tmp1.z;
// make sure the coords are inrange of this container. due to some floating
// point errors / decimal truncating from the above conversion we could
// end up with one or more that are very slightly out of bounds.
// this is still in "world/tilemap space"
currentX = MathUtils.clamp(currentX, getMinX(), getMaxX());
currentY = MathUtils.clamp(currentY, getMinY(), getMaxY());
currentZ = MathUtils.clamp(currentZ, getMinZ(), getMaxZ());
// convert to the local space of this TileContainer
currentX -= getMinX();
currentY -= getMinY();
currentZ -= getMinZ();
// is the start position colliding with a solid tile?
Tile startTile = get(currentX, currentY, currentZ);
if (startTile.isCollideable())
{
// collision found, set the tile coords of the collision
if (collisionCoords != null) {
collisionCoords.x = currentX;
collisionCoords.y = currentY;
collisionCoords.z = currentZ;
}
// and we're done
return true;
}
// no collision initially, continue on with the rest ...
// step increments in "TileContainer tile" units
int stepX = (int)MathHelpers.sign(ray.direction.x);
int stepY = (int)MathHelpers.sign(ray.direction.y);
int stepZ = (int)MathHelpers.sign(ray.direction.z);
// tile boundary (needs to be in "world/tilemap space")
int tileBoundaryX = getMinX() + currentX + (stepX > 0 ? 1 : 0);
int tileBoundaryY = getMinY() + currentY + (stepY > 0 ? 1 : 0);
int tileBoundaryZ = getMinZ() + currentZ + (stepZ > 0 ? 1 : 0);
// HACK: for the tMax and tDelta initial calculations below, if any of the
// components of ray.direction are zero, it will result in "inf"
// components in tMax or tDelta. This is fine, but it has to be
// *positive* "inf", not negative. What I found was that sometimes
// they would be negative, sometimes positive. So, we force them to be
// positive below. Additionally, "nan" components (which will happen
// if both sides of the divisions are zero) are bad, and we need to
// change that up for "inf" as well.
// determine how far we can travel along the ray before we hit a tile boundary
tmpTMax.set(
(tileBoundaryX - ray.origin.x) / ray.direction.x,
(tileBoundaryY - ray.origin.y) / ray.direction.y,
(tileBoundaryZ - ray.origin.z) / ray.direction.z
);
if (tmpTMax.x == Float.NEGATIVE_INFINITY)
tmpTMax.x = Float.POSITIVE_INFINITY;
if (tmpTMax.y == Float.NEGATIVE_INFINITY)
tmpTMax.y = Float.POSITIVE_INFINITY;
if (tmpTMax.z == Float.NEGATIVE_INFINITY)
tmpTMax.z = Float.POSITIVE_INFINITY;
if (Float.isNaN(tmpTMax.x))
tmpTMax.x = Float.POSITIVE_INFINITY;
if (Float.isNaN(tmpTMax.y))
tmpTMax.y = Float.POSITIVE_INFINITY;
if (Float.isNaN(tmpTMax.z))
tmpTMax.z = Float.POSITIVE_INFINITY;
// determine how far we must travel along the ray before we cross a grid cell
tmpTDelta.set(
stepX / ray.direction.x,
stepY / ray.direction.y,
stepZ / ray.direction.z
);
if (tmpTDelta.x == Float.NEGATIVE_INFINITY)
tmpTDelta.x = Float.POSITIVE_INFINITY;
if (tmpTDelta.y == Float.NEGATIVE_INFINITY)
tmpTDelta.y = Float.POSITIVE_INFINITY;
if (tmpTDelta.z == Float.NEGATIVE_INFINITY)
tmpTDelta.z = Float.POSITIVE_INFINITY;
if (Float.isNaN(tmpTDelta.x))
tmpTDelta.x = Float.POSITIVE_INFINITY;
if (Float.isNaN(tmpTDelta.y))
tmpTDelta.y = Float.POSITIVE_INFINITY;
if (Float.isNaN(tmpTDelta.z))
tmpTDelta.z = Float.POSITIVE_INFINITY;
boolean collided = false;
boolean outOfContainer = false;
while (!outOfContainer)
{
// step up to the next tile using the lowest step value
// (in other words, we figure out on which axis, X, Y, or Z, the next
// tile that lies on the ray is closest, and use that axis step increment
// to move us up to get to the next tile location)
if (tmpTMax.x < tmpTMax.y && tmpTMax.x < tmpTMax.z)
{
// tMax.x is lowest, the YZ tile boundary plane is closest
currentX += stepX;
tmpTMax.x += tmpTDelta.x;
}
else if (tmpTMax.y < tmpTMax.z)
{
// tMax.y is lowest, the XZ tile boundary plane is closest
currentY += stepY;
tmpTMax.y += tmpTDelta.y;
}
else
{
// tMax.z is lowest, the XY tile boundary plane is closest
currentZ += stepZ;
tmpTMax.z += tmpTDelta.z;
}
// need to figure out if this new position is still inside the bounds of
// the container before we can attempt to determine if the current tile is
// solid
// (remember, currentX/Y/Z is in the local "TileContainer space"
if (
currentX < 0 || currentX >= getWidth() ||
currentY < 0 || currentY >= getHeight() ||
currentZ < 0 || currentZ >= getDepth()
)
outOfContainer = true;
else
{
// still inside and at the next position, test for a solid tile
Tile tile = get(currentX, currentY, currentZ);
if (tile.isCollideable())
{
collided = true;
// set the tile coords of the collision
if (collisionCoords != null) {
collisionCoords.x = currentX;
collisionCoords.y = currentY;
collisionCoords.z = currentZ;
}
break;
}
}
}
return collided;
}
public boolean checkForCollision(Ray ray, TileCoord collisionCoords, Vector3 tileMeshCollisionPoint) {
// if the ray doesn't collide with any solid tiles in the first place, then
// we can skip this more expensive triangle collision check...
if (!checkForCollision(ray, tmpCoords))
return false;
if (collisionCoords != null)
collisionCoords.set(tmpCoords);
// now perform the per-triangle collision check against the tile position
// where the ray ended up at the end of the above checkForCollision() call
return checkForCollisionWithTileMesh(
ray,
tmpCoords.x, tmpCoords.y, tmpCoords.z,
tileMeshCollisionPoint
);
}
public boolean checkForCollisionWithTileMesh(Ray ray, int x, int y, int z, Vector3 collisionPoint) {
throw new NotImplementedException();
}
}

View file

@ -0,0 +1,105 @@
package com.blarg.gdx.tilemap3d;
import com.badlogic.gdx.math.Vector3;
public final class TileCoord {
public int x;
public int y;
public int z;
public TileCoord() {
x = 0;
y = 0;
z = 0;
}
public TileCoord(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public TileCoord(TileCoord coord) {
x = coord.x;
y = coord.y;
z = coord.z;
}
public TileCoord(Vector3 vector) {
x = (int)vector.x;
y = (int)vector.y;
z = (int)vector.z;
}
public TileCoord set(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
return this;
}
public TileCoord set(TileCoord coord) {
return set(coord.x, coord.y, coord.z);
}
public TileCoord set(Vector3 vector) {
return set((int)vector.x, (int)vector.y, (int)vector.z);
}
public TileCoord add(int x, int y, int z) {
this.x += x;
this.y += y;
this.z += z;
return this;
}
public TileCoord add(TileCoord coord) {
return add(coord.x, coord.y, coord.z);
}
public TileCoord add(Vector3 vector) {
return add((int)vector.x, (int)vector.y, (int)vector.z);
}
public TileCoord sub(int x, int y, int z) {
this.x -= x;
this.y -= y;
this.z -= z;
return this;
}
public TileCoord sub(TileCoord coord) {
return sub(coord.x, coord.y, coord.z);
}
public TileCoord sub(Vector3 vector) {
return sub((int)vector.x, (int)vector.y, (int)vector.z);
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
TileCoord tileCoord = (TileCoord)o;
if (x != tileCoord.x)
return false;
if (y != tileCoord.y)
return false;
if (z != tileCoord.z)
return false;
return true;
}
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
result = 31 * result + z;
return result;
}
}

View file

@ -0,0 +1,262 @@
package com.blarg.gdx.tilemap3d;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.utils.Disposable;
import com.blarg.gdx.math.IntersectionTester;
public class TileMap extends TileContainer implements Disposable {
final TileChunk[] chunks;
final BoundingBox bounds;
final BoundingBox tmpBounds = new BoundingBox();
final Vector3 tmpPosition = new Vector3();
public final int chunkWidth;
public final int chunkHeight;
public final int chunkDepth;
public final int widthInChunks;
public final int heightInChunks;
public final int depthInChunks;
public final TileMeshCollection tileMeshes;
public final ChunkVertexGenerator vertexGenerator;
public final TileMapLighter lighter;
public byte ambientLightValue;
public byte skyLightValue;
@Override
public int getWidth() {
return widthInChunks * chunkWidth;
}
@Override
public int getHeight() {
return heightInChunks * chunkHeight;
}
@Override
public int getDepth() {
return depthInChunks * chunkDepth;
}
@Override
public int getMinX() {
return 0;
}
@Override
public int getMinY() {
return 0;
}
@Override
public int getMinZ() {
return 0;
}
@Override
public int getMaxX() {
return getWidth() - 1;
}
@Override
public int getMaxY() {
return getHeight() - 1;
}
@Override
public int getMaxZ() {
return getDepth() - 1;
}
@Override
public Vector3 getPosition() {
tmpPosition.set(Vector3.Zero);
return tmpPosition;
}
@Override
public BoundingBox getBounds() {
tmpBounds.set(bounds);
return bounds;
}
public TileMap(
int chunkWidth, int chunkHeight, int chunkDepth,
int widthInChunks, int heightInChunks, int depthInChunks,
TileMeshCollection tileMeshes,
ChunkVertexGenerator vertexGenerator,
TileMapLighter lighter
) {
if (tileMeshes == null)
throw new IllegalArgumentException();
if (vertexGenerator == null)
throw new IllegalArgumentException();
this.tileMeshes = tileMeshes;
this.vertexGenerator = vertexGenerator;
this.lighter = lighter;
this.chunkWidth = chunkWidth;
this.chunkHeight = chunkHeight;
this.chunkDepth = chunkDepth;
this.widthInChunks = widthInChunks;
this.heightInChunks = heightInChunks;
this.depthInChunks = depthInChunks;
ambientLightValue = 0;
skyLightValue = Tile.LIGHT_VALUE_SKY;
int numChunks = widthInChunks * heightInChunks * depthInChunks;
chunks = new TileChunk[numChunks];
for (int y = 0; y < heightInChunks; ++y)
{
for (int z = 0; z < depthInChunks; ++z)
{
for (int x = 0; x < widthInChunks; ++x)
{
TileChunk chunk = new TileChunk(
x * chunkWidth, y * chunkHeight, z * chunkDepth,
chunkWidth, chunkHeight, chunkDepth,
this
);
int index = getChunkIndex(x, y, z);
chunks[index] = chunk;
}
}
}
bounds = new BoundingBox();
bounds.min.set(Vector3.Zero);
bounds.max.set(getWidth(), getHeight(), getDepth());
}
public void updateVertices() {
for (int i = 0; i < chunks.length; ++i)
updateChunkVertices(chunks[i]);
}
private void updateChunkVertices(TileChunk chunk) {
chunk.updateVertices(vertexGenerator);
}
public void updateLighting() {
if (lighter != null)
lighter.light(this);
}
public boolean getOverlappedChunks(BoundingBox box, TileCoord min, TileCoord max) {
// make sure the given box actually intersects with the map in the first place
if (!IntersectionTester.overlaps(bounds, box))
return false;
// convert to tile coords. this is in "tilemap space" which is how we want it
// HACK: ceil() calls and "-1"'s keep us from picking up too many/too few
// blocks. these were arrived at through observation
int minX = (int)box.min.x;
int minY = (int)box.min.y;
int minZ = (int)box.min.z;
int maxX = MathUtils.ceil(box.max.x);
int maxY = MathUtils.ceil(box.max.y - 1.0f);
int maxZ = MathUtils.ceil(box.max.z);
// now convert to chunk coords
int minChunkX = minX / chunkWidth;
int minChunkY = minY / chunkHeight;
int minChunkZ = minZ / chunkDepth;
int maxChunkX = maxX / chunkWidth;
int maxChunkY = maxY / chunkHeight;
int maxChunkZ = maxZ / chunkDepth;
// trim off the excess bounds so that we end up with a min-to-max area
// that is completely within the chunk bounds of the map
// HACK: "-1"'s keep us from picking up too many chunks. these were arrived
// at through observation
minChunkX = MathUtils.clamp(minChunkX, 0, widthInChunks);
minChunkY = MathUtils.clamp(minChunkY, 0, (heightInChunks - 1));
minChunkZ = MathUtils.clamp(minChunkZ, 0, depthInChunks);
maxChunkX = MathUtils.clamp(maxChunkX, 0, widthInChunks);
maxChunkY = MathUtils.clamp(maxChunkY, 0, (heightInChunks - 1));
maxChunkZ = MathUtils.clamp(maxChunkZ, 0, depthInChunks);
// return the leftover area
min.x = minChunkX;
min.y = minChunkY;
min.z = minChunkZ;
max.x = maxChunkX;
max.y = maxChunkY;
max.z = maxChunkZ;
return true;
}
@Override
public Tile get(int x, int y, int z) {
TileChunk chunk = getChunkContaining(x, y, z);
int chunkX = x - chunk.x;
int chunkY = y - chunk.y;
int chunkZ = z - chunk.z;
return chunk.get(chunkX, chunkY, chunkZ);
}
@Override
public Tile getSafe(int x, int y, int z) {
if (!isWithinBounds(x, y, z))
return null;
else
return get(x, y, z);
}
public TileChunk getChunk(int chunkX, int chunkY, int chunkZ) {
int index = getChunkIndex(chunkX, chunkY, chunkZ);
return chunks[index];
}
public TileChunk getChunkSafe(int chunkX, int chunkY, int chunkZ) {
if (
(chunkX >= widthInChunks) ||
(chunkY >= heightInChunks) ||
(chunkZ >= depthInChunks)
)
return null;
else
return getChunk(chunkX, chunkY, chunkZ);
}
public TileChunk getChunkNextTo(TileChunk chunk, int chunkOffsetX, int chunkOffsetY, int chunkOffsetZ) {
int checkX = chunk.x + chunkOffsetX;
int checkY = chunk.y + chunkOffsetY;
int checkZ = chunk.z + chunkOffsetZ;
if (
(checkX < 0 || checkX >= widthInChunks) ||
(checkY < 0 || checkY >= heightInChunks) ||
(checkZ < 0 || checkZ >= depthInChunks)
)
return null;
else
return getChunk(checkX, checkY, checkZ);
}
public TileChunk getChunkContaining(int x, int y, int z) {
int index = getChunkIndexAt(x, y, z);
return chunks[index];
}
private int getChunkIndexAt(int x, int y, int z) {
return getChunkIndex(x / chunkWidth, y / chunkHeight, z / chunkDepth);
}
private int getChunkIndex(int chunkX, int chunkY, int chunkZ) {
return (chunkY * widthInChunks * depthInChunks) + (chunkZ * widthInChunks) + chunkX;
}
@Override
public void dispose() {
for (int i = 0; i < chunks.length; ++i)
chunks[i].dispose();
}
}

View file

@ -0,0 +1,5 @@
package com.blarg.gdx.tilemap3d;
public interface TileMapLighter {
void light(TileMap tileMap);
}

View file

@ -0,0 +1,26 @@
package com.blarg.gdx.tilemap3d;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
public class TileMapRenderer {
public void render(ModelBatch modelBatch, Camera camera, TileMap tileMap) {
for (int y = 0; y < tileMap.heightInChunks; ++y)
{
for (int z = 0; z < tileMap.depthInChunks; ++z)
{
for (int x = 0; x < tileMap.widthInChunks; ++x)
{
TileChunk chunk = tileMap.getChunk(x, y, z);
if (camera.frustum.boundsInFrustum(chunk.getBounds()))
{
if (chunk.mesh.mesh.getNumVertices() > 0)
modelBatch.render(chunk.mesh);
if (chunk.alphaMesh.mesh.getNumVertices() > 0)
modelBatch.render(chunk.alphaMesh);
}
}
}
}
}
}

View file

@ -0,0 +1,50 @@
package com.blarg.gdx.tilemap3d;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Disposable;
import com.blarg.gdx.Bitfield;
public abstract class TileMesh implements Disposable {
public static final Vector3 OFFSET = new Vector3(0.5f, 0.5f, 0.5f);
public static final byte SIDE_TOP = 1;
public static final byte SIDE_BOTTOM = 2;
public static final byte SIDE_FRONT = 4;
public static final byte SIDE_BACK = 8;
public static final byte SIDE_LEFT = 16;
public static final byte SIDE_RIGHT = 32;
public static final byte SIDE_ALL = (SIDE_TOP | SIDE_BOTTOM | SIDE_FRONT | SIDE_BACK | SIDE_LEFT | SIDE_RIGHT);
public static final int CUBE_VERTICES_PER_FACE = 6;
public final byte opaqueSides;
public final boolean alpha;
public final float translucency;
public final byte lightValue;
public final Color color;
public abstract Mesh getMesh();
public abstract Vector3[] getCollisionVertices();
public boolean isCompletelyOpaque() {
return opaqueSides == SIDE_ALL;
}
public boolean isOpaque(byte side) {
return Bitfield.isSet(side, opaqueSides);
}
public boolean isLightSource() {
return lightValue > 0;
}
public TileMesh(byte opaqueSides, boolean alpha, float translucency, byte lightValue, Color color) {
this.opaqueSides = opaqueSides;
this.alpha = alpha;
this.translucency = translucency;
this.lightValue = lightValue;
this.color = new Color(color);
}
}

View file

@ -0,0 +1,81 @@
package com.blarg.gdx.tilemap3d;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.utils.Array;
import com.blarg.gdx.Bitfield;
import com.blarg.gdx.graphics.TextureAtlas;
import java.util.Map;
public class TileMeshCollection {
TextureAtlas atlas;
Array<TileMesh> meshes;
public TileMeshCollection(TextureAtlas atlas) {
if (atlas == null)
throw new IllegalArgumentException();
this.atlas = atlas;
this.meshes = new Array<TileMesh>(TileMesh.class);
// the first mesh (index = 0) should always be a null one as this has special meaning
// in other TileMap-related objects (basically, representing empty space)
addMesh(null);
}
private int addMesh(TileMesh mesh) {
meshes.add(mesh);
return meshes.size - 1;
}
public int add(Model model, Map<String, TextureRegion> textures, byte opaqueSides, byte lightValue, boolean alpha, float translucency, Color color) {
ModelTileMesh tileMesh = new ModelTileMesh(model, textures, opaqueSides, lightValue, alpha, translucency, color);
return addMesh(tileMesh);
}
public int add(Model model, Model collisionModel, Map<String, TextureRegion> textures, byte opaqueSides, byte lightValue, boolean alpha, float translucency, Color color) {
ModelTileMesh tileMesh = new ModelTileMesh(model, collisionModel, textures, opaqueSides, lightValue, alpha, translucency, color);
return addMesh(tileMesh);
}
public int addCube(
TextureRegion topTexture,
TextureRegion bottomTexture,
TextureRegion frontTexture,
TextureRegion backTexture,
TextureRegion leftTexture,
TextureRegion rightTexture,
byte opaqueSides, byte lightValue, boolean alpha, float translucency, Color color) {
byte faces = 0;
if (topTexture != null) faces = Bitfield.set(TileMesh.SIDE_TOP, faces);
if (bottomTexture != null) faces = Bitfield.set(TileMesh.SIDE_BOTTOM, faces);
if (frontTexture != null) faces = Bitfield.set(TileMesh.SIDE_FRONT, faces);
if (backTexture != null) faces = Bitfield.set(TileMesh.SIDE_BACK, faces);
if (leftTexture != null) faces = Bitfield.set(TileMesh.SIDE_LEFT, faces);
if (rightTexture != null) faces = Bitfield.set(TileMesh.SIDE_RIGHT, faces);
CubeTileMesh tileMesh = new CubeTileMesh(
topTexture, bottomTexture, frontTexture, backTexture, leftTexture, rightTexture,
faces, opaqueSides, lightValue, alpha, translucency, color
);
return addMesh(tileMesh);
}
public int addCube(TextureRegion texture, byte faces, byte opaqueSides, byte lightValue, boolean alpha, float translucency, Color color) {
CubeTileMesh tileMesh = new CubeTileMesh(
texture, texture, texture, texture, texture, texture,
faces, opaqueSides, lightValue, alpha, translucency, color
);
return addMesh(tileMesh);
}
public TileMesh get(Tile tile) {
return get(tile.tile);
}
public TileMesh get(int index) {
return meshes.items[index];
}
}