voxel_raycast is now template

master
Marc Gilleron 2020-01-26 20:43:40 +00:00
parent 0ac1b7e6dc
commit 87a9766442
5 changed files with 143 additions and 172 deletions

View File

@ -15,53 +15,42 @@ bool VoxelToolTerrain::is_area_editable(const Rect3i &box) const {
return _map->is_area_fully_loaded(box.padded(1));
}
namespace {
struct _VoxelTerrainRaycastContext {
VoxelTerrain &terrain;
//unsigned int channel_mask;
};
} // namespace
static bool cb_raycast_predicate(Vector3i pos, void *context_ptr) {
// TODO This is really not the best way.
// This needs lambda goodness and made available to other volume types (generic?)
ERR_FAIL_COND_V(context_ptr == NULL, false);
_VoxelTerrainRaycastContext *context = (_VoxelTerrainRaycastContext *)context_ptr;
VoxelTerrain &terrain = context->terrain;
//unsigned int channel = context->channel;
Ref<VoxelMap> map = terrain.get_storage();
int v0 = map->get_voxel(pos, VoxelBuffer::CHANNEL_TYPE);
Ref<VoxelLibrary> lib_ref = terrain.get_voxel_library();
if (lib_ref.is_null())
return false;
const VoxelLibrary &lib = **lib_ref;
if (lib.has_voxel(v0) == false)
return false;
const Voxel &voxel = lib.get_voxel_const(v0);
if (voxel.is_transparent() == false)
return true;
float v1 = map->get_voxel_f(pos, VoxelBuffer::CHANNEL_SDF);
return v1 < 0;
}
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 {
const VoxelTerrain &terrain;
bool operator()(Vector3i pos) {
//unsigned int channel = context->channel;
Ref<VoxelMap> map = terrain.get_storage();
int v0 = map->get_voxel(pos, VoxelBuffer::CHANNEL_TYPE);
Ref<VoxelLibrary> lib_ref = terrain.get_voxel_library();
if (lib_ref.is_null())
return false;
const VoxelLibrary &lib = **lib_ref;
if (lib.has_voxel(v0) == false)
return false;
const Voxel &voxel = lib.get_voxel_const(v0);
if (voxel.is_transparent() == false)
return true;
float v1 = map->get_voxel_f(pos, VoxelBuffer::CHANNEL_SDF);
return v1 < 0;
}
};
Vector3i hit_pos;
Vector3i prev_pos;
_VoxelTerrainRaycastContext context = { *_terrain };
Ref<VoxelRaycastResult> res;
if (voxel_raycast(pos, dir, cb_raycast_predicate, &context, max_distance, hit_pos, prev_pos)) {
RaycastPredicate predicate = { *_terrain };
if (voxel_raycast(pos, dir, predicate, max_distance, hit_pos, prev_pos)) {
res.instance();
res->position = hit_pos;

View File

@ -3,7 +3,6 @@
#include "../streams/voxel_stream_file.h"
#include "../util/profiling_clock.h"
#include "../util/utility.h"
#include "../util/voxel_raycast.h"
#include "voxel_block.h"
#include "voxel_map.h"

View File

@ -52,7 +52,7 @@ public:
bool is_smooth_meshing_enabled() const;
void set_smooth_meshing_enabled(bool enabled);
Ref<VoxelMap> get_storage() { return _map; }
Ref<VoxelMap> get_storage() const { return _map; }
Ref<VoxelTool> get_voxel_tool();
struct Stats {

View File

@ -1,123 +0,0 @@
#include "voxel_raycast.h"
#include <core/math/math_funcs.h>
bool voxel_raycast(
Vector3 ray_origin,
Vector3 ray_direction,
VoxelPredicate predicate,
void *predicate_context,
real_t max_distance,
Vector3i &out_hit_pos,
Vector3i &out_prev_pos) {
const float g_infinite = 9999999;
// Equation : p + v*t
// p : ray start position (ray.pos)
// v : ray orientation vector (ray.dir)
// t : parametric variable = a distance if v is normalized
// This raycasting technique is described here :
// http://www.cse.yorku.ca/~amana/research/grid.pdf
// Note : the grid is assumed to have 1-unit square cells.
ERR_FAIL_COND_V(predicate == 0, false);
ERR_FAIL_COND_V(ray_direction.is_normalized() == false, false); // Must be normalized
/* Initialisation */
// Voxel position
Vector3i hit_pos(
Math::floor(ray_origin.x),
Math::floor(ray_origin.y),
Math::floor(ray_origin.z));
Vector3i hit_prev_pos = hit_pos;
// Voxel step
const int xi_step = ray_direction.x > 0 ? 1 : ray_direction.x < 0 ? -1 : 0;
const int yi_step = ray_direction.y > 0 ? 1 : ray_direction.y < 0 ? -1 : 0;
const int zi_step = ray_direction.z > 0 ? 1 : ray_direction.z < 0 ? -1 : 0;
// Parametric voxel step
const real_t tdelta_x = xi_step != 0 ? 1.f / Math::abs(ray_direction.x) : g_infinite;
const real_t tdelta_y = yi_step != 0 ? 1.f / Math::abs(ray_direction.y) : g_infinite;
const real_t tdelta_z = zi_step != 0 ? 1.f / Math::abs(ray_direction.z) : g_infinite;
// Parametric grid-cross
real_t tcross_x; // At which value of T we will cross a vertical line?
real_t tcross_y; // At which value of T we will cross a horizontal line?
real_t tcross_z; // At which value of T we will cross a depth line?
// X initialization
if (xi_step != 0) {
if (xi_step == 1)
tcross_x = (Math::ceil(ray_origin.x) - ray_origin.x) * tdelta_x;
else
tcross_x = (ray_origin.x - Math::floor(ray_origin.x)) * tdelta_x;
} else
tcross_x = g_infinite; // Will never cross on X
// Y initialization
if (yi_step != 0) {
if (yi_step == 1)
tcross_y = (Math::ceil(ray_origin.y) - ray_origin.y) * tdelta_y;
else
tcross_y = (ray_origin.y - Math::floor(ray_origin.y)) * tdelta_y;
} else
tcross_y = g_infinite; // Will never cross on X
// Z initialization
if (zi_step != 0) {
if (zi_step == 1)
tcross_z = (Math::ceil(ray_origin.z) - ray_origin.z) * tdelta_z;
else
tcross_z = (ray_origin.z - Math::floor(ray_origin.z)) * tdelta_z;
} else
tcross_z = g_infinite; // Will never cross on X
/* Iteration */
do {
hit_prev_pos = hit_pos;
if (tcross_x < tcross_y) {
if (tcross_x < tcross_z) {
// X collision
//hit.prevPos.x = hit.pos.x;
hit_pos.x += xi_step;
if (tcross_x > max_distance)
return false;
tcross_x += tdelta_x;
} else {
// Z collision (duplicate code)
//hit.prevPos.z = hit.pos.z;
hit_pos.z += zi_step;
if (tcross_z > max_distance)
return false;
tcross_z += tdelta_z;
}
} else {
if (tcross_y < tcross_z) {
// Y collision
//hit.prevPos.y = hit.pos.y;
hit_pos.y += yi_step;
if (tcross_y > max_distance)
return false;
tcross_y += tdelta_y;
} else {
// Z collision (duplicate code)
//hit.prevPos.z = hit.pos.z;
hit_pos.z += zi_step;
if (tcross_z > max_distance)
return false;
tcross_z += tdelta_z;
}
}
} while (!predicate(hit_pos, predicate_context));
out_hit_pos = hit_pos;
out_prev_pos = hit_prev_pos;
return true;
}

View File

@ -1,16 +1,122 @@
#include "../math/vector3i.h"
#include <core/math/vector3.h>
// TODO that could be a template function
// pos: voxel position
// context: arguments to carry (as a lamdbda capture)
typedef bool (*VoxelPredicate)(Vector3i pos, void *context);
template <typename Predicate_F> // f(Vector3i position) -> bool
bool voxel_raycast(
Vector3 ray_origin,
Vector3 ray_direction,
VoxelPredicate predicate,
void *predicate_context, // Handle that one with care
Predicate_F predicate,
real_t max_distance,
Vector3i &out_hit_pos,
Vector3i &out_prev_pos);
Vector3i &out_prev_pos) {
const float g_infinite = 9999999;
// Equation : p + v*t
// p : ray start position (ray.pos)
// v : ray orientation vector (ray.dir)
// t : parametric variable = a distance if v is normalized
// This raycasting technique is described here :
// http://www.cse.yorku.ca/~amana/research/grid.pdf
// Note : the grid is assumed to have 1-unit square cells.
ERR_FAIL_COND_V(ray_direction.is_normalized() == false, false); // Must be normalized
/* Initialisation */
// Voxel position
Vector3i hit_pos(
Math::floor(ray_origin.x),
Math::floor(ray_origin.y),
Math::floor(ray_origin.z));
Vector3i hit_prev_pos = hit_pos;
// Voxel step
const int xi_step = ray_direction.x > 0 ? 1 : ray_direction.x < 0 ? -1 : 0;
const int yi_step = ray_direction.y > 0 ? 1 : ray_direction.y < 0 ? -1 : 0;
const int zi_step = ray_direction.z > 0 ? 1 : ray_direction.z < 0 ? -1 : 0;
// Parametric voxel step
const real_t tdelta_x = xi_step != 0 ? 1.f / Math::abs(ray_direction.x) : g_infinite;
const real_t tdelta_y = yi_step != 0 ? 1.f / Math::abs(ray_direction.y) : g_infinite;
const real_t tdelta_z = zi_step != 0 ? 1.f / Math::abs(ray_direction.z) : g_infinite;
// Parametric grid-cross
real_t tcross_x; // At which value of T we will cross a vertical line?
real_t tcross_y; // At which value of T we will cross a horizontal line?
real_t tcross_z; // At which value of T we will cross a depth line?
// X initialization
if (xi_step != 0) {
if (xi_step == 1)
tcross_x = (Math::ceil(ray_origin.x) - ray_origin.x) * tdelta_x;
else
tcross_x = (ray_origin.x - Math::floor(ray_origin.x)) * tdelta_x;
} else
tcross_x = g_infinite; // Will never cross on X
// Y initialization
if (yi_step != 0) {
if (yi_step == 1)
tcross_y = (Math::ceil(ray_origin.y) - ray_origin.y) * tdelta_y;
else
tcross_y = (ray_origin.y - Math::floor(ray_origin.y)) * tdelta_y;
} else
tcross_y = g_infinite; // Will never cross on X
// Z initialization
if (zi_step != 0) {
if (zi_step == 1)
tcross_z = (Math::ceil(ray_origin.z) - ray_origin.z) * tdelta_z;
else
tcross_z = (ray_origin.z - Math::floor(ray_origin.z)) * tdelta_z;
} else
tcross_z = g_infinite; // Will never cross on X
/* Iteration */
do {
hit_prev_pos = hit_pos;
if (tcross_x < tcross_y) {
if (tcross_x < tcross_z) {
// X collision
//hit.prevPos.x = hit.pos.x;
hit_pos.x += xi_step;
if (tcross_x > max_distance)
return false;
tcross_x += tdelta_x;
} else {
// Z collision (duplicate code)
//hit.prevPos.z = hit.pos.z;
hit_pos.z += zi_step;
if (tcross_z > max_distance)
return false;
tcross_z += tdelta_z;
}
} else {
if (tcross_y < tcross_z) {
// Y collision
//hit.prevPos.y = hit.pos.y;
hit_pos.y += yi_step;
if (tcross_y > max_distance)
return false;
tcross_y += tdelta_y;
} else {
// Z collision (duplicate code)
//hit.prevPos.z = hit.pos.z;
hit_pos.z += zi_step;
if (tcross_z > max_distance)
return false;
tcross_z += tdelta_z;
}
}
} while (!predicate(hit_pos));
out_hit_pos = hit_pos;
out_prev_pos = hit_prev_pos;
return true;
}