Skip to content

Commit

Permalink
Added function to convert an image into a 1-voxel thick "sprite mesh"
Browse files Browse the repository at this point in the history
  • Loading branch information
Zylann committed Apr 17, 2023
1 parent da9131d commit 2064027
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 34 deletions.
2 changes: 2 additions & 0 deletions doc/source/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Semver is not yet in place, so each version can have breaking changes, although
- Separated `paste` into `paste` and `paste_masked` functions. The latter performs masking using a specific channel and value.
- `VoxelToolLodTerrain`:
- Added support for `paste`
- `VoxelMesherCubes`:
- Added helper function to convert an image into a 1-voxel thick "sprite mesh"

- Fixes
- Fixed editor not shrinking properly on narrow screens with a terrain selected. Stats appearing in bottom panel will use a scrollbar if the area is too small.
Expand Down
34 changes: 0 additions & 34 deletions editor/vox/vox_import_funcs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,6 @@

namespace zylann {

static void scale_vec3_array(PackedVector3Array &array, float scale) {
// Getting raw pointer because between GDExtension and modules, syntax and performance of operator[] differs.
Vector3 *array_data = array.ptrw();
const int count = array.size();
for (int i = 0; i < count; ++i) {
array_data[i] *= scale;
}
}

static void offset_vec3_array(PackedVector3Array &array, Vector3 offset) {
// Getting raw pointer because between GDExtension and modules, syntax and performance of operator[] differs.
Vector3 *array_data = array.ptrw();
const int count = array.size();
for (int i = 0; i < count; ++i) {
array_data[i] += offset;
}
}

static void scale_surface(Array &surface, float scale) {
PackedVector3Array positions = surface[Mesh::ARRAY_VERTEX];
// Avoiding stupid CoW, assuming this array holds the only instance of this vector
surface[Mesh::ARRAY_VERTEX] = PackedVector3Array();
scale_vec3_array(positions, scale);
surface[Mesh::ARRAY_VERTEX] = positions;
}

static void offset_surface(Array &surface, Vector3 offset) {
PackedVector3Array positions = surface[Mesh::ARRAY_VERTEX];
// Avoiding stupid CoW, assuming this array holds the only instance of this vector
surface[Mesh::ARRAY_VERTEX] = PackedVector3Array();
offset_vec3_array(positions, offset);
surface[Mesh::ARRAY_VERTEX] = positions;
}

namespace voxel::magica {

Ref<Mesh> build_mesh(const VoxelBufferInternal &voxels, VoxelMesher &mesher,
Expand Down
83 changes: 83 additions & 0 deletions meshers/cubes/voxel_mesher_cubes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,17 @@ void VoxelMesherCubes::build(VoxelMesher::Output &output, const VoxelMesher::Inp
}
break;

case VoxelBufferInternal::DEPTH_32_BIT:
if (params.greedy_meshing) {
build_voxel_mesh_as_greedy_cubes(cache.arrays_per_material,
raw_channel.reinterpret_cast_to<uint32_t>(), block_size, cache.mask_memory_pool,
Color8::from_u32);
} else {
build_voxel_mesh_as_simple_cubes(cache.arrays_per_material,
raw_channel.reinterpret_cast_to<uint32_t>(), block_size, Color8::from_u32);
}
break;

default:
ERR_PRINT("Unsupported voxel depth");
return;
Expand Down Expand Up @@ -1041,6 +1052,75 @@ Ref<Material> VoxelMesherCubes::_b_get_transparent_material() const {
return get_material_by_index(MATERIAL_TRANSPARENT);
}

Ref<Mesh> VoxelMesherCubes::generate_mesh_from_image(Ref<Image> image, float voxel_size) {
ZN_PROFILE_SCOPE();
ZN_ASSERT_RETURN_V(image.is_valid(), Ref<Mesh>());
ZN_ASSERT_RETURN_V(voxel_size > 0.001f, Ref<Mesh>());
ZN_ASSERT_RETURN_V_MSG(
!image->is_compressed(), Ref<Mesh>(), format("Image format not supported: {}", image->get_format()));

// Convert image
VoxelBufferInternal voxels;
voxels.set_channel_depth(VoxelBufferInternal::CHANNEL_COLOR, VoxelBufferInternal::DEPTH_32_BIT);
const int im_size_x = image->get_width();
const int im_size_y = image->get_height();
// Currently all meshers require pre-padded voxel data...
voxels.create(im_size_x + VoxelMesherCubes::PADDING * 2, im_size_y + VoxelMesherCubes::PADDING * 2,
1 + VoxelMesherCubes::PADDING * 2);
for (int y = 0; y < im_size_y; ++y) {
for (int x = 0; x < im_size_x; ++x) {
const Color cf = image->get_pixel(x, y);
const Color8 c(cf);
voxels.set_voxel(c.to_u32(),
Vector3i(x + VoxelMesherCubes::PADDING,
// Flip Y axis, since Y goes up in world space, but Y goes down in Image space
(im_size_y - 1 - y) + VoxelMesherCubes::PADDING, VoxelMesherCubes::PADDING),
VoxelBufferInternal::CHANNEL_COLOR);
}
}

// Build mesh

Ref<VoxelMesherCubes> mesher;
mesher.instantiate();
VoxelMesher::Output output;
VoxelMesher::Input input = { voxels, nullptr, nullptr, Vector3i(), 0, false };
mesher->build(output, input);

if (output.surfaces.size() == 0) {
return Ref<ArrayMesh>();
}

Ref<ArrayMesh> mesh;
mesh.instantiate();

const Vector3 centering_offset = -Vector3(im_size_x, im_size_y, 1) / 2.0;

for (unsigned int i = 0; i < output.surfaces.size(); ++i) {
VoxelMesher::Output::Surface &surface = output.surfaces[i];
Array arrays = surface.arrays;

if (arrays.is_empty()) {
continue;
}

CRASH_COND(arrays.size() != Mesh::ARRAY_MAX);
if (!is_surface_triangulated(arrays)) {
continue;
}

offset_surface(arrays, centering_offset);

if (voxel_size != 1.f) {
scale_surface(arrays, voxel_size);
}

mesh->add_surface_from_arrays(output.primitive_type, arrays, Array(), Dictionary(), output.mesh_flags);
}

return mesh;
}

void VoxelMesherCubes::_bind_methods() {
ClassDB::bind_method(
D_METHOD("set_greedy_meshing_enabled", "enable"), &VoxelMesherCubes::set_greedy_meshing_enabled);
Expand All @@ -1061,6 +1141,9 @@ void VoxelMesherCubes::_bind_methods() {
ClassDB::bind_method(
D_METHOD("_set_transparent_material", "material"), &VoxelMesherCubes::_b_set_transparent_material);

ClassDB::bind_static_method(VoxelMesherCubes::get_class_static(),
D_METHOD("generate_mesh_from_image", "image", "voxel_size"), &VoxelMesherCubes::generate_mesh_from_image);

ADD_PROPERTY(PropertyInfo(Variant::BOOL, "greedy_meshing_enabled"), "set_greedy_meshing_enabled",
"is_greedy_meshing_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "color_mode", PROPERTY_HINT_ENUM, "Raw,MesherPalette,ShaderPalette"),
Expand Down
2 changes: 2 additions & 0 deletions meshers/cubes/voxel_mesher_cubes.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class VoxelMesherCubes : public VoxelMesher {
void set_material_by_index(Materials id, Ref<Material> material);
Ref<Material> get_material_by_index(unsigned int i) const override;

static Ref<Mesh> generate_mesh_from_image(Ref<Image> image, float voxel_size);

// Structs

// Using std::vector because they make this mesher twice as fast than Godot Vectors.
Expand Down
34 changes: 34 additions & 0 deletions util/godot/classes/mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,38 @@ Array generate_debug_seams_wireframe_surface(const Mesh &src_mesh, int surface_i
// return wire_mesh;
}

void scale_vec3_array(PackedVector3Array &array, float scale) {
// Getting raw pointer because between GDExtension and modules, syntax and performance of operator[] differs.
Vector3 *array_data = array.ptrw();
const int count = array.size();
for (int i = 0; i < count; ++i) {
array_data[i] *= scale;
}
}

void offset_vec3_array(PackedVector3Array &array, Vector3 offset) {
// Getting raw pointer because between GDExtension and modules, syntax and performance of operator[] differs.
Vector3 *array_data = array.ptrw();
const int count = array.size();
for (int i = 0; i < count; ++i) {
array_data[i] += offset;
}
}

void scale_surface(Array &surface, float scale) {
PackedVector3Array positions = surface[Mesh::ARRAY_VERTEX];
// Avoiding stupid CoW, assuming this array holds the only instance of this vector
surface[Mesh::ARRAY_VERTEX] = PackedVector3Array();
scale_vec3_array(positions, scale);
surface[Mesh::ARRAY_VERTEX] = positions;
}

void offset_surface(Array &surface, Vector3 offset) {
PackedVector3Array positions = surface[Mesh::ARRAY_VERTEX];
// Avoiding stupid CoW, assuming this array holds the only instance of this vector
surface[Mesh::ARRAY_VERTEX] = PackedVector3Array();
offset_vec3_array(positions, offset);
surface[Mesh::ARRAY_VERTEX] = positions;
}

} // namespace zylann
7 changes: 7 additions & 0 deletions util/godot/classes/mesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ using namespace godot;

namespace zylann {

// Mesh utilities

bool is_surface_triangulated(const Array &surface);
bool is_mesh_empty(const Mesh &mesh);
bool is_mesh_empty(Span<const Array> surfaces);

// Generates a wireframe-mesh that highlights edges of a triangle-mesh where vertices are not shared
Array generate_debug_seams_wireframe_surface(const Mesh &src_mesh, int surface_index);

void scale_vec3_array(PackedVector3Array &array, float scale);
void offset_vec3_array(PackedVector3Array &array, Vector3 offset);
void scale_surface(Array &surface, float scale);
void offset_surface(Array &surface, Vector3 offset);

} // namespace zylann

#endif // ZN_GODOT_MESH_H

0 comments on commit 2064027

Please sign in to comment.