summaryrefslogtreecommitdiffstats
path: root/libdimension/bvst.c
diff options
context:
space:
mode:
Diffstat (limited to 'libdimension/bvst.c')
-rw-r--r--libdimension/bvst.c462
1 files changed, 462 insertions, 0 deletions
diff --git a/libdimension/bvst.c b/libdimension/bvst.c
new file mode 100644
index 0000000..a73b4b2
--- /dev/null
+++ b/libdimension/bvst.c
@@ -0,0 +1,462 @@
+/*************************************************************************
+ * Copyright (C) 2009 Tavian Barnes <tavianator@gmail.com> *
+ * *
+ * This file is part of The Dimension Library. *
+ * *
+ * The Dimension Library is free software; you can redistribute it and/ *
+ * or modify it under the terms of the GNU Lesser General Public License *
+ * as published by the Free Software Foundation; either version 3 of the *
+ * License, or (at your option) any later version. *
+ * *
+ * The Dimension Library is distributed in the hope that it will be *
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty *
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+ * Lesser General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Lesser General Public *
+ * License along with this program. If not, see *
+ * <http://www.gnu.org/licenses/>. *
+ *************************************************************************/
+
+#include "dimension_impl.h"
+#include <stdlib.h>
+
+static dmnsn_bvst_node *dmnsn_new_bvst_node();
+static void dmnsn_delete_bvst_node(dmnsn_bvst_node *node);
+
+/* Return an empty tree */
+dmnsn_bvst *
+dmnsn_new_bvst()
+{
+ dmnsn_bvst *tree = malloc(sizeof(dmnsn_bvst));
+ if (tree) {
+ tree->root = NULL;
+ } else {
+ dmnsn_error(DMNSN_SEVERITY_HIGH, "kD splay tree allocation failed.");
+ }
+ return tree;
+}
+
+/* Recursively copy the nodes of a kD splay tree */
+static dmnsn_bvst_node *
+dmnsn_bvst_copy_recursive(dmnsn_bvst_node *root)
+{
+ dmnsn_bvst_node *node = dmnsn_new_bvst_node();
+ *node = *root;
+ if (node->contains) {
+ node->contains = dmnsn_bvst_copy_recursive(node->contains);
+ node->contains->parent = node;
+ }
+ if (node->container) {
+ node->container = dmnsn_bvst_copy_recursive(node->container);
+ node->container->parent = node;
+ }
+ return node;
+}
+
+/* Copy a kD splay tree */
+dmnsn_bvst *
+dmnsn_bvst_copy(dmnsn_bvst *tree)
+{
+ dmnsn_bvst *copy = dmnsn_new_bvst();
+ if (tree->root)
+ copy->root = dmnsn_bvst_copy_recursive(tree->root);
+ else
+ copy->root = NULL;
+ return copy;
+}
+
+/* Recursively free a kD splay tree */
+void
+dmnsn_delete_bvst_recursive(dmnsn_bvst_node *node)
+{
+ if (node) {
+ dmnsn_delete_bvst_recursive(node->contains);
+ dmnsn_delete_bvst_recursive(node->container);
+ dmnsn_delete_bvst_node(node);
+ }
+}
+
+/* Free a kD splay tree */
+void
+dmnsn_delete_bvst(dmnsn_bvst *tree)
+{
+ if (tree) {
+ dmnsn_delete_bvst_recursive(tree->root);
+ free(tree);
+ }
+}
+
+/* Return whether node1 contains node2 */
+static int dmnsn_box_contains(dmnsn_vector p,
+ dmnsn_vector min, dmnsn_vector max);
+/* Expand node to contain the bounding box from min to max */
+static void dmnsn_bvst_node_swallow(dmnsn_bvst_node *node,
+ dmnsn_vector min, dmnsn_vector max);
+
+/* Insert an object into the tree */
+void
+dmnsn_bvst_insert(dmnsn_bvst *tree, dmnsn_object *object)
+{
+ dmnsn_bvst_node *node = dmnsn_new_bvst_node(), *parent = tree->root;
+
+ /* Store the inverse of the transformation matrix */
+ object->trans_inv = dmnsn_matrix_inverse(object->trans);
+
+ node->contains = NULL;
+ node->container = NULL;
+ node->parent = NULL;
+ node->object = object;
+
+ /* Calculate the new bounding box by finding the minimum coordinate of the
+ transformed corners of the object's original bounding box */
+
+ node->min = dmnsn_matrix_vector_mul(object->trans, object->min);
+ node->max = node->min;
+
+ dmnsn_vector corner;
+ corner = dmnsn_new_vector(object->min.x, object->min.y, object->max.z);
+ corner = dmnsn_matrix_vector_mul(object->trans, corner);
+ dmnsn_bvst_node_swallow(node, corner, corner);
+
+ corner = dmnsn_new_vector(object->min.x, object->max.y, object->min.z);
+ corner = dmnsn_matrix_vector_mul(object->trans, corner);
+ dmnsn_bvst_node_swallow(node, corner, corner);
+
+ corner = dmnsn_new_vector(object->min.x, object->max.y, object->max.z);
+ corner = dmnsn_matrix_vector_mul(object->trans, corner);
+ dmnsn_bvst_node_swallow(node, corner, corner);
+
+ corner = dmnsn_new_vector(object->max.x, object->min.y, object->min.z);
+ corner = dmnsn_matrix_vector_mul(object->trans, corner);
+ dmnsn_bvst_node_swallow(node, corner, corner);
+
+ corner = dmnsn_new_vector(object->max.x, object->min.y, object->max.z);
+ corner = dmnsn_matrix_vector_mul(object->trans, corner);
+ dmnsn_bvst_node_swallow(node, corner, corner);
+
+ corner = dmnsn_new_vector(object->max.x, object->max.y, object->min.z);
+ corner = dmnsn_matrix_vector_mul(object->trans, corner);
+ dmnsn_bvst_node_swallow(node, corner, corner);
+
+ corner = dmnsn_new_vector(object->max.x, object->max.y, object->max.z);
+ corner = dmnsn_matrix_vector_mul(object->trans, corner);
+ dmnsn_bvst_node_swallow(node, corner, corner);
+
+ /* Now insert the node */
+
+ while (parent) {
+ if (dmnsn_box_contains(node->min, parent->min, parent->max)
+ && dmnsn_box_contains(node->max, parent->min, parent->max)) {
+ /* parent fully contains node */
+ if (parent->contains)
+ parent = parent->contains;
+ else {
+ /* We found our parent; insert node into the tree */
+ parent->contains = node;
+ node->parent = parent;
+ break;
+ }
+ } else {
+ /* Expand node's bounding box to fully contain parent's if it doesn't
+ already */
+ dmnsn_bvst_node_swallow(node, parent->min, parent->max);
+ /* node now fully contains parent */
+ if (parent->container)
+ parent = parent->container;
+ else {
+ /* We found our parent; insert node into the tree */
+ parent->container = node;
+ node->parent = parent;
+ break;
+ }
+ }
+ }
+
+ dmnsn_bvst_splay(tree, node);
+}
+
+/* Return whether p is within the axis-aligned box with corners min and max */
+static int
+dmnsn_box_contains(dmnsn_vector p, dmnsn_vector min, dmnsn_vector max)
+{
+ return (p.x >= min.x && p.y >= min.y && p.z >= min.z)
+ && (p.x <= max.x && p.y <= max.y && p.z <= max.z);
+}
+
+/* Expand node to contain the bounding box from min to max */
+static void
+dmnsn_bvst_node_swallow(dmnsn_bvst_node *node,
+ dmnsn_vector min, dmnsn_vector max)
+{
+ if (node->min.x > min.x) node->min.x = min.x;
+ if (node->min.y > min.y) node->min.y = min.y;
+ if (node->min.z > min.z) node->min.z = min.z;
+
+ if (node->max.x < max.x) node->max.x = max.x;
+ if (node->max.y < max.y) node->max.y = max.y;
+ if (node->max.z < max.z) node->max.z = max.z;
+}
+
+/* Tree rotations */
+static void dmnsn_bvst_rotate(dmnsn_bvst_node *node);
+
+/* Splay a node: move it to the root via tree rotations */
+void
+dmnsn_bvst_splay(dmnsn_bvst *tree, dmnsn_bvst_node *node)
+{
+ while (node->parent) {
+ if (!node->parent->parent) {
+ /* Zig step - we are a child of the root node */
+ dmnsn_bvst_rotate(node);
+ break;
+ } else if ((node == node->parent->contains
+ && node->parent == node->parent->parent->contains)
+ || (node == node->parent->container
+ && node->parent == node->parent->parent->container)) {
+ /* Zig-zig step - we are a child on the same side as our parent */
+ dmnsn_bvst_rotate(node->parent);
+ dmnsn_bvst_rotate(node);
+ } else {
+ /* Zig-zag step - we are a child on a different side than our parent is */
+ dmnsn_bvst_rotate(node);
+ dmnsn_bvst_rotate(node);
+ }
+ }
+ tree->root = node;
+}
+
+/* Rotate a tree on the edge connecting node and node->parent */
+static void
+dmnsn_bvst_rotate(dmnsn_bvst_node *node)
+{
+ dmnsn_bvst_node *P, *Q, *B;
+ if (node == node->parent->contains) {
+ /* We are a left child; perform a right rotation:
+ *
+ * Q P
+ * / \ / \
+ * P C ---> A Q
+ * / \ / \
+ * A B B C
+ */
+ Q = node->parent;
+ P = node;
+ /* A = node->contains; */
+ B = node->container;
+ /* C = node->parent->container; */
+
+ /* First fix up the parents */
+ if (Q->parent) {
+ if (Q->parent->contains == Q)
+ Q->parent->contains = P;
+ else
+ Q->parent->container = P;
+ }
+ P->parent = Q->parent;
+ Q->parent = P;
+ if (B) B->parent = Q;
+
+ /* Then the children */
+ P->container = Q;
+ Q->contains = B;
+ } else {
+ /* We are a right child; perform a left rotation:
+ *
+ * P Q
+ * / \ / \
+ * A Q ---> P C
+ * / \ / \
+ * B C A B
+ */
+ P = node->parent;
+ Q = node;
+ /* A = node->parent->contains; */
+ B = node->contains;
+ /* C = node->container; */
+
+ /* First fix up the parents */
+ if (P->parent) {
+ if (P->parent->contains == P)
+ P->parent->contains = Q;
+ else
+ P->parent->container = Q;
+ }
+ Q->parent = P->parent;
+ P->parent = Q;
+ if (B) B->parent = P;
+
+ /* Then the children */
+ Q->contains = P;
+ P->container = B;
+ }
+}
+
+typedef struct {
+ dmnsn_bvst_node *node;
+ dmnsn_intersection *intersection;
+} dmnsn_bvst_search_result;
+
+static dmnsn_bvst_search_result
+dmnsn_bvst_search_recursive(dmnsn_bvst_node *node, dmnsn_line ray, double t);
+
+dmnsn_intersection *
+dmnsn_bvst_search(dmnsn_bvst *tree, dmnsn_line ray)
+{
+ dmnsn_bvst_search_result result
+ = dmnsn_bvst_search_recursive(tree->root, ray, -1.0);
+
+ if (result.node)
+ dmnsn_bvst_splay(tree, result.node);
+
+ return result.intersection;
+}
+
+static int dmnsn_ray_box_intersection(dmnsn_line ray, dmnsn_vector min,
+ dmnsn_vector max, double t);
+
+static dmnsn_bvst_search_result
+dmnsn_bvst_search_recursive(dmnsn_bvst_node *node, dmnsn_line ray, double t)
+{
+ dmnsn_line ray_trans;
+ dmnsn_bvst_search_result result = { NULL, NULL }, result_temp;
+
+ if (!node)
+ return result;
+
+ /* Go down the right subtree first because the closest object is more likely
+ to lie in the larger bounding boxes */
+ result_temp = dmnsn_bvst_search_recursive(node->container, ray, t);
+ if (result_temp.node && (t < 0.0 || result_temp.intersection->t < t)) {
+ result = result_temp;
+ t = result.intersection->t;
+ } else {
+ dmnsn_delete_intersection(result_temp.intersection);
+ }
+
+ if (dmnsn_box_contains(ray.x0, node->min, node->max)
+ || dmnsn_ray_box_intersection(ray, node->min, node->max, t))
+ {
+ /* Transform the ray according to the object */
+ ray_trans = dmnsn_matrix_line_mul(node->object->trans_inv, ray);
+
+ if (dmnsn_box_contains(ray_trans.x0, node->object->min, node->object->max)
+ || dmnsn_ray_box_intersection(ray_trans, node->object->min,
+ node->object->max, t))
+ {
+ result_temp.intersection =
+ (*node->object->intersection_fn)(node->object, ray_trans);
+
+ if (result_temp.intersection
+ && (t < 0.0 || result_temp.intersection->t < t)) {
+ dmnsn_delete_intersection(result.intersection);
+ result.node = node;
+ result.intersection = result_temp.intersection;
+ t = result.intersection->t;
+
+ /* Transform the intersection back to the observer's view */
+ result.intersection->ray = ray;
+ result.intersection->normal = dmnsn_vector_normalize(
+ dmnsn_vector_sub(
+ dmnsn_matrix_vector_mul(
+ node->object->trans,
+ result.intersection->normal
+ ),
+ dmnsn_matrix_vector_mul(
+ node->object->trans,
+ dmnsn_zero
+ )
+ )
+ );
+ } else {
+ dmnsn_delete_intersection(result_temp.intersection);
+ }
+ }
+
+ /* Go down the left subtree */
+ result_temp = dmnsn_bvst_search_recursive(node->contains, ray, t);
+ if (result_temp.node && (t < 0.0 || result_temp.intersection->t < t)) {
+ dmnsn_delete_intersection(result.intersection);
+ result = result_temp;
+ t = result.intersection->t;
+ } else {
+ dmnsn_delete_intersection(result_temp.intersection);
+ }
+ }
+
+ return result;
+}
+
+static int
+dmnsn_ray_box_intersection(dmnsn_line line, dmnsn_vector min, dmnsn_vector max,
+ double t)
+{
+ double t_temp;
+ dmnsn_vector p;
+
+ if (line.n.x != 0.0) {
+ /* x == min.x */
+ t_temp = (min.x - line.x0.x)/line.n.x;
+ p = dmnsn_line_point(line, t_temp);
+ if (p.y >= min.y && p.y <= max.y && p.z >= min.z && p.z <= max.z
+ && t_temp >= 0.0 && (t < 0.0 || t_temp < t))
+ return 1;
+
+ /* x == max.x */
+ t_temp = (max.x - line.x0.x)/line.n.x;
+ p = dmnsn_line_point(line, t_temp);
+ if (p.y >= min.y && p.y <= max.y && p.z >= min.z && p.z <= max.z
+ && t_temp >= 0.0 && (t < 0.0 || t_temp < t))
+ return 1;
+ }
+
+ if (line.n.y != 0.0) {
+ /* y == -1.0 */
+ t_temp = (min.y - line.x0.y)/line.n.y;
+ p = dmnsn_line_point(line, t_temp);
+ if (p.x >= min.x && p.x <= max.x && p.z >= min.z && p.z <= max.z
+ && t_temp >= 0.0 && (t < 0.0 || t_temp < t))
+ return 1;
+
+ /* y == 1.0 */
+ t_temp = (max.y - line.x0.y)/line.n.y;
+ p = dmnsn_line_point(line, t_temp);
+ if (p.x >= min.x && p.x <= max.x && p.z >= min.z && p.z <= max.z
+ && t_temp >= 0.0 && (t < 0.0 || t_temp < t))
+ return 1;
+ }
+
+ if (line.n.z != 0.0) {
+ /* z == -1.0 */
+ t_temp = (min.z - line.x0.z)/line.n.z;
+ p = dmnsn_line_point(line, t_temp);
+ if (p.x >= min.x && p.x <= max.x && p.y >= min.y && p.y <= max.y
+ && t_temp >= 0.0 && (t < 0.0 || t_temp < t))
+ return 1;
+
+ /* z == 1.0 */
+ t_temp = (max.z - line.x0.z)/line.n.z;
+ p = dmnsn_line_point(line, t_temp);
+ if (p.x >= min.x && p.x <= max.x && p.y >= min.y && p.y <= max.y
+ && t_temp >= 0.0 && (t < 0.0 || t_temp < t))
+ return 1;
+ }
+
+ return 0;
+}
+
+static dmnsn_bvst_node *
+dmnsn_new_bvst_node()
+{
+ dmnsn_bvst_node *node = malloc(sizeof(dmnsn_bvst_node));
+ if (!node) {
+ dmnsn_error(DMNSN_SEVERITY_HIGH, "kD splay tree node allocation failed.");
+ }
+ return node;
+}
+
+static void
+dmnsn_delete_bvst_node(dmnsn_bvst_node *node)
+{
+ free(node);
+}