Moved instance generation to its own class
parent
b65b0b8045
commit
4daf6718f8
|
@ -69,6 +69,7 @@ void register_voxel_types() {
|
|||
ClassDB::register_class<VoxelLodTerrain>();
|
||||
ClassDB::register_class<VoxelViewer>();
|
||||
ClassDB::register_class<VoxelInstancer>();
|
||||
ClassDB::register_class<VoxelInstanceGenerator>();
|
||||
|
||||
// Streams
|
||||
ClassDB::register_virtual_class<VoxelStream>();
|
||||
|
|
|
@ -0,0 +1,311 @@
|
|||
#include "voxel_instance_generator.h"
|
||||
#include "../util/profiling.h"
|
||||
#include <scene/resources/mesh.h>
|
||||
|
||||
static inline Vector3 normalized(Vector3 pos, float &length) {
|
||||
length = pos.length();
|
||||
if (length == 0) {
|
||||
return Vector3();
|
||||
}
|
||||
pos.x /= length;
|
||||
pos.y /= length;
|
||||
pos.z /= length;
|
||||
return pos;
|
||||
}
|
||||
|
||||
void VoxelInstanceGenerator::generate_transforms(
|
||||
std::vector<Transform> &out_transforms,
|
||||
Vector3i grid_position,
|
||||
int lod_index,
|
||||
int layer_index,
|
||||
Array surface_arrays,
|
||||
const Transform &block_local_transform,
|
||||
UpMode up_mode) {
|
||||
|
||||
VOXEL_PROFILE_SCOPE();
|
||||
|
||||
if (surface_arrays.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
PoolVector3Array vertices = surface_arrays[ArrayMesh::ARRAY_VERTEX];
|
||||
|
||||
if (vertices.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
PoolVector3Array normals = surface_arrays[ArrayMesh::ARRAY_NORMAL];
|
||||
|
||||
const uint32_t block_pos_hash = Vector3iHasher::hash(grid_position);
|
||||
|
||||
const uint32_t density_u32 = 0xffffffff * _density;
|
||||
const float vertical_alignment = _vertical_alignment;
|
||||
const float scale_min = _min_scale;
|
||||
const float scale_range = _max_scale - _min_scale;
|
||||
const bool random_vertical_flip = _random_vertical_flip;
|
||||
const float offset_along_normal = _offset_along_normal;
|
||||
const float normal_min_y = _min_surface_normal_y;
|
||||
const float normal_max_y = _max_surface_normal_y;
|
||||
const bool slope_filter = normal_min_y != -1.f || normal_max_y != 1.f;
|
||||
const bool height_filter = _min_height != std::numeric_limits<float>::min() ||
|
||||
_max_height != std::numeric_limits<float>::max();
|
||||
const float min_height = _min_height;
|
||||
const float max_height = _max_height;
|
||||
|
||||
Vector3 global_up(0.f, 1.f, 0.f);
|
||||
|
||||
// Using different number generators so changing parameters affecting one doesn't affect the other
|
||||
const uint64_t seed = block_pos_hash + layer_index;
|
||||
RandomPCG pcg0;
|
||||
pcg0.seed(seed);
|
||||
RandomPCG pcg1;
|
||||
pcg1.seed(seed + 1);
|
||||
|
||||
out_transforms.clear();
|
||||
|
||||
PoolVector3Array::Read vr = vertices.read();
|
||||
PoolVector3Array::Read nr = normals.read();
|
||||
|
||||
// TODO This part might be moved to the meshing thread if it turns out to be too heavy
|
||||
|
||||
for (size_t i = 0; i < vertices.size(); ++i) {
|
||||
// TODO We could actually generate indexes and pick those, rather than iterating them all and rejecting
|
||||
if (pcg0.rand() >= density_u32) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Transform t;
|
||||
t.origin = vr[i];
|
||||
|
||||
// TODO Check if that position has been edited somehow, so we can decide to not spawn there
|
||||
// Or remesh from generator and compare sdf but that's expensive
|
||||
|
||||
Vector3 axis_y;
|
||||
|
||||
// Warning: sometimes mesh normals are not perfectly normalized.
|
||||
// The cause is for meshing speed on CPU. It's normalized on GPU anyways.
|
||||
Vector3 surface_normal = nr[i];
|
||||
bool surface_normal_is_normalized = false;
|
||||
bool sphere_up_is_computed = false;
|
||||
bool sphere_distance_is_computed = false;
|
||||
float sphere_distance;
|
||||
|
||||
if (vertical_alignment == 0.f) {
|
||||
surface_normal.normalize();
|
||||
surface_normal_is_normalized = true;
|
||||
axis_y = surface_normal;
|
||||
|
||||
} else {
|
||||
if (up_mode == UP_MODE_SPHERE) {
|
||||
global_up = normalized(block_local_transform.origin + t.origin, sphere_distance);
|
||||
sphere_up_is_computed = true;
|
||||
sphere_distance_is_computed = true;
|
||||
}
|
||||
|
||||
if (vertical_alignment < 1.f) {
|
||||
axis_y = surface_normal.linear_interpolate(global_up, vertical_alignment).normalized();
|
||||
|
||||
} else {
|
||||
axis_y = global_up;
|
||||
}
|
||||
}
|
||||
|
||||
if (slope_filter) {
|
||||
if (!surface_normal_is_normalized) {
|
||||
surface_normal.normalize();
|
||||
}
|
||||
|
||||
float ny = surface_normal.y;
|
||||
if (up_mode == UP_MODE_SPHERE) {
|
||||
if (!sphere_up_is_computed) {
|
||||
global_up = normalized(block_local_transform.origin + t.origin, sphere_distance);
|
||||
sphere_up_is_computed = true;
|
||||
sphere_distance_is_computed = true;
|
||||
}
|
||||
ny = surface_normal.dot(global_up);
|
||||
}
|
||||
|
||||
if (ny < normal_min_y || ny > normal_max_y) {
|
||||
// Discard
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (height_filter) {
|
||||
float y = t.origin.y;
|
||||
if (up_mode == UP_MODE_SPHERE) {
|
||||
if (!sphere_distance_is_computed) {
|
||||
sphere_distance = (block_local_transform.origin + t.origin).length();
|
||||
sphere_distance_is_computed = true;
|
||||
}
|
||||
y = sphere_distance;
|
||||
}
|
||||
|
||||
if (y < min_height || y > max_height) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
t.origin += offset_along_normal * axis_y;
|
||||
|
||||
// Allows to use two faces of a single rock to create variety in the same layer
|
||||
if (random_vertical_flip && (pcg1.rand() & 1) == 1) {
|
||||
axis_y = -axis_y;
|
||||
// TODO Should have to flip another axis as well?
|
||||
}
|
||||
|
||||
// Pick a random rotation from the floor's normal.
|
||||
// TODO A pool of precomputed random directions would do the job too
|
||||
const Vector3 dir = Vector3(pcg1.randf() - 0.5f, pcg1.randf() - 0.5f, pcg1.randf() - 0.5f);
|
||||
const Vector3 axis_x = axis_y.cross(dir).normalized();
|
||||
const Vector3 axis_z = axis_x.cross(axis_y);
|
||||
|
||||
t.basis = Basis(
|
||||
Vector3(axis_x.x, axis_y.x, axis_z.x),
|
||||
Vector3(axis_x.y, axis_y.y, axis_z.y),
|
||||
Vector3(axis_x.z, axis_y.z, axis_z.z));
|
||||
|
||||
if (scale_range > 0.f) {
|
||||
const float scale = scale_min + scale_range * pcg1.randf();
|
||||
t.basis.scale(Vector3(scale, scale, scale));
|
||||
|
||||
} else if (scale_min != 1.f) {
|
||||
t.basis.scale(Vector3(scale_min, scale_min, scale_min));
|
||||
}
|
||||
|
||||
out_transforms.push_back(t);
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelInstanceGenerator::set_density(float density) {
|
||||
_density = max(density, 0.f);
|
||||
}
|
||||
|
||||
float VoxelInstanceGenerator::get_density() const {
|
||||
return _density;
|
||||
}
|
||||
|
||||
void VoxelInstanceGenerator::set_min_scale(float min_scale) {
|
||||
_min_scale = min_scale;
|
||||
}
|
||||
|
||||
float VoxelInstanceGenerator::get_min_scale() const {
|
||||
return _min_scale;
|
||||
}
|
||||
|
||||
void VoxelInstanceGenerator::set_max_scale(float max_scale) {
|
||||
_max_scale = max_scale;
|
||||
}
|
||||
|
||||
float VoxelInstanceGenerator::get_max_scale() const {
|
||||
return _max_scale;
|
||||
}
|
||||
|
||||
void VoxelInstanceGenerator::set_vertical_alignment(float amount) {
|
||||
_vertical_alignment = clamp(amount, 0.f, 1.f);
|
||||
}
|
||||
|
||||
float VoxelInstanceGenerator::get_vertical_alignment() const {
|
||||
return _vertical_alignment;
|
||||
}
|
||||
|
||||
void VoxelInstanceGenerator::set_offset_along_normal(float offset) {
|
||||
_offset_along_normal = offset;
|
||||
}
|
||||
|
||||
float VoxelInstanceGenerator::get_offset_along_normal() const {
|
||||
return _offset_along_normal;
|
||||
}
|
||||
|
||||
void VoxelInstanceGenerator::set_min_slope_degrees(float degrees) {
|
||||
_max_surface_normal_y = min(1.f, Math::cos(Math::deg2rad(clamp(degrees, -180.f, 180.f))));
|
||||
}
|
||||
|
||||
float VoxelInstanceGenerator::get_min_slope_degrees() const {
|
||||
return _max_surface_normal_y;
|
||||
}
|
||||
|
||||
void VoxelInstanceGenerator::set_max_slope_degrees(float degrees) {
|
||||
_min_surface_normal_y = max(-1.f, Math::cos(Math::deg2rad(clamp(degrees, -180.f, 180.f))));
|
||||
}
|
||||
|
||||
float VoxelInstanceGenerator::get_max_slope_degrees() const {
|
||||
return _min_surface_normal_y;
|
||||
}
|
||||
|
||||
void VoxelInstanceGenerator::set_min_height(float h) {
|
||||
_min_height = h;
|
||||
}
|
||||
|
||||
float VoxelInstanceGenerator::get_min_height() const {
|
||||
return _min_height;
|
||||
}
|
||||
|
||||
void VoxelInstanceGenerator::set_max_height(float h) {
|
||||
_max_height = h;
|
||||
}
|
||||
|
||||
float VoxelInstanceGenerator::get_max_height() const {
|
||||
return _max_height;
|
||||
}
|
||||
|
||||
void VoxelInstanceGenerator::set_random_vertical_flip(bool flip_enabled) {
|
||||
_random_vertical_flip = flip_enabled;
|
||||
}
|
||||
|
||||
bool VoxelInstanceGenerator::get_random_vertical_flip() const {
|
||||
return _random_vertical_flip;
|
||||
}
|
||||
|
||||
void VoxelInstanceGenerator::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_density", "density"), &VoxelInstanceGenerator::set_density);
|
||||
ClassDB::bind_method(D_METHOD("get_density"), &VoxelInstanceGenerator::get_density);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_min_scale", "min_scale"), &VoxelInstanceGenerator::set_min_scale);
|
||||
ClassDB::bind_method(D_METHOD("get_min_scale"), &VoxelInstanceGenerator::get_min_scale);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_max_scale", "max_scale"), &VoxelInstanceGenerator::set_max_scale);
|
||||
ClassDB::bind_method(D_METHOD("get_max_scale"), &VoxelInstanceGenerator::get_max_scale);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_vertical_alignment", "amount"), &VoxelInstanceGenerator::set_vertical_alignment);
|
||||
ClassDB::bind_method(D_METHOD("get_vertical_alignment"), &VoxelInstanceGenerator::get_vertical_alignment);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_offset_along_normal", "offset"),
|
||||
&VoxelInstanceGenerator::set_offset_along_normal);
|
||||
ClassDB::bind_method(D_METHOD("get_offset_along_normal"), &VoxelInstanceGenerator::get_offset_along_normal);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_min_slope_degrees", "degrees"), &VoxelInstanceGenerator::set_min_slope_degrees);
|
||||
ClassDB::bind_method(D_METHOD("get_min_slope_degrees"), &VoxelInstanceGenerator::get_min_slope_degrees);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_max_slope_degrees", "degrees"), &VoxelInstanceGenerator::set_max_slope_degrees);
|
||||
ClassDB::bind_method(D_METHOD("get_max_slope_degrees"), &VoxelInstanceGenerator::get_max_slope_degrees);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_min_height", "height"), &VoxelInstanceGenerator::set_min_height);
|
||||
ClassDB::bind_method(D_METHOD("get_min_height"), &VoxelInstanceGenerator::get_min_height);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_max_height", "height"), &VoxelInstanceGenerator::set_max_height);
|
||||
ClassDB::bind_method(D_METHOD("get_max_height"), &VoxelInstanceGenerator::get_max_height);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_random_vertical_flip", "enabled"),
|
||||
&VoxelInstanceGenerator::set_random_vertical_flip);
|
||||
ClassDB::bind_method(D_METHOD("get_random_vertical_flip"), &VoxelInstanceGenerator::get_random_vertical_flip);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "density", PROPERTY_HINT_RANGE, "0.0, 10.0, 0.1"),
|
||||
"set_density", "get_density");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "min_scale", PROPERTY_HINT_RANGE, "0.0, 1000.0, 0.1"),
|
||||
"set_min_scale", "get_min_scale");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_scale", PROPERTY_HINT_RANGE, "0.0, 1000.0, 0.1"),
|
||||
"set_max_scale", "get_max_scale");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "vertical_alignment", PROPERTY_HINT_RANGE, "0.0, 1.0, 0.01"),
|
||||
"set_vertical_alignment", "get_vertical_alignment");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "offset_along_normal"),
|
||||
"set_offset_along_normal", "get_offset_along_normal");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "min_slope_degrees", PROPERTY_HINT_RANGE, "-180.0, 180.0, 0.1"),
|
||||
"set_min_slope_degrees", "get_min_slope_degrees");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_slope_degrees", PROPERTY_HINT_RANGE, "-180.0, 180.0, 0.1"),
|
||||
"set_max_slope_degrees", "get_max_slope_degrees");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "min_height"), "set_min_height", "get_min_height");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_height"), "set_max_height", "get_max_height");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "random_vertical_flip"),
|
||||
"set_random_vertical_flip", "get_random_vertical_flip");
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
#ifndef VOXEL_INSTANCE_GENERATOR_H
|
||||
#define VOXEL_INSTANCE_GENERATOR_H
|
||||
|
||||
#include "../storage/voxel_buffer.h"
|
||||
#include <core/resource.h>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
// TODO This may have to be moved to the meshing thread some day
|
||||
|
||||
// Decides where to spawn instances on top of a voxel surface.
|
||||
// Note: to generate voxels themselves, see `VoxelGenerator`.
|
||||
class VoxelInstanceGenerator : public Resource {
|
||||
GDCLASS(VoxelInstanceGenerator, Resource)
|
||||
public:
|
||||
// Tells how to interpret where "upwards" is in the current volume
|
||||
enum UpMode {
|
||||
// The world is a plane, so altitude is obtained from the Y coordinate and upwards is always toward +Y.
|
||||
UP_MODE_POSITIVE_Y,
|
||||
// The world is a sphere (planet), so altitude is obtained from distance to the origin (0,0,0),
|
||||
// and upwards is the normalized vector from origin to current position.
|
||||
UP_MODE_SPHERE,
|
||||
// How many up modes there are
|
||||
UP_MODE_COUNT
|
||||
};
|
||||
|
||||
// This API might change so for now it's not exposed to scripts
|
||||
void generate_transforms(
|
||||
std::vector<Transform> &out_transforms,
|
||||
Vector3i grid_position,
|
||||
int lod_index,
|
||||
int layer_index,
|
||||
Array surface_arrays,
|
||||
const Transform &block_local_transform,
|
||||
UpMode up_mode);
|
||||
|
||||
void set_density(float d);
|
||||
float get_density() const;
|
||||
|
||||
void set_vertical_alignment(float valign);
|
||||
float get_vertical_alignment() const;
|
||||
|
||||
void set_min_scale(float min_scale);
|
||||
float get_min_scale() const;
|
||||
|
||||
void set_max_scale(float max_scale);
|
||||
float get_max_scale() const;
|
||||
|
||||
void set_offset_along_normal(float offset);
|
||||
float get_offset_along_normal() const;
|
||||
|
||||
void set_min_slope_degrees(float degrees);
|
||||
float get_min_slope_degrees() const;
|
||||
|
||||
void set_max_slope_degrees(float degrees);
|
||||
float get_max_slope_degrees() const;
|
||||
|
||||
void set_min_height(float h);
|
||||
float get_min_height() const;
|
||||
|
||||
void set_max_height(float h);
|
||||
float get_max_height() const;
|
||||
|
||||
void set_random_vertical_flip(bool flip);
|
||||
bool get_random_vertical_flip() const;
|
||||
|
||||
private:
|
||||
static void _bind_methods();
|
||||
|
||||
float _density = 0.1f;
|
||||
float _vertical_alignment = 1.f;
|
||||
float _min_scale = 1.f;
|
||||
float _max_scale = 1.f;
|
||||
float _offset_along_normal = 0.f;
|
||||
float _min_surface_normal_y = -1.f;
|
||||
float _max_surface_normal_y = 1.f;
|
||||
float _min_height = std::numeric_limits<float>::min();
|
||||
float _max_height = std::numeric_limits<float>::max();
|
||||
bool _random_vertical_flip = false;
|
||||
};
|
||||
|
||||
#endif // VOXEL_INSTANCE_GENERATOR_H
|
|
@ -234,6 +234,12 @@ int VoxelInstancer::add_layer(int lod_index) {
|
|||
Layer *layer = memnew(Layer);
|
||||
layer->lod_index = lod_index;
|
||||
layer->id = id;
|
||||
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
// Put a default generator
|
||||
layer->generator.instance();
|
||||
}
|
||||
|
||||
_layers[layer_index] = layer;
|
||||
|
||||
Lod &lod = _lods[lod_index];
|
||||
|
@ -242,6 +248,14 @@ int VoxelInstancer::add_layer(int lod_index) {
|
|||
return layer_index;
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_generator(int layer_index, Ref<VoxelInstanceGenerator> generator) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
ERR_FAIL_COND(layer == nullptr);
|
||||
|
||||
layer->generator = generator;
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_mesh(int layer_index, Ref<Mesh> mesh) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
|
@ -250,86 +264,6 @@ void VoxelInstancer::set_layer_mesh(int layer_index, Ref<Mesh> mesh) {
|
|||
layer->mesh = mesh;
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_random_vertical_flip(int layer_index, bool flip_enabled) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
ERR_FAIL_COND(layer == nullptr);
|
||||
|
||||
layer->random_vertical_flip = flip_enabled;
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_density(int layer_index, float density) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
ERR_FAIL_COND(layer == nullptr);
|
||||
|
||||
layer->density = max(density, 0.f);
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_min_scale(int layer_index, float min_scale) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
ERR_FAIL_COND(layer == nullptr);
|
||||
|
||||
layer->min_scale = max(min_scale, 0.01f);
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_max_scale(int layer_index, float max_scale) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
ERR_FAIL_COND(layer == nullptr);
|
||||
|
||||
layer->max_scale = max(max_scale, 0.01f);
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_vertical_alignment(int layer_index, float amount) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
ERR_FAIL_COND(layer == nullptr);
|
||||
|
||||
layer->vertical_alignment = clamp(amount, 0.f, 1.f);
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_offset_along_normal(int layer_index, float offset) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
ERR_FAIL_COND(layer == nullptr);
|
||||
|
||||
layer->offset_along_normal = offset;
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_min_slope_degrees(int layer_index, float degrees) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
ERR_FAIL_COND(layer == nullptr);
|
||||
|
||||
layer->max_surface_normal_y = min(1.f, Math::cos(Math::deg2rad(clamp(degrees, -180.f, 180.f))));
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_max_slope_degrees(int layer_index, float degrees) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
ERR_FAIL_COND(layer == nullptr);
|
||||
|
||||
layer->min_surface_normal_y = max(-1.f, Math::cos(Math::deg2rad(clamp(degrees, -180.f, 180.f))));
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_min_height(int layer_index, float h) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
ERR_FAIL_COND(layer == nullptr);
|
||||
|
||||
layer->min_height = h;
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_max_height(int layer_index, float h) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
ERR_FAIL_COND(layer == nullptr);
|
||||
|
||||
layer->max_height = h;
|
||||
}
|
||||
|
||||
void VoxelInstancer::set_layer_collision_layer(int layer_index, int collision_layer) {
|
||||
ERR_FAIL_INDEX(layer_index, _layers.size());
|
||||
Layer *layer = _layers[layer_index];
|
||||
|
@ -691,14 +625,16 @@ void VoxelInstancer::generate_block_on_each_layer(Vector3i grid_position, int lo
|
|||
const Transform block_local_transform = Transform(Basis(), (grid_position * lod_block_size).to_vec3());
|
||||
const Transform block_transform = parent_transform * block_local_transform;
|
||||
|
||||
const uint32_t block_pos_hash = Vector3iHasher::hash(grid_position);
|
||||
|
||||
for (auto it = lod.layers.begin(); it != lod.layers.end(); ++it) {
|
||||
const int layer_index = *it;
|
||||
|
||||
Layer *layer = _layers[layer_index];
|
||||
CRASH_COND(layer == nullptr);
|
||||
|
||||
if (layer->generator.is_null()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int *block_index_ptr = layer->blocks.getptr(grid_position);
|
||||
|
||||
if (block_index_ptr != nullptr) {
|
||||
|
@ -706,146 +642,16 @@ void VoxelInstancer::generate_block_on_each_layer(Vector3i grid_position, int lo
|
|||
continue;
|
||||
}
|
||||
|
||||
const uint32_t density_u32 = 0xffffffff * layer->density;
|
||||
const float vertical_alignment = layer->vertical_alignment;
|
||||
const float scale_min = layer->min_scale;
|
||||
const float scale_range = layer->max_scale - layer->min_scale;
|
||||
const bool random_vertical_flip = layer->random_vertical_flip;
|
||||
const float offset_along_normal = layer->offset_along_normal;
|
||||
const float normal_min_y = layer->min_surface_normal_y;
|
||||
const float normal_max_y = layer->max_surface_normal_y;
|
||||
const bool slope_filter = normal_min_y != -1.f || normal_max_y != 1.f;
|
||||
const bool height_filter = layer->min_height != std::numeric_limits<float>::min() ||
|
||||
layer->max_height != std::numeric_limits<float>::max();
|
||||
const float min_height = layer->min_height;
|
||||
const float max_height = layer->max_height;
|
||||
|
||||
Vector3 global_up(0.f, 1.f, 0.f);
|
||||
|
||||
// Using different number generators so changing parameters affecting one doesn't affect the other
|
||||
const uint64_t seed = block_pos_hash + layer_index;
|
||||
RandomPCG pcg0;
|
||||
pcg0.seed(seed);
|
||||
RandomPCG pcg1;
|
||||
pcg1.seed(seed + 1);
|
||||
|
||||
_transform_cache.clear();
|
||||
{
|
||||
VOXEL_PROFILE_SCOPE();
|
||||
|
||||
PoolVector3Array::Read vr = vertices.read();
|
||||
PoolVector3Array::Read nr = normals.read();
|
||||
|
||||
// TODO This part might be moved to the meshing thread if it turns out to be too heavy
|
||||
|
||||
for (size_t i = 0; i < vertices.size(); ++i) {
|
||||
// TODO We could actually generate indexes and pick those, rather than iterating them all and rejecting
|
||||
if (pcg0.rand() >= density_u32) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Transform t;
|
||||
t.origin = vr[i];
|
||||
|
||||
// TODO Check if that position has been edited somehow, so we can decide to not spawn there
|
||||
// Or remesh from generator and compare sdf but that's expensive
|
||||
|
||||
Vector3 axis_y;
|
||||
|
||||
// Warning: sometimes mesh normals are not perfectly normalized.
|
||||
// The cause is for meshing speed on CPU. It's normalized on GPU anyways.
|
||||
Vector3 surface_normal = nr[i];
|
||||
bool surface_normal_is_normalized = false;
|
||||
bool sphere_up_is_computed = false;
|
||||
bool sphere_distance_is_computed = false;
|
||||
float sphere_distance;
|
||||
|
||||
if (vertical_alignment == 0.f) {
|
||||
surface_normal.normalize();
|
||||
surface_normal_is_normalized = true;
|
||||
axis_y = surface_normal;
|
||||
|
||||
} else {
|
||||
if (_up_mode == UP_MODE_SPHERE) {
|
||||
global_up = normalized(block_local_transform.origin + t.origin, sphere_distance);
|
||||
sphere_up_is_computed = true;
|
||||
sphere_distance_is_computed = true;
|
||||
}
|
||||
|
||||
if (vertical_alignment < 1.f) {
|
||||
axis_y = surface_normal.linear_interpolate(global_up, vertical_alignment).normalized();
|
||||
|
||||
} else {
|
||||
axis_y = global_up;
|
||||
}
|
||||
}
|
||||
|
||||
if (slope_filter) {
|
||||
if (!surface_normal_is_normalized) {
|
||||
surface_normal.normalize();
|
||||
}
|
||||
|
||||
float ny = surface_normal.y;
|
||||
if (_up_mode == UP_MODE_SPHERE) {
|
||||
if (!sphere_up_is_computed) {
|
||||
global_up = normalized(block_local_transform.origin + t.origin, sphere_distance);
|
||||
sphere_up_is_computed = true;
|
||||
sphere_distance_is_computed = true;
|
||||
}
|
||||
ny = surface_normal.dot(global_up);
|
||||
}
|
||||
|
||||
if (ny < normal_min_y || ny > normal_max_y) {
|
||||
// Discard
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (height_filter) {
|
||||
float y = t.origin.y;
|
||||
if (_up_mode == UP_MODE_SPHERE) {
|
||||
if (!sphere_distance_is_computed) {
|
||||
sphere_distance = (block_local_transform.origin + t.origin).length();
|
||||
sphere_distance_is_computed = true;
|
||||
}
|
||||
y = sphere_distance;
|
||||
}
|
||||
|
||||
if (y < min_height || y > max_height) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
t.origin += offset_along_normal * axis_y;
|
||||
|
||||
// Allows to use two faces of a single rock to create variety in the same layer
|
||||
if (random_vertical_flip && (pcg1.rand() & 1) == 1) {
|
||||
axis_y = -axis_y;
|
||||
// TODO Should have to flip another axis as well?
|
||||
}
|
||||
|
||||
// Pick a random rotation from the floor's normal.
|
||||
// TODO A pool of precomputed random directions would do the job too
|
||||
const Vector3 dir = Vector3(pcg1.randf() - 0.5f, pcg1.randf() - 0.5f, pcg1.randf() - 0.5f);
|
||||
const Vector3 axis_x = axis_y.cross(dir).normalized();
|
||||
const Vector3 axis_z = axis_x.cross(axis_y);
|
||||
|
||||
t.basis = Basis(
|
||||
Vector3(axis_x.x, axis_y.x, axis_z.x),
|
||||
Vector3(axis_x.y, axis_y.y, axis_z.y),
|
||||
Vector3(axis_x.z, axis_y.z, axis_z.z));
|
||||
|
||||
if (scale_range > 0.f) {
|
||||
const float scale = scale_min + scale_range * pcg1.randf();
|
||||
t.basis.scale(Vector3(scale, scale, scale));
|
||||
|
||||
} else if (scale_min != 1.f) {
|
||||
t.basis.scale(Vector3(scale_min, scale_min, scale_min));
|
||||
}
|
||||
|
||||
_transform_cache.push_back(t);
|
||||
}
|
||||
}
|
||||
layer->generator->generate_transforms(
|
||||
_transform_cache,
|
||||
grid_position,
|
||||
lod_index,
|
||||
layer_index,
|
||||
surface_arrays,
|
||||
block_local_transform,
|
||||
static_cast<VoxelInstanceGenerator::UpMode>(_up_mode));
|
||||
|
||||
if (_transform_cache.size() == 0) {
|
||||
continue;
|
||||
|
@ -910,8 +716,16 @@ void VoxelInstancer::save_block(Vector3i grid_pos, int lod_index) const {
|
|||
|
||||
ERR_FAIL_COND(layer->id < 0);
|
||||
layer_data.id = layer->id;
|
||||
layer_data.scale_min = layer->min_scale;
|
||||
layer_data.scale_max = layer->max_scale;
|
||||
|
||||
if (layer->generator.is_valid()) {
|
||||
layer_data.scale_min = layer->generator->get_min_scale();
|
||||
layer_data.scale_max = layer->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;
|
||||
}
|
||||
|
||||
layer_data.instances.resize(instance_count);
|
||||
|
||||
// TODO Optimization: it would be nice to get the whole array at once
|
||||
|
@ -1095,30 +909,13 @@ void VoxelInstancer::_bind_methods() {
|
|||
|
||||
ClassDB::bind_method(D_METHOD("add_layer", "lod_index"), &VoxelInstancer::add_layer);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_layer_generator", "layer_index", "generator"),
|
||||
&VoxelInstancer::set_layer_generator);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_layer_mesh", "layer_index", "mesh"), &VoxelInstancer::set_layer_mesh);
|
||||
ClassDB::bind_method(D_METHOD("set_layer_mesh_material_override", "layer_index", "material"),
|
||||
&VoxelInstancer::set_layer_material_override);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_layer_density", "layer_index", "density"), &VoxelInstancer::set_layer_density);
|
||||
ClassDB::bind_method(D_METHOD("set_layer_min_scale", "layer_index", "min_scale"),
|
||||
&VoxelInstancer::set_layer_min_scale);
|
||||
ClassDB::bind_method(D_METHOD("set_layer_max_scale", "layer_index", "max_scale"),
|
||||
&VoxelInstancer::set_layer_max_scale);
|
||||
ClassDB::bind_method(D_METHOD("set_layer_vertical_alignment", "layer_index", "amount"),
|
||||
&VoxelInstancer::set_layer_vertical_alignment);
|
||||
ClassDB::bind_method(D_METHOD("set_layer_random_vertical_flip", "layer_index", "enabled"),
|
||||
&VoxelInstancer::set_layer_random_vertical_flip);
|
||||
ClassDB::bind_method(D_METHOD("set_layer_offset_along_normal", "layer_index", "offset"),
|
||||
&VoxelInstancer::set_layer_offset_along_normal);
|
||||
ClassDB::bind_method(D_METHOD("set_layer_min_slope_degrees", "layer_index", "degrees"),
|
||||
&VoxelInstancer::set_layer_min_slope_degrees);
|
||||
ClassDB::bind_method(D_METHOD("set_layer_max_slope_degrees", "layer_index", "degrees"),
|
||||
&VoxelInstancer::set_layer_max_slope_degrees);
|
||||
ClassDB::bind_method(D_METHOD("set_layer_min_height", "layer_index", "height"),
|
||||
&VoxelInstancer::set_layer_min_height);
|
||||
ClassDB::bind_method(D_METHOD("set_layer_max_height", "layer_index", "height"),
|
||||
&VoxelInstancer::set_layer_max_height);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_layer_collision_layer", "layer_index", "collision_layer"),
|
||||
&VoxelInstancer::set_layer_collision_layer);
|
||||
ClassDB::bind_method(D_METHOD("set_layer_collision_mask", "layer_index", "collision_mask"),
|
||||
|
@ -1133,6 +930,8 @@ void VoxelInstancer::_bind_methods() {
|
|||
|
||||
ClassDB::bind_method(D_METHOD("debug_get_block_count"), &VoxelInstancer::debug_get_block_count);
|
||||
|
||||
BIND_CONSTANT(MAX_LOD);
|
||||
|
||||
BIND_ENUM_CONSTANT(UP_MODE_POSITIVE_Y);
|
||||
BIND_ENUM_CONSTANT(UP_MODE_SPHERE);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "../util/array_slice.h"
|
||||
#include "../util/direct_multimesh_instance.h"
|
||||
#include "../util/fixed_array.h"
|
||||
#include "voxel_instance_generator.h"
|
||||
|
||||
#include <scene/3d/spatial.h>
|
||||
//#include <scene/resources/material.h> // Included by node.h lol
|
||||
|
@ -14,19 +15,10 @@
|
|||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class VoxelGenerator;
|
||||
class VoxelLodTerrain;
|
||||
class VoxelInstancerRigidBody;
|
||||
class PhysicsBody;
|
||||
|
||||
// TODO Decouple this?
|
||||
// class VoxelInstanceGenerator : public Resource {
|
||||
// GDCLASS(VoxelInstanceGenerator, Resource)
|
||||
// public:
|
||||
// void generate_transforms(std::vector<Transform> &out_transforms,
|
||||
// Vector3i grid_position, int lod_index, Ref<VoxelBuffer> voxels, Array surface_arrays);
|
||||
// };
|
||||
|
||||
// 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 {
|
||||
|
@ -34,25 +26,15 @@ class VoxelInstancer : public 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();
|
||||
|
||||
// enum Source {
|
||||
// SOURCE_VERTICES
|
||||
// SOURCE_FACES
|
||||
// };
|
||||
|
||||
// Tells how to interpret where "upwards" is in the current volume
|
||||
enum UpMode {
|
||||
// The world is a plane, so altitude is obtained from the Y coordinate and upwards is always toward +Y.
|
||||
UP_MODE_POSITIVE_Y,
|
||||
// The world is a sphere (planet), so altitude is obtained from distance to the origin (0,0,0),
|
||||
// and upwards is the normalized vector from origin to current position.
|
||||
UP_MODE_SPHERE,
|
||||
// How many up modes there are
|
||||
UP_MODE_COUNT
|
||||
};
|
||||
|
||||
void set_up_mode(UpMode mode);
|
||||
UpMode get_up_mode() const;
|
||||
|
||||
|
@ -62,20 +44,11 @@ public:
|
|||
|
||||
int add_layer(int lod_index);
|
||||
|
||||
void set_layer_generator(int layer_index, Ref<VoxelInstanceGenerator> generator);
|
||||
|
||||
void set_layer_mesh(int layer_index, Ref<Mesh> mesh);
|
||||
void set_layer_material_override(int layer_index, Ref<Material> material);
|
||||
|
||||
void set_layer_random_vertical_flip(int layer_index, bool flip_enabled);
|
||||
void set_layer_density(int layer_index, float density);
|
||||
void set_layer_min_scale(int layer_index, float min_scale);
|
||||
void set_layer_max_scale(int layer_index, float max_scale);
|
||||
void set_layer_vertical_alignment(int layer_index, float vertical_alignment);
|
||||
void set_layer_offset_along_normal(int layer_index, float offset);
|
||||
void set_layer_min_slope_degrees(int layer_index, float degrees);
|
||||
void set_layer_max_slope_degrees(int layer_index, float degrees);
|
||||
void set_layer_min_height(int layer_index, float h);
|
||||
void set_layer_max_height(int layer_index, float h);
|
||||
|
||||
void set_layer_collision_layer(int layer_index, int collision_layer);
|
||||
void set_layer_collision_mask(int layer_index, int collision_mask);
|
||||
void set_layer_collision_shapes(int layer_index, Array shape_infos);
|
||||
|
@ -149,16 +122,7 @@ private:
|
|||
|
||||
int lod_index = 0;
|
||||
|
||||
float density = 0.1f;
|
||||
float vertical_alignment = 1.f;
|
||||
float min_scale = 1.f;
|
||||
float max_scale = 1.f;
|
||||
float offset_along_normal = 0.f;
|
||||
float min_surface_normal_y = -1.f;
|
||||
float max_surface_normal_y = 1.f;
|
||||
float min_height = std::numeric_limits<float>::min();
|
||||
float max_height = std::numeric_limits<float>::max();
|
||||
bool random_vertical_flip = false;
|
||||
Ref<VoxelInstanceGenerator> generator;
|
||||
|
||||
// TODO lods?
|
||||
Ref<Mesh> mesh;
|
||||
|
|
Loading…
Reference in New Issue