/* Copyright (c) 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.chunky.model;
import java.util.List;
import se.llbit.chunky.resources.Texture;
import se.llbit.chunky.world.Block;
import se.llbit.chunky.world.BlockData;
import se.llbit.chunky.world.Material;
import se.llbit.math.DoubleSidedQuad;
import se.llbit.math.Quad;
import se.llbit.math.QuickMath;
import se.llbit.math.Ray;
import se.llbit.math.Triangle;
import se.llbit.math.Vector2;
import se.llbit.math.Vector3;
import se.llbit.math.Vector4;
import se.llbit.math.primitive.Primitive;
import se.llbit.math.primitive.TexturedTriangle;
/**
* A water block. The height of the top water block is slightly
* lower than a regular block.
*
* @author Jesper Öqvist <jesper@llbit.se>
*/
public class WaterModel {
private static Quad[] fullBlock = {
// bottom
new DoubleSidedQuad(new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(0, 0, 1),
new Vector4(0, 1, 0, 1)),
// top
new DoubleSidedQuad(new Vector3(0, 1, 0), new Vector3(1, 1, 0), new Vector3(0, 1, 1),
new Vector4(0, 1, 0, 1)),
// west
new DoubleSidedQuad(new Vector3(0, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1),
new Vector4(0, 1, 0, 1)),
// east
new DoubleSidedQuad(new Vector3(1, 0, 0), new Vector3(1, 1, 0), new Vector3(1, 0, 1),
new Vector4(0, 1, 0, 1)),
// north
new DoubleSidedQuad(new Vector3(0, 1, 0), new Vector3(1, 1, 0), new Vector3(0, 0, 0),
new Vector4(0, 1, 0, 0)),
// south
new DoubleSidedQuad(new Vector3(0, 1, 1), new Vector3(1, 1, 1), new Vector3(0, 0, 1),
new Vector4(0, 1, 0, 1)),};
static final DoubleSidedQuad bot =
new DoubleSidedQuad(new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(0, 0, 1),
new Vector4(0, 1, 0, 1));
static final Triangle[][][] t012 = new Triangle[8][8][8];
static final Triangle[][][] t230 = new Triangle[8][8][8];
static final Triangle[][] westt = new Triangle[8][8];
static final Triangle[] westb = new Triangle[8];
static final Triangle[][] northt = new Triangle[8][8];
static final Triangle[] northb = new Triangle[8];
static final Triangle[][] eastt = new Triangle[8][8];
static final Triangle[] eastb = new Triangle[8];
static final Triangle[][] southt = new Triangle[8][8];
static final Triangle[] southb = new Triangle[8];
/**
* Water height levels
*/
static final double height[] =
{14 / 16., 12.25 / 16., 10.5 / 16, 8.75 / 16, 7. / 16, 5.25 / 16, 3.5 / 16, 1.75 / 16};
private static final float[][][] normalMap;
private static final int normalMapW;
/**
* Block data offset for water above flag
*/
public static final int FULL_BLOCK = 12;
static {
// precompute normal map
Texture waterHeight = new Texture("water-height");
normalMapW = waterHeight.getWidth();
normalMap = new float[normalMapW][normalMapW][2];
for (int u = 0; u < normalMapW; ++u) {
for (int v = 0; v < normalMapW; ++v) {
float hx0 = (waterHeight.getColorWrapped(u, v) & 0xFF) / 255.f;
float hx1 = (waterHeight.getColorWrapped(u + 1, v) & 0xFF) / 255.f;
float hz0 = (waterHeight.getColorWrapped(u, v) & 0xFF) / 255.f;
float hz1 = (waterHeight.getColorWrapped(u, v + 1) & 0xFF) / 255.f;
normalMap[u][v][0] = hx1 - hx0;
normalMap[u][v][1] = hz1 - hz0;
}
}
// precompute water triangles
for (int i = 0; i < 8; ++i) {
double c0 = height[i];
for (int j = 0; j < 8; ++j) {
double c1 = height[j];
for (int k = 0; k < 8; ++k) {
double c2 = height[k];
t012[i][j][k] =
new Triangle(new Vector3(1, c1, 1), new Vector3(1, c2, 0), new Vector3(0, c0, 1));
}
}
}
for (int i = 0; i < 8; ++i) {
double c2 = height[i];
for (int j = 0; j < 8; ++j) {
double c3 = height[j];
for (int k = 0; k < 8; ++k) {
double c0 = height[k];
t230[i][j][k] =
new Triangle(new Vector3(0, c3, 0), new Vector3(0, c0, 1), new Vector3(1, c2, 0));
}
}
}
for (int i = 0; i < 8; ++i) {
double c0 = height[i];
for (int j = 0; j < 8; ++j) {
double c3 = height[j];
westt[i][j] =
new Triangle(new Vector3(0, c3, 0), new Vector3(0, 0, 0), new Vector3(0, c0, 1));
}
}
for (int i = 0; i < 8; ++i) {
double c0 = height[i];
westb[i] = new Triangle(new Vector3(0, 0, 1), new Vector3(0, c0, 1), new Vector3(0, 0, 0));
}
for (int i = 0; i < 8; ++i) {
double c1 = height[i];
for (int j = 0; j < 8; ++j) {
double c2 = height[j];
eastt[i][j] =
new Triangle(new Vector3(1, c2, 0), new Vector3(1, c1, 1), new Vector3(1, 0, 0));
}
}
for (int i = 0; i < 8; ++i) {
double c1 = height[i];
eastb[i] = new Triangle(new Vector3(1, c1, 1), new Vector3(1, 0, 1), new Vector3(1, 0, 0));
}
for (int i = 0; i < 8; ++i) {
double c2 = height[i];
for (int j = 0; j < 8; ++j) {
double c3 = height[j];
northt[i][j] =
new Triangle(new Vector3(0, c3, 0), new Vector3(1, c2, 0), new Vector3(0, 0, 0));
}
}
for (int i = 0; i < 8; ++i) {
double c2 = height[i];
northb[i] =
new Triangle(new Vector3(1, 0, 0), new Vector3(0, 0, 0), new Vector3(1, c2, 0));
}
for (int i = 0; i < 8; ++i) {
double c0 = height[i];
for (int j = 0; j < 8; ++j) {
double c1 = height[j];
southt[i][j] =
new Triangle(new Vector3(0, c0, 1), new Vector3(0, 0, 1), new Vector3(1, c1, 1));
}
}
for (int i = 0; i < 8; ++i) {
double c1 = height[i];
southb[i] =
new Triangle(new Vector3(1, 0, 1), new Vector3(1, c1, 1), new Vector3(0, 0, 1));
}
}
public static boolean intersect(Ray ray) {
ray.t = Double.POSITIVE_INFINITY;
int data = ray.getCurrentData();
int isFull = (data >> FULL_BLOCK) & 1;
//int level = data >> 8;
if (isFull != 0) {
boolean hit = false;
for (Quad quad : fullBlock) {
if (quad.intersect(ray)) {
Texture.water.getAvgColorLinear(ray.color);
ray.t = ray.tNext;
ray.n.set(quad.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(quad.n)));
hit = true;
}
}
if (hit) {
ray.distance += ray.t;
ray.o.scaleAdd(ray.t, ray.d);
}
return hit;
}
boolean hit = false;
if (bot.intersect(ray)) {
ray.n.set(bot.n);
ray.n.scale(-QuickMath.signum(ray.d.dot(bot.n)));
ray.t = ray.tNext;
hit = true;
}
int c0 = (0xF & (data >> 16)) % 8;
int c1 = (0xF & (data >> 20)) % 8;
int c2 = (0xF & (data >> 24)) % 8;
int c3 = (0xF & (data >> 28)) % 8;
Triangle triangle = t012[c0][c1][c2];
if (triangle.intersect(ray)) {
ray.n.set(triangle.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(triangle.n)));
ray.t = ray.tNext;
hit = true;
}
triangle = t230[c2][c3][c0];
if (triangle.intersect(ray)) {
ray.n.set(triangle.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(triangle.n)));
ray.t = ray.tNext;
ray.u = 1 - ray.u;
ray.v = 1 - ray.v;
hit = true;
}
triangle = westt[c0][c3];
if (triangle.intersect(ray)) {
ray.n.set(triangle.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(triangle.n)));
ray.t = ray.tNext;
hit = true;
}
triangle = westb[c0];
if (triangle.intersect(ray)) {
ray.n.set(triangle.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(triangle.n)));
ray.t = ray.tNext;
ray.u = 1 - ray.u;
ray.v = 1 - ray.v;
hit = true;
}
triangle = eastt[c1][c2];
if (triangle.intersect(ray)) {
ray.n.set(triangle.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(triangle.n)));
ray.t = ray.tNext;
hit = true;
}
triangle = eastb[c1];
if (triangle.intersect(ray)) {
ray.n.set(triangle.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(triangle.n)));
ray.t = ray.tNext;
ray.u = 1 - ray.u;
ray.v = 1 - ray.v;
hit = true;
}
triangle = southt[c0][c1];
if (triangle.intersect(ray)) {
ray.n.set(triangle.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(triangle.n)));
ray.t = ray.tNext;
hit = true;
}
triangle = southb[c1];
if (triangle.intersect(ray)) {
ray.n.set(triangle.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(triangle.n)));
ray.t = ray.tNext;
ray.u = 1 - ray.u;
ray.v = 1 - ray.v;
hit = true;
}
triangle = northt[c2][c3];
if (triangle.intersect(ray)) {
ray.n.set(triangle.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(triangle.n)));
ray.t = ray.tNext;
hit = true;
}
triangle = northb[c2];
if (triangle.intersect(ray)) {
ray.n.set(triangle.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(triangle.n)));
ray.t = ray.tNext;
ray.u = 1 - ray.u;
ray.v = 1 - ray.v;
hit = true;
}
if (hit) {
Texture.water.getAvgColorLinear(ray.color);
ray.distance += ray.t;
ray.o.scaleAdd(ray.t, ray.d);
}
return hit;
}
public static boolean intersectTop(Ray ray) {
ray.t = Double.POSITIVE_INFINITY;
int data = ray.getCurrentData();
boolean hit = false;
int c0 = (0xF & (data >> 16)) % 8;
int c1 = (0xF & (data >> 20)) % 8;
int c2 = (0xF & (data >> 24)) % 8;
int c3 = (0xF & (data >> 28)) % 8;
Triangle triangle = t012[c0][c1][c2];
if (triangle.intersect(ray)) {
ray.n.set(triangle.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(triangle.n)));
ray.t = ray.tNext;
hit = true;
}
triangle = t230[c2][c3][c0];
if (triangle.intersect(ray)) {
ray.n.set(triangle.n);
ray.n.scale(QuickMath.signum(-ray.d.dot(triangle.n)));
ray.t = ray.tNext;
ray.u = 1 - ray.u;
ray.v = 1 - ray.v;
hit = true;
}
if (hit) {
Texture.water.getAvgColorLinear(ray.color);
ray.distance += ray.t;
ray.o.scaleAdd(ray.t, ray.d);
}
return hit;
}
/**
* Displace the normal using the water displacement map.
*/
public static void doWaterDisplacement(Ray ray) {
int w = (1 << 4);
double x = ray.o.x / w - QuickMath.floor(ray.o.x / w);
double z = ray.o.z / w - QuickMath.floor(ray.o.z / w);
int u = (int) (x * normalMapW - Ray.EPSILON);
int v = (int) ((1 - z) * normalMapW - Ray.EPSILON);
ray.n.set(normalMap[u][v][0], .15f, normalMap[u][v][1]);
w = (1 << 1);
x = ray.o.x / w - QuickMath.floor(ray.o.x / w);
z = ray.o.z / w - QuickMath.floor(ray.o.z / w);
u = (int) (x * normalMapW - Ray.EPSILON);
v = (int) ((1 - z) * normalMapW - Ray.EPSILON);
ray.n.x += normalMap[u][v][0] / 2;
ray.n.z += normalMap[u][v][1] / 2;
ray.n.normalize();
}
public static void addPrimitives(List<Primitive> primitives, int data, int x, int y, int z,
int size) {
// Lily pad test.
if ((data & (1 << BlockData.LILY_PAD)) != 0) {
double height = y + 1 - 0.12;
Vector3 c1 = new Vector3(x, height, z);
Vector3 c2 = new Vector3(x, height, z + size);
Vector3 c3 = new Vector3(x + size, height, z + size);
Vector3 c4 = new Vector3(x + size, height, z);
Vector2 t1 = new Vector2(0, 0);
Vector2 t2 = new Vector2(0, 1);
Vector2 t3 = new Vector2(1, 1);
Vector2 t4 = new Vector2(1, 0);
int dir = 3 & (data >> BlockData.LILY_PAD_ROTATION);
Material lilyMaterial = Block.get(Block.LILY_PAD_ID);
switch (dir) {
case 0:
primitives.add(new TexturedTriangle(c1, c3, c2, t1, t3, t2, lilyMaterial));
primitives.add(new TexturedTriangle(c1, c4, c3, t1, t4, t3, lilyMaterial));
break;
case 1:
primitives.add(new TexturedTriangle(c1, c3, c2, t4, t2, t1, lilyMaterial));
primitives.add(new TexturedTriangle(c1, c4, c3, t4, t3, t2, lilyMaterial));
break;
case 2:
primitives.add(new TexturedTriangle(c1, c3, c2, t3, t1, t4, lilyMaterial));
primitives.add(new TexturedTriangle(c1, c4, c3, t3, t2, t1, lilyMaterial));
break;
case 3:
primitives.add(new TexturedTriangle(c1, c3, c2, t2, t4, t3, lilyMaterial));
primitives.add(new TexturedTriangle(c1, c4, c3, t2, t1, t4, lilyMaterial));
break;
}
}
}
}