All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.llbit.chunky.model.WaterModel Maven / Gradle / Ivy

There is a newer version: 1.4.5
Show newest version
/* Copyright (c) 2012 Jesper Öqvist 
 *
 * 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 .
 */
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 
 */
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 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;
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy