diff --git a/collections/vector3i.cpp b/collections/vector3i.cpp deleted file mode 100644 index 61487bb..0000000 --- a/collections/vector3i.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "vector3i.h" diff --git a/data/voxel.h b/data/voxel.h index 454ba71..ce556c8 100644 --- a/data/voxel.h +++ b/data/voxel.h @@ -2,7 +2,7 @@ #define VOXEL_H #include "core/reference.h" -#include "../collections/vector3i.h" +#include "../math/vector3i.h" #include "core/math/quat.h" #include "core/resource.h" #include "core/vector.h" diff --git a/data/voxel_light.h b/data/voxel_light.h index a6a3efe..991f12a 100644 --- a/data/voxel_light.h +++ b/data/voxel_light.h @@ -5,7 +5,7 @@ #include "core/reference.h" #include "core/vector.h" -#include "../collections/vector3i.h" +#include "../math/vector3i.h" class VoxelLight : public Reference { GDCLASS(VoxelLight, Reference); diff --git a/math/rect3i.h b/math/rect3i.h new file mode 100644 index 0000000..ae177de --- /dev/null +++ b/math/rect3i.h @@ -0,0 +1,99 @@ +/** +* +* Voxel Tools for Godot Engine +* +* Copyright(c) 2016 Marc Gilleron +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +* files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +* modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software +* is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +#ifndef RECT3I_H +#define RECT3I_H + +#include "vector3i.h" +#include + +class Rect3i { + +public: + Vector3i pos; + Vector3i size; + + Rect3i() {} + + Rect3i(Vector3i p_pos, Vector3i p_size) : + pos(p_pos), + size(p_size) {} + + Rect3i(const Rect3i &other) : + pos(other.pos), + size(other.size) {} + + static inline Rect3i from_center_extents(Vector3i center, Vector3i extents) { + return Rect3i(center - extents, 2 * extents); + } + + static inline Rect3i get_bounding_box(Rect3i a, Rect3i b) { + + Rect3i box; + + box.pos.x = MIN(a.pos.x, b.pos.x); + box.pos.y = MIN(a.pos.y, b.pos.y); + box.pos.z = MIN(a.pos.z, b.pos.z); + + Vector3i max_a = a.pos + a.size; + Vector3i max_b = b.pos + b.size; + + box.size.x = MAX(max_a.x, max_b.x) - box.pos.x; + box.size.y = MAX(max_a.y, max_b.y) - box.pos.y; + box.size.z = MAX(max_a.z, max_b.z) - box.pos.z; + + return box; + } + + bool inline contains(Vector3i p_pos) const { + Vector3i end = pos + size; + return p_pos.x >= pos.x && + p_pos.y >= pos.y && + p_pos.z >= pos.z && + p_pos.x < end.x && + p_pos.y < end.y && + p_pos.z < end.z; + } + + String to_string() const { + return String("(o:{0}, s:{1})").format(varray(pos.to_vec3(), size.to_vec3())); + } + + bool intersects(Rect3i other) { + if (pos.x > other.pos.x + other.size.x) + return false; + if (pos.y > other.pos.y + other.size.y) + return false; + if (pos.z > other.pos.z + other.size.z) + return false; + if (other.pos.x > pos.x + size.x) + return false; + if (other.pos.y > pos.y + size.y) + return false; + if (other.pos.z > pos.z + size.z) + return false; + return true; + } +}; + +inline bool operator!=(const Rect3i &a, const Rect3i &b) { + return a.pos != b.pos || a.size != b.size; +} + +#endif // RECT3I_H diff --git a/collections/vector3i.h b/math/vector3i.h similarity index 70% rename from collections/vector3i.h rename to math/vector3i.h index 5d7b7ae..ce27c46 100644 --- a/collections/vector3i.h +++ b/math/vector3i.h @@ -1,3 +1,22 @@ +/** +* +* Voxel Tools for Godot Engine +* +* Copyright(c) 2016 Marc Gilleron +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +* files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +* modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software +* is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + #ifndef VOXEL_VECTOR3I_H #define VOXEL_VECTOR3I_H @@ -65,13 +84,6 @@ struct Vector3i { return *this; } - _FORCE_INLINE_ Vector3i &operator=(const Vector3 &other) { - x = (int) other.x; - y = (int) other.y; - z = (int) other.z; - return *this; - } - _FORCE_INLINE_ void operator+=(const Vector3i &other) { x += other.x; y += other.y; @@ -162,6 +174,27 @@ _FORCE_INLINE_ bool operator!=(const Vector3i &a, const Vector3i &b) { return a.x != b.x || a.y != b.y || a.z != b.z; } +_FORCE_INLINE_ Vector3i operator<<(const Vector3i &a, int b) { + return Vector3i(a.x << b, a.y << b, a.z << b); +} + +_FORCE_INLINE_ Vector3i operator>>(const Vector3i &a, int b) { + return Vector3i(a.x >> b, a.y >> b, a.z >> b); +} + +_FORCE_INLINE_ bool operator<(const Vector3i &a, const Vector3i &b) { + + if (a.x == b.x) { + if (a.y == b.y) { + return a.z < b.z; + } else { + return a.y < b.y; + } + } else { + return a.x < b.x; + } +} + _FORCE_INLINE_ int Vector3i::distance_sq(const Vector3i &other) const { return (other - *this).length_sq(); } diff --git a/meshers/voxel_mesher.h b/meshers/voxel_mesher.h index 585482b..cf1c2a9 100644 --- a/meshers/voxel_mesher.h +++ b/meshers/voxel_mesher.h @@ -16,7 +16,7 @@ #include "scene/resources/concave_polygon_shape.h" #include "../library/voxelman_library.h" -#include "../collections/vector3i.h" +#include "../math/vector3i.h" #include "../data/voxel.h" #include "../library/voxelman_library.h" #include "../utility/marching_cubes_voxel_query.h" diff --git a/world/voxel_buffer.cpp b/world/voxel_buffer.cpp new file mode 100644 index 0000000..980ac09 --- /dev/null +++ b/world/voxel_buffer.cpp @@ -0,0 +1,349 @@ +/** +* +* Voxel Tools for Godot Engine +* +* Copyright(c) 2016 Marc Gilleron +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +* files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +* modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software +* is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +#include "voxel_buffer.h" + +#include +#include + +const char *VoxelBuffer::CHANNEL_ID_HINT_STRING = "Type,Sdf,Data2,Data3,Data4,Data5,Data6,Data7"; + +VoxelBuffer::VoxelBuffer() { + _channels[CHANNEL_ISOLEVEL].defval = 255; +} + +VoxelBuffer::~VoxelBuffer() { + clear(); +} + +void VoxelBuffer::create(int sx, int sy, int sz) { + if (sx <= 0 || sy <= 0 || sz <= 0) { + return; + } + Vector3i new_size(sx, sy, sz); + if (new_size != _size) { + for (unsigned int i = 0; i < MAX_CHANNELS; ++i) { + Channel &channel = _channels[i]; + if (channel.data) { + // Channel already contained data + // TODO Optimize with realloc + delete_channel(i); + create_channel(i, new_size, channel.defval); + } + } + _size = new_size; + } +} + +void VoxelBuffer::clear() { + for (unsigned int i = 0; i < MAX_CHANNELS; ++i) { + Channel &channel = _channels[i]; + if (channel.data) { + delete_channel(i); + } + } +} + +void VoxelBuffer::clear_channel(unsigned int channel_index, int clear_value) { + ERR_FAIL_INDEX(channel_index, MAX_CHANNELS); + if (_channels[channel_index].data) { + delete_channel(channel_index); + } + _channels[channel_index].defval = clear_value; +} + +void VoxelBuffer::set_default_values(uint8_t values[VoxelBuffer::MAX_CHANNELS]) { + for (unsigned int i = 0; i < MAX_CHANNELS; ++i) { + _channels[i].defval = values[i]; + } +} + +int VoxelBuffer::get_voxel(int x, int y, int z, unsigned int channel_index) const { + ERR_FAIL_INDEX_V(channel_index, MAX_CHANNELS, 0); + + const Channel &channel = _channels[channel_index]; + + if (validate_pos(x, y, z) && channel.data) { + return channel.data[index(x, y, z)]; + } else { + return channel.defval; + } +} + +void VoxelBuffer::set_voxel(int value, int x, int y, int z, unsigned int channel_index) { + ERR_FAIL_INDEX(channel_index, MAX_CHANNELS); + ERR_FAIL_COND(!validate_pos(x, y, z)); + + Channel &channel = _channels[channel_index]; + + if (channel.data == NULL) { + if (channel.defval != value) { + // Allocate channel with same initial values as defval + create_channel(channel_index, _size, channel.defval); + channel.data[index(x, y, z)] = value; + } + } else { + channel.data[index(x, y, z)] = value; + } +} + +// This version does not cause errors if out of bounds. Use only if it's okay to be outside. +void VoxelBuffer::try_set_voxel(int x, int y, int z, int value, unsigned int channel_index) { + ERR_FAIL_INDEX(channel_index, MAX_CHANNELS); + if (!validate_pos(x, y, z)) { + return; + } + + Channel &channel = _channels[channel_index]; + + if (channel.data == NULL) { + if (channel.defval != value) { + create_channel(channel_index, _size, channel.defval); + channel.data[index(x, y, z)] = value; + } + } else { + channel.data[index(x, y, z)] = value; + } +} + +void VoxelBuffer::set_voxel_v(int value, Vector3 pos, unsigned int channel_index) { + set_voxel(value, pos.x, pos.y, pos.z, channel_index); +} + +void VoxelBuffer::fill(int defval, unsigned int channel_index) { + ERR_FAIL_INDEX(channel_index, MAX_CHANNELS); + + Channel &channel = _channels[channel_index]; + if (channel.data == NULL) { + // Channel is already optimized and uniform + if (channel.defval == defval) { + // No change + return; + } else { + // Just change default value + channel.defval = defval; + return; + } + } else { + create_channel_noinit(channel_index, _size); + } + + unsigned int volume = get_volume(); + memset(channel.data, defval, volume); +} + +void VoxelBuffer::fill_area(int defval, Vector3i min, Vector3i max, unsigned int channel_index) { + ERR_FAIL_INDEX(channel_index, MAX_CHANNELS); + + Vector3i::sort_min_max(min, max); + + min.clamp_to(Vector3i(0, 0, 0), _size + Vector3i(1, 1, 1)); + max.clamp_to(Vector3i(0, 0, 0), _size + Vector3i(1, 1, 1)); + Vector3i area_size = max - min; + + if (area_size.x == 0 || area_size.y == 0 || area_size.z == 0) { + return; + } + + Channel &channel = _channels[channel_index]; + if (channel.data == NULL) { + if (channel.defval == defval) { + return; + } else { + create_channel(channel_index, _size, channel.defval); + } + } + + Vector3i pos; + int volume = get_volume(); + for (pos.z = min.z; pos.z < max.z; ++pos.z) { + for (pos.x = min.x; pos.x < max.x; ++pos.x) { + unsigned int dst_ri = index(pos.x, pos.y + min.y, pos.z); + CRASH_COND(dst_ri >= volume); + memset(&channel.data[dst_ri], defval, area_size.y * sizeof(uint8_t)); + } + } +} + +bool VoxelBuffer::is_uniform(unsigned int channel_index) const { + ERR_FAIL_INDEX_V(channel_index, MAX_CHANNELS, true); + + const Channel &channel = _channels[channel_index]; + if (channel.data == NULL) { + // Channel has been optimized + return true; + } + + // Channel isn't optimized, so must look at each voxel + uint8_t voxel = channel.data[0]; + unsigned int volume = get_volume(); + for (unsigned int i = 1; i < volume; ++i) { + if (channel.data[i] != voxel) { + return false; + } + } + + return true; +} + +void VoxelBuffer::compress_uniform_channels() { + for (unsigned int i = 0; i < MAX_CHANNELS; ++i) { + if (_channels[i].data && is_uniform(i)) { + clear_channel(i, _channels[i].data[0]); + } + } +} + +void VoxelBuffer::copy_from(const VoxelBuffer &other, unsigned int channel_index) { + ERR_FAIL_INDEX(channel_index, MAX_CHANNELS); + ERR_FAIL_COND(other._size == _size); + + Channel &channel = _channels[channel_index]; + const Channel &other_channel = other._channels[channel_index]; + + if (other_channel.data) { + if (channel.data == NULL) { + create_channel_noinit(channel_index, _size); + } + memcpy(channel.data, other_channel.data, get_volume() * sizeof(uint8_t)); + } else if (channel.data) { + delete_channel(channel_index); + } + + channel.defval = other_channel.defval; +} + +void VoxelBuffer::copy_from(const VoxelBuffer &other, Vector3i src_min, Vector3i src_max, Vector3i dst_min, unsigned int channel_index) { + + ERR_FAIL_INDEX(channel_index, MAX_CHANNELS); + + Channel &channel = _channels[channel_index]; + const Channel &other_channel = other._channels[channel_index]; + + Vector3i::sort_min_max(src_min, src_max); + + src_min.clamp_to(Vector3i(0, 0, 0), other._size); + src_max.clamp_to(Vector3i(0, 0, 0), other._size + Vector3i(1, 1, 1)); + + dst_min.clamp_to(Vector3i(0, 0, 0), _size); + Vector3i area_size = src_max - src_min; + //Vector3i dst_max = dst_min + area_size; + + if (area_size == _size) { + copy_from(other, channel_index); + } else { + if (other_channel.data) { + if (channel.data == NULL) { + create_channel(channel_index, _size, channel.defval); + } + // Copy row by row + Vector3i pos; + for (pos.z = 0; pos.z < area_size.z; ++pos.z) { + for (pos.x = 0; pos.x < area_size.x; ++pos.x) { + // Row direction is Y + unsigned int src_ri = other.index(pos.x + src_min.x, pos.y + src_min.y, pos.z + src_min.z); + unsigned int dst_ri = index(pos.x + dst_min.x, pos.y + dst_min.y, pos.z + dst_min.z); + memcpy(&channel.data[dst_ri], &other_channel.data[src_ri], area_size.y * sizeof(uint8_t)); + } + } + } else if (channel.defval != other_channel.defval) { + if (channel.data == NULL) { + create_channel(channel_index, _size, channel.defval); + } + // Set row by row + Vector3i pos; + for (pos.z = 0; pos.z < area_size.z; ++pos.z) { + for (pos.x = 0; pos.x < area_size.x; ++pos.x) { + unsigned int dst_ri = index(pos.x + dst_min.x, pos.y + dst_min.y, pos.z + dst_min.z); + memset(&channel.data[dst_ri], other_channel.defval, area_size.y * sizeof(uint8_t)); + } + } + } + } +} + +uint8_t *VoxelBuffer::get_channel_raw(unsigned int channel_index) const { + ERR_FAIL_INDEX_V(channel_index, MAX_CHANNELS, NULL); + const Channel &channel = _channels[channel_index]; + return channel.data; +} + +void VoxelBuffer::create_channel(int i, Vector3i size, uint8_t defval) { + create_channel_noinit(i, size); + memset(_channels[i].data, defval, get_volume() * sizeof(uint8_t)); +} + +void VoxelBuffer::create_channel_noinit(int i, Vector3i size) { + Channel &channel = _channels[i]; + unsigned int volume = size.x * size.y * size.z; + channel.data = (uint8_t *)memalloc(volume * sizeof(uint8_t)); +} + +void VoxelBuffer::delete_channel(int i) { + Channel &channel = _channels[i]; + ERR_FAIL_COND(channel.data == NULL); + memfree(channel.data); + channel.data = NULL; +} + +void VoxelBuffer::_bind_methods() { + + ClassDB::bind_method(D_METHOD("create", "sx", "sy", "sz"), &VoxelBuffer::create); + ClassDB::bind_method(D_METHOD("clear"), &VoxelBuffer::clear); + + ClassDB::bind_method(D_METHOD("get_size"), &VoxelBuffer::_get_size_binding); + ClassDB::bind_method(D_METHOD("get_size_x"), &VoxelBuffer::get_size_x); + ClassDB::bind_method(D_METHOD("get_size_y"), &VoxelBuffer::get_size_y); + ClassDB::bind_method(D_METHOD("get_size_z"), &VoxelBuffer::get_size_z); + + ClassDB::bind_method(D_METHOD("set_voxel", "value", "x", "y", "z", "channel"), &VoxelBuffer::_set_voxel_binding, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("set_voxel_f", "value", "x", "y", "z", "channel"), &VoxelBuffer::_set_voxel_f_binding, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("set_voxel_v", "value", "pos", "channel"), &VoxelBuffer::set_voxel_v, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_voxel", "x", "y", "z", "channel"), &VoxelBuffer::_get_voxel_binding, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_voxel_f", "x", "y", "z", "channel"), &VoxelBuffer::get_voxel_f, DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("fill", "value", "channel"), &VoxelBuffer::fill, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("fill_f", "value", "channel"), &VoxelBuffer::fill_f, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("fill_area", "value", "min", "max", "channel"), &VoxelBuffer::_fill_area_binding, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("copy_from", "other", "channel"), &VoxelBuffer::_copy_from_binding, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("copy_from_area", "other", "src_min", "src_max", "dst_min", "channel"), &VoxelBuffer::_copy_from_area_binding, DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("is_uniform", "channel"), &VoxelBuffer::is_uniform); + ClassDB::bind_method(D_METHOD("optimize"), &VoxelBuffer::compress_uniform_channels); + + BIND_ENUM_CONSTANT(CHANNEL_TYPE); + BIND_ENUM_CONSTANT(CHANNEL_ISOLEVEL); + BIND_ENUM_CONSTANT(CHANNEL_DATA2); + BIND_ENUM_CONSTANT(CHANNEL_DATA3); + BIND_ENUM_CONSTANT(CHANNEL_DATA4); + BIND_ENUM_CONSTANT(CHANNEL_DATA5); + BIND_ENUM_CONSTANT(CHANNEL_DATA6); + BIND_ENUM_CONSTANT(CHANNEL_DATA7); + BIND_ENUM_CONSTANT(MAX_CHANNELS); +} + +void VoxelBuffer::_copy_from_binding(Ref other, unsigned int channel) { + ERR_FAIL_COND(other.is_null()); + copy_from(**other, channel); +} + +void VoxelBuffer::_copy_from_area_binding(Ref other, Vector3 src_min, Vector3 src_max, Vector3 dst_min, unsigned int channel) { + ERR_FAIL_COND(other.is_null()); + copy_from(**other, Vector3i(src_min), Vector3i(src_max), Vector3i(dst_min), channel); +} diff --git a/world/voxel_buffer.h b/world/voxel_buffer.h new file mode 100644 index 0000000..f09352a --- /dev/null +++ b/world/voxel_buffer.h @@ -0,0 +1,170 @@ +/** +* +* Voxel Tools for Godot Engine +* +* Copyright(c) 2016 Marc Gilleron +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +* files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +* modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software +* is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +#ifndef VOXEL_BUFFER_H +#define VOXEL_BUFFER_H + +#include "math/vector3i.h" +#include +#include + +// Dense voxels data storage. +// Organized in 8-bit channels like images, all optional. +// Note: for float storage (marching cubes for example), you can map [0..256] to [0..1] and save 3 bytes per cell + +class VoxelBuffer : public Reference { + GDCLASS(VoxelBuffer, Reference) + +public: + enum ChannelId { + CHANNEL_TYPE = 0, + CHANNEL_ISOLEVEL, + CHANNEL_DATA2, + CHANNEL_DATA3, + CHANNEL_DATA4, + CHANNEL_DATA5, + CHANNEL_DATA6, + CHANNEL_DATA7, + // Arbitrary value, 8 should be enough. Tweak for your needs. + MAX_CHANNELS + }; + + // TODO use C++17 inline to initialize right here... + static const char *CHANNEL_ID_HINT_STRING; + + // TODO Quantification options + // enum ChannelFormat { + // FORMAT_I8_Q256U, // 0..255 integer + // FORMAT_F8_Q1S, // -1..1 float stored in 8 bits + // FORMAT_F16_Q128S // -128..128 float stored in 16 bits + // }; + + // Converts -1..1 float into 0..255 integer + static inline int iso_to_byte(real_t iso) { + int v = static_cast(128.f * iso + 128.f); + if (v > 255) + return 255; + else if (v < 0) + return 0; + return v; + } + + // Converts 0..255 integer into -1..1 float + static inline real_t byte_to_iso(int b) { + return static_cast(b - 128) / 128.f; + } + + VoxelBuffer(); + ~VoxelBuffer(); + + void create(int sx, int sy, int sz); + void clear(); + void clear_channel(unsigned int channel_index, int clear_value = 0); + _FORCE_INLINE_ void clear_channel_f(unsigned int channel_index, float clear_value = 0) { clear_channel(channel_index, iso_to_byte(clear_value)); } + + _FORCE_INLINE_ const Vector3i &get_size() const { return _size; } + + void set_default_values(uint8_t values[MAX_CHANNELS]); + + int get_voxel(int x, int y, int z, unsigned int channel_index = 0) const; + void set_voxel(int value, int x, int y, int z, unsigned int channel_index = 0); + void set_voxel_v(int value, Vector3 pos, unsigned int channel_index = 0); + + void try_set_voxel(int x, int y, int z, int value, unsigned int channel_index = 0); + + _FORCE_INLINE_ void set_voxel_f(real_t value, int x, int y, int z, unsigned int channel_index = 0) { set_voxel(iso_to_byte(value), x, y, z, channel_index); } + _FORCE_INLINE_ real_t get_voxel_f(int x, int y, int z, unsigned int channel_index = 0) const { return byte_to_iso(get_voxel(x, y, z, channel_index)); } + + _FORCE_INLINE_ int get_voxel(const Vector3i pos, unsigned int channel_index = 0) const { return get_voxel(pos.x, pos.y, pos.z, channel_index); } + _FORCE_INLINE_ void set_voxel(int value, const Vector3i pos, unsigned int channel_index = 0) { set_voxel(value, pos.x, pos.y, pos.z, channel_index); } + + void fill(int defval, unsigned int channel_index = 0); + _FORCE_INLINE_ void fill_f(float value, unsigned int channel = 0) { fill(iso_to_byte(value), channel); } + void fill_area(int defval, Vector3i min, Vector3i max, unsigned int channel_index = 0); + + bool is_uniform(unsigned int channel_index) const; + + void compress_uniform_channels(); + + void copy_from(const VoxelBuffer &other, unsigned int channel_index = 0); + void copy_from(const VoxelBuffer &other, Vector3i src_min, Vector3i src_max, Vector3i dst_min, unsigned int channel_index = 0); + + _FORCE_INLINE_ bool validate_pos(unsigned int x, unsigned int y, unsigned int z) const { + return x < _size.x && y < _size.y && z < _size.z; + } + + _FORCE_INLINE_ unsigned int index(unsigned int x, unsigned int y, unsigned int z) const { + return y + _size.y * (x + _size.x * z); + } + + // _FORCE_INLINE_ unsigned int row_index(unsigned int x, unsigned int y, unsigned int z) const { + // return _size.y * (x + _size.x * z); + // } + + _FORCE_INLINE_ unsigned int get_volume() const { + return _size.x * _size.y * _size.z; + } + + uint8_t *get_channel_raw(unsigned int channel_index) const; + +private: + void create_channel_noinit(int i, Vector3i size); + void create_channel(int i, Vector3i size, uint8_t defval); + void delete_channel(int i); + +protected: + static void _bind_methods(); + + _FORCE_INLINE_ int get_size_x() const { return _size.x; } + _FORCE_INLINE_ int get_size_y() const { return _size.y; } + _FORCE_INLINE_ int get_size_z() const { return _size.z; } + _FORCE_INLINE_ Vector3 _get_size_binding() const { return _size.to_vec3(); } + + _FORCE_INLINE_ int _get_voxel_binding(int x, int y, int z, unsigned int channel) const { return get_voxel(x, y, z, channel); } + _FORCE_INLINE_ void _set_voxel_binding(int value, int x, int y, int z, unsigned int channel) { set_voxel(value, x, y, z, channel); } + void _copy_from_binding(Ref other, unsigned int channel); + void _copy_from_area_binding(Ref other, Vector3 src_min, Vector3 src_max, Vector3 dst_min, unsigned int channel); + _FORCE_INLINE_ void _fill_area_binding(int defval, Vector3 min, Vector3 max, unsigned int channel_index) { fill_area(defval, Vector3i(min), Vector3i(max), channel_index); } + _FORCE_INLINE_ void _set_voxel_f_binding(real_t value, int x, int y, int z, unsigned int channel) { set_voxel_f(value, x, y, z, channel); } + +private: + struct Channel { + // Allocated when the channel is populated. + // Flat array, in order [z][x][y] because it allows faster vertical-wise access (the engine is Y-up). + uint8_t *data; + + // Default value when data is null + uint8_t defval; + + Channel() : + data(NULL), + defval(0) {} + }; + + // Each channel can store arbitary data. + // For example, you can decide to store colors (R, G, B, A), gameplay types (type, state, light) or both. + Channel _channels[MAX_CHANNELS]; + + // How many voxels are there in the three directions. All populated channels have the same size. + Vector3i _size; +}; + +VARIANT_ENUM_CAST(VoxelBuffer::ChannelId) + +#endif // VOXEL_BUFFER_H