ca.blarg.gdx.tilemap3d.TileMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gdx-tilemap3d Show documentation
Show all versions of gdx-tilemap3d Show documentation
Library to handle management and rendering of a game world composed of 3D "tiles" arranged in a uniform 3D grid, via libGDX.
The newest version!
package ca.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 ca.blarg.gdx.math.IntersectionTester;
import ca.blarg.gdx.tilemap3d.lighting.TileMapLighter;
import ca.blarg.gdx.tilemap3d.tilemesh.TileMeshCollection;
public class TileMap extends TileContainer implements Disposable {
final TileChunk[] chunks;
final BoundingBox bounds;
final BoundingBox tmpBounds = new BoundingBox();
final Vector3 tmpPosition = new Vector3();
TileMapUpdater updater;
Thread updaterThread;
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;
public TileChunk[] getChunks() {
return chunks;
}
@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 float getUpdateProgress() {
return updater.currentProgress();
}
public boolean isUpdating() {
return updater.isUpdating();
}
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());
updater = new TileMapUpdater(this);
updaterThread = null;
}
public void updateVertices() {
for (int i = 0; i < chunks.length; ++i)
chunks[i].updateVertices(vertexGenerator);
}
public void beginUpdateVerticesAsync() {
if (isUpdating())
throw new IllegalStateException("Async vertices update for this TileMap is currently underway.");
updaterThread = new Thread(updater);
updaterThread.start();
}
public boolean updateVerticesAsync() {
if (!isUpdating())
return true; // done updating (or we were never updating in the first place ...)
if (updater.isWaitingForVboCreation()) {
TileChunk chunkNeedingVboCreation = updater.chunkNeedingVboCreation;
ChunkVertexGenerator.GeneratedChunkMeshes meshes = vertexGenerator.createMeshFromVertices();
chunkNeedingVboCreation.setMeshes(meshes);
updater.signalDoneVboCreation();
}
return false;
}
public void updateLighting() {
if (isUpdating())
throw new UnsupportedOperationException("Cannot update a TileMap until the current update operation is complete.");
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.test(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() {
updater.signalStop();
for (int i = 0; i < chunks.length; ++i)
chunks[i].dispose();
}
}