Package se.llbit.math

Source Code of se.llbit.math.Octree$Node

/* Copyright (c) 2010-2012 Jesper Öqvist <jesper@llbit.se>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package se.llbit.math;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import org.apache.commons.math3.util.FastMath;

import se.llbit.chunky.model.TexturedBlockModel;
import se.llbit.chunky.renderer.scene.Scene;
import se.llbit.chunky.world.Block;

/**
* A simple voxel Octree.
*
* @author Jesper Öqvist (jesper@llbit.se)
*/
public class Octree {

  /**
   * An Octree node
   */
  public static final class Node {
    /**
     * The node type. Type is -1 if it's a non-leaf node.
     */
    public int type;

    /**
     * Child array
     */
    public Node[] children;

    /**
     * Create new octree leaf node with the given type
     * @param type
     */
    public Node(int type) {
      this.type = type;
    }

    /**
     * Subdivide this leaf node
     */
    public final void subdivide() {
      children = new Node[8];
      children[0] = new Node(type);
      children[1] = new Node(type);
      children[2] = new Node(type);
      children[3] = new Node(type);
      children[4] = new Node(type);
      children[5] = new Node(type);
      children[6] = new Node(type);
      children[7] = new Node(type);
      type = -1;
    }

    /**
     * Merge the leafs of this node and make this node a
     * leaf node.
     * @param newType
     */
    public final void merge(int newType) {
      type = newType;
      children = null;
    }

    /**
     * @return Calculated data size, in bytes, to store this node
     */
    public int dataSize() {
      if (type != -1) {
        return 1;
      } else {
        int total = 9;// type plus child indices
        for (Node child: children)
          total += child.dataSize();
        return total;
      }
    }

    /**
     * @param index
     * @param data
     * @return A dump of the octree data in this node
     */
    public int dumpData(int index, int[] data) {
      data[index++] = type;
      if (type == -1) {
        int childIndex = index;
        index += 8;
        for (int i = 0; i < 8; ++i) {
          data[childIndex+i] = index;
          index = children[i].dumpData(index, data);
        }
      }
      return index;
    }

    /**
     * Serialize this node
     * @param out
     * @throws IOException
     */
    public void store(DataOutputStream out) throws IOException {
      out.writeInt(type);
      if (type == -1) {
        for (int i = 0; i < 8; ++i) {
          children[i].store(out);
        }
      }
    }

    /**
     * Deserialize node
     * @param in
     * @throws IOException
     */
    public void load(DataInputStream in) throws IOException {
      type = in.readInt();
      if (type == -1) {
        children = new Node[8];
        for (int i = 0; i < 8; ++i) {
          children[i] = new Node(0);
          children[i].load(in);
        }
      }
    }
  }

  /**
   * Recursive depth of the octree
   */
  public final int depth;

  /**
   * Root node
   */
  public final Node root;

  private final Node[] parents;
  private final Node[] cache;
  private int cx = 0;
  private int cy = 0;
  private int cz = 0;
  private int cacheLevel;

  /**
   * Create a new Octree. The dimensions of the Octree
   * are 2^levels.
   * @param octreeDepth The number of levels in the Octree.
   */
  public Octree(int octreeDepth) {
    depth = octreeDepth;
    root = new Node(0);
    parents = new Node[depth];
    cache = new Node[depth+1];
    cache[depth] = root;
    cacheLevel = depth;
  }

  /**
   * Set the voxel type at the given coordinates.
   *
   * @param type The new voxel type to be set
   * @param x
   * @param y
   * @param z
   */
  public synchronized void set(int type, int x, int y, int z) {
    Node node = root;
    int parentLvl = depth-1;
    int level = parentLvl;
    for (int i = depth-1; i >= 0; --i) {
      level = i;
      parents[i] = node;

      if (node.type == type) {
        return;
      } else if (node.children == null) {
        node.subdivide();
        parentLvl = i;
      }

      int xbit = 1 & (x >> i);
      int ybit = 1 & (y >> i);
      int zbit = 1 & (z >> i);
      node = node.children[(xbit<<2) | (ybit<<1) | zbit];

    }
    node.type = type;

    // merge nodes where all children have been set to the same type
    for (int i = level; i <= parentLvl; ++i) {
      Node parent = parents[i];

      boolean allSame = true;
      for (Node child: parent.children) {
        if (child.type != node.type) {
          allSame = false;
          break;
        }
      }

      if (allSame) {
        parent.merge(node.type);
        cacheLevel = FastMath.max(i, cacheLevel);
      } else {
        break;
      }
    }

  }

  /**
   * @param x
   * @param y
   * @param z
   * @return The voxel type at the given coordinates
   */
  public synchronized int get(int x, int y, int z) {
    while (cacheLevel < depth && ((x >>> cacheLevel) != cx ||
          (y >>> cacheLevel) != cy || (z >>> cacheLevel) != cz))
      cacheLevel += 1;

    int type;
    while ((type = cache[cacheLevel].type) == -1) {
      cacheLevel -= 1;
      cx = x >>> cacheLevel;
      cy = y >>> cacheLevel;
      cz = z >>> cacheLevel;
      cache[cacheLevel] = cache[cacheLevel+1]
          .children[((cx&1)<<2) | ((cy&1)<<1) | (cz&1)];
    }
    return type;
  }

  /**
   * Create a data buffer containing the octree data
   * @return The data buffer representing the full octree data
   */
  public int[] toDataBuffer() {
    int size = 0;
    size = root.dataSize();
    int[] data = new int[size];
    root.dumpData(0, data);
    return data;
  }

  /**
   * Serialize this octree to a data output stream
   * @param out
   * @throws IOException
   */
  public void store(DataOutputStream out) throws IOException {
    out.writeInt(depth);
    root.store(out);
  }

  /**
   * Deserialize the octree from a data input stream
   * @param in
   * @return The deserialized octree
   * @throws IOException
   */
  public static Octree load(DataInputStream in) throws IOException {
    int treeDepth = in.readInt();
    Octree tree = new Octree(treeDepth);
    tree.root.load(in);
    return tree;
  }

  /**
   * Test whether the ray intersects any voxel before exiting the Octree.
   * @param scene
   * @param ray the ray
   * @return <code>true</code> if the ray intersects a voxel
   */
  public boolean intersect(Scene scene, Ray ray) {

    int level;
    Octree.Node node;
    boolean first = true;

    int lx, ly, lz;
    int x, y, z;
    int nx = 0, ny = 0, nz = 0;
    double tNear = Double.POSITIVE_INFINITY;
    double t;
    Vector3d d = ray.d;

    while (true) {

      // add small offset past the intersection to avoid
      // recursion to the same octree node!
      x = (int) QuickMath.floor(ray.x.x + ray.d.x * Ray.OFFSET);
      y = (int) QuickMath.floor(ray.x.y + ray.d.y * Ray.OFFSET);
      z = (int) QuickMath.floor(ray.x.z + ray.d.z * Ray.OFFSET);

      node = root;
      level = depth;
      lx = x >>> level;
      ly = y >>> level;
      lz = z >>> level;

      if (lx != 0 || ly != 0 || lz != 0) {

        // ray origin is outside octree!
        ray.currentMaterial = Block.AIR_ID;

        // only check octree intersection if this is the first iteration
        if (first) {
          // test if it is entering the octree
          t = -ray.x.x / d.x;
          if (t > Ray.EPSILON) {
            tNear = t;
            nx = 1;
            ny = nz = 0;
          }
          t = ((1<<level) - ray.x.x) / d.x;
          if (t < tNear && t > Ray.EPSILON) {
            tNear = t;
            nx = -1;
            ny = nz = 0;
          }
          t = -ray.x.y / d.y;
          if (t < tNear && t > Ray.EPSILON) {
            tNear = t;
            ny = 1;
            nx = nz = 0;
          }
          t = ((1<<level) - ray.x.y) / d.y;
          if (t < tNear && t > Ray.EPSILON) {
            tNear = t;
            ny = -1;
            nx = nz = 0;
          }
          t = -ray.x.z / d.z;
          if (t < tNear && t > Ray.EPSILON) {
            tNear = t;
            nz = 1;
            nx = ny = 0;
          }
          t = ((1<<level) - ray.x.z) / d.z;
          if (t < tNear && t > Ray.EPSILON) {
            tNear = t;
            nz = -1;
            nx = ny = 0;
          }

          if (tNear < Double.MAX_VALUE) {
            ray.x.scaleAdd(tNear, d, ray.x);
            ray.n.set(nx, ny, nz);
            ray.distance += tNear;
            tNear = Double.POSITIVE_INFINITY;
            continue;
          } else {
            return false;// outside of octree!
          }
        } else {
          return false;// outside of octree!
        }
      }

      first = false;

      while (node.type == -1) {
        level -= 1;
        lx = x >>> level;
        ly = y >>> level;
        lz = z >>> level;
        node = node.children[((lx&1)<<2) | ((ly&1)<<1) | (lz&1)];
      }

      // old octree visualization code
      /*double w = .1 * (1 + level);
      w*=w;
      if (ray.x.x < (lx<<level) + w && (ray.x.y < (ly<<level) + w || ray.x.y > ((ly+1)<<level) - w) ||
          ray.x.x < (lx<<level) + w && (ray.x.z < (lz<<level) + w || ray.x.z > ((lz+1)<<level) - w) ||
          ray.x.y < (ly<<level) + w && (ray.x.z < (lz<<level) + w || ray.x.z > ((lz+1)<<level) - w) ||
          ray.x.x > ((lx+1)<<level) - w && (ray.x.y < (ly<<level) + w || ray.x.y > ((ly+1)<<level) - w) ||
          ray.x.x > ((lx+1)<<level) - w && (ray.x.z < (lz<<level) + w || ray.x.z > ((lz+1)<<level) - w) ||
          ray.x.y > ((ly+1)<<level) - w && (ray.x.z < (lz<<level) + w || ray.x.z > ((lz+1)<<level) - w)) {
        ray.color.x = .5;
        ray.color.y = .5;
        ray.color.z = .5;
        ray.color.w = 1;
        ray.prevMaterial = Block.AIR.id;
        ray.currentMaterial = 0xFF;
        return true;
      }*/

      if (ray.currentMaterial == -1) {
        ray.prevMaterial = 0;
        ray.currentMaterial = node.type;
      }

      Block currentBlock = Block.get(node.type);
      Block prevBlock = Block.get(ray.currentMaterial);

      ray.prevMaterial = ray.currentMaterial;
      ray.currentMaterial = node.type;


      if (currentBlock.localIntersect) {

        if (currentBlock == Block.WATER &&
            prevBlock == Block.WATER) {
          return exitWater(scene, ray);
        }

        if (currentBlock.intersect(ray, scene)) {
          if (prevBlock != currentBlock)
            return true;

          ray.x.scaleAdd(Ray.OFFSET, ray.d, ray.x);
          continue;
        } else {
          // exit ray from this local block
          ray.currentMaterial = 0;// current material is air

          ray.exitBlock(x, y, z);
          continue;
        }
      } else if (!currentBlock.isSameMaterial(prevBlock) && currentBlock != Block.AIR) {
        TexturedBlockModel.getIntersectionColor(ray);
        return true;
      }

      t = ((lx<<level) - ray.x.x) / d.x;
      if (t > Ray.EPSILON) {
        tNear = t;
        nx = 1;
        ny = nz = 0;
      } else {
        t = (((lx+1)<<level) - ray.x.x) / d.x;
        if (t < tNear && t > Ray.EPSILON) {
          tNear = t;
          nx = -1;
          ny = nz = 0;
        }
      }

      t = ((ly<<level) - ray.x.y) / d.y;
      if (t < tNear && t > Ray.EPSILON) {
        tNear = t;
        ny = 1;
        nx = nz = 0;
      } else {
        t = (((ly+1)<<level) - ray.x.y) / d.y;
        if (t < tNear && t > Ray.EPSILON) {
          tNear = t;
          ny = -1;
          nx = nz = 0;
        }
      }

      t = ((lz<<level) - ray.x.z) / d.z;
      if (t < tNear && t > Ray.EPSILON) {
        tNear = t;
        nz = 1;
        nx = ny = 0;
      } else {
        t = (((lz+1)<<level) - ray.x.z) / d.z;
        if (t < tNear && t > Ray.EPSILON) {
          tNear = t;
          nz = -1;
          nx = ny = 0;
        }
      }

      ray.x.scaleAdd(tNear, d, ray.x);
      ray.n.set(nx, ny, nz);
      ray.distance += tNear;
      tNear = Double.POSITIVE_INFINITY;
    }
  }

  /**
   * @param scene
   * @param ray
   * @return <code>true</code> if exited water
   */
  private boolean exitWater(Scene scene, Ray ray) {
    int level;
    Octree.Node node;

    int lx, ly, lz;
    int x, y, z;

    double nx, ny, nz;
    double xx, xy, xz;
    double cx, cy, cz, cw;
    double distance;

    while (true) {
      Block.WATER.intersect(ray, scene);
      ray.n.x = -ray.n.x;
      ray.n.y = -ray.n.y;
      ray.n.z = -ray.n.z;

      xx = ray.x.x;
      xy = ray.x.y;
      xz = ray.x.z;
      nx = ray.n.x;
      ny = ray.n.y;
      nz = ray.n.z;
      cx = ray.color.x;
      cy = ray.color.y;
      cz = ray.color.z;
      cw = ray.color.w;
      distance = ray.distance;

      // add small offset past the intersection to avoid
      // recursion to the same octree node!
      x = (int) QuickMath.floor(ray.x.x + ray.d.x * Ray.OFFSET);
      y = (int) QuickMath.floor(ray.x.y + ray.d.y * Ray.OFFSET);
      z = (int) QuickMath.floor(ray.x.z + ray.d.z * Ray.OFFSET);

      node = root;
      level = depth;
      lx = x >>> level;
      ly = y >>> level;
      lz = z >>> level;

      if (lx != 0 || ly != 0 || lz != 0) {

        // ray origin is outside octree!
        ray.currentMaterial = Block.AIR.id;
        return true;
      }

      while (node.type == -1) {
        level -= 1;
        lx = x >>> level;
        ly = y >>> level;
        lz = z >>> level;
        node = node.children[((lx&1)<<2) | ((ly&1)<<1) | (lz&1)];
      }

      Block currentBlock = Block.get(node.type);
      Block prevBlock = Block.get(ray.currentMaterial);

      ray.prevMaterial = ray.currentMaterial;
      ray.currentMaterial = node.type;

      if (currentBlock.localIntersect) {

        if (!currentBlock.intersect(ray, scene)) {
          ray.currentMaterial = Block.AIR.id;
          return true;
        }

        if (ray.distance > distance) {
          ray.x.set(xx, xy, xz);
          ray.n.set(nx, ny, nz);
          ray.color.set(cx, cy, cz, cw);
          ray.distance = distance;
          ray.currentMaterial = Block.AIR.id;
          return true;
        } else if (currentBlock == Block.WATER) {
          ray.x.scaleAdd(Ray.OFFSET, ray.d, ray.x);
          continue;
        } else {
          return true;
        }
      }

      if (currentBlock != prevBlock) {
        TexturedBlockModel.getIntersectionColor(ray);
        ray.n.scale(-1);
        return true;
      }
    }
  }
}
TOP

Related Classes of se.llbit.math.Octree$Node

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.