Add initial support for instancing scenes with VoxelInstancer

master
Marc Gilleron 2021-07-25 20:37:30 +01:00
parent e7e8e72507
commit 27d54a0d5c
17 changed files with 1024 additions and 471 deletions

View File

@ -23,6 +23,7 @@ Ongoing development - `master`
- Added `get_voxel_f_interpolated` to `VoxelToolLodTerrain`, useful to obtain interpolated SDF
- Added option to simplify meshes with Transvoxel, using MeshOptimizer
- Added extra option to `VoxelInstanceGenerator` to emit from faces more precisely, especially when meshes got simplified (slower than the other options)
- `VoxelInstancer`: added initial support for instancing regular scenes (slower than multimeshes)
- Breaking changes
- `VoxelBuffer` channels `DATA3` and `DATA4` were renamed `INDICES` and `WEIGHTS`
@ -34,6 +35,7 @@ Ongoing development - `master`
- `VoxelMesherTransvoxel`: no longer crashes when the input buffer is not cubic
- `VoxelLodTerrain`: fixed errors and crashes when editing voxels near loading borders
- `VoxelTool` channel no longer defaults to 7 when using `get_voxel_tool` from a terrain with a stream assigned. Instead it picks first used channel of the mesher (fallback order is mesher, then generator, then stream).
- `VoxelInstancer`: fix error when node visibility changes
09/05/2021 - `godot3.3`

View File

@ -1,9 +1,13 @@
Instancing
=============
The module provides an instancing system with the [VoxelInstancer](api/VoxelInstancer.md) node. It allows to spawn 3D models on top of the terrain's surface. It uses `MultiMeshInstance` internally to allow rendering a lot of them at once, and can support collisions and removal. The node must be added as child of [VoxelLodTerrain](api/VoxelLodTerrain.md).
The module provides an instancing system with the [VoxelInstancer](api/VoxelInstancer.md) node. This node must be added as child of a voxel terrain. It allows to spawn 3D models on top of the terrain's surface, which can later be removed when modified.
This system is primarily intented at natural spawning: grass, rocks, trees and other kinds of semi-random foliage. It is not suited for complex man-made structures like houses or villages.
It can spawn two different kinds of objects:
- **Multimesh instances**. They can be extremely numerous, and can optionally have collision.
- **Scene instances**. They use regular scenes, however it is much slower so should be tuned to low numbers.
This system is primarily intented at natural spawning: grass, rocks, trees and other kinds of semi-random foliage. It is not suited for complex man-made structures like houses or villages, although scene instances can be used in some cases, if the available features suit your game.
This feature is currently only available under `VoxelLodTerrain`.
@ -19,7 +23,7 @@ Select a `VoxelInstancer`. In the inspector, assign a library to the `library` p
![Screenshot of the VoxelInstanceLibrary menu](images/instance_library_menu.png)
In this menu, you can add items to the library by clicking `VoxelInstanceLibrary -> Add item`.
In this menu, you can add items to the library by clicking `VoxelInstanceLibrary -> Add Multimesh item`.
Items created this way come with a default setup, so you should be able to see something appear on top of the voxel surface.
@ -91,6 +95,16 @@ At time of writing, only [VoxelStreamSQLite](api/VoxelStreamSQLite.md) supports
The save format is described in [this document](specs/instances_format.md).
### Scene instances
Instancing scenes is supported by adding items of type `VoxelInstanceLibrarySceneItem`. Instead of spawning multimeshes, regular scene instances will be created as child of `VoxelInstancer`. The advantage is the ability to put much more varied behavior on them, such as scripts, sounds, animations, or even further spawning logic or interaction. The only constraint is, the root of the scene must be `Spatial` or derive from it.
This freedom has a high price compared to multimesh instances. Adding many instances can become slow quickly, so the default density of these items is lower when you create them from the editor. It is strongly recommended to not use too complex scenes, because depending on the settings, it can lead to a freeze or crash of Godot if your computer cannot handle too many instances.
!!! note
Warning: if you add a scene to the library and then try to load that library from that same scene, Godot will crash. This is a cyclic reference and is hard to detect in all cases at the moment.
Procedural generation
-----------------------

View File

@ -1,4 +1,6 @@
#include "voxel_instance_library_editor_plugin.h"
#include "../../terrain/instancing/voxel_instance_library_item.h"
#include "../../terrain/instancing/voxel_instance_library_scene_item.h"
#include <scene/gui/dialogs.h>
#include <scene/gui/menu_button.h>
@ -9,7 +11,8 @@ VoxelInstanceLibraryEditorPlugin::VoxelInstanceLibraryEditorPlugin(EditorNode *p
_menu_button->set_text(TTR("VoxelInstanceLibrary"));
// TODO Icon
//_menu_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("MeshLibrary", "EditorIcons"));
_menu_button->get_popup()->add_item(TTR("Add Item"), MENU_ADD_ITEM);
_menu_button->get_popup()->add_item(TTR("Add Multimesh Item (fast)"), MENU_ADD_MULTIMESH_ITEM);
_menu_button->get_popup()->add_item(TTR("Add Scene Item (slow)"), MENU_ADD_SCENE_ITEM);
_menu_button->get_popup()->add_item(TTR("Remove Selected Item"), MENU_REMOVE_ITEM);
// TODO Add and update from scene
_menu_button->get_popup()->connect("id_pressed", this, "_on_menu_id_pressed");
@ -24,6 +27,16 @@ VoxelInstanceLibraryEditorPlugin::VoxelInstanceLibraryEditorPlugin(EditorNode *p
_info_dialog = memnew(AcceptDialog);
base_control->add_child(_info_dialog);
_open_scene_dialog = memnew(EditorFileDialog);
List<String> extensions;
ResourceLoader::get_recognized_extensions_for_type("PackedScene", &extensions);
for (List<String>::Element *E = extensions.front(); E; E = E->next()) {
_open_scene_dialog->add_filter("*." + E->get());
}
_open_scene_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE);
base_control->add_child(_open_scene_dialog);
_open_scene_dialog->connect("file_selected", this, "_on_open_scene_dialog_file_selected");
add_control_to_container(EditorPlugin::CONTAINER_SPATIAL_EDITOR_MENU, _menu_button);
}
@ -43,7 +56,7 @@ void VoxelInstanceLibraryEditorPlugin::make_visible(bool visible) {
void VoxelInstanceLibraryEditorPlugin::_on_menu_id_pressed(int id) {
switch (id) {
case MENU_ADD_ITEM: {
case MENU_ADD_MULTIMESH_ITEM: {
ERR_FAIL_COND(_library.is_null());
Ref<VoxelInstanceLibraryItem> item;
@ -60,12 +73,16 @@ void VoxelInstanceLibraryEditorPlugin::_on_menu_id_pressed(int id) {
const int item_id = _library->get_next_available_id();
UndoRedo &ur = get_undo_redo();
ur.create_action("Add item");
ur.create_action("Add multimesh item");
ur.add_do_method(*_library, "add_item", item_id, item);
ur.add_undo_method(*_library, "remove_item", item_id);
ur.commit_action();
} break;
case MENU_ADD_SCENE_ITEM: {
_open_scene_dialog->popup_centered_ratio();
} break;
case MENU_REMOVE_ITEM: {
ERR_FAIL_COND(_library.is_null());
@ -111,8 +128,35 @@ void VoxelInstanceLibraryEditorPlugin::_on_remove_item_confirmed() {
_item_id_to_remove = -1;
}
void VoxelInstanceLibraryEditorPlugin::_on_open_scene_dialog_file_selected(String fpath) {
ERR_FAIL_COND(_library.is_null());
Ref<PackedScene> scene = ResourceLoader::load(fpath);
ERR_FAIL_COND(scene.is_null());
Ref<VoxelInstanceLibrarySceneItem> item;
item.instance();
// Setup some defaults
item->set_lod_index(2);
item->set_scene(scene);
Ref<VoxelInstanceGenerator> generator;
generator.instance();
generator->set_density(0.01f); // Low density for scenes because that's heavier
item->set_generator(generator);
const int item_id = _library->get_next_available_id();
UndoRedo &ur = get_undo_redo();
ur.create_action("Add scene item");
ur.add_do_method(*_library, "add_item", item_id, item);
ur.add_undo_method(*_library, "remove_item", item_id);
ur.commit_action();
}
void VoxelInstanceLibraryEditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("_on_menu_id_pressed", "id"), &VoxelInstanceLibraryEditorPlugin::_on_menu_id_pressed);
ClassDB::bind_method(D_METHOD("_on_remove_item_confirmed"),
&VoxelInstanceLibraryEditorPlugin::_on_remove_item_confirmed);
ClassDB::bind_method(D_METHOD("_on_open_scene_dialog_file_selected", "fpath"),
&VoxelInstanceLibraryEditorPlugin::_on_open_scene_dialog_file_selected);
}

View File

@ -21,11 +21,13 @@ public:
private:
void _on_menu_id_pressed(int id);
void _on_remove_item_confirmed();
void _on_open_scene_dialog_file_selected(String fpath);
static void _bind_methods();
enum MenuOption {
MENU_ADD_ITEM,
MENU_ADD_MULTIMESH_ITEM,
MENU_ADD_SCENE_ITEM,
MENU_REMOVE_ITEM
};
@ -33,6 +35,7 @@ private:
ConfirmationDialog *_confirmation_dialog = nullptr;
AcceptDialog *_info_dialog = nullptr;
int _item_id_to_remove = -1;
EditorFileDialog *_open_scene_dialog;
Ref<VoxelInstanceLibrary> _library;
};

View File

@ -40,6 +40,8 @@
#include "util/noise/fast_noise_2.h"
#endif
#include "constants/voxel_string_names.h"
#include "terrain/instancing/voxel_instance_component.h"
#include "terrain/instancing/voxel_instance_library_scene_item.h"
#include "util/noise/fast_noise_lite.h"
#include "util/noise/fast_noise_lite_gradient.h"
@ -69,7 +71,9 @@ void register_voxel_types() {
ClassDB::register_class<VoxelLibrary>();
ClassDB::register_class<VoxelColorPalette>();
ClassDB::register_class<VoxelInstanceLibrary>();
ClassDB::register_class<VoxelInstanceLibraryItemBase>();
ClassDB::register_class<VoxelInstanceLibraryItem>();
ClassDB::register_class<VoxelInstanceLibrarySceneItem>();
// Storage
ClassDB::register_class<VoxelBuffer>();
@ -81,6 +85,7 @@ void register_voxel_types() {
ClassDB::register_class<VoxelViewer>();
ClassDB::register_class<VoxelInstanceGenerator>();
ClassDB::register_class<VoxelInstancer>();
ClassDB::register_class<VoxelInstanceComponent>();
// Streams
ClassDB::register_virtual_class<VoxelStream>();

View File

@ -0,0 +1,112 @@
#ifndef VOXEL_INSTANCE_COMPONENT_H
#define VOXEL_INSTANCE_COMPONENT_H
#include "voxel_instancer.h"
#include <scene/main/node.h>
// Used as child of scene items instanced with VoxelInstancer.
//
// It is needed because such instances are tied with some of the logic in VoxelInstancer.
// The root of a scene could be anything derived from Spatial,
// so offering an API using inheritance on the root node is impractical.
// So instead the component approach is taken.
// If a huge amount of instances is needed, prefer using fast/multimesh instances.
class VoxelInstanceComponent : public Node {
GDCLASS(VoxelInstanceComponent, Node)
public:
void mark_modified() {
ERR_FAIL_COND(_instancer == nullptr);
_instancer->on_scene_instance_modified(_data_block_position, _render_block_index);
}
void detach() {
ERR_FAIL_COND_MSG(_instancer == nullptr, "Already detached");
_instancer = nullptr;
}
void attach(VoxelInstancer *instancer) {
ERR_FAIL_COND_MSG(_instancer != nullptr, "Already attached");
_instancer = instancer;
}
// TODO Need to investigate if we need this
//
// This must be called by the user from a script if they want the instancer to remember a removal.
// It may be common to call `queue_free()` on the root (which could be a body or an area) but there doesn't seem to
// be a reliable way to detect this scenario happens from a child node.
// `_exit_tree` could happen for different reasons.
// `unparented` won't happen because the parent is unparented, not the child.
void detach_as_removed() {
ERR_FAIL_COND_MSG(_instancer == nullptr, "Already detached");
_instancer->on_scene_instance_removed(_data_block_position, _render_block_index, _instance_index);
_instancer = nullptr;
}
Variant serialize_state() {
// TODO Scripting
return Variant();
}
Variant deserialize_state() {
// TODO Scripting
return Variant();
}
void set_instance_index(int instance_index) {
_instance_index = instance_index;
}
void set_data_block_position(Vector3i data_block_position) {
_data_block_position = data_block_position;
}
void set_render_block_index(unsigned int render_block_index) {
_render_block_index = render_block_index;
}
static VoxelInstanceComponent *find_in(Node *root) {
ERR_FAIL_COND_V(root == nullptr, nullptr);
for (int i = 0; i < root->get_child_count(); ++i) {
Node *child = root->get_child(i);
VoxelInstanceComponent *cmp = Object::cast_to<VoxelInstanceComponent>(child);
if (cmp != nullptr) {
return cmp;
}
}
return nullptr;
}
protected:
void _notification(int p_what) {
switch (p_what) {
// case NOTIFICATION_PARENTED:
// Spatial *spatial = Object::cast_to<Spatial>(get_parent());
// if (spatial == nullptr) {
// ERR_PRINT("VoxelInstanceComponent must have a parent derived from Spatial");
// }
// break;
// TODO Optimization: this is also called when we quit the game or destroy the world
// which can make things a bit slow, but I don't know if it can easily be avoided
case NOTIFICATION_UNPARENTED:
// The user could queue_free() that node or its parent in game for some reason,
// so we have to notify the instancer to remove the instance
if (_instancer != nullptr) {
detach_as_removed();
}
break;
}
}
private:
static void _bind_methods() {
// TODO Scripting
}
VoxelInstancer *_instancer = nullptr;
Vector3i _data_block_position;
unsigned int _render_block_index;
int _instance_index = -1;
};
#endif // VOXEL_INSTANCE_COMPONENT_H

View File

@ -6,7 +6,7 @@
#include <scene/3d/physics_body.h>
VoxelInstanceLibrary::~VoxelInstanceLibrary() {
for_each_item([this](int id, VoxelInstanceLibraryItem &item) {
for_each_item([this](int id, VoxelInstanceLibraryItemBase &item) {
item.remove_listener(this, id);
});
}
@ -19,39 +19,39 @@ int VoxelInstanceLibrary::get_next_available_id() {
}
}
void VoxelInstanceLibrary::add_item(int id, Ref<VoxelInstanceLibraryItem> item) {
void VoxelInstanceLibrary::add_item(int id, Ref<VoxelInstanceLibraryItemBase> item) {
ERR_FAIL_COND(_items.has(id));
ERR_FAIL_COND(id < 0 || id >= MAX_ID);
ERR_FAIL_COND(item.is_null());
_items.insert(id, item);
item->add_listener(this, id);
notify_listeners(id, VoxelInstanceLibraryItem::CHANGE_ADDED);
notify_listeners(id, VoxelInstanceLibraryItemBase::CHANGE_ADDED);
_change_notify();
}
void VoxelInstanceLibrary::remove_item(int id) {
Map<int, Ref<VoxelInstanceLibraryItem> >::Element *E = _items.find(id);
Map<int, Ref<VoxelInstanceLibraryItemBase>>::Element *E = _items.find(id);
ERR_FAIL_COND(E == nullptr);
Ref<VoxelInstanceLibraryItem> item = E->value();
Ref<VoxelInstanceLibraryItemBase> item = E->value();
if (item.is_valid()) {
item->remove_listener(this, id);
}
_items.erase(E);
notify_listeners(id, VoxelInstanceLibraryItem::CHANGE_REMOVED);
notify_listeners(id, VoxelInstanceLibraryItemBase::CHANGE_REMOVED);
_change_notify();
}
void VoxelInstanceLibrary::clear() {
for_each_item([this](int id, const VoxelInstanceLibraryItem &item) {
notify_listeners(id, VoxelInstanceLibraryItem::CHANGE_REMOVED);
for_each_item([this](int id, const VoxelInstanceLibraryItemBase &item) {
notify_listeners(id, VoxelInstanceLibraryItemBase::CHANGE_REMOVED);
});
_items.clear();
_change_notify();
}
int VoxelInstanceLibrary::find_item_by_name(String name) const {
for (Map<int, Ref<VoxelInstanceLibraryItem> >::Element *E = _items.front(); E != nullptr; E = E->next()) {
const Ref<VoxelInstanceLibraryItem> &item = E->value();
for (Map<int, Ref<VoxelInstanceLibraryItemBase>>::Element *E = _items.front(); E != nullptr; E = E->next()) {
const Ref<VoxelInstanceLibraryItemBase> &item = E->value();
ERR_FAIL_COND_V(item.is_null(), -1);
if (item->get_name() == name) {
return E->key();
@ -64,40 +64,40 @@ int VoxelInstanceLibrary::get_item_count() const {
return _items.size();
}
Ref<VoxelInstanceLibraryItem> VoxelInstanceLibrary::_b_get_item(int id) {
Map<int, Ref<VoxelInstanceLibraryItem> >::Element *E = _items.find(id);
Ref<VoxelInstanceLibraryItem> item;
Ref<VoxelInstanceLibraryItemBase> VoxelInstanceLibrary::_b_get_item(int id) {
Map<int, Ref<VoxelInstanceLibraryItemBase>>::Element *E = _items.find(id);
Ref<VoxelInstanceLibraryItemBase> item;
if (E != nullptr) {
item = E->value();
}
return item;
}
VoxelInstanceLibraryItem *VoxelInstanceLibrary::get_item(int id) {
Map<int, Ref<VoxelInstanceLibraryItem> >::Element *E = _items.find(id);
VoxelInstanceLibraryItemBase *VoxelInstanceLibrary::get_item(int id) {
Map<int, Ref<VoxelInstanceLibraryItemBase>>::Element *E = _items.find(id);
if (E != nullptr) {
Ref<VoxelInstanceLibraryItem> &item = E->value();
Ref<VoxelInstanceLibraryItemBase> &item = E->value();
ERR_FAIL_COND_V(item.is_null(), nullptr);
return *item;
}
return nullptr;
}
const VoxelInstanceLibraryItem *VoxelInstanceLibrary::get_item_const(int id) const {
const Map<int, Ref<VoxelInstanceLibraryItem> >::Element *E = _items.find(id);
const VoxelInstanceLibraryItemBase *VoxelInstanceLibrary::get_item_const(int id) const {
const Map<int, Ref<VoxelInstanceLibraryItemBase>>::Element *E = _items.find(id);
if (E != nullptr) {
const Ref<VoxelInstanceLibraryItem> &item = E->value();
const Ref<VoxelInstanceLibraryItemBase> &item = E->value();
ERR_FAIL_COND_V(item.is_null(), nullptr);
return *item;
}
return nullptr;
}
void VoxelInstanceLibrary::on_library_item_changed(int id, VoxelInstanceLibraryItem::ChangeType change) {
void VoxelInstanceLibrary::on_library_item_changed(int id, VoxelInstanceLibraryItemBase::ChangeType change) {
notify_listeners(id, change);
}
void VoxelInstanceLibrary::notify_listeners(int item_id, VoxelInstanceLibraryItem::ChangeType change) {
void VoxelInstanceLibrary::notify_listeners(int item_id, VoxelInstanceLibraryItemBase::ChangeType change) {
for (int i = 0; i < _listeners.size(); ++i) {
IListener *listener = _listeners[i];
listener->on_library_item_changed(item_id, change);
@ -121,10 +121,10 @@ bool VoxelInstanceLibrary::_set(const StringName &p_name, const Variant &p_value
if (name.begins_with("item_")) {
const int id = name.substr(5).to_int();
Ref<VoxelInstanceLibraryItem> item = p_value;
Ref<VoxelInstanceLibraryItemBase> item = p_value;
ERR_FAIL_COND_V_MSG(item.is_null(), false, "Setting a null item is not allowed");
Map<int, Ref<VoxelInstanceLibraryItem> >::Element *E = _items.find(id);
Map<int, Ref<VoxelInstanceLibraryItemBase>>::Element *E = _items.find(id);
if (E == nullptr) {
add_item(id, item);
@ -132,14 +132,14 @@ bool VoxelInstanceLibrary::_set(const StringName &p_name, const Variant &p_value
} else {
// Replace
if (E->value() != item) {
Ref<VoxelInstanceLibraryItem> old_item = E->value();
Ref<VoxelInstanceLibraryItemBase> old_item = E->value();
if (old_item.is_valid()) {
old_item->remove_listener(this, id);
notify_listeners(id, VoxelInstanceLibraryItem::CHANGE_REMOVED);
notify_listeners(id, VoxelInstanceLibraryItemBase::CHANGE_REMOVED);
}
E->value() = item;
item->add_listener(this, id);
notify_listeners(id, VoxelInstanceLibraryItem::CHANGE_ADDED);
notify_listeners(id, VoxelInstanceLibraryItemBase::CHANGE_ADDED);
}
}
@ -152,7 +152,7 @@ bool VoxelInstanceLibrary::_get(const StringName &p_name, Variant &r_ret) const
const String name = p_name;
if (name.begins_with("item_")) {
const int id = name.substr(5).to_int();
const Map<int, Ref<VoxelInstanceLibraryItem> >::Element *E = _items.find(id);
const Map<int, Ref<VoxelInstanceLibraryItemBase>>::Element *E = _items.find(id);
if (E != nullptr) {
r_ret = E->value();
return true;
@ -162,9 +162,10 @@ bool VoxelInstanceLibrary::_get(const StringName &p_name, Variant &r_ret) const
}
void VoxelInstanceLibrary::_get_property_list(List<PropertyInfo> *p_list) const {
for (Map<int, Ref<VoxelInstanceLibraryItem> >::Element *E = _items.front(); E != nullptr; E = E->next()) {
for (Map<int, Ref<VoxelInstanceLibraryItemBase>>::Element *E = _items.front(); E != nullptr; E = E->next()) {
const String name = "item_" + itos(E->key());
p_list->push_back(PropertyInfo(Variant::OBJECT, name, PROPERTY_HINT_RESOURCE_TYPE, "VoxelInstanceLibraryItem"));
p_list->push_back(
PropertyInfo(Variant::OBJECT, name, PROPERTY_HINT_RESOURCE_TYPE, "VoxelInstanceLibraryItemBase"));
}
}

View File

@ -1,23 +1,23 @@
#ifndef VOXEL_INSTANCE_MODEL_LIBRARY_H
#define VOXEL_INSTANCE_MODEL_LIBRARY_H
#include "voxel_instance_library_item.h"
#include "voxel_instance_library_item_base.h"
// Contains a list of models that can be used by VoxelInstancer, associated with a unique ID
class VoxelInstanceLibrary : public Resource, public VoxelInstanceLibraryItem::IListener {
// Contains a list of items that can be used by VoxelInstancer, associated with a unique ID
class VoxelInstanceLibrary : public Resource, public VoxelInstanceLibraryItemBase::IListener {
GDCLASS(VoxelInstanceLibrary, Resource)
public:
static const int MAX_ID = 0xffff;
class IListener {
public:
virtual void on_library_item_changed(int id, VoxelInstanceLibraryItem::ChangeType change) = 0;
virtual void on_library_item_changed(int id, VoxelInstanceLibraryItemBase::ChangeType change) = 0;
};
~VoxelInstanceLibrary();
int get_next_available_id();
void add_item(int id, Ref<VoxelInstanceLibraryItem> item);
void add_item(int id, Ref<VoxelInstanceLibraryItemBase> item);
void remove_item(int id);
void clear();
int find_item_by_name(String name) const;
@ -25,12 +25,12 @@ public:
// Internal
const VoxelInstanceLibraryItem *get_item_const(int id) const;
VoxelInstanceLibraryItem *get_item(int id);
const VoxelInstanceLibraryItemBase *get_item_const(int id) const;
VoxelInstanceLibraryItemBase *get_item(int id);
template <typename F>
void for_each_item(F f) {
for (Map<int, Ref<VoxelInstanceLibraryItem> >::Element *E = _items.front(); E != nullptr; E = E->next()) {
for (Map<int, Ref<VoxelInstanceLibraryItemBase>>::Element *E = _items.front(); E != nullptr; E = E->next()) {
CRASH_COND(E->value().is_null());
f(E->key(), **E->value());
}
@ -40,21 +40,21 @@ public:
void remove_listener(IListener *listener);
protected:
Ref<VoxelInstanceLibraryItem> _b_get_item(int id);
Ref<VoxelInstanceLibraryItemBase> _b_get_item(int id);
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
private:
void on_library_item_changed(int id, VoxelInstanceLibraryItem::ChangeType change) override;
void notify_listeners(int item_id, VoxelInstanceLibraryItem::ChangeType change);
void on_library_item_changed(int id, VoxelInstanceLibraryItemBase::ChangeType change) override;
void notify_listeners(int item_id, VoxelInstanceLibraryItemBase::ChangeType change);
static void _bind_methods();
// ID => Item
// Using a Map keeps items ordered, so the last item has highest ID
Map<int, Ref<VoxelInstanceLibraryItem> > _items;
Map<int, Ref<VoxelInstanceLibraryItemBase>> _items;
Vector<IListener *> _listeners;
};

View File

@ -6,53 +6,6 @@
#include <scene/3d/mesh_instance.h>
#include <scene/3d/physics_body.h>
void VoxelInstanceLibraryItem::set_item_name(String name) {
_name = name;
}
String VoxelInstanceLibraryItem::get_item_name() const {
return _name;
}
void VoxelInstanceLibraryItem::set_lod_index(int lod) {
ERR_FAIL_COND(lod < 0 || lod >= VoxelInstancer::MAX_LOD);
if (_lod_index == lod) {
return;
}
_lod_index = lod;
notify_listeners(CHANGE_LOD_INDEX);
}
int VoxelInstanceLibraryItem::get_lod_index() const {
return _lod_index;
}
void VoxelInstanceLibraryItem::set_generator(Ref<VoxelInstanceGenerator> generator) {
if (_generator == generator) {
return;
}
if (_generator.is_valid()) {
_generator->disconnect(CoreStringNames::get_singleton()->changed, this, "_on_generator_changed");
}
_generator = generator;
if (_generator.is_valid()) {
_generator->connect(CoreStringNames::get_singleton()->changed, this, "_on_generator_changed");
}
notify_listeners(CHANGE_GENERATOR);
}
Ref<VoxelInstanceGenerator> VoxelInstanceLibraryItem::get_generator() const {
return _generator;
}
void VoxelInstanceLibraryItem::set_persistent(bool persistent) {
_persistent = persistent;
}
bool VoxelInstanceLibraryItem::is_persistent() const {
return _persistent;
}
void VoxelInstanceLibraryItem::set_mesh(Ref<Mesh> mesh, int mesh_lod_index) {
ERR_FAIL_INDEX(mesh_lod_index, static_cast<int>(_mesh_lods.size()));
if (_mesh_lods[mesh_lod_index] == mesh) {
@ -178,34 +131,6 @@ void VoxelInstanceLibraryItem::setup_from_template(Node *root) {
notify_listeners(CHANGE_VISUAL);
}
void VoxelInstanceLibraryItem::add_listener(IListener *listener, int id) {
ListenerSlot slot;
slot.listener = listener;
slot.id = id;
ERR_FAIL_COND(_listeners.find(slot) != -1);
_listeners.push_back(slot);
}
void VoxelInstanceLibraryItem::remove_listener(IListener *listener, int id) {
ListenerSlot slot;
slot.listener = listener;
slot.id = id;
int i = _listeners.find(slot);
ERR_FAIL_COND(i == -1);
_listeners.remove(i);
}
void VoxelInstanceLibraryItem::notify_listeners(ChangeType change) {
for (int i = 0; i < _listeners.size(); ++i) {
ListenerSlot &slot = _listeners.write[i];
slot.listener->on_library_item_changed(slot.id, change);
}
}
void VoxelInstanceLibraryItem::_on_generator_changed() {
notify_listeners(CHANGE_GENERATOR);
}
void VoxelInstanceLibraryItem::_b_set_collision_shapes(Array shape_infos) {
ERR_FAIL_COND(shape_infos.size() % 2 != 0);
@ -233,19 +158,6 @@ Array VoxelInstanceLibraryItem::_b_get_collision_shapes() const {
}
void VoxelInstanceLibraryItem::_bind_methods() {
// Can't be just "set_name" because Resource already defines that, despite being for a `resource_name` property
ClassDB::bind_method(D_METHOD("set_item_name", "name"), &VoxelInstanceLibraryItem::set_item_name);
ClassDB::bind_method(D_METHOD("get_item_name"), &VoxelInstanceLibraryItem::get_item_name);
ClassDB::bind_method(D_METHOD("set_lod_index", "lod"), &VoxelInstanceLibraryItem::set_lod_index);
ClassDB::bind_method(D_METHOD("get_lod_index"), &VoxelInstanceLibraryItem::get_lod_index);
ClassDB::bind_method(D_METHOD("set_generator", "generator"), &VoxelInstanceLibraryItem::set_generator);
ClassDB::bind_method(D_METHOD("get_generator"), &VoxelInstanceLibraryItem::get_generator);
ClassDB::bind_method(D_METHOD("set_persistent", "persistent"), &VoxelInstanceLibraryItem::set_persistent);
ClassDB::bind_method(D_METHOD("is_persistent"), &VoxelInstanceLibraryItem::is_persistent);
ClassDB::bind_method(D_METHOD("set_mesh", "mesh", "mesh_lod_index"), &VoxelInstanceLibraryItem::set_mesh);
ClassDB::bind_method(D_METHOD("get_mesh", "mesh_lod_index"), &VoxelInstanceLibraryItem::get_mesh);
@ -281,15 +193,6 @@ void VoxelInstanceLibraryItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("setup_from_template", "node"), &VoxelInstanceLibraryItem::setup_from_template);
ClassDB::bind_method(D_METHOD("_on_generator_changed"), &VoxelInstanceLibraryItem::_on_generator_changed);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "name"), "set_item_name", "get_item_name");
ADD_PROPERTY(PropertyInfo(Variant::INT, "lod_index", PROPERTY_HINT_RANGE, "0,8,1"),
"set_lod_index", "get_lod_index");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "generator", PROPERTY_HINT_RESOURCE_TYPE, "VoxelInstanceGenerator"),
"set_generator", "get_generator");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "persistent"), "set_persistent", "is_persistent");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"),
"_set_mesh_lod0", "_get_mesh_lod0");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh_lod1", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"),
@ -302,7 +205,8 @@ void VoxelInstanceLibraryItem::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "Material"),
"set_material_override", "get_material_override");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadow", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_cast_shadows_setting", "get_cast_shadows_setting");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadow", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"),
"set_cast_shadows_setting", "get_cast_shadows_setting");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_3D_PHYSICS),
"set_collision_layer", "get_collision_layer");

View File

@ -1,16 +1,13 @@
#ifndef VOXEL_INSTANCE_LIBRARY_ITEM_H
#define VOXEL_INSTANCE_LIBRARY_ITEM_H
#include <core/resource.h>
#include <scene/resources/mesh.h>
#include <scene/resources/shape.h>
#include "voxel_instance_library_item_base.h"
#include "../../util/fixed_array.h"
#include "voxel_instance_generator.h"
// TODO Rename VoxelInstanceLibraryMultimeshItem (did not do it for compatibility)
// Settings for a model that can be used by VoxelInstancer
class VoxelInstanceLibraryItem : public Resource {
GDCLASS(VoxelInstanceLibraryItem, Resource)
class VoxelInstanceLibraryItem : public VoxelInstanceLibraryItemBase {
GDCLASS(VoxelInstanceLibraryItem, VoxelInstanceLibraryItemBase)
public:
static const int MAX_MESH_LODS = 4;
@ -19,31 +16,6 @@ public:
Ref<Shape> shape;
};
enum ChangeType {
CHANGE_LOD_INDEX,
CHANGE_GENERATOR,
CHANGE_VISUAL,
CHANGE_ADDED,
CHANGE_REMOVED
};
class IListener {
public:
virtual void on_library_item_changed(int id, ChangeType change) = 0;
};
void set_item_name(String name);
String get_item_name() const;
void set_lod_index(int lod);
int get_lod_index() const;
void set_generator(Ref<VoxelInstanceGenerator> generator);
Ref<VoxelInstanceGenerator> get_generator() const;
void set_persistent(bool persistent);
bool is_persistent() const;
void set_mesh(Ref<Mesh> mesh, int mesh_lod_index);
Ref<Mesh> get_mesh(int mesh_lod_index) const;
int get_mesh_lod_count() const;
@ -68,13 +40,7 @@ public:
return _collision_shapes;
}
void add_listener(IListener *listener, int id);
void remove_listener(IListener *listener, int id);
private:
void notify_listeners(ChangeType change);
void _on_generator_changed();
static void _bind_methods();
void _b_set_collision_shapes(Array shape_infos);
@ -90,26 +56,9 @@ private:
void _b_set_mesh_lod2(Ref<Mesh> mesh) { set_mesh(mesh, 2); }
void _b_set_mesh_lod3(Ref<Mesh> mesh) { set_mesh(mesh, 3); }
// For the user, not used by the engine
String _name;
// If a layer is persistent, any change to its instances will be saved if the volume has a stream
// supporting instances. It will also not generate on top of modified surfaces.
// If a layer is not persistent, changes won't get saved, and it will keep generating on all compliant
// surfaces.
bool _persistent = false;
// Which LOD of the octree this model will spawn into.
// Higher means larger distances, but lower precision and density
int _lod_index = 0;
Ref<VoxelInstanceGenerator> _generator;
FixedArray<Ref<Mesh>, MAX_MESH_LODS> _mesh_lods;
unsigned int _mesh_lod_count = 1;
// TODO Scenes?
// It is preferred to have materials on the mesh already,
// but this is in case OBJ meshes are used, which often dont have a material of their own
Ref<Material> _material_override;
@ -119,17 +68,6 @@ private:
int _collision_mask = 1;
int _collision_layer = 1;
Vector<CollisionShapeInfo> _collision_shapes;
struct ListenerSlot {
IListener *listener;
int id;
inline bool operator==(const ListenerSlot &other) const {
return listener == other.listener && id == other.id;
}
};
Vector<ListenerSlot> _listeners;
};
#endif // VOXEL_INSTANCE_LIBRARY_ITEM_H

View File

@ -0,0 +1,106 @@
#include "voxel_instance_library_item.h"
#include "voxel_instancer.h"
#include <core/core_string_names.h>
#include <scene/3d/collision_shape.h>
#include <scene/3d/mesh_instance.h>
#include <scene/3d/physics_body.h>
void VoxelInstanceLibraryItemBase::set_item_name(String name) {
_name = name;
}
String VoxelInstanceLibraryItemBase::get_item_name() const {
return _name;
}
void VoxelInstanceLibraryItemBase::set_lod_index(int lod) {
ERR_FAIL_COND(lod < 0 || lod >= VoxelInstancer::MAX_LOD);
if (_lod_index == lod) {
return;
}
_lod_index = lod;
notify_listeners(CHANGE_LOD_INDEX);
}
int VoxelInstanceLibraryItemBase::get_lod_index() const {
return _lod_index;
}
void VoxelInstanceLibraryItemBase::set_generator(Ref<VoxelInstanceGenerator> generator) {
if (_generator == generator) {
return;
}
if (_generator.is_valid()) {
_generator->disconnect(CoreStringNames::get_singleton()->changed, this, "_on_generator_changed");
}
_generator = generator;
if (_generator.is_valid()) {
_generator->connect(CoreStringNames::get_singleton()->changed, this, "_on_generator_changed");
}
notify_listeners(CHANGE_GENERATOR);
}
Ref<VoxelInstanceGenerator> VoxelInstanceLibraryItemBase::get_generator() const {
return _generator;
}
void VoxelInstanceLibraryItemBase::set_persistent(bool persistent) {
_persistent = persistent;
}
bool VoxelInstanceLibraryItemBase::is_persistent() const {
return _persistent;
}
void VoxelInstanceLibraryItemBase::add_listener(IListener *listener, int id) {
ListenerSlot slot;
slot.listener = listener;
slot.id = id;
ERR_FAIL_COND(_listeners.find(slot) != -1);
_listeners.push_back(slot);
}
void VoxelInstanceLibraryItemBase::remove_listener(IListener *listener, int id) {
ListenerSlot slot;
slot.listener = listener;
slot.id = id;
int i = _listeners.find(slot);
ERR_FAIL_COND(i == -1);
_listeners.remove(i);
}
void VoxelInstanceLibraryItemBase::notify_listeners(ChangeType change) {
for (int i = 0; i < _listeners.size(); ++i) {
ListenerSlot &slot = _listeners.write[i];
slot.listener->on_library_item_changed(slot.id, change);
}
}
void VoxelInstanceLibraryItemBase::_on_generator_changed() {
notify_listeners(CHANGE_GENERATOR);
}
void VoxelInstanceLibraryItemBase::_bind_methods() {
// Can't be just "set_name" because Resource already defines that, despite being for a `resource_name` property
ClassDB::bind_method(D_METHOD("set_item_name", "name"), &VoxelInstanceLibraryItemBase::set_item_name);
ClassDB::bind_method(D_METHOD("get_item_name"), &VoxelInstanceLibraryItemBase::get_item_name);
ClassDB::bind_method(D_METHOD("set_lod_index", "lod"), &VoxelInstanceLibraryItemBase::set_lod_index);
ClassDB::bind_method(D_METHOD("get_lod_index"), &VoxelInstanceLibraryItemBase::get_lod_index);
ClassDB::bind_method(D_METHOD("set_generator", "generator"), &VoxelInstanceLibraryItemBase::set_generator);
ClassDB::bind_method(D_METHOD("get_generator"), &VoxelInstanceLibraryItemBase::get_generator);
ClassDB::bind_method(D_METHOD("set_persistent", "persistent"), &VoxelInstanceLibraryItemBase::set_persistent);
ClassDB::bind_method(D_METHOD("is_persistent"), &VoxelInstanceLibraryItemBase::is_persistent);
ClassDB::bind_method(D_METHOD("_on_generator_changed"), &VoxelInstanceLibraryItemBase::_on_generator_changed);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "name"), "set_item_name", "get_item_name");
ADD_PROPERTY(PropertyInfo(Variant::INT, "lod_index", PROPERTY_HINT_RANGE, "0,8,1"),
"set_lod_index", "get_lod_index");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "generator", PROPERTY_HINT_RESOURCE_TYPE, "VoxelInstanceGenerator"),
"set_generator", "get_generator");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "persistent"), "set_persistent", "is_persistent");
}

View File

@ -0,0 +1,82 @@
#ifndef VOXEL_INSTANCE_LIBRARY_ITEM_BASE_H
#define VOXEL_INSTANCE_LIBRARY_ITEM_BASE_H
#include <core/resource.h>
#include <scene/resources/mesh.h>
#include <scene/resources/shape.h>
#include "../../util/fixed_array.h"
#include "voxel_instance_generator.h"
// TODO Rename VoxelInstanceLibraryItem (did not do it for compatibility)
class VoxelInstanceLibraryItemBase : public Resource {
GDCLASS(VoxelInstanceLibraryItemBase, Resource)
public:
enum ChangeType {
CHANGE_LOD_INDEX,
CHANGE_GENERATOR,
CHANGE_VISUAL,
CHANGE_ADDED,
CHANGE_REMOVED,
CHANGE_SCENE
};
class IListener {
public:
virtual void on_library_item_changed(int id, ChangeType change) = 0;
};
void set_item_name(String name);
String get_item_name() const;
void set_lod_index(int lod);
int get_lod_index() const;
void set_generator(Ref<VoxelInstanceGenerator> generator);
Ref<VoxelInstanceGenerator> get_generator() const;
void set_persistent(bool persistent);
bool is_persistent() const;
// Internal
void add_listener(IListener *listener, int id);
void remove_listener(IListener *listener, int id);
protected:
void notify_listeners(ChangeType change);
private:
void _on_generator_changed();
static void _bind_methods();
// For the user, not used by the engine
String _name;
// If a layer is persistent, any change to its instances will be saved if the volume has a stream
// supporting instances. It will also not generate on top of modified surfaces.
// If a layer is not persistent, changes won't get saved, and it will keep generating on all compliant
// surfaces.
bool _persistent = false;
// Which LOD of the octree this model will spawn into.
// Higher means larger distances, but lower precision and density
int _lod_index = 0;
Ref<VoxelInstanceGenerator> _generator;
struct ListenerSlot {
IListener *listener;
int id;
inline bool operator==(const ListenerSlot &other) const {
return listener == other.listener && id == other.id;
}
};
Vector<ListenerSlot> _listeners;
};
#endif // VOXEL_INSTANCE_LIBRARY_ITEM_BASE_H

View File

@ -0,0 +1,17 @@
#include "voxel_instance_library_scene_item.h"
void VoxelInstanceLibrarySceneItem::set_scene(Ref<PackedScene> scene) {
if (scene != _scene) {
_scene = scene;
notify_listeners(CHANGE_SCENE);
}
}
Ref<PackedScene> VoxelInstanceLibrarySceneItem::get_scene() const {
return _scene;
}
void VoxelInstanceLibrarySceneItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_scene", "scene"), &VoxelInstanceLibrarySceneItem::set_scene);
ClassDB::bind_method(D_METHOD("get_scene"), &VoxelInstanceLibrarySceneItem::get_scene);
}

View File

@ -0,0 +1,20 @@
#ifndef VOXEL_INSTANCE_LIBRARY_SCENE_ITEM_H
#define VOXEL_INSTANCE_LIBRARY_SCENE_ITEM_H
#include "voxel_instance_library_item_base.h"
#include <scene/resources/packed_scene.h>
class VoxelInstanceLibrarySceneItem : public VoxelInstanceLibraryItemBase {
GDCLASS(VoxelInstanceLibrarySceneItem, VoxelInstanceLibraryItemBase)
public:
void set_scene(Ref<PackedScene> scene);
Ref<PackedScene> get_scene() const;
private:
static void _bind_methods();
Ref<PackedScene> _scene;
};
#endif // VOXEL_INSTANCE_LIBRARY_SCENE_ITEM_H

View File

@ -1,75 +1,19 @@
#include "voxel_instancer.h"
#include "../../edition/voxel_tool.h"
#include "../../util/funcs.h"
#include "../../util/godot/funcs.h"
#include "../../util/macros.h"
#include "../../util/profiling.h"
#include "../voxel_lod_terrain.h"
#include "voxel_instance_component.h"
#include "voxel_instance_library_scene_item.h"
#include "voxel_instancer_rigidbody.h"
#include <scene/3d/camera.h>
#include <scene/3d/collision_shape.h>
#include <scene/3d/mesh_instance.h>
#include <scene/3d/physics_body.h>
#include <scene/main/viewport.h>
#include <algorithm>
class VoxelInstancerRigidBody : public RigidBody {
GDCLASS(VoxelInstancerRigidBody, RigidBody);
public:
VoxelInstancerRigidBody() {
set_mode(RigidBody::MODE_STATIC);
}
void set_data_block_position(Vector3i data_block_position) {
_data_block_position = data_block_position;
}
void set_render_block_index(unsigned int render_block_index) {
_render_block_index = render_block_index;
}
void set_instance_index(int instance_index) {
_instance_index = instance_index;
}
void attach(VoxelInstancer *parent) {
_parent = parent;
}
void detach_and_destroy() {
_parent = nullptr;
queue_delete();
}
// Note, for this the body must switch to convex shapes
// void detach_and_become_rigidbody() {
// //...
// }
protected:
void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_UNPARENTED:
// The user could queue_free() that node in game,
// so we have to notify the instancer to remove the multimesh instance and pointer
if (_parent != nullptr) {
_parent->on_body_removed(_data_block_position, _render_block_index, _instance_index);
_parent = nullptr;
}
break;
}
}
private:
VoxelInstancer *_parent = nullptr;
Vector3i _data_block_position;
unsigned int _render_block_index;
int _instance_index = -1;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VoxelInstancer::VoxelInstancer() {
set_notify_transform(true);
set_process_internal(true);
@ -95,6 +39,13 @@ void VoxelInstancer::clear_blocks() {
VoxelInstancerRigidBody *body = block->bodies[i];
body->detach_and_destroy();
}
for (int i = 0; i < block->scene_instances.size(); ++i) {
SceneInstance instance = block->scene_instances[i];
ERR_CONTINUE(instance.component == nullptr);
instance.component->detach();
ERR_CONTINUE(instance.root == nullptr);
instance.root->queue_delete();
}
memdelete(block);
}
_blocks.clear();
@ -252,10 +203,15 @@ void VoxelInstancer::process_mesh_lods() {
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
Block *block = *it;
const VoxelInstanceLibraryItem *item = _library->get_item_const(block->layer_id);
const VoxelInstanceLibraryItemBase *item_base = _library->get_item_const(block->layer_id);
ERR_CONTINUE(item_base == nullptr);
// TODO Optimization: would be nice to not need this cast by iterating only the same item types
const VoxelInstanceLibraryItem *item = Object::cast_to<VoxelInstanceLibraryItem>(item_base);
ERR_CONTINUE(item == nullptr);
const int mesh_lod_count = item->get_mesh_lod_count();
if (mesh_lod_count <= 1) {
// This block has no LOD
// TODO Optimization: would be nice to not need this conditional by iterating only item types that define lods
continue;
}
@ -347,7 +303,7 @@ void VoxelInstancer::set_library(Ref<VoxelInstanceLibrary> library) {
clear_layers();
if (_library.is_valid()) {
_library->for_each_item([this](int id, const VoxelInstanceLibraryItem &item) {
_library->for_each_item([this](int id, const VoxelInstanceLibraryItemBase &item) {
add_layer(id, item.get_lod_index());
if (_parent != nullptr && is_inside_tree()) {
regenerate_layer(id, true);
@ -375,7 +331,7 @@ void VoxelInstancer::regenerate_layer(uint16_t layer_id, bool regenerate_blocks)
Layer *layer = get_layer(layer_id);
CRASH_COND(layer == nullptr);
Ref<VoxelInstanceLibraryItem> item = _library->get_item(layer_id);
Ref<VoxelInstanceLibraryItemBase> item = _library->get_item(layer_id);
ERR_FAIL_COND(item.is_null());
if (item->get_generator().is_null()) {
return;
@ -496,8 +452,10 @@ void VoxelInstancer::regenerate_layer(uint16_t layer_id, bool regenerate_blocks)
}
void VoxelInstancer::update_layer_meshes(int layer_id) {
Ref<VoxelInstanceLibraryItem> item = _library->get_item(layer_id);
ERR_FAIL_COND(item.is_null());
Ref<VoxelInstanceLibraryItemBase> item_base = _library->get_item(layer_id);
ERR_FAIL_COND(item_base.is_null());
VoxelInstanceLibraryItem *item = Object::cast_to<VoxelInstanceLibraryItem>(*item_base);
ERR_FAIL_COND(item == nullptr);
for (auto it = _blocks.begin(); it != _blocks.end(); ++it) {
Block *block = *it;
@ -519,35 +477,64 @@ void VoxelInstancer::update_layer_meshes(int layer_id) {
}
}
void VoxelInstancer::on_library_item_changed(int item_id, VoxelInstanceLibraryItem::ChangeType change) {
void VoxelInstancer::update_layer_scenes(int layer_id) {
Ref<VoxelInstanceLibraryItemBase> item_base = _library->get_item(layer_id);
ERR_FAIL_COND(item_base.is_null());
VoxelInstanceLibrarySceneItem *item = Object::cast_to<VoxelInstanceLibrarySceneItem>(*item_base);
ERR_FAIL_COND(item == nullptr);
const int data_block_size_po2 = _parent->get_data_block_size_pow2();
for (unsigned int block_index = 0; block_index < _blocks.size(); ++block_index) {
Block *block = _blocks[block_index];
ERR_CONTINUE(block == nullptr);
for (int instance_index = 0; instance_index < block->scene_instances.size(); ++instance_index) {
SceneInstance prev_instance = block->scene_instances[instance_index];
ERR_CONTINUE(prev_instance.root == nullptr);
SceneInstance instance = create_scene_instance(
*item, instance_index, block_index, prev_instance.root->get_transform(), data_block_size_po2);
ERR_CONTINUE(instance.root == nullptr);
block->scene_instances.write[instance_index] = instance;
// We just drop the instance without saving, because this function is supposed to occur only in editor,
// or in the very rare cases where library is modified in game (which would invalidate saves anyways).
prev_instance.root->queue_delete();
}
}
}
void VoxelInstancer::on_library_item_changed(int item_id, VoxelInstanceLibraryItemBase::ChangeType change) {
ERR_FAIL_COND(_library.is_null());
// TODO It's unclear yet if some code paths do the right thing in case instances got edited
switch (change) {
case VoxelInstanceLibraryItem::CHANGE_ADDED: {
Ref<VoxelInstanceLibraryItem> item = _library->get_item(item_id);
case VoxelInstanceLibraryItemBase::CHANGE_ADDED: {
Ref<VoxelInstanceLibraryItemBase> item = _library->get_item(item_id);
ERR_FAIL_COND(item.is_null());
add_layer(item_id, item->get_lod_index());
regenerate_layer(item_id, true);
update_configuration_warning();
} break;
case VoxelInstanceLibraryItem::CHANGE_REMOVED:
case VoxelInstanceLibraryItemBase::CHANGE_REMOVED:
remove_layer(item_id);
update_configuration_warning();
break;
case VoxelInstanceLibraryItem::CHANGE_GENERATOR:
case VoxelInstanceLibraryItemBase::CHANGE_GENERATOR:
regenerate_layer(item_id, false);
break;
case VoxelInstanceLibraryItem::CHANGE_VISUAL:
case VoxelInstanceLibraryItemBase::CHANGE_VISUAL:
update_layer_meshes(item_id);
break;
case VoxelInstanceLibraryItem::CHANGE_LOD_INDEX: {
Ref<VoxelInstanceLibraryItem> item = _library->get_item(item_id);
case VoxelInstanceLibraryItemBase::CHANGE_SCENE:
update_layer_scenes(item_id);
break;
case VoxelInstanceLibraryItemBase::CHANGE_LOD_INDEX: {
Ref<VoxelInstanceLibraryItemBase> item = _library->get_item(item_id);
ERR_FAIL_COND(item.is_null());
clear_blocks_in_layer(item_id);
@ -637,6 +624,14 @@ void VoxelInstancer::remove_block(unsigned int block_index) {
body->detach_and_destroy();
}
for (int i = 0; i < block->scene_instances.size(); ++i) {
SceneInstance instance = block->scene_instances[i];
ERR_CONTINUE(instance.component == nullptr);
instance.component->detach();
ERR_CONTINUE(instance.root == nullptr);
instance.root->queue_delete();
}
memdelete(block);
if (block != moved_block) {
@ -714,6 +709,34 @@ void VoxelInstancer::save_all_modified_blocks() {
}
}
VoxelInstancer::SceneInstance VoxelInstancer::create_scene_instance(const VoxelInstanceLibrarySceneItem &scene_item,
int instance_index, unsigned int block_index, Transform transform, int data_block_size_po2) {
SceneInstance instance;
ERR_FAIL_COND_V(scene_item.get_scene().is_null(), instance);
Node *root = scene_item.get_scene()->instance();
ERR_FAIL_COND_V(root == nullptr, instance);
instance.root = Object::cast_to<Spatial>(root);
ERR_FAIL_COND_V_MSG(instance.root == nullptr, instance, "Root of scene instance must be derived from Spatial");
instance.component = VoxelInstanceComponent::find_in(instance.root);
if (instance.component == nullptr) {
instance.component = memnew(VoxelInstanceComponent);
instance.root->add_child(instance.component);
}
instance.component->attach(this);
instance.component->set_instance_index(instance_index);
instance.component->set_render_block_index(block_index);
instance.component->set_data_block_position(Vector3i::from_floored(transform.origin) >> data_block_size_po2);
instance.root->set_transform(transform);
// This is the SLOWEST part
add_child(instance.root);
return instance;
}
int VoxelInstancer::create_block(Layer *layer, uint16_t layer_id, Vector3i grid_position) {
Block *block = memnew(Block);
block->layer_id = layer_id;
@ -730,12 +753,12 @@ int VoxelInstancer::create_block(Layer *layer, uint16_t layer_id, Vector3i grid_
}
void VoxelInstancer::update_block_from_transforms(int block_index, Span<const Transform> transforms,
Vector3i grid_position, Layer *layer, const VoxelInstanceLibraryItem *item, uint16_t layer_id,
Vector3i grid_position, Layer *layer, const VoxelInstanceLibraryItemBase *item_base, uint16_t layer_id,
World *world, const Transform &block_transform) {
VOXEL_PROFILE_SCOPE();
CRASH_COND(layer == nullptr);
CRASH_COND(item == nullptr);
CRASH_COND(item_base == nullptr);
// Get or create block
Block *block = nullptr;
@ -747,87 +770,131 @@ void VoxelInstancer::update_block_from_transforms(int block_index, Span<const Tr
}
// Update multimesh
const VoxelInstanceLibraryItem *item = Object::cast_to<VoxelInstanceLibraryItem>(item_base);
if (item != nullptr) {
if (transforms.size() == 0) {
if (block->multimesh_instance.is_valid()) {
block->multimesh_instance.set_multimesh(Ref<MultiMesh>());
block->multimesh_instance.destroy();
}
if (transforms.size() == 0) {
if (block->multimesh_instance.is_valid()) {
block->multimesh_instance.set_multimesh(Ref<MultiMesh>());
block->multimesh_instance.destroy();
}
} else {
Ref<MultiMesh> multimesh = block->multimesh_instance.get_multimesh();
if (multimesh.is_null()) {
multimesh.instance();
multimesh->set_transform_format(MultiMesh::TRANSFORM_3D);
multimesh->set_color_format(MultiMesh::COLOR_NONE);
multimesh->set_custom_data_format(MultiMesh::CUSTOM_DATA_NONE);
} else {
multimesh->set_visible_instance_count(-1);
}
PoolRealArray bulk_array = DirectMultiMeshInstance::make_transform_3d_bulk_array(transforms);
multimesh->set_instance_count(transforms.size());
multimesh->set_as_bulk_array(bulk_array);
Ref<MultiMesh> multimesh = block->multimesh_instance.get_multimesh();
if (multimesh.is_null()) {
multimesh.instance();
multimesh->set_transform_format(MultiMesh::TRANSFORM_3D);
multimesh->set_color_format(MultiMesh::COLOR_NONE);
multimesh->set_custom_data_format(MultiMesh::CUSTOM_DATA_NONE);
} else {
multimesh->set_visible_instance_count(-1);
}
PoolRealArray bulk_array = DirectMultiMeshInstance::make_transform_3d_bulk_array(transforms);
multimesh->set_instance_count(transforms.size());
multimesh->set_as_bulk_array(bulk_array);
if (item->get_mesh_lod_count() > 0) {
multimesh->set_mesh(item->get_mesh(item->get_mesh_lod_count() - 1));
if (item->get_mesh_lod_count() > 0) {
multimesh->set_mesh(item->get_mesh(item->get_mesh_lod_count() - 1));
}
if (!block->multimesh_instance.is_valid()) {
block->multimesh_instance.create();
block->multimesh_instance.set_visible(is_visible());
}
block->multimesh_instance.set_multimesh(multimesh);
block->multimesh_instance.set_world(world);
block->multimesh_instance.set_transform(block_transform);
block->multimesh_instance.set_material_override(item->get_material_override());
block->multimesh_instance.set_cast_shadows_setting(item->get_cast_shadows_setting());
}
if (!block->multimesh_instance.is_valid()) {
block->multimesh_instance.create();
// Update bodies
const Vector<VoxelInstanceLibraryItem::CollisionShapeInfo> &collision_shapes = item->get_collision_shapes();
if (collision_shapes.size() > 0) {
VOXEL_PROFILE_SCOPE_NAMED("Update multimesh bodies");
const int data_block_size_po2 = _parent->get_data_block_size_pow2();
// Add new bodies
for (unsigned int instance_index = 0; instance_index < transforms.size(); ++instance_index) {
const Transform &local_transform = transforms[instance_index];
const Transform body_transform = block_transform * local_transform;
VoxelInstancerRigidBody *body;
if (instance_index < static_cast<unsigned int>(block->bodies.size())) {
body = block->bodies.write[instance_index];
} else {
body = memnew(VoxelInstancerRigidBody);
body->attach(this);
body->set_instance_index(instance_index);
body->set_render_block_index(block_index);
body->set_data_block_position(Vector3i::from_floored(body_transform.origin) >> data_block_size_po2);
for (int i = 0; i < collision_shapes.size(); ++i) {
const VoxelInstanceLibraryItem::CollisionShapeInfo &shape_info = collision_shapes[i];
CollisionShape *cs = memnew(CollisionShape);
cs->set_shape(shape_info.shape);
cs->set_transform(shape_info.transform);
body->add_child(cs);
}
add_child(body);
block->bodies.push_back(body);
}
body->set_transform(body_transform);
}
// Remove old bodies
for (int instance_index = transforms.size(); instance_index < block->bodies.size(); ++instance_index) {
VoxelInstancerRigidBody *body = block->bodies[instance_index];
body->detach_and_destroy();
}
block->bodies.resize(transforms.size());
}
block->multimesh_instance.set_multimesh(multimesh);
block->multimesh_instance.set_world(world);
block->multimesh_instance.set_transform(block_transform);
block->multimesh_instance.set_material_override(item->get_material_override());
block->multimesh_instance.set_cast_shadows_setting(item->get_cast_shadows_setting());
}
// Update bodies
const Vector<VoxelInstanceLibraryItem::CollisionShapeInfo> &collision_shapes = item->get_collision_shapes();
if (collision_shapes.size() > 0) {
VOXEL_PROFILE_SCOPE();
// Update scene instances
const VoxelInstanceLibrarySceneItem *scene_item = Object::cast_to<VoxelInstanceLibrarySceneItem>(item_base);
if (scene_item != nullptr) {
VOXEL_PROFILE_SCOPE_NAMED("Update scene instances");
ERR_FAIL_COND(scene_item->get_scene().is_null());
const int data_block_size_po2 = _parent->get_data_block_size_pow2();
// Add new bodies
// Add new instances
for (unsigned int instance_index = 0; instance_index < transforms.size(); ++instance_index) {
const Transform &local_transform = transforms[instance_index];
const Transform body_transform = block_transform * local_transform;
VoxelInstancerRigidBody *body;
SceneInstance instance;
if (instance_index < static_cast<unsigned int>(block->bodies.size())) {
body = block->bodies.write[instance_index];
instance = block->scene_instances.write[instance_index];
instance.root->set_transform(body_transform);
} else {
body = memnew(VoxelInstancerRigidBody);
body->attach(this);
body->set_instance_index(instance_index);
body->set_render_block_index(block_index);
body->set_data_block_position(Vector3i::from_floored(body_transform.origin) >> data_block_size_po2);
for (int i = 0; i < collision_shapes.size(); ++i) {
const VoxelInstanceLibraryItem::CollisionShapeInfo &shape_info = collision_shapes[i];
CollisionShape *cs = memnew(CollisionShape);
cs->set_shape(shape_info.shape);
cs->set_transform(shape_info.transform);
body->add_child(cs);
}
add_child(body);
block->bodies.push_back(body);
instance = create_scene_instance(
*scene_item, instance_index, block_index, body_transform, data_block_size_po2);
ERR_CONTINUE(instance.root == nullptr);
block->scene_instances.push_back(instance);
}
body->set_transform(body_transform);
// TODO Deserialize state
}
// Remove old bodies
for (int instance_index = transforms.size(); instance_index < block->bodies.size(); ++instance_index) {
VoxelInstancerRigidBody *body = block->bodies[instance_index];
body->detach_and_destroy();
// Remove old instances
for (int instance_index = transforms.size(); instance_index < block->scene_instances.size(); ++instance_index) {
SceneInstance instance = block->scene_instances[instance_index];
ERR_CONTINUE(instance.component == nullptr);
instance.component->detach();
ERR_CONTINUE(instance.root == nullptr);
instance.root->queue_delete();
}
block->bodies.resize(transforms.size());
block->scene_instances.resize(transforms.size());
}
}
@ -914,7 +981,7 @@ void VoxelInstancer::create_render_blocks(Vector3i render_grid_position, int lod
}
}
const VoxelInstanceLibraryItem *item = _library->get_item(layer_id);
const VoxelInstanceLibraryItemBase *item = _library->get_item(layer_id);
CRASH_COND(item == nullptr);
// Generate the rest
@ -966,6 +1033,8 @@ void VoxelInstancer::save_block(Vector3i data_grid_pos, int lod_index) const {
data->position_range = data_block_size;
const int render_to_data_factor = _parent->get_mesh_block_size() / _parent->get_data_block_size();
ERR_FAIL_COND_MSG(render_to_data_factor < 1 || render_to_data_factor > 2, "Unsupported block size");
const int half_render_block_size = _parent->get_mesh_block_size() / 2;
const Vector3i render_block_pos = data_grid_pos.floordiv(render_to_data_factor);
@ -974,7 +1043,7 @@ void VoxelInstancer::save_block(Vector3i data_grid_pos, int lod_index) const {
for (auto it = lod.layers.begin(); it != lod.layers.end(); ++it) {
const int layer_id = *it;
const VoxelInstanceLibraryItem *item = _library->get_item_const(layer_id);
const VoxelInstanceLibraryItemBase *item = _library->get_item_const(layer_id);
CRASH_COND(item == nullptr);
if (!item->is_persistent()) {
continue;
@ -986,62 +1055,95 @@ void VoxelInstancer::save_block(Vector3i data_grid_pos, int lod_index) const {
ERR_FAIL_COND(layer_id < 0);
const unsigned int *render_block_index_ptr = layer->blocks.getptr(render_block_pos);
if (render_block_index_ptr == nullptr) {
continue;
}
if (render_block_index_ptr != nullptr) {
const unsigned int render_block_index = *render_block_index_ptr;
const unsigned int render_block_index = *render_block_index_ptr;
#ifdef DEBUG_ENABLED
CRASH_COND(render_block_index >= _blocks.size());
CRASH_COND(render_block_index >= _blocks.size());
#endif
Block *render_block = _blocks[render_block_index];
Block *render_block = _blocks[render_block_index];
ERR_CONTINUE(render_block == nullptr);
data->layers.push_back(VoxelInstanceBlockData::LayerData());
VoxelInstanceBlockData::LayerData &layer_data = data->layers.back();
data->layers.push_back(VoxelInstanceBlockData::LayerData());
VoxelInstanceBlockData::LayerData &layer_data = data->layers.back();
layer_data.id = layer_id;
layer_data.instances.clear();
layer_data.id = layer_id;
if (item->get_generator().is_valid()) {
layer_data.scale_min = item->get_generator()->get_min_scale();
layer_data.scale_max = item->get_generator()->get_max_scale();
} else {
// TODO Calculate scale range automatically in the serializer
layer_data.scale_min = 0.1f;
layer_data.scale_max = 10.f;
if (item->get_generator().is_valid()) {
layer_data.scale_min = item->get_generator()->get_min_scale();
layer_data.scale_max = item->get_generator()->get_max_scale();
} else {
// TODO Calculate scale range automatically in the serializer
layer_data.scale_min = 0.1f;
layer_data.scale_max = 10.f;
}
if (render_block->multimesh_instance.is_valid()) {
// Multimeshes
Ref<MultiMesh> multimesh = render_block->multimesh_instance.get_multimesh();
CRASH_COND(multimesh.is_null());
VOXEL_PROFILE_SCOPE();
const int instance_count = get_visible_instance_count(**multimesh);
if (render_to_data_factor == 1) {
layer_data.instances.resize(instance_count);
// TODO Optimization: it would be nice to get the whole array at once
for (int instance_index = 0; instance_index < instance_count; ++instance_index) {
// TODO This is terrible in MT mode! Think about keeping a local copy...
layer_data.instances[instance_index].transform =
multimesh->get_instance_transform(instance_index);
}
} else if (render_to_data_factor == 2) {
for (int instance_index = 0; instance_index < instance_count; ++instance_index) {
// TODO This is terrible in MT mode! Think about keeping a local copy...
const Transform t = multimesh->get_instance_transform(instance_index);
const int instance_octant_index =
VoxelInstanceGenerator::get_octant_index(t.origin, half_render_block_size);
if (instance_octant_index == octant_index) {
VoxelInstanceBlockData::InstanceData d;
d.transform = t;
layer_data.instances.push_back(d);
}
}
}
if (render_block->multimesh_instance.is_valid()) {
Ref<MultiMesh> multimesh = render_block->multimesh_instance.get_multimesh();
CRASH_COND(multimesh.is_null());
} else if (render_block->scene_instances.size() > 0) {
// Scenes
VOXEL_PROFILE_SCOPE();
VOXEL_PROFILE_SCOPE();
const unsigned int instance_count = render_block->scene_instances.size();
layer_data.instances.clear();
const int instance_count = get_visible_instance_count(**multimesh);
if (render_to_data_factor == 1) {
layer_data.instances.resize(instance_count);
if (render_to_data_factor == 1) {
layer_data.instances.resize(instance_count);
for (unsigned int instance_index = 0; instance_index < instance_count; ++instance_index) {
const SceneInstance instance = render_block->scene_instances[instance_index];
ERR_CONTINUE(instance.root == nullptr);
layer_data.instances[instance_index].transform = instance.root->get_transform();
}
// TODO Optimization: it would be nice to get the whole array at once
for (int i = 0; i < instance_count; ++i) {
// TODO This is terrible in MT mode! Think about keeping a local copy...
layer_data.instances[i].transform = multimesh->get_instance_transform(i);
} else if (render_to_data_factor == 2) {
for (unsigned int instance_index = 0; instance_index < instance_count; ++instance_index) {
const SceneInstance instance = render_block->scene_instances[instance_index];
ERR_CONTINUE(instance.root == nullptr);
const Transform t = instance.root->get_transform();
const int instance_octant_index =
VoxelInstanceGenerator::get_octant_index(t.origin, half_render_block_size);
if (instance_octant_index == octant_index) {
VoxelInstanceBlockData::InstanceData d;
d.transform = t;
layer_data.instances.push_back(d);
}
} else if (render_to_data_factor == 2) {
for (int i = 0; i < instance_count; ++i) {
// TODO This is terrible in MT mode! Think about keeping a local copy...
const Transform t = multimesh->get_instance_transform(i);
const int instance_octant_index =
VoxelInstanceGenerator::get_octant_index(t.origin, half_render_block_size);
if (instance_octant_index == octant_index) {
VoxelInstanceBlockData::InstanceData d;
d.transform = t;
layer_data.instances.push_back(d);
}
}
} else {
ERR_FAIL_MSG("Unsupported block size");
// TODO Serialize state
}
}
}
@ -1051,6 +1153,154 @@ void VoxelInstancer::save_block(Vector3i data_grid_pos, int lod_index) const {
VoxelServer::get_singleton()->request_instance_block_save(volume_id, std::move(data), data_grid_pos, lod_index);
}
void VoxelInstancer::remove_floating_multimesh_instances(Block &block, const Transform &parent_transform,
Box3i p_voxel_box, const VoxelTool &voxel_tool, int block_size_po2) {
if (!block.multimesh_instance.is_valid()) {
// Empty block
return;
}
Ref<MultiMesh> multimesh = block.multimesh_instance.get_multimesh();
ERR_FAIL_COND(multimesh.is_null());
const int initial_instance_count = get_visible_instance_count(**multimesh);
int instance_count = initial_instance_count;
const Transform block_global_transform = Transform(parent_transform.basis,
parent_transform.xform((block.grid_position << block_size_po2).to_vec3()));
// Let's check all instances one by one
// Note: the fact we have to query VisualServer in and out is pretty bad though.
// - We probably have to sync with its thread in MT mode
// - A hashmap RID lookup is performed to check `RID_Owner::id_map`
for (int instance_index = 0; instance_index < instance_count; ++instance_index) {
// TODO This is terrible in MT mode! Think about keeping a local copy...
const Transform mm_transform = multimesh->get_instance_transform(instance_index);
const Vector3i voxel_pos(mm_transform.origin + block_global_transform.origin);
if (!p_voxel_box.contains(voxel_pos)) {
continue;
}
// 1-voxel cheap check without interpolation
const float sdf = voxel_tool.get_voxel_f(voxel_pos);
if (sdf < -0.1f) {
// Still enough ground
continue;
}
// Remove the MultiMesh instance
const int last_instance_index = --instance_count;
// TODO This is terrible in MT mode! Think about keeping a local copy...
const Transform last_trans = multimesh->get_instance_transform(last_instance_index);
multimesh->set_instance_transform(instance_index, last_trans);
// Remove the body if this block has some
// TODO In the case of bodies, we could use an overlap check
if (block.bodies.size() > 0) {
VoxelInstancerRigidBody *rb = block.bodies[instance_index];
// Detach so it won't try to update our instances, we already do it here
rb->detach_and_destroy();
VoxelInstancerRigidBody *moved_rb = block.bodies[last_instance_index];
if (moved_rb != rb) {
moved_rb->set_instance_index(instance_index);
block.bodies.write[instance_index] = moved_rb;
}
}
--instance_index;
// DEBUG
// Ref<CubeMesh> cm;
// cm.instance();
// cm->set_size(Vector3(0.5, 0.5, 0.5));
// MeshInstance *mi = memnew(MeshInstance);
// mi->set_mesh(cm);
// mi->set_transform(get_global_transform() *
// (Transform(Basis(), (block_pos << layer->lod_index).to_vec3()) * t));
// add_child(mi);
}
if (instance_count < initial_instance_count) {
// According to the docs, set_instance_count() resets the array so we only hide them instead
multimesh->set_visible_instance_count(instance_count);
if (block.bodies.size() > 0) {
block.bodies.resize(instance_count);
}
// Array args;
// args.push_back(instance_count);
// args.push_back(initial_instance_count);
// args.push_back(block_pos.to_vec3());
// args.push_back(layer->lod_index);
// args.push_back(multimesh->get_instance_count());
// print_line(
// String("Hiding instances from {0} to {1}. P: {2}, lod: {3}, total: {4}").format(args));
}
}
void VoxelInstancer::remove_floating_scene_instances(Block &block, const Transform &parent_transform,
Box3i p_voxel_box, const VoxelTool &voxel_tool, int block_size_po2) {
const int initial_instance_count = block.scene_instances.size();
int instance_count = initial_instance_count;
const Transform block_global_transform = Transform(parent_transform.basis,
parent_transform.xform((block.grid_position << block_size_po2).to_vec3()));
// Let's check all instances one by one
// Note: the fact we have to query VisualServer in and out is pretty bad though.
// - We probably have to sync with its thread in MT mode
// - A hashmap RID lookup is performed to check `RID_Owner::id_map`
for (int instance_index = 0; instance_index < instance_count; ++instance_index) {
SceneInstance instance = block.scene_instances[instance_index];
ERR_CONTINUE(instance.root == nullptr);
const Transform scene_transform = instance.root->get_transform();
const Vector3i voxel_pos(scene_transform.origin + block_global_transform.origin);
if (!p_voxel_box.contains(voxel_pos)) {
continue;
}
// 1-voxel cheap check without interpolation
const float sdf = voxel_tool.get_voxel_f(voxel_pos);
if (sdf < -0.1f) {
// Still enough ground
continue;
}
// Remove the MultiMesh instance
const int last_instance_index = --instance_count;
// TODO In the case of scene instances, we could use an overlap check or a signal.
// Detach so it won't try to update our instances, we already do it here
ERR_CONTINUE(instance.component == nullptr);
// Not using detach_as_removed(),
// this function is not marking the block as modified. It may be done by the caller.
instance.component->detach();
instance.root->queue_delete();
SceneInstance moved_instance = block.scene_instances[last_instance_index];
if (moved_instance.root != instance.root) {
if (moved_instance.component == nullptr) {
ERR_PRINT("Instance component should not be null");
} else {
moved_instance.component->set_instance_index(instance_index);
}
block.scene_instances.write[instance_index] = moved_instance;
}
--instance_index;
}
if (instance_count < initial_instance_count) {
if (block.scene_instances.size() > 0) {
block.scene_instances.resize(instance_count);
}
}
}
void VoxelInstancer::on_area_edited(Box3i p_voxel_box) {
VOXEL_PROFILE_SCOPE();
ERR_FAIL_COND(_parent == nullptr);
@ -1058,6 +1308,7 @@ void VoxelInstancer::on_area_edited(Box3i p_voxel_box) {
const int data_block_size = _parent->get_data_block_size();
Ref<VoxelTool> voxel_tool = _parent->get_voxel_tool();
ERR_FAIL_COND(voxel_tool.is_null());
voxel_tool->set_channel(VoxelBuffer::CHANNEL_SDF);
const Transform parent_transform = get_global_transform();
@ -1078,7 +1329,13 @@ void VoxelInstancer::on_area_edited(Box3i p_voxel_box) {
const std::vector<Block *> &blocks = _blocks;
const int block_size_po2 = base_block_size_po2 + layer->lod_index;
render_blocks_box.for_each_cell([layer, &blocks, voxel_tool, p_voxel_box, parent_transform, block_size_po2, &lod, data_blocks_box](
render_blocks_box.for_each_cell([layer,
&blocks,
voxel_tool,
p_voxel_box,
parent_transform,
block_size_po2,
&lod, data_blocks_box](
Vector3i block_pos) {
const unsigned int *iptr = layer->blocks.getptr(block_pos);
if (iptr == nullptr) {
@ -1087,71 +1344,14 @@ void VoxelInstancer::on_area_edited(Box3i p_voxel_box) {
}
Block *block = blocks[*iptr];
if (!block->multimesh_instance.is_valid()) {
// Empty block
return;
}
ERR_FAIL_COND(block == nullptr);
Ref<MultiMesh> multimesh = block->multimesh_instance.get_multimesh();
ERR_FAIL_COND(multimesh.is_null());
const int initial_instance_count = get_visible_instance_count(**multimesh);
int instance_count = initial_instance_count;
const Transform block_global_transform = Transform(parent_transform.basis,
parent_transform.xform((block_pos << block_size_po2).to_vec3()));
// Let's check all instances one by one
// Note: the fact we have to query VisualServer in and out is pretty bad though.
// - We probably have to sync with its thread in MT mode
// - A hashmap RID lookup is performed to check `RID_Owner::id_map`
for (int instance_index = 0; instance_index < instance_count; ++instance_index) {
// TODO This is terrible in MT mode! Think about keeping a local copy...
const Transform mm_transform = multimesh->get_instance_transform(instance_index);
const Vector3i voxel_pos(mm_transform.origin + block_global_transform.origin);
if (!p_voxel_box.contains(voxel_pos)) {
continue;
}
// 1-voxel cheap check without interpolation
const float sdf = voxel_tool->get_voxel_f(voxel_pos);
if (sdf < -0.1f) {
// Still enough ground
continue;
}
// Remove the MultiMesh instance
const int last_instance_index = --instance_count;
// TODO This is terrible in MT mode! Think about keeping a local copy...
const Transform last_trans = multimesh->get_instance_transform(last_instance_index);
multimesh->set_instance_transform(instance_index, last_trans);
// Remove the body if this block has some
// TODO In the case of bodies, we could use an overlap check
if (block->bodies.size() > 0) {
VoxelInstancerRigidBody *rb = block->bodies[instance_index];
// Detach so it won't try to update our instances, we already do it here
rb->detach_and_destroy();
VoxelInstancerRigidBody *moved_rb = block->bodies[last_instance_index];
if (moved_rb != rb) {
moved_rb->set_instance_index(instance_index);
block->bodies.write[instance_index] = moved_rb;
}
}
--instance_index;
// DEBUG
// Ref<CubeMesh> cm;
// cm.instance();
// cm->set_size(Vector3(0.5, 0.5, 0.5));
// MeshInstance *mi = memnew(MeshInstance);
// mi->set_mesh(cm);
// mi->set_transform(get_global_transform() *
// (Transform(Basis(), (block_pos << layer->lod_index).to_vec3()) * t));
// add_child(mi);
if (block->scene_instances.size() > 0) {
remove_floating_scene_instances(
*block, parent_transform, p_voxel_box, **voxel_tool, block_size_po2);
} else {
remove_floating_multimesh_instances(
*block, parent_transform, p_voxel_box, **voxel_tool, block_size_po2);
}
// All instances have to be frozen as edited.
@ -1160,24 +1360,6 @@ void VoxelInstancer::on_area_edited(Box3i p_voxel_box) {
data_blocks_box.for_each_cell([&lod](Vector3i data_block_pos) {
lod.modified_blocks.set(data_block_pos, true);
});
if (instance_count < initial_instance_count) {
// According to the docs, set_instance_count() resets the array so we only hide them instead
multimesh->set_visible_instance_count(instance_count);
if (block->bodies.size() > 0) {
block->bodies.resize(instance_count);
}
// Array args;
// args.push_back(instance_count);
// args.push_back(initial_instance_count);
// args.push_back(block_pos.to_vec3());
// args.push_back(layer->lod_index);
// args.push_back(multimesh->get_instance_count());
// print_line(
// String("Hiding instances from {0} to {1}. P: {2}, lod: {3}, total: {4}").format(args));
}
});
}
}
@ -1222,6 +1404,40 @@ void VoxelInstancer::on_body_removed(Vector3i data_block_position, unsigned int
lod.modified_blocks.set(data_block_position, true);
}
void VoxelInstancer::on_scene_instance_removed(Vector3i data_block_position, unsigned int render_block_index, int instance_index) {
Block *block = _blocks[render_block_index];
CRASH_COND(block == nullptr);
ERR_FAIL_INDEX(instance_index, block->bodies.size());
// Unregister the scene instance
int instance_count = block->scene_instances.size();
int last_instance_index = --instance_count;
SceneInstance moved_instance = block->scene_instances[last_instance_index];
if (instance_index != last_instance_index) {
ERR_FAIL_COND(moved_instance.component == nullptr);
moved_instance.component->set_instance_index(instance_index);
block->scene_instances.write[instance_index] = moved_instance;
}
block->scene_instances.resize(instance_count);
// Mark data block as modified
const Layer *layer = get_layer(block->layer_id);
CRASH_COND(layer == nullptr);
Lod &lod = _lods[layer->lod_index];
lod.modified_blocks.set(data_block_position, true);
}
void VoxelInstancer::on_scene_instance_modified(Vector3i data_block_position, unsigned int render_block_index) {
Block *block = _blocks[render_block_index];
CRASH_COND(block == nullptr);
// Mark data block as modified
const Layer *layer = get_layer(block->layer_id);
CRASH_COND(layer == nullptr);
Lod &lod = _lods[layer->lod_index];
lod.modified_blocks.set(data_block_position, true);
}
int VoxelInstancer::debug_get_block_count() const {
return _blocks.size();
}

View File

@ -7,6 +7,7 @@
#include "../../util/math/box3i.h"
#include "voxel_instance_generator.h"
#include "voxel_instance_library.h"
#include "voxel_instance_library_item.h"
#include <scene/3d/spatial.h>
//#include <scene/resources/material.h> // Included by node.h lol
@ -17,6 +18,9 @@
class VoxelLodTerrain;
class VoxelInstancerRigidBody;
class VoxelInstanceComponent;
class VoxelInstanceLibrarySceneItem;
class VoxelTool;
class PhysicsBody;
// Note: a large part of this node could be made generic to support the sole idea of instancing within octants?
@ -58,6 +62,8 @@ public:
void on_mesh_block_exit(Vector3i render_grid_position, unsigned int lod_index);
void on_area_edited(Box3i p_voxel_box);
void on_body_removed(Vector3i data_block_position, unsigned int render_block_index, int instance_index);
void on_scene_instance_removed(Vector3i data_block_position, unsigned int render_block_index, int instance_index);
void on_scene_instance_modified(Vector3i data_block_position, unsigned int render_block_index);
// Debug
@ -89,15 +95,31 @@ private:
const Layer *get_layer_const(int id) const;
void regenerate_layer(uint16_t layer_id, bool regenerate_blocks);
void update_layer_meshes(int layer_id);
void update_layer_scenes(int layer_id);
void create_render_blocks(Vector3i grid_position, int lod_index, Array surface_arrays);
struct SceneInstance {
VoxelInstanceComponent *component = nullptr;
Spatial *root = nullptr;
};
SceneInstance create_scene_instance(const VoxelInstanceLibrarySceneItem &scene_item,
int instance_index, unsigned int block_index, Transform transform, int data_block_size_po2);
void update_block_from_transforms(int block_index, Span<const Transform> transforms,
Vector3i grid_position, Layer *layer, const VoxelInstanceLibraryItem *item, uint16_t layer_id,
Vector3i grid_position, Layer *layer, const VoxelInstanceLibraryItemBase *item_base, uint16_t layer_id,
World *world, const Transform &block_transform);
void on_library_item_changed(int item_id, VoxelInstanceLibraryItem::ChangeType change) override;
struct Block;
static void remove_floating_multimesh_instances(Block &block, const Transform &parent_transform, Box3i p_voxel_box,
const VoxelTool &voxel_tool, int block_size_po2);
static void remove_floating_scene_instances(Block &block, const Transform &parent_transform, Box3i p_voxel_box,
const VoxelTool &voxel_tool, int block_size_po2);
static void _bind_methods();
// TODO Rename RenderBlock?
@ -111,7 +133,9 @@ private:
// For physics we use nodes because it's easier to manage.
// Such instances may be less numerous.
// If the item associated to this block has no collisions, this will be empty.
// Indices in the vector correspond to index of the instance in multimesh.
Vector<VoxelInstancerRigidBody *> bodies;
Vector<SceneInstance> scene_instances;
};
struct Layer {

View File

@ -0,0 +1,65 @@
#ifndef VOXEL_INSTANCER_RIGIDBODY_H
#define VOXEL_INSTANCER_RIGIDBODY_H
#include "voxel_instancer.h"
#include <scene/3d/physics_body.h>
// Provides collision to VoxelInstancer multimesh instances
class VoxelInstancerRigidBody : public RigidBody {
GDCLASS(VoxelInstancerRigidBody, RigidBody);
public:
VoxelInstancerRigidBody() {
set_mode(RigidBody::MODE_STATIC);
}
void set_data_block_position(Vector3i data_block_position) {
_data_block_position = data_block_position;
}
void set_render_block_index(unsigned int render_block_index) {
_render_block_index = render_block_index;
}
void set_instance_index(int instance_index) {
_instance_index = instance_index;
}
void attach(VoxelInstancer *parent) {
_parent = parent;
}
void detach_and_destroy() {
_parent = nullptr;
queue_delete();
}
// Note, for this the body must switch to convex shapes
// void detach_and_become_rigidbody() {
// //...
// }
protected:
void _notification(int p_what) {
switch (p_what) {
// TODO Optimization: this is also called when we quit the game or destroy the world
// which can make things a bit slow, but I don't know if it can easily be avoided
case NOTIFICATION_UNPARENTED:
// The user could queue_free() that node in game,
// so we have to notify the instancer to remove the multimesh instance and pointer
if (_parent != nullptr) {
_parent->on_body_removed(_data_block_position, _render_block_index, _instance_index);
_parent = nullptr;
}
break;
}
}
private:
VoxelInstancer *_parent = nullptr;
Vector3i _data_block_position;
unsigned int _render_block_index;
int _instance_index = -1;
};
#endif // VOXEL_INSTANCER_RIGIDBODY_H