// // 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 ); }; });