]> git.localhorst.tv Git - tacos.git/blobdiff - src/world/world.cpp
ray/floor intersection experiments
[tacos.git] / src / world / world.cpp
index 25723ba81c052f21e7fbeda023518817fdb2c42f..cbff1005b744a16d73bf7551f868076bfd20143b 100644 (file)
@@ -1,6 +1,9 @@
 #include "Cursor.hpp"
 #include "Floor.hpp"
 
+#include <iostream>
+#include <glm/gtx/io.hpp>
+
 
 namespace tacos {
 
@@ -211,20 +214,137 @@ void Floor::DrawVAO(int vao_x, int vao_z) const noexcept {
 }
 
 bool Floor::Intersection(const Ray &ray, glm::vec3 &point) {
-       // TODO: this tests for Y=0 plane intersection, change to respect heightmap
-       if (std::abs(ray.direction.y) < std::numeric_limits<float>::epsilon()) {
-               // ray parallel to plane
-               return false;
+       // see http://www.flipcode.com/archives/Raytracing_Topics_Techniques-Part_4_Spatial_Subdivisions.shtml section Grid Traversal
+
+       // TODO: somehow this is not reliable at all, maybe due to numeric inaccuracy
+       //       the result is determined by checking the ray against triangles and it's
+       //       possible that it sometimes slips through the theoratically inexistant seams
+
+       // cache 1/dir to avoid some conditionals and divisions
+       glm::vec3 inverse_direction(ray.InverseDirection());
+
+       // cell indicates the current tile we're considering
+       glm::ivec2 cell(int(ray.origin.x), int(ray.origin.z));
+
+       // store the previous height to check against the lower of cell entry and exit
+       float prev_height = ray.origin.y;
+
+       // if ray's origin is outside the grid, advance to the first cell it hits
+       float x_near, x_far, z_near, z_far, t_min, t_max;
+       if (cell.x < 0 || cell.x >= width || cell.y < 0 || cell.y >= depth) {
+               x_near = (-ray.origin.x) * inverse_direction.x;
+               // subtracting one so the point is in the last cell, rather than after it, when approaching from the far end
+               x_far = (width - 1 - ray.origin.x) * inverse_direction.x;
+               z_near = (-ray.origin.z) * inverse_direction.z;
+               z_far = (depth - 1 - ray.origin.z) * inverse_direction.z;
+               t_min = std::max(std::min(x_near, x_far), std::min(z_near, z_far));
+               t_max = std::min(std::max(x_near, x_far), std::max(z_near, z_far));
+               if (t_max < 0.0f || t_min > t_max) {
+                       // ray doesn't touch our grid at all
+                       //std::cout << "        ray does not touch grid" << std::endl;
+                       return false;
+               }
+               glm::vec3 contact = ray.origin + t_min * ray.direction;
+               cell.x = int(contact.x);
+               cell.y = int(contact.z);
+               prev_height = contact.y;
        }
-       float factor = ray.origin.y / ray.direction.y;
-       if (factor > 0.0f) {
-               // intersection "behind" the ray
+
+       // step hold the direction we're traversing the grid
+       glm::ivec2 step(glm::sign(ray.direction.x), glm::sign(ray.direction.z));
+
+       if (step.x == 0 && step.y == 0) {
+               // ray shoots straight up or down
+               // check the current cell (if it's valid)
+               if (cell.x >= 0 && cell.x < width && cell.y >= 0 && cell.y < depth) {
+                       if (TriangleIntersection(
+                               ray,
+                               glm::vec3(float(cell.x + 0), GetElevation(cell.x + 0, cell.y + 0), float(cell.y + 0)),
+                               glm::vec3(float(cell.x + 1), GetElevation(cell.x + 1, cell.y + 0), float(cell.y + 0)),
+                               glm::vec3(float(cell.x + 0), GetElevation(cell.x + 0, cell.y + 1), float(cell.y + 1)),
+                               point
+                       )) {
+                               return true;
+                       }
+                       if (TriangleIntersection(
+                               ray,
+                               glm::vec3(float(cell.x + 1), GetElevation(cell.x + 1, cell.y + 0), float(cell.y + 0)),
+                               glm::vec3(float(cell.x + 1), GetElevation(cell.x + 1, cell.y + 1), float(cell.y + 1)),
+                               glm::vec3(float(cell.x + 0), GetElevation(cell.x + 0, cell.y + 1), float(cell.y + 1)),
+                               point
+                       )) {
+                               return true;
+                       }
+               }
+               //std::cout << "        ray is vertical and outside of grid" << std::endl;
                return false;
        }
-       point.x = ray.origin.x - (ray.direction.x * factor);
-       point.y = 0.0f;
-       point.z = ray.origin.z - (ray.direction.z * factor);
-       return point.x >= 0.0f && point.x <= float(width) && point.z >= 0.0f && point.z <= float(depth);
+
+       // cache for the height of the vertices of the current cell
+       float height[4];
+
+       // now step through each cell until Y gets below the surface or one of X or Z exit the grid bounds
+       while (cell.x >= 0 && cell.x < width && cell.y >= 0 && cell.y < depth) {
+               // pull heights for the current cell
+               height[0] = GetElevation(cell.x + 0, cell.y + 0);
+               height[1] = GetElevation(cell.x + 1, cell.y + 0);
+               height[2] = GetElevation(cell.x + 0, cell.y + 1);
+               height[3] = GetElevation(cell.x + 1, cell.y + 1);
+               // highest point in the cell
+               float max_height = std::max(std::max(height[0], height[1]), std::max(height[2], height[3]));
+
+               // check where the ray exits the current cell
+               // test how far away the ray is from each plane and choose the closest
+               x_near = (float(cell.x + step.x) - ray.origin.x) * inverse_direction.x;
+               z_near = (float(cell.y + step.y) - ray.origin.z) * inverse_direction.z;
+               // if dir is 0, inverse dir is infinity. multiplying 0 by infinity is NaN. min(x, inf) is x, min(x, nan) is x
+               t_min = std::min(x_near, z_near);
+               // heightof the ray at exit
+               float cur_height = ray.origin.y + (t_min * ray.direction.y);
+               // lowest point of the ray in the cell
+               float ray_low = std::min(prev_height, cur_height);
+               // store exit height for next cell's entry height
+               prev_height = cur_height;
+
+               // check if we might end up below the surface
+               // if this is true, there still could be no intersection if the ray is close to parallel to the surface
+               // or due to precision issues, which are currently biting me
+               if (ray_low < max_height) {
+                       // possibly, so check individual surfaces
+                       // the triangles used for rendering are (x,z), (x+1,z), (x,z+1) and
+                       // (x+1,z),(x+1,z+1), (x,z+1), so height indices 012 and 132
+                       if (TriangleIntersection(
+                               ray,
+                               glm::vec3(float(cell.x + 0), height[0], float(cell.y + 0)),
+                               glm::vec3(float(cell.x + 1), height[1], float(cell.y + 0)),
+                               glm::vec3(float(cell.x + 0), height[2], float(cell.y + 1)),
+                               point
+                       )) {
+                               return true;
+                       }
+                       if (TriangleIntersection(
+                               ray,
+                               glm::vec3(float(cell.x + 1), height[1], float(cell.y + 0)),
+                               glm::vec3(float(cell.x + 1), height[3], float(cell.y + 1)),
+                               glm::vec3(float(cell.x + 0), height[2], float(cell.y + 1)),
+                               point
+                       )) {
+                               return true;
+                       }
+                       // hmm, maybe I should check against planes and if true test if the XZ of the intersection points
+                       // lie within their corresponding half-square with some flexibility and somehow pick the right one
+                       //std::cout << "        ray got below max floor height at cell " << cell << " but did not intersect a triangle" << std::endl;
+               }
+               // okay, we're still above, advance to the next cell
+               if (x_near < z_near || std::isnan(z_near)) {
+                       cell.x += step.x;
+               } else {
+                       cell.y += step.y;
+               }
+       }
+       //std::cout << "        ray left grid at cell " << cell << std::endl;
+       // we left the grid, so no intersection
+       return false;
 }
 
 }