godot_voxel/terrain/instancing/voxel_instancer.h
2021-02-17 20:34:35 +00:00

168 lines
5.5 KiB
C++

#ifndef VOXEL_INSTANCER_H
#define VOXEL_INSTANCER_H
#include "../../streams/instance_data.h"
#include "../../util/array_slice.h"
#include "../../util/fixed_array.h"
#include "../../util/godot/direct_multimesh_instance.h"
#include "../../util/math/rect3i.h"
#include "voxel_instance_generator.h"
#include "voxel_instance_library.h"
#include <scene/3d/spatial.h>
//#include <scene/resources/material.h> // Included by node.h lol
#include <limits>
#include <memory>
#include <unordered_map>
#include <vector>
class VoxelLodTerrain;
class VoxelInstancerRigidBody;
class PhysicsBody;
// 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.
// 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 Spatial, public VoxelInstanceLibrary::IListener {
GDCLASS(VoxelInstancer, Spatial)
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_block_data_loaded(Vector3i grid_position, unsigned int lod_index,
std::unique_ptr<VoxelInstanceBlockData> instances);
void on_block_enter(Vector3i grid_position, unsigned int lod_index, Array surface_arrays);
void on_block_exit(Vector3i grid_position, unsigned int lod_index);
void on_area_edited(Rect3i p_voxel_box);
void on_body_removed(unsigned int block_index, int instance_index);
// Debug
int debug_get_block_count() const;
String get_configuration_warning() const override;
protected:
void _notification(int p_what);
private:
struct Block;
struct Layer;
void process_mesh_lods();
void add_layer(int layer_id, int lod_index);
void remove_layer(int layer_id);
int create_block(Layer *layer, uint16_t layer_id, Vector3i grid_position);
void remove_block(unsigned int block_index);
void set_world(World *world);
void clear_blocks();
void clear_blocks_in_layer(int layer_id);
void clear_layers();
void update_visibility();
void save_block(Vector3i grid_pos, int lod_index) const;
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 create_blocks(const VoxelInstanceBlockData *instances_data, Vector3i grid_position, int lod_index,
Array surface_arrays);
void update_block_from_transforms(int block_index, ArraySlice<const Transform> transforms,
Vector3i grid_position, Layer *layer, const VoxelInstanceLibraryItem *item, uint16_t layer_id,
World *world, const Transform &block_transform);
void on_library_item_changed(int item_id, VoxelInstanceLibraryItem::ChangeType change) override;
static void _bind_methods();
struct Block {
uint16_t layer_id;
uint8_t current_mesh_lod = 0;
uint8_t lod_index;
Vector3i grid_position;
DirectMultiMeshInstance multimesh_instance;
// For physics we use nodes because it's easier to manage.
// Such instances may be less numerous.
Vector<VoxelInstancerRigidBody *> bodies;
};
struct Layer {
unsigned int lod_index;
HashMap<Vector3i, unsigned int, Vector3iHasher> 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 {
std::vector<int> layers;
// Blocks that have have unsaved changes
HashMap<Vector3i, bool, Vector3iHasher> 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.
// Can't use `HashMap` because it lacks move semantics.
std::unordered_map<Vector3i, std::unique_ptr<VoxelInstanceBlockData> > loaded_instances_data;
FixedArray<MeshLodDistances, VoxelInstanceLibraryItem::MAX_MESH_LODS> mesh_lod_distances;
Lod() = default;
Lod(const Lod &) = delete; // non construction-copyable
Lod &operator=(const Lod &) = delete; // non copyable
};
UpMode _up_mode = UP_MODE_POSITIVE_Y;
FixedArray<Lod, MAX_LOD> _lods;
std::vector<Block *> _blocks; // Does not have nulls
HashMap<int, Layer> _layers; // Each layer corresponds to a library item
Ref<VoxelInstanceLibrary> _library;
std::vector<Transform> _transform_cache;
VoxelLodTerrain *_parent;
};
VARIANT_ENUM_CAST(VoxelInstancer::UpMode);
#endif // VOXEL_INSTANCER_H