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:
parent
913ec916ce
commit
382da2985f
194
src/com/blarg/gdx/tilemap3d/ChunkVertexGenerator.java
Normal file
194
src/com/blarg/gdx/tilemap3d/ChunkVertexGenerator.java
Normal 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);
|
||||
}
|
||||
}
|
373
src/com/blarg/gdx/tilemap3d/CubeTileMesh.java
Normal file
373
src/com/blarg/gdx/tilemap3d/CubeTileMesh.java
Normal 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();
|
||||
}
|
||||
}
|
159
src/com/blarg/gdx/tilemap3d/ModelTileMesh.java
Normal file
159
src/com/blarg/gdx/tilemap3d/ModelTileMesh.java
Normal 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();
|
||||
}
|
||||
}
|
154
src/com/blarg/gdx/tilemap3d/PositionAndSkyTileMapLighter.java
Normal file
154
src/com/blarg/gdx/tilemap3d/PositionAndSkyTileMapLighter.java
Normal 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);
|
||||
}
|
||||
}
|
70
src/com/blarg/gdx/tilemap3d/SimpleTileMapLighter.java
Normal file
70
src/com/blarg/gdx/tilemap3d/SimpleTileMapLighter.java
Normal 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);
|
||||
}
|
||||
}
|
130
src/com/blarg/gdx/tilemap3d/Tile.java
Normal file
130
src/com/blarg/gdx/tilemap3d/Tile.java
Normal 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;
|
||||
}
|
||||
}
|
190
src/com/blarg/gdx/tilemap3d/TileChunk.java
Normal file
190
src/com/blarg/gdx/tilemap3d/TileChunk.java
Normal 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;
|
||||
}
|
||||
}
|
29
src/com/blarg/gdx/tilemap3d/TileChunkMesh.java
Normal file
29
src/com/blarg/gdx/tilemap3d/TileChunkMesh.java
Normal 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();
|
||||
}
|
||||
}
|
281
src/com/blarg/gdx/tilemap3d/TileContainer.java
Normal file
281
src/com/blarg/gdx/tilemap3d/TileContainer.java
Normal 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();
|
||||
}
|
||||
}
|
105
src/com/blarg/gdx/tilemap3d/TileCoord.java
Normal file
105
src/com/blarg/gdx/tilemap3d/TileCoord.java
Normal 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;
|
||||
}
|
||||
}
|
262
src/com/blarg/gdx/tilemap3d/TileMap.java
Normal file
262
src/com/blarg/gdx/tilemap3d/TileMap.java
Normal 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();
|
||||
}
|
||||
}
|
5
src/com/blarg/gdx/tilemap3d/TileMapLighter.java
Normal file
5
src/com/blarg/gdx/tilemap3d/TileMapLighter.java
Normal file
|
@ -0,0 +1,5 @@
|
|||
package com.blarg.gdx.tilemap3d;
|
||||
|
||||
public interface TileMapLighter {
|
||||
void light(TileMap tileMap);
|
||||
}
|
26
src/com/blarg/gdx/tilemap3d/TileMapRenderer.java
Normal file
26
src/com/blarg/gdx/tilemap3d/TileMapRenderer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
50
src/com/blarg/gdx/tilemap3d/TileMesh.java
Normal file
50
src/com/blarg/gdx/tilemap3d/TileMesh.java
Normal 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);
|
||||
}
|
||||
}
|
81
src/com/blarg/gdx/tilemap3d/TileMeshCollection.java
Normal file
81
src/com/blarg/gdx/tilemap3d/TileMeshCollection.java
Normal 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];
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue