Added importer to import vox files into a single mesh

- New importer, needs restart to switch but it's a limitation of Godot
- Might blow up if the scene spans a really big area, may be optimized in the future.
- The scene importer remains the default choice
- Promoted some ints to 64-bit to avoid oveflowing with large volumes or distances
- Handle a few cases of malloc failure
master
Marc Gilleron 2021-08-14 16:49:46 +01:00
parent 10f8e76db4
commit 026c14fcb5
17 changed files with 486 additions and 92 deletions

View File

@ -3,6 +3,8 @@
#include "../../meshers/voxel_mesher.h"
// Some common functions to vox importers
namespace VoxImportUtils {
Ref<Mesh> build_mesh(VoxelBuffer &voxels, VoxelMesher &mesher,

View File

@ -18,7 +18,7 @@ String VoxelVoxImporter::get_importer_name() const {
}
String VoxelVoxImporter::get_visible_name() const {
return "VoxelVoxImporter";
return "VoxelVoxSceneImporter";
}
void VoxelVoxImporter::get_recognized_extensions(List<String> *p_extensions) const {

View File

@ -3,6 +3,7 @@
#include <editor/import/editor_import_plugin.h>
// TODO Rename VoxelVoxSceneImporter
// Imports a vox file as a scene, where the internal scene layout is preserved as nodes
class VoxelVoxImporter : public ResourceImporter {
GDCLASS(VoxelVoxImporter, ResourceImporter)

View File

@ -2,6 +2,7 @@
#include "../../constants/voxel_string_names.h"
#include "../../meshers/cubes/voxel_mesher_cubes.h"
#include "../../storage/voxel_buffer.h"
#include "../../storage/voxel_memory_pool.h"
#include "../../streams/vox_data.h"
#include "../../util/profiling.h"
#include "vox_import_funcs.h"
@ -53,6 +54,161 @@ bool VoxelVoxMeshImporter::get_option_visibility(const String &p_option,
return true;
}
struct ForEachModelInstanceArgs {
const vox::Model *model;
// Pivot position, which turns out to be at the center in MagicaVoxel
Vector3i position;
Basis basis;
};
template <typename F>
static Error for_each_model_instance_in_scene_graph(
const vox::Data &data, int node_id, Transform transform, int depth, F &f) {
//
ERR_FAIL_COND_V(depth > 10, ERR_INVALID_DATA);
const vox::Node *vox_node = data.get_node(node_id);
switch (vox_node->type) {
case vox::Node::TYPE_TRANSFORM: {
const vox::TransformNode *vox_transform_node = reinterpret_cast<const vox::TransformNode *>(vox_node);
// Calculate global transform of the child
const Transform child_trans(
transform.basis * vox_transform_node->rotation.basis,
transform.xform(vox_transform_node->position.to_vec3()));
for_each_model_instance_in_scene_graph(data, vox_transform_node->child_node_id, child_trans, depth + 1, f);
} break;
case vox::Node::TYPE_GROUP: {
const vox::GroupNode *vox_group_node = reinterpret_cast<const vox::GroupNode *>(vox_node);
for (unsigned int i = 0; i < vox_group_node->child_node_ids.size(); ++i) {
const int child_node_id = vox_group_node->child_node_ids[i];
for_each_model_instance_in_scene_graph(data, child_node_id, transform, depth + 1, f);
}
} break;
case vox::Node::TYPE_SHAPE: {
const vox::ShapeNode *vox_shape_node = reinterpret_cast<const vox::ShapeNode *>(vox_node);
ForEachModelInstanceArgs args;
args.model = &data.get_model(vox_shape_node->model_id);
args.position = Vector3i::from_rounded(transform.origin);
args.basis = transform.basis;
f(args);
} break;
default:
ERR_FAIL_V(ERR_INVALID_DATA);
break;
}
return OK;
}
template <typename F>
void for_each_model_instance(const vox::Data &vox_data, F &f) {
if (vox_data.get_model_count() == 0) {
return;
}
if (vox_data.get_root_node_id() == -1) {
// No scene graph
ForEachModelInstanceArgs args;
args.model = &vox_data.get_model(0);
// Put at center to match what MagicaVoxel would do
args.position = args.model->size / 2;
args.basis = Basis();
f(args);
return;
}
for_each_model_instance_in_scene_graph(vox_data, vox_data.get_root_node_id(), Transform(), 0, f);
}
// Find intersecting or touching models, merge their voxels into the same grid, mesh the result, then combine meshes.
struct ModelInstance {
// Model with baked rotation
Ref<VoxelBuffer> voxels;
// Lowest corner position
Vector3i position;
};
static void extract_model_instances(const vox::Data &vox_data, std::vector<ModelInstance> &out_instances) {
// Gather all models and bake their rotations
for_each_model_instance(vox_data, [&out_instances](ForEachModelInstanceArgs args) {
ERR_FAIL_COND(args.model == nullptr);
const vox::Model &model = *args.model;
Span<const uint8_t> src_color_indices;
Vector3i dst_size = model.size;
// Using temporary copy to rotate the data
std::vector<uint8_t> temp_voxels;
if (args.basis == Basis()) {
// No transformation
src_color_indices = to_span_const(model.color_indexes);
} else {
IntBasis basis;
basis.x = args.basis.get_axis(Vector3::AXIS_X);
basis.y = args.basis.get_axis(Vector3::AXIS_Y);
basis.z = args.basis.get_axis(Vector3::AXIS_Z);
temp_voxels.resize(model.color_indexes.size());
dst_size = transform_3d_array_zxy(
to_span_const(model.color_indexes), to_span(temp_voxels), model.size, basis);
src_color_indices = to_span_const(temp_voxels);
}
// TODO Optimization: implement transformation for VoxelBuffers so we can avoid using a temporary copy.
// Didn't do it yet because VoxelBuffers also have metadata and the `transform_3d_array_zxy` function only works on arrays.
Ref<VoxelBuffer> voxels;
voxels.instance();
voxels->create(dst_size);
voxels->decompress_channel(VoxelBuffer::CHANNEL_COLOR);
Span<uint8_t> dst_color_indices;
ERR_FAIL_COND(!voxels->get_channel_raw(VoxelBuffer::CHANNEL_COLOR, dst_color_indices));
CRASH_COND(src_color_indices.size() != dst_color_indices.size());
memcpy(dst_color_indices.data(), src_color_indices.data(), dst_color_indices.size() * sizeof(uint8_t));
ModelInstance mi;
mi.voxels = voxels;
mi.position = args.position - voxels->get_size() / 2;
out_instances.push_back(mi);
});
}
static Ref<VoxelBuffer> make_single_voxel_grid(Span<const ModelInstance> instances) {
// Determine total size
const ModelInstance &first_instance = instances[0];
Box3i bounding_box(first_instance.position, first_instance.voxels->get_size());
for (unsigned int instance_index = 1; instance_index < instances.size(); ++instance_index) {
const ModelInstance &mi = instances[instance_index];
bounding_box.merge_with(Box3i(mi.position, mi.voxels->get_size()));
}
// Extra sanity check
// 3 gigabytes
const size_t limit = 3'000'000'000ull;
const size_t volume = bounding_box.size.volume();
ERR_FAIL_COND_V_MSG(volume > limit, Ref<VoxelBuffer>(),
String("Vox data is too big to be meshed as a single mesh ({0}: {0} bytes)")
.format(varray(bounding_box.size.to_vec3(), volume)));
Ref<VoxelBuffer> voxels;
voxels.instance();
voxels->create(bounding_box.size + Vector3i(VoxelMesherCubes::PADDING * 2));
voxels->set_channel_depth(VoxelBuffer::CHANNEL_COLOR, VoxelBuffer::DEPTH_8_BIT);
voxels->decompress_channel(VoxelBuffer::CHANNEL_COLOR);
for (unsigned int instance_index = 0; instance_index < instances.size(); ++instance_index) {
const ModelInstance &mi = instances[instance_index];
voxels->copy_from(**mi.voxels, Vector3i(), mi.voxels->get_size(),
mi.position - bounding_box.pos + Vector3i(VoxelMesherCubes::PADDING),
VoxelBuffer::CHANNEL_COLOR);
}
return voxels;
}
Error VoxelVoxMeshImporter::import(const String &p_source_file, const String &p_save_path,
const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files,
Variant *r_metadata) {
@ -60,58 +216,54 @@ Error VoxelVoxMeshImporter::import(const String &p_source_file, const String &p_
const bool p_store_colors_in_textures = p_options[VoxelStringNames::get_singleton()->store_colors_in_texture];
const float p_scale = p_options[VoxelStringNames::get_singleton()->scale];
vox::Data data;
const Error load_err = data.load_from_file(p_source_file);
vox::Data vox_data;
const Error load_err = vox_data.load_from_file(p_source_file);
ERR_FAIL_COND_V(load_err != OK, load_err);
// Get color palette
Ref<VoxelColorPalette> palette;
palette.instance();
for (unsigned int i = 0; i < data.get_palette().size(); ++i) {
Color8 color = data.get_palette()[i];
for (unsigned int i = 0; i < vox_data.get_palette().size(); ++i) {
const Color8 color = vox_data.get_palette()[i];
palette->set_color8(i, color);
}
Ref<VoxelMesherCubes> mesher;
mesher.instance();
mesher->set_color_mode(VoxelMesherCubes::COLOR_MESHER_PALETTE);
mesher->set_palette(palette);
mesher->set_greedy_meshing_enabled(true);
mesher->set_store_colors_in_texture(p_store_colors_in_textures);
FixedArray<Ref<SpatialMaterial>, 2> materials;
for (unsigned int i = 0; i < materials.size(); ++i) {
Ref<SpatialMaterial> &mat = materials[i];
mat.instance();
mat->set_roughness(1.f);
if (!p_store_colors_in_textures) {
// In this case we store colors in vertices
mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
}
}
materials[1]->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
if (data.get_model_count() == 0) {
if (vox_data.get_model_count() == 0) {
// wut
return ERR_CANT_CREATE;
}
// TODO Merge all models into one
const vox::Model &model = data.get_model(0);
Ref<VoxelBuffer> voxels;
voxels.instance();
voxels->create(model.size + Vector3i(VoxelMesherCubes::PADDING * 2));
voxels->decompress_channel(VoxelBuffer::CHANNEL_COLOR);
Span<uint8_t> dst_color_indices;
ERR_FAIL_COND_V(!voxels->get_channel_raw(VoxelBuffer::CHANNEL_COLOR, dst_color_indices), ERR_BUG);
Span<const uint8_t> src_color_indices = to_span_const(model.color_indexes);
copy_3d_region_zxy(dst_color_indices, voxels->get_size(), Vector3i(VoxelMesherCubes::PADDING),
src_color_indices, model.size, Vector3i(), model.size);
std::vector<unsigned int> surface_index_to_material;
Ref<Image> atlas;
Ref<Mesh> mesh = VoxImportUtils::build_mesh(**voxels, **mesher, surface_index_to_material, atlas, p_scale);
Ref<Mesh> mesh;
std::vector<unsigned int> surface_index_to_material;
{
std::vector<ModelInstance> model_instances;
extract_model_instances(vox_data, model_instances);
// From this point we no longer need vox data so we can free some memory
vox_data.clear();
// TODO Optimization: this approach uses a lot of memory, might fail on scenes with a large bounding box.
// One workaround would be to mesh the scene incrementally in chunks, giving up greedy meshing beyond 256 or so.
Ref<VoxelBuffer> voxels = make_single_voxel_grid(to_span_const(model_instances));
ERR_FAIL_COND_V(voxels.is_null(), ERR_CANT_CREATE);
// We no longer need these
model_instances.clear();
Ref<VoxelMesherCubes> mesher;
mesher.instance();
mesher->set_color_mode(VoxelMesherCubes::COLOR_MESHER_PALETTE);
mesher->set_palette(palette);
mesher->set_greedy_meshing_enabled(true);
mesher->set_store_colors_in_texture(p_store_colors_in_textures);
mesh = VoxImportUtils::build_mesh(**voxels, **mesher, surface_index_to_material, atlas, p_scale);
// Deallocate large temporary memory to free space.
// This is a workaround because VoxelBuffer uses this by default, however it doesn't fit the present use case.
// Eventually we should avoid using this pool here.
VoxelMemoryPool::get_singleton()->clear_unused_blocks();
}
if (mesh.is_null()) {
// wut
@ -134,6 +286,18 @@ Error VoxelVoxMeshImporter::import(const String &p_source_file, const String &p_
// atlas->save_png(String("debug_atlas{0}.png").format(varray(model_index)));
// }
FixedArray<Ref<SpatialMaterial>, 2> materials;
for (unsigned int i = 0; i < materials.size(); ++i) {
Ref<SpatialMaterial> &mat = materials[i];
mat.instance();
mat->set_roughness(1.f);
if (!p_store_colors_in_textures) {
// In this case we store colors in vertices
mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
}
}
materials[1]->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
// Assign materials
if (p_store_colors_in_textures) {
// Can't share materials at the moment, because each atlas is specific to its mesh

View File

@ -53,6 +53,8 @@ public:
bool supports_lod() const override { return true; }
// Structs
// Using std::vector because they make this mesher twice as fast than Godot Vectors.
// See why: https://github.com/godotengine/godot/issues/24731
struct Arrays {

View File

@ -182,4 +182,68 @@ inline void debug_check_texture_indices(FixedArray<uint8_t, 4> indices) {
}
}
struct IntBasis {
Vector3i x;
Vector3i y;
Vector3i z;
Vector3i get_axis(int i) const {
// TODO Optimization: could use a union with an array
switch (i) {
case Vector3i::AXIS_X:
return x;
case Vector3i::AXIS_Y:
return y;
case Vector3i::AXIS_Z:
return z;
default:
CRASH_NOW();
}
}
};
// Rotates/flips/transposes the contents of a 3D array using a basis.
// Returns the transformed size. Volume remains the same.
// The array's coordinate convention uses ZXY (index+1 does Y+1).
template <typename T>
Vector3i transform_3d_array_zxy(Span<const T> src_grid, Span<T> dst_grid, Vector3i src_size, IntBasis basis) {
ERR_FAIL_COND_V(!basis.x.is_unit_vector(), src_size);
ERR_FAIL_COND_V(!basis.y.is_unit_vector(), src_size);
ERR_FAIL_COND_V(!basis.z.is_unit_vector(), src_size);
ERR_FAIL_COND_V(src_grid.size() != src_size.volume(), src_size);
ERR_FAIL_COND_V(dst_grid.size() != src_size.volume(), src_size);
const int xa = basis.x.x != 0 ? 0 : basis.x.y != 0 ? 1 : 2;
const int ya = basis.y.x != 0 ? 0 : basis.y.y != 0 ? 1 : 2;
const int za = basis.z.x != 0 ? 0 : basis.z.y != 0 ? 1 : 2;
Vector3i dst_size;
dst_size[xa] = src_size.x;
dst_size[ya] = src_size.y;
dst_size[za] = src_size.z;
// If an axis is negative, it means iteration starts from the end
const int ox = basis.get_axis(xa).x < 0 ? dst_size.x - 1 : 0;
const int oy = basis.get_axis(ya).y < 0 ? dst_size.y - 1 : 0;
const int oz = basis.get_axis(za).z < 0 ? dst_size.z - 1 : 0;
int src_i = 0;
for (int z = 0; z < src_size.z; ++z) {
for (int x = 0; x < src_size.x; ++x) {
for (int y = 0; y < src_size.y; ++y) {
// TODO Optimization: can be moved in the outer loop, we only need to add a number to dst_i
const int dst_x = ox + x * basis.x.x + y * basis.y.x + z * basis.z.x;
const int dst_y = oy + x * basis.x.y + y * basis.y.y + z * basis.z.y;
const int dst_z = oz + x * basis.x.z + y * basis.y.z + z * basis.z.z;
const int dst_i = dst_y + dst_size.y * (dst_x + dst_size.x * dst_z);
dst_grid[dst_i] = src_grid[src_i];
++src_i;
}
}
}
return dst_size;
}
#endif // VOXEL_STORAGE_FUNCS_H

View File

@ -17,7 +17,7 @@
namespace {
inline uint8_t *allocate_channel_data(uint32_t size) {
inline uint8_t *allocate_channel_data(size_t size) {
#ifdef VOXEL_BUFFER_USE_MEMORY_POOL
return VoxelMemoryPool::get_singleton()->allocate(size);
#else
@ -148,7 +148,7 @@ void VoxelBuffer::create(unsigned int sx, unsigned int sy, unsigned int sz) {
if (channel.data) {
// Channel already contained data
delete_channel(i);
create_channel(i, new_size, channel.defval);
ERR_FAIL_COND(!create_channel(i, new_size, channel.defval));
}
}
_size = new_size;
@ -235,7 +235,7 @@ void VoxelBuffer::set_voxel(uint64_t value, int x, int y, int z, unsigned int ch
if (channel.data == nullptr) {
if (channel.defval != value) {
// Allocate channel with same initial values as defval
create_channel(channel_index, _size, channel.defval);
ERR_FAIL_COND(!create_channel(channel_index, _size, channel.defval));
} else {
do_set = false;
}
@ -297,7 +297,7 @@ void VoxelBuffer::fill(uint64_t defval, unsigned int channel_index) {
}
}
const unsigned int volume = get_volume();
const size_t volume = get_volume();
switch (channel.depth) {
case DEPTH_8_BIT:
@ -305,19 +305,19 @@ void VoxelBuffer::fill(uint64_t defval, unsigned int channel_index) {
break;
case DEPTH_16_BIT:
for (uint32_t i = 0; i < volume; ++i) {
for (size_t i = 0; i < volume; ++i) {
reinterpret_cast<uint16_t *>(channel.data)[i] = defval;
}
break;
case DEPTH_32_BIT:
for (uint32_t i = 0; i < volume; ++i) {
for (size_t i = 0; i < volume; ++i) {
reinterpret_cast<uint32_t *>(channel.data)[i] = defval;
}
break;
case DEPTH_64_BIT:
for (uint32_t i = 0; i < volume; ++i) {
for (size_t i = 0; i < volume; ++i) {
reinterpret_cast<uint64_t *>(channel.data)[i] = defval;
}
break;
@ -346,15 +346,15 @@ void VoxelBuffer::fill_area(uint64_t defval, Vector3i min, Vector3i max, unsigne
if (channel.defval == defval) {
return;
} else {
create_channel(channel_index, _size, channel.defval);
ERR_FAIL_COND(!create_channel(channel_index, _size, channel.defval));
}
}
Vector3i pos;
const unsigned int volume = get_volume();
const size_t 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) {
const unsigned int dst_ri = get_index(pos.x, pos.y + min.y, pos.z);
const size_t dst_ri = get_index(pos.x, pos.y + min.y, pos.z);
CRASH_COND(dst_ri >= volume);
switch (channel.depth) {
@ -401,7 +401,7 @@ void VoxelBuffer::fill_f(real_t value, unsigned int channel) {
}
template <typename T>
inline bool is_uniform_b(const uint8_t *data, unsigned int item_count) {
inline bool is_uniform_b(const uint8_t *data, size_t item_count) {
return is_uniform<T>(reinterpret_cast<const T *>(data), item_count);
}
@ -414,7 +414,7 @@ bool VoxelBuffer::is_uniform(unsigned int channel_index) const {
return true;
}
const unsigned int volume = get_volume();
const size_t volume = get_volume();
// Channel isn't optimized, so must look at each voxel
switch (channel.depth) {
@ -448,7 +448,7 @@ void VoxelBuffer::decompress_channel(unsigned int channel_index) {
ERR_FAIL_INDEX(channel_index, MAX_CHANNELS);
Channel &channel = _channels[channel_index];
if (channel.data == nullptr) {
create_channel(channel_index, _size, channel.defval);
ERR_FAIL_COND(!create_channel(channel_index, _size, channel.defval));
}
}
@ -485,7 +485,7 @@ void VoxelBuffer::copy_from(const VoxelBuffer &other, unsigned int channel_index
if (other_channel.data != nullptr) {
if (channel.data == nullptr) {
create_channel_noinit(channel_index, _size);
ERR_FAIL_COND(!create_channel_noinit(channel_index, _size));
}
CRASH_COND(channel.size_in_bytes != other_channel.size_in_bytes);
memcpy(channel.data, other_channel.data, channel.size_in_bytes);
@ -517,7 +517,7 @@ void VoxelBuffer::copy_from(const VoxelBuffer &other, Vector3i src_min, Vector3i
if (channel.data == nullptr) {
// Note, we do this even if the pasted data happens to be all the same value as our current channel.
// We assume that this case is not frequent enough to bother, and compression can happen later
create_channel(channel_index, _size, channel.defval);
ERR_FAIL_COND(!create_channel(channel_index, _size, channel.defval));
}
const unsigned int item_size = get_depth_byte_count(channel.depth);
Span<const uint8_t> src(other_channel.data, other_channel.size_in_bytes);
@ -561,9 +561,12 @@ bool VoxelBuffer::get_channel_raw(unsigned int channel_index, Span<uint8_t> &sli
return false;
}
void VoxelBuffer::create_channel(int i, Vector3i size, uint64_t defval) {
create_channel_noinit(i, size);
bool VoxelBuffer::create_channel(int i, Vector3i size, uint64_t defval) {
if (!create_channel_noinit(i, size)) {
return false;
}
fill(defval, i);
return true;
}
uint32_t VoxelBuffer::get_size_in_bytes_for_volume(Vector3i size, Depth depth) {
@ -574,12 +577,14 @@ uint32_t VoxelBuffer::get_size_in_bytes_for_volume(Vector3i size, Depth depth) {
return size_in_bytes;
}
void VoxelBuffer::create_channel_noinit(int i, Vector3i size) {
bool VoxelBuffer::create_channel_noinit(int i, Vector3i size) {
Channel &channel = _channels[i];
uint32_t size_in_bytes = get_size_in_bytes_for_volume(size, channel.depth);
size_t size_in_bytes = get_size_in_bytes_for_volume(size, channel.depth);
CRASH_COND(channel.data != nullptr);
channel.data = allocate_channel_data(size_in_bytes);
ERR_FAIL_COND_V(channel.data == nullptr, false);
channel.size_in_bytes = size_in_bytes;
return true;
}
void VoxelBuffer::delete_channel(int i) {
@ -669,7 +674,7 @@ bool VoxelBuffer::equals(const VoxelBuffer &p_other) const {
} else {
ERR_FAIL_COND_V(channel.size_in_bytes != other_channel.size_in_bytes, false);
for (unsigned int i = 0; i < channel.size_in_bytes; ++i) {
for (size_t i = 0; i < channel.size_in_bytes; ++i) {
if (channel.data[i] != other_channel.data[i]) {
return false;
}

View File

@ -222,11 +222,11 @@ public:
}
}
static _FORCE_INLINE_ unsigned int get_index(const Vector3i pos, const Vector3i size) {
static _FORCE_INLINE_ size_t get_index(const Vector3i pos, const Vector3i size) {
return pos.get_zxy_index(size);
}
_FORCE_INLINE_ unsigned int get_index(unsigned int x, unsigned int y, unsigned int z) const {
_FORCE_INLINE_ size_t get_index(unsigned int x, unsigned int y, unsigned int z) const {
return y + _size.y * (x + _size.x * z); // ZXY index
}
@ -238,7 +238,7 @@ public:
for (pos.z = min_pos.z; pos.z < max_pos.z; ++pos.z) {
for (pos.x = min_pos.x; pos.x < max_pos.x; ++pos.x) {
pos.y = min_pos.y;
unsigned int i = get_index(pos.x, pos.y, pos.z);
size_t i = get_index(pos.x, pos.y, pos.z);
for (; pos.y < max_pos.y; ++pos.y) {
f(i, pos);
++i;
@ -258,7 +258,7 @@ public:
#endif
Span<Data_T> data = Span<uint8_t>(channel.data, channel.size_in_bytes)
.reinterpret_cast_to<Data_T>();
for_each_index_and_pos(box, [data, action_func, offset](unsigned int i, Vector3i pos) {
for_each_index_and_pos(box, [data, action_func, offset](size_t i, Vector3i pos) {
data[i] = action_func(pos + offset, data[i]);
});
}
@ -280,7 +280,7 @@ public:
.reinterpret_cast_to<Data0_T>();
Span<Data1_T> data1 = Span<uint8_t>(channel1.data, channel1.size_in_bytes)
.reinterpret_cast_to<Data1_T>();
for_each_index_and_pos(box, [action_func, offset, &data0, &data1](unsigned int i, Vector3i pos) {
for_each_index_and_pos(box, [action_func, offset, &data0, &data1](size_t i, Vector3i pos) {
// TODO The caller must still specify exactly the correct type, maybe some conversion could be used
action_func(pos + offset, data0[i], data1[i]);
});
@ -373,8 +373,8 @@ public:
return Box3i(Vector3i(), _size).contains(box);
}
_FORCE_INLINE_ unsigned int get_volume() const {
return _size.x * _size.y * _size.z;
_FORCE_INLINE_ uint64_t get_volume() const {
return _size.volume();
}
// TODO Have a template version based on channel depth
@ -432,8 +432,8 @@ public:
Ref<Image> debug_print_sdf_to_image_top_down();
private:
void create_channel_noinit(int i, Vector3i size);
void create_channel(int i, Vector3i size, uint64_t defval);
bool create_channel_noinit(int i, Vector3i size);
bool create_channel(int i, Vector3i size, uint64_t defval);
void delete_channel(int i);
static void _bind_methods();

View File

@ -39,7 +39,7 @@ VoxelMemoryPool::~VoxelMemoryPool() {
clear();
}
uint8_t *VoxelMemoryPool::allocate(uint32_t size) {
uint8_t *VoxelMemoryPool::allocate(size_t size) {
VOXEL_PROFILE_SCOPE();
MutexLock lock(_mutex);
Pool *pool = get_or_create_pool(size);
@ -49,12 +49,13 @@ uint8_t *VoxelMemoryPool::allocate(uint32_t size) {
pool->blocks.pop_back();
} else {
block = (uint8_t *)memalloc(size * sizeof(uint8_t));
ERR_FAIL_COND_V(block == nullptr, nullptr);
}
++_used_blocks;
return block;
}
void VoxelMemoryPool::recycle(uint8_t *block, uint32_t size) {
void VoxelMemoryPool::recycle(uint8_t *block, size_t size) {
MutexLock lock(_mutex);
Pool *pool = _pools[size]; // If not found, entry will be created! It would be an error
// Check recycling before having allocated
@ -63,9 +64,24 @@ void VoxelMemoryPool::recycle(uint8_t *block, uint32_t size) {
--_used_blocks;
}
void VoxelMemoryPool::clear_unused_blocks() {
MutexLock lock(_mutex);
const size_t *key = nullptr;
while ((key = _pools.next(key))) {
Pool *pool = _pools.get(*key);
CRASH_COND(pool == nullptr);
for (auto it = pool->blocks.begin(); it != pool->blocks.end(); ++it) {
uint8_t *ptr = *it;
CRASH_COND(ptr == nullptr);
memfree(ptr);
}
pool->blocks.clear();
}
}
void VoxelMemoryPool::clear() {
MutexLock lock(_mutex);
const uint32_t *key = nullptr;
const size_t *key = nullptr;
while ((key = _pools.next(key))) {
Pool *pool = _pools.get(*key);
CRASH_COND(pool == nullptr);
@ -84,12 +100,12 @@ void VoxelMemoryPool::debug_print() {
if (_pools.size() == 0) {
print_line("No pools created");
} else {
const uint32_t *key = nullptr;
const size_t *key = nullptr;
int i = 0;
while ((key = _pools.next(key))) {
Pool *pool = _pools.get(*key);
print_line(String("Pool {0} for size {1}: {2} blocks")
.format(varray(i, *key, SIZE_T_TO_VARIANT(pool->blocks.size()))));
.format(varray(i, SIZE_T_TO_VARIANT(*key), SIZE_T_TO_VARIANT(pool->blocks.size()))));
++i;
}
}
@ -100,7 +116,7 @@ unsigned int VoxelMemoryPool::debug_get_used_blocks() const {
return _used_blocks;
}
VoxelMemoryPool::Pool *VoxelMemoryPool::get_or_create_pool(uint32_t size) {
VoxelMemoryPool::Pool *VoxelMemoryPool::get_or_create_pool(size_t size) {
Pool *pool;
Pool **ppool = _pools.getptr(size);
if (ppool == nullptr) {

View File

@ -22,17 +22,19 @@ public:
VoxelMemoryPool();
~VoxelMemoryPool();
uint8_t *allocate(uint32_t size);
void recycle(uint8_t *block, uint32_t size);
uint8_t *allocate(size_t size);
void recycle(uint8_t *block, size_t size);
void clear_unused_blocks();
void debug_print();
unsigned int debug_get_used_blocks() const;
private:
Pool *get_or_create_pool(uint32_t size);
Pool *get_or_create_pool(size_t size);
void clear();
HashMap<uint32_t, Pool *> _pools;
HashMap<size_t, Pool *> _pools;
unsigned int _used_blocks = 0;
Mutex _mutex;
};

View File

@ -183,6 +183,7 @@ void Data::clear() {
_models.clear();
_scene_graph.clear();
_layers.clear();
_materials.clear();
_root_node_id = -1;
}

View File

@ -55,6 +55,7 @@ struct Rotation {
struct TransformNode : public Node {
int child_node_id;
int layer_id;
// Pivot position, which turns out to be at the center in MagicaVoxel
Vector3i position;
Rotation rotation;
String name;

View File

@ -759,6 +759,105 @@ void test_instance_data_serialization() {
}
}
void test_transform_3d_array_zxy() {
// YXZ
int src_grid[] = {
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15,
16, 17, 18, 19,
20, 21, 22, 23
};
const Vector3i src_size(3, 4, 2);
const int volume = src_size.volume();
FixedArray<int, 24> dst_grid;
ERR_FAIL_COND(dst_grid.size() != volume);
{
int expected_dst_grid[] = {
0, 4, 8,
1, 5, 9,
2, 6, 10,
3, 7, 11,
12, 16, 20,
13, 17, 21,
14, 18, 22,
15, 19, 23
};
const Vector3i expected_dst_size(4, 3, 2);
IntBasis basis;
basis.x = Vector3i(0, 1, 0);
basis.y = Vector3i(1, 0, 0);
basis.z = Vector3i(0, 0, 1);
const Vector3i dst_size = transform_3d_array_zxy(
Span<const int>(src_grid, 0, volume),
to_span(dst_grid), src_size, basis);
ERR_FAIL_COND(dst_size != expected_dst_size);
for (unsigned int i = 0; i < volume; ++i) {
ERR_FAIL_COND(dst_grid[i] != expected_dst_grid[i]);
}
}
{
int expected_dst_grid[] = {
3, 2, 1, 0,
7, 6, 5, 4,
11, 10, 9, 8,
15, 14, 13, 12,
19, 18, 17, 16,
23, 22, 21, 20
};
const Vector3i expected_dst_size(3, 4, 2);
IntBasis basis;
basis.x = Vector3i(1, 0, 0);
basis.y = Vector3i(0, -1, 0);
basis.z = Vector3i(0, 0, 1);
const Vector3i dst_size = transform_3d_array_zxy(
Span<const int>(src_grid, 0, volume),
to_span(dst_grid), src_size, basis);
ERR_FAIL_COND(dst_size != expected_dst_size);
for (unsigned int i = 0; i < volume; ++i) {
ERR_FAIL_COND(dst_grid[i] != expected_dst_grid[i]);
}
}
{
int expected_dst_grid[] = {
15, 14, 13, 12,
19, 18, 17, 16,
23, 22, 21, 20,
3, 2, 1, 0,
7, 6, 5, 4,
11, 10, 9, 8
};
const Vector3i expected_dst_size(3, 4, 2);
IntBasis basis;
basis.x = Vector3i(1, 0, 0);
basis.y = Vector3i(0, -1, 0);
basis.z = Vector3i(0, 0, -1);
const Vector3i dst_size = transform_3d_array_zxy(
Span<const int>(src_grid, 0, volume),
to_span(dst_grid), src_size, basis);
ERR_FAIL_COND(dst_size != expected_dst_size);
for (unsigned int i = 0; i < volume; ++i) {
ERR_FAIL_COND(dst_grid[i] != expected_dst_grid[i]);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define VOXEL_TEST(fname) \
@ -779,6 +878,7 @@ void run_voxel_tests() {
VOXEL_TEST(test_island_finder);
VOXEL_TEST(test_unordered_remove_if);
VOXEL_TEST(test_instance_data_serialization);
VOXEL_TEST(test_transform_3d_array_zxy);
print_line("------------ Voxel tests end -------------");
}

View File

@ -139,7 +139,7 @@ inline void sort(T &a, T &b, T &c, T &d) {
// Tests if POD items in an array are all the same.
// Better tailored for more than hundred items that have power-of-two size.
template <typename Item_T>
inline bool is_uniform(const Item_T *p_data, uint32_t item_count) {
inline bool is_uniform(const Item_T *p_data, size_t item_count) {
const Item_T v0 = p_data[0];
//typedef size_t Bucket_T;
@ -152,36 +152,36 @@ inline bool is_uniform(const Item_T *p_data, uint32_t item_count) {
};
if (sizeof(Bucket_T) > sizeof(Item_T) && sizeof(Bucket_T) % sizeof(Item_T) == 0) {
static const unsigned int ITEMS_PER_BUCKET = sizeof(Bucket_T) / sizeof(Item_T);
static const size_t ITEMS_PER_BUCKET = sizeof(Bucket_T) / sizeof(Item_T);
// Make a reference bucket
union {
Bucket_T packed_items;
Item_T items[ITEMS_PER_BUCKET];
} reference_bucket;
for (unsigned int i = 0; i < ITEMS_PER_BUCKET; ++i) {
for (size_t i = 0; i < ITEMS_PER_BUCKET; ++i) {
reference_bucket.items[i] = v0;
}
// Compare using buckets of items rather than individual items
const unsigned int bucket_count = item_count / ITEMS_PER_BUCKET;
const size_t bucket_count = item_count / ITEMS_PER_BUCKET;
const Bucket_T *buckets = (const Bucket_T *)p_data;
for (unsigned int i = 0; i < bucket_count; ++i) {
for (size_t i = 0; i < bucket_count; ++i) {
if (buckets[i] != reference_bucket.packed_items) {
return false;
}
}
// Compare last elements individually if they don't fit in a bucket
const unsigned int remaining_items_start = item_count - (item_count % ITEMS_PER_BUCKET);
for (unsigned int i = remaining_items_start; i < item_count; ++i) {
const size_t remaining_items_start = item_count - (item_count % ITEMS_PER_BUCKET);
for (size_t i = remaining_items_start; i < item_count; ++i) {
if (p_data[i] != v0) {
return false;
}
}
} else {
for (unsigned int i = 1; i < item_count; ++i) {
for (size_t i = 1; i < item_count; ++i) {
if (p_data[i] != v0) {
return false;
}

View File

@ -334,10 +334,27 @@ public:
inline bool is_empty() const {
return size.x <= 0 || size.y <= 0 || size.z <= 0;
}
void merge_with(const Box3i &other) {
const Vector3i min_pos(
min(pos.x, other.pos.x),
min(pos.y, other.pos.y),
min(pos.z, other.pos.z));
const Vector3i max_pos(
max(pos.x + size.x, other.pos.x + other.size.x),
max(pos.y + size.y, other.pos.y + other.size.y),
max(pos.z + size.z, other.pos.z + other.size.z));
pos = min_pos;
size = max_pos - min_pos;
}
};
inline bool operator!=(const Box3i &a, const Box3i &b) {
return a.pos != b.pos || a.size != b.size;
}
inline bool operator==(const Box3i &a, const Box3i &b) {
return a.pos == b.pos && a.size == b.size;
}
#endif // BOX3I_H

View File

@ -93,6 +93,8 @@ inline T squared(const T x) {
// Performs euclidean division, aka floored division.
// This implementation expects a strictly positive divisor.
//
// Example with division by 3:
//
// x | `/` | floordiv
// ----------------------
// -6 | -2 | -2

View File

@ -55,23 +55,36 @@ struct Vector3i {
Math::floor(f.z));
}
static inline Vector3i from_rounded(const Vector3 &f) {
return Vector3i(
Math::round(f.x),
Math::round(f.y),
Math::round(f.z));
}
_FORCE_INLINE_ Vector3 to_vec3() const {
return Vector3(x, y, z);
}
_FORCE_INLINE_ int volume() const {
// Returning a 64-bit integer because volumes can quickly overflow INT_MAX (like 1300^3),
// even though dense volumes of that size will rarely be encountered in this module
_FORCE_INLINE_ int64_t volume() const {
#ifdef DEBUG_ENABLED
ERR_FAIL_COND_V(x < 0 || y < 0 || z < 0, 0);
#endif
return x * y * z;
}
_FORCE_INLINE_ int length_sq() const {
// Returning a 64-bit integer because squared distances can quickly overflow INT_MAX
_FORCE_INLINE_ int64_t length_sq() const {
return x * x + y * y + z * z;
}
_FORCE_INLINE_ real_t length() const {
return Math::sqrt((real_t)length_sq());
return Math::sqrt(real_t(length_sq()));
}
_FORCE_INLINE_ int distance_sq(const Vector3i &other) const;
_FORCE_INLINE_ int64_t distance_sq(const Vector3i &other) const;
_FORCE_INLINE_ Vector3i &operator=(const Vector3i &other) {
x = other.x;
@ -183,6 +196,10 @@ struct Vector3i {
return x == y && y == z;
}
inline bool is_unit_vector() const {
return Math::abs(x) + Math::abs(y) + Math::abs(z) == 1;
}
static inline Vector3i min(const Vector3i a, const Vector3i b) {
return Vector3i(::min(a.x, b.x), ::min(a.y, b.y), ::min(a.z, b.z));
}
@ -258,7 +275,7 @@ _FORCE_INLINE_ bool operator<(const Vector3i &a, const Vector3i &b) {
}
}
_FORCE_INLINE_ int Vector3i::distance_sq(const Vector3i &other) const {
_FORCE_INLINE_ int64_t Vector3i::distance_sq(const Vector3i &other) const {
return (other - *this).length_sq();
}