All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.llbit.math.BVH Maven / Gradle / Ivy

There is a newer version: 1.4.5
Show newest version
/* Copyright (c) 2014 Jesper Öqvist 
 *
 * This file is part of Chunky.
 *
 * Chunky is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Chunky 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 General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with Chunky.  If not, see .
 */
package se.llbit.math;

import se.llbit.math.primitive.MutableAABB;
import se.llbit.math.primitive.Primitive;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Stack;

/**
 * Bounding Volume Hierarchy based on AABBs.
 *
 * @author Jesper Öqvist 
 */
public class BVH {

  /**
   * Indicates whether to use SAH for construction.
   */
  private static final Method METHOD = Method.MIDPOINT;


  private enum Method {
    MIDPOINT,
    SAH,
    SAH_MA,
  }


  static abstract class Node {
    protected final AABB bb;
    protected final Primitive[] primitives;

    /**
     * Create a new BVH node.
     */
    public Node(Primitive[] primitives) {
      this.bb = bb(primitives);
      this.primitives = primitives;
    }

    /**
     * Create new BVH node with specific bounds.
     */
    public Node(AABB bb, Primitive[] primitives) {
      this.bb = bb;
      this.primitives = primitives;
    }

    abstract public boolean closestIntersection(Ray ray);

    abstract public boolean anyIntersection(Ray ray);

    abstract public int size();
  }


  static class Group extends Node {
    protected final Node child1;
    protected final Node child2;
    private final int numPrimitives;

    /**
     * Create a new BVH node.
     */
    public Group(Node child1, Node child2) {
      super(child1.bb.expand(child2.bb), new Primitive[0]);
      this.numPrimitives = child1.size() + child2.size();
      this.child1 = child1;
      this.child2 = child2;
    }

    @Override public boolean closestIntersection(Ray ray) {
      double t1 = Double.POSITIVE_INFINITY;
      double t2 = Double.POSITIVE_INFINITY;
      if (child1.bb.inside(ray.o)) {
        t1 = 0;
      } else if (child1.bb.quickIntersect(ray)) {
        t1 = ray.tNext;
      }
      if (child2.bb.inside(ray.o)) {
        t2 = 0;
      } else if (child2.bb.quickIntersect(ray)) {
        t2 = ray.tNext;
      }

      boolean hit;
      if (t1 < t2) {
        hit = child1.closestIntersection(ray);
        hit = (t2 < ray.t && child2.closestIntersection(ray)) || hit;
      } else if (t2 < t1) {
        hit = child2.closestIntersection(ray);
        hit = (t1 < ray.t && child1.closestIntersection(ray)) || hit;
      } else {
        hit = (t1 != Double.POSITIVE_INFINITY) && child1.closestIntersection(ray);
        hit = ((t2 < ray.t) && child2.closestIntersection(ray)) || hit;
      }
      return hit;
    }

    @Override public boolean anyIntersection(Ray ray) {
      return (child1.bb.hitTest(ray) && child1.anyIntersection(ray)) || (child2.bb.hitTest(ray)
          && child2.anyIntersection(ray));
    }

    @Override public int size() {
      return numPrimitives;
    }
  }


  static class Leaf extends Node {

    public Leaf(Primitive[] primitives) {
      super(primitives);
    }

    @Override public boolean closestIntersection(Ray ray) {
      boolean hit = false;
      for (Primitive primitive : primitives) {
        hit = primitive.intersect(ray) || hit;
      }
      return hit;
    }

    @Override public boolean anyIntersection(Ray ray) {
      for (Primitive primitive : primitives) {
        if (primitive.intersect(ray)) {
          return true;
        }
      }
      return false;
    }

    @Override public int size() {
      return primitives.length;
    }
  }


  private static final int SPLIT_LIMIT = 4;

  Node root;


  interface Selector {
    boolean select(AABB bounds, double split);
  }


  private final Comparator cmpX = (g1, g2) -> {
    AABB b1 = g1.bounds();
    AABB b2 = g2.bounds();
    double c1 = b1.xmin + (b1.xmax - b1.xmin) / 2;
    double c2 = b2.xmin + (b2.xmax - b2.xmin) / 2;
    return Double.compare(c1, c2);
  };
  private final Selector selectX = (bounds, split) -> {
    double centroid = bounds.xmin + (bounds.xmax - bounds.xmin) / 2;
    return centroid < split;
  };
  private final Comparator cmpY = (g1, g2) -> {
    AABB b1 = g1.bounds();
    AABB b2 = g2.bounds();
    double c1 = b1.ymin + (b1.ymax - b1.ymin) / 2;
    double c2 = b2.ymin + (b2.ymax - b2.ymin) / 2;
    return Double.compare(c1, c2);
  };
  private final Selector selectY = (bounds, split) -> {
    double centroid = bounds.ymin + (bounds.ymax - bounds.ymin) / 2;
    return centroid < split;
  };
  private final Comparator cmpZ = (g1, g2) -> {
    AABB b1 = g1.bounds();
    AABB b2 = g2.bounds();
    double c1 = b1.zmin + (b1.zmax - b1.zmin) / 2;
    double c2 = b2.zmin + (b2.zmax - b2.zmin) / 2;
    return Double.compare(c1, c2);
  };
  private final Selector selectZ = (bounds, split) -> {
    double centroid = bounds.zmin + (bounds.zmax - bounds.zmin) / 2;
    return centroid < split;
  };

  /**
   * Construct a new BVH containing the given primitives.
   */
  public BVH(List primitives) {
    switch (METHOD) {
      case MIDPOINT:
        root = constructMidpointSplit(primitives.toArray(new Primitive[primitives.size()]));
        break;
      case SAH:
        root = constructSAH(primitives.toArray(new Primitive[primitives.size()]));
        break;
      case SAH_MA:
        root = constructSAH_MA(primitives.toArray(new Primitive[primitives.size()]));
        break;
    }
  }

  enum Action {
    PUSH,
    MERGE,
  }

  /**
   * Simple BVH construction using splitting by major axis.
   *
   * @return root node of constructed BVH
   */
  private Node constructMidpointSplit(Primitive[] primitives) {
    Stack nodes = new Stack<>();
    Stack actions = new Stack<>();
    Stack chunks = new Stack<>();
    chunks.push(primitives);
    actions.push(Action.PUSH);
    while (!actions.isEmpty()) {
      Action action = actions.pop();
      if (action == Action.MERGE) {
        nodes.push(new Group(nodes.pop(), nodes.pop()));
      } else {
        Primitive[] chunk = chunks.pop();
        if (chunk.length < SPLIT_LIMIT) {
          nodes.push(new Leaf(chunk));
        } else {
          splitMidpointMajorAxis(chunk, actions, chunks);
        }
      }
    }
    return nodes.pop();
  }

  /**
   * Split a chunk on the major axis.
   */
  private void splitMidpointMajorAxis(Primitive[] chunk, Stack actions,
      Stack chunks) {
    AABB bb = bb(chunk);
    double xl = bb.xmax - bb.xmin;
    double yl = bb.ymax - bb.ymin;
    double zl = bb.zmax - bb.zmin;
    double splitPos;
    Selector selector;
    if (xl >= yl && xl >= zl) {
      splitPos = bb.xmin + (bb.xmax - bb.xmin) / 2;
      selector = selectX;
      Arrays.sort(chunk, cmpX);
    } else if (yl >= xl && yl >= zl) {
      splitPos = bb.ymin + (bb.ymax - bb.ymin) / 2;
      selector = selectY;
      Arrays.sort(chunk, cmpY);
    } else {
      splitPos = bb.zmin + (bb.zmax - bb.zmin) / 2;
      selector = selectZ;
      Arrays.sort(chunk, cmpZ);
    }

    int split;
    int end = chunk.length;
    for (split = 1; split < end; ++split) {
      if (!selector.select(chunk[split].bounds(), splitPos)) {
        break;
      }
    }

    actions.push(Action.MERGE);
    Primitive[] cons = new Primitive[split];
    System.arraycopy(chunk, 0, cons, 0, split);
    chunks.push(cons);
    actions.push(Action.PUSH);

    cons = new Primitive[end - split];
    System.arraycopy(chunk, split, cons, 0, end - split);
    chunks.push(cons);
    actions.push(Action.PUSH);
  }

  /**
   * Construct a BVH using Surface Area Heuristic (SAH).
   *
   * @return root node of constructed BVH
   */
  private Node constructSAH(Primitive[] primitives) {
    Stack nodes = new Stack<>();
    Stack actions = new Stack<>();
    Stack chunks = new Stack<>();
    chunks.push(primitives);
    actions.push(Action.PUSH);
    while (!actions.isEmpty()) {
      Action action = actions.pop();
      if (action == Action.MERGE) {
        nodes.push(new Group(nodes.pop(), nodes.pop()));
      } else {
        Primitive[] chunk = chunks.pop();
        if (chunk.length < SPLIT_LIMIT) {
          nodes.push(new Leaf(chunk));
        } else {
          splitSAH(chunk, actions, chunks);
        }
      }
    }
    return nodes.pop();
  }

  /**
   * Construct a BVH using Surface Area Heuristic (SAH)
   *
   * @return root node of constructed BVH
   */
  private Node constructSAH_MA(Primitive[] primitives) {
    Stack nodes = new Stack<>();
    Stack actions = new Stack<>();
    Stack chunks = new Stack<>();
    chunks.push(primitives);
    actions.push(Action.PUSH);
    while (!actions.isEmpty()) {
      Action action = actions.pop();
      if (action == Action.MERGE) {
        nodes.push(new Group(nodes.pop(), nodes.pop()));
      } else {
        Primitive[] chunk = chunks.pop();
        if (chunk.length < SPLIT_LIMIT) {
          nodes.push(new Leaf(chunk));
        } else {
          splitSAH_MA(chunk, actions, chunks);
        }
      }
    }
    return nodes.pop();
  }

  /**
   * Split a chunk based on Surface Area Heuristic of all possible splits
   */
  private void splitSAH(Primitive[] chunk, Stack actions, Stack chunks) {
    MutableAABB bounds = new MutableAABB(0, 0, 0, 0, 0, 0);
    double cmin = Double.POSITIVE_INFINITY;
    int split = 0;
    int end = chunk.length;

    double[] sl = new double[end];
    double[] sr = new double[end];

    Comparator cmp = cmpX;
    Arrays.sort(chunk, cmpX);
    for (int i = 0; i < end - 1; ++i) {
      bounds.expand(chunk[i].bounds());
      sl[i] = bounds.surfaceArea();
    }
    bounds = new MutableAABB(0, 0, 0, 0, 0, 0);
    for (int i = end - 1; i > 0; --i) {
      bounds.expand(chunk[i].bounds());
      sr[i - 1] = bounds.surfaceArea();
    }
    for (int i = 0; i < end - 1; ++i) {
      double c = sl[i] * (i + 1) + sr[i] * (end - i - 1);
      if (c < cmin) {
        cmin = c;
        split = i;
      }
    }

    Arrays.sort(chunk, cmpY);
    for (int i = 0; i < end - 1; ++i) {
      bounds.expand(chunk[i].bounds());
      sl[i] = bounds.surfaceArea();
    }
    bounds = new MutableAABB(0, 0, 0, 0, 0, 0);
    for (int i = end - 1; i > 0; --i) {
      bounds.expand(chunk[i].bounds());
      sr[i - 1] = bounds.surfaceArea();
    }
    for (int i = 0; i < end - 1; ++i) {
      double c = sl[i] * (i + 1) + sr[i] * (end - i - 1);
      if (c < cmin) {
        cmin = c;
        split = i;
        cmp = cmpY;
      }
    }

    Arrays.sort(chunk, cmpZ);
    for (int i = 0; i < end - 1; ++i) {
      bounds.expand(chunk[i].bounds());
      sl[i] = bounds.surfaceArea();
    }
    bounds = new MutableAABB(0, 0, 0, 0, 0, 0);
    for (int i = end - 1; i > 0; --i) {
      bounds.expand(chunk[i].bounds());
      sr[i - 1] = bounds.surfaceArea();
    }
    for (int i = 0; i < end - 1; ++i) {
      double c = sl[i] * (i + 1) + sr[i] * (end - i - 1);
      if (c < cmin) {
        cmin = c;
        split = i;
        cmp = cmpZ;
      }
    }

    if (cmp != cmpZ) {
      Arrays.sort(chunk, cmp);
    }

    split += 1;

    actions.push(Action.MERGE);
    Primitive[] cons = new Primitive[split];
    System.arraycopy(chunk, 0, cons, 0, split);
    chunks.push(cons);
    actions.push(Action.PUSH);

    cons = new Primitive[end - split];
    System.arraycopy(chunk, split, cons, 0, end - split);
    chunks.push(cons);
    actions.push(Action.PUSH);
  }

  /**
   * Split a chunk based on Surface Area Heuristic of all possible splits.
   */
  private void splitSAH_MA(Primitive[] chunk, Stack actions, Stack chunks) {
    AABB bb = bb(chunk);
    double xl = bb.xmax - bb.xmin;
    double yl = bb.ymax - bb.ymin;
    double zl = bb.zmax - bb.zmin;
    Comparator cmp;
    if (xl >= yl && xl >= zl) {
      cmp = cmpX;
      Arrays.sort(chunk, cmpX);
    } else if (yl >= xl && yl >= zl) {
      cmp = cmpY;
    } else {
      cmp = cmpZ;
    }

    MutableAABB bounds = new MutableAABB(0, 0, 0, 0, 0, 0);
    double cmin = Double.POSITIVE_INFINITY;
    int split = 0;
    int end = chunk.length;

    double[] sl = new double[end];
    double[] sr = new double[end];

    Arrays.sort(chunk, cmp);
    for (int i = 0; i < end - 1; ++i) {
      bounds.expand(chunk[i].bounds());
      sl[i] = bounds.surfaceArea();
    }
    bounds = new MutableAABB(0, 0, 0, 0, 0, 0);
    for (int i = end - 1; i > 0; --i) {
      bounds.expand(chunk[i].bounds());
      sr[i - 1] = bounds.surfaceArea();
    }
    for (int i = 0; i < end - 1; ++i) {
      double c = sl[i] * (i + 1) + sr[i] * (end - i - 1);
      if (c < cmin) {
        cmin = c;
        split = i;
      }
    }

    split += 1;

    actions.push(Action.MERGE);
    Primitive[] cons = new Primitive[split];
    System.arraycopy(chunk, 0, cons, 0, split);
    chunks.push(cons);
    actions.push(Action.PUSH);

    cons = new Primitive[end - split];
    System.arraycopy(chunk, split, cons, 0, end - split);
    chunks.push(cons);
    actions.push(Action.PUSH);
  }

  private static AABB bb(Primitive[] primitives) {
    double xmin = Double.POSITIVE_INFINITY;
    double xmax = Double.NEGATIVE_INFINITY;
    double ymin = Double.POSITIVE_INFINITY;
    double ymax = Double.NEGATIVE_INFINITY;
    double zmin = Double.POSITIVE_INFINITY;
    double zmax = Double.NEGATIVE_INFINITY;

    for (Primitive primitive : primitives) {
      AABB bb = primitive.bounds();
      if (bb.xmin < xmin)
        xmin = bb.xmin;
      if (bb.xmax > xmax)
        xmax = bb.xmax;
      if (bb.ymin < ymin)
        ymin = bb.ymin;
      if (bb.ymax > ymax)
        ymax = bb.ymax;
      if (bb.zmin < zmin)
        zmin = bb.zmin;
      if (bb.zmax > zmax)
        zmax = bb.zmax;
    }
    return new AABB(xmin, xmax, ymin, ymax, zmin, zmax);
  }

  /**
   * Find closest intersection between the ray and any object in the BVH.
   *
   * @return {@code true} if there exists any intersection
   */
  public boolean closestIntersection(Ray ray) {
    return root.bb.hitTest(ray) && root.closestIntersection(ray);
  }

  /**
   * Find any intersection between the ray and any object in the BVH. For simple
   * intersection tests this method is quicker. The closest point search costs a
   * bit more.
   *
   * @return {@code true} if there exists any intersection
   */
  public boolean anyIntersection(Ray ray) {
    return root.bb.hitTest(ray) && root.anyIntersection(ray);
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy