skydome – Documentation Wiki

Introduction

SkyDome - A Skydome with dynamics skylight based on “A practical Analytic Model for Daylight” by A. J. Preetham, Peter Shirley, Brian Smits (University of Utah).

http://www.cs.utah.edu/vissim/papers/sunsky/

See also: com.jme.scene.Skybox

TODO List

Source Code

The following is the code for the SkyDome class.

/*
 * SkyDome.java
 *
 */
 
package yourpackage;
 
import com.jme.bounding.BoundingBox;
import com.jme.image.Image;
import com.jme.image.Texture;
import com.jme.light.DirectionalLight;
import com.jme.light.LightNode;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.SceneElement;
import com.jme.scene.Spatial;
import com.jme.scene.batch.TriangleBatch;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Dome;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
import com.jme.util.geom.BufferUtils;
import com.jmex.effects.LensFlare;
import com.jmex.effects.LensFlareFactory;
import java.nio.FloatBuffer;
 
/**
 * sky gradient based on "A practical analytic model for daylight"
 * by A. J. Preetham, Peter Shirley, Brian Smits (University of Utah)
 * @author Highnik
 */
public class SkyDome extends Node {
    public static final float INFINITY = 3.3e+38f;
    public static final float EPSILON  = 0.000001f;
 
    private Dome dome;
    private Vector3f cameraPos = new Vector3f();
 
    // shading parameters
    private float thetaSun;
    private float phiSun;
    private float turbidity = 2.0f;
    private boolean isLinearExpControl;
    private float exposure = 18.0f;
    private float overcast;
    private float gammaCorrection = 2.5f;
 
    // time parameters
    private float timeOfDay = 0.0f;
    private float julianDay = 0.0f;
    private float latitude  = 0.0f;
    private float longitude = 0.0f;
    private float stdMeridian = 0.0f;
    private float sunnyTime = 12.0f;
    private float solarDeclination = 0.0f;
    private float latitudeInRadian = 0.0f;
    private boolean isNight = false;
    // timer control.
    private Timer timer;
    private float currentTime;
    private float updateTime = 0.0f;
    private float timeWarp = 180.0f;
    private boolean renderRequired = true;
 
    // used at update color
    private float chi;
    private float zenithLuminance;
    private float zenithX;
    private float zenithY;
    private float[] perezLuminance;
    private float[] perezX;
    private float[] perezY;
    private Vector3f sunDirection = new Vector3f();
    private Vector3f sunPosition = new Vector3f();
    private ColorXYZ color;
    private ColorXYZ colorTemp;
    private TriangleBatch batch;
    private FloatBuffer colorBuf;
    private FloatBuffer normalBuf;
    private Vector3f vertex = new Vector3f();
    private float gamma;
    private float cosTheta;
    private float cosGamma2;
    private float x_value;
    private float y_value;
    private float yClear;
    private float yOver;
    private float _Y;
    private float _X;
    private float _Z;
 
    private DirectionalLight dr;
    private LightNode sun;
    private LensFlare flare;
    private boolean sunEnabled = true;
 
    /** Distribution coefficients for the luminance(Y) distribution function */
    private float distributionLuminance[][] = {	// Perez distributions
        {  0.17872f , -1.46303f },		// a = darkening or brightening of the horizon
        { -0.35540f ,  0.42749f },		// b = luminance gradient near the horizon,
        { -0.02266f ,  5.32505f },		// c = relative intensity of the circumsolar region
        {  0.12064f , -2.57705f },		// d = width of the circumsolar region
        { -0.06696f ,  0.37027f }};		// e = relative backscattered light
 
    /** Distribution coefficients for the x distribution function */
    private float distributionXcomp[][] = {
        { -0.01925f , -0.25922f },
        { -0.06651f ,  0.00081f },
        { -0.00041f ,  0.21247f },
        { -0.06409f , -0.89887f },
        { -0.00325f ,  0.04517f }};
 
    /** Distribution coefficients for the y distribution function */
    private float distributionYcomp[][] = {
        { -0.01669f , -0.26078f },
        { -0.09495f ,  0.00921f },
        { -0.00792f ,  0.21023f },
        { -0.04405f , -1.65369f },
        { -0.01092f ,  0.05291f }};
 
    /** Zenith x value */
    private float zenithXmatrix[][] = {
        {  0.00165f, -0.00375f,  0.00209f,  0.00000f },
        { -0.02903f,  0.06377f, -0.03202f,  0.00394f },
        {  0.11693f, -0.21196f,  0.06052f,  0.25886f }};
 
    /** Zenith y value */
    private float zenithYmatrix[][] = {
        {  0.00275f, -0.00610f,  0.00317f,  0.00000f },
        { -0.04214f,  0.08970f, -0.04153f,  0.00516f },
        {  0.15346f, -0.26756f,  0.06670f,  0.26688f }};
 
 
    /** Creates a new instance of SkyDome */
    public SkyDome() {
        this("SkyDome", 11, 18, 100f);
    }
 
    public SkyDome(String name) {
        this(name, 11, 18, 100f);
    }
 
    public SkyDome(String name, int planes, int radialSamples, float radius) {
        this(name, new Vector3f(0, 0, 0), planes, radialSamples, radius);
    }
 
    public SkyDome(String name, Vector3f center, int planes, int radialSamples, float radius) {
        dome = new Dome(name, center, planes, radialSamples, radius, true);
        dome.setIsCollidable(false);
        dome.setSolidColor(ColorRGBA.black);
        attachChild(dome);
        timer = Timer.getTimer();
        currentTime = timer.getTimeInSeconds();
 
        solarDeclination = calc_solar_declination(julianDay);
        sunnyTime = calc_sunny_time(latitude,  solarDeclination);
 
        // create a lens flare effects
        setupLensFlare();
 
        ZBufferState zbuff = DisplaySystem.getDisplaySystem().getRenderer().createZBufferState();
        zbuff.setWritable(false);
        zbuff.setEnabled(true);
        zbuff.setFunction(ZBufferState.CF_LEQUAL);
        setRenderState(zbuff);
        setCullMode(SceneElement.CULL_NEVER);
        setLightCombineMode(LightState.OFF);
        setTextureCombineMode(TextureState.REPLACE);
    }
 
    /**
     * Set Sun's positon
     */
    public void setSunPosition(Vector3f sunPos) {
        Vector3f pos = new Vector3f();
        pos = FastMath.cartesianToSpherical(sunPos, pos);
        thetaSun = pos.z;
        phiSun = pos.y;
    }
 
    /**
     * Return Sun's position
     */
    public Vector3f getSunPosition() {
        return sunPosition;
    }
 
    /**
     * Convert time to sun position
     * @param time
     *              Sets a time of day between 0 to 24 (6,25 = 6:15 hs)
     */
    public void setSunPosition(float time) {
        float solarTime, solarAltitude, opp, adj, solarAzimuth, cosSolarDeclination, sinSolarDeclination, sinLatitude, cosLatitude;
        this.timeOfDay = time;
 
        sinLatitude = FastMath.sin(latitudeInRadian);
        cosLatitude = FastMath.cos(latitudeInRadian);
        sinSolarDeclination = FastMath.sin(solarDeclination);
        cosSolarDeclination = FastMath.cos(solarDeclination);
 
        // real time
        solarTime = time + (0.170f * FastMath.sin(4f * FastMath.PI * (julianDay - 80f) / 373f) -
                0.129f * FastMath.sin(FastMath.TWO_PI * (julianDay - 8f) / 355f)) +
                (stdMeridian - longitude) / 15;
 
        solarAltitude = FastMath.asin(sinLatitude * sinSolarDeclination -
                cosLatitude * cosSolarDeclination *
                FastMath.cos(FastMath.PI * solarTime / sunnyTime));
 
        opp = -cosSolarDeclination * FastMath.sin(FastMath.PI * solarTime / sunnyTime);
 
        adj = -(cosLatitude * sinSolarDeclination + sinLatitude * cosSolarDeclination *
                FastMath.cos(FastMath.PI * solarTime / sunnyTime));
 
        solarAzimuth = FastMath.atan2(opp / adj);
 
        if (solarAltitude > 0.0f) {
 
            isNight = false;
            if ((opp < 0.0f && solarAzimuth < 0.0f) || (opp > 0.0f && solarAzimuth > 0.0f)) {
                solarAzimuth = FastMath.HALF_PI + solarAzimuth;
            } else {
                solarAzimuth = FastMath.HALF_PI - solarAzimuth;
            }
            phiSun = FastMath.TWO_PI - solarAzimuth;
            thetaSun = FastMath.HALF_PI - solarAltitude;
 
            sunDirection.x = dome.radius;
            sunDirection.y = phiSun;
            sunDirection.z = solarAltitude;
            sunPosition = FastMath.sphericalToCartesian(sunDirection, sunPosition);
            if (solarAzimuth < 0.0f) {
                sunPosition.x *= -1;
            }
 
            if (this.isSunEnabled())
                sun.setLocalTranslation(sunPosition);
 
        } else {
            isNight = true;
        }
 
    }
 
    /**
     * Return if now is night
     */
    public boolean isNight() {
        return isNight;
    }
 
    /**
     * Set Day of year between 0 to 364
     */
    public void setDay(float julianDay) {
        this.julianDay = clamp(julianDay, 0.0f, 365.0f);
        // Solar declination
        solarDeclination = calc_solar_declination(julianDay);
        sunnyTime = calc_sunny_time(latitude, solarDeclination);
    }
 
    /**
     * Get Day of year
     */
    public float getDay() {
        return julianDay;
    }
 
    /**
     * Set latitude
     */
    public void setLatitude(float latitude) {
        this.latitude = clamp(latitude, -90.0f, 90.0f);
        latitudeInRadian = FastMath.DEG_TO_RAD * latitude;
        sunnyTime = calc_sunny_time(latitudeInRadian, solarDeclination);
    }
 
    /**
     * Get latitude
     */
    public float getLatitude() {
        return latitude;
    }
 
    /**
     * Set longitude
     */
    public void setLongitude(float longitude) {
        this.longitude = longitude;
    }
 
    /**
     * Get longitude
     */
    public float getLongitude() {
        return longitude;
    }
 
    /**
     * Set standar meridian
     * @param stdMeridian
     *                      TimeZone * 15
     */
    public void setStandardMeridian(float stdMeridian) {
        this.stdMeridian = stdMeridian;
    }
 
    /**
     * Get standar meridian
     */
    public float getStandardMeridian() {
        return stdMeridian;
    }
 
    public void setTurbidity(float turbidity ) {
        this.turbidity = clamp(turbidity, 1.0f, 512.0f);
    }
 
    /**
     * Set Exposure factor
     */
    public void setExposure(boolean isLinearExpControl, float exposure) {
        this.isLinearExpControl = isLinearExpControl;
        this.exposure = 1.0f / clamp(exposure, 1.0f, INFINITY );
    }
 
    /**
     * Set Over Cast factor
     */
    public void setOvercastFactor(float overcast) {
        this.overcast = clamp(overcast, 0.0f, 1.0f );
    }
 
    /**
     * Set gamma correction factor
     */
    public void setGammaCorrection(float gamma) {
        this.gammaCorrection = 1.0f / clamp(gamma, EPSILON, INFINITY );
    }
 
    /**
     * Seconds to update
     */
    public void setUpdateTime(float seconds) {
        this.updateTime = seconds;
    }
 
    public float getUpdateTime() {
        return updateTime;
    }
 
    /**
     * if updateTime = 1 and timeWarp = 1, every seconds will be updated
     */
    public void setTimeWarp(float timeWarp) {
        this.timeWarp = timeWarp;
    }
 
    public float getTimeWarp() {
        return timeWarp;
    }
 
    public void update() {
        if (updateTime > 0.0f) {
            if ((timer.getTimeInSeconds() - currentTime) >= updateTime) {
                currentTime = timer.getTimeInSeconds();
                timeOfDay += updateTime * timeWarp / 3600f;
                setSunPosition(timeOfDay);
                renderRequired = true;
            }
        }
        cameraPos = DisplaySystem.getDisplaySystem().getRenderer().getCamera().getLocation();
        dome.setLocalTranslation(new Vector3f(cameraPos.x, 0.0f, cameraPos.z));
    }
 
    /**
     * update Sky color
     */
    public void render() {
 
        if (! renderRequired)
            return;
 
        if (isNight) {
            dome.setSolidColor(ColorRGBA.black);
            return;
        }
 
        // get zenith luminance
        chi = ( (4.0f / 9.0f) - (turbidity / 120.0f) ) * ( FastMath.PI - (2.0f * thetaSun) );
        zenithLuminance = ( (4.0453f * turbidity) - 4.9710f ) * FastMath.tan(chi) - (0.2155f * turbidity) + 2.4192f;
        if (zenithLuminance < 0.0f)
            zenithLuminance = -zenithLuminance;
 
        // get x / y zenith
        zenithX = getZenith( zenithXmatrix, thetaSun, turbidity );
        zenithY = getZenith( zenithYmatrix, thetaSun, turbidity );
 
        // get perez function parameters
        perezLuminance = getPerez(distributionLuminance, turbidity );
        perezX = getPerez(distributionXcomp, turbidity );
        perezY = getPerez(distributionYcomp, turbidity );
 
        // make some precalculation
        zenithX = perezFunctionO1( perezX, thetaSun, zenithX );
        zenithY = perezFunctionO1( perezY, thetaSun, zenithY );
        zenithLuminance = perezFunctionO1( perezLuminance, thetaSun, zenithLuminance );
 
        // build sun direction vector
        sunDirection.x = FastMath.cos(FastMath.HALF_PI - thetaSun) * FastMath.cos(phiSun);
        sunDirection.y = FastMath.sin(FastMath.HALF_PI - thetaSun);
        sunDirection.z = FastMath.cos(FastMath.HALF_PI - thetaSun) * FastMath.sin(phiSun);
        sunDirection.normalize();
 
        // trough all vertices
        for (int i = 0; i < dome.getBatchCount(); i++) {
            batch = dome.getBatch(i);
 
            normalBuf = batch.getNormalBuffer();
            colorBuf = batch.getColorBuffer();
 
            for (int j = 0; j < batch.getVertexCount(); j++) {
 
                BufferUtils.populateFromBuffer(vertex, normalBuf, j);
 
                // angle between sun and vertex
                gamma = FastMath.acos(vertex.dot(sunDirection));
 
                if (vertex.y < 0.05f)
                    vertex.y = 0.05f;
 
                cosTheta = 1.0f / vertex.y;
                cosGamma2 = FastMath.sqr(FastMath.cos(gamma));
 
                // Compute x,y values
                x_value = perezFunctionO2( perezX, cosTheta, gamma, cosGamma2, zenithX );
                y_value = perezFunctionO2( perezY, cosTheta, gamma, cosGamma2, zenithY );
 
                // luminance(Y) for clear & overcast sky
                yClear = perezFunctionO2( perezLuminance, cosTheta, gamma, cosGamma2, zenithLuminance );
                yOver = (1.0f + 2.0f * vertex.y) / 3.0f;
 
                _Y = FastMath.LERP(overcast, yClear, yOver);
                _X = (x_value / y_value) * _Y;
                _Z = ((1.0f - x_value - y_value) / y_value) * _Y;
 
                colorTemp = new ColorXYZ(_X, _Y, _Z);
                color = colorTemp.convertXYZtoRGB();
                colorTemp = color.convertRGBtoHSV();
 
                if (isLinearExpControl) {                                       // linear scale
                    colorTemp.setValue(colorTemp.getValue() * exposure);
                } else {                                                        // exp scale
                    colorTemp.setValue(1.0f - FastMath.exp(-exposure * colorTemp.getValue()));
                }
                color = colorTemp.convertHSVtoRGB();
 
                // gamma control
                color.setGammaCorrection(gammaCorrection);
 
                // clamp rgb between 0.0 - 1.0
                color.clamp();
 
                // change the color
                BufferUtils.setInBuffer(color.getRGBA(), colorBuf, j);
            }
        }
        renderRequired = false;
    }
 
    /**
     * Returns a LightNode that represents the Sun
     */
    public LightNode getSun() {
        return sun;
    }
 
    /**
     * Set the rootNode to flare
     */
    public void setRootNode(Node value) {
        if (flare != null) {
            flare.setRootNode(value);
        }
    }
 
    /**
     * Set a intensity to Flare
     */
    public void setIntensity(float value) {
        if (flare != null) {
            flare.setIntensity(value);
        }
    }
 
    /**
     * Set a target to LightNode
     */
    public void setTarget(Spatial node) {
        if (sun != null) {
            sun.setTarget(node);
        }
    }
 
    public void setSunEnabled(boolean enable) {
        this.sunEnabled = enable;
        sun.getLight().setEnabled(enable);
    }
 
    public boolean isSunEnabled() {
        return sunEnabled;
    }
 
    private float calc_solar_declination(float jDay) {
        return (0.4093f * FastMath.sin(FastMath.TWO_PI * (284f + jDay) / 365f));
    }
 
    private float calc_sunny_time(float lat, float solarDeclin) {
        // Time of hours over horizon
        float sunnyTime;
        sunnyTime = (2.0f * FastMath.acos(-FastMath.tan(lat) * FastMath.tan(solarDeclin)));
        sunnyTime = (sunnyTime * FastMath.RAD_TO_DEG) / 15;
        return sunnyTime;
    }
 
    /**
     * Create Lens flare effect
     */
    private void setupLensFlare() {
 
        dr = new DirectionalLight();
        dr.setEnabled(true);
        dr.setDiffuse(ColorRGBA.white);
        dr.setAmbient(ColorRGBA.gray);
        dr.setDirection(new Vector3f(0.0f, 0.0f, 0.0f));
 
        sun = new LightNode("SunNode", DisplaySystem.getDisplaySystem().getRenderer().createLightState());
        sun.setLight(dr);
 
        Vector3f min2 = new Vector3f( -0.1f, -0.1f, -0.1f);
        Vector3f max2 = new Vector3f(0.1f, 0.1f, 0.1f);
        Box lightBox = new Box("lightbox", min2, max2);
        lightBox.setModelBound(new BoundingBox());
        lightBox.updateModelBound();
        sun.attachChild(lightBox);
        lightBox.setLightCombineMode(LightState.OFF);
 
        // Setup the lensflare textures.
        TextureState[] tex = new TextureState[4];
        tex[0] = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
        tex[0].setTexture(
                TextureManager.loadTexture(
                SkyDome.class.getClassLoader().getResource(
                "resources/images/texture/flare1.png"),
                Texture.MM_LINEAR_LINEAR,
                Texture.FM_LINEAR,
                Image.RGBA8888,
                1.0f,
                true));
        tex[0].setEnabled(true);
 
        tex[1] = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
        tex[1].setTexture(
                TextureManager.loadTexture(
                SkyDome.class.getClassLoader().getResource(
                "resources/images/texture/flare2.png"),
                Texture.MM_LINEAR_LINEAR,
                Texture.FM_LINEAR));
        tex[1].setEnabled(true);
 
        tex[2] = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
        tex[2].setTexture(
                TextureManager.loadTexture(
                SkyDome.class.getClassLoader().getResource(
                "resources/images/texture/flare3.png"),
                Texture.MM_LINEAR_LINEAR,
                Texture.FM_LINEAR));
        tex[2].setEnabled(true);
 
        tex[3] = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
        tex[3].setTexture(
                TextureManager.loadTexture(
                SkyDome.class.getClassLoader().getResource(
                "resources/images/texture/flare4.png"),
                Texture.MM_LINEAR_LINEAR,
                Texture.FM_LINEAR));
        tex[3].setEnabled(true);
 
        flare = LensFlareFactory.createBasicLensFlare("flare", tex);
 
        flare.setIntensity(0.5f);
 
        // notice that it comes at the end
        sun.attachChild(flare);
        attachChild(sun);
    }
 
    private float[] getPerez(float[][] distribution, float turbidity ) {
        float[] perez = new float[5];
        perez[0] = distribution[0][0] * turbidity + distribution[0][1];
        perez[1] = distribution[1][0] * turbidity + distribution[1][1];
        perez[2] = distribution[2][0] * turbidity + distribution[2][1];
        perez[3] = distribution[3][0] * turbidity + distribution[3][1];
        perez[4] = distribution[4][0] * turbidity + distribution[4][1];
        return perez;
    }
 
    private float getZenith(float[][] zenithMatrix, float theta, float turbidity) {
        float theta2 = theta * theta;
        float theta3 = theta * theta2;
 
        return	(zenithMatrix[0][0] * theta3 + zenithMatrix[0][1] * theta2 + zenithMatrix[0][2] * theta + zenithMatrix[0][3]) * turbidity * turbidity +
                (zenithMatrix[1][0] * theta3 + zenithMatrix[1][1] * theta2 + zenithMatrix[1][2] * theta + zenithMatrix[1][3]) * turbidity +
                (zenithMatrix[2][0] * theta3 + zenithMatrix[2][1] * theta2 + zenithMatrix[2][2] * theta + zenithMatrix[2][3]);
    }
 
    private float perezFunctionO1(float[] perezCoeffs, float thetaSun, float zenithValue ) {
        float val = (1.0f + perezCoeffs[0] * FastMath.exp(perezCoeffs[1])) *
                (1.0f + perezCoeffs[2] * FastMath.exp(perezCoeffs[3] * thetaSun ) + perezCoeffs[4] * FastMath.sqr(FastMath.cos(thetaSun)));
        return zenithValue / val;
    }
 
    private float perezFunctionO2(float[] perezCoeffs, float cosTheta, float gamma, float cosGamma2, float zenithValue ) {
        return zenithValue * (1.0f + perezCoeffs[0] * FastMath.exp(perezCoeffs[1] * cosTheta )) *
                (1.0f + perezCoeffs[2] * FastMath.exp(perezCoeffs[3] * gamma) + perezCoeffs[4] * cosGamma2);
    }
 
    /**
     * clamp the value between min and max values
     */
    private float clamp(float value, float min, float max) {
        if (value < min)
            return min;
        else if (value > max)
            return max;
        else
            return value;
    }
 
    class ColorXYZ {
        private float x = 0.0f;
        private float y = 0.0f;
        private float z = 0.0f;
        private float r = 0.0f;
        private float g = 0.0f;
        private float b = 0.0f;
        private float a = 1.0f;
        private float hue = 0.0f;
        private float saturation = 0.0f;
        private float value = 0.0f;
 
        public ColorXYZ(float x, float y, float z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }
 
        public void setValue(float value) {
            this.value = value;
        }
 
        public float getValue() {
            return this.value;
        }
 
        public void clamp() {
            if (r < 0)
                r = 0;
            if (g < 0)
                g = 0;
            if (b < 0)
                b = 0;
            if (r > 1)
                r = 1;
            if (g > 1)
                g = 1;
            if (b > 1)
                b = 1;
        }
 
        public void setGammaCorrection(float gammaCorrection) {
            r = FastMath.pow(r, gammaCorrection);
            g = FastMath.pow(g, gammaCorrection);
            b = FastMath.pow(b, gammaCorrection);
        }
 
        /**
         * Retorna o RGBA color
         */
        public ColorRGBA getRGBA() {
            return new ColorRGBA(r,g,b,a);
        }
 
        /**
         * Converte XYZ to RGB color
         */
        public ColorXYZ convertXYZtoRGB() {
            this.r =  3.240479f * x - 1.537150f * y - 0.498535f * z;
            this.g = -0.969256f * x + 1.875992f * y + 0.041556f * z;
            this.b =  0.055648f * x - 0.204043f * y + 1.057311f * z;
            return this;
        }
 
        /**
         * Converte RGB to HSV
         */
        public ColorXYZ convertRGBtoHSV() {
            float minColor = Math.min(Math.min(r,g),b);
            float maxColor = Math.max(Math.max(r,g),b);
            float delta = maxColor - minColor;
 
            this.value = maxColor;                                              // Value
            if ( ! (FastMath.abs(maxColor) < EPSILON)) {
                this.saturation = delta / maxColor;                             // Saturation
            } else {                                                            // r = g = b = 0
                this.saturation = 0.0f;                                         // Saturation = 0
                this.hue = -1;                                                  // Hue = undefined
                return this;
            }
 
            if (FastMath.abs(r - maxColor) < EPSILON)
                this.hue = (g - b) / delta;                                     // between yellow & magenta
            else if (FastMath.abs(g - maxColor) < EPSILON)
                this.hue = 2.0f + (b-r) / delta;                                // between cyan & yellow
            else
                this.hue = 4.0f + (r-g) / delta;                                // between magenta & cyan
 
            this.hue *= 60.0f;                                                  // degrees
 
            if (this.hue < 0.0f )
                this.hue += 360.0f;                                             // positive
            return this;
        }
 
        /**
         * Converte HSV to RGB
         */
        public ColorXYZ convertHSVtoRGB() {
            if (FastMath.abs(saturation) < EPSILON) {                           // achromatic (grey)
                this.r = value;
                this.g = value;
                this.b = value;
                this.a = value;
            }
 
            hue /= 60.0f;							// sector 0 to 5
            int sector = (int) FastMath.floor(hue);
 
            float f = hue - sector;                                             // factorial part of hue
            float p = value * (1.0f - saturation);
            float q = value * (1.0f - saturation * f );
            float t = value * (1.0f - saturation * (1.0f - f));
            switch (sector) {
                case 0:
                    this.r = value;
                    this.g = t;
                    this.b = p;
                    break;
                case 1:
                    this.r = q;
                    this.g = value;
                    this.b = p;
                    break;
                case 2:
                    this.r = p;
                    this.g = value;
                    this.b = t;
                    break;
                case 3:
                    this.r = p;
                    this.g = q;
                    this.b = value;
                    break;
                case 4:
                    this.r = t;
                    this.g = p;
                    this.b = value;
                    break;
                default:                                                        // case 5:
                    this.r = value;
                    this.g = p;
                    this.b = q;
                    break;
            }
            return this;
        }
    }
}

Source Code (Version 2.0)

The following is the code for the SkyDome class.

/*
 * SkyDome.java
 *
 */
 
package yourpackage;
 
import java.nio.FloatBuffer;
import com.jme.bounding.BoundingBox;
import com.jme.image.Image;
import com.jme.image.Texture;
import com.jme.light.DirectionalLight;
import com.jme.light.LightNode;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Dome;
import com.jme.scene.state.CullState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
import com.jme.util.geom.BufferUtils;
import com.jme.util.resource.ResourceLocatorTool;
import com.jmex.effects.LensFlare;
import com.jmex.effects.LensFlareFactory;
 
/**
 * sky gradient based on "A practical analytic model for daylight"
 * by A. J. Preetham, Peter Shirley, Brian Smits (University of Utah)
 * @author Highnik
 */
public class SkyDome3 extends Node {
 
    public static final float INFINITY = 3.3e+38f;
    public static final float EPSILON = 0.000001f;
    private Dome dome;
    private Vector3f cameraPos = new Vector3f();
    // shading parameters
    private float thetaSun;
    private float phiSun;
    private float turbidity = 2.0f;
    private boolean isLinearExpControl;
    private float exposure = 18.0f;
    private float overcast;
    private float gammaCorrection = 2.5f;
    // time parameters
    private float timeOfDay = 0.0f;
    private float julianDay = 0.0f;
    private float latitude = 0.0f;
    private float longitude = 0.0f;
    private float stdMeridian = 0.0f;
    private float sunnyTime = 12.0f;
    private float solarDeclination = 0.0f;
    private float latitudeInRadian = 0.0f;
    private boolean isNight = false;
    // timer control.
    private Timer timer;
    private float currentTime;
    private float updateTime = 0.0f;
    private float timeWarp = 180.0f;
    private boolean renderRequired = true;
    // used at update color
    private float chi;
    private float zenithLuminance;
    private float zenithX;
    private float zenithY;
    private float[] perezLuminance;
    private float[] perezX;
    private float[] perezY;
    private Vector3f sunDirection = new Vector3f();
    private Vector3f sunPosition = new Vector3f();
    private FloatBuffer colorBuf;
    private FloatBuffer normalBuf;
    private Vector3f vertex = new Vector3f();
    private float gamma;
    private float cosTheta;
    private float cosGamma2;
    private float x_value;
    private float y_value;
    private float yClear;
    private float yOver;
    private float _Y;
    private float _X;
    private float _Z;
    private DirectionalLight dr;
    private LightNode sun;
    private LensFlare flare;
    private boolean sunEnabled = true;
    /** Distribution coefficients for the luminance(Y) distribution function */
    private float distributionLuminance[][] = { // Perez distributions
        {0.17872f, -1.46303f}, // a = darkening or brightening of the horizon
        {-0.35540f, 0.42749f}, // b = luminance gradient near the horizon,
        {-0.02266f, 5.32505f}, // c = relative intensity of the circumsolar region
        {0.12064f, -2.57705f}, // d = width of the circumsolar region
        {-0.06696f, 0.37027f}};   // e = relative backscattered light
    /** Distribution coefficients for the x distribution function */
    private float distributionXcomp[][] = {
        {-0.01925f, -0.25922f},
        {-0.06651f, 0.00081f},
        {-0.00041f, 0.21247f},
        {-0.06409f, -0.89887f},
        {-0.00325f, 0.04517f}
    };
    /** Distribution coefficients for the y distribution function */
    private float distributionYcomp[][] = {
        {-0.01669f, -0.26078f},
        {-0.09495f, 0.00921f},
        {-0.00792f, 0.21023f},
        {-0.04405f, -1.65369f},
        {-0.01092f, 0.05291f}
    };
    /** Zenith x value */
    private float zenithXmatrix[][] = {
        {0.00165f, -0.00375f, 0.00209f, 0.00000f},
        {-0.02903f, 0.06377f, -0.03202f, 0.00394f},
        {0.11693f, -0.21196f, 0.06052f, 0.25886f}
    };
    /** Zenith y value */
    private float zenithYmatrix[][] = {
        {0.00275f, -0.00610f, 0.00317f, 0.00000f},
        {-0.04214f, 0.08970f, -0.04153f, 0.00516f},
        {0.15346f, -0.26756f, 0.06670f, 0.26688f}
    };
 
    private LightState lightState;
 
    /** Creates a new instance of SkyDome */
    public SkyDome3() {
        this("SkyDome", 11, 18, 100f);
    }
 
    public SkyDome3(String name) {
        this(name, 11, 18, 100f);
    }
 
    public SkyDome3(String name, int planes, int radialSamples, float radius) {
        this(name, new Vector3f(0, 0, 0), planes, radialSamples, radius);
    }
 
    public SkyDome3(String name, Vector3f center, int planes, int radialSamples, float radius) {
        dome = new Dome(name, center, planes, radialSamples, radius, true);
        dome.setIsCollidable(false);
        dome.setSolidColor(ColorRGBA.black);
        attachChild(dome);
        timer = Timer.getTimer();
        currentTime = timer.getTimeInSeconds();
 
        solarDeclination = calc_solar_declination(julianDay);
        sunnyTime = calc_sunny_time(latitude, solarDeclination);
 
        // create a lens flare effects
        setupLensFlare();
 
        ZBufferState zbuff = DisplaySystem.getDisplaySystem().getRenderer().createZBufferState();
        zbuff.setWritable(false);
        zbuff.setEnabled(true);
        zbuff.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
        setRenderState(zbuff);
 
        setCullHint(CullHint.Never);
        setLightCombineMode(Spatial.LightCombineMode.Off);
        setTextureCombineMode(Spatial.TextureCombineMode.Replace);
 
        CullState cs = DisplaySystem.getDisplaySystem().getRenderer().createCullState();
        cs.setEnabled(true);
        cs.setCullFace(CullState.Face.None);
        dome.setRenderState(cs);
        dome.setCullHint(CullHint.Never);
        dome.setLightCombineMode(LightCombineMode.Off);
        dome.setTextureCombineMode(Spatial.TextureCombineMode.Replace);
    }
 
    /**
     * Set Sun's positon
     */
    public void setSunPosition(Vector3f sunPos) {
        Vector3f pos = new Vector3f();
        pos = FastMath.cartesianToSpherical(sunPos, pos);
        thetaSun = pos.z;
        phiSun = pos.y;
    }
 
    /**
     * Return Sun's position
     */
    public Vector3f getSunPosition() {
        return sunPosition;
    }
 
    /**
     * Convert time to sun position
     * @param time
     *              Sets a time of day between 0 to 24 (6,25 = 6:15 hs)
     */
    public void setSunPosition(float time) {
        float solarTime, solarAltitude, opp, adj, solarAzimuth, cosSolarDeclination, sinSolarDeclination, sinLatitude, cosLatitude;
        this.timeOfDay = time;
 
        sinLatitude = FastMath.sin(latitudeInRadian);
        cosLatitude = FastMath.cos(latitudeInRadian);
        sinSolarDeclination = FastMath.sin(solarDeclination);
        cosSolarDeclination = FastMath.cos(solarDeclination);
 
        // real time
        solarTime = time + (0.170f * FastMath.sin(4f * FastMath.PI * (julianDay - 80f) / 373f) -
                0.129f * FastMath.sin(FastMath.TWO_PI * (julianDay - 8f) / 355f)) +
                (stdMeridian - longitude) / 15;
 
        solarAltitude = FastMath.asin(sinLatitude * sinSolarDeclination -
                cosLatitude * cosSolarDeclination *
                FastMath.cos(FastMath.PI * solarTime / sunnyTime));
 
        opp = -cosSolarDeclination * FastMath.sin(FastMath.PI * solarTime / sunnyTime);
 
        adj = (cosLatitude * sinSolarDeclination + sinLatitude * cosSolarDeclination *
                FastMath.cos(FastMath.PI * solarTime / sunnyTime));
 
        solarAzimuth = FastMath.atan2(opp, adj);
 
        if (solarAltitude > 0.0f) {
 
            isNight = false;
            if ((opp < 0.0f && solarAzimuth < 0.0f) || (opp > 0.0f && solarAzimuth > 0.0f)) {
                solarAzimuth = FastMath.HALF_PI + solarAzimuth;
            } else {
                solarAzimuth = FastMath.HALF_PI - solarAzimuth;
            }
            phiSun = FastMath.TWO_PI - solarAzimuth;
            thetaSun = FastMath.HALF_PI - solarAltitude;
 
            sunDirection.x = dome.getRadius();
            sunDirection.y = phiSun;
            sunDirection.z = solarAltitude;
            sunPosition = FastMath.sphericalToCartesian(sunDirection, sunPosition);
 
            if (this.isSunEnabled()) {
                sun.setLocalTranslation(sunPosition);
            }
 
        } else {
            isNight = true;
        }
 
    }
 
    /**
     * Return if now is night
     */
    public boolean isNight() {
        return isNight;
    }
 
    /**
     * Set Day of year between 0 to 364
     */
    public void setDay(float julianDay) {
        this.julianDay = clamp(julianDay, 0.0f, 365.0f);
        // Solar declination
        solarDeclination = calc_solar_declination(julianDay);
        sunnyTime = calc_sunny_time(latitudeInRadian, solarDeclination);
    }
 
    /**
     * Get Day of year
     */
    public float getDay() {
        return julianDay;
    }
 
    /**
     * Set latitude
     */
    public void setLatitude(float latitude) {
        this.latitude = clamp(latitude, -90.0f, 90.0f);
        latitudeInRadian = FastMath.DEG_TO_RAD * latitude;
        sunnyTime = calc_sunny_time(latitudeInRadian, solarDeclination);
    }
 
    /**
     * Get latitude
     */
    public float getLatitude() {
        return latitude;
    }
 
    /**
     * Set longitude
     */
    public void setLongitude(float longitude) {
        this.longitude = longitude;
    }
 
    /**
     * Get longitude
     */
    public float getLongitude() {
        return longitude;
    }
 
    /**
     * Set standar meridian
     * @param stdMeridian
     *                      TimeZone * 15
     */
    public void setStandardMeridian(float stdMeridian) {
        this.stdMeridian = stdMeridian;
    }
 
    /**
     * Get standar meridian
     */
    public float getStandardMeridian() {
        return stdMeridian;
    }
 
    public void setTurbidity(float turbidity) {
        this.turbidity = clamp(turbidity, 1.0f, 512.0f);
    }
 
    /**
     * Set Exposure factor
     */
    public void setExposure(boolean isLinearExpControl, float exposure) {
        this.isLinearExpControl = isLinearExpControl;
        this.exposure = 1.0f / clamp(exposure, 1.0f, INFINITY);
    }
 
    /**
     * Set Over Cast factor
     */
    public void setOvercastFactor(float overcast) {
        this.overcast = clamp(overcast, 0.0f, 1.0f);
    }
 
    /**
     * Set gamma correction factor
     */
    public void setGammaCorrection(float gamma) {
        this.gammaCorrection = 1.0f / clamp(gamma, EPSILON, INFINITY);
    }
 
    /**
     * Seconds to update
     */
    public void setUpdateTime(float seconds) {
        this.updateTime = seconds;
    }
 
    public float getUpdateTime() {
        return updateTime;
    }
 
    /**
     * if updateTime = 1 and timeWarp = 1, every seconds will be updated
     */
    public void setTimeWarp(float timeWarp) {
        this.timeWarp = timeWarp;
    }
 
    public float getTimeWarp() {
        return timeWarp;
    }
 
    public void update() {
        if (updateTime > 0.0f) {
            if ((timer.getTimeInSeconds() - currentTime) >= updateTime) {
                currentTime = timer.getTimeInSeconds();
                timeOfDay += updateTime * timeWarp / 3600f;
                setSunPosition(timeOfDay);
                renderRequired = true;
            }
        }
        cameraPos = DisplaySystem.getDisplaySystem().getRenderer().getCamera().getLocation();
        dome.setLocalTranslation(new Vector3f(cameraPos.x, 0.0f, cameraPos.z));
    }
 
    /**
     * update Sky color
     */
    public void render() {
 
        if (!renderRequired) {
            return;
        }
 
        if (isNight) {
            dome.setSolidColor(ColorRGBA.black);
            return;
        }
 
        // get zenith luminance
        chi = ((4.0f / 9.0f) - (turbidity / 120.0f)) * (FastMath.PI - (2.0f * thetaSun));
        zenithLuminance = ((4.0453f * turbidity) - 4.9710f) * FastMath.tan(chi) - (0.2155f * turbidity) + 2.4192f;
        if (zenithLuminance < 0.0f) {
            zenithLuminance = -zenithLuminance;
        }
 
        // get x / y zenith
        zenithX = getZenith(zenithXmatrix, thetaSun, turbidity);
        zenithY = getZenith(zenithYmatrix, thetaSun, turbidity);
 
        // get perez function parameters
        perezLuminance = getPerez(distributionLuminance, turbidity);
        perezX = getPerez(distributionXcomp, turbidity);
        perezY = getPerez(distributionYcomp, turbidity);
 
        // make some precalculation
        zenithX = perezFunctionO1(perezX, thetaSun, zenithX);
        zenithY = perezFunctionO1(perezY, thetaSun, zenithY);
        zenithLuminance = perezFunctionO1(perezLuminance, thetaSun, zenithLuminance);
 
        // build sun direction vector
        sunDirection.x = FastMath.cos(FastMath.HALF_PI - thetaSun) * FastMath.cos(phiSun);
        sunDirection.y = FastMath.sin(FastMath.HALF_PI - thetaSun);
        sunDirection.z = FastMath.cos(FastMath.HALF_PI - thetaSun) * FastMath.sin(phiSun);
        sunDirection.normalize();
 
        // trough all vertices
        normalBuf = dome.getNormalBuffer();
        colorBuf = dome.getColorBuffer();
 
        for (int i = 0; i < (normalBuf.limit() / 3); i++) {
 
            BufferUtils.populateFromBuffer(vertex, normalBuf, i);
 
            // angle between sun and vertex
            gamma = FastMath.acos(vertex.dot(sunDirection));
 
            if (vertex.y < 0.05f) {
                vertex.y = 0.05f;
            }
 
            cosTheta = 1.0f / vertex.y;
            cosGamma2 = FastMath.sqr(FastMath.cos(gamma));
 
            // Compute x,y values
            x_value = perezFunctionO2(perezX, cosTheta, gamma, cosGamma2, zenithX);
            y_value = perezFunctionO2(perezY, cosTheta, gamma, cosGamma2, zenithY);
 
            // luminance(Y) for clear & overcast sky
            yClear = perezFunctionO2(perezLuminance, cosTheta, gamma, cosGamma2, zenithLuminance);
            yOver = (1.0f + 2.0f * vertex.y) / 3.0f;
 
            _Y = FastMath.LERP(overcast, yClear, yOver);
            _X = (x_value / y_value) * _Y;
            _Z = ((1.0f - x_value - y_value) / y_value) * _Y;
 
            ColorXYZ color = new ColorXYZ(_X, _Y, _Z);
            color.convertXYZtoRGB();
            color.convertRGBtoHSV();
 
            if (isLinearExpControl) {                                       // linear scale
                color.setValue(color.getValue() * exposure);
            } else {                                                        // exp scale
                color.setValue(1.0f - FastMath.exp(-exposure * color.getValue()));
            }
            color.convertHSVtoRGB();
 
            // gamma control
            color.setGammaCorrection(gammaCorrection);
 
            // clamp rgb between 0.0 - 1.0
            color.clamp();
 
            // change the color
            BufferUtils.setInBuffer(color.getRGBA(), colorBuf, i);
        }
        renderRequired = false;
    }
 
    /**
     * Returns a LightNode that represents the Sun
     */
    public LightNode getSun() {
        return sun;
    }
 
    /**
     * Set the rootNode to flare
     */
    public void setRootNode(Node value) {
        if (flare != null) {
            flare.setRootNode(value);
        }
    }
 
    /**
     * Set a intensity to Flare
     */
    public void setIntensity(float value) {
        if (flare != null) {
            flare.setIntensity(value);
        }
    }
 
    /**
     * Attach the lightstate to scene
     */
    public void setTarget(Spatial node) {
        node.setRenderState(lightState);
    }
 
    public void setSunEnabled(boolean enable) {
        this.sunEnabled = enable;
        sun.getLight().setEnabled(enable);
    }
 
    public boolean isSunEnabled() {
        return sunEnabled;
    }
 
    private float calc_solar_declination(float jDay) {
        return (0.4093f * FastMath.sin(FastMath.TWO_PI * (284f + jDay) / 365f));
    }
 
    private float calc_sunny_time(float lat, float solarDeclin) {
        // Time of hours over horizon
        sunnyTime = (2.0f * FastMath.acos(-FastMath.tan(lat) * FastMath.tan(solarDeclin)));
        sunnyTime = (sunnyTime * FastMath.RAD_TO_DEG) / 15;
        return sunnyTime;
    }
 
    /**
     * Create Lens flare effect
     */
    private void setupLensFlare() {
 
        dr = new DirectionalLight();
        dr.setEnabled(true);
        dr.setDiffuse(ColorRGBA.white);
        dr.setAmbient(ColorRGBA.gray);
        dr.setDirection(new Vector3f(0.0f, 0.0f, 0.0f));
 
        lightState = DisplaySystem.getDisplaySystem().getRenderer().createLightState();
        lightState.setEnabled(true);
        lightState.attach(dr);
 
        sun = new LightNode("SunNode");
        sun.setLight(dr);
 
        Vector3f min2 = new Vector3f(-0.1f, -0.1f, -0.1f);
        Vector3f max2 = new Vector3f(0.1f, 0.1f, 0.1f);
        Box lightBox = new Box("lightbox", min2, max2);
        lightBox.setModelBound(new BoundingBox());
        lightBox.updateModelBound();
        sun.attachChild(lightBox);
        lightBox.setLightCombineMode(Spatial.LightCombineMode.Off);
 
        // Setup the lensflare textures.
        TextureState[] tex = new TextureState[4];
        tex[0] = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
        tex[0].setTexture(
                TextureManager.loadTexture(
                ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, "flare1.png"),
                Texture.MinificationFilter.Trilinear,
                Texture.MagnificationFilter.Bilinear,
                Image.Format.RGBA8,
                1.0f,
                true));
        tex[0].setEnabled(true);
 
        tex[1] = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
        tex[1].setTexture(
                TextureManager.loadTexture(
                ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, "flare2.png"),
                Texture.MinificationFilter.Trilinear,
                Texture.MagnificationFilter.Bilinear));
        tex[1].setEnabled(true);
 
        tex[2] = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
        tex[2].setTexture(
                TextureManager.loadTexture(
                ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, "flare3.png"),
                Texture.MinificationFilter.Trilinear,
                Texture.MagnificationFilter.Bilinear));
        tex[2].setEnabled(true);
 
        tex[3] = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
        tex[3].setTexture(
                TextureManager.loadTexture(
                ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, "flare4.png"),
                Texture.MinificationFilter.Trilinear,
                Texture.MagnificationFilter.Bilinear));
        tex[3].setEnabled(true);
 
        flare = LensFlareFactory.createBasicLensFlare("flare", tex);
 
        flare.setIntensity(0.5f);
 
        flare.setModelBound(new BoundingBox());
        flare.setCullHint(CullHint.Never);
        flare.updateModelBound();
 
        flare.setTriangleAccurateOcclusion(true);
 
        // notice that it comes at the end
        sun.attachChild(flare);
        attachChild(sun);
    }
 
    private float[] getPerez(float[][] distribution, float turbidity) {
        float[] perez = new float[5];
        perez[0] = distribution[0][0] * turbidity + distribution[0][1];
        perez[1] = distribution[1][0] * turbidity + distribution[1][1];
        perez[2] = distribution[2][0] * turbidity + distribution[2][1];
        perez[3] = distribution[3][0] * turbidity + distribution[3][1];
        perez[4] = distribution[4][0] * turbidity + distribution[4][1];
        return perez;
    }
 
    private float getZenith(float[][] zenithMatrix, float theta, float turbidity) {
        float theta2 = theta * theta;
        float theta3 = theta * theta2;
 
        return (zenithMatrix[0][0] * theta3 + zenithMatrix[0][1] * theta2 + zenithMatrix[0][2] * theta + zenithMatrix[0][3]) * turbidity * turbidity +
                (zenithMatrix[1][0] * theta3 + zenithMatrix[1][1] * theta2 + zenithMatrix[1][2] * theta + zenithMatrix[1][3]) * turbidity +
                (zenithMatrix[2][0] * theta3 + zenithMatrix[2][1] * theta2 + zenithMatrix[2][2] * theta + zenithMatrix[2][3]);
    }
 
    private float perezFunctionO1(float[] perezCoeffs, float thetaSun, float zenithValue) {
        float val = (1.0f + perezCoeffs[0] * FastMath.exp(perezCoeffs[1])) *
                (1.0f + perezCoeffs[2] * FastMath.exp(perezCoeffs[3] * thetaSun) + perezCoeffs[4] * FastMath.sqr(FastMath.cos(thetaSun)));
        return zenithValue / val;
    }
 
    private float perezFunctionO2(float[] perezCoeffs, float cosTheta, float gamma, float cosGamma2, float zenithValue) {
        return zenithValue * (1.0f + perezCoeffs[0] * FastMath.exp(perezCoeffs[1] * cosTheta)) *
                (1.0f + perezCoeffs[2] * FastMath.exp(perezCoeffs[3] * gamma) + perezCoeffs[4] * cosGamma2);
    }
 
    /**
     * clamp the value between min and max values
     */
    private float clamp(float value, float min, float max) {
        if (value < min) {
            return min;
        } else if (value > max) {
            return max;
        } else {
            return value;
        }
    }
 
    class ColorXYZ {
 
        private float x = 0.0f;
        private float y = 0.0f;
        private float z = 0.0f;
        private float r = 0.0f;
        private float g = 0.0f;
        private float b = 0.0f;
        private float a = 1.0f;
        private float hue = 0.0f;
        private float saturation = 0.0f;
        private float value = 0.0f;
 
        public ColorXYZ(float x, float y, float z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }
 
        public void setValue(float value) {
            this.value = value;
        }
 
        public float getValue() {
            return this.value;
        }
 
        public void clamp() {
            if (r < 0) {
                r = 0;
            }
            if (g < 0) {
                g = 0;
            }
            if (b < 0) {
                b = 0;
            }
            if (r > 1) {
                r = 1;
            }
            if (g > 1) {
                g = 1;
            }
            if (b > 1) {
                b = 1;
            }
        }
 
        public void setGammaCorrection(float gammaCorrection) {
            r = FastMath.pow(r, gammaCorrection);
            g = FastMath.pow(g, gammaCorrection);
            b = FastMath.pow(b, gammaCorrection);
        }
 
        /**
         * Retorna o RGBA color
         */
        public ColorRGBA getRGBA() {
            return new ColorRGBA(r, g, b, a);
        }
 
        /**
         * Converte XYZ to RGB color
         */
        public void convertXYZtoRGB() {
            this.r = 3.240479f * x - 1.537150f * y - 0.498530f * z;
            this.g = -0.969256f * x + 1.875991f * y + 0.041556f * z;
            this.b = 0.055648f * x - 0.204043f * y + 1.057311f * z;
        }
 
        /**
         * Converte RGB to HSV
         */
        public void convertRGBtoHSV() {
            float minColor = Math.min(Math.min(r, g), b);
            float maxColor = Math.max(Math.max(r, g), b);
            float delta = maxColor - minColor;
 
            this.value = maxColor;                                              // Value
            if (!(FastMath.abs(maxColor) < EPSILON)) {
                this.saturation = delta / maxColor;                             // Saturation
            } else {                                                            // r = g = b = 0
                this.saturation = 0.0f;                                         // Saturation = 0
                this.hue = -1;                                                  // Hue = undefined
                return;
            }
 
            if (FastMath.abs(r - maxColor) < EPSILON) {
                this.hue = (g - b) / delta;
            } // between yellow & magenta
            else if (FastMath.abs(g - maxColor) < EPSILON) {
                this.hue = 2.0f + (b - r) / delta;
            } // between cyan & yellow
            else {
                this.hue = 4.0f + (r - g) / delta;
            }                                // between magenta & cyan
 
            this.hue *= 60.0f;                                                  // degrees
 
            if (this.hue < 0.0f) {
                this.hue += 360.0f;
            }                                             // positive
        }
 
        /**
         * Converte HSV to RGB
         */
        public ColorXYZ convertHSVtoRGB() {
            if (FastMath.abs(saturation) < EPSILON) {                           // achromatic (grey)
                this.r = value;
                this.g = value;
                this.b = value;
                this.a = value;
            }
 
            hue /= 60.0f;             // sector 0 to 5
            int sector = (int) FastMath.floor(hue);
 
            float f = hue - sector;                                             // factorial part of hue
            float p = value * (1.0f - saturation);
            float q = value * (1.0f - saturation * f);
            float t = value * (1.0f - saturation * (1.0f - f));
            switch (sector) {
                case 0:
                    this.r = value;
                    this.g = t;
                    this.b = p;
                    break;
                case 1:
                    this.r = q;
                    this.g = value;
                    this.b = p;
                    break;
                case 2:
                    this.r = p;
                    this.g = value;
                    this.b = t;
                    break;
                case 3:
                    this.r = p;
                    this.g = q;
                    this.b = value;
                    break;
                case 4:
                    this.r = t;
                    this.g = p;
                    this.b = value;
                    break;
                default:                                                        // case 5:
                    this.r = value;
                    this.g = p;
                    this.b = q;
                    break;
            }
            return this;
        }
    }
}

Example Source Code

To use SkyDome write the following:

package yourpackage;
 
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingSphere;
import com.jme.image.Texture;
import com.jme.math.Vector3f;
import com.jme.scene.state.CullState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jmex.terrain.TerrainPage;
import com.jmex.terrain.util.FaultFractalHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;
import javax.swing.ImageIcon;
 
/**
 * TestSkyDome.java
 *
 * @author Highnik
 */
public class TestSkyDome extends SimpleGame {
 
    private SkyDome dome;
    private TerrainPage terrain;
    private Vector3f camPos = new Vector3f();
 
    /**
     * Update time
     */
    protected void simpleUpdate() {
        camPos.x = cam.getLocation().x;
        camPos.y = terrain.getHeight(cam.getLocation()) + 10;
        camPos.z = cam.getLocation().z;
        cam.setLocation(camPos);
        dome.update();
    }
 
    /**
     * update colors
     */
    protected void simpleRender() {
        dome.render();
    }
 
    /*
     * (non-Javadoc)
     *
     * @see com.jme.app.SimpleGame#initGame()
     */
    protected void simpleInitGame() {
        display.setTitle("TestSkyDome");
 
        lightState.setTwoSidedLighting(true);
 
        setupTerrain();
 
        setupSkyDome();
    }
 
    /**
     * add terrain
     */
    private void setupTerrain() {
 
        CullState cs = DisplaySystem.getDisplaySystem().getRenderer().createCullState();
        cs.setCullMode(CullState.CS_BACK);
        cs.setEnabled(true);
        rootNode.setRenderState(cs);
 
        FaultFractalHeightMap heightMap = new FaultFractalHeightMap(257, 32, 0, 255, 0.75f);
        Vector3f terrainScale = new Vector3f(10,0.75f,10);
        heightMap.setHeightScale( 0.001f);
        terrain = new TerrainPage("Terrain", 33, heightMap.getSize(), terrainScale, heightMap.getHeightMap(), false);
        terrain.setDetailTexture(1, 128);
        rootNode.attachChild(terrain);
 
        ProceduralTextureGenerator pt = new ProceduralTextureGenerator(heightMap);
        pt.addTexture(new ImageIcon(TestSkyDome.class.getClassLoader().getResource("jmetest/data/texture/grassb.png")), -128, 0, 155);
        pt.addTexture(new ImageIcon(TestSkyDome.class.getClassLoader().getResource("jmetest/data/texture/dirt.jpg")), 0, 155, 220);
        pt.addTexture(new ImageIcon(TestSkyDome.class.getClassLoader().getResource("jmetest/data/texture/highest.jpg")), 155, 220, 512);
        pt.createTexture(512);
 
        TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
        ts.setEnabled(true);
 
        Texture t1 = TextureManager.loadTexture(pt.getImageIcon().getImage(),
                Texture.MM_LINEAR_LINEAR,
                Texture.FM_LINEAR,
                true);
        ts.setTexture(t1, 0);
 
        Texture t2 = TextureManager.loadTexture(TestSkyDome.class.getClassLoader().getResource("jmetest/data/texture/Detail.jpg"),
                Texture.MM_LINEAR_LINEAR,
                Texture.FM_LINEAR);
        ts.setTexture(t2, 1);
        t2.setWrap(Texture.WM_WRAP_S_WRAP_T);
 
        t1.setApply(Texture.AM_COMBINE);
        t1.setCombineFuncRGB(Texture.ACF_MODULATE);
        t1.setCombineSrc0RGB(Texture.ACS_TEXTURE);
        t1.setCombineOp0RGB(Texture.ACO_SRC_COLOR);
        t1.setCombineSrc1RGB(Texture.ACS_PRIMARY_COLOR);
        t1.setCombineOp1RGB(Texture.ACO_SRC_COLOR);
        t1.setCombineScaleRGB(1.0f);
 
        t2.setApply(Texture.AM_COMBINE);
        t2.setCombineFuncRGB(Texture.ACF_ADD_SIGNED);
        t2.setCombineSrc0RGB(Texture.ACS_TEXTURE);
        t2.setCombineOp0RGB(Texture.ACO_SRC_COLOR);
        t2.setCombineSrc1RGB(Texture.ACS_PREVIOUS);
        t2.setCombineOp1RGB(Texture.ACO_SRC_COLOR);
        t2.setCombineScaleRGB(1.0f);
        terrain.setRenderState(ts);
    }
 
    /**
     * Initialize SkyDome
     */
    private void setupSkyDome() {
        dome = new SkyDome("skyDome", new Vector3f(0.0f,0.0f,0.0f), 11, 18, 850.0f);
        dome.setModelBound(new BoundingSphere());
        dome.updateModelBound();
        dome.updateRenderState();
        dome.setUpdateTime(5.0f);
        dome.setTimeWarp(180.0f);
        dome.setDay(267);
        dome.setLatitude(-22.9f);
        dome.setLongitude(-47.083f);
        dome.setStandardMeridian(-45.0f);
        dome.setSunPosition(5.75f);             // 5:45 am
        dome.setTurbidity(2.0f);
        dome.setSunEnabled(true);
        dome.setExposure(true, 18.0f);
        dome.setOvercastFactor(0.0f);
        dome.setGammaCorrection(2.5f);
        dome.setRootNode(rootNode);
        dome.setIntensity(1.0f);
        // setup a target to LightNode, if you dont want terrain with light's effect remove it.
        dome.setTarget(terrain);
        rootNode.attachChild(dome);
    }
 
    /**
     * Entry point
     */
    public static void main(String[] args) {
        TestSkyDome app = new TestSkyDome();
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }
}
/var/wwworg/wiki/data/pages/skydome.txt · Last modified: 2010/03/06 14:55 by zathras
You are here: jME Wikiskydome