From ef4d07ca0364f9780df54661bca14ee787fe6c97 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Sun, 26 Mar 2017 20:07:01 +0200 Subject: [PATCH] Added simple voxel raycast --- voxel_raycast.cpp | 141 ++++++++++++++++++++++++++++++++++++++++++++++ voxel_raycast.h | 18 ++++++ voxel_terrain.cpp | 47 ++++++++++++++++ voxel_terrain.h | 2 + 4 files changed, 208 insertions(+) create mode 100644 voxel_raycast.cpp create mode 100644 voxel_raycast.h diff --git a/voxel_raycast.cpp b/voxel_raycast.cpp new file mode 100644 index 0000000..a9734fb --- /dev/null +++ b/voxel_raycast.cpp @@ -0,0 +1,141 @@ +#include "voxel_raycast.h" +#include + +const float g_infinite = 9999999; + +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 +){ + // 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; +} + diff --git a/voxel_raycast.h b/voxel_raycast.h new file mode 100644 index 0000000..5eb7e05 --- /dev/null +++ b/voxel_raycast.h @@ -0,0 +1,18 @@ +#include +#include "vector3i.h" + +// TODO Having a C++11 lambda would be nice... +// pos: voxel position +// context: arguments to carry (as a lamdbda capture) +typedef bool(*VoxelPredicate)(Vector3i pos, void * context); + +bool voxel_raycast( + Vector3 ray_origin, + Vector3 ray_direction, + VoxelPredicate predicate, + void * predicate_context, // Handle that one with care + real_t max_distance, + Vector3i & out_hit_pos, + Vector3i & out_prev_pos +); + diff --git a/voxel_terrain.cpp b/voxel_terrain.cpp index 406603e..7d206da 100644 --- a/voxel_terrain.cpp +++ b/voxel_terrain.cpp @@ -1,6 +1,7 @@ #include "voxel_terrain.h" #include #include +#include "voxel_raycast.h" VoxelTerrain::VoxelTerrain(): Node(), _min_y(-4), _max_y(4), _generate_collisions(true) { @@ -24,6 +25,10 @@ Ref VoxelTerrain::get_provider() { return _provider; } +Ref VoxelTerrain::get_voxel_library() { + return _mesher->get_library(); +} + void VoxelTerrain::set_generate_collisions(bool enabled) { _generate_collisions = enabled; } @@ -212,6 +217,46 @@ void VoxelTerrain::update_block_mesh(Vector3i block_pos) { // } //} +static bool _raycast_binding_predicate(Vector3i pos, void *context) { + + ERR_FAIL_COND_V(context == NULL, false); + VoxelTerrain * terrain = (VoxelTerrain*)context; + + Ref lib_ref = terrain->get_voxel_library(); + if(lib_ref.is_null()) + return false; + const VoxelLibrary & lib = **lib_ref; + + Ref map = terrain->get_map(); + // TODO In the future we may want to query more channels + int v = map->get_voxel(pos, 0); + if(lib.has_voxel(v) == false) + return false; + + const Voxel & voxel = lib.get_voxel_const(v); + return !voxel.is_transparent(); +} + +Variant VoxelTerrain::_raycast_binding(Vector3 origin, Vector3 direction, real_t max_distance) { + + // TODO Transform input if the terrain is rotated (in the future it can be made a Spatial node) + + Vector3i hit_pos; + Vector3i prev_pos; + + if(voxel_raycast(origin, direction, _raycast_binding_predicate, this, max_distance, hit_pos, prev_pos)) { + + Dictionary hit = Dictionary(); + hit["position"] = hit_pos.to_vec3(); + hit["prev_position"] = prev_pos.to_vec3(); + return hit; + } + else { + return Variant(); // Null dictionary, no alloc + } +} + + void VoxelTerrain::_bind_methods() { ClassDB::bind_method(D_METHOD("set_provider", "provider:VoxelProvider"), &VoxelTerrain::set_provider); @@ -231,5 +276,7 @@ void VoxelTerrain::_bind_methods() { ClassDB::bind_method(D_METHOD("force_load_blocks", "center", "extents"), &VoxelTerrain::_force_load_blocks_binding); + ClassDB::bind_method(D_METHOD("raycast:Dictionary", "origin", "direction", "max_distance"), &VoxelTerrain::_raycast_binding, DEFVAL(100)); + } diff --git a/voxel_terrain.h b/voxel_terrain.h index 3cf71cb..b773748 100644 --- a/voxel_terrain.h +++ b/voxel_terrain.h @@ -27,6 +27,7 @@ public: Ref get_mesher() { return _mesher; } Ref get_map() { return _map; } + Ref get_voxel_library(); protected: void _notification(int p_what); @@ -44,6 +45,7 @@ protected: Vector3 _voxel_to_block_binding(Vector3 pos) { return Vector3i(VoxelMap::voxel_to_block(pos)).to_vec3(); } Vector3 _block_to_voxel_binding(Vector3 pos) { return Vector3i(VoxelMap::block_to_voxel(pos)).to_vec3(); } void _force_load_blocks_binding(Vector3 center, Vector3 extents) { force_load_blocks(center, extents); } + Variant _raycast_binding(Vector3 origin, Vector3 direction, real_t max_distance); private: // Parameters