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

se.llbit.chunky.renderer.scene.Sun Maven / Gradle / Ivy

There is a newer version: 1.4.5
Show newest version
/* Copyright (c) 2012-2014 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.renderer.scene;

import java.util.Random;

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

import se.llbit.chunky.renderer.Refreshable;
import se.llbit.chunky.resources.Texture;
import se.llbit.json.JsonObject;
import se.llbit.math.QuickMath;
import se.llbit.math.Ray;
import se.llbit.math.Vector3;
import se.llbit.util.JSONifiable;

/**
 * Sun model for ray tracing
 *
 * @author Jesper Öqvist 
 */
public class Sun implements JSONifiable {

  /**
   * Default sun intensity
   */
  public static final double DEFAULT_INTENSITY = 1.25;

  /**
   * Maximum sun intensity
   */
  public static final double MAX_INTENSITY = 50;

  /**
   * Minimum sun intensity
   */
  public static final double MIN_INTENSITY = 0.1;

  private static final double xZenithChroma[][] =
      {{0.00166, -0.00375, 0.00209, 0}, {-0.02903, 0.06377, -0.03203, 0.00394},
          {0.11693, -0.21196, 0.06052, 0.25886},};
  private static final double yZenithChroma[][] =
      {{0.00275, -0.00610, 0.00317, 0}, {-0.04214, 0.08970, -0.04153, 0.00516},
          {0.15346, -0.26756, 0.06670, 0.26688},};
  private static final double mdx[][] =
      {{-0.0193, -0.2592}, {-0.0665, 0.0008}, {-0.0004, 0.2125}, {-0.0641, -0.8989},
          {-0.0033, 0.0452}};
  private static final double mdy[][] =
      {{-0.0167, -0.2608}, {-0.0950, 0.0092}, {-0.0079, 0.2102}, {-0.0441, -1.6537},
          {-0.0109, 0.0529}};
  private static final double mdY[][] =
      {{0.1787, -1.4630}, {-0.3554, 0.4275}, {-0.0227, 5.3251}, {0.1206, -2.5771},
          {-0.0670, 0.3703}};

  private static double turb = 2.5;
  private static double turb2 = turb * turb;
  private static Vector3 A = new Vector3();
  private static Vector3 B = new Vector3();
  private static Vector3 C = new Vector3();
  private static Vector3 D = new Vector3();
  private static Vector3 E = new Vector3();

  /**
   * Sun texture
   */
  public static Texture texture = new Texture();

  static {
    A.x = mdx[0][0] * turb + mdx[0][1];
    B.x = mdx[1][0] * turb + mdx[1][1];
    C.x = mdx[2][0] * turb + mdx[2][1];
    D.x = mdx[3][0] * turb + mdx[3][1];
    E.x = mdx[4][0] * turb + mdx[4][1];

    A.y = mdy[0][0] * turb + mdy[0][1];
    B.y = mdy[1][0] * turb + mdy[1][1];
    C.y = mdy[2][0] * turb + mdy[2][1];
    D.y = mdy[3][0] * turb + mdy[3][1];
    E.y = mdy[4][0] * turb + mdy[4][1];

    A.z = mdY[0][0] * turb + mdY[0][1];
    B.z = mdY[1][0] * turb + mdY[1][1];
    C.z = mdY[2][0] * turb + mdY[2][1];
    D.z = mdY[3][0] * turb + mdY[3][1];
    E.z = mdY[4][0] * turb + mdY[4][1];
  }

  private double zenith_Y;
  private double zenith_x;
  private double zenith_y;
  private double f0_Y;
  private double f0_x;
  private double f0_y;

  private final Refreshable scene;

  /**
   * Sun radius
   */
  public static final double RADIUS = .03;
  public static final double RADIUS_COS = FastMath.cos(RADIUS);
  public static final double RADIUS_SIN = FastMath.sin(RADIUS);

  private static final double AMBIENT = .3;

  private double intensity = DEFAULT_INTENSITY;

  private double azimuth = Math.PI / 2.5;
  private double altitude = Math.PI / 3;

  // Support vectors.
  private final Vector3 su = new Vector3();
  private final Vector3 sv = new Vector3();

  /**
   * Location of the sun in the sky.
   */
  private final Vector3 sw = new Vector3();

  protected final Vector3 emittance = new Vector3(1, 1, 1);

  // final to ensure that we don't do a lot of redundant re-allocation
  private final Vector3 color = new Vector3(1, 1, 1);

  /**
   * Calculate skylight for ray using Preetham day sky model.
   */
  public void calcSkyLight(Ray ray, double horizonOffset) {
    double cosTheta = ray.d.y;
    cosTheta += horizonOffset * (1 - cosTheta);
    if (cosTheta < 0)
      cosTheta = 0;
    double cosGamma = ray.d.dot(sw);
    double gamma = FastMath.acos(cosGamma);
    double cos2Gamma = cosGamma * cosGamma;
    double x = zenith_x * perezF(cosTheta, gamma, cos2Gamma, A.x, B.x, C.x, D.x, E.x) * f0_x;
    double y = zenith_y * perezF(cosTheta, gamma, cos2Gamma, A.y, B.y, C.y, D.y, E.y) * f0_y;
    double z = zenith_Y * perezF(cosTheta, gamma, cos2Gamma, A.z, B.z, C.z, D.z, E.z) * f0_Y;
    if (y <= Ray.EPSILON) {
      ray.color.set(0, 0, 0, 1);
    } else {
      double f = (z / y);
      double x2 = x * f;
      double y2 = z;
      double z2 = (1 - x - y) * f;
      // CIE to RGB M^-1 matrix from http://www.brucelindbloom.com/Eqn_RGB_XYZ_Matrix.html
      ray.color.set(2.3706743 * x2 - 0.9000405 * y2 - 0.4706338 * z2,
          -0.513885 * x2 + 1.4253036 * y2 + 0.0885814 * z2,
          0.0052982 * x2 - 0.0146949 * y2 + 1.0093968 * z2, 1);
      ray.color.scale(0.045);
    }
  }

  private double chroma(double turb, double turb2, double sunTheta, double[][] matrix) {

    double t1 = sunTheta;
    double t2 = t1 * t1;
    double t3 = t1 * t2;

    return turb2 * (matrix[0][0] * t3 + matrix[0][1] * t2 + matrix[0][2] * t1 + matrix[0][3]) +
        turb * (matrix[1][0] * t3 + matrix[1][1] * t2 + matrix[1][2] * t1 + matrix[1][3]) +
        (matrix[2][0] * t3 + matrix[2][1] * t2 + matrix[2][2] * t1 + matrix[2][3]);
  }

  private static double perezF(double cosTheta, double gamma, double cos2Gamma, double A, double B,
      double C, double D, double E) {

    return (1 + A * FastMath.exp(B / cosTheta)) * (1 + C * FastMath.exp(D * gamma) + E * cos2Gamma);
  }

  /**
   * Create new sun model.
   */
  public Sun(Refreshable sceneDescription) {
    this.scene = sceneDescription;
    initSun();
  }

  /**
   * Set equal to other sun model.
   */
  public void set(Sun other) {
    azimuth = other.azimuth;
    altitude = other.altitude;
    color.set(other.color);
    intensity = other.intensity;
    initSun();
  }

  private void initSun() {
    double theta = azimuth;
    double phi = altitude;

    double r = QuickMath.abs(FastMath.cos(phi));

    sw.set(FastMath.cos(theta) * r, FastMath.sin(phi), FastMath.sin(theta) * r);

    if (QuickMath.abs(sw.x) > .1) {
      su.set(0, 1, 0);
    } else {
      su.set(1, 0, 0);
    }
    sv.cross(sw, su);
    sv.normalize();
    su.cross(sv, sw);

    emittance.set(color);
    emittance.scale(FastMath.pow(intensity, Scene.DEFAULT_GAMMA));

    updateSkylightValues();
  }

  /**
   * Angle of the sun around the horizon, measured from north.
   */
  public void setAzimuth(double value) {
    azimuth = QuickMath.modulo(value, Math.PI * 2);
    initSun();
    scene.refresh();
  }

  /**
   * Sun altitude from the horizon.
   */
  public void setAltitude(double value) {
    altitude = QuickMath.clamp(value, 0, Math.PI / 2);
    initSun();
    scene.refresh();
  }

  /**
   * @return Zenith angle
   */
  public double getAltitude() {
    return altitude;
  }

  /**
   * @return Azimuth
   */
  public double getAzimuth() {
    return azimuth;
  }

  /**
   * Check if the ray intersects the sun>
   *
   * @return true if the ray intersects the sun model
   */
  public boolean intersect(Ray ray) {
    if (ray.d.dot(sw) < .5) {
      return false;
    }

    double WIDTH = RADIUS * 4;
    double WIDTH2 = WIDTH * 2;
    double a;
    a = Math.PI / 2 - FastMath.acos(ray.d.dot(su)) + WIDTH;
    if (a >= 0 && a < WIDTH2) {
      double b = Math.PI / 2 - FastMath.acos(ray.d.dot(sv)) + WIDTH;
      if (b >= 0 && b < WIDTH2) {
        texture.getColor(a / WIDTH2, b / WIDTH2, ray.color);
        ray.color.x *= emittance.x * 10;
        ray.color.y *= emittance.y * 10;
        ray.color.z *= emittance.z * 10;
        return true;
      }
    }

    return false;
  }

  /**
   * Calculate flat shading for ray.
   */
  public void flatShading(Ray ray) {
    double shading = ray.n.x * sw.x + ray.n.y * sw.y + ray.n.z * sw.z;
    shading = QuickMath.max(AMBIENT, shading);
    ray.color.x *= emittance.x * shading;
    ray.color.y *= emittance.y * shading;
    ray.color.z *= emittance.z * shading;
  }

  public void setColor(Vector3 newColor) {
    this.color.set(newColor);
    initSun();
    scene.refresh();
  }

  private void updateSkylightValues() {
    double sunTheta = Math.PI / 2 - altitude;
    double cosTheta = FastMath.cos(sunTheta);
    double cos2Theta = cosTheta * cosTheta;
    double chi = (4.0 / 9.0 - turb / 120.0) * (Math.PI - 2 * sunTheta);
    zenith_Y = (4.0453 * turb - 4.9710) * Math.tan(chi) - 0.2155 * turb + 2.4192;
    zenith_Y = (zenith_Y < 0) ? -zenith_Y : zenith_Y;
    zenith_x = chroma(turb, turb2, sunTheta, xZenithChroma);
    zenith_y = chroma(turb, turb2, sunTheta, yZenithChroma);
    f0_x = 1 / perezF(1, sunTheta, cos2Theta, A.x, B.x, C.x, D.x, E.x);
    f0_y = 1 / perezF(1, sunTheta, cos2Theta, A.y, B.y, C.y, D.y, E.y);
    f0_Y = 1 / perezF(1, sunTheta, cos2Theta, A.z, B.z, C.z, D.z, E.z);
  }

  /**
   * Set the sun intensity
   */
  public void setIntensity(double value) {
    intensity = value;
    initSun();
    scene.refresh();
  }

  /**
   * @return The sun intensity
   */
  public double getIntensity() {
    return intensity;
  }

  /**
   * Point ray in random direction within sun solid angle
   */
  public void getRandomSunDirection(Ray reflected, Random random) {
    double x1 = random.nextDouble();
    double x2 = random.nextDouble();
    double cos_a = 1 - x1 + x1 * RADIUS_COS;
    double sin_a = FastMath.sqrt(1 - cos_a * cos_a);
    double phi = 2 * Math.PI * x2;

    Vector3 u = new Vector3(su);
    Vector3 v = new Vector3(sv);
    Vector3 w = new Vector3(sw);

    u.scale(FastMath.cos(phi) * sin_a);
    v.scale(FastMath.sin(phi) * sin_a);
    w.scale(cos_a);

    reflected.d.add(u, v);
    reflected.d.add(w);
    reflected.d.normalize();
  }

  @Override public JsonObject toJson() {
    JsonObject sun = new JsonObject();
    sun.add("altitude", altitude);
    sun.add("azimuth", azimuth);
    sun.add("intensity", intensity);
    JsonObject colorObj = new JsonObject();
    colorObj.add("red", color.x);
    colorObj.add("green", color.y);
    colorObj.add("blue", color.z);
    sun.add("color", colorObj);
    return sun;
  }

  @Override public void fromJson(JsonObject obj) {
    azimuth = obj.get("azimuth").doubleValue(Math.PI / 2.5);
    altitude = obj.get("altitude").doubleValue(Math.PI / 3);
    intensity = obj.get("intensity").doubleValue(DEFAULT_INTENSITY);

    JsonObject colorObj = obj.get("color").object();
    color.x = colorObj.get("red").doubleValue(1);
    color.y = colorObj.get("green").doubleValue(1);
    color.z = colorObj.get("blue").doubleValue(1);

    initSun();
  }

  /**
   * @return sun color
   */
  public Vector3 getColor() {
    return new Vector3(color);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy