#include "modifiers_gd.h" #include "../terrain/variable_lod/voxel_lod_terrain.h" #include "../util/errors.h" #include "../util/math/conv.h" namespace zylann::voxel::gd { void post_edit_modifier(VoxelLodTerrain &volume, AABB aabb) { volume.post_edit_modifiers(Box3i(math::floor_to_int(aabb.position), math::floor_to_int(aabb.size))); } // template // void edit_modifier(VoxelLodTerrain &volume, Modifier_T &modifier, F action) { // const AABB prev_aabb = modifier->get_aabb(); // action(modifier); // const AABB new_aabb = modifier->get_aabb(); // post_edit_modifier(volume, prev_aabb); // post_edit_modifier(volume, new_aabb); // } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// VoxelModifier::VoxelModifier() { set_notify_local_transform(true); } zylann::voxel::VoxelModifier *VoxelModifier::create(zylann::voxel::VoxelModifierStack &modifiers, uint32_t id) { ZN_PRINT_ERROR("Not implemented"); return nullptr; } zylann::voxel::VoxelModifierSdf::Operation to_op(VoxelModifier::Operation op) { return zylann::voxel::VoxelModifierSdf::Operation(op); } void VoxelModifier::set_operation(Operation op) { ZN_ASSERT_RETURN(op >= 0 && op < OPERATION_COUNT); if (op == _operation) { return; } _operation = op; if (_volume == nullptr) { return; } VoxelData &data = _volume->get_storage(); VoxelModifierStack &modifiers = data.get_modifiers(); zylann::voxel::VoxelModifier *modifier = modifiers.get_modifier(_modifier_id); ZN_ASSERT_RETURN(modifier != nullptr); ZN_ASSERT_RETURN(modifier->is_sdf()); zylann::voxel::VoxelModifierSdf *sdf_modifier = static_cast(modifier); sdf_modifier->set_operation(to_op(_operation)); post_edit_modifier(*_volume, modifier->get_aabb()); } VoxelModifier::Operation VoxelModifier::get_operation() const { return _operation; } void VoxelModifier::set_smoothness(float s) { if (s == _smoothness) { return; } _smoothness = math::max(s, 0.f); if (_volume == nullptr) { return; } VoxelData &data = _volume->get_storage(); VoxelModifierStack &modifiers = data.get_modifiers(); zylann::voxel::VoxelModifier *modifier = modifiers.get_modifier(_modifier_id); ZN_ASSERT_RETURN(modifier != nullptr); ZN_ASSERT_RETURN(modifier->is_sdf()); zylann::voxel::VoxelModifierSdf *sdf_modifier = static_cast(modifier); const AABB prev_aabb = modifier->get_aabb(); sdf_modifier->set_smoothness(_smoothness); const AABB new_aabb = modifier->get_aabb(); post_edit_modifier(*_volume, prev_aabb); post_edit_modifier(*_volume, new_aabb); } float VoxelModifier::get_smoothness() const { return _smoothness; } void VoxelModifier::_notification(int p_what) { switch (p_what) { case Node::NOTIFICATION_PARENTED: { Node *parent = get_parent(); ZN_ASSERT_RETURN(parent != nullptr); ZN_ASSERT_RETURN(_volume == nullptr); VoxelLodTerrain *volume = Object::cast_to(parent); _volume = volume; if (_volume != nullptr) { VoxelData &data = _volume->get_storage(); VoxelModifierStack &modifiers = data.get_modifiers(); const uint32_t id = modifiers.allocate_id(); zylann::voxel::VoxelModifier *modifier = create(modifiers, id); if (modifier->is_sdf()) { zylann::voxel::VoxelModifierSdf *sdf_modifier = static_cast(modifier); sdf_modifier->set_operation(to_op(_operation)); sdf_modifier->set_smoothness(_smoothness); } modifier->set_transform(get_transform()); _modifier_id = id; // TODO Optimize: on loading of a scene, this could be very bad for performance because there could be, // a lot of modifiers on the map, but there is no distinction possible in Godot at the moment... post_edit_modifier(*_volume, modifier->get_aabb()); } } break; case Node::NOTIFICATION_UNPARENTED: { if (_volume != nullptr) { VoxelData &data = _volume->get_storage(); VoxelModifierStack &modifiers = data.get_modifiers(); zylann::voxel::VoxelModifier *modifier = modifiers.get_modifier(_modifier_id); ZN_ASSERT_RETURN_MSG(modifier != nullptr, "The modifier node wasn't linked properly"); post_edit_modifier(*_volume, modifier->get_aabb()); modifiers.remove_modifier(_modifier_id); _volume = nullptr; _modifier_id = 0; } } break; case Node3D::NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { if (_volume != nullptr && is_inside_tree()) { VoxelData &data = _volume->get_storage(); VoxelModifierStack &modifiers = data.get_modifiers(); zylann::voxel::VoxelModifier *modifier = modifiers.get_modifier(_modifier_id); ZN_ASSERT_RETURN(modifier != nullptr); const AABB prev_aabb = modifier->get_aabb(); modifier->set_transform(get_transform()); const AABB aabb = modifier->get_aabb(); post_edit_modifier(*_volume, prev_aabb); post_edit_modifier(*_volume, aabb); // TODO Handle nesting properly, though it's a pain in the ass // When the terrain is moved, the local transform of modifiers technically changes too. // However it did not change relative to the terrain. But because we don't have a way to check that, // all modifiers will trigger updates at the same time... } } break; } } void VoxelModifier::_bind_methods() { ClassDB::bind_method(D_METHOD("set_operation", "op"), &VoxelModifier::set_operation); ClassDB::bind_method(D_METHOD("get_operation"), &VoxelModifier::get_operation); ClassDB::bind_method(D_METHOD("set_smoothness", "smoothness"), &VoxelModifier::set_smoothness); ClassDB::bind_method(D_METHOD("get_smoothness"), &VoxelModifier::get_smoothness); ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Add,Remove"), "set_operation", "get_operation"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "smoothness", PROPERTY_HINT_RANGE, "0.0, 100.0, 0.1"), "set_smoothness", "get_smoothness"); BIND_ENUM_CONSTANT(OPERATION_ADD); BIND_ENUM_CONSTANT(OPERATION_REMOVE); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// template T *get_modifier(VoxelLodTerrain &volume, uint32_t id, zylann::voxel::VoxelModifier::Type type) { VoxelData &data = volume.get_storage(); VoxelModifierStack &modifiers = data.get_modifiers(); zylann::voxel::VoxelModifier *modifier = modifiers.get_modifier(id); ZN_ASSERT_RETURN_V(modifier != nullptr, nullptr); ZN_ASSERT_RETURN_V(modifier->get_type() == type, nullptr); return static_cast(modifier); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// zylann::voxel::VoxelModifierSphere *get_sphere(VoxelLodTerrain &volume, uint32_t id) { return get_modifier(volume, id, zylann::voxel::VoxelModifier::TYPE_SPHERE); } float VoxelModifierSphere::get_radius() const { return _radius; } void VoxelModifierSphere::set_radius(float r) { _radius = math::max(r, 0.f); if (_volume == nullptr) { return; } zylann::voxel::VoxelModifierSphere *sphere = get_sphere(*_volume, _modifier_id); ZN_ASSERT_RETURN(sphere != nullptr); const AABB prev_aabb = sphere->get_aabb(); sphere->set_radius(r); const AABB new_aabb = sphere->get_aabb(); post_edit_modifier(*_volume, prev_aabb); post_edit_modifier(*_volume, new_aabb); } zylann::voxel::VoxelModifier *VoxelModifierSphere::create(zylann::voxel::VoxelModifierStack &modifiers, uint32_t id) { zylann::voxel::VoxelModifierSphere *sphere = modifiers.add_modifier(id); sphere->set_radius(_radius); return sphere; } void VoxelModifierSphere::_bind_methods() { ClassDB::bind_method(D_METHOD("set_radius", "r"), &VoxelModifierSphere::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &VoxelModifierSphere::get_radius); ADD_PROPERTY( PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0, 100.0, 0.1"), "set_radius", "get_radius"); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// zylann::voxel::VoxelModifierBuffer *get_buffer_modifier(VoxelLodTerrain &volume, uint32_t id) { return get_modifier(volume, id, zylann::voxel::VoxelModifier::TYPE_BUFFER); } static void set_buffer(zylann::voxel::VoxelModifierBuffer &bmod, Ref mesh_sdf) { if (mesh_sdf.is_null() || mesh_sdf->get_voxel_buffer() == nullptr) { bmod.set_buffer(nullptr, Vector3f(), Vector3f()); } else { const AABB aabb = mesh_sdf->get_aabb(); bmod.set_buffer(mesh_sdf->get_voxel_buffer()->get_buffer_shared(), to_vec3f(aabb.position), to_vec3f(aabb.position + aabb.size)); } } void VoxelModifierMesh::set_mesh_sdf(Ref mesh_sdf) { if (mesh_sdf == _mesh_sdf) { return; } if (_mesh_sdf.is_valid()) { _mesh_sdf->disconnect("baked", callable_mp(this, &VoxelModifierMesh::_on_mesh_sdf_baked)); } _mesh_sdf = mesh_sdf; if (_mesh_sdf.is_valid()) { _mesh_sdf->connect("baked", callable_mp(this, &VoxelModifierMesh::_on_mesh_sdf_baked)); } if (_volume == nullptr) { return; } zylann::voxel::VoxelModifierBuffer *bmod = get_buffer_modifier(*_volume, _modifier_id); ZN_ASSERT_RETURN(bmod != nullptr); const AABB prev_aabb = bmod->get_aabb(); set_buffer(*bmod, _mesh_sdf); const AABB new_aabb = bmod->get_aabb(); post_edit_modifier(*_volume, prev_aabb); post_edit_modifier(*_volume, new_aabb); } Ref VoxelModifierMesh::get_mesh_sdf() const { return _mesh_sdf; } void VoxelModifierMesh::set_isolevel(float isolevel) { if (isolevel == _isolevel) { return; } _isolevel = isolevel; if (_volume == nullptr) { return; } zylann::voxel::VoxelModifierBuffer *bmod = get_buffer_modifier(*_volume, _modifier_id); ZN_ASSERT_RETURN(bmod != nullptr); bmod->set_isolevel(_isolevel); post_edit_modifier(*_volume, bmod->get_aabb()); } float VoxelModifierMesh::get_isolevel() const { return _isolevel; } zylann::voxel::VoxelModifier *VoxelModifierMesh::create(zylann::voxel::VoxelModifierStack &modifiers, uint32_t id) { zylann::voxel::VoxelModifierBuffer *bmod = modifiers.add_modifier(id); set_buffer(*bmod, _mesh_sdf); bmod->set_isolevel(_isolevel); return bmod; } void VoxelModifierMesh::_on_mesh_sdf_baked() { if (_volume == nullptr) { return; } zylann::voxel::VoxelModifierBuffer *bmod = get_buffer_modifier(*_volume, _modifier_id); ZN_ASSERT_RETURN(bmod != nullptr); const AABB prev_aabb = bmod->get_aabb(); set_buffer(*bmod, _mesh_sdf); const AABB new_aabb = bmod->get_aabb(); post_edit_modifier(*_volume, prev_aabb); post_edit_modifier(*_volume, new_aabb); } void VoxelModifierMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mesh_sdf", "mesh_sdf"), &VoxelModifierMesh::set_mesh_sdf); ClassDB::bind_method(D_METHOD("get_mesh_sdf"), &VoxelModifierMesh::get_mesh_sdf); ClassDB::bind_method(D_METHOD("set_isolevel", "isolevel"), &VoxelModifierMesh::set_isolevel); ClassDB::bind_method(D_METHOD("get_isolevel"), &VoxelModifierMesh::get_isolevel); ADD_PROPERTY( PropertyInfo(Variant::OBJECT, "mesh_sdf", PROPERTY_HINT_RESOURCE_TYPE, VoxelMeshSDF::get_class_static()), "set_mesh_sdf", "get_mesh_sdf"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "isolevel", PROPERTY_HINT_RANGE, "-100.0, 100.0, 0.01"), "set_isolevel", "get_isolevel"); } } // namespace zylann::voxel::gd