godot_voxel/terrain/instancing/voxel_instancer.h

252 lines
8.4 KiB
C++

#ifndef VOXEL_INSTANCER_H
#define VOXEL_INSTANCER_H
#include "../../constants/voxel_constants.h"
#include "../../streams/instance_data.h"
#include "../../util/fixed_array.h"
#include "../../util/godot/direct_multimesh_instance.h"
#include "../../util/math/box3i.h"
#include "../../util/memory.h"
#include "voxel_instance_generator.h"
#include "voxel_instance_library.h"
#include "voxel_instance_library_multimesh_item.h"
#ifdef TOOLS_ENABLED
#include "../../editor/voxel_debug.h"
#endif
#include <scene/3d/node_3d.h>
//#include <scene/resources/material.h> // Included by node.h lol
#include <limits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
class PhysicsBody3D;
namespace zylann::voxel {
class VoxelNode;
class VoxelInstancerRigidBody;
class VoxelInstanceComponent;
class VoxelInstanceLibrarySceneItem;
class VoxelTool;
// Note: a large part of this node could be made generic to support the sole idea of instancing within octants?
// Even nodes like gridmaps could be rebuilt on top of this, if its concept of "grid" was decoupled.
// It is coupled to terrain at the moment because of performance.
// Add-on to voxel nodes, allowing to spawn elements on the surface.
// These elements are rendered with hardware instancing, can have collisions, and also be persistent.
class VoxelInstancer : public Node3D, public VoxelInstanceLibrary::IListener {
GDCLASS(VoxelInstancer, Node3D)
public:
static const int MAX_LOD = 8;
enum UpMode {
UP_MODE_POSITIVE_Y = VoxelInstanceGenerator::UP_MODE_POSITIVE_Y,
UP_MODE_SPHERE = VoxelInstanceGenerator::UP_MODE_SPHERE,
UP_MODE_COUNT = VoxelInstanceGenerator::UP_MODE_COUNT
};
VoxelInstancer();
~VoxelInstancer();
// Properties
void set_up_mode(UpMode mode);
UpMode get_up_mode() const;
void set_library(Ref<VoxelInstanceLibrary> library);
Ref<VoxelInstanceLibrary> get_library() const;
// Actions
void save_all_modified_blocks();
// Event handlers
void on_data_block_loaded(Vector3i grid_position, unsigned int lod_index, UniquePtr<InstanceBlockData> instances);
void on_mesh_block_enter(Vector3i render_grid_position, unsigned int lod_index, Array surface_arrays);
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, unsigned int instance_index);
void on_scene_instance_removed(
Vector3i data_block_position, unsigned int render_block_index, unsigned int instance_index);
void on_scene_instance_modified(Vector3i data_block_position, unsigned int render_block_index);
// Internal properties
void set_mesh_block_size_po2(unsigned int p_mesh_block_size_po2);
void set_data_block_size_po2(unsigned int p_data_block_size_po2);
void set_mesh_lod_distance(float p_lod_distance);
// Debug
int debug_get_block_count() const;
void debug_get_instance_counts(std::unordered_map<uint32_t, uint32_t> &counts_per_layer) const;
void debug_dump_as_scene(String fpath) const;
Node *debug_dump_as_nodes() const;
void debug_set_draw_enabled(bool enabled);
bool debug_is_draw_enabled() const;
enum DebugDrawFlag { //
DEBUG_DRAW_ALL_BLOCKS,
DEBUG_DRAW_EDITED_BLOCKS,
DEBUG_DRAW_FLAGS_COUNT
};
void debug_set_draw_flag(DebugDrawFlag flag_index, bool enabled);
bool debug_get_draw_flag(DebugDrawFlag flag_index) const;
// Editor
#ifdef TOOLS_ENABLED
TypedArray<String> get_configuration_warnings() const override;
#endif
protected:
void _notification(int p_what);
private:
struct Layer;
void process_mesh_lods();
void add_layer(int layer_id, int lod_index);
void remove_layer(int layer_id);
unsigned int create_block(Layer &layer, uint16_t layer_id, Vector3i grid_position);
void remove_block(unsigned int block_index);
void set_world(World3D *world);
void clear_blocks();
void clear_blocks_in_layer(int layer_id);
void clear_layers();
void update_visibility();
void save_block(Vector3i data_grid_pos, int lod_index) const;
// Get a layer assuming it exists
Layer &get_layer(int id);
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);
#ifdef TOOLS_ENABLED
void process_gizmos();
#endif
struct SceneInstance {
// Owned by the scene tree.
VoxelInstanceComponent *component = nullptr;
Node3D *root = nullptr;
};
SceneInstance create_scene_instance(const VoxelInstanceLibrarySceneItem &scene_item, int instance_index,
unsigned int block_index, Transform3D transform, int data_block_size_po2);
void update_block_from_transforms(int block_index, Span<const Transform3D> transforms, Vector3i grid_position,
Layer &layer, const VoxelInstanceLibraryItem &item_base, uint16_t layer_id, World3D &world,
const Transform3D &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 Transform3D &parent_transform,
Box3i p_voxel_box, const VoxelTool &voxel_tool, int block_size_po2);
static void remove_floating_scene_instances(Block &block, const Transform3D &parent_transform, Box3i p_voxel_box,
const VoxelTool &voxel_tool, int block_size_po2);
Dictionary _b_debug_get_instance_counts() const;
static void _bind_methods();
// TODO Rename RenderBlock?
struct Block {
uint16_t layer_id;
uint8_t current_mesh_lod = 0;
uint8_t lod_index;
// Position in mesh block coordinate system
Vector3i grid_position;
DirectMultiMeshInstance multimesh_instance;
// 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.
std::vector<VoxelInstancerRigidBody *> bodies;
std::vector<SceneInstance> scene_instances;
};
struct Layer {
unsigned int lod_index;
// Blocks indexed by grid position.
// Keys follow the mesh block coordinate system.
std::unordered_map<Vector3i, unsigned int> blocks;
};
struct MeshLodDistances {
// Multimesh LOD updates based on the distance between the camera and the center of the block.
// Two distances are used to implement hysteresis, which allows to avoid oscillating too fast between lods.
// TODO Need to investigate if Godot 4 implements LOD for multimeshes
// Despite this, due to how Godot 4 implements LOD, it may still be beneficial to have a custom LOD system,
// so we can switch to impostors rather than only decimating geometry
// Distance above which the mesh starts being used, taking precedence over meshes of lower distance.
float enter_distance_squared;
// Distance under which the mesh stops being used
float exit_distance_squared;
};
struct Lod : public NonCopyable {
std::vector<int> layers;
// Blocks that have have unsaved changes.
// Keys follows the data block coordinate system.
std::unordered_set<Vector3i> modified_blocks;
// This is a temporary place to store loaded instances data while it's not visible yet.
// These instances are user-authored ones. If a block does not have an entry there,
// it will get generated instances.
// Keys follows the data block coordinate system.
// Can't use `HashMap` because it lacks move semantics.
std::unordered_map<Vector3i, UniquePtr<InstanceBlockData>> loaded_instances_data;
FixedArray<MeshLodDistances, VoxelInstanceLibraryMultiMeshItem::MAX_MESH_LODS> mesh_lod_distances;
};
UpMode _up_mode = UP_MODE_POSITIVE_Y;
FixedArray<Lod, MAX_LOD> _lods;
// Does not have nulls. Indices matter.
std::vector<UniquePtr<Block>> _blocks;
// Each layer corresponds to a library item. Addresses of values in the map are expected to be stable.
std::unordered_map<int, Layer> _layers;
Ref<VoxelInstanceLibrary> _library;
VoxelNode *_parent = nullptr;
unsigned int _parent_data_block_size_po2 = constants::DEFAULT_BLOCK_SIZE_PO2;
unsigned int _parent_mesh_block_size_po2 = constants::DEFAULT_BLOCK_SIZE_PO2;
float _mesh_lod_distance = 0.f;
#ifdef TOOLS_ENABLED
DebugRenderer _debug_renderer;
bool _gizmos_enabled = false;
uint8_t _debug_draw_flags = 0;
#endif
};
} // namespace zylann::voxel
VARIANT_ENUM_CAST(zylann::voxel::VoxelInstancer::UpMode);
VARIANT_ENUM_CAST(zylann::voxel::VoxelInstancer::DebugDrawFlag);
#endif // VOXEL_INSTANCER_H