godot_voxel/storage/modifiers_gd.cpp
Marc Gilleron 86ba74ce3a Some changes and fixes related modifiers
- VoxelLodTerrain no longer caches generated voxels by default, so
  generating on the fly is no longer exclusive to full load mode.
  Might add an option later, but not for now (VoxelTerrain is still
  unaffected and keeps caching them)
- The "Cached" state is represented with blocks having no voxel data,
  so it needs extra checks in some areas to avoid null access
- Fix generate task was not including modifiers after the base generator
- The "save_generator_output" option on streams now means such blocks are
  considered edited
- Modifying modifiers now clears cached generated blocks
  intersecting with them.
- Fix "re-generate" was erasing the internal stack of modifiers
- Added docs
2022-06-18 23:14:18 +01:00

308 lines
11 KiB
C++

#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 <typename Modifier_T, typename F>
// 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;
}
std::shared_ptr<VoxelDataLodMap> data = _volume->get_storage();
VoxelModifierStack &modifiers = data->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<zylann::voxel::VoxelModifierSdf *>(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;
}
std::shared_ptr<VoxelDataLodMap> data = _volume->get_storage();
VoxelModifierStack &modifiers = data->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<zylann::voxel::VoxelModifierSdf *>(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<VoxelLodTerrain>(parent);
_volume = volume;
if (_volume != nullptr) {
std::shared_ptr<VoxelDataLodMap> data = _volume->get_storage();
VoxelModifierStack &modifiers = data->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<zylann::voxel::VoxelModifierSdf *>(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) {
std::shared_ptr<VoxelDataLodMap> data = _volume->get_storage();
VoxelModifierStack &modifiers = data->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()) {
std::shared_ptr<VoxelDataLodMap> data = _volume->get_storage();
VoxelModifierStack &modifiers = data->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");
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T>
T *get_modifier(VoxelLodTerrain &volume, uint32_t id, zylann::voxel::VoxelModifier::Type type) {
std::shared_ptr<VoxelDataLodMap> data = volume.get_storage();
VoxelModifierStack &modifiers = data->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<T *>(modifier);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
zylann::voxel::VoxelModifierSphere *get_sphere(VoxelLodTerrain &volume, uint32_t id) {
return get_modifier<zylann::voxel::VoxelModifierSphere>(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<zylann::voxel::VoxelModifierSphere>(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<zylann::voxel::VoxelModifierBuffer>(volume, id, zylann::voxel::VoxelModifier::TYPE_BUFFER);
}
static void set_buffer(zylann::voxel::VoxelModifierBuffer &bmod, Ref<VoxelMeshSDF> 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<VoxelMeshSDF> 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<VoxelMeshSDF> 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<zylann::voxel::VoxelModifierBuffer>(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