From 6e9086029322b2428efb35a91993bb9aad452e8e Mon Sep 17 00:00:00 2001 From: gered Date: Thu, 31 Jan 2013 13:07:46 -0500 Subject: [PATCH] add basic version of 3d tilemap sources --- src/tilemap/chunkrenderer.cpp | 77 ++++ src/tilemap/chunkrenderer.h | 28 ++ src/tilemap/chunkvertexgenerator.cpp | 251 +++++++++++ src/tilemap/chunkvertexgenerator.h | 31 ++ src/tilemap/cubetilemesh.cpp | 282 ++++++++++++ src/tilemap/cubetilemesh.h | 57 +++ src/tilemap/litchunkvertexgenerator.cpp | 91 ++++ src/tilemap/litchunkvertexgenerator.h | 23 + src/tilemap/positionandskytilemaplighter.cpp | 235 ++++++++++ src/tilemap/positionandskytilemaplighter.h | 29 ++ src/tilemap/simpletilemaplighter.cpp | 87 ++++ src/tilemap/simpletilemaplighter.h | 25 ++ src/tilemap/statictilemesh.cpp | 116 +++++ src/tilemap/statictilemesh.h | 37 ++ src/tilemap/tile.cpp | 29 ++ src/tilemap/tile.h | 161 +++++++ src/tilemap/tilechunk.cpp | 417 ++++++++++++++++++ src/tilemap/tilechunk.h | 121 +++++ src/tilemap/tilelightdefs.h | 13 + src/tilemap/tilemap.cpp | 439 +++++++++++++++++++ src/tilemap/tilemap.h | 212 +++++++++ src/tilemap/tilemaplighter.h | 16 + src/tilemap/tilemaprenderer.cpp | 104 +++++ src/tilemap/tilemaprenderer.h | 40 ++ src/tilemap/tilemesh.h | 56 +++ src/tilemap/tilemeshcollection.cpp | 87 ++++ src/tilemap/tilemeshcollection.h | 42 ++ src/tilemap/tilemeshdefs.h | 25 ++ 28 files changed, 3131 insertions(+) create mode 100644 src/tilemap/chunkrenderer.cpp create mode 100644 src/tilemap/chunkrenderer.h create mode 100644 src/tilemap/chunkvertexgenerator.cpp create mode 100644 src/tilemap/chunkvertexgenerator.h create mode 100644 src/tilemap/cubetilemesh.cpp create mode 100644 src/tilemap/cubetilemesh.h create mode 100644 src/tilemap/litchunkvertexgenerator.cpp create mode 100644 src/tilemap/litchunkvertexgenerator.h create mode 100644 src/tilemap/positionandskytilemaplighter.cpp create mode 100644 src/tilemap/positionandskytilemaplighter.h create mode 100644 src/tilemap/simpletilemaplighter.cpp create mode 100644 src/tilemap/simpletilemaplighter.h create mode 100644 src/tilemap/statictilemesh.cpp create mode 100644 src/tilemap/statictilemesh.h create mode 100644 src/tilemap/tile.cpp create mode 100644 src/tilemap/tile.h create mode 100644 src/tilemap/tilechunk.cpp create mode 100644 src/tilemap/tilechunk.h create mode 100644 src/tilemap/tilelightdefs.h create mode 100644 src/tilemap/tilemap.cpp create mode 100644 src/tilemap/tilemap.h create mode 100644 src/tilemap/tilemaplighter.h create mode 100644 src/tilemap/tilemaprenderer.cpp create mode 100644 src/tilemap/tilemaprenderer.h create mode 100644 src/tilemap/tilemesh.h create mode 100644 src/tilemap/tilemeshcollection.cpp create mode 100644 src/tilemap/tilemeshcollection.h create mode 100644 src/tilemap/tilemeshdefs.h diff --git a/src/tilemap/chunkrenderer.cpp b/src/tilemap/chunkrenderer.cpp new file mode 100644 index 0000000..61abc6f --- /dev/null +++ b/src/tilemap/chunkrenderer.cpp @@ -0,0 +1,77 @@ +#include "../framework/debug.h" + +#include "chunkrenderer.h" + +#include "../framework/graphics/blendstate.h" +#include "../framework/graphics/color.h" +#include "../framework/graphics/graphicsdevice.h" +#include "../framework/graphics/renderstate.h" +#include "../framework/graphics/textureatlas.h" +#include "../framework/graphics/vertexbuffer.h" +#include "../framework/math/matrix4x4.h" +#include "tilechunk.h" +#include "tilemap.h" +#include "tilemeshcollection.h" + +ChunkRenderer::ChunkRenderer(GraphicsDevice *graphicsDevice) +{ + STACK_TRACE; + m_graphicsDevice = graphicsDevice; + + m_renderState = new RENDERSTATE_DEFAULT; + ASSERT(m_renderState != NULL); + + m_defaultBlendState = new BLENDSTATE_DEFAULT; + ASSERT(m_defaultBlendState != NULL); + + m_alphaBlendState = new BLENDSTATE_ALPHABLEND; + ASSERT(m_alphaBlendState != NULL); +} + +ChunkRenderer::~ChunkRenderer() +{ + STACK_TRACE; + SAFE_DELETE(m_renderState); + SAFE_DELETE(m_defaultBlendState); + SAFE_DELETE(m_alphaBlendState); +} + +uint32_t ChunkRenderer::Render(const TileChunk *chunk) +{ + STACK_TRACE; + const Texture *texture = chunk->GetTileMap()->GetMeshes()->GetTextureAtlas()->GetTexture(); + + uint32_t numVertices = chunk->GetNumVertices(); + + m_renderState->Apply(); + m_defaultBlendState->Apply(); + m_graphicsDevice->BindTexture(texture); + m_graphicsDevice->BindVertexBuffer(chunk->GetVertices()); + m_graphicsDevice->RenderTriangles(0, numVertices / 3); + m_graphicsDevice->UnbindVertexBuffer(); + + return numVertices; +} + +uint32_t ChunkRenderer::RenderAlpha(const TileChunk *chunk) +{ + STACK_TRACE; + uint32_t numVertices = 0; + + if (chunk->IsAlphaEnabled()) + { + const Texture *texture = chunk->GetTileMap()->GetMeshes()->GetTextureAtlas()->GetTexture(); + + numVertices = chunk->GetNumAlphaVertices(); + + m_renderState->Apply(); + m_alphaBlendState->Apply(); + m_graphicsDevice->BindTexture(texture); + m_graphicsDevice->BindVertexBuffer(chunk->GetAlphaVertices()); + m_graphicsDevice->RenderTriangles(0, numVertices / 3); + m_graphicsDevice->UnbindVertexBuffer(); + } + + return numVertices; +} + diff --git a/src/tilemap/chunkrenderer.h b/src/tilemap/chunkrenderer.h new file mode 100644 index 0000000..342007d --- /dev/null +++ b/src/tilemap/chunkrenderer.h @@ -0,0 +1,28 @@ +#ifndef __TILEMAP_CHUNKRENDERER_H_INCLUDED__ +#define __TILEMAP_CHUNKRENDERER_H_INCLUDED__ + +#include "../framework/common.h" + +class BlendState; +class GraphicsDevice; +class RenderState; +class TileChunk; + +class ChunkRenderer +{ +public: + ChunkRenderer(GraphicsDevice *graphicsDevice); + virtual ~ChunkRenderer(); + + uint32_t Render(const TileChunk *chunk); + uint32_t RenderAlpha(const TileChunk *chunk); + +private: + GraphicsDevice *m_graphicsDevice; + RenderState *m_renderState; + BlendState *m_defaultBlendState; + BlendState *m_alphaBlendState; +}; + +#endif + diff --git a/src/tilemap/chunkvertexgenerator.cpp b/src/tilemap/chunkvertexgenerator.cpp new file mode 100644 index 0000000..426f019 --- /dev/null +++ b/src/tilemap/chunkvertexgenerator.cpp @@ -0,0 +1,251 @@ +#include "../framework/debug.h" +#include "../framework/common.h" + +#include "chunkvertexgenerator.h" + +#include "../framework/graphics/color.h" +#include "../framework/graphics/vertexbuffer.h" +#include "../framework/math/matrix4x4.h" +#include "../framework/math/point3.h" +#include "../framework/math/vector3.h" +#include "cubetilemesh.h" +#include "tilechunk.h" +#include "tilemap.h" +#include "tilemesh.h" +#include "tilemeshcollection.h" +#include "tilemeshdefs.h" + +ChunkVertexGenerator::ChunkVertexGenerator() +{ + STACK_TRACE; +} + +ChunkVertexGenerator::~ChunkVertexGenerator() +{ + STACK_TRACE; +} + +void ChunkVertexGenerator::Generate(TileChunk *chunk, uint32_t &numVertices, uint32_t &numAlphaVertices) +{ + STACK_TRACE; + numVertices = 0; + numAlphaVertices = 0; + + chunk->GetVertices()->MoveToStart(); + if (chunk->IsAlphaEnabled()) + chunk->GetAlphaVertices()->MoveToStart(); + + const TileMap *tileMap = chunk->GetTileMap(); + + for (uint32_t y = 0; y < chunk->GetHeight(); ++y) + { + for (uint32_t z = 0; z < chunk->GetDepth(); ++z) + { + for (uint32_t x = 0; x < chunk->GetWidth(); ++x) + { + Tile *tile = chunk->Get(x, y, z); + if (tile->tile == NO_TILE) + continue; + + const TileMesh *mesh = chunk->GetTileMap()->GetMeshes()->Get(tile); + + // enable alpha for this chunk if this tile has an alpha-enabled + // mesh and we haven't done that already + // TODO: optimize so that we only do this if the tile is visible + if (mesh->IsAlpha()) + { + if (!chunk->IsAlphaEnabled()) + chunk->EnableAlphaVertices(TRUE); + } + + // "tilemap space" position that this tile is at + Point3 position; + position.x = x + (int32_t)chunk->GetPosition().x; + position.y = y + (int32_t)chunk->GetPosition().y; + position.z = z + (int32_t)chunk->GetPosition().z; + + const Matrix4x4 *transform = tile->GetTransformationMatrix(); + + // tile color + Color color; + if (tile->HasCustomColor()) + color = Color::FromInt(tile->color); + else + color = mesh->GetColor(); + + if (mesh->GetType() == TILEMESH_CUBE) + { + 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 == NO_TILE || !tileMap->GetMeshes()->Get(left)->IsOpaque(SIDE_RIGHT)) && cubeMesh->HasFace(SIDE_LEFT)) + { + // left face is visible + if (cubeMesh->IsAlpha()) + numAlphaVertices += AddMesh(cubeMesh, chunk, TRUE, position, transform, color, cubeMesh->GetLeftFaceVertexOffset(), CUBE_VERTICES_PER_FACE); + else + numVertices += AddMesh(cubeMesh, chunk, FALSE, position, transform, color, cubeMesh->GetLeftFaceVertexOffset(), CUBE_VERTICES_PER_FACE); + } + if ((right == NULL || right->tile == NO_TILE || !tileMap->GetMeshes()->Get(right)->IsOpaque(SIDE_LEFT)) && cubeMesh->HasFace(SIDE_RIGHT)) + { + // right face is visible + if (cubeMesh->IsAlpha()) + numAlphaVertices += AddMesh(cubeMesh, chunk, TRUE, position, transform, color, cubeMesh->GetRightFaceVertexOffset(), CUBE_VERTICES_PER_FACE); + else + numVertices += AddMesh(cubeMesh, chunk, FALSE, position, transform, color, cubeMesh->GetRightFaceVertexOffset(), CUBE_VERTICES_PER_FACE); + } + if ((forward == NULL || forward->tile == NO_TILE || !tileMap->GetMeshes()->Get(forward)->IsOpaque(SIDE_BACK)) && cubeMesh->HasFace(SIDE_FRONT)) + { + // front face is visible + if (cubeMesh->IsAlpha()) + numAlphaVertices += AddMesh(cubeMesh, chunk, TRUE, position, transform, color, cubeMesh->GetFrontFaceVertexOffset(), CUBE_VERTICES_PER_FACE); + else + numVertices += AddMesh(cubeMesh, chunk, FALSE, position, transform, color, cubeMesh->GetFrontFaceVertexOffset(), CUBE_VERTICES_PER_FACE); + } + if ((backward == NULL || backward->tile == NO_TILE || !tileMap->GetMeshes()->Get(backward)->IsOpaque(SIDE_FRONT)) && cubeMesh->HasFace(SIDE_BACK)) + { + // back face is visible + if (cubeMesh->IsAlpha()) + numAlphaVertices += AddMesh(cubeMesh, chunk, TRUE, position, transform, color, cubeMesh->GetBackFaceVertexOffset(), CUBE_VERTICES_PER_FACE); + else + numVertices += AddMesh(cubeMesh, chunk, FALSE, position, transform, color, cubeMesh->GetBackFaceVertexOffset(), CUBE_VERTICES_PER_FACE); + } + if ((down == NULL || down->tile == NO_TILE || !tileMap->GetMeshes()->Get(down)->IsOpaque(SIDE_TOP)) && cubeMesh->HasFace(SIDE_BOTTOM)) + { + // bottom face is visible + if (cubeMesh->IsAlpha()) + numAlphaVertices += AddMesh(cubeMesh, chunk, TRUE, position, transform, color, cubeMesh->GetBottomFaceVertexOffset(), CUBE_VERTICES_PER_FACE); + else + numVertices += AddMesh(cubeMesh, chunk, FALSE, position, transform, color, cubeMesh->GetBottomFaceVertexOffset(), CUBE_VERTICES_PER_FACE); + } + if ((up == NULL || up->tile == NO_TILE || !tileMap->GetMeshes()->Get(up)->IsOpaque(SIDE_BOTTOM)) && cubeMesh->HasFace(SIDE_TOP)) + { + // top face is visible + if (cubeMesh->IsAlpha()) + numAlphaVertices += AddMesh(cubeMesh, chunk, TRUE, position, transform, color, cubeMesh->GetTopFaceVertexOffset(), CUBE_VERTICES_PER_FACE); + else + numVertices += AddMesh(cubeMesh, chunk, FALSE, position, transform, color, cubeMesh->GetTopFaceVertexOffset(), CUBE_VERTICES_PER_FACE); + } + } + else + { + BOOL 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 == NO_TILE || !tileMap->GetMeshes()->Get(left)->IsOpaque(SIDE_RIGHT)) || + (right == NULL || right->tile == NO_TILE || !tileMap->GetMeshes()->Get(right)->IsOpaque(SIDE_LEFT)) || + (forward == NULL || forward->tile == NO_TILE || !tileMap->GetMeshes()->Get(forward)->IsOpaque(SIDE_BACK)) || + (backward == NULL || backward->tile == NO_TILE || !tileMap->GetMeshes()->Get(backward)->IsOpaque(SIDE_FRONT)) || + (up == NULL || up->tile == NO_TILE || !tileMap->GetMeshes()->Get(up)->IsOpaque(SIDE_BOTTOM)) || + (down == NULL || down->tile == NO_TILE || !tileMap->GetMeshes()->Get(down)->IsOpaque(SIDE_TOP)) + ) + visible = TRUE; + + if (visible) + { + if (mesh->IsAlpha()) + numAlphaVertices += AddMesh(mesh, chunk, TRUE, position, transform, color, 0, mesh->GetBuffer()->GetNumElements()); + else + numVertices += AddMesh(mesh, chunk, FALSE, position, transform, color, 0, mesh->GetBuffer()->GetNumElements()); + } + } + } + } + } + + if (numAlphaVertices == 0) + chunk->EnableAlphaVertices(FALSE); +} + +uint32_t ChunkVertexGenerator::AddMesh(const TileMesh *mesh, TileChunk *chunk, BOOL isAlpha, const Point3 &position, const Matrix4x4 *transform, const Color &color, uint32_t firstVertex, uint32_t numVertices) +{ + STACK_TRACE; + VertexBuffer *sourceBuffer = mesh->GetBuffer(); + sourceBuffer->MoveToStart(); + + VertexBuffer *destBuffer; + if (isAlpha) + destBuffer = chunk->GetAlphaVertices(); + else + destBuffer = chunk->GetVertices(); + + ASSERT(firstVertex < sourceBuffer->GetNumElements()); + ASSERT((firstVertex + numVertices - 1) < sourceBuffer->GetNumElements()); + + // ensure there is enough space in the destination buffer + uint32_t verticesToAdd = numVertices; + if (destBuffer->GetRemainingSpace() < verticesToAdd) + { + // not enough space, need to resize the destination buffer + // resize by the exact amount needed making sure there's no wasted space at the end + destBuffer->Extend(verticesToAdd - destBuffer->GetRemainingSpace()); + ASSERT(destBuffer->GetRemainingSpace() >= verticesToAdd); + } + + // 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 + Vector3 positionOffset = TILEMESH_OFFSET; + positionOffset.x += (float)position.x; + positionOffset.y += (float)position.y; + positionOffset.z += (float)position.z; + + // copy vertices + sourceBuffer->MoveTo(firstVertex); + for (uint32_t i = 0; i < numVertices; ++i) + { + CopyVertex(chunk, sourceBuffer, destBuffer, positionOffset, transform, color); + + sourceBuffer->MoveNext(); + destBuffer->MoveNext(); + } + + return verticesToAdd; +} + +void ChunkVertexGenerator::CopyVertex(const TileChunk *chunk, VertexBuffer *sourceBuffer, VertexBuffer *destBuffer, const Vector3 &positionOffset, const Matrix4x4 *transform, const Color &color) +{ + Vector3 v = sourceBuffer->GetCurrentPosition3(); + Vector3 n = sourceBuffer->GetCurrentNormal(); + + if (transform != NULL) + { + // need to transform the vertex + normal first before copying it + v = v * (*transform); + n = n * (*transform); + } + + // translate vertex into "tilemap space" + v += positionOffset; + + // copy to destination + destBuffer->SetCurrentPosition3(v); + destBuffer->SetCurrentNormal(n); + + // just directly copy the tex coord as-is + destBuffer->SetCurrentTexCoord(sourceBuffer->GetCurrentTexCoord()); + + // color is the same for the entire mesh + destBuffer->SetCurrentColor(color); +} + diff --git a/src/tilemap/chunkvertexgenerator.h b/src/tilemap/chunkvertexgenerator.h new file mode 100644 index 0000000..afcec42 --- /dev/null +++ b/src/tilemap/chunkvertexgenerator.h @@ -0,0 +1,31 @@ +#ifndef __TILEMAP_CHUNKVERTEXGENERATOR_H_INCLUDED__ +#define __TILEMAP_CHUNKVERTEXGENERATOR_H_INCLUDED__ + +#include "../framework/common.h" + +class CubeTileMesh; +class StaticTileMesh; +class TileMesh; +class VertexBuffer; +struct Color; +struct Matrix4x4; +struct Point3; +struct Vector3; + +class TileChunk; + +class ChunkVertexGenerator +{ +public: + ChunkVertexGenerator(); + virtual ~ChunkVertexGenerator(); + + void Generate(TileChunk *chunk, uint32_t &numVertices, uint32_t &numAlphaVertices); + +private: + uint32_t AddMesh(const TileMesh *mesh, TileChunk *chunk, BOOL isAlpha, const Point3 &position, const Matrix4x4 *transform, const Color &color, uint32_t firstVertex, uint32_t numVertices); + virtual void CopyVertex(const TileChunk *chunk, VertexBuffer *sourceBuffer, VertexBuffer *destBuffer, const Vector3 &positionOffset, const Matrix4x4 *transform, const Color &color); +}; + +#endif + diff --git a/src/tilemap/cubetilemesh.cpp b/src/tilemap/cubetilemesh.cpp new file mode 100644 index 0000000..6177890 --- /dev/null +++ b/src/tilemap/cubetilemesh.cpp @@ -0,0 +1,282 @@ +#include "../framework/debug.h" + +#include "cubetilemesh.h" +#include "tilemeshdefs.h" +#include "../framework/graphics/color.h" +#include "../framework/graphics/vertexbuffer.h" +#include "../framework/math/mathhelpers.h" +#include "../framework/math/rectf.h" +#include "../framework/math/vector2.h" +#include "../framework/math/vector3.h" + +CubeTileMesh::CubeTileMesh(CUBE_FACES faces, const RectF *textureAtlasTileBoundaries, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue, BOOL alpha, float translucency, const Color &color) +{ + STACK_TRACE; + m_faces = faces; + + SetOpaque(opaqueSides); + SetAlpha(alpha); + SetColor(color); + SetTranslucency(translucency); + SetLight(lightValue); + + m_topFaceVertexOffset = 0; + m_bottomFaceVertexOffset = 0; + m_frontFaceVertexOffset = 0; + m_backFaceVertexOffset = 0; + m_leftFaceVertexOffset = 0; + m_rightFaceVertexOffset = 0; + + uint32_t numVertices = 0; + + if (HasFace(SIDE_TOP)) + { + m_topFaceVertexOffset = numVertices; + numVertices += CUBE_VERTICES_PER_FACE; + } + if (HasFace(SIDE_BOTTOM)) + { + m_bottomFaceVertexOffset = numVertices; + numVertices += CUBE_VERTICES_PER_FACE; + } + if (HasFace(SIDE_FRONT)) + { + m_frontFaceVertexOffset = numVertices; + numVertices += CUBE_VERTICES_PER_FACE; + } + if (HasFace(SIDE_BACK)) + { + m_backFaceVertexOffset = numVertices; + numVertices += CUBE_VERTICES_PER_FACE; + } + if (HasFace(SIDE_LEFT)) + { + m_leftFaceVertexOffset = numVertices; + numVertices += CUBE_VERTICES_PER_FACE; + } + if (HasFace(SIDE_RIGHT)) + { + m_rightFaceVertexOffset = numVertices; + numVertices += CUBE_VERTICES_PER_FACE; + } + + m_vertices = new VertexBuffer(BUFFEROBJECT_USAGE_STATIC); + ASSERT(m_vertices != NULL); + m_vertices->AddAttribute(VERTEX_POS_3D); + m_vertices->AddAttribute(VERTEX_NORMAL); + m_vertices->AddAttribute(VERTEX_COLOR); + m_vertices->AddAttribute(VERTEX_TEXCOORD); + m_vertices->Create(numVertices); + + SetupFaceVertices(textureAtlasTileBoundaries); + SetupCollisionVertices(); +} + +CubeTileMesh::~CubeTileMesh() +{ + STACK_TRACE; + SAFE_DELETE(m_vertices); +} + +void CubeTileMesh::SetupFaceVertices(const RectF *textureAtlasTileBoundaries) +{ + STACK_TRACE; + uint32_t pos = 0; + Vector3 a(-0.5f, -0.5f, -0.5f); + Vector3 b(0.5f, 0.5f, 0.5f); + + if (HasFace(SIDE_TOP)) + { + pos = m_topFaceVertexOffset; + + m_vertices->SetPosition3(pos, Vector3(a.x, b.y, b.z)); + m_vertices->SetNormal(pos, UP); + m_vertices->SetTexCoord(pos, ScaleTexCoord(Vector2(0.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 1, Vector3(b.x, b.y, b.z)); + m_vertices->SetNormal(pos + 1, UP); + m_vertices->SetTexCoord(pos + 1, ScaleTexCoord(Vector2(1.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 2, Vector3(a.x, b.y, a.z)); + m_vertices->SetNormal(pos + 2, UP); + m_vertices->SetTexCoord(pos + 2, ScaleTexCoord(Vector2(0.0f, 0.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 3, Vector3(b.x, b.y, b.z)); + m_vertices->SetNormal(pos + 3, UP); + m_vertices->SetTexCoord(pos + 3, ScaleTexCoord(Vector2(1.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 4, Vector3(b.x, b.y, a.z)); + m_vertices->SetNormal(pos + 4, UP); + m_vertices->SetTexCoord(pos + 4, ScaleTexCoord(Vector2(1.0f, 0.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 5, Vector3(a.x, b.y, a.z)); + m_vertices->SetNormal(pos + 5, UP); + m_vertices->SetTexCoord(pos + 5, ScaleTexCoord(Vector2(0.0f, 0.0f), *textureAtlasTileBoundaries)); + } + + if (HasFace(SIDE_BOTTOM)) + { + pos = m_bottomFaceVertexOffset; + + m_vertices->SetPosition3(pos, Vector3(b.x, a.y, b.z)); + m_vertices->SetNormal(pos, DOWN); + m_vertices->SetTexCoord(pos, ScaleTexCoord(Vector2(0.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 1, Vector3(a.x, a.y, b.z)); + m_vertices->SetNormal(pos + 1, DOWN); + m_vertices->SetTexCoord(pos + 1, ScaleTexCoord(Vector2(1.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 2, Vector3(b.x, a.y, a.z)); + m_vertices->SetNormal(pos + 2, DOWN); + m_vertices->SetTexCoord(pos + 2, ScaleTexCoord(Vector2(0.0f, 0.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 3, Vector3(a.x, a.y, b.z)); + m_vertices->SetNormal(pos + 3, DOWN); + m_vertices->SetTexCoord(pos + 3, ScaleTexCoord(Vector2(1.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 4, Vector3(a.x, a.y, a.z)); + m_vertices->SetNormal(pos + 4, DOWN); + m_vertices->SetTexCoord(pos + 4, ScaleTexCoord(Vector2(1.0f, 0.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 5, Vector3(b.x, a.y, a.z)); + m_vertices->SetNormal(pos + 5, DOWN); + m_vertices->SetTexCoord(pos + 5, ScaleTexCoord(Vector2(0.0f, 0.0f), *textureAtlasTileBoundaries)); + } + + if (HasFace(SIDE_FRONT)) + { + pos = m_frontFaceVertexOffset; + + m_vertices->SetPosition3(pos, Vector3(b.x, a.y, a.z)); + m_vertices->SetNormal(pos, FORWARD); + m_vertices->SetTexCoord(pos, ScaleTexCoord(Vector2(0.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 1, Vector3(a.x, a.y, a.z)); + m_vertices->SetNormal(pos + 1, FORWARD); + m_vertices->SetTexCoord(pos + 1, ScaleTexCoord(Vector2(1.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 2, Vector3(b.x, b.y, a.z)); + m_vertices->SetNormal(pos + 2, FORWARD); + m_vertices->SetTexCoord(pos + 2, ScaleTexCoord(Vector2(0.0f, 0.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 3, Vector3(a.x, a.y, a.z)); + m_vertices->SetNormal(pos + 3, FORWARD); + m_vertices->SetTexCoord(pos + 3, ScaleTexCoord(Vector2(1.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 4, Vector3(a.x, b.y, a.z)); + m_vertices->SetNormal(pos + 4, FORWARD); + m_vertices->SetTexCoord(pos + 4, ScaleTexCoord(Vector2(1.0f, 0.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 5, Vector3(b.x, b.y, a.z)); + m_vertices->SetNormal(pos + 5, FORWARD); + m_vertices->SetTexCoord(pos + 5, ScaleTexCoord(Vector2(0.0f, 0.0f), *textureAtlasTileBoundaries)); + } + + if (HasFace(SIDE_BACK)) + { + pos = m_backFaceVertexOffset; + + m_vertices->SetPosition3(pos, Vector3(a.x, a.y, b.z)); + m_vertices->SetNormal(pos, BACKWARD); + m_vertices->SetTexCoord(pos, ScaleTexCoord(Vector2(0.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 1, Vector3(b.x, a.y, b.z)); + m_vertices->SetNormal(pos + 1, BACKWARD); + m_vertices->SetTexCoord(pos + 1, ScaleTexCoord(Vector2(1.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 2, Vector3(a.x, b.y, b.z)); + m_vertices->SetNormal(pos + 2, BACKWARD); + m_vertices->SetTexCoord(pos + 2, ScaleTexCoord(Vector2(0.0f, 0.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 3, Vector3(b.x, a.y, b.z)); + m_vertices->SetNormal(pos + 3, BACKWARD); + m_vertices->SetTexCoord(pos + 3, ScaleTexCoord(Vector2(1.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 4, Vector3(b.x, b.y, b.z)); + m_vertices->SetNormal(pos + 4, BACKWARD); + m_vertices->SetTexCoord(pos + 4, ScaleTexCoord(Vector2(1.0f, 0.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 5, Vector3(a.x, b.y, b.z)); + m_vertices->SetNormal(pos + 5, BACKWARD); + m_vertices->SetTexCoord(pos + 5, ScaleTexCoord(Vector2(0.0f, 0.0f), *textureAtlasTileBoundaries)); + } + + if (HasFace(SIDE_LEFT)) + { + pos = m_leftFaceVertexOffset; + + m_vertices->SetPosition3(pos, Vector3(a.x, a.y, a.z)); + m_vertices->SetNormal(pos, LEFT); + m_vertices->SetTexCoord(pos, ScaleTexCoord(Vector2(0.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 1, Vector3(a.x, a.y, b.z)); + m_vertices->SetNormal(pos + 1, LEFT); + m_vertices->SetTexCoord(pos + 1, ScaleTexCoord(Vector2(1.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 2, Vector3(a.x, b.y, a.z)); + m_vertices->SetNormal(pos + 2, LEFT); + m_vertices->SetTexCoord(pos + 2, ScaleTexCoord(Vector2(0.0f, 0.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 3, Vector3(a.x, a.y, b.z)); + m_vertices->SetNormal(pos + 3, LEFT); + m_vertices->SetTexCoord(pos + 3, ScaleTexCoord(Vector2(1.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 4, Vector3(a.x, b.y, b.z)); + m_vertices->SetNormal(pos + 4, LEFT); + m_vertices->SetTexCoord(pos + 4, ScaleTexCoord(Vector2(1.0f, 0.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 5, Vector3(a.x, b.y, a.z)); + m_vertices->SetNormal(pos + 5, LEFT); + m_vertices->SetTexCoord(pos + 5, ScaleTexCoord(Vector2(0.0f, 0.0f), *textureAtlasTileBoundaries)); + } + + if (HasFace(SIDE_RIGHT)) + { + pos = m_rightFaceVertexOffset; + + m_vertices->SetPosition3(pos, Vector3(b.x, a.y, b.z)); + m_vertices->SetNormal(pos, RIGHT); + m_vertices->SetTexCoord(pos, ScaleTexCoord(Vector2(0.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 1, Vector3(b.x, a.y, a.z)); + m_vertices->SetNormal(pos + 1, RIGHT); + m_vertices->SetTexCoord(pos + 1, ScaleTexCoord(Vector2(1.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 2, Vector3(b.x, b.y, b.z)); + m_vertices->SetNormal(pos + 2, RIGHT); + m_vertices->SetTexCoord(pos + 2, ScaleTexCoord(Vector2(0.0f, 0.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 3, Vector3(b.x, a.y, a.z)); + m_vertices->SetNormal(pos + 3, RIGHT); + m_vertices->SetTexCoord(pos + 3, ScaleTexCoord(Vector2(1.0f, 1.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 4, Vector3(b.x, b.y, a.z)); + m_vertices->SetNormal(pos + 4, RIGHT); + m_vertices->SetTexCoord(pos + 4, ScaleTexCoord(Vector2(1.0f, 0.0f), *textureAtlasTileBoundaries)); + + m_vertices->SetPosition3(pos + 5, Vector3(b.x, b.y, b.z)); + m_vertices->SetNormal(pos + 5, RIGHT); + m_vertices->SetTexCoord(pos + 5, ScaleTexCoord(Vector2(0.0f, 0.0f), *textureAtlasTileBoundaries)); + } +} + +void CubeTileMesh::SetupCollisionVertices() +{ + STACK_TRACE; + m_numCollisionVertices = m_vertices->GetNumElements(); + + m_collisionVertices = new Vector3[m_numCollisionVertices]; + ASSERT(m_collisionVertices != NULL); + + for (uint32_t i = 0; i < m_numCollisionVertices; ++i) + m_collisionVertices[i] = m_vertices->GetPosition3(i); +} + +inline Vector2 CubeTileMesh::ScaleTexCoord(const Vector2 &texCoord, const RectF &tileBoundaries) const +{ + Vector2 out; + out.x = ScaleRange(texCoord.x, 0.0f, 1.0f, tileBoundaries.left, tileBoundaries.right); + out.y = ScaleRange(texCoord.y, 0.0f, 1.0f, tileBoundaries.top, tileBoundaries.bottom); + return out; +} + diff --git a/src/tilemap/cubetilemesh.h b/src/tilemap/cubetilemesh.h new file mode 100644 index 0000000..b5f1457 --- /dev/null +++ b/src/tilemap/cubetilemesh.h @@ -0,0 +1,57 @@ +#ifndef __TILEMAP_CUBETILEMESH_H_INCLUDED__ +#define __TILEMAP_CUBETILEMESH_H_INCLUDED__ + +#include "../framework/common.h" +#include "tilelightdefs.h" +#include "tilemesh.h" +#include "tilemeshdefs.h" +#include "../framework/graphics/color.h" + +class VertexBuffer; +struct RectF; +struct Vector2; +struct Vector3; + +class CubeTileMesh : public TileMesh +{ +public: + CubeTileMesh(CUBE_FACES faces, const RectF *textureAtlasTileBoundaries, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue, BOOL alpha, float translucency = 1.0f, const Color &color = COLOR_WHITE); + virtual ~CubeTileMesh(); + + VertexBuffer* GetBuffer() const { return m_vertices; } + uint32_t GetTopFaceVertexOffset() const { return m_topFaceVertexOffset; } + uint32_t GetBottomFaceVertexOffset() const { return m_bottomFaceVertexOffset; } + uint32_t GetFrontFaceVertexOffset() const { return m_frontFaceVertexOffset; } + uint32_t GetBackFaceVertexOffset() const { return m_backFaceVertexOffset; } + uint32_t GetLeftFaceVertexOffset() const { return m_leftFaceVertexOffset; } + uint32_t GetRightFaceVertexOffset() const { return m_rightFaceVertexOffset; } + + uint32_t GetNumCollisionVertices() const { return m_numCollisionVertices; } + const Vector3* GetCollisionVertices() const { return m_collisionVertices; } + + CUBE_FACES GetFaces() const { return m_faces; } + BOOL HasFace(CUBE_FACES face) const { return IsBitSet(face, m_faces); } + + TILEMESH_TYPE GetType() const { return TILEMESH_CUBE; } + +private: + void SetupFaceVertices(const RectF *textureAtlasTileBoundaries); + void SetupCollisionVertices(); + + Vector2 ScaleTexCoord(const Vector2 &texCoord, const RectF &tileBoundaries) const; + + VertexBuffer *m_vertices; + uint32_t m_topFaceVertexOffset; + uint32_t m_bottomFaceVertexOffset; + uint32_t m_frontFaceVertexOffset; + uint32_t m_backFaceVertexOffset; + uint32_t m_leftFaceVertexOffset; + uint32_t m_rightFaceVertexOffset; + uint32_t m_numCollisionVertices; + Vector3 *m_collisionVertices; + CUBE_FACES m_faces; +}; + + +#endif + diff --git a/src/tilemap/litchunkvertexgenerator.cpp b/src/tilemap/litchunkvertexgenerator.cpp new file mode 100644 index 0000000..6707152 --- /dev/null +++ b/src/tilemap/litchunkvertexgenerator.cpp @@ -0,0 +1,91 @@ +#include "../framework/debug.h" + +#include "litchunkvertexgenerator.h" +#include "tile.h" +#include "tilechunk.h" +#include "tilelightdefs.h" +#include "tilemap.h" +#include "../framework/graphics/color.h" +#include "../framework/graphics/vertexbuffer.h" +#include "../framework/math/matrix4x4.h" +#include "../framework/math/vector3.h" + +LitChunkVertexGenerator::LitChunkVertexGenerator() +{ + STACK_TRACE; +} + +LitChunkVertexGenerator::~LitChunkVertexGenerator() +{ + STACK_TRACE; +} + +void LitChunkVertexGenerator::CopyVertex(const TileChunk *chunk, VertexBuffer *sourceBuffer, VertexBuffer *destBuffer, const Vector3 &positionOffset, const Matrix4x4 *transform, const Color &color) +{ + // figure out what the default lighting value is for this chunk + TILE_LIGHT_VALUE defaultLightValue = chunk->GetTileMap()->GetSkyLightValue(); + if (chunk->GetTileMap()->GetAmbientLightValue() > defaultLightValue) + defaultLightValue = chunk->GetTileMap()->GetAmbientLightValue(); + + Vector3 v = sourceBuffer->GetCurrentPosition3(); + Vector3 n = sourceBuffer->GetCurrentNormal(); + + if (transform != NULL) + { + // need to transform the vertex + normal first before copying it + v = v * (*transform); + n = n * (*transform); + } + + // translate vertex into "tilemap space" + v += positionOffset; + + destBuffer->SetCurrentPosition3(v); + destBuffer->SetCurrentNormal(n); + + // just directly copy the tex coord as-is + destBuffer->SetCurrentTexCoord(sourceBuffer->GetCurrentTexCoord()); + + // the color we set to the destination determines the brightness (lighting) + + // use the tile that's adjacent to this one in the direction that + // this vertex's normal is pointing as the light source + Vector3 lightSource = positionOffset + n; + + // if the light source position is off the bounds of the entire world + // then use the default light value. + // the below call to TileChunk::GetWithinSelfOrNeighbour() actually does + // do bounds checking, but we would need to cast from float to int + // first. this causes some issues when the one or more of the + // lightSource x/y/z values are between 0 and -1 (rounds up to 0 when + // using a cast). rather then do some weird custom rounding, we just + // check for negatives to ensure we catch them and handle it properly + // NOTE: this is only a problem currently because world coords are + // always >= 0. this will need to be adjusted if that changes + float brightness; + if (lightSource.x < 0.0f || lightSource.y < 0.0f || lightSource.z < 0.0f) + brightness = Tile::GetBrightness(defaultLightValue); + else + { + // light source is within the boundaries of the world, get the + // actual tile (may or may not be in a neighbouring chunk) + int32_t lightX = (int32_t)lightSource.x - chunk->GetX(); + int32_t lightY = (int32_t)lightSource.y - chunk->GetY(); + int32_t lightZ = (int32_t)lightSource.z - chunk->GetZ(); + + const Tile *lightTile = chunk->GetWithinSelfOrNeighbourSafe(lightX, lightY, lightZ); + if (lightTile == NULL) + brightness = Tile::GetBrightness(defaultLightValue); + else + brightness = lightTile->GetBrightness(); + } + + Color resultingColor; + resultingColor.r = color.r * brightness; + resultingColor.g = color.g * brightness; + resultingColor.b = color.b * brightness; + resultingColor.a = color.a; + + destBuffer->SetCurrentColor(resultingColor); +} + diff --git a/src/tilemap/litchunkvertexgenerator.h b/src/tilemap/litchunkvertexgenerator.h new file mode 100644 index 0000000..a05cb76 --- /dev/null +++ b/src/tilemap/litchunkvertexgenerator.h @@ -0,0 +1,23 @@ +#ifndef __TILEMAP_LITCHUNKVERTEXGENERATOR_H_INCLUDED__ +#define __TILEMAP_LITCHUNKVERTEXGENERATOR_H_INCLUDED__ + +#include "chunkvertexgenerator.h" + +class TileChunk; +class VertexBuffer; +struct Color; +struct Matrix4x4; +struct Vector3; + +class LitChunkVertexGenerator : public ChunkVertexGenerator +{ +public: + LitChunkVertexGenerator(); + virtual ~LitChunkVertexGenerator(); + +private: + void CopyVertex(const TileChunk *chunk, VertexBuffer *sourceBuffer, VertexBuffer *destBuffer, const Vector3 &positionOffset, const Matrix4x4 *transform, const Color &color); +}; + +#endif + diff --git a/src/tilemap/positionandskytilemaplighter.cpp b/src/tilemap/positionandskytilemaplighter.cpp new file mode 100644 index 0000000..4716426 --- /dev/null +++ b/src/tilemap/positionandskytilemaplighter.cpp @@ -0,0 +1,235 @@ +#include "../framework/debug.h" + +#include "positionandskytilemaplighter.h" +#include "tile.h" +#include "tilechunk.h" +#include "tilelightdefs.h" +#include "tilemap.h" +#include "tilemesh.h" +#include "tilemeshcollection.h" +#include "tilemeshdefs.h" + +PositionAndSkyTileMapLighter::PositionAndSkyTileMapLighter() +{ + STACK_TRACE; +} + +PositionAndSkyTileMapLighter::~PositionAndSkyTileMapLighter() +{ + STACK_TRACE; +} + +void PositionAndSkyTileMapLighter::Light(TileMap *tileMap) +{ + STACK_TRACE; + ResetLightValues(tileMap); + SetupSkyLight(tileMap); + ApplyLighting(tileMap); +} + +void PositionAndSkyTileMapLighter::ResetLightValues(TileMap *tileMap) +{ + STACK_TRACE; + for (uint32_t y = 0; y < tileMap->GetHeight(); ++y) + { + for (uint32_t z = 0; z < tileMap->GetDepth(); ++z) + { + for (uint32_t 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 + ClearBit(TILE_LIGHT_SKY, tile->flags); + tile->skyLight = 0; + tile->tileLight = tileMap->GetAmbientLightValue(); + } + } + } +} + +void PositionAndSkyTileMapLighter::SetupSkyLight(TileMap *tileMap) +{ + STACK_TRACE; + // 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 (uint32_t x = 0; x < tileMap->GetWidth(); ++x) + { + for (uint32_t z = 0; z < tileMap->GetDepth(); ++z) + { + BOOL stillSkyLit = TRUE; + TILE_LIGHT_VALUE currentSkyLightValue = tileMap->GetSkyLightValue(); + + for (int32_t y = tileMap->GetHeight() - 1; y >= 0 && stillSkyLit; --y) + { + Tile *tile = tileMap->Get(x, y, z); + const TileMesh *mesh = tileMap->GetMeshes()->Get(tile); + if (mesh == NULL || (mesh != NULL && !mesh->IsOpaque(SIDE_TOP) && !mesh->IsOpaque(SIDE_BOTTOM))) + { + // tile is partially transparent or this tile is empty space + SetBit(TILE_LIGHT_SKY, tile->flags); + + if (mesh != NULL) + Tile::AdjustLightForTranslucency(currentSkyLightValue, mesh->GetTranslucency()); + + tile->skyLight = currentSkyLightValue; + } + else + { + // tile is present and is fully solid, sky lighting stops + // at the tile above this one + stillSkyLit = FALSE; + } + } + } + } +} + +void PositionAndSkyTileMapLighter::ApplyLighting(TileMap *tileMap) +{ + STACK_TRACE; + // for each light source (sky or not), recursively go through and set + // appropriate lighting for each adjacent tile + for (uint32_t y = 0; y < tileMap->GetHeight(); ++y) + { + for (uint32_t z = 0; z < tileMap->GetDepth(); ++z) + { + for (uint32_t 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 + { + const TileMesh *mesh = tileMap->GetMeshes()->Get(tile); + if (mesh->IsLightSource()) + SpreadTileLight(x, y, z, tile, mesh->GetLightValue(), tileMap); + } + } + } + } +} + +void PositionAndSkyTileMapLighter::SpreadSkyLight(int32_t x, int32_t y, int32_t z, Tile *tile, TILE_LIGHT_VALUE 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->GetMeshes()->Get(left)->IsOpaque(SIDE_RIGHT)) && left->skyLight < light) + { + TILE_LIGHT_VALUE spreadLight = light; + if (!left->IsEmptySpace()) + spreadLight = Tile::AdjustLightForTranslucency(spreadLight, tileMap->GetMeshes()->Get(left)->GetTranslucency()); + SpreadSkyLight(x - 1, y, z, left, spreadLight, tileMap); + } + if (right != NULL && (right->IsEmptySpace() || !tileMap->GetMeshes()->Get(right)->IsOpaque(SIDE_LEFT)) && right->skyLight < light) + { + TILE_LIGHT_VALUE spreadLight = light; + if (!right->IsEmptySpace()) + spreadLight = Tile::AdjustLightForTranslucency(spreadLight, tileMap->GetMeshes()->Get(right)->GetTranslucency()); + SpreadSkyLight(x + 1, y, z, right, spreadLight, tileMap); + } + if (forward != NULL && (forward->IsEmptySpace() || !tileMap->GetMeshes()->Get(forward)->IsOpaque(SIDE_BACK)) && forward->skyLight < light) + { + TILE_LIGHT_VALUE spreadLight = light; + if (!forward->IsEmptySpace()) + spreadLight = Tile::AdjustLightForTranslucency(spreadLight, tileMap->GetMeshes()->Get(forward)->GetTranslucency()); + SpreadSkyLight(x, y, z - 1, forward, spreadLight, tileMap); + } + if (backward != NULL && (backward->IsEmptySpace() || !tileMap->GetMeshes()->Get(backward)->IsOpaque(SIDE_FRONT)) && backward->skyLight < light) + { + TILE_LIGHT_VALUE spreadLight = light; + if (!backward->IsEmptySpace()) + spreadLight = Tile::AdjustLightForTranslucency(spreadLight, tileMap->GetMeshes()->Get(backward)->GetTranslucency()); + SpreadSkyLight(x, y, z + 1, backward, spreadLight, tileMap); + } + if (up != NULL && (up->IsEmptySpace() || !tileMap->GetMeshes()->Get(up)->IsOpaque(SIDE_BOTTOM)) && up->skyLight < light) + { + TILE_LIGHT_VALUE spreadLight = light; + if (!up->IsEmptySpace()) + spreadLight = Tile::AdjustLightForTranslucency(spreadLight, tileMap->GetMeshes()->Get(up)->GetTranslucency()); + SpreadSkyLight(x, y + 1, z, up, spreadLight, tileMap); + } + if (down != NULL && (down->IsEmptySpace() || !tileMap->GetMeshes()->Get(down)->IsOpaque(SIDE_TOP)) && down->skyLight < light) + { + TILE_LIGHT_VALUE spreadLight = light; + if (!down->IsEmptySpace()) + spreadLight = Tile::AdjustLightForTranslucency(spreadLight, tileMap->GetMeshes()->Get(down)->GetTranslucency()); + SpreadSkyLight(x, y - 1, z, down, spreadLight, tileMap); + } + } +} + +void PositionAndSkyTileMapLighter::SpreadTileLight(int32_t x, int32_t y, int32_t z, Tile *tile, TILE_LIGHT_VALUE 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->GetMeshes()->Get(left)->IsOpaque(SIDE_RIGHT)) && left->tileLight < light) + { + TILE_LIGHT_VALUE spreadLight = light; + if (!left->IsEmptySpace()) + spreadLight = Tile::AdjustLightForTranslucency(spreadLight, tileMap->GetMeshes()->Get(left)->GetTranslucency()); + SpreadTileLight(x - 1, y, z, left, spreadLight, tileMap); + } + if (right != NULL && (right->IsEmptySpace() || !tileMap->GetMeshes()->Get(right)->IsOpaque(SIDE_LEFT)) && right->tileLight < light) + { + TILE_LIGHT_VALUE spreadLight = light; + if (!right->IsEmptySpace()) + spreadLight = Tile::AdjustLightForTranslucency(spreadLight, tileMap->GetMeshes()->Get(right)->GetTranslucency()); + SpreadTileLight(x + 1, y, z, right, spreadLight, tileMap); + } + if (forward != NULL && (forward->IsEmptySpace() || !tileMap->GetMeshes()->Get(forward)->IsOpaque(SIDE_BACK)) && forward->tileLight < light) + { + TILE_LIGHT_VALUE spreadLight = light; + if (!forward->IsEmptySpace()) + spreadLight = Tile::AdjustLightForTranslucency(spreadLight, tileMap->GetMeshes()->Get(forward)->GetTranslucency()); + SpreadTileLight(x, y, z - 1, forward, spreadLight, tileMap); + } + if (backward != NULL && (backward->IsEmptySpace() || !tileMap->GetMeshes()->Get(backward)->IsOpaque(SIDE_FRONT)) && backward->tileLight < light) + { + TILE_LIGHT_VALUE spreadLight = light; + if (!backward->IsEmptySpace()) + spreadLight = Tile::AdjustLightForTranslucency(spreadLight, tileMap->GetMeshes()->Get(backward)->GetTranslucency()); + SpreadTileLight(x, y, z + 1, backward, spreadLight, tileMap); + } + if (up != NULL && (up->IsEmptySpace() || !tileMap->GetMeshes()->Get(up)->IsOpaque(SIDE_BOTTOM)) && up->tileLight < light) + { + TILE_LIGHT_VALUE spreadLight = light; + if (!up->IsEmptySpace()) + spreadLight = Tile::AdjustLightForTranslucency(spreadLight, tileMap->GetMeshes()->Get(up)->GetTranslucency()); + SpreadTileLight(x, y + 1, z, up, spreadLight, tileMap); + } + if (down != NULL && (down->IsEmptySpace() || !tileMap->GetMeshes()->Get(down)->IsOpaque(SIDE_TOP)) && down->tileLight < light) + { + TILE_LIGHT_VALUE spreadLight = light; + if (!down->IsEmptySpace()) + spreadLight = Tile::AdjustLightForTranslucency(spreadLight, tileMap->GetMeshes()->Get(down)->GetTranslucency()); + SpreadTileLight(x, y - 1, z, down, spreadLight, tileMap); + } + } +} + diff --git a/src/tilemap/positionandskytilemaplighter.h b/src/tilemap/positionandskytilemaplighter.h new file mode 100644 index 0000000..54b1f67 --- /dev/null +++ b/src/tilemap/positionandskytilemaplighter.h @@ -0,0 +1,29 @@ +#ifndef __TILEMAP_POSITIONANDSKYTILEMAPLIGHTER_H_INCLUDED__ +#define __TILEMAP_POSITIONANDSKYTILEMAPLIGHTER_H_INCLUDED__ + +#include "../framework/common.h" + +#include "tile.h" +#include "tilelightdefs.h" +#include "tilemaplighter.h" + +class TileMap; + +class PositionAndSkyTileMapLighter : public TileMapLighter +{ +public: + PositionAndSkyTileMapLighter(); + virtual ~PositionAndSkyTileMapLighter(); + + void Light(TileMap *tileMap); + +private: + void ResetLightValues(TileMap *tileMap); + void SetupSkyLight(TileMap *tileMap); + void ApplyLighting(TileMap *tileMap); + void SpreadSkyLight(int32_t x, int32_t y, int32_t z, Tile *tile, TILE_LIGHT_VALUE light, TileMap *tileMap); + void SpreadTileLight(int32_t x, int32_t y, int32_t z, Tile *tile, TILE_LIGHT_VALUE light, TileMap *tileMap); +}; + +#endif + diff --git a/src/tilemap/simpletilemaplighter.cpp b/src/tilemap/simpletilemaplighter.cpp new file mode 100644 index 0000000..518354a --- /dev/null +++ b/src/tilemap/simpletilemaplighter.cpp @@ -0,0 +1,87 @@ +#include "../framework/debug.h" + +#include "simpletilemaplighter.h" +#include "tile.h" +#include "tilechunk.h" +#include "tilelightdefs.h" +#include "tilemap.h" +#include "tilemesh.h" +#include "tilemeshcollection.h" +#include "tilemeshdefs.h" + +SimpleTileMapLighter::SimpleTileMapLighter() +{ + STACK_TRACE; +} + +SimpleTileMapLighter::~SimpleTileMapLighter() +{ + STACK_TRACE; +} + +void SimpleTileMapLighter::Light(TileMap *tileMap) +{ + STACK_TRACE; + ResetLightValues(tileMap); + ApplySkyLight(tileMap); +} + +void SimpleTileMapLighter::ResetLightValues(TileMap *tileMap) +{ + STACK_TRACE; + for (uint32_t y = 0; y < tileMap->GetHeight(); ++y) + { + for (uint32_t z = 0; z < tileMap->GetDepth(); ++z) + { + for (uint32_t 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 + ClearBit(TILE_LIGHT_SKY, tile->flags); + tile->skyLight = 0; + tile->tileLight = tileMap->GetAmbientLightValue(); + } + } + } +} + +void SimpleTileMapLighter::ApplySkyLight(TileMap *tileMap) +{ + STACK_TRACE; + // 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 (uint32_t x = 0; x < tileMap->GetWidth(); ++x) + { + for (uint32_t z = 0; z < tileMap->GetDepth(); ++z) + { + BOOL stillSkyLit = TRUE; + TILE_LIGHT_VALUE currentSkyLightValue = tileMap->GetSkyLightValue(); + + for (int32_t y = tileMap->GetHeight() - 1; y >= 0 && stillSkyLit; --y) + { + Tile *tile = tileMap->Get(x, y, z); + const TileMesh *mesh = tileMap->GetMeshes()->Get(tile); + if (mesh == NULL || (mesh != NULL && !mesh->IsOpaque(SIDE_TOP) && !mesh->IsOpaque(SIDE_BOTTOM))) + { + // tile is partially transparent or this tile is empty space + SetBit(TILE_LIGHT_SKY, tile->flags); + + if (mesh != NULL) + Tile::AdjustLightForTranslucency(currentSkyLightValue, mesh->GetTranslucency()); + + tile->skyLight = tileMap->GetSkyLightValue(); + } + else + { + // tile is present and is fully solid, sky lighting stops + // at the tile above this one + stillSkyLit = FALSE; + } + } + } + } +} diff --git a/src/tilemap/simpletilemaplighter.h b/src/tilemap/simpletilemaplighter.h new file mode 100644 index 0000000..f0094a8 --- /dev/null +++ b/src/tilemap/simpletilemaplighter.h @@ -0,0 +1,25 @@ +#ifndef __TILEMAP_SIMPLETILEMAPLIGHTER_H_INCLUDED__ +#define __TILEMAP_SIMPLETILEMAPLIGHTER_H_INCLUDED__ + +#include "../framework/common.h" + +#include "tile.h" +#include "tilemaplighter.h" + +class TileMap; + +class SimpleTileMapLighter : public TileMapLighter +{ +public: + SimpleTileMapLighter(); + virtual ~SimpleTileMapLighter(); + + void Light(TileMap *tileMap); + +private: + void ResetLightValues(TileMap *tileMap); + void ApplySkyLight(TileMap *tileMap); +}; + +#endif + diff --git a/src/tilemap/statictilemesh.cpp b/src/tilemap/statictilemesh.cpp new file mode 100644 index 0000000..3220113 --- /dev/null +++ b/src/tilemap/statictilemesh.cpp @@ -0,0 +1,116 @@ +#include "../framework/debug.h" + +#include "statictilemesh.h" +#include "../framework/assets/static/staticmesh.h" +#include "../framework/assets/static/staticmeshsubset.h" +#include "../framework/graphics/vertexbuffer.h" +#include "../framework/math/mathhelpers.h" +#include "../framework/math/rectf.h" + +StaticTileMesh::StaticTileMesh(const StaticMesh *mesh, const RectF *textureAtlasTileBoundaries, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue, BOOL alpha, float translucency, const Color &color, const StaticMesh *collisionMesh) +{ + STACK_TRACE; + // only work with the first mesh subset + StaticMeshSubset *subset = mesh->GetSubset(0); + + // copy the source buffer + m_vertices = new VertexBuffer(BUFFEROBJECT_USAGE_STATIC); + ASSERT(m_vertices != NULL); + m_vertices->CreateCopyOf(subset->GetVertices()); + + SetOpaque(opaqueSides); + SetAlpha(alpha); + SetColor(color); + SetTranslucency(translucency); + SetLight(lightValue); + + // adjust texture coordinates, which are likely all within the full range 0.0f to 1.0f + // to fit into the texture atlas tile's texture coordinate boundaries + for (uint32_t i = 0; i < m_vertices->GetNumElements(); ++i) + { + Vector2 texCoord = m_vertices->GetTexCoord(i); + texCoord.x = ScaleRange(texCoord.x, 0.0f, 1.0f, textureAtlasTileBoundaries->left, textureAtlasTileBoundaries->right); + texCoord.y = ScaleRange(texCoord.y, 0.0f, 1.0f, textureAtlasTileBoundaries->top, textureAtlasTileBoundaries->bottom); + m_vertices->SetTexCoord(i, texCoord); + } + + SetupCollisionVertices(collisionMesh); +} + +StaticTileMesh::StaticTileMesh(const StaticMesh *mesh, const RectF *textureAtlasTileBoundaries, uint32_t numTiles, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue, BOOL alpha, float translucency, const Color &color, const StaticMesh *collisionMesh) +{ + STACK_TRACE; + ASSERT(numTiles > 0); + ASSERT(mesh->GetNumSubsets() >= numTiles); + + // count up all the vertices needed for the number of subsets we're going to use + uint32_t numVertices = 0; + for (uint32_t i = 0; i < numTiles; ++i) + numVertices += mesh->GetSubset(i)->GetVertices()->GetNumElements(); + + // create the vertex buffer using the same attribs as the source mesh + // (assuming all subsets have the same attribs in the mesh) + m_vertices = new VertexBuffer(BUFFEROBJECT_USAGE_STATIC); + ASSERT(m_vertices != NULL); + m_vertices->CopyAttributesFrom(mesh->GetSubset(0)->GetVertices()); + m_vertices->Create(numVertices); + + SetOpaque(opaqueSides); + SetAlpha(alpha); + SetColor(color); + SetTranslucency(translucency); + SetLight(lightValue); + + uint32_t currentVertex = 0; + for (uint32_t i = 0; i < numTiles; ++i) + { + StaticMeshSubset *subset = mesh->GetSubset(i); + const RectF *tileBoundaries = &textureAtlasTileBoundaries[i]; + + // copy all this subset's vertices + m_vertices->Copy(subset->GetVertices(), currentVertex); + + // now we need to adjust the copied texture coordinates to match the tile boundaries + for (uint32_t t = 0; t < subset->GetVertices()->GetNumElements(); ++t) + { + uint32_t position = t + currentVertex; + + Vector2 texCoord = m_vertices->GetTexCoord(position); + texCoord.x = ScaleRange(texCoord.x, 0.0f, 1.0f, tileBoundaries->left, tileBoundaries->right); + texCoord.y = ScaleRange(texCoord.y, 0.0f, 1.0f, tileBoundaries->top, tileBoundaries->bottom); + m_vertices->SetTexCoord(position, texCoord); + } + + currentVertex += subset->GetVertices()->GetNumElements(); + } + + SetupCollisionVertices(collisionMesh); +} + +void StaticTileMesh::SetupCollisionVertices(const StaticMesh *collisionMesh) +{ + STACK_TRACE; + const VertexBuffer *srcCollisionVertices; + if (collisionMesh != NULL) + srcCollisionVertices = collisionMesh->GetSubset(0)->GetVertices(); + else + srcCollisionVertices = m_vertices; + + m_numCollisionVertices = srcCollisionVertices->GetNumElements(); + + m_collisionVertices = new Vector3[m_numCollisionVertices]; + ASSERT(m_collisionVertices != NULL); + + for (uint32_t i = 0; i < m_numCollisionVertices; ++i) + m_collisionVertices[i] = srcCollisionVertices->GetPosition3(i); +} + +StaticTileMesh::~StaticTileMesh() +{ + STACK_TRACE; + SAFE_DELETE(m_vertices); + SAFE_DELETE(m_collisionVertices); +} + + + diff --git a/src/tilemap/statictilemesh.h b/src/tilemap/statictilemesh.h new file mode 100644 index 0000000..2aa5b92 --- /dev/null +++ b/src/tilemap/statictilemesh.h @@ -0,0 +1,37 @@ +#ifndef __TILEMAP_STATICTILEMESH_H_INCLUDED__ +#define __TILEMAP_STATICTILEMESH_H_INCLUDED__ + +#include "../framework/common.h" +#include "tilelightdefs.h" +#include "tilemesh.h" +#include "tilemeshdefs.h" +#include "../framework/graphics/color.h" + +class StaticMesh; +class VertexBuffer; +struct RectF; +struct Vector3; + +class StaticTileMesh : public TileMesh +{ +public: + StaticTileMesh(const StaticMesh *mesh, const RectF *textureAtlasTileBoundaries, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue, BOOL alpha, float translucency = 1.0f, const Color &color = COLOR_WHITE, const StaticMesh *collisionMesh = NULL); + StaticTileMesh(const StaticMesh *mesh, const RectF *textureAtlasTileBoundaries, uint32_t numTiles, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue, BOOL alpha, float translucency = 1.0f, const Color &color = COLOR_WHITE, const StaticMesh *collisionMesh = NULL); + virtual ~StaticTileMesh(); + + VertexBuffer* GetBuffer() const { return m_vertices; } + uint32_t GetNumCollisionVertices() const { return m_numCollisionVertices; } + const Vector3* GetCollisionVertices() const { return m_collisionVertices; } + + TILEMESH_TYPE GetType() const { return TILEMESH_STATIC; } + +private: + void SetupCollisionVertices(const StaticMesh *collisionMesh); + + VertexBuffer *m_vertices; + uint32_t m_numCollisionVertices; + Vector3 *m_collisionVertices; +}; + +#endif + diff --git a/src/tilemap/tile.cpp b/src/tilemap/tile.cpp new file mode 100644 index 0000000..2fac3b3 --- /dev/null +++ b/src/tilemap/tile.cpp @@ -0,0 +1,29 @@ +#include "../framework/debug.h" + +#include "tile.h" +#include "../framework/common.h" +#include "../framework/math/common.h" +#include "../framework/math/matrix4x4.h" + +const Matrix4x4* Tile::GetTransformationMatrix() const +{ + // static so that we only need to set them up once, since this method will + // be used inside inner loops and such + // **IMPORTANT**: these rotations assume every TileMesh is modeled facing north! + static Matrix4x4 faceNorth = Matrix4x4::CreateRotationY(RADIANS_0); + static Matrix4x4 faceEast = Matrix4x4::CreateRotationY(RADIANS_90); + static Matrix4x4 faceSouth = Matrix4x4::CreateRotationY(RADIANS_180); + static Matrix4x4 faceWest = Matrix4x4::CreateRotationY(RADIANS_270); + + if (IsBitSet(TILE_FACE_NORTH, flags)) + return &faceNorth; + else if (IsBitSet(TILE_FACE_EAST, flags)) + return &faceEast; + else if (IsBitSet(TILE_FACE_SOUTH, flags)) + return &faceSouth; + else if (IsBitSet(TILE_FACE_WEST, flags)) + return &faceWest; + else + return NULL; +} + diff --git a/src/tilemap/tile.h b/src/tilemap/tile.h new file mode 100644 index 0000000..90d87b4 --- /dev/null +++ b/src/tilemap/tile.h @@ -0,0 +1,161 @@ +#ifndef __TILEMAP_TILE_H_INCLUDED__ +#define __TILEMAP_TILE_H_INCLUDED__ + +#include "../framework/common.h" +#include "tilelightdefs.h" +#include "../framework/graphics/color.h" +#include "../framework/math/mathhelpers.h" +#include + +struct Matrix4x4; + +typedef uint16_t TILE_INDEX; +typedef uint16_t TILE_FLAG_BITS; + +const TILE_INDEX NO_TILE = 0; + +enum TILE_FLAGS +{ + TILE_COLLIDABLE = 1, + TILE_FACE_NORTH = 2, + TILE_FACE_EAST = 4, + TILE_FACE_SOUTH = 8, + TILE_FACE_WEST = 16, + TILE_CUSTOM_COLOR = 32, + TILE_FRICTION_SLIPPERY = 64, + TILE_LIGHT_SKY = 128, + TILE_WALKABLE_SURFACE = 256 +}; + +struct Tile +{ + Tile(); + + void Set(TILE_INDEX tile); + void Set(TILE_INDEX tile, TILE_FLAG_BITS flags); + void Set(TILE_INDEX tile, TILE_FLAG_BITS flags, const Color &color); + void Set(TILE_INDEX tile, TILE_FLAG_BITS flags, uint32_t color); + const Matrix4x4* GetTransformationMatrix() const; + + void SetCustomColor(const Color &color); + void SetCustomColor(uint32_t color); + void ClearCustomColor(); + + float GetBrightness() const; + static float GetBrightness(TILE_LIGHT_VALUE light); + static TILE_LIGHT_VALUE AdjustLightForTranslucency(TILE_LIGHT_VALUE light, float translucency); + + BOOL IsEmptySpace() const; + BOOL IsCollideable() const; + BOOL HasCustomColor() const; + BOOL IsSlippery() const; + BOOL IsSkyLit() const; + + TILE_INDEX tile; + TILE_FLAG_BITS flags; + TILE_LIGHT_VALUE tileLight; + TILE_LIGHT_VALUE skyLight; + uint32_t color; // save some memory (4 byte int, versus 16 byte Color obj) +}; + +inline Tile::Tile() +{ + tile = NO_TILE; + flags = 0; + tileLight = 0; + skyLight = 0; + color = 0; +} + +inline void Tile::Set(TILE_INDEX tile) +{ + this->tile = tile; +} + +inline void Tile::Set(TILE_INDEX tile, TILE_FLAG_BITS flags) +{ + this->tile = tile; + this->flags = flags; +} + +inline void Tile::Set(TILE_INDEX tile, TILE_FLAG_BITS flags, const Color &color) +{ + Set(tile, flags, color.ToInt()); +} + +inline void Tile::Set(TILE_INDEX tile, TILE_FLAG_BITS flags, uint32_t color) +{ + SetBit(TILE_CUSTOM_COLOR, flags); + + this->tile = tile; + this->flags = flags; + this->color = color; +} + +inline void Tile::SetCustomColor(const Color &color) +{ + SetCustomColor(color.ToInt()); +} + +inline void Tile::SetCustomColor(uint32_t color) +{ + SetBit(TILE_CUSTOM_COLOR, this->flags); + this->color = color; +} + +inline void Tile::ClearCustomColor() +{ + ClearBit(TILE_CUSTOM_COLOR, this->flags); + this->color = 0; +} + +inline float Tile::GetBrightness() const +{ + if (this->tileLight > this->skyLight) + return GetBrightness(this->tileLight); + else + return GetBrightness(this->skyLight); +} + +inline float Tile::GetBrightness(TILE_LIGHT_VALUE light) +{ + // this is a copy of the brightness formula listed here: + // http://gamedev.stackexchange.com/a/21247 + + const float BASE_BRIGHTNESS = 0.086f; + float normalizedLightValue = (float)light / (float)(TILE_LIGHT_VALUE_MAX + 1); + return powf(normalizedLightValue, 1.4f) + BASE_BRIGHTNESS; +} + +inline TILE_LIGHT_VALUE Tile::AdjustLightForTranslucency(TILE_LIGHT_VALUE light, float translucency) +{ + return (TILE_LIGHT_VALUE)Round((float)light * translucency); +} + +inline BOOL Tile::IsEmptySpace() const +{ + return this->tile == NO_TILE; +} + +inline BOOL Tile::IsCollideable() const +{ + return IsBitSet(TILE_COLLIDABLE, this->flags); +} + +inline BOOL Tile::HasCustomColor() const +{ + return IsBitSet(TILE_CUSTOM_COLOR, this->flags); +} + +inline BOOL Tile::IsSlippery() const +{ + return IsBitSet(TILE_FRICTION_SLIPPERY, this->flags); +} + +inline BOOL Tile::IsSkyLit() const +{ + return IsBitSet(TILE_LIGHT_SKY, this->flags); +} + +#endif + diff --git a/src/tilemap/tilechunk.cpp b/src/tilemap/tilechunk.cpp new file mode 100644 index 0000000..557a883 --- /dev/null +++ b/src/tilemap/tilechunk.cpp @@ -0,0 +1,417 @@ +#include "../framework/debug.h" + +#include "tilechunk.h" + +#include "chunkvertexgenerator.h" +#include "tilemap.h" +#include "tilemesh.h" +#include "tilemeshcollection.h" + +#include "../framework/graphics/graphicsdevice.h" +#include "../framework/graphics/vertexbuffer.h" +#include "../framework/math/intersectiontester.h" +#include "../framework/math/mathhelpers.h" +#include "../framework/math/ray.h" +#include "../framework/math/vector3.h" + +#include + +TileChunk::TileChunk(uint32_t x, uint32_t y, uint32_t z, uint32_t width, uint32_t height, uint32_t depth, const TileMap *tileMap, GraphicsDevice *graphicsDevice) +{ + STACK_TRACE; + ASSERT(tileMap != NULL); + ASSERT(graphicsDevice != NULL); + + m_tileMap = tileMap; + m_graphicsDevice = graphicsDevice; + + m_width = width; + m_height = height; + m_depth = depth; + m_x = x; + m_y = y; + m_z = z; + m_position = Vector3((float)m_x, (float)m_y, (float)m_z); + + m_bounds.min = Vector3((float)m_x, (float)m_y, (float)m_z); + m_bounds.max = Vector3((float)(m_x + m_width), (float)(m_y + m_height), (float)(m_z + m_depth)); + + m_data = new Tile[width * height * depth]; + ASSERT(m_data != NULL); + + // TODO: is 16 a good starting default size? + m_vertices = new VertexBuffer(BUFFEROBJECT_USAGE_STATIC); + ASSERT(m_vertices != NULL); + m_vertices->AddAttribute(VERTEX_POS_3D); + m_vertices->AddAttribute(VERTEX_NORMAL); + m_vertices->AddAttribute(VERTEX_TEXCOORD); + m_vertices->AddAttribute(VERTEX_COLOR); + m_vertices->Create(16); + m_numVertices = 0; + + // start off assuming we don't have any alpha vertices + m_alphaVertices = NULL; + m_numAlphaVertices = 0; + + m_vertices->CreateInVRAM(); + m_graphicsDevice->RegisterManagedResource(m_vertices); +} + +TileChunk::~TileChunk() +{ + STACK_TRACE; + m_graphicsDevice->UnregisterManagedResource(m_vertices); + if (m_alphaVertices != NULL) + m_graphicsDevice->UnregisterManagedResource(m_alphaVertices); + + SAFE_DELETE_ARRAY(m_data); + SAFE_DELETE(m_vertices); + SAFE_DELETE(m_alphaVertices); +} + +void TileChunk::GetBoundingBoxFor(uint32_t x, uint32_t y, uint32_t z, BoundingBox *box) const +{ + STACK_TRACE; + ASSERT(box != NULL); + + // "chunk space" + box->min = Vector3((float)x, (float)y, (float)z); + box->max = Vector3(x + 1.0f, y + 1.0f, z + 1.0f); // 1.0f = tile width + + // move to world space ("tilemap space" maybe .. ?) + box->min += m_bounds.min; + box->max += m_bounds.min; +} + +BoundingBox TileChunk::GetBoundingBoxFor(uint32_t x, uint32_t y, uint32_t z) const +{ + STACK_TRACE; + BoundingBox box; + + // "chunk space" + box.min = Vector3((float)x, (float)y, (float)z); + box.max = Vector3(x + 1.0f, y + 1.0f, z + 1.0f); // 1.0f = tile width + + // move to world space ("tilemap space" maybe .. ?) + box.min += m_bounds.min; + box.max += m_bounds.min; + + return box; +} + +BOOL TileChunk::CheckForCollision(const Ray &ray, uint32_t &x, uint32_t &y, uint32_t &z) const +{ + STACK_TRACE; + // make sure that the ray and this chunk can actually collide in the first place + Vector3 position; + if (!IntersectionTester::Test(ray, m_bounds, &position)) + return FALSE; + + // convert initial chunk collision point to tile coords (this is in "tilemap space") + int32_t currentX = (int32_t)position.x; + int32_t currentY = (int32_t)position.y; + int32_t currentZ = (int32_t)position.z; + + // make sure the coords are inrange of this chunk. 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 "tilemap space" + currentX = Clamp(currentX, (int32_t)GetX(), (int32_t)(GetX() + GetWidth() - 1)); + currentY = Clamp(currentY, (int32_t)GetY(), (int32_t)(GetY() + GetHeight() - 1)); + currentZ = Clamp(currentZ, (int32_t)GetZ(), (int32_t)(GetZ() + GetDepth() - 1)); + + // convert to "chunk space" + currentX -= (int32_t)GetX(); + currentY -= (int32_t)GetY(); + currentZ -= (int32_t)GetZ(); + + // is the start position colliding with a solid tile? + Tile *startTile = Get(currentX, currentY, currentZ); + if (IsBitSet(TILE_COLLIDABLE, startTile->flags)) + { + // collision found, set the tile coords of the collision + x = currentX; + y = currentY; + z = currentZ; + + // and we're done + return TRUE; + } + + // no collision initially, continue on with the rest ... + + // step increments in "chunk tile" units + int32_t stepX = (int32_t)Sign(ray.direction.x); + int32_t stepY = (int32_t)Sign(ray.direction.y); + int32_t stepZ = (int32_t)Sign(ray.direction.z); + + // tile boundary (needs to be in "tilemap space") + int32_t tileBoundaryX = (int32_t)GetX() + currentX + (stepX > 0 ? 1 : 0); + int32_t tileBoundaryY = (int32_t)GetY() + currentY + (stepY > 0 ? 1 : 0); + int32_t tileBoundaryZ = (int32_t)GetZ() + 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. + // TODO: check that this is compatible with Visual C++ + + // determine how far we can travel along the ray before we hit a tile boundary + Vector3 tMax = Vector3( + (tileBoundaryX - ray.position.x) / ray.direction.x, + (tileBoundaryY - ray.position.y) / ray.direction.y, + (tileBoundaryZ - ray.position.z) / ray.direction.z + ); + if (tMax.x == -INFINITY) + tMax.x = INFINITY; + if (tMax.y == -INFINITY) + tMax.y = INFINITY; + if (tMax.z == -INFINITY) + tMax.z = INFINITY; + if (isnan(tMax.x)) + tMax.x = INFINITY; + if (isnan(tMax.y)) + tMax.y = INFINITY; + if (isnan(tMax.z)) + tMax.z = INFINITY; + + // determine how far we must travel along the ray before we cross a grid cell + Vector3 tDelta = Vector3( + stepX / ray.direction.x, + stepY / ray.direction.y, + stepZ / ray.direction.z + ); + if (tDelta.x == -INFINITY) + tDelta.x = INFINITY; + if (tDelta.y == -INFINITY) + tDelta.y = INFINITY; + if (tDelta.z == -INFINITY) + tDelta.z = INFINITY; + if (isnan(tDelta.x)) + tDelta.x = INFINITY; + if (isnan(tDelta.y)) + tDelta.y = INFINITY; + if (isnan(tDelta.z)) + tDelta.z = INFINITY; + + BOOL collided = FALSE; + BOOL outOfChunk = FALSE; + while (!outOfChunk) + { + // 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 (tMax.x < tMax.y && tMax.x < tMax.z) + { + // tMax.x is lowest, the YZ tile boundary plane is closest + currentX += stepX; + tMax.x += tDelta.x; + } + else if (tMax.y < tMax.z) + { + // tMax.y is lowest, the XZ tile boundary plane is closest + currentY += stepY; + tMax.y += tDelta.y; + } + else + { + // tMax.z is lowest, the XY tile boundary plane is closest + currentZ += stepZ; + tMax.z += tDelta.z; + } + + // need to figure out if this new position is still inside the bounds of + // the chunk before we can attempt to determine if the current tile is + // solid + if ( + currentX < 0 || currentX >= (int32_t)GetWidth() || + currentY < 0 || currentY >= (int32_t)GetHeight() || + currentZ < 0 || currentZ >= (int32_t)GetDepth() + ) + outOfChunk = TRUE; + else + { + // still inside and at the next position, test for a solid tile + Tile *tile = Get(currentX, currentY, currentZ); + if (IsBitSet(TILE_COLLIDABLE, tile->flags)) + { + collided = TRUE; + + // set the tile coords of the collision + x = currentX; + y = currentY; + z = currentZ; + + break; + } + } + } + + return collided; +} + +BOOL TileChunk::CheckForCollision(const Ray &ray, Vector3 &point, uint32_t &x, uint32_t &y, uint32_t &z) const +{ + STACK_TRACE; + // 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, x, y, z)) + return FALSE; + + // 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 CheckForCollisionWithTile(ray, point, x, y, z); +} + +BOOL TileChunk::CheckForCollisionWithTile(const Ray &ray, Vector3 &point, uint32_t x, uint32_t y, uint32_t z) const +{ + STACK_TRACE; + const Tile *tile = Get(x, y, z); + const TileMesh *mesh = m_tileMap->GetMeshes()->Get(tile); + + uint32_t numVertices = mesh->GetNumCollisionVertices(); + const Vector3 *vertices = mesh->GetCollisionVertices(); + + // world position of this tile, will be used to move each + // mesh triangle into world space + Vector3 tileWorldPosition = Vector3((float)x, (float)y, (float)z); + + float closestSquaredDistance = FLT_MAX; + BOOL collided = FALSE; + Vector3 collisionPoint = ZERO_VECTOR; + + for (uint32_t i = 0; i < numVertices; i += 3) + { + // get the vertices making up this triangle + Vector3 a = vertices[i]; + Vector3 b = vertices[i + 1]; + Vector3 c = vertices[i + 2]; + + // move these vertices into world space + a += tileWorldPosition; + b += tileWorldPosition; + c += tileWorldPosition; + + if (IntersectionTester::Test(ray, a, b, c, &collisionPoint)) + { + collided = TRUE; + + // if this is the closest collision yet, then keep the distance + // and point of collision + float squaredDistance = Vector3::LengthSquared(collisionPoint - ray.position); + if (squaredDistance < closestSquaredDistance) + { + closestSquaredDistance = squaredDistance; + point = collisionPoint; + } + } + } + + return collided; +} + +BOOL TileChunk::GetOverlappedTiles(const BoundingBox &box, uint32_t &x1, uint32_t &y1, uint32_t &z1, uint32_t &x2, uint32_t &y2, uint32_t &z2) const +{ + STACK_TRACE; + // make sure the given box actually intersects with this chunk in the first place + if (!IntersectionTester::Test(m_bounds, box)) + return FALSE; + + // convert to tile coords (these will be in "tilemap space") + // HACK: ceil() calls and "-1"'s keep us from picking up too many/too few + // blocks. these were arrived at through observation + int32_t minX = (int32_t)box.min.x; + int32_t minY = (int32_t)box.min.y; + int32_t minZ = (int32_t)box.min.z; + int32_t maxX = (int32_t)ceil(box.max.x); + int32_t maxY = (int32_t)ceil(box.max.y - 1.0f); + int32_t maxZ = (int32_t)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 chunk + // HACK: "-1"'s keep us from picking up too many blocks. these were arrived + // at through observation + minX = Clamp(minX, (int32_t)GetX(), (int32_t)(GetX() + GetWidth())); + minY = Clamp(minY, (int32_t)GetY(), (int32_t)(GetY() + GetHeight() - 1)); + minZ = Clamp(minZ, (int32_t)GetZ(), (int32_t)(GetZ() + GetDepth())); + maxX = Clamp(maxX, (int32_t)GetX(), (int32_t)(GetX() + GetWidth())); + maxY = Clamp(maxY, (int32_t)GetY(), (int32_t)(GetY() + GetHeight() - 1)); + maxZ = Clamp(maxZ, (int32_t)GetZ(), (int32_t)(GetZ() + GetDepth())); + + // return the leftover area, converted to "chunk space" + x1 = minX - (int32_t)GetX(); + y1 = minY - (int32_t)GetY(); + z1 = minZ - (int32_t)GetZ(); + x2 = maxX - (int32_t)GetX(); + y2 = maxY - (int32_t)GetY(); + z2 = maxZ - (int32_t)GetZ(); + + return TRUE; +} + +Tile* TileChunk::GetWithinSelfOrNeighbour(int32_t x, int32_t y, int32_t z) const +{ + int32_t checkX = (int32_t)GetX() + x; + int32_t checkY = (int32_t)GetY() + y; + int32_t checkZ = (int32_t)GetZ() + z; + return m_tileMap->Get(checkX, checkY, checkZ); +} + +Tile* TileChunk::GetWithinSelfOrNeighbourSafe(int32_t x, int32_t y, int32_t z) const +{ + int32_t checkX = (int32_t)GetX() + x; + int32_t checkY = (int32_t)GetY() + y; + int32_t checkZ = (int32_t)GetZ() + z; + if (!m_tileMap->IsWithinBounds(checkX, checkY, checkZ)) + return NULL; + else + return m_tileMap->Get(checkX, checkY, checkZ); +} + +void TileChunk::EnableAlphaVertices(BOOL enable) +{ + STACK_TRACE; + if (enable) + { + if (m_alphaVertices != NULL) + return; + + // need to create the vertex buffer + // TODO: is '16' a good default size? it probably isn't likely that + // chunks will have a lot of these. has to be non-zero anyway... + m_alphaVertices = new VertexBuffer(BUFFEROBJECT_USAGE_STATIC); + ASSERT(m_alphaVertices != NULL); + m_alphaVertices->AddAttribute(VERTEX_POS_3D); + m_alphaVertices->AddAttribute(VERTEX_NORMAL); + m_alphaVertices->AddAttribute(VERTEX_TEXCOORD); + m_alphaVertices->AddAttribute(VERTEX_COLOR); + m_alphaVertices->Create(16); + m_numAlphaVertices = 0; + + m_graphicsDevice->RegisterManagedResource(m_alphaVertices); + m_alphaVertices->CreateInVRAM(); + } + else + { + if (m_alphaVertices == NULL) + return; + + // need to free the vertex buffer + m_graphicsDevice->RegisterManagedResource(m_alphaVertices); + m_alphaVertices->FreeFromVRAM(); + SAFE_DELETE(m_alphaVertices); + m_numAlphaVertices = 0; + } +} + +void TileChunk::UpdateVertices(ChunkVertexGenerator *vertexGenerator) +{ + STACK_TRACE; + vertexGenerator->Generate(this, m_numVertices, m_numAlphaVertices); +} + diff --git a/src/tilemap/tilechunk.h b/src/tilemap/tilechunk.h new file mode 100644 index 0000000..ae5966a --- /dev/null +++ b/src/tilemap/tilechunk.h @@ -0,0 +1,121 @@ +#ifndef __TILEMAP_TILECHUNK_H_INCLUDED__ +#define __TILEMAP_TILECHUNK_H_INCLUDED__ + +#include "../framework/common.h" + +#include "tile.h" +#include "../framework/math/boundingbox.h" + +class ChunkVertexGenerator; +class GraphicsDevice; +class TileMap; +class VertexBuffer; +struct Ray; +struct Vector3; + +class TileChunk +{ +public: + TileChunk(uint32_t x, uint32_t y, uint32_t z, uint32_t width, uint32_t height, uint32_t depth, const TileMap *tileMap, GraphicsDevice *graphicsDevice); + virtual ~TileChunk(); + + void EnableAlphaVertices(BOOL enable); + BOOL IsAlphaEnabled() const { return m_alphaVertices != NULL; } + void UpdateVertices(ChunkVertexGenerator *vertexGenerator); + + Tile* Get(uint32_t x, uint32_t y, uint32_t z) const; + Tile* GetSafe(uint32_t x, uint32_t y, uint32_t z) const; + Tile* GetWithinSelfOrNeighbour(int32_t x, int32_t y, int32_t z) const; + Tile* GetWithinSelfOrNeighbourSafe(int32_t x, int32_t y, int32_t z) const; + void GetBoundingBoxFor(uint32_t x, uint32_t y, uint32_t z, BoundingBox *box) const; + BoundingBox GetBoundingBoxFor(uint32_t x, uint32_t y, uint32_t z) const; + + BOOL CheckForCollision(const Ray &ray, uint32_t &x, uint32_t &y, uint32_t &z) const; + BOOL CheckForCollision(const Ray &ray, Vector3 &point, uint32_t &x, uint32_t &y, uint32_t &z) const; + BOOL CheckForCollisionWithTile(const Ray &ray, Vector3 &point, uint32_t x, uint32_t y, uint32_t z) const; + BOOL GetOverlappedTiles(const BoundingBox &box, uint32_t &x1, uint32_t &y1, uint32_t &z1, uint32_t &x2, uint32_t &y2, uint32_t &z2) const; + + BOOL IsWithinBounds(int32_t x, int32_t y, int32_t z) const; + BOOL IsWithinLocalBounds(int32_t x, int32_t y, int32_t z) const; + + uint32_t GetX() const { return m_x; } + uint32_t GetY() const { return m_y; } + uint32_t GetZ() const { return m_z; } + const Vector3& GetPosition() const { return m_position; } + uint32_t GetWidth() const { return m_width; } + uint32_t GetHeight() const { return m_height; } + uint32_t GetDepth() const { return m_depth; } + const BoundingBox& GetBounds() const { return m_bounds; } + + const TileMap* GetTileMap() const { return m_tileMap; } + VertexBuffer* GetVertices() const { return m_vertices; } + VertexBuffer* GetAlphaVertices() const { return m_alphaVertices; } + uint32_t GetNumVertices() const { return m_numVertices; } + uint32_t GetNumAlphaVertices() const { return m_numAlphaVertices; } + +private: + uint32_t GetIndexOf(uint32_t x, uint32_t y, uint32_t z) const; + + Tile *m_data; + const TileMap *m_tileMap; + GraphicsDevice *m_graphicsDevice; + VertexBuffer *m_vertices; + VertexBuffer *m_alphaVertices; + uint32_t m_numVertices; + uint32_t m_numAlphaVertices; + + uint32_t m_x; + uint32_t m_y; + uint32_t m_z; + uint32_t m_width; + uint32_t m_height; + uint32_t m_depth; + BoundingBox m_bounds; + Vector3 m_position; +}; + +inline Tile* TileChunk::Get(uint32_t x, uint32_t y, uint32_t z) const +{ + uint32_t index = GetIndexOf(x, y, z); + return &m_data[index]; +} + +inline Tile* TileChunk::GetSafe(uint32_t x, uint32_t y, uint32_t z) const +{ + if (!IsWithinLocalBounds((int32_t)x, (int32_t)y, (int32_t)z)) + return NULL; + else + return Get(x, y, z); +} + +inline uint32_t TileChunk::GetIndexOf(uint32_t x, uint32_t y, uint32_t z) const +{ + return (y * m_width * m_depth) + (z * m_width) + x; +} + +inline BOOL TileChunk::IsWithinBounds(int32_t x, int32_t y, int32_t z) const +{ + if (x < (int32_t)GetX() || x >= (int32_t)(GetX() + m_width)) + return FALSE; + if (y < (int32_t)GetY() || y >= (int32_t)(GetY() + m_height)) + return FALSE; + if (z < (int32_t)GetZ() || z >= (int32_t)(GetZ() + m_depth)) + return FALSE; + + return TRUE; +} + +inline BOOL TileChunk::IsWithinLocalBounds(int32_t x, int32_t y, int32_t z) const +{ + if (x < 0 || x >= (int32_t)m_width) + return FALSE; + if (y < 0 || y >= (int32_t)m_height) + return FALSE; + if (z < 0 || z >= (int32_t)m_depth) + return FALSE; + + return TRUE; +} + +#endif + diff --git a/src/tilemap/tilelightdefs.h b/src/tilemap/tilelightdefs.h new file mode 100644 index 0000000..eab98b6 --- /dev/null +++ b/src/tilemap/tilelightdefs.h @@ -0,0 +1,13 @@ +#ifndef __TILEMAP_TILELIGHTDEFS_H_INCLUDED__ +#define __TILEMAP_TILELIGHTDEFS_H_INCLUDED__ + +#include "../framework/common.h" + +typedef uint8_t TILE_LIGHT_VALUE; + +const TILE_LIGHT_VALUE TILE_LIGHT_VALUE_MAX = 15; +const TILE_LIGHT_VALUE TILE_LIGHT_VALUE_SKY = TILE_LIGHT_VALUE_MAX; + + +#endif + diff --git a/src/tilemap/tilemap.cpp b/src/tilemap/tilemap.cpp new file mode 100644 index 0000000..fd26d65 --- /dev/null +++ b/src/tilemap/tilemap.cpp @@ -0,0 +1,439 @@ +#include "../framework/debug.h" + +#include "tilemap.h" + +#include "chunkvertexgenerator.h" +#include "tile.h" +#include "tilemaplighter.h" +#include "tilemesh.h" +#include "tilemeshcollection.h" +#include "tilemeshdefs.h" +#include "../framework/math/intersectiontester.h" +#include "../framework/math/mathhelpers.h" +#include "../framework/math/ray.h" +#include "../framework/math/rectf.h" +#include "../framework/math/vector3.h" + +TileMap::TileMap(TileMeshCollection *tileMeshes, ChunkVertexGenerator *vertexGenerator, TileMapLighter *lighter, GraphicsDevice *graphicsDevice) +{ + STACK_TRACE; + ASSERT(tileMeshes != NULL); + ASSERT(vertexGenerator != NULL); + ASSERT(graphicsDevice != NULL); + + m_tileMeshes = tileMeshes; + m_vertexGenerator = vertexGenerator; + m_lighter = lighter; + m_graphicsDevice = graphicsDevice; + + m_numChunks = 0; + m_widthInChunks = 0; + m_heightInChunks = 0; + m_depthInChunks = 0; + m_chunkWidth = 0; + m_chunkHeight = 0; + m_chunkDepth = 0; + m_chunks = NULL; + + m_ambientLightValue = 0; + m_skyLightValue = TILE_LIGHT_VALUE_SKY; + + m_bounds.min = ZERO_VECTOR; + m_bounds.max = ZERO_VECTOR; +} + +TileMap::~TileMap() +{ + STACK_TRACE; + Clear(); +} + +void TileMap::SetSize(uint32_t numChunksX, uint32_t numChunksY, uint32_t numChunksZ, uint32_t chunkSizeX, uint32_t chunkSizeY, uint32_t chunkSizeZ) +{ + STACK_TRACE; + ASSERT(numChunksX > 0); + ASSERT(numChunksY > 0); + ASSERT(numChunksZ > 0); + ASSERT(chunkSizeX > 0); + ASSERT(chunkSizeY > 0); + ASSERT(chunkSizeZ > 0); + + if (m_numChunks > 0) + Clear(); + + m_numChunks = numChunksX * numChunksY * numChunksZ; + m_widthInChunks = numChunksX; + m_heightInChunks = numChunksY; + m_depthInChunks = numChunksZ; + m_chunkWidth = chunkSizeX; + m_chunkHeight = chunkSizeY; + m_chunkDepth = chunkSizeZ; + + m_chunks = new TileChunk*[m_numChunks]; + ASSERT(m_chunks != NULL); + + // set each one up + for (uint32_t y = 0; y < numChunksY; ++y) + { + for (uint32_t z = 0; z < numChunksZ; ++z) + { + for (uint32_t x = 0; x < numChunksX; ++x) + { + TileChunk *chunk = new TileChunk(x * m_chunkWidth, y * m_chunkHeight, z * m_chunkDepth, m_chunkWidth, m_chunkHeight, m_chunkDepth, this, m_graphicsDevice); + ASSERT(chunk != NULL); + + uint32_t index = GetChunkIndex(x, y, z); + m_chunks[index] = chunk; + } + } + } + + m_bounds.min = ZERO_VECTOR; + m_bounds.max = Vector3((float)GetWidth(), (float)GetHeight(), (float)GetDepth()); +} + +void TileMap::Clear() +{ + STACK_TRACE; + for (uint32_t i = 0; i < m_numChunks; ++i) + { + TileChunk *chunk = m_chunks[i]; + SAFE_DELETE(chunk); + } + SAFE_DELETE_ARRAY(m_chunks); + + m_numChunks = 0; + m_widthInChunks = 0; + m_heightInChunks = 0; + m_depthInChunks = 0; + m_chunkWidth = 0; + m_chunkHeight = 0; + m_chunkDepth = 0; +} + +BOOL TileMap::CheckForCollision(const Ray &ray, uint32_t &x, uint32_t &y, uint32_t &z) const +{ + STACK_TRACE; + // make sure that the ray can actually collide with this map in the first place + Vector3 position; + if (!IntersectionTester::Test(ray, m_bounds, &position)) + return FALSE; + + // convert initial tilemap collision point to tile coords. this is in + // "tilemap space" which is how we want it for the rest of this method + int32_t currentX = (int32_t)position.x; + int32_t currentY = (int32_t)position.y; + int32_t currentZ = (int32_t)position.z; + + // make sure the coords are inrange of the map bounds. 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. + currentX = Clamp(currentX, 0, (int32_t)(GetWidth() - 1)); + currentY = Clamp(currentY, 0, (int32_t)(GetHeight() - 1)); + currentZ = Clamp(currentZ, 0, (int32_t)(GetDepth() - 1)); + + // is the start position colliding with a solid tile? + Tile *startTile = Get(currentX, currentY, currentZ); + if (IsBitSet(TILE_COLLIDABLE, startTile->flags)) + { + // collision found set the tile coords of the collision + x = currentX; + y = currentY; + z = currentZ; + + // and we're done + return TRUE; + } + + // no collision initially, continue on with the rest ... + + // step increments in whole tile units + int32_t stepX = (int32_t)Sign(ray.direction.x); + int32_t stepY = (int32_t)Sign(ray.direction.y); + int32_t stepZ = (int32_t)Sign(ray.direction.z); + + // tile boundary + int32_t tileBoundaryX = currentX + (stepX > 0 ? 1 : 0); + int32_t tileBoundaryY = currentY + (stepY > 0 ? 1 : 0); + int32_t tileBoundaryZ = 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. + // TODO: check that this is compatible with Visual C++ + + // determine how far we can travel along the ray before we hit a tile boundary + Vector3 tMax = Vector3( + (tileBoundaryX - ray.position.x) / ray.direction.x, + (tileBoundaryY - ray.position.y) / ray.direction.y, + (tileBoundaryZ - ray.position.z) / ray.direction.z + ); + if (tMax.x == -INFINITY) + tMax.x = INFINITY; + if (tMax.y == -INFINITY) + tMax.y = INFINITY; + if (tMax.z == -INFINITY) + tMax.z = INFINITY; + if (isnan(tMax.x)) + tMax.x = INFINITY; + if (isnan(tMax.y)) + tMax.y = INFINITY; + if (isnan(tMax.z)) + tMax.z = INFINITY; + + // determine how far we must travel along the ray before we cross a grid cell + Vector3 tDelta = Vector3( + stepX / ray.direction.x, + stepY / ray.direction.y, + stepZ / ray.direction.z + ); + if (tDelta.x == -INFINITY) + tDelta.x = INFINITY; + if (tDelta.y == -INFINITY) + tDelta.y = INFINITY; + if (tDelta.z == -INFINITY) + tDelta.z = INFINITY; + if (isnan(tDelta.x)) + tDelta.x = INFINITY; + if (isnan(tDelta.y)) + tDelta.y = INFINITY; + if (isnan(tDelta.z)) + tDelta.z = INFINITY; + + BOOL collided = FALSE; + BOOL outOfMap = FALSE; + while (!outOfMap) + { + // 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 (tMax.x < tMax.y && tMax.x < tMax.z) + { + // tMax.x is lowest, the YZ tile boundary plane is closest + currentX += stepX; + tMax.x += tDelta.x; + } + else if (tMax.y < tMax.z) + { + // tMax.y is lowest, the XZ tile boundary plane is closest + currentY += stepY; + tMax.y += tDelta.y; + } + else + { + // tMax.z is lowest, the XY tile boundary plane is closest + currentZ += stepZ; + tMax.z += tDelta.z; + } + + // need to figure out if this new position is still inside the bounds of + // the map before we can attempt to determine if the current tile is + // solid + if ( + currentX < 0 || currentX >= (int32_t)GetWidth() || + currentY < 0 || currentY >= (int32_t)GetHeight() || + currentZ < 0 || currentZ >= (int32_t)GetDepth() + ) + outOfMap = TRUE; + else + { + // still inside and at the next position, test for a solid tile + Tile *tile = Get(currentX, currentY, currentZ); + if (IsBitSet(TILE_COLLIDABLE, tile->flags)) + { + collided = TRUE; + + // set the tile coords of the collision + x = currentX; + y = currentY; + z = currentZ; + + break; + } + } + } + + return collided; +} + +BOOL TileMap::CheckForCollision(const Ray &ray, Vector3 &point, uint32_t &x, uint32_t &y, uint32_t &z) const +{ + STACK_TRACE; + // 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, x, y, z)) + return FALSE; + + // 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 CheckForCollisionWithTile(ray, point, x, y, z); +} + +BOOL TileMap::CheckForCollisionWithTile(const Ray &ray, Vector3 &point, uint32_t x, uint32_t y, uint32_t z) const +{ + STACK_TRACE; + const Tile *tile = Get(x, y, z); + const TileMesh *mesh = m_tileMeshes->Get(tile); + + uint32_t numVertices = mesh->GetNumCollisionVertices(); + const Vector3 *vertices = mesh->GetCollisionVertices(); + + // world position of this tile, will be used to move each + // mesh triangle into world space + Vector3 tileWorldPosition = Vector3((float)x, (float)y, (float)z); + + float closestSquaredDistance = FLT_MAX; + BOOL collided = FALSE; + Vector3 collisionPoint = ZERO_VECTOR; + + for (uint32_t i = 0; i < numVertices; i += 3) + { + // get the vertices making up this triangle + Vector3 a = vertices[i]; + Vector3 b = vertices[i + 1]; + Vector3 c = vertices[i + 2]; + + // move these vertices into world space + a += tileWorldPosition; + b += tileWorldPosition; + c += tileWorldPosition; + + if (IntersectionTester::Test(ray, a, b, c, &collisionPoint)) + { + collided = TRUE; + + // if this is the closest collision yet, then keep the distance + // and point of collision + float squaredDistance = Vector3::LengthSquared(collisionPoint - ray.position); + if (squaredDistance < closestSquaredDistance) + { + closestSquaredDistance = squaredDistance; + point = collisionPoint; + } + } + } + + return collided; +} + +BOOL TileMap::GetOverlappedTiles(const BoundingBox &box, uint32_t &x1, uint32_t &y1, uint32_t &z1, uint32_t &x2, uint32_t &y2, uint32_t &z2) const +{ + STACK_TRACE; + // make sure the given box actually intersects with the map in the first place + if (!IntersectionTester::Test(m_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 + int32_t minX = (int32_t)box.min.x; + int32_t minY = (int32_t)box.min.y; + int32_t minZ = (int32_t)box.min.z; + int32_t maxX = (int32_t)ceil(box.max.x); + int32_t maxY = (int32_t)ceil(box.max.y - 1.0f); + int32_t maxZ = (int32_t)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 the map + // HACK: "-1"'s keep us from picking up too many blocks. these were arrived + // at through observation + minX = Clamp(minX, 0, (int32_t)GetWidth()); + minY = Clamp(minY, 0, (int32_t)(GetHeight() - 1)); + minZ = Clamp(minZ, 0, (int32_t)GetDepth()); + maxX = Clamp(maxX, 0, (int32_t)GetWidth()); + maxY = Clamp(maxY, 0, (int32_t)(GetHeight() - 1)); + maxZ = Clamp(maxZ, 0, (int32_t)GetDepth()); + + // return the leftover area + x1 = minX; + y1 = minY; + z1 = minZ; + x2 = maxX; + y2 = maxY; + z2 = maxZ; + + return TRUE; +} + +BOOL TileMap::GetOverlappedChunks(const BoundingBox &box, uint32_t &x1, uint32_t &y1, uint32_t &z1, uint32_t &x2, uint32_t &y2, uint32_t &z2) const +{ + STACK_TRACE; + // make sure the given box actually intersects with the map in the first place + if (!IntersectionTester::Test(m_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 + int32_t minX = (int32_t)box.min.x; + int32_t minY = (int32_t)box.min.y; + int32_t minZ = (int32_t)box.min.z; + int32_t maxX = (int32_t)ceil(box.max.x); + int32_t maxY = (int32_t)ceil(box.max.y - 1.0f); + int32_t maxZ = (int32_t)ceil(box.max.z); + + // now convert to chunk coords + int32_t minChunkX = minX / (int32_t)GetChunkWidth(); + int32_t minChunkY = minY / (int32_t)GetChunkHeight(); + int32_t minChunkZ = minZ / (int32_t)GetChunkDepth(); + int32_t maxChunkX = maxX / (int32_t)GetChunkWidth(); + int32_t maxChunkY = maxY / (int32_t)GetChunkHeight(); + int32_t maxChunkZ = maxZ / (int32_t)GetChunkDepth(); + + // 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 = Clamp(minChunkX, 0, (int32_t)GetWidthInChunks()); + minChunkY = Clamp(minChunkY, 0, (int32_t)(GetHeightInChunks() - 1)); + minChunkZ = Clamp(minChunkZ, 0, (int32_t)GetDepthInChunks()); + maxChunkX = Clamp(maxChunkX, 0, (int32_t)GetWidthInChunks()); + maxChunkY = Clamp(maxChunkY, 0, (int32_t)(GetHeightInChunks() - 1)); + maxChunkZ = Clamp(maxChunkZ, 0, (int32_t)GetDepthInChunks()); + + // return the leftover area + x1 = minChunkX; + y1 = minChunkY; + z1 = minChunkZ; + x2 = maxChunkX; + y2 = maxChunkY; + z2 = maxChunkZ; + + return TRUE; +} + +void TileMap::UpdateVertices() +{ + STACK_TRACE; + ASSERT(m_numChunks > 0); + + for (uint32_t i = 0; i < m_numChunks; ++i) + { + TileChunk *chunk = m_chunks[i]; + UpdateChunkVertices(chunk); + } +} + +void TileMap::UpdateChunkVertices(TileChunk *chunk) +{ + STACK_TRACE; + ASSERT(m_numChunks > 0); + ASSERT(chunk != NULL); + + chunk->UpdateVertices(m_vertexGenerator); +} + +void TileMap::UpdateLighting() +{ + STACK_TRACE; + ASSERT(m_numChunks > 0); + + if (m_lighter != NULL) + m_lighter->Light(this); +} diff --git a/src/tilemap/tilemap.h b/src/tilemap/tilemap.h new file mode 100644 index 0000000..071e6a0 --- /dev/null +++ b/src/tilemap/tilemap.h @@ -0,0 +1,212 @@ +#ifndef __TILEMAP_TILEMAP_H_INCLUDED__ +#define __TILEMAP_TILEMAP_H_INCLUDED__ + +#include "../framework/common.h" + +#include "tile.h" +#include "tilechunk.h" +#include "tilelightdefs.h" +#include "../framework/math/boundingbox.h" +#include "../framework/math/vector3.h" + +#include + +class ChunkVertexGenerator; +class GraphicsDevice; +class TileChunk; +class TileMapLighter; +class TileMesh; +class TileMeshCollection; +struct RectF; +struct Ray; +struct Tile; + +class TileMap +{ +public: + TileMap(TileMeshCollection *tileMeshes, ChunkVertexGenerator *vertexGenerator, TileMapLighter *lighter, GraphicsDevice *graphicsDevice); + virtual ~TileMap(); + + void SetSize(uint32_t numChunksX, uint32_t numChunksY, uint32_t numChunksZ, uint32_t chunkSizeX, uint32_t chunkSizeY, uint32_t chunkSizeZ); + + TileMeshCollection* GetMeshes() const { return m_tileMeshes; } + ChunkVertexGenerator* GetVertexGenerator() const { return m_vertexGenerator; } + TileMapLighter* GetLighter() const { return m_lighter; } + + Tile* Get(uint32_t x, uint32_t y, uint32_t z) const; + Tile* GetSafe(uint32_t x, uint32_t y, uint32_t z) const; + TileChunk* GetChunk(uint32_t chunkX, uint32_t chunkY, uint32_t chunkZ) const; + TileChunk* GetChunkSafe(uint32_t chunkX, uint32_t chunkY, uint32_t chunkZ) const; + TileChunk* GetChunkNextTo(TileChunk *chunk, int32_t offsetX, int32_t offsetY, int32_t offsetZ) const; + TileChunk* GetChunkContaining(uint32_t x, uint32_t y, uint32_t z) const; + + void GetBoundingBoxFor(uint32_t x, uint32_t y, uint32_t z, BoundingBox *box) const; + BoundingBox GetBoundingBoxFor(uint32_t x, uint32_t y, uint32_t z) const; + + BOOL CheckForCollision(const Ray &ray, uint32_t &x, uint32_t &y, uint32_t &z) const; + BOOL CheckForCollision(const Ray &ray, Vector3 &point, uint32_t &x, uint32_t &y, uint32_t &z) const; + BOOL CheckForCollisionWithTile(const Ray &ray, Vector3 &point, uint32_t x, uint32_t y, uint32_t z) const; + BOOL GetOverlappedTiles(const BoundingBox &box, uint32_t &x1, uint32_t &y1, uint32_t &z1, uint32_t &x2, uint32_t &y2, uint32_t &z2) const; + BOOL GetOverlappedChunks(const BoundingBox &box, uint32_t &x1, uint32_t &y1, uint32_t &z1, uint32_t &x2, uint32_t &y2, uint32_t &z2) const; + + void UpdateChunkVertices(uint32_t chunkX, uint32_t chunkY, uint32_t chunkZ); + void UpdateVertices(); + + void UpdateLighting(); + + BOOL IsWithinBounds(int32_t x, int32_t y, int32_t z) const; + + uint32_t GetWidth() const { return m_widthInChunks * m_chunkWidth; } + uint32_t GetHeight() const { return m_heightInChunks * m_chunkHeight; } + uint32_t GetDepth() const { return m_depthInChunks * m_chunkDepth; } + + uint32_t GetChunkWidth() const { return m_chunkWidth; } + uint32_t GetChunkHeight() const { return m_chunkHeight; } + uint32_t GetChunkDepth() const { return m_chunkDepth; } + + uint32_t GetWidthInChunks() const { return m_widthInChunks; } + uint32_t GetHeightInChunks() const { return m_heightInChunks; } + uint32_t GetDepthInChunks() const { return m_depthInChunks; } + const BoundingBox& GetBounds() const { return m_bounds; } + + uint32_t GetNumChunks() const { return m_numChunks; } + + void SetAmbientLightValue(TILE_LIGHT_VALUE value) { m_ambientLightValue = value; } + TILE_LIGHT_VALUE GetAmbientLightValue() const { return m_ambientLightValue; } + void SetSkyLightValue(TILE_LIGHT_VALUE value) { m_skyLightValue = value; } + TILE_LIGHT_VALUE GetSkyLightValue() const { return m_skyLightValue; } + +private: + void Clear(); + + uint32_t GetChunkIndexAt(uint32_t x, uint32_t y, uint32_t z) const; + uint32_t GetChunkIndex(uint32_t chunkX, uint32_t chunkY, uint32_t chunkZ) const; + + void UpdateChunkVertices(TileChunk *chunk); + + TileMeshCollection *m_tileMeshes; + TileChunk **m_chunks; + GraphicsDevice *m_graphicsDevice; + ChunkVertexGenerator *m_vertexGenerator; + TileMapLighter *m_lighter; + + uint32_t m_chunkWidth; + uint32_t m_chunkHeight; + uint32_t m_chunkDepth; + + uint32_t m_widthInChunks; + uint32_t m_heightInChunks; + uint32_t m_depthInChunks; + BoundingBox m_bounds; + + uint32_t m_numChunks; + + TILE_LIGHT_VALUE m_ambientLightValue; + TILE_LIGHT_VALUE m_skyLightValue; +}; + +inline Tile* TileMap::Get(uint32_t x, uint32_t y, uint32_t z) const +{ + TileChunk *chunk = GetChunkContaining(x, y, z); + uint32_t chunkX = x - chunk->GetX(); + uint32_t chunkY = y - chunk->GetY(); + uint32_t chunkZ = z - chunk->GetZ(); + + return chunk->Get(chunkX, chunkY, chunkZ); +} + +inline Tile* TileMap::GetSafe(uint32_t x, uint32_t y, uint32_t z) const +{ + if (!IsWithinBounds((int32_t)x, (int32_t)y, (int32_t)z)) + return NULL; + else + return Get(x, y, z); +} + +inline TileChunk* TileMap::GetChunk(uint32_t chunkX, uint32_t chunkY, uint32_t chunkZ) const +{ + uint32_t index = GetChunkIndex(chunkX, chunkY, chunkZ); + return m_chunks[index]; +} + +inline TileChunk* TileMap::GetChunkSafe(uint32_t chunkX, uint32_t chunkY, uint32_t chunkZ) const +{ + if ( + (chunkX >= m_widthInChunks) || + (chunkY >= m_heightInChunks) || + (chunkZ >= m_depthInChunks) + ) + return NULL; + else + return GetChunk(chunkX, chunkY, chunkZ); +} + +inline TileChunk* TileMap::GetChunkNextTo(TileChunk *chunk, int32_t offsetX, int32_t offsetY, int32_t offsetZ) const +{ + int32_t checkX = chunk->GetX() + offsetX; + int32_t checkY = chunk->GetY() + offsetY; + int32_t checkZ = chunk->GetZ() + offsetZ; + + if ( + (checkX < 0 || (uint32_t)checkX >= m_widthInChunks) || + (checkY < 0 || (uint32_t)checkY >= m_heightInChunks) || + (checkZ < 0 || (uint32_t)checkZ >= m_depthInChunks) + ) + return NULL; + else + return GetChunk(checkX, checkY, checkZ); +} + +inline TileChunk* TileMap::GetChunkContaining(uint32_t x, uint32_t y, uint32_t z) const +{ + uint32_t index = GetChunkIndexAt(x, y, z); + return m_chunks[index]; +} + +inline void TileMap::GetBoundingBoxFor(uint32_t x, uint32_t y, uint32_t z, BoundingBox *box) const +{ + TileChunk *chunk = GetChunkContaining(x, y, z); + uint32_t chunkX = x - chunk->GetX(); + uint32_t chunkY = y - chunk->GetY(); + uint32_t chunkZ = z - chunk->GetZ(); + + chunk->GetBoundingBoxFor(chunkX, chunkY, chunkZ, box); +} + +inline BoundingBox TileMap::GetBoundingBoxFor(uint32_t x, uint32_t y, uint32_t z) const +{ + BoundingBox box; + GetBoundingBoxFor(x, y, z, &box); + return box; +} + +inline void TileMap::UpdateChunkVertices(uint32_t chunkX, uint32_t chunkY, uint32_t chunkZ) +{ + uint32_t index = GetChunkIndex(chunkX, chunkY, chunkZ); + TileChunk *chunk = m_chunks[index]; + UpdateChunkVertices(chunk); +} + +inline uint32_t TileMap::GetChunkIndexAt(uint32_t x, uint32_t y, uint32_t z) const +{ + return GetChunkIndex(x / m_chunkWidth, y / m_chunkHeight, z / m_chunkDepth); +} + +inline uint32_t TileMap::GetChunkIndex(uint32_t chunkX, uint32_t chunkY, uint32_t chunkZ) const +{ + return (chunkY * m_widthInChunks * m_depthInChunks) + (chunkZ * m_widthInChunks) + chunkX; +} + +inline BOOL TileMap::IsWithinBounds(int32_t x, int32_t y, int32_t z) const +{ + if (x < 0 || x >= (int32_t)GetWidth()) + return FALSE; + if (y < 0 || y >= (int32_t)GetHeight()) + return FALSE; + if (z < 0 || z >= (int32_t)GetDepth()) + return FALSE; + + return TRUE; +} +#endif + diff --git a/src/tilemap/tilemaplighter.h b/src/tilemap/tilemaplighter.h new file mode 100644 index 0000000..67c9572 --- /dev/null +++ b/src/tilemap/tilemaplighter.h @@ -0,0 +1,16 @@ +#ifndef __TILEMAP_TILEMAPLIGHTER_H_INCLUDED__ +#define __TILEMAP_TILEMAPLIGHTER_H_INCLUDED__ + +class TileMap; + +class TileMapLighter +{ +public: + TileMapLighter() {} + virtual ~TileMapLighter() {} + + virtual void Light(TileMap *tileMap) = 0; +}; + +#endif + diff --git a/src/tilemap/tilemaprenderer.cpp b/src/tilemap/tilemaprenderer.cpp new file mode 100644 index 0000000..5d3558d --- /dev/null +++ b/src/tilemap/tilemaprenderer.cpp @@ -0,0 +1,104 @@ +#include "../framework/debug.h" + +#include "tilemaprenderer.h" + +#include "tilemap.h" +#include "../framework/graphics/graphicsdevice.h" +#include "../framework/graphics/renderstate.h" +#include "../framework/graphics/shader.h" +#include "../framework/graphics/simplecolortextureshader.h" +#include "../framework/graphics/viewcontext.h" +#include "../framework/math/camera.h" +#include "../framework/math/frustum.h" + +TileMapRenderer::TileMapRenderer(GraphicsDevice *graphicsDevice) +{ + STACK_TRACE; + m_graphicsDevice = graphicsDevice; + + m_chunkRenderer = new ChunkRenderer(graphicsDevice); + + m_numChunksRendered = 0; + m_numAlphaChunksRendered = 0; + m_numVerticesRendered = 0; + m_numAlphaVerticesRendered = 0; +} + +TileMapRenderer::~TileMapRenderer() +{ + STACK_TRACE; + SAFE_DELETE(m_chunkRenderer); +} + +void TileMapRenderer::Render(const TileMap *tileMap, Shader *shader) +{ + STACK_TRACE; + m_numChunksRendered = 0; + m_numVerticesRendered = 0; + + if (shader == NULL) + { + m_graphicsDevice->BindShader(m_graphicsDevice->GetSimpleColorTextureShader()); + m_graphicsDevice->GetSimpleColorTextureShader()->SetModelViewMatrix(m_graphicsDevice->GetViewContext()->GetModelViewMatrix()); + m_graphicsDevice->GetSimpleColorTextureShader()->SetProjectionMatrix(m_graphicsDevice->GetViewContext()->GetProjectionMatrix()); + } + else + { + ASSERT(shader->IsReadyForUse() == TRUE); + m_graphicsDevice->BindShader(shader); + } + + for (uint32_t y = 0; y < tileMap->GetHeightInChunks(); ++y) + { + for (uint32_t z = 0; z < tileMap->GetDepthInChunks(); ++z) + { + for (uint32_t x = 0; x < tileMap->GetWidthInChunks(); ++x) + { + TileChunk *chunk = tileMap->GetChunk(x, y, z); + if (m_graphicsDevice->GetViewContext()->GetCamera()->GetFrustum()->Test(chunk->GetBounds())) + { + m_numVerticesRendered += m_chunkRenderer->Render(chunk); + ++m_numChunksRendered; + } + } + } + } + + m_graphicsDevice->UnbindShader(); +} + +void TileMapRenderer::RenderAlpha(const TileMap *tileMap, Shader *shader) +{ + m_numAlphaChunksRendered = 0; + m_numAlphaVerticesRendered = 0; + + if (shader == NULL) + { + m_graphicsDevice->BindShader(m_graphicsDevice->GetSimpleColorTextureShader()); + m_graphicsDevice->GetSimpleColorTextureShader()->SetModelViewMatrix(m_graphicsDevice->GetViewContext()->GetModelViewMatrix()); + m_graphicsDevice->GetSimpleColorTextureShader()->SetProjectionMatrix(m_graphicsDevice->GetViewContext()->GetProjectionMatrix()); + } + else + { + ASSERT(shader->IsReadyForUse() == TRUE); + m_graphicsDevice->BindShader(shader); + } + + for (uint32_t y = 0; y < tileMap->GetHeightInChunks(); ++y) + { + for (uint32_t z = 0; z < tileMap->GetDepthInChunks(); ++z) + { + for (uint32_t x = 0; x < tileMap->GetWidthInChunks(); ++x) + { + TileChunk *chunk = tileMap->GetChunk(x, y, z); + if (chunk->IsAlphaEnabled() && m_graphicsDevice->GetViewContext()->GetCamera()->GetFrustum()->Test(chunk->GetBounds())) + { + m_numAlphaVerticesRendered += m_chunkRenderer->RenderAlpha(chunk); + ++m_numAlphaChunksRendered; + } + } + } + } + + m_graphicsDevice->UnbindShader(); +} diff --git a/src/tilemap/tilemaprenderer.h b/src/tilemap/tilemaprenderer.h new file mode 100644 index 0000000..b5b451a --- /dev/null +++ b/src/tilemap/tilemaprenderer.h @@ -0,0 +1,40 @@ +#ifndef __TILEMAP_TILEMAPRENDERER_H_INCLUDED__ +#define __TILEMAP_TILEMAPRENDERER_H_INCLUDED__ + +#include "../framework/common.h" + +#include "chunkrenderer.h" + +class GraphicsDevice; +class TileMap; +class Shader; + +class TileMapRenderer +{ +public: + TileMapRenderer(GraphicsDevice *graphicsDevice); + virtual ~TileMapRenderer(); + + void Render(const TileMap *tileMap, Shader *shader = NULL); + void RenderAlpha(const TileMap *tileMap, Shader *shader = NULL); + + uint32_t GetNumVerticesRendered() const { return m_numVerticesRendered; } + uint32_t GetNumAlphaVerticesRendered() const { return m_numAlphaVerticesRendered; } + uint32_t GetNumChunksRendered() const { return m_numChunksRendered; } + uint32_t GetNumAlphaChunksRendered() const { return m_numAlphaChunksRendered; } + + uint32_t GetTotalVerticesRendered() const { return m_numVerticesRendered + m_numAlphaVerticesRendered; } + uint32_t GetTotalChunksRendered() const { return m_numChunksRendered + m_numAlphaChunksRendered; } + +private: + GraphicsDevice *m_graphicsDevice; + ChunkRenderer *m_chunkRenderer; + + uint32_t m_numVerticesRendered; + uint32_t m_numAlphaVerticesRendered; + uint32_t m_numChunksRendered; + uint32_t m_numAlphaChunksRendered; +}; + +#endif + diff --git a/src/tilemap/tilemesh.h b/src/tilemap/tilemesh.h new file mode 100644 index 0000000..9d81479 --- /dev/null +++ b/src/tilemap/tilemesh.h @@ -0,0 +1,56 @@ +#ifndef __TILEMAP_TILEMESH_H_INCLUDED__ +#define __TILEMAP_TILEMESH_H_INCLUDED__ + +#include "../framework/common.h" +#include "tilelightdefs.h" +#include "tilemeshdefs.h" +#include "../framework/math/vector3.h" +#include "../framework/graphics/color.h" + +class VertexBuffer; + +const Vector3 TILEMESH_OFFSET = Vector3(0.5f, 0.5f, 0.5f); + +enum TILEMESH_TYPE +{ + TILEMESH_STATIC, + TILEMESH_CUBE +}; + +class TileMesh +{ +public: + TileMesh() {} + virtual ~TileMesh() {} + + virtual VertexBuffer* GetBuffer() const = 0; + virtual uint32_t GetNumCollisionVertices() const = 0; + virtual const Vector3* GetCollisionVertices() const = 0; + + virtual TILEMESH_TYPE GetType() const = 0; + + BOOL IsCompletelyOpaque() const { return m_opaqueSides == SIDE_ALL; } + BOOL IsOpaque(MESH_SIDES sides) const { return IsBitSet(sides, m_opaqueSides); } + BOOL IsAlpha() const { return m_alpha; } + const Color& GetColor() const { return m_color; } + float GetTranslucency() const { return m_translucency; } + BOOL IsLightSource() const { return m_lightValue > 0; } + TILE_LIGHT_VALUE GetLightValue() const { return m_lightValue; } + +protected: + void SetOpaque(MESH_SIDES sides) { m_opaqueSides = sides; } + void SetAlpha(BOOL alpha) { m_alpha = alpha; } + void SetColor(const Color &color) { m_color = color; } + void SetTranslucency(float translucency) { m_translucency = translucency; } + void SetLight(TILE_LIGHT_VALUE lightValue) { m_lightValue = lightValue; } + +private: + MESH_SIDES m_opaqueSides; + BOOL m_alpha; + Color m_color; + float m_translucency; + TILE_LIGHT_VALUE m_lightValue; +}; + +#endif + diff --git a/src/tilemap/tilemeshcollection.cpp b/src/tilemap/tilemeshcollection.cpp new file mode 100644 index 0000000..3672564 --- /dev/null +++ b/src/tilemap/tilemeshcollection.cpp @@ -0,0 +1,87 @@ +#include "../framework/debug.h" + +#include "tilemeshcollection.h" +#include "cubetilemesh.h" +#include "statictilemesh.h" +#include "tilelightdefs.h" +#include "tilemesh.h" +#include "tilemeshdefs.h" +#include "../framework/assets/static/staticmesh.h" +#include "../framework/graphics/color.h" +#include "../framework/graphics/textureatlas.h" + +TileMeshCollection::TileMeshCollection(const TextureAtlas *textureAtlas) +{ + STACK_TRACE; + ASSERT(textureAtlas != NULL); + m_textureAtlas = textureAtlas; + + // the first mesh (index = 0) should always be a NULL one as this has special meaning + // in other TileMap-related objects + AddMesh(NULL); +} + +TileMeshCollection::~TileMeshCollection() +{ + STACK_TRACE; + for (size_t i = 0; i < m_meshes.size(); ++i) + { + TileMesh *mesh = m_meshes[i]; + SAFE_DELETE(mesh); + } +} + +uint32_t TileMeshCollection::Add(const StaticMesh *mesh, uint32_t textureIndex, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue, BOOL alpha, float translucency, const Color &color) +{ + STACK_TRACE; + return Add(mesh, (StaticMesh*)NULL, textureIndex, opaqueSides, lightValue, alpha, translucency, color); +} + +uint32_t TileMeshCollection::Add(const StaticMesh *mesh, const StaticMesh *collisionMesh, uint32_t textureIndex, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue, BOOL alpha, float translucency, const Color &color) +{ + STACK_TRACE; + StaticTileMesh *tileMesh = new StaticTileMesh(mesh, &m_textureAtlas->GetTile(textureIndex).texCoords, opaqueSides, lightValue, alpha, translucency, color, collisionMesh); + ASSERT(tileMesh != NULL); + + return AddMesh(tileMesh); +} + +uint32_t TileMeshCollection::Add(const StaticMesh *mesh, uint32_t *textureIndexes, uint32_t numIndexes, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue, BOOL alpha, float translucency, const Color &color) +{ + STACK_TRACE; + return Add(mesh, (StaticMesh*)NULL, textureIndexes, numIndexes, opaqueSides, lightValue, alpha, translucency, color); +} + +uint32_t TileMeshCollection::Add(const StaticMesh *mesh, const StaticMesh *collisionMesh, uint32_t *textureIndexes, uint32_t numIndexes, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue, BOOL alpha, float translucency, const Color &color) +{ + STACK_TRACE; + ASSERT(numIndexes > 0); + RectF *tileBoundaries = new RectF[numIndexes]; + ASSERT(tileBoundaries != NULL); + + for (uint32_t i = 0; i < numIndexes; ++i) + tileBoundaries[i] = m_textureAtlas->GetTile(textureIndexes[i]).texCoords; + + StaticTileMesh *tileMesh = new StaticTileMesh(mesh, tileBoundaries, numIndexes, opaqueSides, lightValue, alpha, translucency, color, collisionMesh); + ASSERT(tileMesh != NULL); + + SAFE_DELETE_ARRAY(tileBoundaries); + + return AddMesh(tileMesh); +} + +uint32_t TileMeshCollection::AddCube(uint32_t textureIndex, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue, BOOL alpha, float translucency, const Color &color) +{ + STACK_TRACE; + CubeTileMesh *tileMesh = new CubeTileMesh(SIDE_ALL, &m_textureAtlas->GetTile(textureIndex).texCoords, opaqueSides, lightValue, alpha, translucency, color); + ASSERT(tileMesh != NULL); + + return AddMesh(tileMesh); +} + +uint32_t TileMeshCollection::AddMesh(TileMesh *mesh) +{ + STACK_TRACE; + m_meshes.push_back(mesh); + return m_meshes.size() - 1; +} diff --git a/src/tilemap/tilemeshcollection.h b/src/tilemap/tilemeshcollection.h new file mode 100644 index 0000000..a361d0e --- /dev/null +++ b/src/tilemap/tilemeshcollection.h @@ -0,0 +1,42 @@ +#ifndef __TILEMAP_TILEMESHCOLLECTION_H_INCLUDED__ +#define __TILEMAP_TILEMESHCOLLECTION_H_INCLUDED__ + +#include "../framework/common.h" +#include "../framework/graphics/color.h" +#include "tile.h" +#include "tilelightdefs.h" +#include "tilemeshdefs.h" +#include + +class StaticMesh; +class TextureAtlas; +class TileMesh; + +typedef stl::vector TileMeshList; + +class TileMeshCollection +{ +public: + TileMeshCollection(const TextureAtlas *textureAtlas); + virtual ~TileMeshCollection(); + + const TextureAtlas* GetTextureAtlas() const { return m_textureAtlas; } + + uint32_t Add(const StaticMesh *mesh, uint32_t textureIndex, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue = 0, BOOL alpha = FALSE, float translucency = 1.0f, const Color &color = COLOR_WHITE); + uint32_t Add(const StaticMesh *mesh, const StaticMesh *collisionMesh, uint32_t textureIndex, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue = 0, BOOL alpha = FALSE, float translucency = 1.0f, const Color &color = COLOR_WHITE); + uint32_t Add(const StaticMesh *mesh, uint32_t *textureIndexes, uint32_t numIndexes, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue = 0, BOOL alpha = FALSE, float translucency = 1.0f, const Color &color = COLOR_WHITE); + uint32_t Add(const StaticMesh *mesh, const StaticMesh *collisionMesh, uint32_t *textureIndexes, uint32_t numIndexes, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue = 0, BOOL alpha = FALSE, float translucency = 1.0f, const Color &color = COLOR_WHITE); + uint32_t AddCube(uint32_t textureIndex, MESH_SIDES opaqueSides, TILE_LIGHT_VALUE lightValue = 0, BOOL alpha = FALSE, float translucency = 1.0f, const Color &color = COLOR_WHITE); + + TileMesh* Get(const Tile *tile) const { return m_meshes[tile->tile]; } + TileMesh* Get(uint32_t index) const { return m_meshes[index]; } + uint32_t GetCount() const { return m_meshes.size(); } + +private: + uint32_t AddMesh(TileMesh *mesh); + + const TextureAtlas *m_textureAtlas; + TileMeshList m_meshes; +}; + +#endif diff --git a/src/tilemap/tilemeshdefs.h b/src/tilemap/tilemeshdefs.h new file mode 100644 index 0000000..db2dab3 --- /dev/null +++ b/src/tilemap/tilemeshdefs.h @@ -0,0 +1,25 @@ +#ifndef __TILEMAP_TILEMESHDEFS_H_INCLUDED__ +#define __TILEMAP_TILEMESHDEFS_H_INCLUDED__ + +#include "../framework/common.h" + +// random place for global-ish mesh definitions and constants + +enum SIDES +{ + SIDE_TOP = 1, + SIDE_BOTTOM = 2, + SIDE_FRONT = 4, + SIDE_BACK = 8, + SIDE_LEFT = 16, + SIDE_RIGHT = 32, + SIDE_ALL = 255 +}; + +typedef uint8_t CUBE_FACES; +typedef uint8_t MESH_SIDES; + +const uint32_t CUBE_VERTICES_PER_FACE = 6; + +#endif +