Added random tick API for blocky voxels

master
Marc Gilleron 2020-07-28 20:32:33 +01:00
parent b95f47b018
commit 0cdf5ea550
5 changed files with 96 additions and 1 deletions

View File

@ -2,6 +2,10 @@
#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);
@ -16,7 +20,6 @@ bool VoxelToolTerrain::is_area_editable(const Rect3i &box) const {
}
Ref<VoxelRaycastResult> VoxelToolTerrain::raycast(Vector3 pos, Vector3 dir, float max_distance) {
// TODO Transform input if the terrain is rotated (in the future it can be made a Spatial node)
struct RaycastPredicate {
@ -90,3 +93,75 @@ void VoxelToolTerrain::_post_edit(const Rect3i &box) {
ERR_FAIL_COND(_terrain == nullptr);
_terrain->make_area_dirty(box);
}
// 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();
// 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) {
// 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);
const unsigned int v = block->voxels->get_voxel(rpos, VoxelBuffer::CHANNEL_TYPE);
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));
}

View File

@ -5,15 +5,19 @@
class VoxelTerrain;
class VoxelMap;
class FuncRef;
class VoxelToolTerrain : public VoxelTool {
GDCLASS(VoxelToolTerrain, VoxelTool)
public:
VoxelToolTerrain();
VoxelToolTerrain(VoxelTerrain *terrain, Ref<VoxelMap> map);
bool is_area_editable(const Rect3i &box) const override;
Ref<VoxelRaycastResult> raycast(Vector3 pos, Vector3 dir, float max_distance) override;
void run_blocky_random_tick(AABB voxel_area, int voxel_count, Ref<FuncRef> callback, int block_batch_count) const;
protected:
int _get_voxel(Vector3i pos) override;
float _get_voxel_f(Vector3i pos) override;
@ -22,6 +26,8 @@ protected:
void _post_edit(const Rect3i &box) override;
private:
static void _bind_methods();
VoxelTerrain *_terrain = nullptr;
Ref<VoxelMap> _map;
};

View File

@ -352,6 +352,10 @@ Ref<Voxel> Voxel::set_cube_geometry(float sy) {
return Ref<Voxel>(this);
}
void Voxel::set_random_tickable(bool rt) {
_random_tickable = rt;
}
void Voxel::set_cube_uv_side(int side, Vector2 tile_pos) {
_cube_tiles[side] = tile_pos;
// TODO Better have a dirty flag, otherwise UVs will be needlessly updated at least 6 times everytime a Voxel resource is loaded!
@ -445,6 +449,9 @@ void Voxel::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_transparent", "transparent"), &Voxel::set_transparent);
ClassDB::bind_method(D_METHOD("is_transparent"), &Voxel::is_transparent);
ClassDB::bind_method(D_METHOD("set_random_tickable", "rt"), &Voxel::set_random_tickable);
ClassDB::bind_method(D_METHOD("is_random_tickable"), &Voxel::is_random_tickable);
ClassDB::bind_method(D_METHOD("set_material_id", "id"), &Voxel::set_material_id);
ClassDB::bind_method(D_METHOD("get_material_id"), &Voxel::get_material_id);
@ -461,6 +468,7 @@ void Voxel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "voxel_name"), "set_voxel_name", "get_voxel_name");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "transparent"), "set_transparent", "is_transparent");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "random_tickable"), "set_random_tickable", "is_random_tickable");
ADD_PROPERTY(PropertyInfo(Variant::INT, "material_id"), "set_material_id", "get_material_id");
ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry_type", PROPERTY_HINT_ENUM, "None,Cube,CustomMesh"), "set_geometry_type", "get_geometry_type");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_custom_mesh", "get_custom_mesh");

View File

@ -47,6 +47,9 @@ public:
void set_custom_mesh(Ref<Mesh> mesh);
Ref<Mesh> get_custom_mesh() const { return _custom_mesh; }
void set_random_tickable(bool rt);
inline bool is_random_tickable() const { return _random_tickable; }
//-------------------------------------------
// Built-in geometry generators
@ -121,6 +124,7 @@ private:
Ref<Mesh> _custom_mesh;
std::vector<AABB> _collision_aabbs;
bool _contributes_to_ao = false;
bool _random_tickable = false;
FixedArray<uint32_t, Cube::SIDE_COUNT> _side_pattern_index;

View File

@ -1,5 +1,6 @@
#include "register_types.h"
#include "edition/voxel_tool.h"
#include "edition/voxel_tool_terrain.h"
#include "editor/editor_plugin.h"
#include "editor/voxel_graph_editor_plugin.h"
#include "generators/graph/voxel_generator_graph.h"
@ -58,6 +59,7 @@ void register_voxel_types() {
ClassDB::register_class<VoxelBoxMover>();
ClassDB::register_class<VoxelRaycastResult>();
ClassDB::register_class<VoxelTool>();
ClassDB::register_class<VoxelToolTerrain>();
ClassDB::register_class<VoxelBlockSerializer>();
// Meshers