777 lines
25 KiB
C++
777 lines
25 KiB
C++
#include "voxel_instance_generator.h"
|
|
#include "../../util/container_funcs.h"
|
|
#include "../../util/profiling.h"
|
|
|
|
#include <core/core_string_names.h>
|
|
#include <scene/resources/mesh.h>
|
|
|
|
namespace zylann::voxel {
|
|
|
|
namespace {
|
|
const float MAX_DENSITY = 1.f;
|
|
const char *DENSITY_HINT_STRING = "0.0, 1.0, 0.01";
|
|
} // namespace
|
|
|
|
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;
|
|
}
|
|
|
|
// Heron's formula is overly represented on SO but uses 4 square roots. This uses only one.
|
|
// A parallelogram's area is found with the magnitude of the cross product of two adjacent side vectors,
|
|
// so a triangle's area is half of it
|
|
inline float get_triangle_area(Vector3 p0, Vector3 p1, Vector3 p2) {
|
|
const Vector3 p01 = p1 - p0;
|
|
const Vector3 p02 = p2 - p0;
|
|
const Vector3 c = p01.cross(p02);
|
|
return 0.5f * c.length();
|
|
}
|
|
|
|
void VoxelInstanceGenerator::generate_transforms(std::vector<Transform3D> &out_transforms, Vector3i grid_position,
|
|
int lod_index, int layer_id, Array surface_arrays, const Transform3D &block_local_transform, UpMode up_mode,
|
|
uint8_t octant_mask, float block_size) {
|
|
ZN_PROFILE_SCOPE();
|
|
|
|
if (surface_arrays.size() < ArrayMesh::ARRAY_VERTEX && surface_arrays.size() < ArrayMesh::ARRAY_NORMAL &&
|
|
surface_arrays.size() < ArrayMesh::ARRAY_INDEX) {
|
|
return;
|
|
}
|
|
|
|
PackedVector3Array vertices = surface_arrays[ArrayMesh::ARRAY_VERTEX];
|
|
if (vertices.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
if (_density <= 0.f) {
|
|
return;
|
|
}
|
|
|
|
PackedVector3Array normals = surface_arrays[ArrayMesh::ARRAY_NORMAL];
|
|
ERR_FAIL_COND(normals.size() == 0);
|
|
|
|
PackedInt32Array indices = surface_arrays[ArrayMesh::ARRAY_INDEX];
|
|
ERR_FAIL_COND(indices.size() == 0);
|
|
ERR_FAIL_COND(indices.size() % 3 != 0);
|
|
|
|
const uint32_t block_pos_hash = Vector3iHasher::hash(grid_position);
|
|
|
|
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_id;
|
|
RandomPCG pcg0;
|
|
pcg0.seed(seed);
|
|
RandomPCG pcg1;
|
|
pcg1.seed(seed + 1);
|
|
|
|
out_transforms.clear();
|
|
|
|
// TODO This part might be moved to the meshing thread if it turns out to be too heavy
|
|
|
|
static thread_local std::vector<Vector3> g_vertex_cache;
|
|
static thread_local std::vector<Vector3> g_normal_cache;
|
|
static thread_local std::vector<float> g_noise_cache;
|
|
|
|
std::vector<Vector3> &vertex_cache = g_vertex_cache;
|
|
std::vector<Vector3> &normal_cache = g_normal_cache;
|
|
|
|
vertex_cache.clear();
|
|
normal_cache.clear();
|
|
|
|
// Pick random points
|
|
{
|
|
ZN_PROFILE_SCOPE_NAMED("mesh to points");
|
|
|
|
// PackedVector3Array::Read vertices_r = vertices.read();
|
|
// PackedVector3Array::Read normals_r = normals.read();
|
|
|
|
// Generate base positions
|
|
switch (_emit_mode) {
|
|
case EMIT_FROM_VERTICES: {
|
|
// Density is interpreted differently here,
|
|
// so it's possible a different emit mode will produce different amounts of instances.
|
|
// I had to use `uint64` and clamp it because floats can't contain `0xffffffff` accurately. Instead
|
|
// it results in `0x100000000`, one unit above.
|
|
const uint32_t density_u32 =
|
|
math::min(uint64_t(double(0xffffffff) * _density / MAX_DENSITY), uint64_t(0xffffffff));
|
|
const int size = vertices.size();
|
|
for (int i = 0; i < size; ++i) {
|
|
// TODO We could actually generate indexes and pick those,
|
|
// rather than iterating them all and rejecting
|
|
if (pcg0.rand() >= density_u32) {
|
|
continue;
|
|
}
|
|
vertex_cache.push_back(vertices[i]);
|
|
normal_cache.push_back(normals[i]);
|
|
}
|
|
} break;
|
|
|
|
case EMIT_FROM_FACES_FAST: {
|
|
// PoolIntArray::Read indices_r = indices.read();
|
|
|
|
const int triangle_count = indices.size() / 3;
|
|
|
|
// Assumes triangles are all roughly under the same size, and Transvoxel ones do (when not simplified),
|
|
// so we can use number of triangles as a metric proportional to the number of instances
|
|
const int instance_count = _density * triangle_count;
|
|
|
|
vertex_cache.resize(instance_count);
|
|
normal_cache.resize(instance_count);
|
|
|
|
for (int instance_index = 0; instance_index < instance_count; ++instance_index) {
|
|
// Pick a random triangle
|
|
const uint32_t ii = (pcg0.rand() % triangle_count) * 3;
|
|
|
|
const int ia = indices[ii];
|
|
const int ib = indices[ii + 1];
|
|
const int ic = indices[ii + 2];
|
|
|
|
const Vector3 &pa = vertices[ia];
|
|
const Vector3 &pb = vertices[ib];
|
|
const Vector3 &pc = vertices[ic];
|
|
|
|
const Vector3 &na = normals[ia];
|
|
const Vector3 &nb = normals[ib];
|
|
const Vector3 &nc = normals[ic];
|
|
|
|
const float t0 = pcg1.randf();
|
|
const float t1 = pcg1.randf();
|
|
|
|
// This formula gives pretty uniform distribution but involves a square root
|
|
//const Vector3 p = pa.linear_interpolate(pb, t0).linear_interpolate(pc, 1.f - sqrt(t1));
|
|
|
|
// This is an approximation
|
|
const Vector3 p = pa.lerp(pb, t0).lerp(pc, t1);
|
|
const Vector3 n = na.lerp(nb, t0).lerp(nc, t1);
|
|
|
|
vertex_cache[instance_index] = p;
|
|
normal_cache[instance_index] = n;
|
|
}
|
|
|
|
} break;
|
|
|
|
case EMIT_FROM_FACES: {
|
|
// PackedInt32Array::Read indices_r = indices.read();
|
|
|
|
const int triangle_count = indices.size() / 3;
|
|
|
|
// static thread_local std::vector<float> g_area_cache;
|
|
// std::vector<float> &area_cache = g_area_cache;
|
|
// area_cache.resize(triangle_count);
|
|
|
|
// Does not assume triangles have the same size, so instead a "unit size" is used,
|
|
// and more instances will be placed in triangles larger than this.
|
|
// This is roughly the size of one voxel's triangle
|
|
//const float unit_area = 0.5f * squared(block_size / 32.f);
|
|
|
|
float accumulator = 0.f;
|
|
const float inv_density = 1.f / _density;
|
|
|
|
for (int triangle_index = 0; triangle_index < triangle_count; ++triangle_index) {
|
|
const uint32_t ii = triangle_index * 3;
|
|
|
|
const int ia = indices[ii];
|
|
const int ib = indices[ii + 1];
|
|
const int ic = indices[ii + 2];
|
|
|
|
const Vector3 &pa = vertices[ia];
|
|
const Vector3 &pb = vertices[ib];
|
|
const Vector3 &pc = vertices[ic];
|
|
|
|
const Vector3 &na = normals[ia];
|
|
const Vector3 &nb = normals[ib];
|
|
const Vector3 &nc = normals[ic];
|
|
|
|
const float triangle_area = get_triangle_area(pa, pb, pc);
|
|
accumulator += triangle_area;
|
|
|
|
const int count_in_triangle = int(accumulator * _density);
|
|
|
|
for (int i = 0; i < count_in_triangle; ++i) {
|
|
const float t0 = pcg1.randf();
|
|
const float t1 = pcg1.randf();
|
|
|
|
// This formula gives pretty uniform distribution but involves a square root
|
|
//const Vector3 p = pa.linear_interpolate(pb, t0).linear_interpolate(pc, 1.f - sqrt(t1));
|
|
|
|
// This is an approximation
|
|
const Vector3 p = pa.lerp(pb, t0).lerp(pc, t1);
|
|
const Vector3 n = na.lerp(nb, t0).lerp(nc, t1);
|
|
|
|
vertex_cache.push_back(p);
|
|
normal_cache.push_back(n);
|
|
}
|
|
|
|
accumulator -= count_in_triangle * inv_density;
|
|
}
|
|
|
|
} break;
|
|
|
|
default:
|
|
CRASH_NOW();
|
|
}
|
|
}
|
|
|
|
// Filter out by octants
|
|
// This is done so some octants can be filled with user-edited data instead,
|
|
// because mesh size may not necessarily match data block size
|
|
if ((octant_mask & 0xff) != 0xff) {
|
|
ZN_PROFILE_SCOPE_NAMED("octant filter");
|
|
const float h = block_size / 2.f;
|
|
for (unsigned int i = 0; i < vertex_cache.size(); ++i) {
|
|
const Vector3 &pos = vertex_cache[i];
|
|
const uint8_t octant_index = get_octant_index(pos, h);
|
|
if ((octant_mask & (1 << octant_index)) == 0) {
|
|
unordered_remove(vertex_cache, i);
|
|
unordered_remove(normal_cache, i);
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<float> &noise_cache = g_noise_cache;
|
|
|
|
// Filter out by noise
|
|
if (_noise.is_valid()) {
|
|
ZN_PROFILE_SCOPE_NAMED("noise filter");
|
|
|
|
noise_cache.clear();
|
|
|
|
switch (_noise_dimension) {
|
|
case DIMENSION_2D: {
|
|
for (size_t i = 0; i < vertex_cache.size(); ++i) {
|
|
const Vector3 &pos = vertex_cache[i] + block_local_transform.origin;
|
|
const float n = _noise->get_noise_2d(pos.x, pos.z);
|
|
if (n < 0) {
|
|
unordered_remove(vertex_cache, i);
|
|
unordered_remove(normal_cache, i);
|
|
--i;
|
|
} else {
|
|
noise_cache.push_back(n);
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case DIMENSION_3D: {
|
|
for (size_t i = 0; i < vertex_cache.size(); ++i) {
|
|
const Vector3 &pos = vertex_cache[i] + block_local_transform.origin;
|
|
const float n = _noise->get_noise_3d(pos.x, pos.y, pos.z);
|
|
if (n < 0) {
|
|
unordered_remove(vertex_cache, i);
|
|
unordered_remove(normal_cache, i);
|
|
--i;
|
|
} else {
|
|
noise_cache.push_back(n);
|
|
}
|
|
}
|
|
} break;
|
|
|
|
default:
|
|
ERR_FAIL();
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
const Vector3 fixed_look_axis = up_mode == UP_MODE_POSITIVE_Y ? Vector3(1, 0, 0) : Vector3(0, 1, 0);
|
|
const Vector3 fixed_look_axis_alternative = up_mode == UP_MODE_POSITIVE_Y ? Vector3(0, 1, 0) : Vector3(1, 0, 0);
|
|
|
|
// Calculate orientations and scales
|
|
for (size_t vertex_index = 0; vertex_index < vertex_cache.size(); ++vertex_index) {
|
|
Transform3D t;
|
|
t.origin = vertex_cache[vertex_index];
|
|
|
|
// 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 = normal_cache[vertex_index];
|
|
|
|
Vector3 axis_y;
|
|
|
|
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.lerp(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 = block_local_transform.origin.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.
|
|
// We may check for cases too close to Y to avoid broken basis due to float precision limits,
|
|
// even if that could differ from the expected result
|
|
Vector3 dir;
|
|
if (_random_rotation) {
|
|
do {
|
|
// TODO A pool of precomputed random directions would do the job too?
|
|
dir = Vector3(pcg1.randf() - 0.5f, pcg1.randf() - 0.5f, pcg1.randf() - 0.5f).normalized();
|
|
// TODO Any way to check if the two vectors are close to aligned without normalizing `dir`?
|
|
} while (Math::abs(dir.dot(axis_y)) > 0.9999f);
|
|
|
|
} else {
|
|
// If the surface is aligned with this axis, it will create a "pole" where all instances are looking at.
|
|
// When getting too close to it, we may pick a different axis.
|
|
dir = fixed_look_axis;
|
|
if (Math::abs(dir.dot(axis_y)) > 0.9999f) {
|
|
dir = fixed_look_axis_alternative;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
float r = pcg1.randf();
|
|
|
|
switch (_scale_distribution) {
|
|
case DISTRIBUTION_QUADRATIC:
|
|
r = r * r;
|
|
break;
|
|
case DISTRIBUTION_CUBIC:
|
|
r = r * r * r;
|
|
break;
|
|
case DISTRIBUTION_QUINTIC:
|
|
r = r * r * r * r * r;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (_noise.is_valid() && _noise_on_scale > 0.f) {
|
|
#ifdef DEBUG_ENABLED
|
|
CRASH_COND(vertex_index >= noise_cache.size());
|
|
#endif
|
|
// Multiplied noise because it gives more pronounced results
|
|
const float n = math::clamp(noise_cache[vertex_index] * 2.f, 0.f, 1.f);
|
|
r *= Math::lerp(1.f, n, _noise_on_scale);
|
|
}
|
|
|
|
float scale = scale_min + scale_range * r;
|
|
|
|
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);
|
|
}
|
|
|
|
// TODO Investigate if this helps (won't help with authored terrain)
|
|
// if (graph_generator.is_valid()) {
|
|
// for (size_t i = 0; i < _transform_cache.size(); ++i) {
|
|
// Transform &t = _transform_cache[i];
|
|
// const Vector3 up = t.get_basis().get_axis(Vector3::AXIS_Y);
|
|
// t.origin = graph_generator->approximate_surface(t.origin, up * 0.5f);
|
|
// }
|
|
// }
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_density(float density) {
|
|
density = math::max(density, 0.f);
|
|
if (density == _density) {
|
|
return;
|
|
}
|
|
_density = density;
|
|
emit_changed();
|
|
}
|
|
|
|
float VoxelInstanceGenerator::get_density() const {
|
|
return _density;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_emit_mode(EmitMode mode) {
|
|
ERR_FAIL_INDEX(mode, EMIT_MODE_COUNT);
|
|
if (_emit_mode == mode) {
|
|
return;
|
|
}
|
|
_emit_mode = mode;
|
|
emit_changed();
|
|
}
|
|
|
|
VoxelInstanceGenerator::EmitMode VoxelInstanceGenerator::get_emit_mode() const {
|
|
return _emit_mode;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_min_scale(float min_scale) {
|
|
if (_min_scale == min_scale) {
|
|
return;
|
|
}
|
|
_min_scale = min_scale;
|
|
emit_changed();
|
|
}
|
|
|
|
float VoxelInstanceGenerator::get_min_scale() const {
|
|
return _min_scale;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_max_scale(float max_scale) {
|
|
if (max_scale == _max_scale) {
|
|
return;
|
|
}
|
|
_max_scale = max_scale;
|
|
emit_changed();
|
|
}
|
|
|
|
float VoxelInstanceGenerator::get_max_scale() const {
|
|
return _max_scale;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_scale_distribution(Distribution distribution) {
|
|
ERR_FAIL_INDEX(distribution, DISTRIBUTION_COUNT);
|
|
if (distribution == _scale_distribution) {
|
|
return;
|
|
}
|
|
_scale_distribution = distribution;
|
|
emit_changed();
|
|
}
|
|
|
|
VoxelInstanceGenerator::Distribution VoxelInstanceGenerator::get_scale_distribution() const {
|
|
return _scale_distribution;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_vertical_alignment(float amount) {
|
|
amount = math::clamp(amount, 0.f, 1.f);
|
|
if (_vertical_alignment == amount) {
|
|
return;
|
|
}
|
|
_vertical_alignment = amount;
|
|
emit_changed();
|
|
}
|
|
|
|
float VoxelInstanceGenerator::get_vertical_alignment() const {
|
|
return _vertical_alignment;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_offset_along_normal(float offset) {
|
|
if (_offset_along_normal == offset) {
|
|
return;
|
|
}
|
|
_offset_along_normal = offset;
|
|
emit_changed();
|
|
}
|
|
|
|
float VoxelInstanceGenerator::get_offset_along_normal() const {
|
|
return _offset_along_normal;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_min_slope_degrees(float degrees) {
|
|
_min_slope_degrees = math::clamp(degrees, 0.f, 180.f);
|
|
const float max_surface_normal_y = math::min(1.f, Math::cos(Math::deg2rad(_min_slope_degrees)));
|
|
if (max_surface_normal_y == _max_surface_normal_y) {
|
|
return;
|
|
}
|
|
_max_surface_normal_y = max_surface_normal_y;
|
|
emit_changed();
|
|
}
|
|
|
|
float VoxelInstanceGenerator::get_min_slope_degrees() const {
|
|
return _min_slope_degrees;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_max_slope_degrees(float degrees) {
|
|
_max_slope_degrees = math::clamp(degrees, 0.f, 180.f);
|
|
const float min_surface_normal_y = math::max(-1.f, Math::cos(Math::deg2rad(_max_slope_degrees)));
|
|
if (min_surface_normal_y == _min_surface_normal_y) {
|
|
return;
|
|
}
|
|
_min_surface_normal_y = min_surface_normal_y;
|
|
emit_changed();
|
|
}
|
|
|
|
float VoxelInstanceGenerator::get_max_slope_degrees() const {
|
|
return _max_slope_degrees;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_min_height(float h) {
|
|
if (h == _min_height) {
|
|
return;
|
|
}
|
|
_min_height = h;
|
|
emit_changed();
|
|
}
|
|
|
|
float VoxelInstanceGenerator::get_min_height() const {
|
|
return _min_height;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_max_height(float h) {
|
|
if (_max_height == h) {
|
|
return;
|
|
}
|
|
_max_height = h;
|
|
emit_changed();
|
|
}
|
|
|
|
float VoxelInstanceGenerator::get_max_height() const {
|
|
return _max_height;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_random_vertical_flip(bool flip_enabled) {
|
|
if (flip_enabled == _random_vertical_flip) {
|
|
return;
|
|
}
|
|
_random_vertical_flip = flip_enabled;
|
|
emit_changed();
|
|
}
|
|
|
|
bool VoxelInstanceGenerator::get_random_vertical_flip() const {
|
|
return _random_vertical_flip;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_random_rotation(bool enabled) {
|
|
if (enabled != _random_rotation) {
|
|
_random_rotation = enabled;
|
|
emit_changed();
|
|
}
|
|
}
|
|
|
|
bool VoxelInstanceGenerator::get_random_rotation() const {
|
|
return _random_rotation;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_noise(Ref<Noise> noise) {
|
|
if (_noise == noise) {
|
|
return;
|
|
}
|
|
if (_noise.is_valid()) {
|
|
_noise->disconnect(CoreStringNames::get_singleton()->changed,
|
|
callable_mp(this, &VoxelInstanceGenerator::_on_noise_changed));
|
|
}
|
|
_noise = noise;
|
|
if (_noise.is_valid()) {
|
|
_noise->connect(CoreStringNames::get_singleton()->changed,
|
|
callable_mp(this, &VoxelInstanceGenerator::_on_noise_changed));
|
|
}
|
|
emit_changed();
|
|
}
|
|
|
|
Ref<Noise> VoxelInstanceGenerator::get_noise() const {
|
|
return _noise;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_noise_dimension(Dimension dim) {
|
|
ERR_FAIL_INDEX(dim, DIMENSION_COUNT);
|
|
if (dim == _noise_dimension) {
|
|
return;
|
|
}
|
|
_noise_dimension = dim;
|
|
emit_changed();
|
|
}
|
|
|
|
VoxelInstanceGenerator::Dimension VoxelInstanceGenerator::get_noise_dimension() const {
|
|
return _noise_dimension;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::set_noise_on_scale(float amount) {
|
|
amount = math::clamp(amount, 0.f, 1.f);
|
|
if (amount == _noise_on_scale) {
|
|
return;
|
|
}
|
|
_noise_on_scale = amount;
|
|
emit_changed();
|
|
}
|
|
|
|
float VoxelInstanceGenerator::get_noise_on_scale() const {
|
|
return _noise_on_scale;
|
|
}
|
|
|
|
void VoxelInstanceGenerator::_on_noise_changed() {
|
|
emit_changed();
|
|
}
|
|
|
|
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_emit_mode", "density"), &VoxelInstanceGenerator::set_emit_mode);
|
|
ClassDB::bind_method(D_METHOD("get_emit_mode"), &VoxelInstanceGenerator::get_emit_mode);
|
|
|
|
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_scale_distribution", "distribution"), &VoxelInstanceGenerator::set_scale_distribution);
|
|
ClassDB::bind_method(D_METHOD("get_scale_distribution"), &VoxelInstanceGenerator::get_scale_distribution);
|
|
|
|
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);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_random_rotation", "enabled"), &VoxelInstanceGenerator::set_random_rotation);
|
|
ClassDB::bind_method(D_METHOD("get_random_rotation"), &VoxelInstanceGenerator::get_random_rotation);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_noise", "noise"), &VoxelInstanceGenerator::set_noise);
|
|
ClassDB::bind_method(D_METHOD("get_noise"), &VoxelInstanceGenerator::get_noise);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_noise_dimension", "dim"), &VoxelInstanceGenerator::set_noise_dimension);
|
|
ClassDB::bind_method(D_METHOD("get_noise_dimension"), &VoxelInstanceGenerator::get_noise_dimension);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_noise_on_scale", "amount"), &VoxelInstanceGenerator::set_noise_on_scale);
|
|
ClassDB::bind_method(D_METHOD("get_noise_on_scale"), &VoxelInstanceGenerator::get_noise_on_scale);
|
|
|
|
// ClassDB::bind_method(D_METHOD("_on_noise_changed"), &VoxelInstanceGenerator::_on_noise_changed);
|
|
|
|
ADD_GROUP("Emission", "");
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "density", PROPERTY_HINT_RANGE, DENSITY_HINT_STRING), "set_density",
|
|
"get_density");
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "emit_mode", PROPERTY_HINT_ENUM, "Vertices,FacesFast,Faces"),
|
|
"set_emit_mode", "get_emit_mode");
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_slope_degrees", PROPERTY_HINT_RANGE, "0.0, 180.0, 0.1"),
|
|
"set_min_slope_degrees", "get_min_slope_degrees");
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_slope_degrees", PROPERTY_HINT_RANGE, "0.0, 180.0, 0.1"),
|
|
"set_max_slope_degrees", "get_max_slope_degrees");
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_height"), "set_min_height", "get_min_height");
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_height"), "set_max_height", "get_max_height");
|
|
|
|
ADD_GROUP("Scale", "");
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_scale", PROPERTY_HINT_RANGE, "0.0, 10.0, 0.01"), "set_min_scale",
|
|
"get_min_scale");
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_scale", PROPERTY_HINT_RANGE, "0.0, 10.0, 0.01"), "set_max_scale",
|
|
"get_max_scale");
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "scale_distribution", PROPERTY_HINT_ENUM, "Linear,Quadratic,Cubic,Quintic"),
|
|
"set_scale_distribution", "get_scale_distribution");
|
|
|
|
ADD_GROUP("Rotation", "");
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "vertical_alignment", PROPERTY_HINT_RANGE, "0.0, 1.0, 0.01"),
|
|
"set_vertical_alignment", "get_vertical_alignment");
|
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "random_vertical_flip"), "set_random_vertical_flip",
|
|
"get_random_vertical_flip");
|
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "random_rotation"), "set_random_rotation", "get_random_rotation");
|
|
|
|
ADD_GROUP("Offset", "");
|
|
|
|
ADD_PROPERTY(
|
|
PropertyInfo(Variant::FLOAT, "offset_along_normal"), "set_offset_along_normal", "get_offset_along_normal");
|
|
|
|
ADD_GROUP("Noise", "");
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "noise", PROPERTY_HINT_RESOURCE_TYPE, "FastNoiseLite"), "set_noise",
|
|
"get_noise");
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "noise_dimension", PROPERTY_HINT_ENUM, "2D,3D"), "set_noise_dimension",
|
|
"get_noise_dimension");
|
|
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "noise_on_scale", PROPERTY_HINT_RANGE, "0.0, 1.0, 0.01"),
|
|
"set_noise_on_scale", "get_noise_on_scale");
|
|
|
|
BIND_ENUM_CONSTANT(EMIT_FROM_VERTICES);
|
|
BIND_ENUM_CONSTANT(EMIT_FROM_FACES_FAST);
|
|
BIND_ENUM_CONSTANT(EMIT_FROM_FACES);
|
|
BIND_ENUM_CONSTANT(EMIT_MODE_COUNT);
|
|
|
|
BIND_ENUM_CONSTANT(DISTRIBUTION_LINEAR);
|
|
BIND_ENUM_CONSTANT(DISTRIBUTION_QUADRATIC);
|
|
BIND_ENUM_CONSTANT(DISTRIBUTION_CUBIC);
|
|
BIND_ENUM_CONSTANT(DISTRIBUTION_QUINTIC);
|
|
BIND_ENUM_CONSTANT(DISTRIBUTION_COUNT);
|
|
|
|
BIND_ENUM_CONSTANT(DIMENSION_2D);
|
|
BIND_ENUM_CONSTANT(DIMENSION_3D);
|
|
BIND_ENUM_CONSTANT(DIMENSION_COUNT);
|
|
}
|
|
|
|
} // namespace zylann::voxel
|