Changed VoxelToolLodTerrain to use write_box
- Should be faster than get/set_voxel - No longer expose internal data map
This commit is contained in:
parent
ff4e7f9560
commit
2971334e85
@ -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));
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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 ↦
|
||||
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 ↦
|
||||
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) {
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user