/* Copyright (c) 2012-2014 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.util.Random; import org.apache.commons.math3.util.FastMath; import se.llbit.chunky.renderer.scene.Scene; import se.llbit.chunky.world.Block; import se.llbit.chunky.world.BlockData; import se.llbit.chunky.world.Material; /** * The ray representation used for ray tracing. * * @author Jesper Öqvist <jesper@llbit.se> */ public class Ray { public static final double EPSILON = 0.000005; public static final double OFFSET = 0.0001; /** * Ray direction. */ public Vector3 d = new Vector3(); /** * Intersection point. */ public Vector3 o = new Vector3(); /** * Intersection normal. */ public Vector3 n = new Vector3(); /** * Distance traveled in current medium. This is updated after all intersection * tests have run and the final t value has been found. */ public double distance; /** * Accumulated color value. */ public Vector4 color = new Vector4(); /** * Emittance of previously intersected surface. */ public Vector3 emittance = new Vector3(); /** * Previous material. */ private Material prevMaterial = Block.AIR; /** * Current material. */ private Material currentMaterial = Block.AIR; /** * Previous block metadata. */ private int prevData; /** * Current block metadata. */ private int currentData; /** * Recursive ray depth */ public int depth; /** * Distance to closest intersection. */ public double t; /** * Distance to next potential intersection. The tNext value is stored by * subroutines when calculating a potential next hit point. This can then be * stored in the t variable based on further decision making. */ public double tNext; /** * Texture coordinate. */ public double u; /** * Texture coordinate. */ public double v; /** * Is the ray specularly reflected */ public boolean specular; /** * Builds an uninitialized ray. */ public Ray() { } /** * Create a copy of the given ray * * @param other ray to copy */ public Ray(Ray other) { set(other); } /** * Set default values for this ray. */ public void setDefault() { distance = 0; prevMaterial = Block.AIR; currentMaterial = Block.AIR; depth = 0; color.set(0, 0, 0, 0); emittance.set(0, 0, 0); specular = true; } /** * Copy state from another ray. */ public void set(Ray other) { prevMaterial = other.prevMaterial; currentMaterial = other.currentMaterial; depth = other.depth + 1; distance = 0; o.set(other.o); d.set(other.d); n.set(other.n); color.set(0, 0, 0, 0); emittance.set(0, 0, 0); specular = other.specular; } /** * The block data value is a 4-bit integer value describing properties of the * current block. * * @return current block data (sometimes called metadata). */ public final int getBlockData() { return 0xF & (currentData >> BlockData.OFFSET); } /** * Initialize a ray with origin and direction. * * @param o origin * @param d direction */ public final void set(Vector3 o, Vector3 d) { setDefault(); this.o.set(o); this.d.set(d); } /** * Find the exit point from the given block for this ray. This marches the ray * forward - i.e. updates ray origin directly. * * @param bx block x coordinate * @param by block y coordinate * @param bz block z coordinate */ public final void exitBlock(int bx, int by, int bz) { int nx = 0; int ny = 0; int nz = 0; double tNext = Double.POSITIVE_INFINITY; double t = (bx - o.x) / d.x; if (t > Ray.EPSILON) { tNext = t; nx = 1; ny = nz = 0; } else { t = ((bx + 1) - o.x) / d.x; if (t < tNext && t > Ray.EPSILON) { tNext = t; nx = -1; ny = nz = 0; } } t = (by - o.y) / d.y; if (t < tNext && t > Ray.EPSILON) { tNext = t; ny = 1; nx = nz = 0; } else { t = ((by + 1) - o.y) / d.y; if (t < tNext && t > Ray.EPSILON) { tNext = t; ny = -1; nx = nz = 0; } } t = (bz - o.z) / d.z; if (t < tNext && t > Ray.EPSILON) { tNext = t; nz = 1; nx = ny = 0; } else { t = ((bz + 1) - o.z) / d.z; if (t < tNext && t > Ray.EPSILON) { tNext = t; nz = -1; nx = ny = 0; } } o.scaleAdd(tNext, d); n.set(nx, ny, nz); distance += tNext; } /** * @return foliage color for the current block */ public float[] getBiomeFoliageColor(Scene scene) { return scene.getFoliageColor((int) (o.x + d.x * OFFSET), (int) (o.z + d.z * OFFSET)); } /** * @return grass color for the current block */ public float[] getBiomeGrassColor(Scene scene) { return scene.getGrassColor((int) (o.x + d.x * OFFSET), (int) (o.z + d.z * OFFSET)); } /** * Set this ray to a random diffuse reflection of the input ray. */ public final void diffuseReflection(Ray ray, Random random) { set(ray); // get random point on unit disk double x1 = random.nextDouble(); double x2 = random.nextDouble(); double r = FastMath.sqrt(x1); double theta = 2 * Math.PI * x2; // project to point on hemisphere in tangent space double tx = r * FastMath.cos(theta); double ty = r * FastMath.sin(theta); double tz = FastMath.sqrt(1 - x1); // transform from tangent space to world space double xx, xy, xz; double ux, uy, uz; double vx, vy, vz; if (QuickMath.abs(n.x) > .1) { xx = 0; xy = 1; xz = 0; } else { xx = 1; xy = 0; xz = 0; } ux = xy * n.z - xz * n.y; uy = xz * n.x - xx * n.z; uz = xx * n.y - xy * n.x; r = 1 / FastMath.sqrt(ux * ux + uy * uy + uz * uz); ux *= r; uy *= r; uz *= r; vx = uy * n.z - uz * n.y; vy = uz * n.x - ux * n.z; vz = ux * n.y - uy * n.x; d.x = ux * tx + vx * ty + n.x * tz; d.y = uy * tx + vy * ty + n.y * tz; d.z = uz * tx + vz * ty + n.z * tz; o.scaleAdd(Ray.OFFSET, d); currentMaterial = prevMaterial; specular = false; } /** * Set this ray to the specular reflection of the input ray. */ public final void specularReflection(Ray ray) { set(ray); d.scaleAdd(-2 * ray.d.dot(ray.n), ray.n, ray.d); o.scaleAdd(0.00001, ray.n); currentMaterial = prevMaterial; } /** * Scatter ray normal * * @param random random number source */ public final void scatterNormal(Random random) { // get random point on unit disk double x1 = random.nextDouble(); double x2 = random.nextDouble(); double r = FastMath.sqrt(x1); double theta = 2 * Math.PI * x2; // project to point on hemisphere in tangent space double tx = r * FastMath.cos(theta); double ty = r * FastMath.sin(theta); double tz = FastMath.sqrt(1 - x1); // transform from tangent space to world space double xx, xy, xz; double ux, uy, uz; double vx, vy, vz; if (QuickMath.abs(n.x) > .1) { xx = 0; xy = 1; xz = 0; } else { xx = 1; xy = 0; xz = 0; } ux = xy * n.z - xz * n.y; uy = xz * n.x - xx * n.z; uz = xx * n.y - xy * n.x; r = 1 / FastMath.sqrt(ux * ux + uy * uy + uz * uz); ux *= r; uy *= r; uz *= r; vx = uy * n.z - uz * n.y; vy = uz * n.x - ux * n.z; vz = ux * n.y - uy * n.x; n.set(ux * tx + vx * ty + n.x * tz, uy * tx + vy * ty + n.y * tz, uz * tx + vz * ty + n.z * tz); } public void setPrevMaterial(Material mat, int data) { this.prevMaterial = mat; this.prevData = data; } public void setCurrentMaterial(Material mat, int data) { this.currentMaterial = mat; this.currentData = data; } public void setMaterial(int blockId) { this.currentMaterial = Block.get(blockId); this.currentData = blockId; } public Material getPrevMaterial() { return prevMaterial; } public Material getCurrentMaterial() { return currentMaterial; } public int getPrevData() { return prevData; } public int getCurrentData() { return currentData; } }