godot_voxel/edition/voxel_tool_terrain.cpp

203 lines
6.3 KiB
C++
Raw Normal View History

#include "voxel_tool_terrain.h"
#include "../terrain/voxel_map.h"
#include "../terrain/voxel_terrain.h"
#include "../util/voxel_raycast.h"
#include <core/func_ref.h>
VoxelToolTerrain::VoxelToolTerrain() {
}
VoxelToolTerrain::VoxelToolTerrain(VoxelTerrain *terrain, Ref<VoxelMap> map) {
ERR_FAIL_COND(terrain == nullptr);
_terrain = terrain;
_map = map;
// Don't destroy the terrain while a voxel tool still references it
}
bool VoxelToolTerrain::is_area_editable(const Rect3i &box) const {
ERR_FAIL_COND_V(_terrain == nullptr, false);
return _map->is_area_fully_loaded(box.padded(1));
}
Ref<VoxelRaycastResult> VoxelToolTerrain::raycast(Vector3 pos, Vector3 dir, float max_distance, uint32_t collision_mask) {
2020-01-26 20:43:40 +00:00
// TODO Transform input if the terrain is rotated (in the future it can be made a Spatial node)
2020-08-24 13:49:44 +01:00
// TODO Optimize using a broad phase on blocks
2020-01-26 20:43:40 +00:00
struct RaycastPredicate {
const VoxelTerrain &terrain;
const uint32_t collision_mask;
2020-01-26 20:43:40 +00:00
bool operator()(Vector3i pos) {
//unsigned int channel = context->channel;
2020-01-26 20:43:40 +00:00
Ref<VoxelMap> map = terrain.get_storage();
int v0 = map->get_voxel(pos, VoxelBuffer::CHANNEL_TYPE);
2020-01-26 20:43:40 +00:00
Ref<VoxelLibrary> lib_ref = terrain.get_voxel_library();
if (lib_ref.is_null()) {
2020-01-26 20:43:40 +00:00
return false;
}
2020-01-26 20:43:40 +00:00
const VoxelLibrary &lib = **lib_ref;
if (lib.has_voxel(v0) == false) {
2020-01-26 20:43:40 +00:00
return false;
}
2020-01-26 20:43:40 +00:00
const Voxel &voxel = lib.get_voxel_const(v0);
if (voxel.is_empty()) {
return false;
}
if ((voxel.get_collision_mask() & collision_mask) == 0) {
return false;
}
if (voxel.is_transparent() == false) {
2020-01-26 20:43:40 +00:00
return true;
}
if (voxel.is_transparent() && voxel.get_collision_aabbs().empty() == false) {
return true;
}
2020-01-26 20:43:40 +00:00
float v1 = map->get_voxel_f(pos, VoxelBuffer::CHANNEL_SDF);
return v1 < 0;
}
};
Vector3i hit_pos;
Vector3i prev_pos;
Ref<VoxelRaycastResult> res;
RaycastPredicate predicate = { *_terrain, collision_mask };
2020-01-26 20:43:40 +00:00
if (voxel_raycast(pos, dir, predicate, max_distance, hit_pos, prev_pos)) {
res.instance();
res->position = hit_pos;
res->previous_position = prev_pos;
}
return res;
}
2020-08-06 19:54:47 +01:00
uint64_t VoxelToolTerrain::_get_voxel(Vector3i pos) {
ERR_FAIL_COND_V(_terrain == nullptr, 0);
return _map->get_voxel(pos, _channel);
}
float VoxelToolTerrain::_get_voxel_f(Vector3i pos) {
ERR_FAIL_COND_V(_terrain == nullptr, 0);
return _map->get_voxel_f(pos, _channel);
}
2020-08-06 19:54:47 +01:00
void VoxelToolTerrain::_set_voxel(Vector3i pos, uint64_t v) {
ERR_FAIL_COND(_terrain == nullptr);
_map->set_voxel(v, pos, _channel);
}
void VoxelToolTerrain::_set_voxel_f(Vector3i pos, float v) {
ERR_FAIL_COND(_terrain == nullptr);
_map->set_voxel_f(v, pos, _channel);
}
void VoxelToolTerrain::_post_edit(const Rect3i &box) {
ERR_FAIL_COND(_terrain == nullptr);
_terrain->make_area_dirty(box);
}
void VoxelToolTerrain::set_voxel_metadata(Vector3i pos, Variant meta) {
ERR_FAIL_COND(_terrain == nullptr);
VoxelBlock *block = _map->get_block(_map->voxel_to_block(pos));
ERR_FAIL_COND_MSG(block == nullptr, "Area not editable");
block->voxels->set_voxel_metadata(_map->to_local(pos), meta);
}
Variant VoxelToolTerrain::get_voxel_metadata(Vector3i pos) {
ERR_FAIL_COND_V(_terrain == nullptr, Variant());
const VoxelBlock *block = _map->get_block(_map->voxel_to_block(pos));
ERR_FAIL_COND_V_MSG(block == nullptr, Variant(), "Area not editable");
return block->voxels->get_voxel_metadata(_map->to_local(pos));
}
// Executes a function on random voxels in the provided area, using the type channel.
// This allows to implement slow "natural" cellular automata behavior, as can be seen in Minecraft.
void VoxelToolTerrain::run_blocky_random_tick(AABB voxel_area, int voxel_count, Ref<FuncRef> callback, int batch_count) const {
ERR_FAIL_COND(_terrain == nullptr);
ERR_FAIL_COND(_terrain->get_voxel_library().is_null());
ERR_FAIL_COND(callback.is_null());
ERR_FAIL_COND(batch_count <= 0);
ERR_FAIL_COND(voxel_count < 0);
if (voxel_count == 0) {
return;
}
const VoxelLibrary &lib = **_terrain->get_voxel_library();
const Vector3i min_pos = Vector3i(voxel_area.position);
const Vector3i max_pos = min_pos + Vector3i(voxel_area.size);
const Vector3i min_block_pos = _map->voxel_to_block(min_pos);
const Vector3i max_block_pos = _map->voxel_to_block(max_pos);
const Vector3i block_area_size = max_block_pos - min_block_pos;
const int block_count = voxel_count / batch_count;
const int bs_mask = _map->get_block_size_mask();
2020-08-06 19:54:47 +01:00
const VoxelBuffer::ChannelId channel = VoxelBuffer::CHANNEL_TYPE;
// Choose blocks at random
for (int bi = 0; bi < block_count; ++bi) {
const Vector3i block_pos = min_block_pos + Vector3i(
Math::rand() % block_area_size.x,
Math::rand() % block_area_size.y,
Math::rand() % block_area_size.z);
const Vector3i block_origin = _map->block_to_voxel(block_pos);
const VoxelBlock *block = _map->get_block(block_pos);
if (block != nullptr) {
if (block->voxels->get_channel_compression(channel) == VoxelBuffer::COMPRESSION_UNIFORM) {
const uint64_t v = block->voxels->get_voxel(0, 0, 0, channel);
if (lib.has_voxel(v)) {
const Voxel &vt = lib.get_voxel_const(v);
if (!vt.is_random_tickable()) {
// Skip whole block
continue;
}
}
}
// Choose a bunch of voxels at random within the block.
// Batching this way improves performance a little by reducing block lookups.
for (int vi = 0; vi < batch_count; ++vi) {
const Vector3i rpos(
Math::rand() & bs_mask,
Math::rand() & bs_mask,
Math::rand() & bs_mask);
2020-08-06 19:54:47 +01:00
const uint64_t v = block->voxels->get_voxel(rpos, channel);
if (lib.has_voxel(v)) {
const Voxel &vt = lib.get_voxel_const(v);
if (vt.is_random_tickable()) {
const Variant vpos = (rpos + block_origin).to_vec3();
const Variant vv = v;
const Variant *args[2];
args[0] = &vpos;
args[1] = &vv;
Variant::CallError error;
callback->call_func(args, 2, error);
// TODO I would really like to know what's the correct way to report such errors...
// Examples I found in the engine are inconsistent
ERR_FAIL_COND(error.error != Variant::CallError::CALL_OK);
// Return if it fails, we don't want an error spam
}
}
}
}
}
}
void VoxelToolTerrain::_bind_methods() {
ClassDB::bind_method(D_METHOD("run_blocky_random_tick", "voxel_count", "callback", "batch_count"),
&VoxelToolTerrain::run_blocky_random_tick, DEFVAL(16));
}