godot_voxel/storage/modifiers_gd.cpp

311 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;
}
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<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;
}
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<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) {
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<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) {
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 <typename T>
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<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