Files
noggit-red/src/noggit/ChunkWater.cpp
2024-08-31 00:05:48 +02:00

435 lines
11 KiB
C++
Executable File

// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <noggit/ChunkWater.hpp>
#include <noggit/TileWater.hpp>
#include <noggit/liquid_layer.hpp>
#include <noggit/MapChunk.h>
#include <noggit/Misc.h>
#include <ClientFile.hpp>
ChunkWater::ChunkWater(MapChunk* chunk, TileWater* water_tile, float x, float z, bool use_mclq_green_lava)
: xbase(x)
, zbase(z)
, vmin(x, 0.f, z)
, vmax(x + CHUNKSIZE, 0.f, z + CHUNKSIZE)
, vcenter((vmin + vmax) * 0.5f)
, _use_mclq_green_lava(use_mclq_green_lava)
, _chunk(chunk)
, _water_tile(water_tile)
{
}
void ChunkWater::from_mclq(std::vector<mclq>& layers)
{
glm::vec3 pos(xbase, 0.0f, zbase);
for (mclq& liquid : layers)
{
std::uint8_t mclq_liquid_type = 0;
for (int z = 0; z < 8; ++z)
{
for (int x = 0; x < 8; ++x)
{
mclq_tile const& tile = liquid.tiles[z * 8 + x];
misc::bit_or(attributes.fishable, x, z, tile.fishable);
misc::bit_or(attributes.fatigue, x, z, tile.fatigue);
if (!tile.dont_render)
{
mclq_liquid_type = tile.liquid_type;
}
}
}
switch (mclq_liquid_type)
{
case 1: // mclq ocean
_layers.emplace_back(this, pos, liquid, LIQUID_OCEAN);
break;
case 3: // mclq slime
_layers.emplace_back(this, pos, liquid, LIQUID_SLIME);
break;
case 4: // mclq river
_layers.emplace_back(this, pos, liquid, LIQUID_WATER);
break;
case 6: // mclq magma
_layers.emplace_back(this, pos, liquid, (_use_mclq_green_lava ? LIQUID_Green_Lava : LIQUID_MAGMA));
break;
default:
LogError << "Invalid/unhandled MCLQ liquid type" << std::endl;
break;
}
_water_tile->tagUpdate();
}
update_layers();
}
void ChunkWater::fromFile(BlizzardArchive::ClientFile &f, size_t basePos)
{
MH2O_Header header;
f.read(&header, sizeof(MH2O_Header));
if (!header.nLayers)
{
return;
}
//render
if (header.ofsAttributes)
{
f.seek(basePos + header.ofsAttributes);
f.read(&attributes, sizeof(MH2O_Attributes));
}
else
{
// when chunks don't have attributes it means everything is set.
attributes.fishable = 0xFFFFFFFFFFFFFFFF;
attributes.fatigue = 0xFFFFFFFFFFFFFFFF;
}
for (std::size_t k = 0; k < header.nLayers; ++k)
{
MH2O_Information info;
uint64_t infoMask = 0xFFFFFFFFFFFFFFFF; // default = all water. InfoMask + HeightMap combined
//info
f.seek(basePos + header.ofsInformation + sizeof(MH2O_Information)* k);
f.read(&info, sizeof(MH2O_Information));
//mask
if (info.ofsInfoMask > 0 && info.height > 0)
{
size_t bitmask_size = static_cast<size_t>(std::ceil(info.height * info.width / 8.0f));
f.seek(info.ofsInfoMask + basePos);
// only read the relevant data
f.read(&infoMask, bitmask_size);
}
glm::vec3 pos(xbase, 0.0f, zbase);
_water_tile->tagUpdate();
// liquid_layer(ChunkWater* chunk, BlizzardArchive::ClientFile& f, std::size_t base_pos, glm::vec3 const& base
// ,MH2O_Information const& info, std::uint64_t infomask);
_layers.emplace_back(this, f, basePos, pos, info, infoMask);
}
update_layers();
}
void ChunkWater::save(sExtendableArray& adt, int base_pos, int& header_pos, int& current_pos)
{
MH2O_Header header;
// remove empty layers
cleanup();
update_attributes();
if (hasData(0))
{
header.nLayers = static_cast<std::uint32_t>(_layers.size());
// fagique only for single layer ocean chunk
bool fatigue = _layers[0].has_fatigue();
if (!fatigue)
{
header.ofsAttributes = current_pos - base_pos;
adt.Insert(current_pos, sizeof(MH2O_Attributes), reinterpret_cast<char*>(&attributes));
current_pos += sizeof(MH2O_Attributes);
}
else
{
header.ofsAttributes = 0;
}
header.ofsInformation = current_pos - base_pos;
int info_pos = current_pos;
std::size_t info_size = sizeof(MH2O_Information) * _layers.size();
current_pos += static_cast<std::uint32_t>(info_size);
adt.Extend(static_cast<long>(info_size));
for (liquid_layer& layer : _layers)
{
layer.save(adt, base_pos, info_pos, current_pos);
}
}
memcpy(adt.GetPointer<char>(header_pos), &header, sizeof(MH2O_Header));
header_pos += sizeof(MH2O_Header);
}
void ChunkWater::autoGen(MapChunk *chunk, float factor)
{
for (liquid_layer& layer : _layers)
{
layer.update_opacity(chunk, factor);
}
update_layers();
}
void ChunkWater::update_underground_vertices_depth(MapChunk* chunk)
{
for (liquid_layer& layer : _layers)
{
layer.update_underground_vertices_depth(chunk);
}
}
void ChunkWater::CropWater(MapChunk* chunkTerrain)
{
for (liquid_layer& layer : _layers)
{
layer.crop(chunkTerrain);
}
update_layers();
}
int ChunkWater::getType(size_t layer) const
{
return hasData(layer) ? _layers[layer].liquidID() : 0;
}
void ChunkWater::setType(int type, size_t layer)
{
if(hasData(layer))
{
_layers[layer].changeLiquidID(type);
}
_water_tile->tagUpdate();
}
bool ChunkWater::is_visible ( const float& cull_distance
, const math::frustum& frustum
, const glm::vec3& camera
, display_mode display
) const
{
static const float chunk_radius = std::sqrt (CHUNKSIZE * CHUNKSIZE / 2.0f);
float dist = display == display_mode::in_3D
? (camera - vcenter).length() - chunk_radius
: std::abs(camera.y - vmax.y);
return frustum.intersects(vmax, vmin) && dist < cull_distance;
}
void ChunkWater::update_layers()
{
std::size_t count = 0;
auto& extents = _water_tile->getExtents();
vmin.y = std::numeric_limits<float>::max();
vmax.y = std::numeric_limits<float>::lowest();
std::uint64_t debug_fishable_old = attributes.fishable;
std::uint64_t debug_fatigue_old = attributes.fatigue;
if (_auto_update_attributes)
{
// reset attributes
attributes.fishable = 0;
attributes.fatigue = 0;
}
for (liquid_layer& layer : _layers)
{
vmin.y = std::min (vmin.y, layer.min());
vmax.y = std::max (vmax.y, layer.max());
extents[0].y = std::min(extents[0].y, vmin.y);
extents[1].y = std::max(extents[1].y, vmax.y);
_water_tile->tagUpdate();
if (_auto_update_attributes)
layer.update_attributes(attributes);
count++;
}
// some tests to compare with blizzard
const bool run_tests = false;
if (run_tests)
{
if (attributes.fishable != debug_fishable_old && _layers.size())
{
uint64_t x = attributes.fishable ^ debug_fishable_old;
int difference_count = 0;
while (x > 0) {
difference_count += x & 1;
x >>= 1;
}
auto type = _layers.front().liquidID();
std::vector<float> depths;
int zero_depth_num = 0;
int below_20_num = 0; // below 0.2
for (auto& vert : _layers.front().getVertices())
{
depths.push_back(vert.depth);
if (vert.depth == 0.0f)
zero_depth_num++;
if (vert.depth < 0.2f && vert.depth != 0.0f)
below_20_num++;
}
}
}
_water_tile->tagExtents(true);
vcenter = (vmin + vmax) * 0.5f;
_layer_count = _layers.size();
}
bool ChunkWater::hasData(size_t layer) const
{
return _layer_count > layer;
}
void ChunkWater::paintLiquid( glm::vec3 const& pos
, float radius
, int liquid_id
, bool add
, math::radians const& angle
, math::radians const& orientation
, bool lock
, glm::vec3 const& origin
, bool override_height
, bool override_liquid_id
, MapChunk* chunk
, float opacity_factor
)
{
if (override_liquid_id && !override_height)
{
bool layer_found = false;
for (liquid_layer& layer : _layers)
{
if (layer.liquidID() == liquid_id)
{
copy_height_to_layer(layer, pos, radius);
layer_found = true;
break;
}
}
if (!layer_found)
{
liquid_layer layer(this, glm::vec3(xbase, 0.0f, zbase), pos.y, liquid_id);
copy_height_to_layer(layer, pos, radius);
_water_tile->tagUpdate();
_layers.push_back(layer);
}
}
bool painted = false;
for (liquid_layer& layer : _layers)
{
// remove the water on all layers or paint the layer with selected id
if (!add || layer.liquidID() == liquid_id || !override_liquid_id)
{
layer.paintLiquid(pos, radius, add, angle, orientation, lock, origin, override_height, chunk, opacity_factor);
painted = true;
}
else
{
layer.paintLiquid(pos, radius, false, angle, orientation, lock, origin, override_height, chunk, opacity_factor);
}
}
cleanup();
if (!add || painted)
{
update_layers();
return;
}
if (hasData(0))
{
liquid_layer layer(_layers[0]);
layer.clear(); // remove the liquid to not override the other layer
layer.paintLiquid(pos, radius, true, angle, orientation, lock, origin, override_height, chunk, opacity_factor);
layer.changeLiquidID(liquid_id);
_water_tile->tagUpdate();
_layers.push_back(layer);
}
else
{
liquid_layer layer(this, glm::vec3(xbase, 0.0f, zbase), pos.y, liquid_id);
layer.paintLiquid(pos, radius, true, angle, orientation, lock, origin, override_height, chunk, opacity_factor);
_water_tile->tagUpdate();
_layers.push_back(layer);
}
update_layers();
}
void ChunkWater::cleanup()
{
for (int i = static_cast<int>(_layers.size() - 1); i >= 0; --i)
{
if (_layers[i].empty())
{
_layers.erase(_layers.begin() + i);
_water_tile->tagUpdate();
}
}
}
void ChunkWater::copy_height_to_layer(liquid_layer& target, glm::vec3 const& pos, float radius)
{
for (liquid_layer& layer : _layers)
{
if (layer.liquidID() == target.liquidID())
{
continue;
}
for (int z = 0; z < 8; ++z)
{
for (int x = 0; x < 8; ++x)
{
if (misc::getShortestDist(pos.x, pos.z, xbase + x*UNITSIZE, zbase + z*UNITSIZE, UNITSIZE) <= radius)
{
if (layer.hasSubchunk(x, z))
{
target.copy_subchunk_height(x, z, layer);
}
}
}
}
}
}
void ChunkWater::update_attributes()
{
attributes.fishable = 0;
attributes.fatigue = 0;
for (liquid_layer& layer : _layers)
{
layer.update_attributes(attributes);
}
}
void ChunkWater::tagUpdate()
{
_water_tile->tagUpdate();
}