Moved instance generation to its own class

master
Marc Gilleron 2021-02-07 21:30:07 +00:00
parent b65b0b8045
commit 4daf6718f8
5 changed files with 445 additions and 288 deletions

View File

@ -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>();

View File

@ -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");
}

View File

@ -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

View File

@ -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);
}

View File

@ -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;