325 lines · 12.4 KB · JavaScript
RawDownload
////  sunModel.js//  scripts/developer////  Created by Nissim Hadar on 2017/12/27.//  Copyright 2013 High Fidelity, Inc.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html////  Sun angle is based on the NOAA model - see https://www.esrl.noaa.gov/gmd/grad/solcalc///(function() {    // Utility functions for trig. calculations    function toRadians(angle_degs) {        return angle_degs * (Math.PI / 180);    }    function toDegrees(angle_rads) {        return angle_rads * (180.0 / Math.PI);    }     // Code to check if Daylight Savings is active    Date.prototype.stdTimezoneOffset = function() {        var fy = this.getFullYear();        if (!Date.prototype.stdTimezoneOffset.cache.hasOwnProperty(fy)) {            var maxOffset = new Date(fy, 0, 1).getTimezoneOffset();            var monthsTestOrder = [6, 7, 5, 8, 4, 9, 3, 10, 2, 11, 1];             for(var mi = 0;mi < 12; ++mi) {                var offset = new Date(fy, monthsTestOrder[mi], 1).getTimezoneOffset();                if (offset != maxOffset) {                     maxOffset = Math.max(maxOffset, offset);                    break;                }            }            Date.prototype.stdTimezoneOffset.cache[fy] = maxOffset;        }        return Date.prototype.stdTimezoneOffset.cache[fy];    };     // Cache the result for per year stdTimezoneOffset so that you don't need to recalculate it when testing multiple dates in     // the same year.    Date.prototype.stdTimezoneOffset.cache = {};     Date.prototype.isDST = function() {        return this.getTimezoneOffset() < this.stdTimezoneOffset();     };         // The Julian Date is the number of days (fractional) that have elapsed since Jan 1st, 4713 BC    // See https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html     function getJulianDay(dateTime) {        var month = dateTime.getMonth() + 1;        var day   = dateTime.getDate()  + 1;        var year  = dateTime.getFullYear();                if (month <=  2) {            year  -=  1;            month += 12;        }         var A = Math.floor(year / 100);        var B = 2 - A + Math.floor(A / 4);        return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5;    }        function getMinutes(dateTime) {        var hours   = dateTime.getHours();        var minutes = dateTime.getMinutes();        var seconds = dateTime.getSeconds();         if (Date.prototype.isDST()) {            hour -= 1;        }         return hours * 60 + minutes + seconds / 60.0;    }     function calcGeomMeanAnomalySun(t) {        var M = 357.52911 + t * (35999.05029 - 0.0001537 * t);        return M;		// in degrees    }     function calcSunEqOfCenter(t) {        var m = calcGeomMeanAnomalySun(t);        var mrad = toRadians(m);        var sinm = Math.sin(mrad);        var sin2m = Math.sin(mrad + mrad);        var sin3m = Math.sin(mrad + mrad + mrad);        var C = sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289;        return C;		// in degrees    }     function calcGeomMeanLongSun(t) {        var L0 = 280.46646 + t * (36000.76983 + t*(0.0003032))        while(L0 > 360.0) {            L0 -= 360.0        }        while(L0 < 0.0) {            L0 += 360.0        }        return L0		// in degrees    }     function calcSunTrueLong(t) {        var l0 = calcGeomMeanLongSun(t);        var c  = calcSunEqOfCenter(t);        var O  = l0 + c;        return O;		// in degrees    }     function calcSunApparentLong(t) {        var o      = calcSunTrueLong(t);        var omega  = 125.04 - 1934.136 * t;        var lambda = o - 0.00569 - 0.00478 * Math.sin(toRadians(omega));        return lambda;		// in degrees    }     function calcMeanObliquityOfEcliptic(t) {        var seconds = 21.448 - t * (46.8150 + t * (0.00059 - t * (0.001813)));        var e0      = 23.0 + (26.0 + (seconds / 60.0)) / 60.0;        return e0;		// in degrees    }     function calcObliquityCorrection(t) {        var e0    = calcMeanObliquityOfEcliptic(t);        var omega = 125.04 - 1934.136 * t;        var e     = e0 + 0.00256 * Math.cos(toRadians(omega));        return e;		// in degrees    }         function calcSunDeclination(t) {        var e      = calcObliquityCorrection(t);        var lambda = calcSunApparentLong(t);         var sint  = Math.sin(toRadians(e)) * Math.sin(toRadians(lambda));        var theta = toDegrees(Math.asin(sint));        return theta;		// in degrees    }     function calcEccentricityEarthOrbit(t) {        var e = 0.016708634 - t * (0.000042037 + 0.0000001267 * t);        return e;		// unitless    }        function calcEquationOfTime(t) {        // Converts from "mean" solar day (i.e. days that are exactly 24 hours)        // to apparent solar day (as observed)        // This is essentially the east-west component of the analemma.        //        // This is caused by 2 effects:  the obliquity of the ecliptic, the eccentricity of earth's orbit        var epsilon = calcObliquityCorrection(t);        var l0 = calcGeomMeanLongSun(t);        var e  = calcEccentricityEarthOrbit(t);        var m  = calcGeomMeanAnomalySun(t);         var y = Math.tan(toRadians(epsilon) / 2.0);        y *= y;         var sin2l0 = Math.sin(2.0 * toRadians(l0));        var sinm   = Math.sin(toRadians(m));        var cos2l0 = Math.cos(2.0 * toRadians(l0));        var sin4l0 = Math.sin(4.0 * toRadians(l0));        var sin2m  = Math.sin(2.0 * toRadians(m));         var Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 -             0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m;                    return toDegrees(Etime) * 4.0;	// in minutes of time    }     function calcSunTrueAnomaly(t) {        var m = calcGeomMeanAnomalySun(t);        var c = calcSunEqOfCenter(t);        var v = m + c;        return v;		// in degrees    }     function calcSunRadVector(t) {        var v = calcSunTrueAnomaly(t);        var e = calcEccentricityEarthOrbit(t);        var R = (1.000001018 * (1 - e * e)) / (1 + e * Math.cos(toRadians(v)));        return R;		// in AUs    }     function parseJSON(json) {        try {            return JSON.parse(json);        } catch (e) {            return undefined;        }    }        var COMPUTATION_CYCLE = 5000;       // Run every 5 seconds    this.preload = function(entityID) { // We don't have the entityID before the preload        // Define user data        var userDataProperties = {            "userData": "{ \"latitude\": 47.0, \"longitude\": 122.0 }"        };        Entities.editEntity(entityID, userDataProperties);          Script.setInterval(            function() {                // Read back user data                var userData = Entities.getEntityProperties(entityID, 'userData').userData;                var data = parseJSON(userData);                 var latitude_degs  = data.latitude;                var longitude_degs = data.longitude;                                // These are used a lot                var latitude  = toRadians(latitude_degs);                var longitude = toRadians(longitude_degs);                                var dateTime = new Date();                                var julianDay         = getJulianDay(dateTime);                var localTimeMinutes  = getMinutes(dateTime);                var timeZone          = -dateTime.getTimezoneOffset() / 60;                var totalTime         = julianDay + localTimeMinutes/1440.0 - timeZone / 24.0;                                // J2000.0 is the epoch starting Jan 1st, 2000 (noon), expressed as a Julian day                var J2000             = 2451545.0                                // Number of years that have passed since J2000.0                var julianDayModified = (J2000 - 2451545.0)/36525.0;                                var eqTime            = calcEquationOfTime(julianDayModified)                var theta_rads        = toRadians(calcSunDeclination(julianDayModified));                var solarTimeFix      = eqTime + 4.0 * longitude_degs - 60.0 * timeZone;                var earthRadVec       = calcSunRadVector(julianDayModified);                                var trueSolarTime = localTimeMinutes + solarTimeFix;                while (trueSolarTime > 1440) {                    trueSolarTime   -= 1440;                }                                var hourAngle = trueSolarTime / 4.0 - 180.0;                if (hourAngle < -180.0) {                    hourAngle += 360.0;                }                var hourAngleRadians = toRadians(hourAngle);                 var csz = Math.sin(latitude) * Math.sin(theta_rads) +                           Math.cos(latitude) * Math.cos(theta_rads) * Math.cos(hourAngleRadians);                csz     = Math.min(1.0, Math.max(-1.0, csz));                                var zenith_degs  = toDegrees(Math.acos(csz));                var azDenom = ( Math.cos(latitude) * Math.sin(toRadians(zenith_degs)));                if (Math.abs(azDenom) > 0.001) {                    azRad = (( Math.sin(latitude) * Math.cos(toRadians(zenith_degs)) ) - Math.sin(theta_rads)) / azDenom;                    if (Math.abs(azRad) > 1.0) {                        if (azRad < 0.0) {                            azRad = -1.0;                        } else {                            azRad = 1.0;                        }                    }                    var solarAzimuth_degs = 180.0 - toDegrees(Math.acos(azRad))                    if (hourAngle > 0.0) {                        solarAzimuth_degs = -solarAzimuth_degs;                    }                } else {                    if (latitude_degs > 0.0) {                        solarAzimuth_degs = 180.0;                    } else {                         solarAzimuth_degs = 0.0;                    }                }                if (solarAzimuth_degs < 0.0) {                    solarAzimuth_degs += 360.0;                }                 // Atmospheric Refraction correction                var exoatmElevation = 90.0 - zenith_degs;                if (exoatmElevation > 85.0) {                    var refractionCorrection = 0.0;                } else {                    var te = Math.tan(toRadians(exoatmElevation));                    if (exoatmElevation > 5.0) {                        var refractionCorrection = 58.1 / te - 0.07 / (te * te * te) + 0.000086 / (te * te * te * te * te);                    } else if (exoatmElevation > -0.575) {                        var refractionCorrection =                             1735.0 + exoatmElevation  *                             (-518.2 + exoatmElevation * (103.4 + exoatmElevation * (-12.79 + exoatmElevation * 0.711)));                    } else {                        var refractionCorrection = -20.774 / te;                    }                    refractionCorrection = refractionCorrection / 3600.0;                }                 var solarZenith = zenith_degs - refractionCorrection;                var solarAltitude_degs = 90.0 - solarZenith; // aka solar elevation                               // Convert to XYZ                var solarAltitude = toRadians(solarAltitude_degs);                var solarAzimuth  = toRadians(solarAzimuth_degs);                                var xPos =  Math.cos(solarAltitude) * Math.sin(solarAzimuth);                var zPos =  Math.cos(solarAltitude) * Math.cos(solarAzimuth);                var yPos = -Math.sin(solarAltitude);                 // Compute intensity, modelling the atmosphere as a spherical shell                // The optical air mass ratio at zenith is 1.0, and around 38.0 at the horizon                // The ratio is limited between 1 and 38                var EARTH_RADIUS_KM = 6371.0;                var ATMOSPHERE_THICKNESS_KM = 9.0;                var r = EARTH_RADIUS_KM / ATMOSPHERE_THICKNESS_KM;                 var opticalAirMassRatio = Math.sqrt(r * r * csz * csz + 2 * r + 1) - r * csz;                opticalAirMassRatio = Math.min(38.0, Math.max(1.0, opticalAirMassRatio));                                Entities.editEntity(                    entityID, {                         keyLight : {                            direction: { x: xPos, y: yPos, z: zPos },                            intensity: 1.0 / opticalAirMassRatio                        }                    }                );            },             COMPUTATION_CYCLE        );    };});