mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
2213 lines
66 KiB
C++
2213 lines
66 KiB
C++
/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
|
|
#include "stdafx.h"
|
|
|
|
#include "V8DataModel/MegaCluster.h"
|
|
|
|
#include "G3D/CollisionDetection.h"
|
|
#include "Reflection/Reflection.h"
|
|
#include "util/Region3int16.h"
|
|
#include "util/RunStateOwner.h"
|
|
#include "util/stringbuffer.h"
|
|
#include "V8DataModel/Workspace.h"
|
|
#include "V8World/Primitive.h"
|
|
#include "V8World/MegaClusterPoly.h"
|
|
#include "V8World/SmoothClusterGeometry.h"
|
|
#include "V8World/TerrainPartition.h"
|
|
#include "V8World/World.h"
|
|
#include "V8World/ContactManager.h"
|
|
#include "Voxel/CellChangeListener.h"
|
|
#include "V8DataModel/TerrainRegion.h"
|
|
#include "Network/Players.h"
|
|
#include "v8datamodel/ChangeHistory.h"
|
|
|
|
#include "Voxel2/Grid.h"
|
|
#include "Voxel2/MaterialTable.h"
|
|
#include "Voxel2/Mesher.h"
|
|
#include "Voxel2/Conversion.h"
|
|
|
|
#include "script/LuaArguments.h"
|
|
|
|
LOGVARIABLE(TerrainCellListener, 0)
|
|
|
|
FASTINTVARIABLE(SmoothTerrainMaxLuaRegion, 4*1024*1024)
|
|
FASTINTVARIABLE(SmoothTerrainMaxCppRegion, 64*1024*1024)
|
|
|
|
namespace RBX
|
|
{
|
|
using namespace Voxel;
|
|
|
|
const Vector3int16 kLegacyChunkCount(16, 4, 16);
|
|
const Vector3int16 kLegacyChunkOffset(8, 0, 8);
|
|
|
|
static const ContentId kSmoothTerrainMaterials("rbxasset://terrain/materials.json");
|
|
|
|
namespace Reflection
|
|
{
|
|
template<> Reflection::EnumDesc<CellMaterial>::EnumDesc()
|
|
:RBX::Reflection::EnumDescriptor("CellMaterial")
|
|
{
|
|
addPair(CELL_MATERIAL_Deprecated_Empty, "Empty");
|
|
addPair(CELL_MATERIAL_Grass, "Grass");
|
|
addPair(CELL_MATERIAL_Sand, "Sand");
|
|
addPair(CELL_MATERIAL_Brick, "Brick");
|
|
addPair(CELL_MATERIAL_Granite, "Granite");
|
|
addPair(CELL_MATERIAL_Asphalt, "Asphalt");
|
|
addPair(CELL_MATERIAL_Iron, "Iron");
|
|
addPair(CELL_MATERIAL_Aluminum, "Aluminum");
|
|
addPair(CELL_MATERIAL_Gold, "Gold");
|
|
addPair(CELL_MATERIAL_Wood_Plank, "WoodPlank");
|
|
addPair(CELL_MATERIAL_Wood_Log, "WoodLog");
|
|
addPair(CELL_MATERIAL_Gravel, "Gravel");
|
|
addPair(CELL_MATERIAL_Cinder_Block, "CinderBlock");
|
|
addPair(CELL_MATERIAL_Stone_Block, "MossyStone");
|
|
addPair(CELL_MATERIAL_Cement, "Cement");
|
|
addPair(CELL_MATERIAL_Red_Plastic, "RedPlastic");
|
|
addPair(CELL_MATERIAL_Blue_Plastic, "BluePlastic");
|
|
addPair(CELL_MATERIAL_Water, "Water");
|
|
}
|
|
template<>
|
|
CellMaterial& Variant::convert<CellMaterial>(void)
|
|
{
|
|
return genericConvert<CellMaterial>();
|
|
}
|
|
|
|
template<> Reflection::EnumDesc<CellBlock>::EnumDesc()
|
|
:RBX::Reflection::EnumDescriptor("CellBlock")
|
|
{
|
|
addPair(CELL_BLOCK_Solid, "Solid" );
|
|
addPair(CELL_BLOCK_VerticalWedge, "VerticalWedge" );
|
|
addPair(CELL_BLOCK_CornerWedge, "CornerWedge" );
|
|
addPair(CELL_BLOCK_InverseCornerWedge, "InverseCornerWedge" );
|
|
addPair(CELL_BLOCK_HorizontalWedge, "HorizontalWedge" );
|
|
}
|
|
template<>
|
|
CellBlock& Variant::convert<CellBlock>(void)
|
|
{
|
|
return genericConvert<CellBlock>();
|
|
}
|
|
|
|
template<> Reflection::EnumDesc<CellOrientation>::EnumDesc()
|
|
:RBX::Reflection::EnumDescriptor("CellOrientation")
|
|
{
|
|
addPair(CELL_ORIENTATION_NegZ, "NegZ");
|
|
addPair(CELL_ORIENTATION_X, "X");
|
|
addPair(CELL_ORIENTATION_Z, "Z");
|
|
addPair(CELL_ORIENTATION_NegX, "NegX");
|
|
}
|
|
template<>
|
|
CellOrientation& Variant::convert<CellOrientation>(void)
|
|
{
|
|
return genericConvert<CellOrientation>();
|
|
}
|
|
|
|
template<> Reflection::EnumDesc<WaterCellForce>::EnumDesc()
|
|
:RBX::Reflection::EnumDescriptor("WaterForce")
|
|
{
|
|
addPair(WATER_CELL_FORCE_None, "None");
|
|
addPair(WATER_CELL_FORCE_Small, "Small");
|
|
addPair(WATER_CELL_FORCE_Medium, "Medium");
|
|
addPair(WATER_CELL_FORCE_Strong, "Strong");
|
|
addPair(WATER_CELL_FORCE_MaxForce, "Max");
|
|
}
|
|
template<>
|
|
WaterCellForce& Variant::convert<WaterCellForce>(void)
|
|
{
|
|
return genericConvert<WaterCellForce>();
|
|
}
|
|
|
|
template<> Reflection::EnumDesc<WaterCellDirection>::EnumDesc()
|
|
:RBX::Reflection::EnumDescriptor("WaterDirection")
|
|
{
|
|
addPair(WATER_CELL_DIRECTION_NegX, "NegX");
|
|
addPair(WATER_CELL_DIRECTION_X, "X");
|
|
addPair(WATER_CELL_DIRECTION_NegY, "NegY");
|
|
addPair(WATER_CELL_DIRECTION_Y, "Y");
|
|
addPair(WATER_CELL_DIRECTION_NegZ, "NegZ");
|
|
addPair(WATER_CELL_DIRECTION_Z, "Z");
|
|
}
|
|
template<>
|
|
WaterCellDirection& Variant::convert<WaterCellDirection>(void)
|
|
{
|
|
return genericConvert<WaterCellDirection>();
|
|
}
|
|
}
|
|
|
|
template<>
|
|
bool RBX::StringConverter<CellMaterial>::convertToValue(const std::string& text, CellMaterial& value)
|
|
{
|
|
return Reflection::EnumDesc<CellMaterial>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
template<>
|
|
bool RBX::StringConverter<CellBlock>::convertToValue(const std::string& text, CellBlock& value)
|
|
{
|
|
return Reflection::EnumDesc<CellBlock>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
template<>
|
|
bool RBX::StringConverter<CellOrientation>::convertToValue(const std::string& text, CellOrientation& value)
|
|
{
|
|
return Reflection::EnumDesc<CellOrientation>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
template<>
|
|
bool RBX::StringConverter<WaterCellForce>::convertToValue(const std::string& text, WaterCellForce& value)
|
|
{
|
|
return Reflection::EnumDesc<WaterCellForce>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
template<>
|
|
bool RBX::StringConverter<WaterCellDirection>::convertToValue(const std::string& text, WaterCellDirection& value)
|
|
{
|
|
return Reflection::EnumDesc<WaterCellDirection>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
|
|
|
|
// these defaults should all correspond with char cell = 0
|
|
const CellMaterial MegaClusterInstance::kDefaultMaterial = CELL_MATERIAL_Deprecated_Empty;
|
|
const CellBlock MegaClusterInstance::kDefaultBlock = CELL_BLOCK_Solid;
|
|
const CellOrientation MegaClusterInstance::kDefaultOrientation = CELL_ORIENTATION_NegZ;
|
|
|
|
const char* const sMegaCluster = "Terrain";
|
|
|
|
using namespace Reflection;
|
|
|
|
REFLECTION_BEGIN();
|
|
// LUA property reflections
|
|
static Reflection::PropDescriptor<MegaClusterInstance, std::string> desc_ClusterGridV1("ClusterGrid", category_Data, NULL, &MegaClusterInstance::setPackagedClusterGridV1, Reflection::PropertyDescriptor::LEGACY, Security::None);
|
|
static Reflection::PropDescriptor<MegaClusterInstance, std::string> desc_ClusterGridV2("ClusterGridV2", category_Data, NULL, &MegaClusterInstance::setPackagedClusterGridV2, Reflection::PropertyDescriptor::LEGACY, Security::None);
|
|
static Reflection::PropDescriptor<MegaClusterInstance, BinaryString> desc_ClusterGridV3("ClusterGridV3", category_Data, &MegaClusterInstance::getPackagedClusteredGridV3, &MegaClusterInstance::setPackagedClusterGridV3, Reflection::PropertyDescriptor::CLUSTER, Security::None);
|
|
|
|
static Reflection::PropDescriptor<MegaClusterInstance, BinaryString> desc_SmoothGrid("SmoothGrid", category_Data, &MegaClusterInstance::getPackagedSmoothGrid, &MegaClusterInstance::setPackagedSmoothGrid, Reflection::PropertyDescriptor::CLUSTER, Security::None);
|
|
|
|
static Reflection::PropDescriptor<MegaClusterInstance, int> desc_SmoothReplicate("SmoothReplicate", category_Data, &MegaClusterInstance::getSmoothReplicate, &MegaClusterInstance::setSmoothReplicate, Reflection::PropertyDescriptor::REPLICATE_ONLY, Security::None);
|
|
|
|
static Reflection::PropDescriptor<MegaClusterInstance, Region3int16> prop_MaxExtents("MaxExtents", category_Data, &MegaClusterInstance::getMaxExtents, NULL, PropertyDescriptor::UI );
|
|
|
|
// LUA function reflections
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, shared_ptr<const Reflection::Tuple>(int,int,int)> func_getCell(&MegaClusterInstance::getCellScript,
|
|
"GetCell", "x", "y", "z", Security::None);
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, void(int,int,int,CellMaterial,CellBlock,CellOrientation)>
|
|
func_setCell(&MegaClusterInstance::setCellScript, "SetCell", "x", "y", "z", "material", "block", "orientation", Security::None );
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, void(Region3int16,CellMaterial,CellBlock,CellOrientation)>
|
|
func_setCells(&MegaClusterInstance::setCellsScript, "SetCells", "region", "material", "block", "orientation", Security::None );
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, shared_ptr<const Reflection::Tuple>(int,int,int)>
|
|
func_getWaterCell(&MegaClusterInstance::getWaterCellScript,"GetWaterCell", "x", "y", "z", Security::None);
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, void(int,int,int,WaterCellForce,WaterCellDirection)>
|
|
func_setWaterCell(&MegaClusterInstance::setWaterCellScript, "SetWaterCell", "x", "y", "z", "force", "direction", Security::None );
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, bool(int, int, int)>
|
|
func_autoWedgeCell(&MegaClusterInstance::autoWedgeCellScript, "AutowedgeCell", "x", "y", "z", Security::None );
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, void(Region3int16)>
|
|
func_autoWedgeCells(&MegaClusterInstance::autoWedgeCellsScript, "AutowedgeCells", "region", Security::None );
|
|
|
|
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, Vector3(int, int, int)>
|
|
func_cellCornerToWorld(&MegaClusterInstance::cellCornerToWorldScript, "CellCornerToWorld", "x", "y", "z", Security::None );
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, Vector3(int, int, int)>
|
|
func_cellCenterToWorld(&MegaClusterInstance::cellCenterToWorldScript, "CellCenterToWorld", "x", "y", "z", Security::None );
|
|
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, Vector3(Vector3)>
|
|
func_worldToCellPreferSolid(&MegaClusterInstance::worldToCellPreferSolidScript, "WorldToCellPreferSolid", "position", Security::None );
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, Vector3(Vector3)>
|
|
func_worldToCellPreferEmpty(&MegaClusterInstance::worldToCellPreferEmptyScript, "WorldToCellPreferEmpty", "position", Security::None );
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, Vector3(Vector3)>
|
|
func_worldToCell(&MegaClusterInstance::worldToCellScript, "WorldToCell", "position", Security::None );
|
|
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, void(void)>
|
|
func_clear(&MegaClusterInstance::clear, "Clear", Security::None );
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, int(void)>
|
|
func_countCells(&MegaClusterInstance::countCellsScript, "CountCells", Security::None );
|
|
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, shared_ptr<Instance> (Region3int16)>
|
|
func_copyRegion(&MegaClusterInstance::copyRegion, "CopyRegion", "region", Security::None );
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, void (shared_ptr<Instance>, Vector3int16, bool)>
|
|
func_pasteRegion(&MegaClusterInstance::pasteRegion, "PasteRegion", "region", "corner", "pasteEmptyCells", Security::None );
|
|
|
|
static Reflection::PropDescriptor<MegaClusterInstance, bool>
|
|
prop_IsSmooth("IsSmooth", category_Data, &MegaClusterInstance::isSmooth, NULL, Reflection::PropertyDescriptor::UI, Security::None);
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, void(void)>
|
|
func_convertToSmooth(&MegaClusterInstance::convertToSmooth, "ConvertToSmooth", Security::Plugin);
|
|
|
|
static Reflection::CustomBoundFuncDesc<MegaClusterInstance, shared_ptr<const Reflection::Tuple>(Region3, float)>
|
|
func_readVoxels(&MegaClusterInstance::readVoxels, "ReadVoxels", "region", "resolution", Security::None );
|
|
static Reflection::CustomBoundFuncDesc<MegaClusterInstance, void(Region3, float, shared_ptr<const Reflection::ValueArray>, shared_ptr<const Reflection::ValueArray>)>
|
|
func_writeVoxels(&MegaClusterInstance::writeVoxels, "WriteVoxels", "region", "resolution", "materials", "occupancy", Security::None );
|
|
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, void(Region3, float, PartMaterial)>
|
|
func_fillRegion(&MegaClusterInstance::fillRegion, "FillRegion", "region", "resolution", "material", Security::None );
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, void(CoordinateFrame, Vector3, PartMaterial)>
|
|
func_fillBlock(&MegaClusterInstance::fillBlock, "FillBlock", "cframe", "size", "material", Security::None );
|
|
static Reflection::BoundFuncDesc<MegaClusterInstance, void(Vector3, float, PartMaterial)>
|
|
func_fillBall(&MegaClusterInstance::fillBall, "FillBall", "center", "radius", "material", Security::None );
|
|
REFLECTION_END();
|
|
|
|
static Reflection::PropDescriptor<MegaClusterInstance, Color3> desc_WaterColor("WaterColor", category_Appearance, &MegaClusterInstance::getWaterColor, &MegaClusterInstance::setWaterColor);
|
|
static Reflection::PropDescriptor<MegaClusterInstance, float> desc_WaterTransparency("WaterTransparency", category_Appearance, &MegaClusterInstance::getWaterTransparency, &MegaClusterInstance::setWaterTransparency);
|
|
static Reflection::PropDescriptor<MegaClusterInstance, float> desc_WaterWaveSize("WaterWaveSize", category_Appearance, &MegaClusterInstance::getWaterWaveSize, &MegaClusterInstance::setWaterWaveSize);
|
|
static Reflection::PropDescriptor<MegaClusterInstance, float> desc_WaterWaveSpeed("WaterWaveSpeed", category_Appearance, &MegaClusterInstance::getWaterWaveSpeed, &MegaClusterInstance::setWaterWaveSpeed);
|
|
|
|
// Properties to make read-only with constant return values
|
|
const bool MegaClusterInstance::kConstArchivable = true;
|
|
const BrickColor MegaClusterInstance::kConstBrickColor = BrickColor();
|
|
const bool MegaClusterInstance::kConstCanCollide = true;
|
|
const float MegaClusterInstance::kConstElasticity(0.3f);
|
|
const float MegaClusterInstance::kConstFriction(0.5f);
|
|
const bool MegaClusterInstance::kConstLocked = true;
|
|
const PartMaterial MegaClusterInstance::kConstMaterial(PLASTIC_MATERIAL);
|
|
const PhysicalProperties MegaClusterInstance::kConstCustomPhysicalProperties = PhysicalProperties();
|
|
|
|
const std::string MegaClusterInstance::kConstName("Terrain");
|
|
const float MegaClusterInstance::kConstReflectance = 0;
|
|
const Vector3 MegaClusterInstance::kConstRotVelocity(0, 0, 0);
|
|
const float MegaClusterInstance::kConstTransparency = 0;
|
|
const Vector3 MegaClusterInstance::kConstVelocity(0, 0, 0);
|
|
|
|
// Member methods
|
|
void MegaClusterInstance::destroyJoints() {
|
|
// Cannot throw as long as this extends PartInstance because other
|
|
// property updates trigger destroyJoints().
|
|
}
|
|
|
|
shared_ptr<Instance> MegaClusterInstance::luaClone() {
|
|
throw std::runtime_error("Cannot Clone() Terrain");
|
|
}
|
|
|
|
void MegaClusterInstance::destroy() {
|
|
throw std::runtime_error("Cannot Destroy() Terrain");
|
|
}
|
|
|
|
void MegaClusterInstance::join() {
|
|
// Cannot throw as long as this extends PartInstance because other
|
|
// property updates trigger join().
|
|
}
|
|
|
|
bool MegaClusterInstance::resize(NormalId normalId, int deltaAmount) {
|
|
throw std::runtime_error("Cannot Resize() Terrain");
|
|
}
|
|
|
|
shared_ptr<const Instances> MegaClusterInstance::getTouchingParts()
|
|
{
|
|
throw RBX::runtime_error("GetTouchingParts is not a valid member of Terrain");
|
|
}
|
|
|
|
MegaClusterInstance::MegaClusterInstance()
|
|
: smoothReplicate(-1)
|
|
, waterColor(0.05f, 0.33f, 0.36f)
|
|
, waterTransparency(0.3f)
|
|
, waterWaveSize(0.15f)
|
|
, waterWaveSpeed(10.f)
|
|
{
|
|
FASTLOG1(FLog::MegaClusterInit, "MegaCluster created - %p", this);
|
|
|
|
Vector3 terrainSize = Vector3(511*4,63*4,511*4);
|
|
setName( "Terrain" );
|
|
PartInstance::setPartSizeXml(terrainSize);
|
|
setAnchored(true);
|
|
setPartLocked(true);
|
|
|
|
setElasticity(kConstElasticity);
|
|
setFriction(kConstFriction);
|
|
|
|
// Initialize tables for legacy terrain
|
|
initBlockOrientationFaceMap();
|
|
|
|
// Initialize tables for smooth terrain
|
|
materialTable.reset(new Voxel2::MaterialTable(ContentProvider::findAsset(kSmoothTerrainMaterials), Voxel2::Cell::Material_Max + 1));
|
|
|
|
Voxel2::Mesher::prepareTables();
|
|
}
|
|
|
|
MegaClusterInstance::~MegaClusterInstance()
|
|
{
|
|
FASTLOG1(FLog::MegaClusterInit, "MegaCluster destroyed - %p", this);
|
|
|
|
Workspace* parentWorkspace = dynamic_cast<Workspace*>( this->getParent() );
|
|
if( parentWorkspace ) parentWorkspace->setTerrain( NULL );
|
|
|
|
// This makes sure we delete physics geometry before voxel grid
|
|
getPartPrimitive()->setGeometryType(Geometry::GEOMETRY_BLOCK);
|
|
}
|
|
|
|
void MegaClusterInstance::setAnchored(bool value)
|
|
{
|
|
// Mega Clusters are ALWAYS anchored
|
|
Super::setAnchored( true );
|
|
}
|
|
|
|
void MegaClusterInstance::setPartSizeXml(const Vector3& rbxSize)
|
|
{
|
|
// do nothing. this property should be essentially read only
|
|
}
|
|
|
|
void MegaClusterInstance::setPartSizeUi(const Vector3& rbxSize)
|
|
{
|
|
// do nothing. this property should be essentially read only
|
|
}
|
|
|
|
void MegaClusterInstance::setTranslationUi(const Vector3& set)
|
|
{
|
|
// do nothing. this property should be essentially read only
|
|
}
|
|
|
|
void MegaClusterInstance::setCoordinateFrame(const CoordinateFrame& value)
|
|
{
|
|
// Disregard any input coordinate frame. Instead, send a fixed cluster
|
|
// coordinate frame
|
|
PartInstance::setCoordinateFrame(clusterCoordinateFrame);
|
|
}
|
|
|
|
void MegaClusterInstance::verifySetParent(const Instance* instance) const
|
|
{
|
|
Super::verifySetParent(instance);
|
|
|
|
const Workspace* workspace = instance != NULL ?
|
|
instance->fastDynamicCast<Workspace>() : NULL;
|
|
if( instance != NULL && ((workspace == NULL) || (workspace->getTerrain() != NULL)) ){
|
|
throw RBX::runtime_error("Unable to change Terrain's parent. Workspace already has Terrain");
|
|
}
|
|
|
|
if (instance)
|
|
const_cast<MegaClusterInstance*>(this)->initialize();
|
|
}
|
|
|
|
void MegaClusterInstance::initialize()
|
|
{
|
|
if (!voxelGrid && !smoothGrid)
|
|
{
|
|
if (smoothReplicate == 1)
|
|
initializeGridSmooth(); // terrain replicated from server, smooth
|
|
else if (smoothReplicate == 0)
|
|
initializeGridMega(); // terrain replicated from server, mega
|
|
else
|
|
initializeGridMega(); // default terrain, mega (for now!)
|
|
}
|
|
}
|
|
|
|
PartType MegaClusterInstance::getPartType() const
|
|
{
|
|
return MEGACLUSTER_PART;
|
|
//return BLOCK_PART;
|
|
}
|
|
|
|
bool MegaClusterInstance::isAllocated() const
|
|
{
|
|
if (smoothGrid)
|
|
return smoothGrid->isAllocated();
|
|
|
|
if (voxelGrid)
|
|
return voxelGrid->isAllocated();
|
|
|
|
return false;
|
|
}
|
|
|
|
void MegaClusterInstance::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
|
|
{
|
|
Super::onServiceProvider(oldProvider, newProvider);
|
|
FASTLOG2(FLog::MegaClusterInit, "onServiceProvider, oldProvider: %p, new parent: %p", oldProvider, newProvider);
|
|
|
|
if(newProvider == NULL && oldProvider != NULL)
|
|
{
|
|
Workspace* workspace = Workspace::findWorkspace(oldProvider);
|
|
if(workspace->getTerrain() == this)
|
|
{
|
|
FASTLOG(FLog::MegaClusterInit, "onServiceProvider: Clearing terrain instance");
|
|
workspace->setTerrain(NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MegaClusterInstance::onAncestorChanged(const AncestorChanged& event)
|
|
{
|
|
Super::onAncestorChanged(event);
|
|
|
|
FASTLOG1(FLog::MegaClusterInit, "Ancestor change, new parent: %p", event.newParent);
|
|
if(event.newParent == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Workspace* workspace = Workspace::findWorkspace(event.newParent);
|
|
|
|
if(!workspace->getTerrain())
|
|
{
|
|
FASTLOG(FLog::MegaClusterInit, "No terrain yet - it's going go be me!");
|
|
workspace->setTerrain(this);
|
|
}
|
|
else if( workspace->getTerrain() != this )
|
|
{
|
|
FASTLOG(FLog::MegaClusterInit, "Terrain already exists - replace the existing one with this one");
|
|
workspace->setTerrain(this);
|
|
}
|
|
}
|
|
|
|
shared_ptr<const Reflection::Tuple> MegaClusterInstance::getCellScript(int x, int y, int z)
|
|
{
|
|
validateApi();
|
|
|
|
if (smoothGrid)
|
|
{
|
|
using namespace Voxel2::Conversion;
|
|
|
|
Voxel2::Cell cell = smoothGrid->getCell(x, y, z);
|
|
|
|
shared_ptr<Reflection::Tuple> result(new Reflection::Tuple(3));
|
|
|
|
if (cell.getMaterial() == Voxel2::Cell::Material_Water)
|
|
{
|
|
result->values[0] = CELL_MATERIAL_Water;
|
|
result->values[1] = kDefaultBlock;
|
|
result->values[2] = kDefaultOrientation;
|
|
}
|
|
else
|
|
{
|
|
CellBlock block = getCellBlockFromCell(cell);
|
|
|
|
if (block == CELL_BLOCK_Empty)
|
|
{
|
|
result->values[0] = kDefaultMaterial;
|
|
result->values[1] = kDefaultBlock;
|
|
result->values[2] = kDefaultOrientation;
|
|
}
|
|
else
|
|
{
|
|
result->values[0] = getCellMaterialFromMaterial(getMaterialFromVoxelMaterial(cell.getMaterial()));
|
|
result->values[1] = block;
|
|
result->values[2] = kDefaultOrientation;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
Vector3int16 internalPos = Vector3int16(x,y,z);
|
|
Cell cellValue = voxelGrid->getCell(internalPos);
|
|
Cell waterCell = voxelGrid->getWaterCell(internalPos);
|
|
CellMaterial cellMaterial = (CellMaterial) voxelGrid->getCellMaterial(internalPos);
|
|
shared_ptr<Reflection::Tuple> result(new Reflection::Tuple(3));
|
|
|
|
if ( cellValue.solid.getBlock() == CELL_BLOCK_Empty )
|
|
{
|
|
if (!waterCell.isEmpty()) {
|
|
result->values[0] = CELL_MATERIAL_Water;
|
|
} else {
|
|
result->values[0] = kDefaultMaterial;
|
|
}
|
|
result->values[1] = kDefaultBlock;
|
|
result->values[2] = kDefaultOrientation;
|
|
} else {
|
|
result->values[0] = cellMaterial;
|
|
result->values[1] = cellValue.solid.getBlock();
|
|
result->values[2] = cellValue.solid.getOrientation();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
void MegaClusterInstance::setCellsScript(Region3int16 region, CellMaterial material, CellBlock block, CellOrientation orientation )
|
|
{
|
|
validateApi();
|
|
|
|
if (smoothGrid)
|
|
{
|
|
Vector3int16 minPos = region.getMinPos();
|
|
Vector3int16 maxPos = region.getMaxPos();
|
|
|
|
if (minPos.x <= maxPos.x && minPos.y <= maxPos.y && minPos.z <= maxPos.z)
|
|
{
|
|
Voxel2::Region rr(Vector3int32(minPos), Vector3int32(maxPos) + Vector3int32::one());
|
|
Vector3int32 rsize = rr.size();
|
|
|
|
Voxel2::Box box(rsize.x, rsize.y, rsize.z);
|
|
|
|
using namespace Voxel2::Conversion;
|
|
|
|
Voxel2::Cell cell =
|
|
(material == CELL_MATERIAL_Water)
|
|
? Voxel2::Cell(Voxel2::Cell::Material_Water, Voxel2::Cell::Occupancy_Max)
|
|
:
|
|
(material == CELL_MATERIAL_Deprecated_Empty || block == CELL_BLOCK_Empty)
|
|
? Voxel2::Cell()
|
|
: Voxel2::Cell(getVoxelMaterialFromMaterial(getMaterialFromCellMaterial(material)), getOccupancyFromSolidBlock(block));
|
|
|
|
if (cell.getMaterial() != Voxel2::Cell::Material_Air)
|
|
{
|
|
for (int y = 0; y < rsize.y; ++y)
|
|
for (int z = 0; z < rsize.z; ++z)
|
|
{
|
|
Voxel2::Cell* row = box.writeRow(0, y, z);
|
|
|
|
for (int x = 0; x < rsize.x; ++x)
|
|
row[x] = cell;
|
|
}
|
|
}
|
|
|
|
smoothGrid->write(rr, box);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Vector3int16 minPos = region.getMinPos();
|
|
Vector3int16 maxPos = region.getMaxPos();
|
|
|
|
// Data is stored in (Y > Z > X) format [so Y is most significant index and X is least in pointer arithmetic], so traverse in this order for significant speed-up!
|
|
for( int j = minPos.y; j <= maxPos.y; ++j )
|
|
{
|
|
for( int k = minPos.z; k <= maxPos.z; ++k )
|
|
{
|
|
for( int i = minPos.x; i <= maxPos.x; ++i )
|
|
{
|
|
setCellScript(i, j, k, material, block, orientation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MegaClusterInstance::autoWedgeCellScript(int scriptX, int scriptY, int scriptZ)
|
|
{
|
|
validateApi();
|
|
|
|
if (smoothGrid)
|
|
return false;
|
|
|
|
Vector3int16 internalCellPos = Vector3int16(scriptX, scriptY, scriptZ);
|
|
Cell currentCell = voxelGrid->getCell(internalCellPos);
|
|
CellMaterial cellMat = voxelGrid->getCellMaterial(internalCellPos);
|
|
if (currentCell.solid.getBlock() != CELL_BLOCK_Empty) {
|
|
|
|
Cell cell;
|
|
|
|
bool surroundings[3][2][3];
|
|
bool sides[4];
|
|
bool corners[4];
|
|
bool sidesAbove[4];
|
|
bool cornersAbove[4];
|
|
int adjacentSides = 0;
|
|
int adjacentSidesAbove = 0;
|
|
int adjacentCorners = 0;
|
|
int adjacentCornersAbove = 0;
|
|
bool blockBelow = (voxelGrid->getCell(Vector3int16(internalCellPos.x, internalCellPos.y - 1, internalCellPos.z)).solid.getBlock() != CELL_BLOCK_Empty);
|
|
|
|
// build out the surrounding geometry
|
|
for ( int j = 0; j <= 1; ++j ){
|
|
for ( int k = 0; k <= 2; ++k ){
|
|
for ( int i = 0; i <= 2; ++i ){
|
|
surroundings[i][j][k] = (voxelGrid->getCell(Vector3int16(internalCellPos.x - 1 + i, internalCellPos.y + j, internalCellPos.z - 1 + k)).solid.getBlock() != CELL_BLOCK_Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( int step = 0; step <= 2; step += 2 ){
|
|
sides[step] = surroundings[step][0][1];
|
|
sides[step + 1] = surroundings[1][0][2 - step];
|
|
|
|
sidesAbove[step] = surroundings[step][1][1];
|
|
sidesAbove[step + 1] = surroundings[1][1][2 - step];
|
|
|
|
corners[step] = surroundings[step][0][step];
|
|
corners[step + 1] = surroundings[step][0][2 - step];
|
|
|
|
cornersAbove[step] = surroundings[step][1][step];
|
|
cornersAbove[step + 1] = surroundings[step][1][2 - step];
|
|
}
|
|
|
|
for ( int i = 0; i < 4; ++i ){
|
|
if (sides[i]) adjacentSides++;
|
|
if (corners[i]) adjacentCorners++;
|
|
if (sidesAbove[i]) adjacentSidesAbove++;
|
|
if (cornersAbove[i]) adjacentCornersAbove++;
|
|
}
|
|
|
|
// Figure out which cell to autowedge to:
|
|
CellBlock wedge;
|
|
CellOrientation rotation;
|
|
// type 1: 45 degree ramp [must not have a block on top and be surrounded by 1 side; or 3 sides and the 2 corners between them]
|
|
if (!surroundings[1][1][1])
|
|
{
|
|
if (adjacentSides == 1 && blockBelow) // if only surrounded by 1 side, then MUST have a block beneath it
|
|
{
|
|
for (int n = 0; n < 4; ++n )
|
|
{
|
|
if (sides[n])
|
|
{
|
|
wedge = (CellBlock)1;
|
|
rotation = (CellOrientation)((n + 1) % 4);
|
|
|
|
cell.solid.setBlock(wedge);
|
|
cell.solid.setOrientation(rotation);
|
|
voxelGrid->setCell(internalCellPos, cell, cellMat);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (adjacentSides == 3)
|
|
{
|
|
for (int n = 0; n < 4; ++n )
|
|
{
|
|
if (sides[n] && corners[(n + 1) % 4] && sides[(n + 1) % 4] && corners[(n + 2) % 4] && sides[(n + 2) % 4])
|
|
{
|
|
wedge = (CellBlock)1;
|
|
rotation = (CellOrientation)((n + 2) % 4);
|
|
|
|
cell.solid.setBlock(wedge);
|
|
cell.solid.setOrientation(rotation);
|
|
voxelGrid->setCell(internalCellPos, cell, cellMat);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//type 2: 45 degree corner [must not have a block on top, and be surrounded by 2 sides and the 1 corner between them; or 3 sides and 1 corner between 2 of them (facing towards that corner)]
|
|
for (int n = 0; n < 4; ++n )
|
|
{
|
|
if (sides[n] && corners[(n + 1) % 4] && sides[(n + 1) % 4] && (adjacentSides == 2 || (adjacentSides == 3 && (corners[(n + 3) % 4] || (sides[(n + 2) % 4] && corners[(n + 2) % 4]) || (sides[(n + 3) % 4] && corners[n])))))
|
|
{
|
|
wedge = (CellBlock)2;
|
|
rotation = (CellOrientation)((n + 2) % 4);
|
|
cell.solid.setBlock(wedge);
|
|
cell.solid.setOrientation(rotation);
|
|
voxelGrid->setCell(internalCellPos, cell, cellMat);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//type 3: 45 degree inverse corner [surrounded by three sides or 4 sides and 3 corners, with nothing above or else a block on top surrounded on 2 sides and the corner between them]
|
|
if (adjacentSides == 3 && surroundings[1][1][1])
|
|
{
|
|
if (adjacentCorners > 1)
|
|
{
|
|
for ( int n = 0; n < 4; ++n )
|
|
{
|
|
if ((!corners[n] || !cornersAbove[n]) && (!sides[(n - 1) % 4] || !sides[n]) && (!sidesAbove[n] && sidesAbove[(n + 1) % 4] && sidesAbove[(n + 2) % 4] && !sidesAbove[(n + 3) % 4]))
|
|
{
|
|
wedge = (CellBlock)3;
|
|
rotation = (CellOrientation)((n + 3) % 4);
|
|
cell.solid.setBlock(wedge);
|
|
cell.solid.setOrientation(rotation);
|
|
voxelGrid->setCell(internalCellPos, cell, cellMat);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( adjacentSides == 4 && adjacentCorners == 3 )
|
|
{
|
|
for ( int n = 0; n < 4; ++n )
|
|
{
|
|
if (!corners[n] && (!surroundings[1][1][1] || (!sidesAbove[n] && sidesAbove[(n + 1) % 4] && cornersAbove[(n + 2) % 4] && sidesAbove[(n + 2) % 4] && !sidesAbove[(n + 3) % 4])))
|
|
{
|
|
wedge = (CellBlock)3;
|
|
rotation = (CellOrientation)((n + 3) % 4);
|
|
cell.solid.setBlock(wedge);
|
|
cell.solid.setOrientation(rotation);
|
|
voxelGrid->setCell(internalCellPos, cell, cellMat);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//type 4: half a cube, as if it were cut diagonally from front to back [surrounded by 2 sides]
|
|
if (adjacentSides == 2 && adjacentCorners < 4)
|
|
{
|
|
for ( int n = 0; n < 4; ++n )
|
|
{
|
|
if ( !sides[n] && !sides[(n + 1) % 4] && (!surroundings[1][1][1] || (!sidesAbove[n] && !sidesAbove[(n + 1) % 4] && sidesAbove[(n + 2) % 4] && sidesAbove[(n + 3) % 4])))
|
|
{
|
|
wedge = (CellBlock)4;
|
|
rotation = (CellOrientation)n;
|
|
cell.solid.setBlock(wedge);
|
|
cell.solid.setOrientation(rotation);
|
|
voxelGrid->setCell(internalCellPos, cell, cellMat);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// should be a regular block
|
|
wedge = (CellBlock)0;
|
|
rotation = (CellOrientation)0;
|
|
cell.solid.setBlock(wedge);
|
|
cell.solid.setOrientation(rotation);
|
|
voxelGrid->setCell(internalCellPos, cell, cellMat);
|
|
return false;
|
|
}
|
|
|
|
// was empty
|
|
return false;
|
|
}
|
|
|
|
void MegaClusterInstance::autoWedgeCellsScript(Region3int16 region)
|
|
{
|
|
validateApi();
|
|
|
|
if (smoothGrid)
|
|
return;
|
|
|
|
std::vector<SpatialRegion::Id> chunks = voxelGrid->getNonEmptyChunksInRegion(region);
|
|
|
|
for (size_t i = 0; i < chunks.size(); ++i)
|
|
{
|
|
Region3int16 regionExtents = SpatialRegion::inclusiveVoxelExtentsOfRegion(chunks[i]);
|
|
Vector3int16 minPos = regionExtents.getMinPos().max(region.getMinPos());
|
|
Vector3int16 maxPos = regionExtents.getMaxPos().min(region.getMaxPos());
|
|
|
|
for (int y = minPos.y; y <= maxPos.y; ++y)
|
|
for (int z = minPos.z; z <= maxPos.z; ++z)
|
|
for (int x = minPos.x; x <= maxPos.x; ++x)
|
|
autoWedgeCellScript(x, y, z);
|
|
}
|
|
}
|
|
|
|
void MegaClusterInstance::autoWedgeCellsInternal(Region3int16 region)
|
|
{
|
|
Vector3int16 minPos = region.getMinPos();
|
|
Vector3int16 maxPos = region.getMaxPos();
|
|
autoWedgeCellsScript(Region3int16(minPos, maxPos));
|
|
}
|
|
|
|
Surface MegaClusterInstance::getSurface(const RbxRay& gridRay, int& surfaceId)
|
|
{
|
|
Geometry* geometry = getPrimitive(this)->getGeometry();
|
|
|
|
Vector3 notUsed1;
|
|
CoordinateFrame notUsed2;
|
|
if (geometry->hitTestTerrain(gridRay, notUsed1, surfaceId, notUsed2))
|
|
return Surface(this, NormalId(surfaceId));
|
|
else
|
|
return Surface();
|
|
}
|
|
|
|
template<class Stream> void writeCountValue(Stream& s, unsigned count)
|
|
{
|
|
RBXASSERT(count <= 0xffff);
|
|
|
|
if(count < 255)
|
|
{
|
|
s << ((unsigned char)count);
|
|
}
|
|
else
|
|
{
|
|
s << (unsigned char)255;
|
|
s << (unsigned char)(count >> 8);
|
|
s << (unsigned char)(count & 0xff);
|
|
}
|
|
}
|
|
|
|
template<class Stream> unsigned readCountValue(Stream& s)
|
|
{
|
|
unsigned char count;
|
|
s >> count;
|
|
|
|
if(count < 255)
|
|
{
|
|
return count;
|
|
}
|
|
else
|
|
{
|
|
unsigned char hi, lo;
|
|
s >> hi;
|
|
s >> lo;
|
|
return (hi << 8) | lo;
|
|
}
|
|
}
|
|
|
|
template <class Stream> void writeInt16(Stream& s, short value)
|
|
{
|
|
unsigned char v0 = value;
|
|
unsigned char v1 = value >> 8;
|
|
|
|
s << v0;
|
|
s << v1;
|
|
}
|
|
|
|
template <class Stream> short readInt16(Stream& s)
|
|
{
|
|
unsigned char v0, v1;
|
|
s >> v0;
|
|
s >> v1;
|
|
|
|
return v0 | (v1 << 8);
|
|
}
|
|
|
|
template <class Stream> void MegaClusterInstance::decodeChunkDataFromStreamV1_Deprecated(const Vector3int16& chunkPos, Stream& encodedData)
|
|
{
|
|
Vector3int16 cellInChunkIndex = Vector3int16( 0, 0, 0 );
|
|
while( cellInChunkIndex.y < Voxel::kY_CHUNK_SIZE )
|
|
{
|
|
unsigned char value;
|
|
unsigned count;
|
|
|
|
encodedData >> value;
|
|
|
|
count = readCountValue(encodedData);
|
|
|
|
RBXASSERT(count > 0);
|
|
|
|
for( unsigned i = 0; i < count; ++i )
|
|
{
|
|
Vector3int16 cellPos =
|
|
Vector3int16( chunkPos.x * Voxel::kXZ_CHUNK_SIZE,
|
|
chunkPos.y * Voxel::kY_CHUNK_SIZE,
|
|
chunkPos.z * Voxel::kXZ_CHUNK_SIZE );
|
|
cellPos += cellInChunkIndex;
|
|
|
|
setCellInternalV1_Deprecated( cellPos, value );
|
|
incrementCellInChunkIndex( cellInChunkIndex );
|
|
|
|
// validate we're not trying to process into other chunks
|
|
if (cellInChunkIndex.y >= Voxel::kY_CHUNK_SIZE)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <class Stream> void MegaClusterInstance::decodeChunkDataFromStream(Voxel::Grid& grid, const Vector3int16& chunkPos, Stream& encodedData)
|
|
{
|
|
FASTLOG3(FLog::MegaClusterDecodeStream,
|
|
"Decode chunk pos (%d,%d,%d)", chunkPos.x, chunkPos.y, chunkPos.z);
|
|
|
|
std::vector<unsigned char> cells(Voxel::kXZ_CHUNK_SIZE * Voxel::kXZ_CHUNK_SIZE * Voxel::kY_CHUNK_SIZE);
|
|
std::vector<unsigned char> materials(Voxel::kXZ_CHUNK_SIZE * Voxel::kXZ_CHUNK_SIZE * Voxel::kY_CHUNK_SIZE);
|
|
|
|
// Read cell data
|
|
unsigned int totalCells = 0;
|
|
|
|
while (totalCells < cells.size())
|
|
{
|
|
unsigned char value;
|
|
unsigned count;
|
|
|
|
encodedData >> value;
|
|
|
|
count = std::min(readCountValue(encodedData), static_cast<unsigned>(cells.size() - totalCells));
|
|
RBXASSERT(count > 0);
|
|
|
|
memset(&cells[totalCells], value, count);
|
|
|
|
totalCells += count;
|
|
}
|
|
|
|
// Read material data
|
|
unsigned int totalMaterials = 0;
|
|
|
|
while (totalMaterials < materials.size())
|
|
{
|
|
unsigned char value;
|
|
unsigned count;
|
|
|
|
encodedData >> value;
|
|
|
|
count = std::min(readCountValue(encodedData), static_cast<unsigned>(materials.size() - totalMaterials));
|
|
RBXASSERT(count > 0);
|
|
|
|
memset(&materials[totalMaterials], value, count);
|
|
|
|
totalMaterials += count;
|
|
}
|
|
|
|
// Set all cells
|
|
Region3int16 region = SpatialRegion::inclusiveVoxelExtentsOfRegion(SpatialRegion::Id(chunkPos));
|
|
Vector3int16 startLocation = region.getMinPos();
|
|
unsigned int offset = 0;
|
|
|
|
bool chunkWasEmpty =
|
|
grid.getRegion(region.getMinPos(), region.getMaxPos()).isGuaranteedAllEmpty();
|
|
|
|
for (int y = 0; y < Voxel::kY_CHUNK_SIZE; ++y)
|
|
{
|
|
for (int z = 0; z < Voxel::kXZ_CHUNK_SIZE; ++z)
|
|
{
|
|
for (int x = 0; x < Voxel::kXZ_CHUNK_SIZE; ++x)
|
|
{
|
|
Cell cell = Voxel::Cell::readUnsignedCharFromFile(cells[offset]);
|
|
|
|
// We only need to set non-empty cells if the chunk was empty before we started
|
|
if (!(chunkWasEmpty && cell.isEmpty()))
|
|
grid.setCell(startLocation + Vector3int16(x, y, z), cell, static_cast<Voxel::CellMaterial>(materials[offset]));
|
|
|
|
offset++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class Stream> void MegaClusterInstance::encodeChunkDataIntoStream( const Voxel::Grid::Region& chunk, Stream& s )
|
|
{
|
|
if (chunk.isGuaranteedAllEmpty())
|
|
{
|
|
s << Voxel::Cell::convertToUnsignedCharForFile(Voxel::Constants::kUniqueEmptyCellRepresentation);
|
|
writeCountValue(s, Voxel::kXZ_CHUNK_SIZE * Voxel::kY_CHUNK_SIZE * Voxel::kXZ_CHUNK_SIZE);
|
|
|
|
s << static_cast<unsigned char>(Voxel::CELL_MATERIAL_Water);
|
|
writeCountValue(s, Voxel::kXZ_CHUNK_SIZE * Voxel::kY_CHUNK_SIZE * Voxel::kXZ_CHUNK_SIZE);
|
|
|
|
return;
|
|
}
|
|
|
|
unsigned char value = 0;
|
|
int count = 0;
|
|
|
|
for (Voxel::Grid::Region::iterator it = chunk.begin(); it != chunk.end(); ++it)
|
|
{
|
|
unsigned char temp = Voxel::Cell::convertToUnsignedCharForFile(it.getCellAtCurrentLocation());
|
|
|
|
if( value != temp && count != 0 )
|
|
{
|
|
s << value;
|
|
writeCountValue(s, count);
|
|
count = 0;
|
|
}
|
|
|
|
value = temp;
|
|
count++;
|
|
}
|
|
|
|
// encode the final value/count pair
|
|
RBXASSERT( count > 0 );
|
|
|
|
s << value;
|
|
writeCountValue(s, count);
|
|
|
|
// Next try to encode new material byte
|
|
value = 0;
|
|
count = 0;
|
|
|
|
for (Voxel::Grid::Region::iterator it = chunk.begin(); it != chunk.end(); ++it)
|
|
{
|
|
Voxel::CellMaterial temp = it.getMaterialAtCurrentLocation();
|
|
|
|
// 0xffff should fit entire chunk if necessary
|
|
if( value != temp && count != 0 )
|
|
{
|
|
s << value;
|
|
writeCountValue(s, count);
|
|
count = 0;
|
|
}
|
|
|
|
value = temp;
|
|
count++;
|
|
}
|
|
|
|
// encode the final value/count pair
|
|
RBXASSERT(count > 0);
|
|
|
|
s << value;
|
|
writeCountValue(s, count);
|
|
}
|
|
|
|
void MegaClusterInstance::incrementCellInChunkIndex( Vector3int16& index ) const
|
|
{
|
|
index.x++;
|
|
if( index.x >= kXZ_CHUNK_SIZE )
|
|
{
|
|
index.x = 0;
|
|
index.z++;
|
|
}
|
|
|
|
if( index.z >= kXZ_CHUNK_SIZE )
|
|
{
|
|
index.z = 0;
|
|
index.y++;
|
|
}
|
|
}
|
|
|
|
void MegaClusterInstance::setPackagedClusterGridV1(const std::string& clusterData)
|
|
{
|
|
if (clusterData.empty())
|
|
return;
|
|
|
|
initializeGridMega();
|
|
|
|
// iterate over all chunks and decode them
|
|
Vector3int16 numChunks = kLegacyChunkCount;
|
|
Vector3int16 chunkPosOffset = kLegacyChunkOffset;
|
|
|
|
StringReadBuffer stream(clusterData);
|
|
|
|
for (int i = 0; i < numChunks.x; ++i)
|
|
for (int j = 0; j < numChunks.y; ++j)
|
|
for (int k = 0; k < numChunks.z; ++k)
|
|
{
|
|
decodeChunkDataFromStreamV1_Deprecated(Vector3int16(i, j, k) - chunkPosOffset, stream);
|
|
if (stream.eof())
|
|
return;
|
|
}
|
|
}
|
|
|
|
void MegaClusterInstance::setPackagedClusterGridV2(const std::string& clusterData)
|
|
{
|
|
if (clusterData.empty())
|
|
return;
|
|
|
|
initializeGridMega();
|
|
|
|
// iterate over all chunks and decode them
|
|
Vector3int16 numChunks = kLegacyChunkCount;
|
|
Vector3int16 chunkPosOffset = kLegacyChunkOffset;
|
|
|
|
StringReadBuffer stream(clusterData);
|
|
|
|
for (int i = 0; i < numChunks.x; ++i)
|
|
for (int j = 0; j < numChunks.y; ++j)
|
|
for (int k = 0; k < numChunks.z; ++k)
|
|
{
|
|
decodeChunkDataFromStream(*voxelGrid, Vector3int16(i, j, k) - chunkPosOffset, stream);
|
|
|
|
if (stream.eof())
|
|
return;
|
|
}
|
|
}
|
|
|
|
void MegaClusterInstance::setPackagedClusterGridV3(const BinaryString& clusterData)
|
|
{
|
|
if (clusterData.value().empty())
|
|
return;
|
|
|
|
initializeGridMega();
|
|
|
|
deserializeGridV3(*voxelGrid, clusterData);
|
|
}
|
|
|
|
BinaryString MegaClusterInstance::getPackagedClusteredGridV3() const
|
|
{
|
|
if (voxelGrid)
|
|
return serializeGridV3(*voxelGrid);
|
|
else
|
|
return BinaryString();
|
|
}
|
|
|
|
void MegaClusterInstance::setPackagedSmoothGrid(const BinaryString& clusterData)
|
|
{
|
|
if (clusterData.value().empty())
|
|
return;
|
|
|
|
initializeGridSmooth();
|
|
|
|
smoothGrid->deserialize(clusterData.value());
|
|
}
|
|
|
|
BinaryString MegaClusterInstance::getPackagedSmoothGrid() const
|
|
{
|
|
if (smoothGrid)
|
|
{
|
|
std::string result;
|
|
smoothGrid->serialize(result);
|
|
return BinaryString(result);
|
|
}
|
|
else
|
|
return BinaryString();
|
|
}
|
|
|
|
BinaryString MegaClusterInstance::serializeGridV3(const Voxel::Grid& grid)
|
|
{
|
|
if (grid.getNonEmptyCellCount() == 0)
|
|
return BinaryString();
|
|
|
|
StringWriteBuffer encodedData;
|
|
|
|
// iterate over chunks
|
|
std::vector<SpatialRegion::Id> chunks = grid.getNonEmptyChunks();
|
|
|
|
for (size_t i = 0; i < chunks.size(); ++i)
|
|
{
|
|
SpatialRegion::Id id = chunks[i];
|
|
|
|
Region3int16 region = SpatialRegion::inclusiveVoxelExtentsOfRegion(id);
|
|
Voxel::Grid::Region chunk = grid.getRegion(region.getMinPos(), region.getMaxPos());
|
|
|
|
writeInt16(encodedData, id.value().x);
|
|
writeInt16(encodedData, id.value().y);
|
|
writeInt16(encodedData, id.value().z);
|
|
|
|
encodeChunkDataIntoStream(chunk, encodedData);
|
|
}
|
|
|
|
return BinaryString(encodedData.str());
|
|
}
|
|
|
|
void MegaClusterInstance::deserializeGridV3(Voxel::Grid& grid, const BinaryString& data)
|
|
{
|
|
if (data.value().empty())
|
|
return;
|
|
|
|
StringReadBuffer stream(data.value());
|
|
|
|
do
|
|
{
|
|
short chunkX = readInt16(stream);
|
|
short chunkY = readInt16(stream);
|
|
short chunkZ = readInt16(stream);
|
|
|
|
decodeChunkDataFromStream(grid, Vector3int16(chunkX, chunkY, chunkZ), stream);
|
|
}
|
|
while (!stream.eof());
|
|
}
|
|
|
|
void MegaClusterInstance::setCellInternalV1_Deprecated(
|
|
const Vector3int16& pos, const unsigned char& inputCell) {
|
|
Cell cell;
|
|
CellMaterial material = getCellMaterial_Deprecated(inputCell);
|
|
if (material == CELL_MATERIAL_Deprecated_Empty) {
|
|
cell = Constants::kUniqueEmptyCellRepresentation;
|
|
material = CELL_MATERIAL_Water;
|
|
} else {
|
|
Cell tmp = Cell::readUnsignedCharFromFile(inputCell);
|
|
cell.solid.setBlock(tmp.solid.getBlock());
|
|
cell.solid.setOrientation(tmp.solid.getOrientation());
|
|
}
|
|
|
|
voxelGrid->setCell(pos, cell, material);
|
|
}
|
|
|
|
shared_ptr<const Reflection::Tuple> MegaClusterInstance::getWaterCellScript(int x, int y, int z)
|
|
{
|
|
validateApi();
|
|
|
|
if (smoothGrid)
|
|
{
|
|
Voxel2::Cell cell = smoothGrid->getCell(x, y, z);
|
|
|
|
shared_ptr<Reflection::Tuple> result(new Reflection::Tuple(3));
|
|
|
|
result->values[0] = (cell.getMaterial() == Voxel2::Cell::Material_Water);
|
|
result->values[1] = WATER_CELL_FORCE_None;
|
|
result->values[2] = WATER_CELL_DIRECTION_NegX;
|
|
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
Cell waterCell = voxelGrid->getWaterCell(Vector3int16(x,y,z));
|
|
shared_ptr<Reflection::Tuple> result(new Reflection::Tuple(3));
|
|
|
|
if ( waterCell.isEmpty() ) {
|
|
result->values[0] = false;
|
|
result->values[1] = (WaterCellForce)0;
|
|
result->values[2] = (WaterCellDirection)0;
|
|
} else {
|
|
result->values[0] = true;
|
|
result->values[1] = waterCell.water.getForce();
|
|
result->values[2] = waterCell.water.getDirection();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
void MegaClusterInstance::setWaterCellScript( int x, int y, int z, WaterCellForce force, WaterCellDirection direction )
|
|
{
|
|
validateApi();
|
|
|
|
if (smoothGrid)
|
|
{
|
|
setCellsScript(Region3int16(Vector3int16(x, y, z), Vector3int16(x, y, z)), CELL_MATERIAL_Water, CELL_BLOCK_Solid, CELL_ORIENTATION_NegZ);
|
|
}
|
|
else
|
|
{
|
|
Vector3int16 pos = Vector3int16(x,y,z);
|
|
|
|
Cell cell;
|
|
cell.solid.setBlock(CELL_BLOCK_Empty);
|
|
cell.water.setForceAndDirection(force, direction);
|
|
voxelGrid->setCell(pos, cell, CELL_MATERIAL_Water);
|
|
}
|
|
}
|
|
|
|
void MegaClusterInstance::setCellScript(int x, int y, int z, CellMaterial material, CellBlock block, CellOrientation orientation )
|
|
{
|
|
validateApi();
|
|
|
|
if (smoothGrid)
|
|
{
|
|
setCellsScript(Region3int16(Vector3int16(x, y, z), Vector3int16(x, y, z)), material, block, orientation);
|
|
}
|
|
else
|
|
{
|
|
Vector3int16 pos = Vector3int16(x,y,z);
|
|
|
|
if (material == CELL_MATERIAL_Water) {
|
|
setWaterCellScript(x,y,z, (WaterCellForce)0, (WaterCellDirection)0);
|
|
return;
|
|
}
|
|
|
|
Cell cell;
|
|
|
|
if (material == CELL_MATERIAL_Deprecated_Empty || block == CELL_BLOCK_Empty) {
|
|
cell = Constants::kUniqueEmptyCellRepresentation;
|
|
material = CELL_MATERIAL_Water;
|
|
} else {
|
|
cell.solid.setBlock(block);
|
|
cell.solid.setOrientation(orientation);
|
|
}
|
|
|
|
voxelGrid->setCell(pos, cell, material);
|
|
}
|
|
}
|
|
|
|
bool MovePosition(Vector3int16& pos, FaceDirection dir)
|
|
{
|
|
switch(dir)
|
|
{
|
|
case PlusX:
|
|
pos.x += 1;
|
|
return true;
|
|
case PlusZ:
|
|
pos.z += 1;
|
|
return true;
|
|
case MinusX:
|
|
if(pos.x == 0)
|
|
return false;
|
|
pos.x -= 1;
|
|
return true;
|
|
case MinusZ:
|
|
if(pos.z == 0)
|
|
return false;
|
|
pos.z -= 1;
|
|
return true;
|
|
case PlusY:
|
|
pos.y += 1;
|
|
return true;
|
|
case MinusY:
|
|
if(pos.y == 0)
|
|
return false;
|
|
pos.y -= 1;
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Vector3 MegaClusterInstance::worldToCellPreferSolidScript(Vector3 worldPos )
|
|
{
|
|
Vector3int16 result = worldToCellWithPreference( worldPos, true );
|
|
return Vector3( result.x, result.y, result.z );
|
|
}
|
|
|
|
Vector3 MegaClusterInstance::worldToCellPreferEmptyScript(Vector3 worldPos )
|
|
{
|
|
Vector3int16 result = worldToCellWithPreference( worldPos, false );
|
|
return Vector3( result.x, result.y, result.z );
|
|
}
|
|
|
|
Vector3 MegaClusterInstance::worldToCellScript(Vector3 worldPos )
|
|
{
|
|
Vector3int16 result = Voxel::worldToCell_floor(worldPos);
|
|
return Vector3( result.x, result.y, result.z );
|
|
}
|
|
|
|
bool MegaClusterInstance::isCellIdealForPreference(
|
|
const Vector3int16& location, const Cell cell,
|
|
bool preferSolid) const {
|
|
Cell tmpCell = voxelGrid->getCell(location);
|
|
return
|
|
((preferSolid && cell.solid.getBlock() != CELL_BLOCK_Empty) ||
|
|
(!preferSolid && tmpCell.isEmpty()));
|
|
}
|
|
|
|
bool MegaClusterInstance::isCellAcceptablePreferenceWaterAlternative(
|
|
const Vector3int16& location, const Cell cell,
|
|
bool preferSolid) const {
|
|
Cell waterCell = voxelGrid->getWaterCell(location);
|
|
bool hasWater = !waterCell.isEmpty();
|
|
return
|
|
((preferSolid && hasWater) ||
|
|
(!preferSolid && hasWater && cell.solid.getBlock() == CELL_BLOCK_Empty));
|
|
}
|
|
|
|
struct WorldToCellCandidate
|
|
{
|
|
Vector3int16 pos;
|
|
float distance;
|
|
|
|
WorldToCellCandidate()
|
|
{
|
|
distance = 0;
|
|
}
|
|
|
|
WorldToCellCandidate(const Vector3& worldPos, int ox, int oy, int oz)
|
|
{
|
|
Vector3int16 center = Voxel::worldToCell_floor(worldPos);
|
|
|
|
pos = center + Vector3int16(ox, oy, oz);
|
|
distance = (worldPos - Voxel::cellToWorld_center(pos)).squaredLength();
|
|
}
|
|
|
|
bool operator<(const WorldToCellCandidate& other) const
|
|
{
|
|
return distance < other.distance;
|
|
}
|
|
|
|
static bool isValid(const Voxel2::Cell& cell, bool preferSolid)
|
|
{
|
|
return (cell.getMaterial() != Voxel2::Cell::Material_Air) == preferSolid;
|
|
}
|
|
};
|
|
|
|
Vector3int16 MegaClusterInstance::worldToCellWithPreference(const Vector3& worldPos, bool preferSolid)
|
|
{
|
|
validateApi();
|
|
|
|
if (smoothGrid)
|
|
{
|
|
Vector3int16 center = Voxel::worldToCell_floor(worldPos);
|
|
|
|
if (WorldToCellCandidate::isValid(smoothGrid->getCell(center.x, center.y, center.z), preferSolid))
|
|
return center;
|
|
|
|
std::vector<WorldToCellCandidate> candidates;
|
|
|
|
candidates.reserve(27);
|
|
|
|
for (int x = -1; x <= 1; ++x)
|
|
for (int y = -1; y <= 1; ++y)
|
|
for (int z = -1; z <= 1; ++z)
|
|
{
|
|
candidates.push_back(WorldToCellCandidate(worldPos, x, y, z));
|
|
}
|
|
|
|
std::sort(candidates.begin(), candidates.end());
|
|
|
|
for (auto& c: candidates)
|
|
if (WorldToCellCandidate::isValid(smoothGrid->getCell(c.pos.x, c.pos.y, c.pos.z), preferSolid))
|
|
return c.pos;
|
|
|
|
return center;
|
|
}
|
|
else
|
|
{
|
|
Vector3int16 firstApproximation = Voxel::worldToCell_floor(worldPos);
|
|
Cell tmpCell = voxelGrid->getCell(firstApproximation);
|
|
|
|
if (isCellIdealForPreference(firstApproximation, tmpCell, preferSolid)) {
|
|
return firstApproximation;
|
|
}
|
|
|
|
bool foundAcceptableWaterCell = false;
|
|
Vector3int16 acceptableWaterCell;
|
|
|
|
const float searchEpsilon = 0.00048828125f; // 1 / 2048
|
|
for (int y = -1; y < 2; y += 2) {
|
|
for (int z = -1; z < 2; z += 2) {
|
|
for (int x = -1; x < 2; x += 2) {
|
|
Vector3int16 test = Voxel::worldToCell_floor(worldPos +
|
|
Vector3(searchEpsilon * x,
|
|
searchEpsilon * y,
|
|
searchEpsilon * z));
|
|
tmpCell = voxelGrid->getCell(test);
|
|
if (isCellIdealForPreference(test, tmpCell, preferSolid)) {
|
|
return test;
|
|
}
|
|
if (isCellAcceptablePreferenceWaterAlternative(test, tmpCell, preferSolid)) {
|
|
foundAcceptableWaterCell = true;
|
|
acceptableWaterCell = test;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (foundAcceptableWaterCell) {
|
|
return acceptableWaterCell;
|
|
}
|
|
|
|
Vector3int16 bestCell = firstApproximation;
|
|
bool foundTotalMatch = false;
|
|
float bestDistance = 10;
|
|
foundAcceptableWaterCell = false;
|
|
float bestWaterDistance = 10;
|
|
|
|
// Ok, now we need to search for empty around it
|
|
for(unsigned i = 0; i < Invalid; i++)
|
|
{
|
|
Vector3int16 npos = firstApproximation;
|
|
if(!MovePosition(npos, (FaceDirection)i))
|
|
continue;
|
|
|
|
float distance = (cellToWorldExtents(npos).center() - worldPos).squaredLength();
|
|
tmpCell = voxelGrid->getCell(npos);
|
|
if (isCellIdealForPreference(npos, tmpCell, preferSolid) &&
|
|
distance < bestDistance) {
|
|
foundTotalMatch = true;
|
|
bestCell = npos;
|
|
bestDistance = distance;
|
|
}
|
|
if (isCellAcceptablePreferenceWaterAlternative(npos, tmpCell,
|
|
preferSolid) &&
|
|
distance < bestWaterDistance) {
|
|
foundAcceptableWaterCell = true;
|
|
acceptableWaterCell = npos;
|
|
bestWaterDistance = distance;
|
|
}
|
|
}
|
|
|
|
if (!foundTotalMatch && foundAcceptableWaterCell) {
|
|
return acceptableWaterCell;
|
|
} else {
|
|
return bestCell;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline Vector3int16 floorToInt(const Vector3& vec)
|
|
{
|
|
return Vector3int16((G3D::int16)floor(vec.x), (G3D::int16)floor(vec.y), (G3D::int16)floor(vec.z));
|
|
}
|
|
|
|
Vector3 MegaClusterInstance::cellCornerToWorld( const Vector3int16& v ) const
|
|
{
|
|
return Voxel::cellToWorld_smallestCorner(v);
|
|
}
|
|
|
|
Vector3 MegaClusterInstance::cellCenterToWorldScript( int x, int y, int z )
|
|
{
|
|
return cellCornerToWorldScript( x, y, z ) + Vector3(2,2,2);
|
|
}
|
|
|
|
void MegaClusterInstance::clear()
|
|
{
|
|
validateApi();
|
|
|
|
if (smoothGrid)
|
|
{
|
|
std::vector<Voxel2::Region> regions = smoothGrid->getNonEmptyRegions();
|
|
|
|
for (size_t i = 0; i < regions.size(); ++i)
|
|
{
|
|
Vector3int32 size = regions[i].size();
|
|
|
|
Voxel2::Box box(size.x, size.y, size.z);
|
|
|
|
smoothGrid->write(regions[i], box);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::vector<SpatialRegion::Id> chunks = voxelGrid->getNonEmptyChunks();
|
|
|
|
for (size_t i = 0; i < chunks.size(); ++i)
|
|
{
|
|
SpatialRegion::Id id = chunks[i];
|
|
Region3int16 region = SpatialRegion::inclusiveVoxelExtentsOfRegion(id);
|
|
|
|
Vector3int16 min = region.getMinPos();
|
|
Vector3int16 max = region.getMaxPos();
|
|
|
|
for (int y = min.y; y <= max.y; ++y)
|
|
for (int z = min.z; z <= max.z; ++z)
|
|
for (int x = min.x; x <= max.x; ++x)
|
|
voxelGrid->setCell(Vector3int16(x,y,z), Constants::kUniqueEmptyCellRepresentation, CELL_MATERIAL_Water);
|
|
}
|
|
}
|
|
}
|
|
|
|
int MegaClusterInstance::countCellsScript()
|
|
{
|
|
return getNonEmptyCellCount();
|
|
}
|
|
|
|
const Region3int16 MegaClusterInstance::getMaxExtents() const
|
|
{
|
|
return Voxel::getTerrainExtentsInCells();
|
|
}
|
|
|
|
void MegaClusterInstance::render3dSelect(Adorn* adorn, SelectState selectState)
|
|
{
|
|
// do nothing. we don't want to render the selection box
|
|
}
|
|
|
|
Extents MegaClusterInstance::cellToWorldExtents( const Vector3int16& cellpos )
|
|
{
|
|
return Extents::fromCenterRadius(Voxel::cellToWorld_center(cellpos), kHALF_CELL);
|
|
}
|
|
|
|
Extents MegaClusterInstance::computeExtentsWorld() const
|
|
{
|
|
Extents result;
|
|
|
|
if (smoothGrid)
|
|
{
|
|
std::vector<Voxel2::Region> regions = smoothGrid->getNonEmptyRegions();
|
|
|
|
for (size_t i = 0; i < regions.size(); ++i)
|
|
{
|
|
result.expandToContain(Voxel::cellSpaceToWorldSpace(regions[i].begin().toVector3()));
|
|
result.expandToContain(Voxel::cellSpaceToWorldSpace(regions[i].end().toVector3()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::vector<SpatialRegion::Id> chunks = voxelGrid->getNonEmptyChunks();
|
|
|
|
for (size_t i = 0; i < chunks.size(); ++i)
|
|
{
|
|
result.expandToContain(SpatialRegion::smallestCornerOfRegionInGlobalCoordStuds(chunks[i]).toVector3());
|
|
result.expandToContain(SpatialRegion::largestCornerOfRegionInGlobalCoordStuds(chunks[i]).toVector3());
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
shared_ptr<Instance> MegaClusterInstance::copyRegion(Region3int16 region)
|
|
{
|
|
validateApi();
|
|
|
|
if (smoothGrid)
|
|
return Creatable<Instance>::create<TerrainRegion>(smoothGrid.get(), region);
|
|
else
|
|
return Creatable<Instance>::create<TerrainRegion>(voxelGrid.get(), region);
|
|
}
|
|
|
|
void MegaClusterInstance::pasteRegion(shared_ptr<Instance> region, Vector3int16 corner, bool pasteEmptyCells)
|
|
{
|
|
validateApi();
|
|
|
|
if (TerrainRegion* terrainRegion = fastDynamicCast<TerrainRegion>(region.get()))
|
|
{
|
|
if (smoothGrid)
|
|
terrainRegion->copyTo(*smoothGrid, corner, pasteEmptyCells);
|
|
else
|
|
terrainRegion->copyTo(*voxelGrid, corner, pasteEmptyCells);
|
|
}
|
|
else
|
|
throw std::runtime_error("region has to be a TerrainRegion");
|
|
}
|
|
|
|
void MegaClusterInstance::reloadMaterialTable()
|
|
{
|
|
if (!smoothGrid)
|
|
return;
|
|
|
|
materialTable.reset(new Voxel2::MaterialTable(ContentProvider::findAsset(kSmoothTerrainMaterials), Voxel2::Cell::Material_Max + 1));
|
|
|
|
Primitive* myPrim = getPartPrimitive();
|
|
|
|
DenseHashSet<Primitive*> primitives(NULL);
|
|
DenseHashSet<Contact*> contacts(NULL);
|
|
|
|
for (int i = 0; i < myPrim->getNumContacts(); ++i)
|
|
{
|
|
primitives.insert(myPrim->getContactOther(i));
|
|
contacts.insert(myPrim->getContact(i));
|
|
}
|
|
|
|
for (DenseHashSet<Contact*>::const_iterator it = contacts.begin(); it != contacts.end(); ++it)
|
|
myPrim->getWorld()->destroyContact(*it);
|
|
|
|
static_cast<SmoothClusterGeometry*>(myPrim->getGeometry())->updateAllChunks();
|
|
|
|
for (DenseHashSet<Primitive*>::const_iterator it = primitives.begin(); it != primitives.end(); ++it)
|
|
myPrim->getWorld()->getContactManager()->checkTerrainContact(*it);
|
|
}
|
|
|
|
Voxel::Grid* MegaClusterInstance::getVoxelGrid()
|
|
{
|
|
RBXASSERT(voxelGrid);
|
|
|
|
return voxelGrid.get();
|
|
}
|
|
|
|
Voxel2::Grid* MegaClusterInstance::getSmoothGrid()
|
|
{
|
|
RBXASSERT(smoothGrid);
|
|
|
|
return smoothGrid.get();
|
|
}
|
|
|
|
bool MegaClusterInstance::isSmooth() const
|
|
{
|
|
return !!smoothGrid;
|
|
}
|
|
|
|
bool MegaClusterInstance::isInitialized() const
|
|
{
|
|
return voxelGrid || smoothGrid;
|
|
}
|
|
|
|
void MegaClusterInstance::setSmoothReplicate(int value)
|
|
{
|
|
if (smoothReplicate != value)
|
|
{
|
|
smoothReplicate = value;
|
|
raisePropertyChanged(desc_SmoothReplicate);
|
|
}
|
|
}
|
|
|
|
unsigned int MegaClusterInstance::getNonEmptyCellCount() const
|
|
{
|
|
if (smoothGrid)
|
|
return smoothGrid->getNonEmptyCellCountApprox();
|
|
|
|
if (voxelGrid)
|
|
return voxelGrid->getNonEmptyCellCount();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void MegaClusterInstance::convertToSmooth()
|
|
{
|
|
validateApi();
|
|
|
|
if (smoothGrid)
|
|
throw std::runtime_error("Terrain is already smooth!");
|
|
|
|
if (Network::Players::getGameMode(this) != Network::EDIT ||
|
|
Network::Players::isCloudEdit(this))
|
|
{
|
|
throw std::runtime_error("Terrain can only be converted in edit mode");
|
|
}
|
|
|
|
// Unparent to get ourselves disconnected from CHS/etc
|
|
Instance* parent = getParent();
|
|
|
|
setLockedParent(NULL);
|
|
|
|
// Stash old grid somewhere and reinitialize as smooth grid
|
|
scoped_ptr<Voxel::Grid> oldGrid;
|
|
voxelGrid.swap(oldGrid);
|
|
|
|
initializeGridSmooth();
|
|
|
|
// Transfer data from voxel grid into smooth grid
|
|
Voxel2::Conversion::convertToSmooth(*oldGrid, *smoothGrid);
|
|
|
|
// Reparent ourselves back
|
|
setLockedParent(parent);
|
|
|
|
// Set a waypoint so that further editing operations are undone up until this point
|
|
ChangeHistoryService* chs = ServiceProvider::find<ChangeHistoryService>(parent);
|
|
|
|
if (chs)
|
|
chs->resetBaseWaypoint();
|
|
|
|
raisePropertyChanged(prop_IsSmooth);
|
|
}
|
|
|
|
void MegaClusterInstance::initializeGridMega()
|
|
{
|
|
RBXASSERT(!getParent());
|
|
RBXASSERT(!voxelGrid && !smoothGrid);
|
|
|
|
voxelGrid.reset(new Voxel::Grid());
|
|
getPartPrimitive()->setGeometryType(Geometry::GEOMETRY_MEGACLUSTER);
|
|
|
|
smoothReplicate = 0;
|
|
}
|
|
|
|
void MegaClusterInstance::initializeGridSmooth()
|
|
{
|
|
RBXASSERT(!getParent());
|
|
RBXASSERT(!voxelGrid && !smoothGrid);
|
|
|
|
smoothGrid.reset(new Voxel2::Grid());
|
|
getPartPrimitive()->setGeometryType(Geometry::GEOMETRY_SMOOTHCLUSTER);
|
|
|
|
smoothReplicate = 1;
|
|
}
|
|
|
|
static Voxel2::Region getRegionAtResolution(const Region3& region, float resolution)
|
|
{
|
|
if (resolution != 4)
|
|
throw std::runtime_error("Resolution has to be 4");
|
|
|
|
Vector3 min = region.minPos() / resolution;
|
|
Vector3 max = region.maxPos() / resolution;
|
|
|
|
Vector3int32 imin(min.x, min.y, min.z);
|
|
Vector3int32 imax(max.x, max.y, max.z);
|
|
|
|
if (imin.toVector3() != min || imax.toVector3() != max)
|
|
throw std::runtime_error("Region has to be aligned to the grid (use Region3:ExpandToGrid)");
|
|
|
|
if (imin.x >= imax.x || imin.y >= imax.y || imin.z >= imax.z)
|
|
throw std::runtime_error("Region cannot be empty");
|
|
|
|
Voxel2::Region result(imin, imax);
|
|
|
|
// One cell is represented as two Lua values, resulting in ~32b/cell overhead
|
|
// 4M voxels would take 128Mb
|
|
if (result.getChunkCount(0) > FInt::SmoothTerrainMaxLuaRegion)
|
|
throw std::runtime_error("Region is too large");
|
|
|
|
return result;
|
|
}
|
|
|
|
static Voxel2::Region getRegionFromExtents(const Vector3& min, const Vector3& max)
|
|
{
|
|
Voxel2::Region result = Voxel2::Region::fromExtents(min, max);
|
|
|
|
if (result.empty())
|
|
throw std::runtime_error("Extents cannot be empty");
|
|
|
|
// 64M voxels would take 128Mb
|
|
if (result.getChunkCount(0) > FInt::SmoothTerrainMaxCppRegion)
|
|
throw std::runtime_error("Extents are too large");
|
|
|
|
return result;
|
|
}
|
|
|
|
template <typename K, typename V, size_t N> struct FixedSizeCache
|
|
{
|
|
K keys[N];
|
|
V values[N];
|
|
size_t next;
|
|
|
|
FixedSizeCache(K invalid): next(0)
|
|
{
|
|
for (size_t i = 0; i < N; ++i)
|
|
keys[i] = invalid;
|
|
}
|
|
|
|
std::pair<bool, size_t> insert(K key)
|
|
{
|
|
for (size_t i = 0; i < N; ++i)
|
|
if (keys[i] == key)
|
|
return std::make_pair(false, i);
|
|
|
|
size_t index = next;
|
|
|
|
keys[index] = key;
|
|
|
|
next = (next + 1) % N;
|
|
|
|
return std::make_pair(true, index);
|
|
}
|
|
|
|
size_t size() const
|
|
{
|
|
return N;
|
|
}
|
|
};
|
|
|
|
int MegaClusterInstance::readVoxels(lua_State* L)
|
|
{
|
|
validateApiSmooth();
|
|
|
|
Lua::LuaArguments args(L, 1);
|
|
|
|
Region3 region;
|
|
if (!args.getRegion3(1, region))
|
|
throw std::runtime_error("Argument 1 missing or nil");
|
|
|
|
double resolution;
|
|
if (!args.getDouble(2, resolution))
|
|
throw std::runtime_error("Argument 2 missing or nil");
|
|
|
|
Voxel2::Region rr = getRegionAtResolution(region, resolution);
|
|
const Voxel2::Box box = smoothGrid->read(rr);
|
|
|
|
const Reflection::EnumDesc<PartMaterial>& matDesc = Reflection::EnumDesc<PartMaterial>::singleton();
|
|
|
|
FixedSizeCache<unsigned char, int, 8> materialCache(Voxel2::Cell::Material_Max + 1);
|
|
|
|
int materialCacheStack = lua_gettop(L);
|
|
|
|
for (size_t i = 0; i < materialCache.size(); ++i)
|
|
lua_pushnil(L);
|
|
|
|
lua_createtable(L, box.getSizeX(), 0); // mat
|
|
lua_createtable(L, box.getSizeX(), 0); // occ
|
|
|
|
for (int x = 0; x < box.getSizeX(); ++x)
|
|
{
|
|
lua_createtable(L, box.getSizeY(), 0); // matX
|
|
lua_createtable(L, box.getSizeY(), 0); // occX
|
|
|
|
for (int y = 0; y < box.getSizeY(); ++y)
|
|
{
|
|
lua_createtable(L, box.getSizeZ(), 0); // matY
|
|
lua_createtable(L, box.getSizeZ(), 0); // occY
|
|
|
|
for (int z = 0; z < box.getSizeZ(); ++z)
|
|
{
|
|
const Voxel2::Cell& cell = box.get(x, y, z);
|
|
|
|
std::pair<bool, size_t> materialCacheIndex = materialCache.insert(cell.getMaterial());
|
|
|
|
// make sure material cache is up to date
|
|
if (materialCacheIndex.first)
|
|
{
|
|
PartMaterial material = Voxel2::Conversion::getMaterialFromVoxelMaterial(cell.getMaterial());
|
|
|
|
Lua::EnumItem::push(L, matDesc.convertToItem(material));
|
|
lua_replace(L, materialCacheStack + materialCacheIndex.second);
|
|
}
|
|
|
|
// push material
|
|
lua_pushvalue(L, materialCacheStack + materialCacheIndex.second);
|
|
|
|
// push occupancy
|
|
if (cell.getMaterial() == Voxel2::Cell::Material_Air)
|
|
{
|
|
lua_pushnumber(L, 0);
|
|
}
|
|
else
|
|
{
|
|
float occScale = 1.f / (Voxel2::Cell::Occupancy_Max + 1);
|
|
lua_pushnumber(L, (cell.getOccupancy() + 1) * occScale);
|
|
}
|
|
|
|
lua_rawseti(L, -3, z + 1);
|
|
lua_rawseti(L, -3, z + 1);
|
|
}
|
|
|
|
lua_rawseti(L, -3, y + 1);
|
|
lua_rawseti(L, -3, y + 1);
|
|
}
|
|
|
|
lua_rawseti(L, -3, x + 1);
|
|
lua_rawseti(L, -3, x + 1);
|
|
}
|
|
|
|
lua_pushstring(L, "Size");
|
|
Lua::Vector3Bridge::pushVector3(L, box.getSize().toVector3());
|
|
|
|
lua_rawset(L, -4);
|
|
|
|
lua_pushstring(L, "Size");
|
|
Lua::Vector3Bridge::pushVector3(L, box.getSize().toVector3());
|
|
|
|
lua_rawset(L, -3);
|
|
|
|
return 2;
|
|
}
|
|
|
|
static bool isArray(lua_State* L, int index, int size)
|
|
{
|
|
return lua_istable(L, index) && lua_objlen(L, index) == size;
|
|
}
|
|
|
|
int MegaClusterInstance::writeVoxels(lua_State* L)
|
|
{
|
|
validateApiSmooth();
|
|
|
|
Lua::LuaArguments args(L, 1);
|
|
|
|
Region3 region;
|
|
if (!args.getRegion3(1, region))
|
|
throw std::runtime_error("Argument 1 missing or nil");
|
|
|
|
double resolution;
|
|
if (!args.getDouble(2, resolution))
|
|
throw std::runtime_error("Argument 2 missing or nil");
|
|
|
|
if (4 > lua_gettop(L))
|
|
throw std::runtime_error("Argument 3 missing or nil");
|
|
|
|
if (5 > lua_gettop(L))
|
|
throw std::runtime_error("Argument 4 missing or nil");
|
|
|
|
Voxel2::Region rr = getRegionAtResolution(region, resolution);
|
|
Voxel2::Box box(rr.size().x, rr.size().y, rr.size().z);
|
|
|
|
const Reflection::EnumDesc<PartMaterial>& matDesc = Reflection::EnumDesc<PartMaterial>::singleton();
|
|
|
|
FixedSizeCache<void*, unsigned char, 8> materialCache(NULL);
|
|
|
|
if (!isArray(L, 4, box.getSizeX()))
|
|
throw RBX::runtime_error("Bad argument materials to 'WriteVoxels' (%dx%dx%d array expected)", box.getSizeX(), box.getSizeY(), box.getSizeZ());
|
|
|
|
if (!isArray(L, 5, box.getSizeX()))
|
|
throw RBX::runtime_error("Bad argument occupancy to 'WriteVoxels' (%dx%dx%d array expected)", box.getSizeX(), box.getSizeY(), box.getSizeZ());
|
|
|
|
for (int x = 0; x < box.getSizeX(); ++x)
|
|
{
|
|
lua_rawgeti(L, 4, x + 1); // matX
|
|
lua_rawgeti(L, 5, x + 1); // occX
|
|
|
|
if (!isArray(L, -2, box.getSizeY()))
|
|
throw RBX::runtime_error("Bad argument materials[%d] to 'WriteVoxels' (%dx%d array expected)", x + 1, box.getSizeY(), box.getSizeZ());
|
|
|
|
if (!isArray(L, -1, box.getSizeY()))
|
|
throw RBX::runtime_error("Bad argument occupancy[%d] to 'WriteVoxels' (%dx%d array expected)", x + 1, box.getSizeY(), box.getSizeZ());
|
|
|
|
for (int y = 0; y < box.getSizeY(); ++y)
|
|
{
|
|
lua_rawgeti(L, -2, y + 1); // matY
|
|
lua_rawgeti(L, -2, y + 1); // occY
|
|
|
|
if (!isArray(L, -2, box.getSizeZ()))
|
|
throw RBX::runtime_error("Bad argument materials[%d][%d] to 'WriteVoxels' (%d-element array expected)", x + 1, y + 1, box.getSizeZ());
|
|
|
|
if (!isArray(L, -1, box.getSizeZ()))
|
|
throw RBX::runtime_error("Bad argument occupancy[%d][%d] to 'WriteVoxels' (%d-element array expected)", x + 1, y + 1, box.getSizeZ());
|
|
|
|
for (int z = 0; z < box.getSizeZ(); ++z)
|
|
{
|
|
lua_rawgeti(L, -2, z + 1); // matZ
|
|
lua_rawgeti(L, -2, z + 1); // occZ
|
|
|
|
void* mat = lua_touserdata(L, -2);
|
|
if (!mat)
|
|
luaL_error(L, "Bad argument materials[%d][%d][%d] to 'WriteVoxels' (Enum.Material expected, got %s)", x + 1, y + 1, z + 1, luaL_typename(L, -2));
|
|
|
|
std::pair<bool, size_t> materialCacheIndex = materialCache.insert(mat);
|
|
|
|
if (materialCacheIndex.first)
|
|
{
|
|
const Reflection::EnumDescriptor::Item* matItemDesc = NULL;
|
|
|
|
if (!Lua::EnumItem::getItem(L, -2, matItemDesc))
|
|
luaL_error(L, "Bad argument materials[%d][%d][%d] to 'WriteVoxels' (Enum.Material expected, got %s)", x + 1, y + 1, z + 1, luaL_typename(L, -2));
|
|
|
|
if (&matItemDesc->owner != &matDesc)
|
|
luaL_error(L, "Bad argument materials[%d][%d][%d] to 'WriteVoxels' (Enum.Material expected, got %s)", x + 1, y + 1, z + 1, matItemDesc->owner.name.c_str());
|
|
|
|
unsigned char material = Voxel2::Conversion::getVoxelMaterialFromMaterial(static_cast<PartMaterial>(matItemDesc->value));
|
|
|
|
materialCache.values[materialCacheIndex.second] = material;
|
|
}
|
|
|
|
unsigned char material = materialCache.values[materialCacheIndex.second];
|
|
|
|
if (!lua_isnumber(L, -1))
|
|
luaL_error(L, "Bad argument occupancy[%d][%d][%d] to 'WriteVoxels' (number expected, got %s)", x + 1, y + 1, z + 1, luaL_typename(L, -1));
|
|
|
|
float occ = lua_tonumber(L, -1);
|
|
|
|
if (material == Voxel2::Cell::Material_Air || Math::isNanInf(occ) || occ <= 0)
|
|
{
|
|
box.set(x, y, z, Voxel2::Cell());
|
|
}
|
|
else
|
|
{
|
|
// We round occupancy to nearest integer value, keeping in mind that 1/256.f means 0 in Cell
|
|
float occInt = occ * (Voxel2::Cell::Occupancy_Max + 1) - 0.5f;
|
|
|
|
box.set(x, y, z, Voxel2::Cell(material, std::min(std::max(static_cast<int>(occInt), 0), static_cast<int>(Voxel2::Cell::Occupancy_Max))));
|
|
}
|
|
|
|
lua_pop(L, 2);
|
|
}
|
|
|
|
lua_pop(L, 2);
|
|
}
|
|
|
|
lua_pop(L, 2);
|
|
}
|
|
|
|
smoothGrid->write(rr, box);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void MegaClusterInstance::fillRegion(Region3 region, float resolution, PartMaterial material)
|
|
{
|
|
validateApiSmooth();
|
|
|
|
Voxel2::Region rr = getRegionAtResolution(region, resolution);
|
|
Vector3int32 rsize = rr.size();
|
|
|
|
Voxel2::Box box(rsize.x, rsize.y, rsize.z);
|
|
|
|
unsigned char voxelMaterial = Voxel2::Conversion::getVoxelMaterialFromMaterial(material);
|
|
|
|
if (voxelMaterial != Voxel2::Cell::Material_Air)
|
|
{
|
|
Voxel2::Cell cell(voxelMaterial, Voxel2::Cell::Occupancy_Max);
|
|
|
|
for (int y = 0; y < rsize.y; ++y)
|
|
for (int z = 0; z < rsize.z; ++z)
|
|
{
|
|
Voxel2::Cell* row = box.writeRow(0, y, z);
|
|
|
|
for (int x = 0; x < rsize.x; ++x)
|
|
row[x] = cell;
|
|
}
|
|
}
|
|
|
|
smoothGrid->write(rr, box);
|
|
}
|
|
|
|
static void fillCell(Voxel2::Cell& cell, unsigned char material, int occ)
|
|
{
|
|
if (material == Voxel2::Cell::Material_Air)
|
|
{
|
|
if (cell.getOccupancy() <= occ)
|
|
cell = Voxel2::Cell();
|
|
else
|
|
{
|
|
int aocc = Voxel2::Cell::Occupancy_Max - occ;
|
|
|
|
if (cell.getOccupancy() > aocc)
|
|
cell = Voxel2::Cell(cell.getMaterial(), aocc);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (cell.getOccupancy() <= occ && occ != 0)
|
|
cell = Voxel2::Cell(material, occ);
|
|
}
|
|
}
|
|
|
|
void MegaClusterInstance::fillBlock(CoordinateFrame cframe, Vector3 size, PartMaterial material)
|
|
{
|
|
validateApiSmooth();
|
|
|
|
AABox aabb = cframe.AABBtoWorldSpace(AABox(-size / 2, size / 2));
|
|
|
|
Voxel2::Region rr = getRegionFromExtents(aabb.low(), aabb.high());
|
|
Vector3int32 rsize = rr.size();
|
|
|
|
Voxel2::Box box = smoothGrid->read(rr);
|
|
|
|
unsigned char voxelMaterial = Voxel2::Conversion::getVoxelMaterialFromMaterial(material);
|
|
|
|
if (voxelMaterial == Voxel2::Cell::Material_Air && box.isEmpty())
|
|
return;
|
|
|
|
// Since we only care about the size if it's less than one cell, we clamp this to make the calculations below faster.
|
|
Vector3 sizeCellsClamped = (size / Voxel::kCELL_SIZE).min(Vector3(1.0f, 1.0f, 1.0f));
|
|
Vector3 sizeCellsHalfOffset = size * (0.5f / Voxel::kCELL_SIZE) + Vector3(0.5f, 0.5f, 0.5f);
|
|
|
|
Vector3 localCorner = cframe.pointToObjectSpace(Vector3(rr.begin().x + 0.5f, rr.begin().y + 0.5f, rr.begin().z + 0.5f) * Voxel::kCELL_SIZE) / Voxel::kCELL_SIZE;
|
|
|
|
Vector3 localAxisX = Vector3(cframe.rotation[0][0], cframe.rotation[0][1], cframe.rotation[0][2]);
|
|
Vector3 localAxisY = Vector3(cframe.rotation[1][0], cframe.rotation[1][1], cframe.rotation[1][2]);
|
|
Vector3 localAxisZ = Vector3(cframe.rotation[2][0], cframe.rotation[2][1], cframe.rotation[2][2]);
|
|
|
|
Vector3 localY = localCorner;
|
|
|
|
for (int y = 0; y < rsize.y; ++y)
|
|
{
|
|
Vector3 localZ = localY;
|
|
|
|
for (int z = 0; z < rsize.z; ++z)
|
|
{
|
|
Vector3 localX = localZ;
|
|
|
|
Voxel2::Cell* row = box.writeRow(0, y, z);
|
|
|
|
for (int x = 0; x < rsize.x; ++x)
|
|
{
|
|
float distX = sizeCellsHalfOffset.x - fabsf(localX.x);
|
|
float distY = sizeCellsHalfOffset.y - fabsf(localX.y);
|
|
float distZ = sizeCellsHalfOffset.z - fabsf(localX.z);
|
|
|
|
// if distance <-0.5, voxel is completely inside the part
|
|
// if distance >+0.5, voxel is completely outside the part
|
|
// note that we include 0.5 offset in the distXYZ calculation
|
|
float factorX = std::max(0.f, std::min(distX, sizeCellsClamped.x));
|
|
float factorY = std::max(0.f, std::min(distY, sizeCellsClamped.y));
|
|
float factorZ = std::max(0.f, std::min(distZ, sizeCellsClamped.z));
|
|
|
|
// occupancy is a linear metric, not volumetric
|
|
// it is more correct to use powf(fx*fy*fz, 1/3.f), but it is slower
|
|
float factor = std::min(factorX, std::min(factorY, factorZ));
|
|
|
|
int occ = static_cast<int>(factor * Voxel2::Cell::Occupancy_Max + 0.5f);
|
|
|
|
fillCell(row[x], voxelMaterial, occ);
|
|
|
|
localX += localAxisX;
|
|
}
|
|
|
|
localZ += localAxisZ;
|
|
}
|
|
|
|
localY += localAxisY;
|
|
}
|
|
|
|
smoothGrid->write(rr, box);
|
|
}
|
|
|
|
void MegaClusterInstance::fillBall(Vector3 center, float radius, PartMaterial material)
|
|
{
|
|
fillBallInternal(center, radius, material, /* skipWater= */ false);
|
|
}
|
|
|
|
void MegaClusterInstance::fillBallInternal(Vector3 center, float radius, PartMaterial material, bool skipWater)
|
|
{
|
|
validateApiSmooth();
|
|
|
|
Voxel2::Region rr = getRegionFromExtents(center - Vector3(radius), center + Vector3(radius));
|
|
Vector3int32 rsize = rr.size();
|
|
|
|
Voxel2::Box box = smoothGrid->read(rr);
|
|
|
|
unsigned char voxelMaterial = Voxel2::Conversion::getVoxelMaterialFromMaterial(material);
|
|
|
|
if (voxelMaterial == Voxel2::Cell::Material_Air && box.isEmpty())
|
|
return;
|
|
|
|
unsigned char skipMaterial = skipWater ? Voxel2::Cell::Material_Water : Voxel2::Cell::Material_Max + 1;
|
|
|
|
Vector3 centerv = center / Voxel::kCELL_SIZE;
|
|
float vradius = radius / Voxel::kCELL_SIZE;
|
|
|
|
for (int y = 0; y < rsize.y; ++y)
|
|
for (int z = 0; z < rsize.z; ++z)
|
|
{
|
|
Vector3 diff = Vector3(rr.begin().x, y + rr.begin().y, z + rr.begin().z) + Vector3(0.5) - centerv;
|
|
|
|
Voxel2::Cell* row = box.writeRow(0, y, z);
|
|
|
|
for (int x = 0; x < rsize.x; ++x, diff.x += 1)
|
|
{
|
|
float d = diff.length();
|
|
|
|
int occ = static_cast<int>(G3D::clamp(vradius - d + 0.5f, 0.f, 1.f) * Voxel2::Cell::Occupancy_Max + 0.5f);
|
|
|
|
if (row[x].getMaterial() != skipMaterial)
|
|
fillCell(row[x], voxelMaterial, occ);
|
|
}
|
|
}
|
|
|
|
smoothGrid->write(rr, box);
|
|
}
|
|
void MegaClusterInstance::validateApi()
|
|
{
|
|
if (!voxelGrid && !smoothGrid)
|
|
throw std::runtime_error("Terrain API is not available");
|
|
}
|
|
|
|
void MegaClusterInstance::validateApiSmooth()
|
|
{
|
|
if (!smoothGrid)
|
|
throw std::runtime_error("Smooth terrain API is not available");
|
|
}
|
|
|
|
void MegaClusterInstance::setWaterColor(const Color3& value)
|
|
{
|
|
if (waterColor != value)
|
|
{
|
|
waterColor = value;
|
|
raisePropertyChanged(desc_WaterColor);
|
|
}
|
|
}
|
|
|
|
void MegaClusterInstance::setWaterTransparency(float value)
|
|
{
|
|
value = G3D::clamp(value, 0.f, 1.f);
|
|
|
|
if (waterTransparency != value)
|
|
{
|
|
waterTransparency = value;
|
|
raisePropertyChanged(desc_WaterTransparency);
|
|
}
|
|
}
|
|
|
|
void MegaClusterInstance::setWaterWaveSize(float value)
|
|
{
|
|
value = G3D::clamp(value, 0.f, 1.f);
|
|
|
|
if (waterWaveSize != value)
|
|
{
|
|
waterWaveSize = value;
|
|
raisePropertyChanged(desc_WaterWaveSize);
|
|
}
|
|
}
|
|
|
|
void MegaClusterInstance::setWaterWaveSpeed(float value)
|
|
{
|
|
value = G3D::clamp(value, 0.f, 100.f);
|
|
|
|
if (waterWaveSpeed != value)
|
|
{
|
|
waterWaveSpeed = value;
|
|
raisePropertyChanged(desc_WaterWaveSpeed);
|
|
}
|
|
}
|
|
|
|
} //namespace
|