Generalize mesh chunking, and make it configurable. (#13179)

* Generalize mesh chunking. Set 3x3x3 chunks.

* Make mesh chunk size configurable... Default to 1 (off).

* Extract all mesh grid maths into a dedicated class

---------

Co-authored-by: x2048 <codeforsmile@gmail.com>
This commit is contained in:
lhofhansl 2023-02-08 13:42:12 -08:00 committed by GitHub
parent 56d2567b5d
commit d3a6ee00e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 116 additions and 116 deletions

@ -1713,6 +1713,13 @@ world_aligned_mode (World-aligned textures mode) enum enable disable,enable,forc
# Warning: This option is EXPERIMENTAL! # Warning: This option is EXPERIMENTAL!
autoscale_mode (Autoscaling mode) enum disable disable,enable,force autoscale_mode (Autoscaling mode) enum disable disable,enable,force
# Side length of a cube of map blocks that the client will consider together
# when generating meshes.
# Larger values increase the utilization of the GPU by reducing the number of
# draw calls, benefiting especially high-end GPUs.
# Systems with a low-end GPU (or no GPU) would benefit from smaller values.
client_mesh_chunk (Client Mesh Chunksize) int 1 1 16
[**Font] [**Font]
font_bold (Font bold by default) bool false font_bold (Font bold by default) bool false

@ -143,6 +143,7 @@ Client::Client(
} }
m_cache_save_interval = g_settings->getU16("server_map_save_interval"); m_cache_save_interval = g_settings->getU16("server_map_save_interval");
m_mesh_grid = { g_settings->getU16("client_mesh_chunk") };
} }
void Client::migrateModStorage() void Client::migrateModStorage()
@ -564,7 +565,7 @@ void Client::step(float dtime)
MapBlock *block = sector->getBlockNoCreateNoEx(r.p.Y); MapBlock *block = sector->getBlockNoCreateNoEx(r.p.Y);
// The block in question is not visible (perhaps it is culled at the server), // The block in question is not visible (perhaps it is culled at the server),
// create a blank block just to hold the 2x2x2 mesh. // create a blank block just to hold the chunk's mesh.
// If the block becomes visible later it will replace the blank block. // If the block becomes visible later it will replace the blank block.
if (!block && r.mesh) if (!block && r.mesh)
block = sector->createBlankBlock(r.p.Y); block = sector->createBlankBlock(r.p.Y);
@ -607,10 +608,10 @@ void Client::step(float dtime)
v3s16 ofs; v3s16 ofs;
// See also mapblock_mesh.cpp for the code that creates the array of minimap blocks. // See also mapblock_mesh.cpp for the code that creates the array of minimap blocks.
for (ofs.Z = 0; ofs.Z <= 1; ofs.Z++) for (ofs.Z = 0; ofs.Z < m_mesh_grid.cell_size; ofs.Z++)
for (ofs.Y = 0; ofs.Y <= 1; ofs.Y++) for (ofs.Y = 0; ofs.Y < m_mesh_grid.cell_size; ofs.Y++)
for (ofs.X = 0; ofs.X <= 1; ofs.X++) { for (ofs.X = 0; ofs.X < m_mesh_grid.cell_size; ofs.X++) {
size_t i = ofs.Z * 4 + ofs.Y * 2 + ofs.X; size_t i = m_mesh_grid.getOffsetIndex(ofs);
if (i < minimap_mapblocks.size() && minimap_mapblocks[i]) if (i < minimap_mapblocks.size() && minimap_mapblocks[i])
m_minimap->addBlock(r.p + ofs, minimap_mapblocks[i]); m_minimap->addBlock(r.p + ofs, minimap_mapblocks[i]);
} }

@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "network/peerhandler.h" #include "network/peerhandler.h"
#include "gameparams.h" #include "gameparams.h"
#include <fstream> #include <fstream>
#include "util/numeric.h"
#define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f #define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f
@ -437,6 +438,11 @@ public:
{ {
return m_env.getLocalPlayer()->formspec_prepend; return m_env.getLocalPlayer()->formspec_prepend;
} }
inline MeshGrid getMeshGrid()
{
return m_mesh_grid;
}
private: private:
void loadMods(); void loadMods();
@ -602,4 +608,7 @@ private:
u32 m_csm_restriction_noderange = 8; u32 m_csm_restriction_noderange = 8;
std::unique_ptr<ModChannelMgr> m_modchannel_mgr; std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
// The number of blocks the client will combine for mesh generation.
MeshGrid m_mesh_grid;
}; };

@ -304,6 +304,7 @@ void ClientMap::updateDrawList()
blocks_seen.getChunk(camera_block).getBits(camera_block) = 0x07; // mark all sides as visible blocks_seen.getChunk(camera_block).getBits(camera_block) = 0x07; // mark all sides as visible
std::set<v3s16> shortlist; std::set<v3s16> shortlist;
MeshGrid mesh_grid = m_client->getMeshGrid();
// Recursively walk the space and pick mapblocks for drawing // Recursively walk the space and pick mapblocks for drawing
while (blocks_to_consider.size() > 0) { while (blocks_to_consider.size() > 0) {
@ -330,7 +331,6 @@ void ClientMap::updateDrawList()
MapBlockMesh *mesh = block ? block->mesh : nullptr; MapBlockMesh *mesh = block ? block->mesh : nullptr;
// Calculate the coordinates for range and frutum culling // Calculate the coordinates for range and frutum culling
v3f mesh_sphere_center; v3f mesh_sphere_center;
f32 mesh_sphere_radius; f32 mesh_sphere_radius;
@ -376,13 +376,21 @@ void ClientMap::updateDrawList()
continue; continue;
} }
// Block meshes are stored in blocks where all coordinates are even (lowest bit set to 0) if (mesh_grid.cell_size > 1) {
// Add them to the de-dup set. // Block meshes are stored in the corner block of a chunk
shortlist.emplace(block_coord.X & ~1, block_coord.Y & ~1, block_coord.Z & ~1); // (where all coordinate are divisible by the chunk size)
// All other blocks we can grab and add to the keeplist right away. // Add them to the de-dup set.
if (block) { shortlist.emplace(mesh_grid.getMeshPos(block_coord.X), mesh_grid.getMeshPos(block_coord.Y), mesh_grid.getMeshPos(block_coord.Z));
m_keeplist.push_back(block); // All other blocks we can grab and add to the keeplist right away.
if (block) {
m_keeplist.push_back(block);
block->refGrab();
}
}
else if (mesh) {
// without mesh chunking we can add the block to the drawlist
block->refGrab(); block->refGrab();
m_drawlist.emplace(block_coord, block);
} }
// Decide which sides to traverse next or to block away // Decide which sides to traverse next or to block away
@ -485,7 +493,7 @@ void ClientMap::updateDrawList()
g_profiler->avg("MapBlocks shortlist [#]", shortlist.size()); g_profiler->avg("MapBlocks shortlist [#]", shortlist.size());
assert(m_drawlist.empty()); assert(m_drawlist.empty() || shortlist.empty());
for (auto pos : shortlist) { for (auto pos : shortlist) {
MapBlock * block = getBlockNoCreateNoEx(pos); MapBlock * block = getBlockNoCreateNoEx(pos);
if (block) { if (block) {
@ -612,6 +620,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
auto is_frustum_culled = m_client->getCamera()->getFrustumCuller(); auto is_frustum_culled = m_client->getCamera()->getFrustumCuller();
const MeshGrid mesh_grid = m_client->getMeshGrid();
for (auto &i : m_drawlist) { for (auto &i : m_drawlist) {
v3s16 block_pos = i.first; v3s16 block_pos = i.first;
MapBlock *block = i.second; MapBlock *block = i.second;
@ -740,7 +749,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
material.TextureLayer[ShadowRenderer::TEXTURE_LAYER_SHADOW].Texture = nullptr; material.TextureLayer[ShadowRenderer::TEXTURE_LAYER_SHADOW].Texture = nullptr;
} }
v3f block_wpos = intToFloat(descriptor.m_pos / 8 * 8 * MAP_BLOCKSIZE, BS); v3f block_wpos = intToFloat(mesh_grid.getMeshPos(descriptor.m_pos) * MAP_BLOCKSIZE, BS);
m.setTranslation(block_wpos - offset); m.setTranslation(block_wpos - offset);
driver->setTransform(video::ETS_WORLD, m); driver->setTransform(video::ETS_WORLD, m);
@ -978,6 +987,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
return; return;
} }
const MeshGrid mesh_grid = m_client->getMeshGrid();
for (const auto &i : m_drawlist_shadow) { for (const auto &i : m_drawlist_shadow) {
// only process specific part of the list & break early // only process specific part of the list & break early
++count; ++count;
@ -1066,7 +1076,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
++material_swaps; ++material_swaps;
} }
v3f block_wpos = intToFloat(descriptor.m_pos / 8 * 8 * MAP_BLOCKSIZE, BS); v3f block_wpos = intToFloat(mesh_grid.getMeshPos(descriptor.m_pos) * MAP_BLOCKSIZE, BS);
m.setTranslation(block_wpos - offset); m.setTranslation(block_wpos - offset);
driver->setTransform(video::ETS_WORLD, m); driver->setTransform(video::ETS_WORLD, m);

@ -38,7 +38,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
MeshMakeData::MeshMakeData(Client *client, bool use_shaders): MeshMakeData::MeshMakeData(Client *client, bool use_shaders):
m_client(client), m_client(client),
m_use_shaders(use_shaders) m_use_shaders(use_shaders),
m_mesh_grid(client->getMeshGrid()),
side_length(MAP_BLOCKSIZE * m_mesh_grid.cell_size)
{} {}
void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos) void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos)
@ -53,12 +55,11 @@ void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos)
m_vmanip.addArea(voxel_area); m_vmanip.addArea(voxel_area);
} }
void MeshMakeData::fillBlockData(const v3s16 &block_offset, MapNode *data) void MeshMakeData::fillBlockData(const v3s16 &bp, MapNode *data)
{ {
v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE); v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1)); VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
v3s16 bp = m_blockpos + block_offset;
v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE; v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE;
m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size); m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size);
} }
@ -1179,18 +1180,18 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
v3s16 bp = data->m_blockpos; v3s16 bp = data->m_blockpos;
// Only generate minimap mapblocks at even coordinates. // Only generate minimap mapblocks at even coordinates.
if (((bp.X | bp.Y | bp.Z) & 1) == 0 && data->m_client->getMinimap()) { if (data->m_mesh_grid.isMeshPos(bp) && data->m_client->getMinimap()) {
m_minimap_mapblocks.resize(8, nullptr); m_minimap_mapblocks.resize(data->m_mesh_grid.getCellVolume(), nullptr);
v3s16 ofs; v3s16 ofs;
// See also client.cpp for the code that reads the array of minimap blocks. // See also client.cpp for the code that reads the array of minimap blocks.
for (ofs.Z = 0; ofs.Z <= 1; ofs.Z++) for (ofs.Z = 0; ofs.Z < data->m_mesh_grid.cell_size; ofs.Z++)
for (ofs.Y = 0; ofs.Y <= 1; ofs.Y++) for (ofs.Y = 0; ofs.Y < data->m_mesh_grid.cell_size; ofs.Y++)
for (ofs.X = 0; ofs.X <= 1; ofs.X++) { for (ofs.X = 0; ofs.X < data->m_mesh_grid.cell_size; ofs.X++) {
v3s16 p = (bp + ofs) * MAP_BLOCKSIZE; v3s16 p = (bp + ofs) * MAP_BLOCKSIZE;
if (data->m_vmanip.getNodeNoEx(p).getContent() != CONTENT_IGNORE) { if (data->m_vmanip.getNodeNoEx(p).getContent() != CONTENT_IGNORE) {
MinimapMapblock *block = new MinimapMapblock; MinimapMapblock *block = new MinimapMapblock;
m_minimap_mapblocks[ofs.Z * 4 + ofs.Y * 2 + ofs.X] = block; m_minimap_mapblocks[data->m_mesh_grid.getOffsetIndex(ofs)] = block;
block->getMinimapNodes(&data->m_vmanip, p); block->getMinimapNodes(&data->m_vmanip, p);
} }
} }
@ -1221,7 +1222,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
Convert FastFaces to MeshCollector Convert FastFaces to MeshCollector
*/ */
v3f offset = intToFloat((data->m_blockpos - data->m_blockpos / 8 * 8) * MAP_BLOCKSIZE, BS); v3f offset = intToFloat((data->m_blockpos - data->m_mesh_grid.getMeshPos(data->m_blockpos)) * MAP_BLOCKSIZE, BS);
MeshCollector collector(m_bounding_sphere_center, offset); MeshCollector collector(m_bounding_sphere_center, offset);
{ {
@ -1584,10 +1585,11 @@ std::unordered_map<v3s16, u8> get_solid_sides(MeshMakeData *data)
{ {
std::unordered_map<v3s16, u8> results; std::unordered_map<v3s16, u8> results;
v3s16 ofs; v3s16 ofs;
const u16 mesh_chunk = data->side_length / MAP_BLOCKSIZE;
for (ofs.X = 0; ofs.X < 2; ofs.X++) for (ofs.X = 0; ofs.X < mesh_chunk; ofs.X++)
for (ofs.Y = 0; ofs.Y < 2; ofs.Y++) for (ofs.Y = 0; ofs.Y < mesh_chunk; ofs.Y++)
for (ofs.Z = 0; ofs.Z < 2; ofs.Z++) { for (ofs.Z = 0; ofs.Z < mesh_chunk; ofs.Z++) {
v3s16 blockpos = data->m_blockpos + ofs; v3s16 blockpos = data->m_blockpos + ofs;
v3s16 blockpos_nodes = blockpos * MAP_BLOCKSIZE; v3s16 blockpos_nodes = blockpos * MAP_BLOCKSIZE;
const NodeDefManager *ndef = data->m_client->ndef(); const NodeDefManager *ndef = data->m_client->ndef();

@ -43,6 +43,7 @@ struct MeshMakeData
v3s16 m_blockpos = v3s16(-1337,-1337,-1337); v3s16 m_blockpos = v3s16(-1337,-1337,-1337);
v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337); v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337);
bool m_smooth_lighting = false; bool m_smooth_lighting = false;
MeshGrid m_mesh_grid;
u16 side_length = MAP_BLOCKSIZE; u16 side_length = MAP_BLOCKSIZE;
Client *m_client; Client *m_client;
@ -54,7 +55,7 @@ struct MeshMakeData
Copy block data manually (to allow optimizations by the caller) Copy block data manually (to allow optimizations by the caller)
*/ */
void fillBlockDataBegin(const v3s16 &blockpos); void fillBlockDataBegin(const v3s16 &blockpos);
void fillBlockData(const v3s16 &block_offset, MapNode *data); void fillBlockData(const v3s16 &bp, MapNode *data);
/* /*
Set the (node) position of a crack Set the (node) position of a crack

@ -77,9 +77,11 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
MutexAutoLock lock(m_mutex); MutexAutoLock lock(m_mutex);
// Mesh is placed at even positions at all coordinates MeshGrid mesh_grid = m_client->getMeshGrid();
// (every 8-th block) and will cover 8 blocks
v3s16 mesh_position(p.X & ~1, p.Y & ~1, p.Z & ~1); // Mesh is placed at the corner block of a chunk
// (where all coordinate are divisible by the chunk size)
v3s16 mesh_position(mesh_grid.getMeshPos(p));
/* /*
Mark the block as urgent if requested Mark the block as urgent if requested
*/ */
@ -99,14 +101,19 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
q->crack_level = m_client->getCrackLevel(); q->crack_level = m_client->getCrackLevel();
q->crack_pos = m_client->getCrackPos(); q->crack_pos = m_client->getCrackPos();
q->urgent |= urgent; q->urgent |= urgent;
for (std::size_t i = 0; i < q->map_blocks.size(); i++) { v3s16 pos;
int i = 0;
for (pos.X = q->p.X - 1; pos.X <= q->p.X + mesh_grid.cell_size; pos.X++)
for (pos.Z = q->p.Z - 1; pos.Z <= q->p.Z + mesh_grid.cell_size; pos.Z++)
for (pos.Y = q->p.Y - 1; pos.Y <= q->p.Y + mesh_grid.cell_size; pos.Y++) {
if (!q->map_blocks[i]) { if (!q->map_blocks[i]) {
MapBlock *block = map->getBlockNoCreateNoEx(q->p + g_64dirs[i]); MapBlock *block = map->getBlockNoCreateNoEx(pos);
if (block) { if (block) {
block->refGrab(); block->refGrab();
q->map_blocks[i] = block; q->map_blocks[i] = block;
} }
} }
i++;
} }
return true; return true;
} }
@ -116,9 +123,12 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
Make a list of blocks necessary for mesh generation and lock the blocks in memory. Make a list of blocks necessary for mesh generation and lock the blocks in memory.
*/ */
std::vector<MapBlock *> map_blocks; std::vector<MapBlock *> map_blocks;
map_blocks.reserve(4*4*4); map_blocks.reserve((mesh_grid.cell_size+2)*(mesh_grid.cell_size+2)*(mesh_grid.cell_size+2));
for (v3s16 dp : g_64dirs) { v3s16 pos;
MapBlock *block = map->getBlockNoCreateNoEx(mesh_position + dp); for (pos.X = mesh_position.X - 1; pos.X <= mesh_position.X + mesh_grid.cell_size; pos.X++)
for (pos.Z = mesh_position.Z - 1; pos.Z <= mesh_position.Z + mesh_grid.cell_size; pos.Z++)
for (pos.Y = mesh_position.Y - 1; pos.Y <= mesh_position.Y + mesh_grid.cell_size; pos.Y++) {
MapBlock *block = map->getBlockNoCreateNoEx(pos);
map_blocks.push_back(block); map_blocks.push_back(block);
if (block) if (block)
block->refGrab(); block->refGrab();
@ -182,13 +192,16 @@ void MeshUpdateQueue::fillDataFromMapBlocks(QueuedMeshUpdate *q)
{ {
MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders); MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders);
q->data = data; q->data = data;
data->side_length = 2 * MAP_BLOCKSIZE;
data->fillBlockDataBegin(q->p); data->fillBlockDataBegin(q->p);
for (std::size_t i = 0; i < 64; i++) { v3s16 pos;
MapBlock *block = q->map_blocks[i]; int i = 0;
data->fillBlockData(g_64dirs[i], block ? block->getData() : block_placeholder.data); for (pos.X = q->p.X - 1; pos.X <= q->p.X + data->m_mesh_grid.cell_size; pos.X++)
for (pos.Z = q->p.Z - 1; pos.Z <= q->p.Z + data->m_mesh_grid.cell_size; pos.Z++)
for (pos.Y = q->p.Y - 1; pos.Y <= q->p.Y + data->m_mesh_grid.cell_size; pos.Y++) {
MapBlock *block = q->map_blocks[i++];
data->fillBlockData(pos, block ? block->getData() : block_placeholder.data);
} }
data->setCrack(q->crack_level, q->crack_pos); data->setCrack(q->crack_level, q->crack_pos);

@ -185,6 +185,7 @@ void set_default_settings()
settings->setDefault("fps_max", "60"); settings->setDefault("fps_max", "60");
settings->setDefault("fps_max_unfocused", "20"); settings->setDefault("fps_max_unfocused", "20");
settings->setDefault("viewing_range", "190"); settings->setDefault("viewing_range", "190");
settings->setDefault("client_mesh_chunk", "1");
#if ENABLE_GLES #if ENABLE_GLES
settings->setDefault("near_plane", "0.1"); settings->setDefault("near_plane", "0.1");
#endif #endif

@ -110,78 +110,6 @@ const v3s16 g_27dirs[27] =
v3s16(0,0,0), v3s16(0,0,0),
}; };
const v3s16 g_64dirs[64] =
{
// +right, +top, +back
v3s16( -1, -1, -1),
v3s16( -1, 0, -1),
v3s16( -1, 1, -1),
v3s16( -1, 2, -1),
v3s16( -1, -1, 0),
v3s16( -1, 0, 0),
v3s16( -1, 1, 0),
v3s16( -1, 2, 0),
v3s16( -1, -1, 1),
v3s16( -1, 0, 1),
v3s16( -1, 1, 1),
v3s16( -1, 2, 1),
v3s16( -1, -1, 2),
v3s16( -1, 0, 2),
v3s16( -1, 1, 2),
v3s16( -1, 2, 2),
v3s16( 0, -1, -1),
v3s16( 0, 0, -1),
v3s16( 0, 1, -1),
v3s16( 0, 2, -1),
v3s16( 0, -1, 0),
v3s16( 0, 0, 0),
v3s16( 0, 1, 0),
v3s16( 0, 2, 0),
v3s16( 0, -1, 1),
v3s16( 0, 0, 1),
v3s16( 0, 1, 1),
v3s16( 0, 2, 1),
v3s16( 0, -1, 2),
v3s16( 0, 0, 2),
v3s16( 0, 1, 2),
v3s16( 0, 2, 2),
v3s16( 1, -1, -1),
v3s16( 1, 0, -1),
v3s16( 1, 1, -1),
v3s16( 1, 2, -1),
v3s16( 1, -1, 0),
v3s16( 1, 0, 0),
v3s16( 1, 1, 0),
v3s16( 1, 2, 0),
v3s16( 1, -1, 1),
v3s16( 1, 0, 1),
v3s16( 1, 1, 1),
v3s16( 1, 2, 1),
v3s16( 1, -1, 2),
v3s16( 1, 0, 2),
v3s16( 1, 1, 2),
v3s16( 1, 2, 2),
v3s16( 2, -1, -1),
v3s16( 2, 0, -1),
v3s16( 2, 1, -1),
v3s16( 2, 2, -1),
v3s16( 2, -1, 0),
v3s16( 2, 0, 0),
v3s16( 2, 1, 0),
v3s16( 2, 2, 0),
v3s16( 2, -1, 1),
v3s16( 2, 0, 1),
v3s16( 2, 1, 1),
v3s16( 2, 2, 1),
v3s16( 2, -1, 2),
v3s16( 2, 0, 2),
v3s16( 2, 1, 2),
v3s16( 2, 2, 2),
};
const u8 wallmounted_to_facedir[6] = { const u8 wallmounted_to_facedir[6] = {
20, 20,
0, 0,

@ -31,9 +31,6 @@ extern const v3s16 g_26dirs[26];
// 26th is (0,0,0) // 26th is (0,0,0)
extern const v3s16 g_27dirs[27]; extern const v3s16 g_27dirs[27];
// all positions around an octablock in sector-first order
extern const v3s16 g_64dirs[64];
extern const u8 wallmounted_to_facedir[6]; extern const u8 wallmounted_to_facedir[6];
extern const v3s16 wallmounted_dirs[8]; extern const v3s16 wallmounted_dirs[8];

@ -145,6 +145,37 @@ inline v3s16 componentwise_max(const v3s16 &a, const v3s16 &b)
return v3s16(MYMAX(a.X, b.X), MYMAX(a.Y, b.Y), MYMAX(a.Z, b.Z)); return v3s16(MYMAX(a.X, b.X), MYMAX(a.Y, b.Y), MYMAX(a.Z, b.Z));
} }
/// @brief Describes a grid with given step, oirginating at (0,0,0)
struct MeshGrid {
u16 cell_size;
u32 getCellVolume() const { return cell_size * cell_size * cell_size; }
/// @brief returns closest step of the grid smaller than p
s16 getMeshPos(s16 p) const
{
return ((p - (p < 0) * (cell_size - 1)) / cell_size * cell_size);
}
/// @brief Returns coordinates of the origin of the grid cell containing p
v3s16 getMeshPos(v3s16 p) const
{
return v3s16(getMeshPos(p.X), getMeshPos(p.Y), getMeshPos(p.Z));
}
/// @brief Returns true if p is an origin of a cell in the grid.
bool isMeshPos(v3s16 &p) const
{
return ((p.X + p.Y + p.Z) % cell_size) == 0;
}
/// @brief Returns index of the given offset in a grid cell
/// All offset coordinates must be smaller than the size of the cell
u16 getOffsetIndex(v3s16 offset) const
{
return (offset.Z * cell_size + offset.Y) * cell_size + offset.X;
}
};
/** Returns \p f wrapped to the range [-360, 360] /** Returns \p f wrapped to the range [-360, 360]
* *