mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-07 13:57:48 +00:00
GEEKING
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "Voxel/Cell.h"
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
static Cell constructEmptyRepresentation() {
|
||||
Cell v;
|
||||
v.solid.setOrientation(CELL_ORIENTATION_NegZ);
|
||||
v.solid.setBlock(CELL_BLOCK_Empty);
|
||||
return v;
|
||||
}
|
||||
static Cell constructWaterOnWedge() {
|
||||
Cell v;
|
||||
v.solid.setBlock(CELL_BLOCK_Empty);
|
||||
v.water.setForceAndDirection(WATER_CELL_FORCE_None, WATER_CELL_DIRECTION_NegY);
|
||||
return v;
|
||||
}
|
||||
|
||||
const Cell Constants::kUniqueEmptyCellRepresentation = constructEmptyRepresentation();
|
||||
const Cell Constants::kWaterOnWedgeCell = constructWaterOnWedge();
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const RBX::Voxel::Cell& v) {
|
||||
return os << (Cell::asUnsignedCharForDeprecatedUses(v));
|
||||
}
|
||||
|
||||
} }
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "Voxel/Grid.h"
|
||||
|
||||
#include "Voxel/Cell.h"
|
||||
|
||||
#include "rbx/Profiler.h"
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
const int Grid::Chunk::kFaceDirectionToPointerOffset[7] = {
|
||||
kXOffsetMultiplier,
|
||||
kZOffsetMultiplier,
|
||||
-kXOffsetMultiplier,
|
||||
-kZOffsetMultiplier,
|
||||
kYOffsetMultiplier,
|
||||
-kYOffsetMultiplier,
|
||||
0
|
||||
};
|
||||
|
||||
|
||||
Grid::Chunk::Chunk() :
|
||||
data(),
|
||||
material(),
|
||||
countOfNonEmptyCells(0),
|
||||
initialized(false)
|
||||
{
|
||||
}
|
||||
|
||||
Grid::Chunk::~Chunk()
|
||||
{
|
||||
RBXPROFILER_COUNTER_SUB("memory/terrain/legacy", data.size() + material.size());
|
||||
}
|
||||
|
||||
void Grid::Chunk::init(const Grid* owner) {
|
||||
using namespace SpatialRegion::Constants;
|
||||
|
||||
if (!initialized) {
|
||||
this->owner = owner;
|
||||
|
||||
static const int kTotalMemorySize =
|
||||
kRegionXDimensionInVoxels *
|
||||
kRegionYDimensionInVoxels *
|
||||
kRegionZDimensionInVoxels;
|
||||
|
||||
std::vector<Cell> tmp1(kTotalMemorySize, Constants::kUniqueEmptyCellRepresentation);
|
||||
data.swap(tmp1);
|
||||
std::vector<unsigned char> tmp2(kTotalMemorySize / 2, 0xff);
|
||||
material.swap(tmp2);
|
||||
initialized = true;
|
||||
|
||||
RBXPROFILER_COUNTER_ADD("memory/terrain/legacy", data.size() + material.size());
|
||||
}
|
||||
}
|
||||
|
||||
} }
|
||||
@@ -0,0 +1,255 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "Voxel/Grid.h"
|
||||
|
||||
#include "rbx/Debug.h"
|
||||
#include "Util/G3DCore.h"
|
||||
#include "Voxel/Cell.h"
|
||||
#include "Voxel/Util.h"
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
Grid::Grid() : chunkMap(), countOfNonEmptyCells(0) {}
|
||||
|
||||
const Cell& Grid::getVoxelLikelyThisChunk(const SpatialRegion::Id& id,
|
||||
const Chunk& chunk, const Vector3int16& coord) const {
|
||||
const Chunk* chunkPtr;
|
||||
SpatialRegion::Id coordChunkId = SpatialRegion::regionContainingVoxel(coord);
|
||||
if (coordChunkId == id) {
|
||||
chunkPtr = &chunk;
|
||||
} else {
|
||||
chunkPtr = chunkMap.find(coordChunkId);
|
||||
if (chunkPtr == NULL) {
|
||||
return Constants::kUniqueEmptyCellRepresentation;
|
||||
}
|
||||
}
|
||||
return chunkPtr->getConstData()[Chunk::voxelCoordToArrayIndex(coord)];
|
||||
}
|
||||
|
||||
void Grid::fillLocalAreaInfo(const Vector3int16& globalCoord,
|
||||
const Water::RelevantNeighbors& relevantNeighbors,
|
||||
Water::LocalAreaInfo* info) const {
|
||||
|
||||
|
||||
SpatialRegion::Id mainChunkId = SpatialRegion::regionContainingVoxel(globalCoord);
|
||||
const Chunk* mainChunkPtr = chunkMap.find(mainChunkId);
|
||||
if (mainChunkPtr == NULL) {
|
||||
RBXASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const Chunk& mainChunk = *mainChunkPtr;
|
||||
const Vector3int16 relativeCoord = SpatialRegion::voxelCoordinateRelativeToEnclosingRegion(globalCoord);
|
||||
|
||||
if (relativeCoord.isBetweenInclusive(Vector3int16::one(),
|
||||
SpatialRegion::getMaxVoxelOffsetInsideRegion() - Vector3int16::one())) {
|
||||
unsigned int index = Chunk::voxelCoordToArrayIndex(globalCoord);
|
||||
info->aboveNeighbor =
|
||||
mainChunk.getConstData()[index +
|
||||
Chunk::voxelCoordOffsetToIndexOffset(relevantNeighbors.aboveNeighbor)];
|
||||
info->primaryNeighbor =
|
||||
mainChunk.getConstData()[index +
|
||||
Chunk::voxelCoordOffsetToIndexOffset(relevantNeighbors.primaryNeighbor)];
|
||||
info->secondaryNeighbor =
|
||||
mainChunk.getConstData()[index +
|
||||
Chunk::voxelCoordOffsetToIndexOffset(relevantNeighbors.secondaryNeighbor)];
|
||||
info->diagonalNeighbor =
|
||||
mainChunk.getConstData()[index +
|
||||
Chunk::voxelCoordOffsetToIndexOffset(relevantNeighbors.diagonalNeighbor)];
|
||||
info->diagonalUpNeighbor =
|
||||
mainChunk.getConstData()[index +
|
||||
Chunk::voxelCoordOffsetToIndexOffset(relevantNeighbors.diagonalUpNeighbor)];
|
||||
} else {
|
||||
|
||||
info->aboveNeighbor = getVoxelLikelyThisChunk(mainChunkId, mainChunk,
|
||||
globalCoord + relevantNeighbors.aboveNeighbor);
|
||||
info->primaryNeighbor = getVoxelLikelyThisChunk(mainChunkId, mainChunk,
|
||||
globalCoord + relevantNeighbors.primaryNeighbor);
|
||||
info->secondaryNeighbor = getVoxelLikelyThisChunk(mainChunkId, mainChunk,
|
||||
globalCoord + relevantNeighbors.secondaryNeighbor);
|
||||
info->diagonalNeighbor = getVoxelLikelyThisChunk(mainChunkId, mainChunk,
|
||||
globalCoord + relevantNeighbors.diagonalNeighbor);
|
||||
info->diagonalUpNeighbor = getVoxelLikelyThisChunk(mainChunkId, mainChunk,
|
||||
globalCoord + relevantNeighbors.diagonalUpNeighbor);
|
||||
}
|
||||
}
|
||||
|
||||
void Grid::setCell(const Vector3int16& location, Cell newCell, CellMaterial inputMaterial)
|
||||
{
|
||||
if (!Voxel::getTerrainExtentsInCells().contains(location))
|
||||
return;
|
||||
|
||||
const SpatialRegion::Id chunkId = SpatialRegion::regionContainingVoxel(location);
|
||||
const unsigned int arrayIndex = Chunk::voxelCoordToArrayIndex(location);
|
||||
|
||||
Cell prevCell = Constants::kUniqueEmptyCellRepresentation;
|
||||
CellMaterial prevMaterial = CELL_MATERIAL_Water;
|
||||
|
||||
Chunk* existingChunk = chunkMap.find(chunkId);
|
||||
|
||||
if (existingChunk) {
|
||||
prevCell = existingChunk->getConstData()[arrayIndex];
|
||||
prevMaterial = readMaterial(&(existingChunk->getConstMaterial()[0]),
|
||||
arrayIndex, prevCell);
|
||||
}
|
||||
|
||||
CellMaterial newMaterial = inputMaterial == CELL_MATERIAL_Unspecified ?
|
||||
prevMaterial : inputMaterial;
|
||||
|
||||
bool changed = prevCell != newCell || prevMaterial != newMaterial;
|
||||
|
||||
if (changed) {
|
||||
// find or create Chunk
|
||||
Chunk* updatingChunk = existingChunk;
|
||||
|
||||
if (!updatingChunk)
|
||||
{
|
||||
updatingChunk = &chunkMap.insert(chunkId);
|
||||
updatingChunk->init(this);
|
||||
}
|
||||
|
||||
bool hadWaterBefore = Water::cellHasWater(updatingChunk, prevCell, location);
|
||||
|
||||
updatingChunk->getData()[arrayIndex] = newCell;
|
||||
writeMaterial(&(updatingChunk->getMaterial()[0]), arrayIndex, newMaterial);
|
||||
|
||||
bool hasWaterAfter = Water::cellHasWater(updatingChunk, newCell, location);
|
||||
|
||||
int nonEmptyDelta = prevCell.isEmpty() - newCell.isEmpty();
|
||||
|
||||
updatingChunk->updateCountOfNonEmptyCells(nonEmptyDelta);
|
||||
countOfNonEmptyCells += nonEmptyDelta;
|
||||
|
||||
if (updatingChunk->hasNoUsefulData()) {
|
||||
chunkMap.erase(chunkId);
|
||||
}
|
||||
|
||||
CellChangeInfo info(location, prevCell, newCell, hadWaterBefore, hasWaterAfter, newMaterial);
|
||||
|
||||
for (unsigned int i = 0; i < cellChangeListeners.size(); ++i) {
|
||||
cellChangeListeners[i]->terrainCellChanged(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Grid::Region Grid::getRegion(
|
||||
const Vector3int16& minCoord, const Vector3int16& maxCoord) const {
|
||||
SpatialRegion::Id minId = SpatialRegion::regionContainingVoxel(minCoord);
|
||||
RBXASSERT(minId == SpatialRegion::regionContainingVoxel(maxCoord));
|
||||
|
||||
return Region(chunkMap.find(minId), minCoord, maxCoord);
|
||||
}
|
||||
|
||||
Cell Grid::getCell(const Vector3int16& pos) const {
|
||||
SpatialRegion::Id chunkId = SpatialRegion::regionContainingVoxel(pos);
|
||||
|
||||
if (const Chunk* chunk = chunkMap.find(chunkId)) {
|
||||
return chunk->getConstData()[Chunk::voxelCoordToArrayIndex(pos)];
|
||||
}
|
||||
|
||||
return Constants::kUniqueEmptyCellRepresentation;
|
||||
}
|
||||
|
||||
CellMaterial Grid::getCellMaterial(const Vector3int16& pos) const {
|
||||
SpatialRegion::Id chunkId = SpatialRegion::regionContainingVoxel(pos);
|
||||
|
||||
if (const Chunk* chunk = chunkMap.find(chunkId)) {
|
||||
const unsigned int index = Chunk::voxelCoordToArrayIndex(pos);
|
||||
return readMaterial(&chunk->getConstMaterial()[0], index,
|
||||
chunk->getConstData()[index]);
|
||||
}
|
||||
|
||||
return CELL_MATERIAL_Water;
|
||||
}
|
||||
|
||||
Cell Grid::getWaterCell(const Vector3int16& pos) const {
|
||||
SpatialRegion::Id chunkId(SpatialRegion::regionContainingVoxel(pos));
|
||||
|
||||
if (const Chunk* chunk = chunkMap.find(chunkId)) {
|
||||
unsigned int arrayIndex = Chunk::voxelCoordToArrayIndex(pos);
|
||||
return Water::interpretAsWaterCell(chunk, chunk->getConstData()[arrayIndex], pos);
|
||||
}
|
||||
|
||||
return Constants::kUniqueEmptyCellRepresentation;
|
||||
}
|
||||
|
||||
void Grid::connectListener(CellChangeListener* listener) {
|
||||
if (std::find(cellChangeListeners.begin(), cellChangeListeners.end(), listener) == cellChangeListeners.end()) {
|
||||
cellChangeListeners.push_back(listener);
|
||||
} else {
|
||||
RBXASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Grid::disconnectListener(CellChangeListener* listener) {
|
||||
std::vector<Voxel::CellChangeListener*>::iterator itr =
|
||||
std::find(cellChangeListeners.begin(), cellChangeListeners.end(), listener);
|
||||
if (itr != cellChangeListeners.end()) {
|
||||
cellChangeListeners.erase(itr);
|
||||
} else {
|
||||
RBXASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SpatialRegion::Id> Grid::getNonEmptyChunks() const
|
||||
{
|
||||
return chunkMap.getChunks();
|
||||
}
|
||||
|
||||
bool Grid::isAllocated() const {
|
||||
return countOfNonEmptyCells > 0;
|
||||
}
|
||||
|
||||
std::vector<SpatialRegion::Id> Grid::getNonEmptyChunksInRegion(const Region3int16& extents) const
|
||||
{
|
||||
if (extents.empty())
|
||||
return std::vector<SpatialRegion::Id>();
|
||||
|
||||
SpatialRegion::Id minRegion(SpatialRegion::regionContainingVoxel(extents.getMinPos()));
|
||||
SpatialRegion::Id maxRegion(SpatialRegion::regionContainingVoxel(extents.getMaxPos()));
|
||||
|
||||
unsigned int totalRegionCount =
|
||||
(maxRegion.value().x - minRegion.value().x + 1) *
|
||||
(maxRegion.value().y - minRegion.value().y + 1) *
|
||||
(maxRegion.value().z - minRegion.value().z + 1);
|
||||
|
||||
// chunkMap.find() is more expensive than isBetweenInclusive
|
||||
if (totalRegionCount < chunkMap.size() * 2)
|
||||
{
|
||||
// We're querying a relatively small area, let's just iterate through all regions
|
||||
std::vector<SpatialRegion::Id> result;
|
||||
|
||||
for (int ry = minRegion.value().y; ry <= maxRegion.value().y; ++ry)
|
||||
for (int rz = minRegion.value().z; rz <= maxRegion.value().z; ++rz)
|
||||
for (int rx = minRegion.value().x; rx <= maxRegion.value().x; ++rx)
|
||||
{
|
||||
SpatialRegion::Id id(rx, ry, rz);
|
||||
|
||||
if (chunkMap.find(id))
|
||||
result.push_back(id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're querying a relatively large area, let's scan through filled regions inside the grid
|
||||
std::vector<SpatialRegion::Id> chunks = chunkMap.getChunks();
|
||||
|
||||
std::vector<SpatialRegion::Id> result;
|
||||
|
||||
for (size_t i = 0; i < chunks.size(); ++i)
|
||||
{
|
||||
SpatialRegion::Id id = chunks[i];
|
||||
|
||||
if (id.value().isBetweenInclusive(minRegion.value(), maxRegion.value()))
|
||||
result.push_back(id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
} }
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "Voxel/Serializer.h"
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
const unsigned char SerializerConstants::kNewCellMarker = 0x01;
|
||||
const unsigned char SerializerConstants::kRepeatCellMarker = 0x02;
|
||||
const unsigned char SerializerConstants::kEndSequenceMarker = 0x03;
|
||||
const unsigned int SerializerConstants::kRecentlyEncodedReferenceBits = 3;
|
||||
|
||||
} }
|
||||
@@ -0,0 +1,140 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "Voxel/Util.h"
|
||||
|
||||
namespace RBX { namespace Voxel {
|
||||
|
||||
const BlockFaceInfo UnOrientedBlockFaceInfos[6] = {
|
||||
// Solid
|
||||
{
|
||||
{
|
||||
{ BlockAxisFace::FullNoneSkipped}, // PlusX
|
||||
{ BlockAxisFace::FullNoneSkipped}, // PlusZ
|
||||
{ BlockAxisFace::FullNoneSkipped}, // MinusX
|
||||
{ BlockAxisFace::FullNoneSkipped}, // MinusZ
|
||||
{ BlockAxisFace::FullNoneSkipped}, // PlusY
|
||||
{ BlockAxisFace::FullNoneSkipped}, // MinusY
|
||||
}
|
||||
},
|
||||
|
||||
// Vertical Wedge
|
||||
{
|
||||
{
|
||||
{ BlockAxisFace::TopLeft, }, // PlusX
|
||||
{ BlockAxisFace::EmptyAllSkipped}, // PlusZ
|
||||
{ BlockAxisFace::TopRight, }, // MinusX
|
||||
{ BlockAxisFace::FullNoneSkipped}, // MinusZ
|
||||
{ BlockAxisFace::EmptyAllSkipped}, // PlusY
|
||||
{ BlockAxisFace::FullNoneSkipped}, // MinusY
|
||||
}
|
||||
},
|
||||
|
||||
// Corner Wedge
|
||||
{
|
||||
{
|
||||
{ BlockAxisFace::TopLeft, }, // PlusX
|
||||
{ BlockAxisFace::EmptyAllSkipped}, // PlusZ
|
||||
{ BlockAxisFace::EmptyAllSkipped}, // MinusX
|
||||
{ BlockAxisFace::TopRight, }, // MinusZ
|
||||
{ BlockAxisFace::EmptyAllSkipped}, // PlusY
|
||||
{ BlockAxisFace::TopLeft, }, // MinusY
|
||||
}
|
||||
},
|
||||
|
||||
// Inverse corner Wedge
|
||||
{
|
||||
{
|
||||
{ BlockAxisFace::FullNoneSkipped}, // PlusX
|
||||
{ BlockAxisFace::TopLeft, }, // PlusZ
|
||||
{ BlockAxisFace::TopRight, }, // MinusX
|
||||
{ BlockAxisFace::FullNoneSkipped}, // MinusZ
|
||||
{ BlockAxisFace::BottomLeft, }, // PlusY
|
||||
{ BlockAxisFace::FullNoneSkipped}, // MinusY
|
||||
}
|
||||
},
|
||||
|
||||
// Horizontal Wedge
|
||||
{
|
||||
{
|
||||
{ BlockAxisFace::FullNoneSkipped}, // PlusX
|
||||
{ BlockAxisFace::EmptyAllSkipped}, // PlusZ
|
||||
{ BlockAxisFace::EmptyAllSkipped}, // MinusX
|
||||
{ BlockAxisFace::FullNoneSkipped}, // MinusZ
|
||||
{ BlockAxisFace::BottomLeft, }, // PlusY
|
||||
{ BlockAxisFace::TopLeft, }, // MinusY
|
||||
}
|
||||
},
|
||||
|
||||
// Empty Wedge
|
||||
{
|
||||
{
|
||||
{ BlockAxisFace::EmptyAllSkipped }, // PlusX
|
||||
{ BlockAxisFace::EmptyAllSkipped }, // PlusZ
|
||||
{ BlockAxisFace::EmptyAllSkipped }, // MinusX
|
||||
{ BlockAxisFace::EmptyAllSkipped }, // MinusZ
|
||||
{ BlockAxisFace::EmptyAllSkipped }, // PlusY
|
||||
{ BlockAxisFace::EmptyAllSkipped }, // MinusY
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BlockAxisFace OrientedFaceMap[ 1536 ];
|
||||
|
||||
BlockAxisFace ComputeOrientedFace(const BlockFaceInfo& wedge, FaceDirection f,
|
||||
CellOrientation orient)
|
||||
{
|
||||
if(f < 4)
|
||||
{
|
||||
unsigned char index = (f + orient) % 4;
|
||||
|
||||
return wedge.faces[index];
|
||||
}
|
||||
|
||||
const BlockAxisFace& basis = wedge.faces[f];
|
||||
if (basis.skippedCorner == BlockAxisFace::FullNoneSkipped ||
|
||||
basis.skippedCorner == BlockAxisFace::EmptyAllSkipped) {
|
||||
return basis;
|
||||
}
|
||||
|
||||
BlockAxisFace result;
|
||||
// minus Y is the same as plus y, except it is wound backwards.
|
||||
// encode this by reversing the orient iteration order for minus y:
|
||||
// 0 1 2 3 => 0 3 2 1
|
||||
result.skippedCorner = BlockAxisFace::rotate(basis.skippedCorner,
|
||||
(CellOrientation)(f == MinusY ? (4 - orient) % 4 : orient));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void initBlockOrientationFaceMap()
|
||||
{
|
||||
// max materials = 8 to represent that 3 legacy bits were used for material
|
||||
static int kLEGACY_MAX_MATERIALS = 8;
|
||||
static int kNUM_WEDGES = 6;
|
||||
|
||||
for( int i = 0; i < MAX_CELL_BLOCKS; ++i )
|
||||
{
|
||||
const BlockFaceInfo& wedge = i >= kNUM_WEDGES ?
|
||||
UnOrientedBlockFaceInfos[0] : UnOrientedBlockFaceInfos[i];
|
||||
|
||||
for( int j = 0; j < kLEGACY_MAX_MATERIALS; ++j )
|
||||
{
|
||||
for( int k = 0; k < MAX_CELL_ORIENTATIONS; ++k )
|
||||
{
|
||||
for( int l = 0; l < 6; ++l ) // # of face directions
|
||||
{
|
||||
Cell voxel;
|
||||
voxel.solid.setBlock((CellBlock)i);
|
||||
voxel.solid.setOrientation((CellOrientation)k);
|
||||
unsigned char mapIndex = Cell::asUnsignedCharForDeprecatedUses(voxel);
|
||||
setCellMaterial_Deprecated( mapIndex, (CellMaterial)j );
|
||||
|
||||
OrientedFaceMap[ mapIndex*6 + l ] =
|
||||
ComputeOrientedFace(wedge, (FaceDirection)l, (CellOrientation)k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} }
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "Voxel/Water.h"
|
||||
|
||||
namespace RBX { namespace Voxel { namespace Water {
|
||||
|
||||
const RelevantNeighbors kRelevantNeighbors[MAX_CELL_ORIENTATIONS] = {
|
||||
RelevantNeighbors(CELL_ORIENTATION_NegZ),
|
||||
RelevantNeighbors(CELL_ORIENTATION_X),
|
||||
RelevantNeighbors(CELL_ORIENTATION_Z),
|
||||
RelevantNeighbors(CELL_ORIENTATION_NegX),
|
||||
};
|
||||
|
||||
RelevantNeighbors::RelevantNeighbors(CellOrientation orientation) :
|
||||
aboveNeighbor(kAboveNeighborCellOffset),
|
||||
primaryNeighbor(kPrimaryNeighborCellOffset[orientation]),
|
||||
secondaryNeighbor(kSecondaryNeighborCellOffset[orientation]),
|
||||
diagonalNeighbor(primaryNeighbor + secondaryNeighbor),
|
||||
diagonalUpNeighbor(primaryNeighbor + secondaryNeighbor + aboveNeighbor) {}
|
||||
|
||||
} } }
|
||||
Reference in New Issue
Block a user