Changed VoxelToolLodTerrain to use write_box

- Should be faster than get/set_voxel
- No longer expose internal data map
This commit is contained in:
Marc Gilleron 2021-09-17 20:01:15 +01:00
parent ff4e7f9560
commit 2971334e85
10 changed files with 184 additions and 41 deletions

View File

@ -1,7 +1,6 @@
#include "voxel_tool.h"
#include "../storage/voxel_buffer.h"
#include "../util/macros.h"
#include "../util/math/sdf.h"
#include "../util/profiling.h"
VoxelTool::VoxelTool() {
@ -189,7 +188,7 @@ void VoxelTool::do_sphere(Vector3 center, float radius) {
if (_channel == VoxelBuffer::CHANNEL_SDF) {
box.for_each_cell([this, center, radius](Vector3i pos) {
float d = _sdf_scale * (pos.to_vec3().distance_to(center) - radius);
float d = _sdf_scale * sdf_sphere(pos.to_vec3(), center, radius);
_set_voxel_f(pos, sdf_blend(d, get_voxel_f(pos), _mode));
});

View File

@ -1,7 +1,9 @@
#ifndef VOXEL_TOOL_H
#define VOXEL_TOOL_H
#include "../storage/funcs.h"
#include "../util/math/box3i.h"
#include "../util/math/sdf.h"
#include "funcs.h"
#include "voxel_raycast_result.h"
@ -158,11 +160,11 @@ protected:
float radius_squared;
TextureParams tp;
TextureBlendSphereOp(Vector3 pCenter, float pRadius, TextureParams pTp) {
center = pCenter;
radius = pRadius;
radius_squared = pRadius * pRadius;
tp = pTp;
TextureBlendSphereOp(Vector3 p_center, float p_radius, TextureParams p_tp) {
center = p_center;
radius = p_radius;
radius_squared = p_radius * p_radius;
tp = p_tp;
}
inline void operator()(Vector3i pos, uint16_t &indices, uint16_t &weights) const {
@ -175,6 +177,43 @@ protected:
}
};
template <typename Op, typename Shape>
struct SdfOperation16bit {
Op op;
Shape shape;
inline uint16_t operator()(Vector3i pos, uint16_t sdf) const {
return norm_to_u16(op(u16_to_norm(sdf), shape(pos.to_vec3())));
}
};
struct SdfUnion {
inline float operator()(float a, float b) const {
return sdf_union(a, b);
}
};
struct SdfSubtract {
inline float operator()(float a, float b) const {
return sdf_subtract(a, b);
}
};
struct SdfSet {
inline float operator()(float a, float b) const {
return b;
}
};
struct SdfSphere {
Vector3 center;
float radius;
float scale;
inline float operator()(Vector3 pos) const {
return scale * sdf_sphere(pos, center, radius);
}
};
// Used on smooth terrain
TextureParams _texture_params;
};

View File

@ -11,8 +11,8 @@
#include <scene/3d/physics_body.h>
#include <scene/main/timer.h>
VoxelToolLodTerrain::VoxelToolLodTerrain(VoxelLodTerrain *terrain, VoxelDataMap &map) :
_terrain(terrain), _map(&map) {
VoxelToolLodTerrain::VoxelToolLodTerrain(VoxelLodTerrain *terrain) :
_terrain(terrain) {
ERR_FAIL_COND(terrain == nullptr);
// At the moment, only LOD0 is supported.
// Don't destroy the terrain while a voxel tool still references it
@ -20,8 +20,7 @@ VoxelToolLodTerrain::VoxelToolLodTerrain(VoxelLodTerrain *terrain, VoxelDataMap
bool VoxelToolLodTerrain::is_area_editable(const Box3i &box) const {
ERR_FAIL_COND_V(_terrain == nullptr, false);
// TODO Take volume bounds into account
return _map->is_area_fully_loaded(box);
return _terrain->is_area_editable(box);
}
template <typename Volume_F>
@ -93,11 +92,13 @@ Ref<VoxelRaycastResult> VoxelToolLodTerrain::raycast(
// TODO Implement reverse raycast? (going from inside ground to air, could be useful for undigging)
struct RaycastPredicate {
const VoxelDataMap &map;
const VoxelLodTerrain *terrain;
bool operator()(Vector3i pos) {
// This is not particularly optimized, but runs fast enough for player raycasts
const float sdf = map.get_voxel_f(pos, VoxelBuffer::CHANNEL_SDF);
const uint64_t raw_value = terrain->get_voxel(pos, VoxelBuffer::CHANNEL_SDF, 0);
// TODO Format should be accessible from terrain
const float sdf = u16_to_norm(raw_value);
return sdf < 0;
}
};
@ -105,7 +106,7 @@ Ref<VoxelRaycastResult> VoxelToolLodTerrain::raycast(
Ref<VoxelRaycastResult> res;
// We use grid-raycast as a middle-phase to roughly detect where the hit will be
RaycastPredicate predicate = { *_map };
RaycastPredicate predicate = { _terrain };
Vector3i hit_pos;
Vector3i prev_pos;
float hit_distance;
@ -134,14 +135,17 @@ Ref<VoxelRaycastResult> VoxelToolLodTerrain::raycast(
if (_raycast_binary_search_iterations > 0) {
// This is not particularly optimized, but runs fast enough for player raycasts
struct VolumeSampler {
const VoxelDataMap &map;
const VoxelLodTerrain *terrain;
inline float operator()(const Vector3i &pos) const {
return map.get_voxel_f(pos, VoxelBuffer::CHANNEL_SDF);
const uint64_t raw_value = terrain->get_voxel(pos, VoxelBuffer::CHANNEL_SDF, 0);
// TODO Format should be accessible from terrain
const float sdf = u16_to_norm(raw_value);
return sdf;
}
};
VolumeSampler sampler{ *_map };
VolumeSampler sampler{ _terrain };
d = hit_distance_prev + approximate_distance_to_isosurface_binary_search(sampler,
pos + dir * hit_distance_prev,
dir, hit_distance - hit_distance_prev,
@ -158,26 +162,50 @@ Ref<VoxelRaycastResult> VoxelToolLodTerrain::raycast(
}
void VoxelToolLodTerrain::do_sphere(Vector3 center, float radius) {
VOXEL_PROFILE_SCOPE();
ERR_FAIL_COND(_terrain == nullptr);
if (_mode != MODE_TEXTURE_PAINT) {
VoxelTool::do_sphere(center, radius);
return;
}
VOXEL_PROFILE_SCOPE();
const Box3i box(Vector3i(center) - Vector3i(Math::floor(radius)), Vector3i(Math::ceil(radius) * 2));
if (!is_area_editable(box)) {
PRINT_VERBOSE("Area not editable");
return;
}
_map->write_box_2(box, VoxelBuffer::CHANNEL_INDICES, VoxelBuffer::CHANNEL_WEIGHTS,
TextureBlendSphereOp{ center, radius, _texture_params });
switch (_mode) {
case MODE_ADD: {
// TODO Support other depths, format should be accessible from the volume
SdfOperation16bit<SdfUnion, SdfSphere> op;
op.shape.center = center;
op.shape.radius = radius;
op.shape.scale = _sdf_scale;
_terrain->write_box(box, VoxelBuffer::CHANNEL_SDF, op);
} break;
_post_edit(box);
case MODE_REMOVE: {
SdfOperation16bit<SdfSubtract, SdfSphere> op;
op.shape.center = center;
op.shape.radius = radius;
op.shape.scale = _sdf_scale;
_terrain->write_box(box, VoxelBuffer::CHANNEL_SDF, op);
} break;
case MODE_SET: {
SdfOperation16bit<SdfSet, SdfSphere> op;
op.shape.center = center;
op.shape.radius = radius;
op.shape.scale = _sdf_scale;
_terrain->write_box(box, VoxelBuffer::CHANNEL_SDF, op);
} break;
case MODE_TEXTURE_PAINT: {
_terrain->write_box_2(box, VoxelBuffer::CHANNEL_INDICES, VoxelBuffer::CHANNEL_WEIGHTS,
TextureBlendSphereOp{ center, radius, _texture_params });
} break;
default:
ERR_PRINT("Unknown mode");
break;
}
}
void VoxelToolLodTerrain::copy(Vector3i pos, Ref<VoxelBuffer> dst, uint8_t channels_mask) const {
@ -186,38 +214,44 @@ void VoxelToolLodTerrain::copy(Vector3i pos, Ref<VoxelBuffer> dst, uint8_t chann
if (channels_mask == 0) {
channels_mask = (1 << _channel);
}
_map->copy(pos, **dst, channels_mask);
_terrain->copy(pos, **dst, channels_mask);
}
float VoxelToolLodTerrain::get_voxel_f_interpolated(Vector3 position) const {
ERR_FAIL_COND_V(_terrain == nullptr, 0);
const VoxelDataMap *map = _map;
const int channel = get_channel();
const VoxelLodTerrain *terrain = _terrain;
// TODO Optimization: is it worth a making a fast-path for this?
return get_sdf_interpolated([map, channel](Vector3i ipos) {
return map->get_voxel_f(ipos, channel);
return get_sdf_interpolated([terrain, channel](Vector3i ipos) {
const uint64_t raw_value = terrain->get_voxel(ipos, VoxelBuffer::CHANNEL_SDF, 0);
// TODO Format should be accessible from terrain
const float sdf = u16_to_norm(raw_value);
return sdf;
},
position);
}
uint64_t VoxelToolLodTerrain::_get_voxel(Vector3i pos) const {
ERR_FAIL_COND_V(_terrain == nullptr, 0);
return _map->get_voxel(pos, _channel);
return _terrain->get_voxel(pos, _channel, 0);
}
float VoxelToolLodTerrain::_get_voxel_f(Vector3i pos) const {
ERR_FAIL_COND_V(_terrain == nullptr, 0);
return _map->get_voxel_f(pos, _channel);
const uint64_t raw_value = _terrain->get_voxel(pos, _channel, 0);
// TODO Format should be accessible from terrain
return u16_to_norm(raw_value);
}
void VoxelToolLodTerrain::_set_voxel(Vector3i pos, uint64_t v) {
ERR_FAIL_COND(_terrain == nullptr);
_map->set_voxel(v, pos, _channel);
_terrain->try_set_voxel_without_update(pos, _channel, v);
}
void VoxelToolLodTerrain::_set_voxel_f(Vector3i pos, float v) {
ERR_FAIL_COND(_terrain == nullptr);
_map->set_voxel_f(v, pos, _channel);
// TODO Format should be accessible from terrain
_terrain->try_set_voxel_without_update(pos, _channel, norm_to_u16(v));
}
void VoxelToolLodTerrain::_post_edit(const Box3i &box) {

View File

@ -10,7 +10,7 @@ class VoxelToolLodTerrain : public VoxelTool {
GDCLASS(VoxelToolLodTerrain, VoxelTool)
public:
VoxelToolLodTerrain() {}
VoxelToolLodTerrain(VoxelLodTerrain *terrain, VoxelDataMap &map);
VoxelToolLodTerrain(VoxelLodTerrain *terrain);
bool is_area_editable(const Box3i &box) const override;
Ref<VoxelRaycastResult> raycast(Vector3 pos, Vector3 dir, float max_distance, uint32_t collision_mask) override;
@ -38,7 +38,6 @@ private:
static void _bind_methods();
VoxelLodTerrain *_terrain = nullptr;
VoxelDataMap *_map = nullptr;
int _raycast_binary_search_iterations = 0;
};

View File

@ -201,7 +201,7 @@ bool VoxelDataMap::is_block_surrounded(Vector3i pos) const {
return true;
}
void VoxelDataMap::copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsigned int channels_mask) {
void VoxelDataMap::copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsigned int channels_mask) const {
const Vector3i max_pos = min_pos + dst_buffer.get_size();
const Vector3i min_block_pos = voxel_to_block(min_pos);

View File

@ -57,7 +57,7 @@ public:
int get_default_voxel(unsigned int channel = 0);
// Gets a copy of all voxels in the area starting at min_pos having the same size as dst_buffer.
void copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsigned int channels_mask);
void copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsigned int channels_mask) const;
void paste(Vector3i min_pos, VoxelBuffer &src_buffer, unsigned int channels_mask, uint64_t mask_value,
bool create_new_blocks);

View File

@ -189,6 +189,8 @@ public:
subdivide_recursively(ROOT_INDEX, Vector3i(), _max_depth, actions);
}
// Gets the bounding box of a node within the LOD0 coordinate system
// (i.e a leaf node will always be 1x1x1, a LOD1 node will be 2x2x2 etc)
static inline Box3i get_node_box(Vector3i pos_within_lod, int lod_index) {
return Box3i(pos_within_lod << lod_index, Vector3i(1 << lod_index));
}

View File

@ -417,6 +417,45 @@ inline int get_octree_size_po2(const VoxelLodTerrain &self) {
return self.get_mesh_block_size_pow2() + self.get_lod_count() - 1;
}
bool VoxelLodTerrain::is_area_editable(Box3i p_voxel_box) const {
const Box3i voxel_box = p_voxel_box.clipped(_bounds_in_voxels);
const Box3i lod0_data_block_box = voxel_box.downscaled(get_data_block_size());
const Lod &lod0 = _lods[0];
const bool all_blocks_present = lod0.data_map.is_area_fully_loaded(lod0_data_block_box);
return all_blocks_present;
}
uint64_t VoxelLodTerrain::get_voxel(Vector3i pos, unsigned int channel, uint64_t defval) const {
Vector3i block_pos = pos >> get_data_block_size_pow2();
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
const Lod &lod = _lods[lod_index];
const VoxelDataBlock *block = lod.data_map.get_block(block_pos);
if (block != nullptr) {
return lod.data_map.get_voxel(pos, channel);
}
// Fallback on lower LOD
block_pos = block_pos >> 1;
}
return defval;
}
bool VoxelLodTerrain::try_set_voxel_without_update(Vector3i pos, unsigned int channel, uint64_t value) {
const Vector3i block_pos_lod0 = pos >> get_data_block_size_pow2();
Lod &lod0 = _lods[0];
VoxelDataBlock *block = lod0.data_map.get_block(block_pos_lod0);
if (block != nullptr) {
lod0.data_map.set_voxel(value, pos, channel);
return true;
} else {
return false;
}
}
void VoxelLodTerrain::copy(Vector3i p_origin_voxels, VoxelBuffer &dst_buffer, uint8_t channels_mask) const {
const Lod &lod0 = _lods[0];
lod0.data_map.copy(p_origin_voxels, dst_buffer, channels_mask);
}
// Marks intersecting blocks in the area as modified, updates LODs and schedules remeshing.
// The provided box must be at LOD0 coordinates.
void VoxelLodTerrain::post_edit_area(Box3i p_box) {
@ -452,7 +491,7 @@ void VoxelLodTerrain::post_edit_area(Box3i p_box) {
}
Ref<VoxelTool> VoxelLodTerrain::get_voxel_tool() {
VoxelToolLodTerrain *vt = memnew(VoxelToolLodTerrain(this, _lods[0].data_map));
VoxelToolLodTerrain *vt = memnew(VoxelToolLodTerrain(this));
// Set to most commonly used channel on this kind of terrain
vt->set_channel(VoxelBuffer::CHANNEL_SDF);
return Ref<VoxelTool>(vt);

View File

@ -79,6 +79,33 @@ public:
unsigned int get_mesh_block_size() const;
void set_mesh_block_size(unsigned int mesh_block_size);
bool is_area_editable(Box3i p_box) const;
uint64_t get_voxel(Vector3i pos, unsigned int channel, uint64_t defval) const;
bool try_set_voxel_without_update(Vector3i pos, unsigned int channel, uint64_t value);
void copy(Vector3i p_origin_voxels, VoxelBuffer &dst_buffer, uint8_t channels_mask) const;
template <typename F>
void write_box(const Box3i &p_voxel_box, unsigned int channel, F action) {
const Box3i voxel_box = p_voxel_box.clipped(_bounds_in_voxels);
if (is_area_editable(voxel_box)) {
_lods[0].data_map.write_box(voxel_box, channel, action);
post_edit_area(voxel_box);
} else {
PRINT_VERBOSE("Area not editable");
}
}
template <typename F>
void write_box_2(const Box3i &p_voxel_box, unsigned int channel1, unsigned int channel2, F action) {
const Box3i voxel_box = p_voxel_box.clipped(_bounds_in_voxels);
if (is_area_editable(voxel_box)) {
_lods[0].data_map.write_box_2(voxel_box, channel1, channel2, action);
post_edit_area(voxel_box);
} else {
PRINT_VERBOSE("Area not editable");
}
}
// These must be called after an edit
void post_edit_area(Box3i p_box);

View File

@ -23,6 +23,10 @@ inline Interval sdf_box(
get_length(max_interval(dx, 0.f), max_interval(dy, 0.f), max_interval(dz, 0.f));
}
inline float sdf_sphere(Vector3 pos, Vector3 center, float radius) {
return pos.distance_to(center) - radius;
}
inline float sdf_torus(float x, float y, float z, float r0, float r1) {
Vector2 q = Vector2(Vector2(x, z).length() - r0, y);
return q.length() - r1;