Files
watrbx-game-engine/Rendering/GfxRender/MegaCluster.cpp
T
2025-09-18 17:55:52 -04:00

1918 lines
65 KiB
C++

#include "stdafx.h"
#include "MegaCluster.h"
#include "v8datamodel/MegaCluster.h"
#include "Voxel/AreaCopy.h"
#include "Voxel/Grid.h"
#include "v8world/Primitive.h"
#include "SceneUpdater.h"
#include "Util.h"
#include "FastLog.h"
#include "Water.h"
#include "G3D/Vector4int8.h"
#include "Material.h"
#include "ShaderManager.h"
#include "TextureManager.h"
#include "LightGrid.h"
#include "SceneManager.h"
#include "VisualEngine.h"
#include "GfxBase/RenderCaps.h"
LOGGROUP(RbxMegaClustersUpdate)
LOGGROUP(MegaClusterDirty)
LOGGROUP(TerrainCellListener)
using namespace RBX::Voxel;
namespace RBX
{
namespace Graphics
{
static Color4uint8 packNormal(float x, float y, float z)
{
Vector3 normal = normalize(Vector3(x, y, z));
return Color4uint8(normal.x * 127 + 127, normal.y * 127 + 127, normal.z * 127 + 127, 0);
}
struct Quad
{
unsigned char p0, p1, p2, p3;
};
struct Wedge
{
Quad geom;
Color4uint8 normals[4];
Color4uint8 tangents[4];
};
const Wedge kWedges[6] =
{
// Block
{
{
7, 4, 1, 2
},
{
packNormal(0,1,1),
packNormal(1,1,0),
packNormal(0,1,-1),
packNormal(-1,1,0),
}
},
// Vertical Wedge
{
{
7, 4, 1, 2
},
{
packNormal(0,1,1),
packNormal(1,1,0),
packNormal(0,1,-1),
packNormal(-1,1,0),
},
{
packNormal(1,0,0),
packNormal(0,0,-1),
packNormal(-1,0,0),
packNormal(0,0,1),
}
},
// Corner Wedge
{
{
7, 7, 0, 2
},
{
packNormal(-1,1,1),
packNormal(1,1,1),
packNormal(1,1,-1),
packNormal(-1,1,-1),
},
{
packNormal(1,0,1),
packNormal(1,0,-1),
packNormal(-1,0,-1),
packNormal(-1,0,1),
}
},
// Inverse corner Wedge
{
{
6, 4, 1, 1,
},
{
packNormal(-1,1,1),
packNormal(1,1,1),
packNormal(1,1,-1),
packNormal(-1,1,-1),
},
{
packNormal(1,0,1),
packNormal(1,0,-1),
packNormal(-1,0,-1),
packNormal(-1,0,1),
}
},
// Horizontal Wedge
{
{
6, 4, 0, 2
},
{
packNormal(-1,0,1),
packNormal(1,0,1),
packNormal(1,0,-1),
packNormal(-1,0,-1),
},
{
packNormal(1,0,1),
packNormal(1,0,-1),
packNormal(-1,0,-1),
packNormal(-1,0,1),
}
},
// Empty Wedge
{
{
7, 4, 1, 2
},
{
packNormal(0,1,1),
packNormal(1,1,0),
packNormal(0,1,-1),
packNormal(-1,1,0),
},
{
packNormal(1,0,0),
packNormal(0,0,1),
packNormal(-1,0,0),
packNormal(0,0,-1),
}
}
};
enum SideStatus
{
SideEmpty = 0,
SideTriangle = 1,
SideFull = 2
};
static const SideStatus kFaceToStatusMap[] = { SideTriangle, SideTriangle, SideTriangle, SideTriangle, SideEmpty, SideFull };
static unsigned char rotateCornerIndex(unsigned char index, CellOrientation orient)
{
unsigned char hipart = index & 0x4;
unsigned lowpart = index & 0x3;
lowpart = (lowpart + orient) % 4;
return hipart | lowpart;
}
static SideStatus getStatusFromFace(const BlockAxisFace& face)
{
return kFaceToStatusMap[ face.skippedCorner ];
}
static const Vector3int16 centerOrig = Vector3int16(kCELL_SIZE /2 , kCELL_SIZE /2, kCELL_SIZE /2);
struct OFFSETINFOV2
{
Vector3int16 position[4];
Color4uint8 normal;
Color4uint8 tangent;
};
static const int HSIZE = kCELL_SIZE/2;
static const OFFSETINFOV2 kAxisSideLookup[6] =
{
{ // PlusX
Vector3int16( HSIZE, HSIZE, -HSIZE),
Vector3int16( HSIZE, HSIZE, HSIZE),
Vector3int16( HSIZE, -HSIZE, HSIZE),
Vector3int16( HSIZE, -HSIZE, -HSIZE),
packNormal(1, 0, 0), // normal
packNormal(0, 0, -1), // tangent
},
{ // PlusZ
Vector3int16( HSIZE, HSIZE, HSIZE),
Vector3int16(-HSIZE, HSIZE, HSIZE),
Vector3int16(-HSIZE, -HSIZE, HSIZE),
Vector3int16( HSIZE, -HSIZE, HSIZE),
packNormal(0, 0, 1), // normal
packNormal(1, 0, 0), // tangent
},
{ // MinusX
Vector3int16(-HSIZE, HSIZE, HSIZE),
Vector3int16(-HSIZE, HSIZE, -HSIZE),
Vector3int16(-HSIZE, -HSIZE, -HSIZE),
Vector3int16(-HSIZE, -HSIZE, HSIZE),
packNormal(-1, 0, 0), // normal
packNormal(0, 0, 1), // tangent
},
{ // MinusZ
Vector3int16(-HSIZE, HSIZE, -HSIZE),
Vector3int16( HSIZE, HSIZE, -HSIZE),
Vector3int16( HSIZE, -HSIZE, -HSIZE),
Vector3int16(-HSIZE, -HSIZE, -HSIZE),
packNormal(0, 0, -1), // normal
packNormal(-1, 0, 0), // tangent
},
{ // PlusY
Vector3int16( HSIZE, HSIZE, -HSIZE),
Vector3int16(-HSIZE, HSIZE, -HSIZE),
Vector3int16(-HSIZE, HSIZE, HSIZE),
Vector3int16( HSIZE, HSIZE, HSIZE),
packNormal(0, 1, 0), // normal
packNormal(1, 0, 0), // tangent
},
{ // MinusY
Vector3int16( HSIZE, -HSIZE, HSIZE),
Vector3int16(-HSIZE, -HSIZE, HSIZE),
Vector3int16(-HSIZE, -HSIZE, -HSIZE),
Vector3int16( HSIZE, -HSIZE, -HSIZE),
packNormal(0, -1, 0), // normal
packNormal(1, 0, 0), // tangent
},
};
struct CORNEROFFSET
{
Vector3int16 posoffset;
};
static CORNEROFFSET CornerLookup[8] =
{
{ Vector3int16(-kCELL_SIZE/2, -kCELL_SIZE/2, -kCELL_SIZE/2) },
{ Vector3int16(-kCELL_SIZE/2, -kCELL_SIZE/2, kCELL_SIZE/2) },
{ Vector3int16(kCELL_SIZE/2, -kCELL_SIZE/2, kCELL_SIZE/2) },
{ Vector3int16(kCELL_SIZE/2, -kCELL_SIZE/2, -kCELL_SIZE/2) },
{ Vector3int16(-kCELL_SIZE/2, kCELL_SIZE/2, -kCELL_SIZE/2) },
{ Vector3int16(-kCELL_SIZE/2, kCELL_SIZE/2, kCELL_SIZE/2) },
{ Vector3int16(kCELL_SIZE/2, kCELL_SIZE/2, kCELL_SIZE/2) },
{ Vector3int16(kCELL_SIZE/2, kCELL_SIZE/2, -kCELL_SIZE/2) },
};
struct MaterialTextureCoordinates
{
const Vector2int16 startPixel;
const Vector2int16 oneCellPixels;
const unsigned int xMask;
const unsigned int yMask;
MaterialTextureCoordinates(const Vector2int16& startPixel, const Vector2int16& endPixel, const Vector2int16& logicalCellSize)
: startPixel(startPixel)
, xMask(logicalCellSize.x - 1)
, yMask(logicalCellSize.y - 1)
, oneCellPixels((endPixel - startPixel) / logicalCellSize)
{
// assert that x and y dimensions are powers of 2
RBXASSERT((logicalCellSize.x & (logicalCellSize.x - 1)) == 0);
RBXASSERT((logicalCellSize.y & (logicalCellSize.y - 1)) == 0);
}
void calculateTextureCoordinates(const Vector2int16& cell, const Vector2int16& atlasOffset, Vector2int16* uvs) const
{
Vector2int16 cellMod = Vector2int16(cell.x & xMask, cell.y & yMask) * oneCellPixels;
Vector2int16 uvMin = startPixel + cellMod + atlasOffset;
Vector2int16 uvMax = uvMin + oneCellPixels;
uvs[0] = Vector2int16(uvMax.x, uvMin.y);
uvs[1] = Vector2int16(uvMin.x, uvMin.y);
uvs[2] = Vector2int16(uvMin.x, uvMax.y);
uvs[3] = Vector2int16(uvMax.x, uvMax.y);
}
};
struct TriangleMaterialTextureCoordinates
{
struct Triangle
{
Vector2int16 a, b, c, d;
};
boost::scoped_array<Triangle> table;
int mask;
TriangleMaterialTextureCoordinates(const Vector2int16& startPixel, const Vector2int16& endPixel, int cellCount, int tiling, bool inverse)
{
table.reset(new Triangle[cellCount * cellCount * tiling * tiling]);
mask = cellCount * tiling - 1;
Vector2 startPixelUv = Vector2(endPixel.x, startPixel.y);
Vector2 areaSizeUv = Vector2(endPixel - startPixel);
// X axis has triangle base, repeated cellCount + 0.5 times
float triangleBase = areaSizeUv.x / (cellCount + 0.5f);
// Y axis has triangle height, repeated cellCount times
float triangleHeight = areaSizeUv.y / cellCount;
Vector2 triangleSide = Vector2(-triangleBase / 2, triangleHeight);
float triangleBaseTile = triangleBase / tiling;
Vector2 triangleSideTile = triangleSide / tiling;
for (int y = 0; y < cellCount * tiling; ++y)
{
for (int x = 0; x < cellCount * tiling; ++x)
{
Triangle& t = table[x + y * cellCount * tiling];
// figure out quad coordinates for this triangle and the inverse triangle pair
Vector2 quadStart = startPixelUv - Vector2(triangleBaseTile, 0) * x + triangleSideTile * y;
Vector2 quadEnd = quadStart - Vector2(triangleBaseTile, 0) + triangleSideTile;
// special treatment for last big triangle - it moves to the right to conserve UV space
if (x / tiling == cellCount - 1 && y / tiling == cellCount - 1 && (x % tiling) + (y % tiling) + !inverse >= tiling)
{
quadStart.x += triangleBase * cellCount;
quadEnd.x += triangleBase * cellCount;
}
if (inverse)
{
t.a = Vector2int16(quadStart);
t.b = Vector2int16(quadStart - Vector2(triangleBaseTile, 0));
t.c = Vector2int16();
t.d = Vector2int16(quadStart + triangleSideTile);
}
else
{
t.a = Vector2int16();
t.b = Vector2int16(quadStart - Vector2(triangleBaseTile, 0));
t.c = Vector2int16(quadEnd);
t.d = Vector2int16(quadStart + triangleSideTile);
}
}
}
}
void calculateTextureCoordinates(const Vector2int16& cell, const Vector2int16& atlasOffset, Vector2int16* uvs) const
{
int cellModX = mask - (cell.x & mask);
int cellModY = cell.y & mask;
int index = cellModX + cellModY * (mask + 1);
uvs[0] = table[index].a + atlasOffset;
uvs[1] = table[index].b + atlasOffset;
uvs[2] = table[index].c + atlasOffset;
uvs[3] = table[index].d + atlasOffset;
}
};
namespace {
const int kMaterialPixelDimension = 512;
const int kAtlasPixelDimension = 4 * kMaterialPixelDimension;
// Top
const MaterialTextureCoordinates kMaterial_Top_High(Vector2int16(8, 8), Vector2int16(248, 248), Vector2int16(4, 4));
const MaterialTextureCoordinates kMaterial_Top_Low (Vector2int16(8, 8), Vector2int16(248, 248), Vector2int16(16, 16));
// TopSide
const MaterialTextureCoordinates kMaterial_TopSide_High(Vector2int16(264, 8), Vector2int16(504, 68), Vector2int16(4, 1));
const MaterialTextureCoordinates kMaterial_TopSide_Low (Vector2int16(264, 8), Vector2int16(504, 68), Vector2int16(16, 4));
// Side
const MaterialTextureCoordinates kMaterial_Side_High(Vector2int16(264, 68), Vector2int16(504, 188), Vector2int16(4, 2));
const MaterialTextureCoordinates kMaterial_Side_Low (Vector2int16(264, 68), Vector2int16(504, 188), Vector2int16(16, 8));
// WedgeVertical
const MaterialTextureCoordinates kMaterial_WedgeVertical_High(Vector2int16(8, 332), Vector2int16(248, 504), Vector2int16(4, 2));
const MaterialTextureCoordinates kMaterial_WedgeVertical_Low (Vector2int16(8, 332), Vector2int16(248, 504), Vector2int16(16, 8));
// WedgeHorizontal
const MaterialTextureCoordinates kMaterial_WedgeHorizontal_High(Vector2int16(332, 384), Vector2int16(504, 504), Vector2int16(2, 2));
const MaterialTextureCoordinates kMaterial_WedgeHorizontal_Low (Vector2int16(332, 384), Vector2int16(504, 504), Vector2int16(8, 8));
// Bottom
const MaterialTextureCoordinates kMaterial_Bottom_High(Vector2int16(8, 264), Vector2int16(216, 316), Vector2int16(4, 1));
const MaterialTextureCoordinates kMaterial_Bottom_Low (Vector2int16(8, 264), Vector2int16(216, 316), Vector2int16(16, 4));
// WedgeCorner
const TriangleMaterialTextureCoordinates kMaterial_WedgeCorner_High(Vector2int16(290, 204), Vector2int16(504, 354), 2, 1, false);
const TriangleMaterialTextureCoordinates kMaterial_WedgeCorner_Low (Vector2int16(290, 204), Vector2int16(504, 354), 2, 4, false);
// WedgeInverseCorner
const TriangleMaterialTextureCoordinates kMaterial_WedgeInverseCorner_High(Vector2int16(290, 204), Vector2int16(504, 354), 2, 1, true);
const TriangleMaterialTextureCoordinates kMaterial_WedgeInverseCorner_Low (Vector2int16(290, 204), Vector2int16(504, 354), 2, 4, true);
}
static Vector2int16 AtlasOffsetLookup[16] =
{
Vector2int16(0, 0) * kMaterialPixelDimension, // Grass
Vector2int16(1, 0) * kMaterialPixelDimension, // Sand
Vector2int16(2, 0) * kMaterialPixelDimension, // Brick
Vector2int16(3, 0) * kMaterialPixelDimension, // Granite
Vector2int16(0, 1) * kMaterialPixelDimension, // Asphault
Vector2int16(3, 1) * kMaterialPixelDimension, // Iron
Vector2int16(1, 1) * kMaterialPixelDimension, // Aluminum
Vector2int16(2, 1) * kMaterialPixelDimension, // Gold
Vector2int16(2, 2) * kMaterialPixelDimension, // WoodPlank
Vector2int16(3, 2) * kMaterialPixelDimension, // WoodLog
Vector2int16(0, 3) * kMaterialPixelDimension, // Gravel
Vector2int16(1, 3) * kMaterialPixelDimension, // CinderBlock
Vector2int16(2, 3) * kMaterialPixelDimension, // MossyStone
Vector2int16(3, 3) * kMaterialPixelDimension, // Cement
Vector2int16(0, 2) * kMaterialPixelDimension, // RedPlastic
Vector2int16(1, 2) * kMaterialPixelDimension, // BluePlastic
};
static const Vector3int16 kFaceDirectionLocationOffset[6] =
{
Vector3int16(1,0,0),
Vector3int16(0,0,1),
Vector3int16(-1,0,0),
Vector3int16(0,0,-1),
Vector3int16(0,1,0),
Vector3int16(0,-1,0)
};
// offsets:
// Pos X 0 => 2
// Pos Z 1 => 3
// Neg X 2 => 0
// Neg Z 3 => 1
// Pos Y 4 => 5
// Neg Y 5 => 4
static const FaceDirection kOppositeSideOffset[6] = { MinusX, MinusZ, PlusX, PlusZ, MinusY, PlusY };
enum RenderPredStatus
{
NO_RENDER = 0,
RENDER_FORWARD = 1,
RENDER_BACKWARD = 2,
RENDER_BOTH = 3,
};
template<class VoxelStore>
struct SolidTerrainRenderPredicate
{
inline bool wedgeFace(const Voxel::Cell cell)
{
return cell.solid.getBlock() != CELL_BLOCK_Solid && cell.solid.getBlock() != CELL_BLOCK_Empty;
}
inline RenderPredStatus internal(const typename VoxelStore::Region::iterator& iterator, FaceDirection direction)
{
Voxel::Cell centerCell = iterator.getCellAtCurrentLocation();
Voxel::Cell neighborCell = iterator.getNeighborCell(direction);
const BlockAxisFace& centerFace = GetOrientedFace(centerCell, direction);
const BlockAxisFace& neighborFace = GetOrientedFace(neighborCell, kOppositeSideOffset[direction]);
SideStatus centerStatus = getStatusFromFace(centerFace);
SideStatus neighborStatus = getStatusFromFace(neighborFace);
if (centerStatus != neighborStatus)
{
return (RenderPredStatus) (RENDER_FORWARD + (neighborStatus > centerStatus));
}
else if (centerStatus == SideTriangle)
{
BlockAxisFace::SkippedCorner mirrorSkippedCorner;
if (direction == PlusY || direction == MinusY)
{
mirrorSkippedCorner = BlockAxisFace::YAxisMirror(centerFace.skippedCorner);
}
else
{
mirrorSkippedCorner = BlockAxisFace::XZAxisMirror(centerFace.skippedCorner);
}
if (mirrorSkippedCorner != neighborFace.skippedCorner)
{
return RENDER_BOTH;
}
}
return NO_RENDER;
}
inline bool external(const typename VoxelStore::Region::iterator& iterator, FaceDirection direction)
{
return GetOrientedFace(iterator.getCellAtCurrentLocation(), direction).skippedCorner != BlockAxisFace::EmptyAllSkipped;
}
};
template<class VoxelStore>
struct WaterRenderPredicate {
inline bool wedgeFace(const Voxel::Cell cell) const
{
return false;
}
inline RenderPredStatus internal(const typename VoxelStore::Region::iterator& iterator, FaceDirection direction) const
{
const Voxel::Cell center = iterator.getCellAtCurrentLocation();
const Voxel::Cell neighbor = iterator.getNeighborCell(direction);
if (!isWedgeSideNotFull(center, direction) || !isWedgeSideNotFull(neighbor, kOppositeSideOffset[direction]))
{
return NO_RENDER;
}
// technically this could be reduced to
// iterator.hasWaterAtCurrentLocation(), but that call is
// moderately expensive, so it is faster to short circuit it when
// possible.
bool centerQualifiesToRender =
!center.isEmpty() &&
center.solid.getBlock() != CELL_BLOCK_Solid &&
(center.solid.getBlock() == CELL_BLOCK_Empty ||
iterator.hasWaterAtCurrentLocation());
bool neighborQualifiesToRender =
!neighbor.isEmpty() &&
neighbor.solid.getBlock() != CELL_BLOCK_Solid &&
(neighbor.solid.getBlock() == CELL_BLOCK_Empty ||
iterator.hasWaterAtNeighbor(direction));
bool willRender = centerQualifiesToRender != neighborQualifiesToRender;
return (RenderPredStatus)
(willRender + (willRender && neighborQualifiesToRender));
}
inline bool external(const typename VoxelStore::Region::iterator& iterator, FaceDirection direction) const
{
Voxel::Cell center = iterator.getCellAtCurrentLocation();
return isWedgeSideNotFull(center, direction) && iterator.hasWaterAtCurrentLocation();
}
};
template<class VoxelStore>
struct FaceCounter
{
int count;
FaceCounter()
: count(0)
{
}
void apply(const typename VoxelStore::Region::iterator& iterator, FaceDirection d, RenderPredStatus status)
{
count += ((status + 1) >> 1);
}
void wedgeFace(const typename VoxelStore::Region::iterator& iterator)
{
count++;
}
};
inline Vector3int16 computeCenter(const Vector3int16& logical)
{
return Vector3int16(
kCELL_SIZE * (logical.x & (kXZ_CHUNK_SIZE - 1)) + centerOrig.x,
kCELL_SIZE * (logical.y & (kY_CHUNK_SIZE - 1)) + centerOrig.y,
kCELL_SIZE * (logical.z & (kXZ_CHUNK_SIZE - 1)) + centerOrig.z);
}
struct EdgeDistanceLookup
{
Color4uint8 values[4];
};
struct FaceVertexLookup
{
unsigned int indices[4];
};
static EdgeDistanceLookup gEdgeDistanceLookup[16][6];
static bool gTriangleOutlineLookup[4][4];
static FaceVertexLookup gFaceVertexLookup[6];
static void initLookupTables()
{
for (unsigned outlineMask = 0; outlineMask < 16; outlineMask++)
{
for (unsigned skippedCorner = BlockAxisFace::TopRight; skippedCorner <= BlockAxisFace::FullNoneSkipped; skippedCorner++)
{
Vector4 edgeMin = Vector4(outlineMask & 0x1 ? 0.0f : 10.0f, outlineMask & 0x2 ? 0.0f : 10.0f, outlineMask & 0x4 ? 0.0f : 10.0f, outlineMask & 0x8 ? 0.0f : 10.0f);
Vector4 edgeMax = Vector4(outlineMask & 0x1 ? 4.0f : 10.0f, outlineMask & 0x2 ? 4.0f : 10.0f, outlineMask & 0x4 ? 4.0f : 10.0f, outlineMask & 0x8 ? 4.0f : 10.0f);
unsigned skippedIndex = skippedCorner < 4 ? (skippedCorner + 3) % 4 : 4;
const Color4uint8 outlineOffsets [] =
{
Color4uint8(edgeMin.x, edgeMax.y, edgeMax.z, edgeMin.w),
Color4uint8(edgeMin.x, edgeMax.y, edgeMin.z, edgeMax.w),
Color4uint8(edgeMax.x, edgeMin.y, edgeMin.z, edgeMax.w),
Color4uint8(edgeMax.x, edgeMin.y, edgeMax.z, edgeMin.w),
};
EdgeDistanceLookup& lookup = gEdgeDistanceLookup[outlineMask][skippedCorner];
for (unsigned int i = 0; i < 4; ++i)
{
if (i == skippedIndex)
{
switch (skippedCorner)
{
case 0: lookup.values[i] = Color4uint8(edgeMin.x, edgeMin.y, edgeMax.z, edgeMin.w); break;
case 1: lookup.values[i] = Color4uint8(edgeMin.x, edgeMax.y, edgeMin.z, edgeMin.w); break;
case 2: lookup.values[i] = Color4uint8(edgeMin.x, edgeMin.y, edgeMin.z, edgeMax.w); break;
case 3: lookup.values[i] = Color4uint8(edgeMax.x, edgeMin.y, edgeMin.z, edgeMin.w); break;
}
}
else
{
lookup.values[i] = outlineOffsets[i];
}
}
}
}
for (unsigned skippedCorner = BlockAxisFace::TopRight; skippedCorner <= BlockAxisFace::BottomRight; skippedCorner++)
{
for (unsigned incomingEdge = 0; incomingEdge < 4; incomingEdge++)
{
unsigned incoming = incomingEdge ^ 0x1;
bool result = false;
switch (skippedCorner)
{
case BlockAxisFace::TopRight:
result = incoming == 1 || incoming == 2;
break;
case BlockAxisFace::TopLeft:
result = incoming == 1 || incoming == 3;
break;
case BlockAxisFace::BottomLeft:
result = incoming == 3 || incoming == 0;
break;
case BlockAxisFace::BottomRight:
result = incoming == 0 || incoming == 2;
break;
}
gTriangleOutlineLookup[skippedCorner][incomingEdge] = result;
}
}
for (unsigned skippedCorner = BlockAxisFace::TopRight; skippedCorner <= BlockAxisFace::FullNoneSkipped; skippedCorner++)
{
unsigned int stitchingOffset = BlockAxisFace::divideTopLeftToBottomRight((BlockAxisFace::SkippedCorner)skippedCorner) ? 0 : 3;
for (unsigned int i = 0; i < 4; ++i)
{
unsigned int stitchI = (i + stitchingOffset) % 4;
gFaceVertexLookup[skippedCorner].indices[stitchI] = i;
}
if (skippedCorner < 4)
{
unsigned int i = (skippedCorner + 3) % 4;
unsigned int stitchI = (skippedCorner + stitchingOffset) % 4;
gFaceVertexLookup[skippedCorner].indices[stitchI] = i;
}
}
}
template <typename Vertex>
inline void outputFace(Vertex* output, const Vector3int16& center,
const OFFSETINFOV2& offsetInfo, const BlockAxisFace& face,
const Vector2int16 highUv[4], const Vector2int16 lowUv[4],
const unsigned char outlineMask)
{
const EdgeDistanceLookup& edgeDistances = gEdgeDistanceLookup[outlineMask][face.skippedCorner];
const Color4uint8& normal = offsetInfo.normal;
const Color4uint8& tangent = offsetInfo.tangent;
const FaceVertexLookup& fvl = gFaceVertexLookup[face.skippedCorner];
int i0 = fvl.indices[0], i1 = fvl.indices[1], i2 = fvl.indices[2], i3 = fvl.indices[3];
output[0].create(center + offsetInfo.position[i0], normal, highUv[i0], lowUv[i0], edgeDistances.values[i0], tangent);
output[1].create(center + offsetInfo.position[i1], normal, highUv[i1], lowUv[i1], edgeDistances.values[i1], tangent);
output[2].create(center + offsetInfo.position[i2], normal, highUv[i2], lowUv[i2], edgeDistances.values[i2], tangent);
output[3].create(center + offsetInfo.position[i3], normal, highUv[i3], lowUv[i3], edgeDistances.values[i3], tangent);
}
template <typename Vertex>
inline void outputFace(Vertex* output, const Vector3int16& center,
const OFFSETINFOV2& offsetInfo, const BlockAxisFace& face, const Vector2int16 highUv[4])
{
const Color4uint8& normal = offsetInfo.normal;
const FaceVertexLookup& fvl = gFaceVertexLookup[face.skippedCorner];
int i0 = fvl.indices[0], i1 = fvl.indices[1], i2 = fvl.indices[2], i3 = fvl.indices[3];
output[0].create(center + offsetInfo.position[i0], normal, highUv[i0 & 3] );
output[1].create(center + offsetInfo.position[i1], normal, highUv[i1 & 3] );
output[2].create(center + offsetInfo.position[i2], normal, highUv[i2 & 3] );
output[3].create(center + offsetInfo.position[i3], normal, highUv[i3 & 3] );
}
template <typename TC> struct RenderLookup
{
Vector3int16 uDot;
Vector3int16 vDot;
const TC* highRes;
const TC* lowRes;
inline Vector2int16 dot(const Vector3int16& cellLocation) const
{
return Vector2int16(
cellLocation.x * uDot.x + cellLocation.y * uDot.y + cellLocation.z * uDot.z,
cellLocation.x * vDot.x + cellLocation.y * vDot.y + cellLocation.z * vDot.z);
}
};
static const RenderLookup<MaterialTextureCoordinates> kRenderLookupByFace[6][2] =
{
{
// +X
{
Vector3int16(-1, 0,-1),
Vector3int16( 0,-1, 0),
&kMaterial_Side_High,
&kMaterial_Side_Low,
},
{
Vector3int16(-1, 0,-1),
Vector3int16( 0,-1, 0),
&kMaterial_TopSide_High,
&kMaterial_TopSide_Low,
},
},
{
// +Z
{
Vector3int16( 1, 0, 1),
Vector3int16( 0,-1, 0),
&kMaterial_Side_High,
&kMaterial_Side_Low,
},
{
Vector3int16( 1, 0, 1),
Vector3int16( 0,-1, 0),
&kMaterial_TopSide_High,
&kMaterial_TopSide_Low,
},
},
{
// -X
{
Vector3int16( 1, 0, 1),
Vector3int16( 0,-1, 0),
&kMaterial_Side_High,
&kMaterial_Side_Low,
},
{
Vector3int16( 1, 0, 1),
Vector3int16( 0,-1, 0),
&kMaterial_TopSide_High,
&kMaterial_TopSide_Low,
},
},
{
// -Z
{
Vector3int16(-1, 0,-1),
Vector3int16( 0,-1, 0),
&kMaterial_Side_High,
&kMaterial_Side_Low,
},
{
Vector3int16(-1, 0,-1),
Vector3int16( 0,-1, 0),
&kMaterial_TopSide_High,
&kMaterial_TopSide_Low,
},
},
{
// +Y
{
Vector3int16( 1, 0, 0),
Vector3int16( 0, 0, 1),
&kMaterial_Top_High,
&kMaterial_Top_Low,
},
{
Vector3int16( 1, 0, 0),
Vector3int16( 0, 0, 1),
&kMaterial_Top_High,
&kMaterial_Top_Low,
},
},
{
// -Y
{
Vector3int16( 1, 0, 0),
Vector3int16( 0, 0,-1),
&kMaterial_Bottom_High,
&kMaterial_Bottom_Low,
},
{
Vector3int16( 1, 0, 0),
Vector3int16( 0, 0,-1),
&kMaterial_Bottom_High,
&kMaterial_Bottom_Low,
},
}
};
static const RenderLookup<MaterialTextureCoordinates> kVerticalWedgeLookupByOrientation[4] =
{
// NegZ
{
Vector3int16( 1, 1, 1),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeVertical_High,
&kMaterial_WedgeVertical_Low,
},
// X
{
Vector3int16( 1, 1,-1),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeVertical_High,
&kMaterial_WedgeVertical_Low,
},
// Z
{
Vector3int16(-1, 1,-1),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeVertical_High,
&kMaterial_WedgeVertical_Low,
},
// NegX
{
Vector3int16(-1, 1, 1),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeVertical_High,
&kMaterial_WedgeVertical_Low,
}
};
static const RenderLookup<MaterialTextureCoordinates> kHorizontalWedgeLookupByOrientation[4] =
{
// NegZ
{
Vector3int16( 0, 0, 1),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeHorizontal_High,
&kMaterial_WedgeHorizontal_Low,
},
// X
{
Vector3int16( 1, 0, 0),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeHorizontal_High,
&kMaterial_WedgeHorizontal_Low,
},
// Z
{
Vector3int16( 0, 0,-1),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeHorizontal_High,
&kMaterial_WedgeHorizontal_Low,
},
// NegX
{
Vector3int16(-1, 0, 0),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeHorizontal_High,
&kMaterial_WedgeHorizontal_Low,
}
};
static const RenderLookup<TriangleMaterialTextureCoordinates> kCornerWedgeLookupByOrientation[4] =
{
// NegZ
{
Vector3int16( 0, 0, 1),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeCorner_High,
&kMaterial_WedgeCorner_Low,
},
// X
{
Vector3int16( 1, 0, 0),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeCorner_High,
&kMaterial_WedgeCorner_Low,
},
// Z
{
Vector3int16( 0, 0,-1),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeCorner_High,
&kMaterial_WedgeCorner_Low,
},
// NegX
{
Vector3int16(-1, 0, 0),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeCorner_High,
&kMaterial_WedgeCorner_Low,
}
};
static const RenderLookup<TriangleMaterialTextureCoordinates> kInverseCornerWedgeLookupByOrientation[4] =
{
// NegZ
{
Vector3int16( 0, 0, 1),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeInverseCorner_High,
&kMaterial_WedgeInverseCorner_Low,
},
// X
{
Vector3int16( 1, 0, 0),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeInverseCorner_High,
&kMaterial_WedgeInverseCorner_Low,
},
// Z
{
Vector3int16( 0, 0,-1),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeInverseCorner_High,
&kMaterial_WedgeInverseCorner_Low,
},
// NegX
{
Vector3int16(-1, 0, 0),
Vector3int16( 0,-1, 0),
&kMaterial_WedgeInverseCorner_High,
&kMaterial_WedgeInverseCorner_Low,
}
};
template<class VoxelStore, typename Vertex>
struct SolidTerrainRenderer
{
Vertex* output;
void renderHelper(
const Voxel::Cell cell, const CellMaterial material,
const Vector3int16& location, bool isTopSide,
const Vector3int16& center, FaceDirection faceDirection, unsigned char outlineMask)
{
const OFFSETINFOV2& offsetInfo = kAxisSideLookup[faceDirection];
const BlockAxisFace& usedFace = GetOrientedFace(cell, faceDirection);
const RenderLookup<MaterialTextureCoordinates>& info = kRenderLookupByFace[faceDirection][isTopSide];
Vector2int16 logicalCell = info.dot(location);
Vector2int16 highUvs[4], lowUvs[4];
info.highRes->calculateTextureCoordinates(logicalCell, AtlasOffsetLookup[material - 1], highUvs);
info.lowRes->calculateTextureCoordinates(logicalCell, AtlasOffsetLookup[material - 1], lowUvs);
outputFace(output, center, offsetInfo, usedFace, highUvs, lowUvs, outlineMask);
output += 4;
}
inline bool faceGenerated(Voxel::Cell& currentCell, Voxel::Cell& neighborCell, FaceDirection direction, unsigned incoming)
{
const BlockAxisFace& centerFace = GetOrientedFace(currentCell, direction);
const BlockAxisFace& neighborFace = GetOrientedFace(neighborCell, kOppositeSideOffset[direction]);
SideStatus centerStatus = getStatusFromFace(centerFace);
SideStatus neighborStatus = getStatusFromFace(neighborFace);
if (centerStatus != SideTriangle)
return centerStatus != neighborStatus;
else
return gTriangleOutlineLookup[centerFace.skippedCorner][incoming];
}
unsigned char detectOutlines(const typename VoxelStore::Region::iterator& iterator, FaceDirection faceDirection, RenderPredStatus status)
{
unsigned char mask = 0xf;
const static FaceDirection adjDirections [6][4] =
{
{ PlusY, MinusY, PlusZ, MinusZ }, // PlusX
{ PlusY, MinusY, MinusX, PlusX }, // PlusZ
{ PlusY, MinusY, MinusZ, PlusZ }, // MinusX
{ PlusY, MinusY, PlusX, MinusX }, // MinusZ
{ MinusZ, PlusZ, MinusX, PlusX }, // PlusY
{ PlusZ, MinusZ, MinusX, PlusX }, // MinusY
};
FaceDirection current = status == RENDER_FORWARD ? Invalid : faceDirection;
FaceDirection next = status == RENDER_FORWARD ? faceDirection : Invalid;
CellMaterial currentMaterial = iterator.getNeighborMaterial(current);
faceDirection = status == RENDER_FORWARD ? faceDirection : kOppositeSideOffset[faceDirection];
unsigned maskBit = 0x1;
for (int i = 0; i < 4; i++)
{
FaceDirection neighborDirection = adjDirections[faceDirection][i];
CellMaterial adjMaterial = iterator.getNeighborMaterial(current, neighborDirection);
if (adjMaterial == currentMaterial)
{
Voxel::Cell neighborCell = iterator.getNeighborCell(current, neighborDirection);
Voxel::Cell nextNeighborCell = iterator.getNeighborCell(next, neighborDirection);
if (faceGenerated(neighborCell, nextNeighborCell, faceDirection, i))
{
mask &= ~maskBit;
}
}
maskBit <<= 1;
}
return mask;
}
FaceDirection rotate(FaceDirection face, CellOrientation orientation)
{
return face >= PlusY ? face : (FaceDirection)((face + 4 - orientation) % 4);
}
void apply(const typename VoxelStore::Region::iterator& iterator, FaceDirection faceDirection, RenderPredStatus status)
{
Vector3int16 center = computeCenter(iterator.getCurrentLocation());
bool impossibleTopSide = faceDirection == PlusY || faceDirection == MinusY;
if (status & RENDER_FORWARD)
{
unsigned char outlineMask = detectOutlines(iterator, faceDirection, RENDER_FORWARD);
bool isTopSide = !impossibleTopSide &&
iterator.getNeighborCell(PlusY).isEmpty();
renderHelper(iterator.getCellAtCurrentLocation(),
iterator.getMaterialAtCurrentLocation(),
iterator.getCurrentLocation(), isTopSide,
center, faceDirection, outlineMask);
}
if (status & RENDER_BACKWARD)
{
unsigned char outlineMask = detectOutlines(iterator, faceDirection, RENDER_BACKWARD);
const Vector3int16& faceDirectionOffset = kFaceDirectionLocationOffset[faceDirection];
bool isTopSide = !impossibleTopSide &&
iterator.getArbitraryNeighborCell(
faceDirectionOffset + kFaceDirectionLocationOffset[PlusY]).isEmpty();
center += faceDirectionOffset * kCELL_SIZE;
renderHelper(
iterator.getNeighborCell(faceDirection),
iterator.getNeighborMaterial(faceDirection),
iterator.getCurrentLocation() + faceDirectionOffset,
isTopSide, center, kOppositeSideOffset[faceDirection], outlineMask);
}
}
unsigned char detectWedgeOutlines(const typename VoxelStore::Region::iterator& iterator)
{
unsigned mask = 0xf;
Voxel::Cell cell = iterator.getCellAtCurrentLocation();
CellMaterial material = iterator.getMaterialAtCurrentLocation();
CellBlock block = cell.solid.getBlock();
CellOrientation orient = cell.solid.getOrientation();
unsigned maskBit = 0x1;
if (block == CELL_BLOCK_VerticalWedge || block == CELL_BLOCK_HorizontalWedge)
{
const static FaceDirection verticalWedgeDirections [4][2] = { { PlusY, MinusZ }, { MinusY, PlusZ }, { MinusX, Invalid }, { PlusX, Invalid } };
const static FaceDirection horizontalWedgeDirections [4][2] = { { PlusY, Invalid }, { MinusY, Invalid }, { MinusX, MinusZ }, { PlusX, PlusZ } };
const FaceDirection (&lookup) [4][2] = block == CELL_BLOCK_VerticalWedge ? verticalWedgeDirections : horizontalWedgeDirections;
unsigned maskBit = 0x1;
for (int i = 0; i < 4; i++)
{
FaceDirection f0 = rotate(lookup[i][0], orient);
FaceDirection f1 = rotate(lookup[i][1], orient);
CellMaterial adjMaterial = iterator.getNeighborMaterial(f0, f1);
Voxel::Cell adjNextCell = iterator.getNeighborCell(f0, f1);
if (adjMaterial == material && adjNextCell.solid.getOrientation() == orient && adjNextCell.solid.getBlock() == block)
mask &= ~maskBit;
maskBit <<= 1;
}
}
else
{
const static FaceDirection cornerWedgeDirections [4] = { PlusX, MinusY, MinusZ, PlusX };
const static FaceDirection inverseCornerWedgeDirections [4] = { PlusY, MinusX, MinusX, PlusZ };
const FaceDirection (&lookup)[4] = block == CELL_BLOCK_CornerWedge ? cornerWedgeDirections : inverseCornerWedgeDirections;
for (int i = 0; i < 4; i++)
{
if (lookup[i] == Invalid)
mask &= ~maskBit;
else
{
FaceDirection f = rotate(lookup[i], orient);
CellMaterial adjMaterial = iterator.getNeighborMaterial(f);
Voxel::Cell adjNextCell = iterator.getNeighborCell(f);
if (adjMaterial == material && adjNextCell.solid.getOrientation() == orient && adjNextCell.solid.getBlock() == 5-block)
mask &= ~maskBit;
}
maskBit <<= 1;
}
}
return mask;
}
void wedgeFace(const typename VoxelStore::Region::iterator& iterator)
{
Voxel::Cell cell = iterator.getCellAtCurrentLocation();
CellMaterial material = iterator.getMaterialAtCurrentLocation();
CellBlock block = cell.solid.getBlock();
CellOrientation orient = cell.solid.getOrientation();
Vector3int16 center = computeCenter(iterator.getCurrentLocation());
unsigned outlineMask = detectWedgeOutlines(iterator);
const Wedge& wedge = kWedges[block];
const Color4uint8& normal = wedge.normals[orient];
const Color4uint8& tangent = wedge.tangents[orient];
unsigned char index0 = rotateCornerIndex(wedge.geom.p0, orient);
unsigned char index1 = rotateCornerIndex(wedge.geom.p1, orient);
unsigned char index2 = rotateCornerIndex(wedge.geom.p2, orient);
unsigned char index3 = rotateCornerIndex(wedge.geom.p3, orient);
OFFSETINFOV2 offsetInfo =
{
CornerLookup[index0].posoffset,
CornerLookup[index1].posoffset,
CornerLookup[index2].posoffset,
CornerLookup[index3].posoffset,
normal,
tangent
};
if (block == CELL_BLOCK_CornerWedge || block == CELL_BLOCK_InverseCornerWedge)
{
BlockAxisFace usedFace;
const RenderLookup<TriangleMaterialTextureCoordinates>* info;
if (block == CELL_BLOCK_CornerWedge)
{
usedFace.skippedCorner = BlockAxisFace::TopRight;
info = &kCornerWedgeLookupByOrientation[orient];
}
else
{
usedFace.skippedCorner = BlockAxisFace::BottomLeft;
info = &kInverseCornerWedgeLookupByOrientation[orient];
}
Vector2int16 logicalCell = info->dot(iterator.getCurrentLocation());
Vector2int16 highUvs[4], lowUvs[4];
info->highRes->calculateTextureCoordinates(logicalCell, AtlasOffsetLookup[material - 1], highUvs);
info->lowRes->calculateTextureCoordinates(logicalCell, AtlasOffsetLookup[material - 1], lowUvs);
outputFace(output, center, offsetInfo, usedFace, highUvs, lowUvs, outlineMask);
output += 4;
}
else
{
BlockAxisFace usedFace;
usedFace.skippedCorner = BlockAxisFace::FullNoneSkipped;
const RenderLookup<MaterialTextureCoordinates>* info;
if (block == CELL_BLOCK_VerticalWedge)
{
info = &kVerticalWedgeLookupByOrientation[orient];
}
else
{
info = &kHorizontalWedgeLookupByOrientation[orient];
}
Vector2int16 logicalCell = info->dot(iterator.getCurrentLocation());
Vector2int16 highUvs[4], lowUvs[4];
info->highRes->calculateTextureCoordinates(logicalCell, AtlasOffsetLookup[material - 1], highUvs);
info->lowRes->calculateTextureCoordinates(logicalCell, AtlasOffsetLookup[material - 1], lowUvs);
outputFace(output, center, offsetInfo, usedFace, highUvs, lowUvs, outlineMask);
output += 4;
}
}
};
static unsigned int getWaterTextureRotation(WaterCellDirection flowDirection, FaceDirection faceDirection)
{
static unsigned int TRANSLATION[6][6] = // indexed by [flowDirection][faceDirection]
{
{ 2, 1, 0, 3, 1, 1 }, // NegX
{ 0, 3, 2, 1, 3, 3 }, // X
{ 0, 0, 0, 0, 0, 2 }, // NegY
{ 2, 2, 2, 2, 2, 0 }, // Y
{ 3, 2, 1, 0, 2, 0 }, // NegZ
{ 1, 0, 3, 2, 0, 2 } // Z
};
return TRANSLATION[flowDirection][faceDirection];
};
template<class VoxelStore, typename Vertex>
struct WaterFaceRenderer
{
Vertex* output;
void apply(const typename VoxelStore::Region::iterator& iterator, FaceDirection faceDirection, RenderPredStatus status)
{
Voxel::Cell cell = iterator.getCellAtCurrentLocation();
Voxel::Cell waterCell = cell;
Vector3int16 location(iterator.getCurrentLocation());
Vector3int16 center = computeCenter(location);
if (status == RENDER_BACKWARD)
{
cell = iterator.getNeighborCell(faceDirection);
waterCell = cell;
location += kFaceDirectionLocationOffset[faceDirection];
center += kFaceDirectionLocationOffset[faceDirection] * kCELL_SIZE;
faceDirection = kOppositeSideOffset[faceDirection];
}
static const Vector2int16 standardUvs[4] =
{
Vector2int16(0,0),
Vector2int16(kAtlasPixelDimension,0),
Vector2int16(kAtlasPixelDimension,kAtlasPixelDimension),
Vector2int16(0,kAtlasPixelDimension)
};
const OFFSETINFOV2& adjustedOffsetInfo = kAxisSideLookup[faceDirection];
BlockAxisFace usedFace = BlockAxisFace::inverse(GetOrientedFace(cell, faceDirection));
outputFace(output, center, adjustedOffsetInfo, usedFace, standardUvs);
output += 4;
}
inline void wedgeFace(const typename VoxelStore::Region::iterator& iterator) const
{
}
};
template<class Predicate, class ActingDelegate, class VoxelStore>
struct EdgeSpewV2
{
const unsigned int maxApplies;
Predicate predicate;
ActingDelegate actingDelegate;
const VoxelStore* store;
EdgeSpewV2(const unsigned int maxApplies)
: maxApplies(maxApplies)
{
}
void handleCells(const SpatialRegion::Id& chunkPos)
{
Region3int16 extents = SpatialRegion::inclusiveVoxelExtentsOfRegion(chunkPos);
Vector3int16 minCell = extents.getMinPos();
Vector3int16 maxCell = extents.getMaxPos();
typename VoxelStore::Region region = store->getRegion(minCell, maxCell);
typename VoxelStore::Region::iterator iterator = region.begin();
if (region.isGuaranteedAllEmpty())
{
return;
}
const unsigned int maxFaces = maxApplies;
const unsigned int safeDistance = 7; // max quads to be output per cell
unsigned int faceCount = 0;
bool reachedMax = (faceCount + safeDistance) >= maxFaces;
while (iterator != region.end() && !reachedMax)
{
// Here and below, face count is incremented by
// (RenderPredStatus + 1) >> 1
// This is assuming the following values for RenderPredStatus:
// 0 => No render (0 faces)
// 1 => Forward (1 faces)
// 2 => Back (1 faces)
// 3 => Both (2 faces)
if (RenderPredStatus s = predicate.internal(iterator, PlusX))
{
actingDelegate.apply(iterator, PlusX, s);
faceCount += (s + 1) >> 1;
}
if (RenderPredStatus s = predicate.internal(iterator, PlusZ))
{
actingDelegate.apply(iterator, PlusZ, s);
faceCount += (s + 1) >> 1;
}
if (RenderPredStatus s = predicate.internal(iterator, PlusY))
{
actingDelegate.apply(iterator, PlusY, s);
faceCount += (s + 1) >> 1;
}
if (predicate.wedgeFace(iterator.getCellAtCurrentLocation()))
{
actingDelegate.wedgeFace(iterator);
faceCount++;
}
reachedMax = faceCount + safeDistance >= maxFaces;
++iterator;
}
}
};
template<class Pred, class VoxelStore>
static unsigned int countQuads(const VoxelStore* store, const SpatialRegion::Id& chunkPos, int facesInIB)
{
EdgeSpewV2<Pred, FaceCounter<VoxelStore>, VoxelStore> edgeSpew(facesInIB);
edgeSpew.store = store;
edgeSpew.handleCells(chunkPos);
return edgeSpew.actingDelegate.count;
}
const Vector3int16 MegaCluster::kMinCellOffset(-2,-1,-2);
const std::string MegaCluster::kTerrainTexClose = "rbxasset://textures/terrain/diffuse";
const std::string MegaCluster::kTerrainTexFar = "rbxasset://textures/terrain/diffusefar";
const std::string MegaCluster::kTerrainTexNormals = "rbxasset://textures/terrain/normal";
const std::string MegaCluster::kTerrainTexSpecular = "rbxasset://textures/terrain/specular";
MegaCluster::MegaCluster(VisualEngine* visualEngine, const boost::shared_ptr<PartInstance>& part)
: visualEngine(visualEngine)
, useShaders(false)
, ignoreWaterUpdatesForTesting(false)
, storage(NULL)
{
RBXASSERT(part->getPartType() == MEGACLUSTER_PART);
partInstance = part;
RBXASSERT(partInstance->getGfxPart() == NULL);
partInstance->setGfxPart(this);
MegaClusterInstance* mci = boost::polymorphic_downcast<MegaClusterInstance*>(part.get());
storage = mci->getVoxelGrid();
FASTLOG2(FLog::TerrainCellListener, "MegaCluster: connecting %p to storage %p", this, storage);
storage->connectListener(this);
connections.push_back(part->ancestryChangedSignal.connect(boost::bind(&MegaCluster::zombify, this)));
// Detect whether we will use shaders
useShaders = visualEngine->getDevice()->getCaps().supportsShaders;
initLookupTables();
}
MegaCluster::~MegaCluster()
{
unbind();
// notify scene updater about destruction so that the pointer to cluster is no longer stored
visualEngine->getSceneUpdater()->notifyDestroyed(this);
}
void MegaCluster::setIgnoreWaterUpdatesForTesting(bool val)
{
ignoreWaterUpdatesForTesting = val;
}
void MegaCluster::updateEntity(bool assetsUpdated)
{
if (!storage)
{
delete this;
return;
}
if (!storage->isAllocated())
return;
std::vector<SpatialRegion::Id> chunks = storage->getNonEmptyChunks();
// We need to generate a chunk in -X/-Y/-Z direction for every original chunk
// to make sure all faces are present
typedef boost::unordered_set<SpatialRegion::Id, SpatialRegion::Id::boost_compatible_hash_value> ChunkSet;
ChunkSet allChunks;
for (size_t i = 0; i < chunks.size(); ++i)
{
SpatialRegion::Id id = chunks[i];
allChunks.insert(id);
allChunks.insert(id + Vector3int16(-1, 0, 0));
allChunks.insert(id + Vector3int16(0, -1, 0));
allChunks.insert(id + Vector3int16(0, 0, -1));
}
for (ChunkSet::const_iterator it = allChunks.begin(); it != allChunks.end(); ++it)
updateChunkGeometry(*it, true, true);
}
void MegaCluster::unbind()
{
GfxPart::unbind();
if (storage)
{
FASTLOG2(FLog::TerrainCellListener, "MegaCluster: disconnecting %p from storage %p", this, storage);
storage->disconnectListener(this);
storage = NULL;
}
}
void MegaCluster::invalidateEntity()
{
visualEngine->getSceneUpdater()->queueFullInvalidateMegaCluster(this);
}
void MegaCluster::terrainCellChanged(const CellChangeInfo& info)
{
bool waterChanged = !ignoreWaterUpdatesForTesting;
CellBlock beforeBlock = info.beforeCell.solid.getBlock();
CellBlock afterBlock = info.afterCell.solid.getBlock();
bool terrainChanged = beforeBlock != CELL_BLOCK_Empty || afterBlock != CELL_BLOCK_Empty;
Vector3int16 pos(info.position);
// mark edit chunk dirty
SpatialRegion::Id chunk = SpatialRegion::regionContainingVoxel(pos);
markDirty(chunk, terrainChanged, waterChanged);
if (SpatialRegion::regionContainingVoxel(pos - Vector3int16(1, 1, 1)) != SpatialRegion::regionContainingVoxel(pos + Vector3int16(1, 1, 1)))
{
// edit cell is at the chunk boundary, need to update neighbors
static const Vector3int16 kSmallXOffset(-1, 0, 0);
static const Vector3int16 kSmallYOffset(0, -1, 0);
static const Vector3int16 kSmallZOffset(0, 0, -1);
static const Vector3int16 kBigXOffset(1, 0, 0);
static const Vector3int16 kBigYOffset(0, 1, 0);
static const Vector3int16 kBigZOffset(0, 0, 1);
Vector3int16 neighborTerrainChunks[3];
unsigned int usedNeighborTerrainChunks = 0;
if (chunk != SpatialRegion::regionContainingVoxel(pos + kSmallXOffset)) {
neighborTerrainChunks[usedNeighborTerrainChunks++] = kSmallXOffset;
}
if (chunk != SpatialRegion::regionContainingVoxel(pos + kBigXOffset)) {
neighborTerrainChunks[usedNeighborTerrainChunks++] = kBigXOffset;
}
if (chunk != SpatialRegion::regionContainingVoxel(pos + kSmallYOffset)) {
neighborTerrainChunks[usedNeighborTerrainChunks++] = kSmallYOffset;
}
if (chunk != SpatialRegion::regionContainingVoxel(pos + kBigYOffset)) {
neighborTerrainChunks[usedNeighborTerrainChunks++] = kBigYOffset;
}
if (chunk != SpatialRegion::regionContainingVoxel(pos + kSmallZOffset)) {
neighborTerrainChunks[usedNeighborTerrainChunks++] = kSmallZOffset;
}
if (chunk != SpatialRegion::regionContainingVoxel(pos + kBigZOffset)) {
neighborTerrainChunks[usedNeighborTerrainChunks++] = kBigZOffset;
}
RBXASSERT(usedNeighborTerrainChunks <= 3);
// mark neighbor chunks dirty
for (unsigned int firstDim = 0; firstDim < usedNeighborTerrainChunks; ++firstDim) {
markDirty(chunk + neighborTerrainChunks[firstDim], terrainChanged, waterChanged);
for (unsigned int secondDim = firstDim + 1; secondDim < usedNeighborTerrainChunks; ++secondDim) {
markDirty(chunk + neighborTerrainChunks[firstDim] + neighborTerrainChunks[secondDim], terrainChanged, waterChanged);
for (unsigned int thirdDim = secondDim + 1; thirdDim < usedNeighborTerrainChunks; ++thirdDim) {
markDirty(chunk + neighborTerrainChunks[firstDim] + neighborTerrainChunks[secondDim] + neighborTerrainChunks[thirdDim], terrainChanged, waterChanged);
}
}
}
}
}
void MegaCluster::updateChunk(const SpatialRegion::Id& pos, bool isWaterChunk)
{
updateChunkGeometry(pos, !isWaterChunk, isWaterChunk);
}
void MegaCluster::markDirty(const SpatialRegion::Id& pos, bool solidDirty, bool waterDirty)
{
ChunkData& chunk = chunks.insert(pos);
FASTLOG5(FLog::MegaClusterDirty, "MegaCluster: Marking chunk %dx%dx%d dirty (solid %d, water %d)", pos.value().x, pos.value().y, pos.value().z, solidDirty, waterDirty);
SceneUpdater* sceneUpdater = visualEngine->getSceneUpdater();
if (waterDirty && !chunk.waterDirty) {
chunk.waterDirty = true;
sceneUpdater->queueChunkInvalidateMegaCluster(this, pos, true);
}
if (solidDirty && !chunk.solidDirty) {
chunk.solidDirty = true;
sceneUpdater->queueChunkInvalidateMegaCluster(this, pos, false);
}
}
void MegaCluster::updateChunkGeometry(const SpatialRegion::Id& pos, bool solidUpdate, bool waterUpdate)
{
ChunkData& chunk = chunks.insert(pos);
// Prepare render area: copy chunk with fringe from storage
const Region3int16 extents = SpatialRegion::inclusiveVoxelExtentsOfRegion(pos);
renderArea.loadData(storage, extents.getMinPos() + MegaCluster::kMinCellOffset);
// Update solid entity
if (solidUpdate)
{
if (chunk.solidEntity)
{
chunk.node->removeEntity(chunk.solidEntity);
delete chunk.solidEntity;
}
chunk.solidEntity = createSolidGeometry(pos, &chunk.solidQuads);
chunk.solidDirty = false;
if (chunk.solidEntity)
chunk.node->addEntity(chunk.solidEntity);
}
// Update water entity
if (waterUpdate)
{
if (chunk.waterEntity)
{
chunk.node->removeEntity(chunk.waterEntity);
delete chunk.waterEntity;
}
chunk.waterEntity = createWaterGeometry(pos, &chunk.waterQuads);
chunk.waterDirty = false;
if (chunk.waterEntity)
chunk.node->addEntity(chunk.waterEntity);
}
// Update quad stats or destroy node if necessary
if (!chunk.solidEntity && !chunk.waterEntity)
{
chunk = ChunkData();
}
else
{
chunk.node->setBlockCount((chunk.solidQuads + chunk.waterQuads) / ChunkData::kApproximateQuadsPerPart);
}
}
void MegaCluster::generateAndReturnWaterGeometry(Voxel::Grid* voxelGrid, const SpatialRegion::Id& pos, std::vector<TerrainVertexFFP>* verticesOut)
{
static const unsigned facesInIB = kMaxIndexBufferSize32Bit / 6;
// Prepare render area: copy chunk with fringe from storage
const Region3int16 extents = SpatialRegion::inclusiveVoxelExtentsOfRegion(pos);
RenderArea renderArea;
renderArea.loadData(voxelGrid, extents.getMinPos() + MegaCluster::kMinCellOffset);
unsigned int quads = countQuads<WaterRenderPredicate<RenderArea>, RenderArea>(&renderArea, pos, facesInIB);
if (quads == 0) return;
verticesOut->resize(quads*4);
EdgeSpewV2<WaterRenderPredicate<RenderArea>, WaterFaceRenderer<RenderArea, TerrainVertexFFP>, RenderArea> spew(facesInIB);
spew.actingDelegate.output = &(*verticesOut)[0];
spew.store = &renderArea;
spew.handleCells(pos);
}
void MegaCluster::generateAndReturnSolidGeometry(Voxel::Grid* voxelGrid, const SpatialRegion::Id& pos, std::vector<TerrainVertexFFP>* verticesOut)
{
static const unsigned facesInIB = kMaxIndexBufferSize32Bit / 6;
// Prepare render area: copy chunk with fringe from storage
const Region3int16 extents = SpatialRegion::inclusiveVoxelExtentsOfRegion(pos);
RenderArea renderArea;
renderArea.loadData(voxelGrid, extents.getMinPos() + MegaCluster::kMinCellOffset);
unsigned int quads = countQuads<SolidTerrainRenderPredicate<RenderArea>, RenderArea>(&renderArea, pos, facesInIB);
if (quads == 0) return;
verticesOut->resize(quads*4);
EdgeSpewV2<SolidTerrainRenderPredicate<RenderArea>, SolidTerrainRenderer<RenderArea, TerrainVertexFFP>, RenderArea> spew(facesInIB);
spew.actingDelegate.output = &(*verticesOut)[0];
spew.store = &renderArea;
spew.handleCells(pos);
}
RenderNode* MegaCluster::updateChunkNode(const SpatialRegion::Id& pos)
{
ChunkData& chunk = chunks.insert(pos);
// Update chunk node
if (!chunk.node)
{
// Create chunk node
chunk.node.reset(new RenderNode(visualEngine, RenderNode::CullMode_SpatialHash));
Vector3int32 minLocation = SpatialRegion::smallestCornerOfRegionInGlobalCoordStuds(pos);
Vector3int32 maxLocation = SpatialRegion::largestCornerOfRegionInGlobalCoordStuds(pos);
chunk.node->setCoordinateFrame(CoordinateFrame(minLocation.toVector3()));
chunk.node->updateWorldBounds(Extents(minLocation.toVector3(), maxLocation.toVector3()));
}
return chunk.node.get();
}
RenderEntity* MegaCluster::createSolidGeometry(const SpatialRegion::Id& pos, unsigned int* outQuads)
{
const shared_ptr<IndexBuffer>& ibuf = getSharedIB();
unsigned int facesInIB = ibuf->getElementCount() / 6;
unsigned int quads = countQuads<SolidTerrainRenderPredicate<RenderArea>, RenderArea>(&renderArea, pos, facesInIB);
*outQuads = quads;
if (quads == 0) return NULL;
shared_ptr<VertexBuffer> vbuf;
if (useShaders)
{
vbuf = visualEngine->getDevice()->createVertexBuffer(sizeof(TerrainVertex), quads*4, GeometryBuffer::Usage_Static);
TerrainVertex* vbptr = static_cast<TerrainVertex*>(vbuf->lock());
EdgeSpewV2<SolidTerrainRenderPredicate<RenderArea>, SolidTerrainRenderer<RenderArea, TerrainVertex>, RenderArea> spew(facesInIB);
spew.actingDelegate.output = vbptr;
spew.store = &renderArea;
spew.handleCells(pos);
vbuf->unlock();
}
else
{
vbuf = visualEngine->getDevice()->createVertexBuffer(sizeof(TerrainVertexFFP), quads*4, GeometryBuffer::Usage_Static);
TerrainVertexFFP* vbptr = static_cast<TerrainVertexFFP*>(vbuf->lock());
EdgeSpewV2<SolidTerrainRenderPredicate<RenderArea>, SolidTerrainRenderer<RenderArea, TerrainVertexFFP>, RenderArea> spew(facesInIB);
spew.actingDelegate.output = vbptr;
spew.store = &renderArea;
spew.handleCells(pos);
vbuf->unlock();
}
return createGeometry(updateChunkNode(pos), vbuf, ibuf, getSolidMaterial(), RenderQueue::Id_Opaque, false);
}
RenderEntity* MegaCluster::createWaterGeometry(const SpatialRegion::Id& pos, unsigned int* outQuads)
{
const shared_ptr<IndexBuffer>& ibuf = getSharedIB();
unsigned int facesInIB = ibuf->getElementCount() / 6;
unsigned int quads = countQuads<WaterRenderPredicate<RenderArea>, RenderArea>(&renderArea, pos, facesInIB);
*outQuads = quads;
if (quads == 0) return NULL;
shared_ptr<VertexBuffer> vbuf;
if (useShaders)
{
vbuf = visualEngine->getDevice()->createVertexBuffer(sizeof(WaterVertex), quads*4, GeometryBuffer::Usage_Static);
WaterVertex* vbptr = static_cast<WaterVertex*>(vbuf->lock());
EdgeSpewV2<WaterRenderPredicate<RenderArea>, WaterFaceRenderer<RenderArea, WaterVertex>, RenderArea> edgeSpew(facesInIB);
edgeSpew.actingDelegate.output = vbptr;
edgeSpew.store = &renderArea;
edgeSpew.handleCells(pos);
vbuf->unlock();
}
else
{
vbuf = visualEngine->getDevice()->createVertexBuffer(sizeof(TerrainVertexFFP), quads*4, GeometryBuffer::Usage_Static);
TerrainVertexFFP* vbptr = static_cast<TerrainVertexFFP*>(vbuf->lock());
EdgeSpewV2<WaterRenderPredicate<RenderArea>, WaterFaceRenderer<RenderArea, TerrainVertexFFP>, RenderArea> edgeSpew(facesInIB);
edgeSpew.actingDelegate.output = vbptr;
edgeSpew.store = &renderArea;
edgeSpew.handleCells(pos);
vbuf->unlock();
}
shared_ptr<Material> material = visualEngine->getWater()->getLegacyMaterial();
return createGeometry(updateChunkNode(pos), vbuf, ibuf, material, RenderQueue::Id_TransparentUnsorted, true);
}
RenderEntity* MegaCluster::createGeometry(RenderNode* node, const shared_ptr<VertexBuffer>& vbuf, const shared_ptr<IndexBuffer>& ibuf, const shared_ptr<Material>& material, RenderQueue::Id renderQueueId, bool isWater)
{
unsigned int quadCount = vbuf->getElementCount() / 4;
shared_ptr<Geometry> geometry = visualEngine->getDevice()->createGeometry(getVertexLayout(isWater), vbuf, ibuf);
return new RenderEntity(node, GeometryBatch(geometry, Geometry::Primitive_Triangles, quadCount * 6, quadCount * 4), material, renderQueueId);
}
const shared_ptr<VertexLayout>& MegaCluster::getVertexLayout(bool isWater)
{
shared_ptr<VertexLayout>& vertexLayout = isWater ? vertexLayouts[1] : vertexLayouts[0];
if (!vertexLayout)
{
std::vector<VertexLayout::Element> elements;
if (useShaders)
{
if (isWater)
{
elements.push_back(VertexLayout::Element(0, offsetof(WaterVertex, pos), VertexLayout::Format_UByte4, VertexLayout::Semantic_Position));
elements.push_back(VertexLayout::Element(0, offsetof(WaterVertex, normal), VertexLayout::Format_UByte4, VertexLayout::Semantic_Normal));
}
else
{
elements.push_back(VertexLayout::Element(0, offsetof(TerrainVertex, pos), VertexLayout::Format_UByte4, VertexLayout::Semantic_Position));
elements.push_back(VertexLayout::Element(0, offsetof(TerrainVertex, normal), VertexLayout::Format_UByte4, VertexLayout::Semantic_Normal));
elements.push_back(VertexLayout::Element(0, offsetof(TerrainVertex, uv), VertexLayout::Format_Short4, VertexLayout::Semantic_Texture, 0));
elements.push_back(VertexLayout::Element(0, offsetof(TerrainVertex, edgeDistances), VertexLayout::Format_UByte4, VertexLayout::Semantic_Texture, 1));
elements.push_back(VertexLayout::Element(0, offsetof(TerrainVertex, tangent), VertexLayout::Format_UByte4, VertexLayout::Semantic_Texture, 2));
}
}
else
{
elements.push_back(VertexLayout::Element(0, offsetof(TerrainVertexFFP, pos), VertexLayout::Format_Float3, VertexLayout::Semantic_Position));
elements.push_back(VertexLayout::Element(0, offsetof(TerrainVertexFFP, normal), VertexLayout::Format_Float3, VertexLayout::Semantic_Normal));
elements.push_back(VertexLayout::Element(0, offsetof(TerrainVertexFFP, uv), VertexLayout::Format_Float2, VertexLayout::Semantic_Texture, 0));
}
vertexLayout = visualEngine->getDevice()->createVertexLayout(elements);
RBXASSERT(vertexLayout);
}
return vertexLayout;
}
template <typename T> static shared_ptr<IndexBuffer> generateQuadIB(Device* device, unsigned int quads)
{
shared_ptr<IndexBuffer> ibuf = device->createIndexBuffer(sizeof(T), quads * 6, GeometryBuffer::Usage_Static);
T* indices = static_cast<T*>(ibuf->lock());
for (unsigned int i = 0; i < quads; i++)
{
// assuming CCW
// 1 - 0
// | \ |
// 2 - 3
*(indices++) = i*4 + 0;
*(indices++) = i*4 + 1;
*(indices++) = i*4 + 3;
*(indices++) = i*4 + 1;
*(indices++) = i*4 + 2;
*(indices++) = i*4 + 3;
}
ibuf->unlock();
return ibuf;
}
const shared_ptr<IndexBuffer>& MegaCluster::getSharedIB()
{
if (sharedIB)
return sharedIB;
if (visualEngine->getDevice()->getCaps().supportsIndex32)
{
sharedIB = generateQuadIB<unsigned int>(visualEngine->getDevice(), kMaxIndexBufferSize32Bit);
}
else
{
sharedIB = generateQuadIB<unsigned short>(visualEngine->getDevice(), kMaxIndexBufferSize16Bit);
}
return sharedIB;
}
#ifdef RBX_PLATFORM_IOS
static const std::string kTextureExtension = ".pvr";
#elif defined(__ANDROID__)
static const std::string kTextureExtension = ".pvr";
#else
static const std::string kTextureExtension = ".dds";
#endif
static void setupTextures(VisualEngine* visualEngine, Technique& technique)
{
TextureManager* tm = visualEngine->getTextureManager();
LightGrid* lightGrid = visualEngine->getLightGrid();
SceneManager* sceneManager = visualEngine->getSceneManager();
technique.setTexture(0, tm->load(ContentId(MegaCluster::kTerrainTexClose + kTextureExtension), TextureManager::Fallback_White), SamplerState::Filter_Linear);
technique.setTexture(1, tm->load(ContentId(MegaCluster::kTerrainTexFar + kTextureExtension), TextureManager::Fallback_White), SamplerState::Filter_Linear);
technique.setTexture(2, tm->load(ContentId(MegaCluster::kTerrainTexNormals + kTextureExtension), TextureManager::Fallback_NormalMap), SamplerState::Filter_Linear);
technique.setTexture(3, tm->load(ContentId(MegaCluster::kTerrainTexSpecular + kTextureExtension), TextureManager::Fallback_Black), SamplerState::Filter_Linear);
if (lightGrid)
{
technique.setTexture(4, lightGrid->getTexture(), SamplerState::Filter_Linear);
technique.setTexture(5, lightGrid->getLookupTexture(), SamplerState(SamplerState::Filter_Point, SamplerState::Address_Clamp));
}
technique.setTexture(6, sceneManager->getShadowMap(), SamplerState(SamplerState::Filter_Linear, SamplerState::Address_Clamp));
}
const shared_ptr<Material>& MegaCluster::getSolidMaterial()
{
if (solidMaterial)
return solidMaterial;
solidMaterial = shared_ptr<Material>(new Material());
if (shared_ptr<ShaderProgram> program = visualEngine->getShaderManager()->getProgram("MegaClusterHQVS", "MegaClusterHQGBufferFS"))
{
Technique technique(program, 0);
setupTextures(visualEngine, technique);
solidMaterial->addTechnique(technique);
}
if (shared_ptr<ShaderProgram> program = visualEngine->getShaderManager()->getProgram("MegaClusterHQVS", "MegaClusterHQFS"))
{
Technique technique(program, 1);
setupTextures(visualEngine, technique);
solidMaterial->addTechnique(technique);
}
if (shared_ptr<ShaderProgram> program = visualEngine->getShaderManager()->getProgramOrFFP("MegaClusterVS", "MegaClusterFS"))
{
Technique technique(program, 2);
setupTextures(visualEngine, technique);
solidMaterial->addTechnique(technique);
}
return solidMaterial;
}
}
}