/* Copyright (c) 2013-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.chunky.renderer.scene; import se.llbit.chunky.renderer.WorkerState; import se.llbit.chunky.world.Block; import se.llbit.math.Ray; import se.llbit.math.Vector3; /** * @author Jesper Öqvist <jesper@llbit.se> */ public class PreviewRayTracer implements RayTracer { /** * Do a quick preview ray tracing for the current ray. */ @Override public void trace(Scene scene, WorkerState state) { Ray ray = state.ray; if (scene.isInWater(ray)) { ray.setCurrentMaterial(Block.get(Block.WATER_ID), 0); } else { ray.setCurrentMaterial(Block.AIR, 0); } while (true) { if (!nextIntersection(scene, ray)) { if (mapIntersection(scene, ray)) { break; } break; } else if (ray.getCurrentMaterial() != Block.AIR && ray.color.w > 0) { break; } else { ray.o.scaleAdd(Ray.OFFSET, ray.d); } } if (ray.getCurrentMaterial() == Block.AIR) { scene.sky.getSkySpecularColor(ray); } else { scene.sun.flatShading(ray); } } /** * Calculate sky occlusion. * @return occlusion value */ public static double skyOcclusion(Scene scene, WorkerState state) { Ray ray = state.ray; double occlusion = 1.0; while (true) { if (!nextIntersection(scene, ray)) { break; } else { occlusion *= (1 - ray.color.w); ray.o.scaleAdd(Ray.OFFSET, ray.d); } } return 1 - occlusion; } /** * Find next ray intersection. * @return Next intersection */ public static boolean nextIntersection(Scene scene, Ray ray) { ray.setPrevMaterial(ray.getCurrentMaterial(), ray.getCurrentData()); ray.t = Double.POSITIVE_INFINITY; boolean hit = false; if (scene.sky().cloudsEnabled()) { hit = scene.sky().cloudIntersection(scene, ray); } if (scene.waterHeight > 0) { hit = waterIntersection(scene, ray) || hit; } if (scene.intersect(ray)) { // Octree tracer handles updating distance. return true; } if (hit) { ray.distance += ray.t; ray.o.scaleAdd(ray.t, ray.d); scene.updateOpacity(ray); return true; } else { ray.setCurrentMaterial(Block.AIR, 0); return false; } } private static boolean waterIntersection(Scene scene, Ray ray) { if (ray.d.y < 0) { double t = (scene.waterHeight - .125 - ray.o.y - scene.origin.y) / ray.d.y; if (t > 0 && t < ray.t) { Vector3 vec = new Vector3(); vec.scaleAdd(t + Ray.OFFSET, ray.d, ray.o); if (!scene.isInsideOctree(vec)) { ray.t = t; Block.get(Block.WATER_ID).getColor(ray); ray.n.set(0, 1, 0); ray.setCurrentMaterial(Block.get(Block.WATER_ID), 0); return true; } } } if (ray.d.y > 0) { double t = (scene.waterHeight - .125 - ray.o.y - scene.origin.y) / ray.d.y; if (t > 0 && t < ray.t) { Vector3 vec = new Vector3(); vec.scaleAdd(t + Ray.OFFSET, ray.d, ray.o); if (!scene.isInsideOctree(vec)) { ray.t = t; Block.get(Block.WATER_ID).getColor(ray); ray.n.set(0, -1, 0); ray.setCurrentMaterial(Block.AIR, 0); return true; } } } return false; } private static boolean mapIntersection(Scene scene, Ray ray) { if (ray.d.y < 0) { double t = (scene.waterHeight - .125 - ray.o.y - scene.origin.y) / ray.d.y; if (t > 0 && t < ray.t) { Vector3 vec = new Vector3(); vec.scaleAdd(t + Ray.OFFSET, ray.d, ray.o); if (!scene.isInsideOctree(vec)) { ray.t = t; ray.o.set(vec); double xm = (ray.o.x % 16.0 + 16.0) % 16.0; double zm = (ray.o.z % 16.0 + 16.0) % 16.0; if (xm > 0.6 && zm > 0.6) { ray.color.set(0.8, 0.8, 0.8, 1); } else { ray.color.set(0.25, 0.25, 0.25, 1); } ray.setCurrentMaterial(Block.get(Block.STONE_ID), 0); ray.n.set(0, 1, 0); return true; } } } return false; } }