Package ca.eandb.jmist.framework.accel

Source Code of ca.eandb.jmist.framework.accel.BoundingIntervalHierarchy$Clip

/**
* Java Modular Image Synthesis Toolkit (JMIST)
* Copyright (C) 2008-2013 Bradley W. Kimmel
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package ca.eandb.jmist.framework.accel;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;

import ca.eandb.jmist.framework.IntersectionRecorder;
import ca.eandb.jmist.framework.NearestIntersectionRecorder;
import ca.eandb.jmist.framework.SceneElement;
import ca.eandb.jmist.framework.scene.SceneElementDecorator;
import ca.eandb.jmist.math.Box3;
import ca.eandb.jmist.math.Interval;
import ca.eandb.jmist.math.MathUtil;
import ca.eandb.jmist.math.Ray3;
import ca.eandb.jmist.util.ArrayUtil;
import ca.eandb.util.UnexpectedException;

/**
* A decorator <code>SceneElement</code> that applies a bounded interval
* hierarchy (BIH) to the primitives of the underlying <code>SceneElement</code>.
*
* This is an implementation of the algorithm described in the following paper:
*
* <blockquote>
* C. Wachter, A. Keller,
* <a href="http://ainc.de/Research/BIH.pdf">Instant ray tracing: The bounded interval hierarchy</a>,
* In <em>Proceedings of the Eurgraphics Symposium on Rendering</em>
* pp. 139-149, 2006.
* </blockquote>
*
* @author Brad Kimmel
*/
public final class BoundingIntervalHierarchy extends SceneElementDecorator {

  /** Serialization version ID. */
  private static final long serialVersionUID = -5882424225852208674L;

  private transient int[] items;

  private transient NodeBuffer buffer;

  private transient int root;

  private transient Box3 boundingBox;

  private transient boolean ready = false;

  private final int maxItemsPerLeaf = 2;

  private final double tolerance = MathUtil.EPSILON;

  /**
   * @param inner
   */
  public BoundingIntervalHierarchy(SceneElement inner) {
    super(inner);
  }

  /**
   * @param inner
   * @throws IOException
   */
  public BoundingIntervalHierarchy(SceneElement inner, String filename) throws IOException {
    super(inner);

    File file = new File(filename);
    if (file.isFile()) {
      FileInputStream fs = new FileInputStream(file);
      try {
        restore(fs);
      } finally {
        fs.close();
      }
    } else {
      FileOutputStream fs = new FileOutputStream(file);
      try {
        save(fs);
        fs.flush();
      } finally {
        fs.close();
      }
    }
  }

  public void save(OutputStream out) throws IOException {
    ensureReady();

    ObjectOutputStream oos = new ObjectOutputStream(out);
    oos.writeObject(items);
    oos.writeInt(root);
    oos.writeObject(boundingBox);
    oos.writeInt(buffer.next);

    byte[] buf = buffer.buf.array();
    oos.write(buf, 0, buffer.next);

    oos.flush();

    System.out.printf("Wrote %d bytes from backing array of size %d.", buffer.next, buf.length);
    System.out.println();
  }

  public void restore(InputStream in) throws IOException {
    ObjectInputStream ois = new ObjectInputStream(in);

    try {
      items = (int[]) ois.readObject();
      root = ois.readInt();
      boundingBox = (Box3) ois.readObject();

      buffer = new NodeBuffer();
      int size = ois.readInt();
      byte[] buf = new byte[size];
      int pos = 0;

      while (pos < size) {
        int read = ois.read(buf, pos, size - pos);
        if (read <= 0) {
          throw new IOException(String.format("Failed to read node buffer, only read %d of %d bytes (read=%d).", pos, size, read));
        }
        pos += read;
      }

      buffer.buf = ByteBuffer.wrap(buf);
      buffer.next = size;

      ready = true;
    } catch (ClassNotFoundException e) {
      throw new UnexpectedException(e);
    }

    System.out.println("Restored BIH from file.");
  }


  public void dump() {
    dump(root, 0);
  }

  private void dump(int node, int depth) {
    int type = buffer.getType(node);
    if (type == NodeBuffer.TYPE_LEAF) {
      int start = buffer.getStart(node);
      int end = buffer.getEnd(node);
      indent(depth);
      System.out.printf("LEAF(%d,%d):", start, end);
      for (int i = start; i < end; i++) {
        System.out.printf(" %d", items[i]);
      }
      System.out.println();
    } else {
      int axis = 'x' + type;
      double left = buffer.getLeftPlane(node);
      double right = buffer.getRightPlane(node);
      indent(depth);
      System.out.printf("INTERNAL: AXIS=%c, CLIP=(%f, %f)\n", axis, left, right);
      int leftChild = buffer.getLeftChild(node);
      if (leftChild >= 0) {
        indent(depth);
        System.out.println("{L");
        dump(leftChild, depth + 1);
        indent(depth);
        System.out.println("}");
      }
      int rightChild = buffer.getRightChild(node);
      if (rightChild >= 0) {
        indent(depth);
        System.out.println("{R");
        dump(rightChild, depth + 1);
        indent(depth);
        System.out.println("}");
      }
    }
  }

  private void indent(int depth) {
    for (int i = 0; i < depth; i++) {
      System.out.print("  ");
    }
  }

  private void ensureReady() {
    if (!ready) {
      build();
    }
  }

  private synchronized void build() {
    if (!ready) {
      buffer = new NodeBuffer();
      items = ArrayUtil.range(0, super.getNumPrimitives() - 1);
      boundingBox = boundingBox();
      Bound bound = new Bound(boundingBox);
      Clip clip = new Clip();
      root = buffer.allocateInternal();
      build(root, bound, 0, items.length, clip);
      ready = true;
    }
  }

  private void build(int offset, Bound bound, int start, int end, Clip clip) {
    assert(end > start);

    double lenx = bound.maxx - bound.minx;
    double leny = bound.maxy - bound.miny;
    double lenz = bound.maxz - bound.minz;
    double maxlen = Math.max(Math.max(lenx, leny), lenz);
    int axis;
    double plane;
    if (lenx > leny && lenx > lenz) {
      axis = 0;
      plane = 0.5 * (bound.minx + bound.maxx);
    } else if (leny > lenz) {
      axis = 1;
      plane = 0.5 * (bound.miny + bound.maxy);
    } else {
      axis = 2;
      plane = 0.5 * (bound.minz + bound.maxz);
    }
    int split = split(axis, plane, start, end, clip);
    int left = split - start;
    int right = end - split;

    int leftChild = (left > maxItemsPerLeaf && maxlen >= tolerance) ? buffer.allocateInternal() : (left > 0) ? buffer.allocateLeaf() : -1;
    int rightChild = (right > maxItemsPerLeaf && maxlen >= tolerance) ? buffer.allocateInternal() : (right > 0) ? buffer.allocateLeaf() : -1;

    assert(offset >= 0);
    int firstChild = (left > 0) ? leftChild : rightChild;

    buffer.writeInternal(offset, axis, clip, firstChild);

    // add new node here
    if (left > maxItemsPerLeaf && maxlen >= tolerance) {
      double temp = bound.setMax(axis, plane);
      build(leftChild, bound, start, split, clip);
      bound.setMax(axis, temp);
    } else if (left > 0) {
      assert(leftChild >= 0);
      if (start == 1) {
        start = 1;
      }
      buffer.writeLeaf(leftChild, start, split);
    }
    if (right > maxItemsPerLeaf && maxlen >= tolerance) {
      double temp = bound.setMin(axis, plane);
      build(rightChild, bound, split, end, clip);
      bound.setMin(axis, temp);
    } else if (right > 0) {
      assert(rightChild >= 0);
      if (split == 1) {
        split = 1;
      }
      buffer.writeLeaf(rightChild, split, end);
    }
  }

  private static class Bound {
    public double minx;
    public double maxx;
    public double miny;
    public double maxy;
    public double minz;
    public double maxz;

    public Bound(Box3 box) {
      minx = box.minimumX();
      miny = box.minimumY();
      minz = box.minimumZ();
      maxx = box.maximumX();
      maxy = box.maximumY();
      maxz = box.maximumZ();
    }

    public double setMin(int axis, double value) {
      double temp;
      switch (axis) {
      case 0:
        temp = minx;
        minx = value;
        return temp;
      case 1:
        temp = miny;
        miny = value;
        return temp;
      case 2:
        temp = minz;
        minz = value;
        return temp;
      }
      throw new IllegalArgumentException();
    }

    public double setMax(int axis, double value) {
      double temp;
      switch (axis) {
      case 0:
        temp = maxx;
        maxx = value;
        return temp;
      case 1:
        temp = maxy;
        maxy = value;
        return temp;
      case 2:
        temp = maxz;
        maxz = value;
        return temp;
      }
      throw new IllegalArgumentException();
    }

  }

  private static class Clip {

    public double left;
    public double right;

    public Clip() {
      reset();
    }

    public void reset() {
      left = Double.NEGATIVE_INFINITY;
      right = Double.POSITIVE_INFINITY;
    }

  }

  private int split(int axis, double plane, int start, int end, Clip clip) {
    double min, max, mid;
    int split = start;
    clip.reset();
    for (int i = start; i < end; i++) {
      Box3 bound = getBoundingBox(items[i]);
      min = bound.minimum(axis);
      max = bound.maximum(axis);
      mid = 0.5 * (min + max);
      if (mid < plane) {
        if (max > clip.left) {
          clip.left = max;
        }
        if (i > split) {
          ArrayUtil.swap(items, split, i);
        }
        split++;
      } else {
        if (min < clip.right) {
          clip.right = min;
        }
      }
    }
    return split;
  }

  private static final class NodeBuffer {

    private static final int SIZE_INTERNAL = 12;

    private static final int SIZE_LEAF = 8;

    public static final int TYPE_LEAF = 3;

    private ByteBuffer buf = ByteBuffer.allocate(16384);

    private int next = 0;

    public void writeInternal(int offset, int axis, Clip clip, int firstChild) {
      assert((firstChild & 0x3) == 0);
      buf.position(offset);
      buf.putInt(firstChild | axis);
      float left = (float) clip.left;
      float right = (float) clip.right;
      if (left < clip.left) {
        left = MathUtil.nextUp(left);
      }
      if (right > clip.right) {
        right = MathUtil.nextDown(right);
      }
      buf.putFloat(left);
      buf.putFloat(right);
    }

    public void writeLeaf(int offset, int start, int end) {
      buf.position(offset);
      buf.putInt((start << 2) | 3);
      buf.putInt(end);
    }

    public int allocateInternal() {
      return allocate(SIZE_INTERNAL);
    }

    public int allocateLeaf() {
      return allocate(SIZE_LEAF);
    }

    private int allocate(int size) {
      int result = next;
      next += size;
      if (next > buf.capacity()) {
        ByteBuffer newBuf = ByteBuffer.allocate(2 * buf.capacity());
        buf.clear();
        newBuf.put(buf);
        buf = newBuf;
      }
      return result;
    }

    public int getStart(int offset) {
      return buf.getInt(offset) >> 2;
    }

    public int getEnd(int offset) {
      return buf.getInt(offset + 4);
    }

    public int getType(int offset) {
      return buf.getInt(offset) & 0x3;
    }

    public boolean isLeaf(int offset) {
      return getType(offset) == TYPE_LEAF;
    }

    public int getNext(int offset) {
      if (isLeaf(offset)) {
        return offset + SIZE_LEAF;
      } else {
        return offset + SIZE_INTERNAL;
      }
    }

    public int getLeftChild(int offset) {
      return Double.isInfinite(getLeftPlane(offset)) ? -1 : getFirstChild(offset);
    }

    public int getRightChild(int offset) {
      if (Double.isInfinite(getRightPlane(offset))) {
        return -1;
      } else if (Double.isInfinite(getLeftPlane(offset))) {
        return getFirstChild(offset);
      } else {
        return getNext(getFirstChild(offset));
      }
    }

    public int getFirstChild(int offset) {
      return buf.getInt(offset) & ~0x3;
    }

    public double getLeftPlane(int offset) {
      return (double) buf.getFloat(offset + 4);
    }

    public double getRightPlane(int offset) {
      return (double) buf.getFloat(offset + 8);
    }

  }

  /* (non-Javadoc)
   * @see ca.eandb.jmist.framework.scene.SceneElementDecorator#intersect(ca.eandb.jmist.math.Ray3, ca.eandb.jmist.framework.IntersectionRecorder)
   */
  @Override
  public void intersect(Ray3 ray, IntersectionRecorder recorder) {
    ensureReady();
    Interval I = boundingBox.intersect(ray).intersect(recorder.interval());
    if (!I.isEmpty()) {
      intersectNode(root, I.minimum(), I.maximum(), ray, recorder);
    }
  }

  private void intersectNode(int node, double near, double far, Ray3 ray,
      IntersectionRecorder recorder) {

    if (far < near) {
      return;
    }

    int type = buffer.getType(node);

    if (type == NodeBuffer.TYPE_LEAF) {
      int start = buffer.getStart(node);
      int end = buffer.getEnd(node);
      for (int i = start; i < end; i++) {
        if (i == 1) {
          i = 1;
        }
        super.intersect(items[i], ray, recorder);
      }
    } else {
      double p = ray.origin().get(type);
      double v = ray.direction().get(type);

      double lp = buffer.getLeftPlane(node);
      double rp = buffer.getRightPlane(node);

      double ld = (lp - p) / v;
      double rd = (rp - p) / v;

      if (v > 0.0) { // left to right

        if (near < ld) {
          int child = buffer.getLeftChild(node);
          if (child >= 0) {
            intersectNode(child, near, Math.min(ld, far), ray, recorder);
            far = Math.min(far, recorder.interval().maximum());
          }
        }

        if (rd < far) {
          int child = buffer.getRightChild(node);
          if (child >= 0) {
            intersectNode(child, Math.max(near, rd), far, ray, recorder);
          }
        }

      } else { // right to left

        if (near < rd) {
          int child = buffer.getRightChild(node);
          if (child >= 0) {
            intersectNode(child, near, Math.min(rd, far), ray, recorder);
            far = Math.min(far, recorder.interval().maximum());
          }
        }

        if (ld < far) {
          int child = buffer.getLeftChild(node);
          if (child >= 0) {
            intersectNode(child, Math.max(near, ld), far, ray, recorder);
          }
        }

      }
    }

  }

  private boolean nodeVisibility(int node, Ray3 ray) {

    if (ray.limit() < 0.0) {
      return true;
    }

    int type = buffer.getType(node);

    if (type == NodeBuffer.TYPE_LEAF) {
      int start = buffer.getStart(node);
      int end = buffer.getEnd(node);
      for (int i = start; i < end; i++) {
        if (i == 1) {
          i = 1;
        }
        if (!visibility(items[i], ray)) {
          return false;
        }
      }
      return true;
    } else {
      double p = ray.origin().get(type);
      double v = ray.direction().get(type);

      double lp = buffer.getLeftPlane(node);
      double rp = buffer.getRightPlane(node);

      double ld = (lp - p) / v;
      double rd = (rp - p) / v;

      if (v > 0.0) { // left to right

        if (0.0 < ld) {
          int child = buffer.getLeftChild(node);
          if (child >= 0) {
            ray = new Ray3(
                ray.origin(),
                ray.direction(),
                Math.min(ld, ray.limit()));
            if (!nodeVisibility(child, ray)) {
              return false;
            }
          }
        }

        if (rd < ray.limit()) {
          int child = buffer.getRightChild(node);
          if (child >= 0) {
            if (rd > 0.0) {
              ray = ray.advance(rd);
            }
            if (!nodeVisibility(child, ray)) {
              return false;
            }
          }
        }

      } else { // right to left

        if (0.0 < rd) {
          int child = buffer.getRightChild(node);
          if (child >= 0) {
            ray = new Ray3(
                ray.origin(),
                ray.direction(),
                Math.min(rd, ray.limit()));
            if (!nodeVisibility(child, ray)) {
              return false;
            }
          }
        }

        if (ld < ray.limit()) {
          int child = buffer.getLeftChild(node);
          if (child >= 0) {
            if (ld > 0.0) {
              ray = ray.advance(ld);
            }
            if (!nodeVisibility(child, ray)) {
              return false;
            }
          }
        }

      }
    }

    return true;

  }

  /* (non-Javadoc)
   * @see ca.eandb.jmist.framework.scene.SceneElementDecorator#visibility(ca.eandb.jmist.math.Ray3)
   */
  @Override
  public boolean visibility(Ray3 ray) {
    NearestIntersectionRecorder recorder = new NearestIntersectionRecorder(new Interval(0.0, ray.limit()));
    intersect(ray, recorder);
    return recorder.isEmpty();
  }


}
TOP

Related Classes of ca.eandb.jmist.framework.accel.BoundingIntervalHierarchy$Clip

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.