// JavaScript source code // Template by Antony Evans // sunset API used with attribution to https://sunrise-sunset.org/api (function () { var sun; var sunlight; var latitude = 0; // will move to userdata var longitude = 0; var dayLength; // in seconds will move to userdata var time = 0.9; // 0-1 for how far through day we are var sunDistance = 2500; // radius for distance of sun var updateFreq = 100; // how often we refresh sun position var starMap = ""; const RADPERDEGREE = 0.0174533; var useRealTime = true; var nightLight; var timer1; var timer2; var sunrise = 0.2484837962962963; var sunset = 0.7506828703703704; var sunData = {}; var civil_twilight_begin = 0.22568287037037038; // these defaults are based on London on spring equinox var civil_twilight_end = 0.7790393518518518; var nautical_twilight_begin = 0.19864583333333333; var nautical_twilight_end = 0.8060763888888889; var astronomical_twilight_begin = 0.1705324074074074; var astronomical_twilight_end = 0.8341898148148148; var startSunset = 0.4; //need to adjust for 0 being sunrise to 0 being midnight var startTwilight = 0.12; //need to adjust function MyObject() { // Can be used a constructor } MyObject.prototype = { preload: function (entityID) { // When an entity instance has been loaded into the world, this is triggered. Script.setTimeout(function () { // this timeout is to ensure the sun and light loads before the script starts var properties = Entities.getEntityProperties(entityID); timer1 = Script.setInterval(function () { // regularly check for updates to USerData var tempLongitude = longitude; var tempLatitude = latitude; properties = Entities.getEntityProperties(entityID); var userData = JSON.parse(properties.userData); dayLength = userData.dayLength; latitude = RADPERDEGREE * userData.latitude; longitude = RADPERDEGREE * userData.longitude; nightLight = userData.nightLight; starMap = userData.nightSkyURL; useRealTime = userData.useRealTime; if ((longitude !== tempLongitude) || (latitude !== tempLatitude)) { // update sunset/sunrise times because we changed userdata location print("updating sunrise"); var request = new XMLHttpRequest(); var lat = latitude / RADPERDEGREE + 0.0001; //small addition fixes bug when supply integers not floats var lng = longitude / RADPERDEGREE + 0.0001; request.open('GET', 'https://api.sunrise-sunset.org/json?lat=' + lat.toString() + '&lng=' + lng.toString() + '&date=today', false); request.onreadystatechange = function () { var status = request.status; if ((status >= 200) && (status < 400)) { if (request.responseText) { var sunData = JSON.parse(request.responseText); print(JSON.stringify(request.responseText)); sunrise = convertDateToDayFraction(sunData.results.sunrise); sunset = convertDateToDayFraction(sunData.results.sunset); civil_twilight_begin = convertDateToDayFraction(sunData.results.civil_twilight_begin); civil_twilight_end = convertDateToDayFraction(sunData.results.civil_twilight_end); nautical_twilight_begin = convertDateToDayFraction(sunData.results.nautical_twilight_begin); nautical_twilight_end = convertDateToDayFraction(sunData.results.nautical_twilight_end); astronomical_twilight_begin = convertDateToDayFraction(sunData.results.astronomical_twilight_begin); astronomical_twilight_end = convertDateToDayFraction(sunData.results.astronomical_twilight_end); //print("sunrise: " + sunrise.toString()); //print("sunset: " + sunset.toString()); //print("nautical_twilight_end: " + nautical_twilight_begin.toString()); //print("time: " + time.toString()); startSunset = - Math.sin(calcAngleFromTime(nautical_twilight_begin)) * Math.cos(latitude); //print("startSunset: " + startSunset.toString()); startTwilight = - Math.sin(calcAngleFromTime(civil_twilight_begin)) * Math.cos(latitude); //print("startTwilight: " + startTwilight.toString()); } } }; // Send request request.send(); } }, 1000); timer2 = Script.setInterval(function () { // this is our main script loop that moves the sun etc // time is a fraction between 0 and 1 for how far through the day we are if (useRealTime) { var d = new Date(); var n = d.getTimezoneOffset(); var h = d.getHours(); var m = d.getMinutes(); var s = d.getSeconds(); var ms = d.getMilliseconds(); time = (h*60 + m + n + (s / 60) + (ms / 60000)) / (24*60) // note this time is UTC fraction of day to match sunset/sunrise times which return in UTC time = ((time < 0) ? 1 + time : time) ; time = ((time >= 1) ? time - 1 : time) ; //print("time: " + h.toString() + ":" + m.toString()); } else { time += updateFreq/(1000 * dayLength); time = ((time < 1) ? time : 0) ; } // print("time is: " + time.toString()); setSun(entityID, time, properties.position); // print("Time: " + time); }, updateFreq); }, 1500); }, unload: function (entityID) { Script.clearInterval(timer1); Script.clearInterval(timer2); } } function convertDateToDayFraction(timeString) { var seconds = 0; var time = timeString.split(" "); if (time[1] == "PM") {seconds += 12*60*60} ; time = time[0].split(":"); seconds += ((time[0] > 11.5) ? 0 : parseInt(time[0])*60*60); //this catches 12 not being 0 seconds += parseInt(time[1])*60; seconds += parseInt(time[2]); var output = seconds / (24*60*60) ; return output; } function calcAngleFromTime(timeInput) { var angle = 0; if (sunset > sunrise) { if (timeInput < sunrise) { // nighttime angle = Math.PI * (timeInput - sunrise) / (1 - sunset + sunrise); } else if (timeInput < sunset) { // day time angle = Math.PI * (timeInput - sunrise) / (sunset - sunrise); } else { // night time angle = Math.PI * (1+((timeInput - sunset) / (1 - sunset + sunrise))); // at sunset angle is pi then increases from there, creating negative sin angle } } else { // this is when longitude is high and we are on opposite side of world so sunrise is PM UTC if (timeInput < sunset) { // day time angle = Math.PI * (1-((sunset - timeInput) / (1 - sunrise + sunset))); } else if (timeInput < sunrise) { // night time angle = Math.PI * (1+((timeInput - sunset) / (sunrise - sunset))); // goes between pi and 2pi } else { // day time angle = Math.PI * (timeInput - sunrise) / (1 - sunrise + sunset); } } return angle; } function setSun(entityID, time, position) { var angle = calcAngleFromTime(time); var x = sunDistance * Math.sin(latitude); var y = sunDistance * Math.sin(angle) * Math.cos(latitude); var z = sunDistance * Math.cos(angle) * Math.cos(latitude); var altitude = y / sunDistance; var sunsetPercent; var nightPercent; var showdows; var skyColor; var sunColor; var hazeColor; var nightSky; var lightDirection; var normalize = Math.sqrt( Math.pow(x,2) + Math.pow(y,2) + Math.pow(z,2)); // this is wrong but is working anyway if (altitude > startSunset) { // midday sun sunsetPercent = 0; nightPercent = 0; sunColor = { "r": 230, "g": 210, "b": 161 }; skyColor = { "r": 0, "g": 100, "b": 255}; hazeColor = { "r": 255, "g": 255, "b": 255}; nightSky = ""; shadows = true; lightDirection = { "x": - x / normalize, "y": - y / normalize, "z": - z / normalize }; } else if (altitude > 0) { //sunrise sunsetPercent = Math.pow(1 - (altitude/startSunset),2); nightPercent = 0; sunColor = { "r": 255 - sunsetPercent*2, "g": 255 - sunsetPercent*183, "b": 255 - sunsetPercent*172}; skyColor = { "r": 0, "g": 100 - sunsetPercent*100, "b": 255 - sunsetPercent*255}; hazeColor = { "r": 255 - sunsetPercent*2, "g": 255 - sunsetPercent*183, "b": 255 - sunsetPercent*172}; nightSky = ""; shadows = true; lightDirection = { "x": - x / normalize, "y": - y / normalize, "z": - z / normalize }; } else if (altitude > - startTwilight) { // twilight sunsetPercent = 1; nightPercent = 1-Math.pow((((startTwilight)+altitude)/startTwilight),2); sunColor = { "r": 255, "g": 255-(183*(1-nightPercent)), "b": 255 -(172*(1-nightPercent))}; skyColor = { "r": 0, "g": 0, "b": 0}; hazeColor = { "r": (255 - 2)*(1-nightPercent), "g": (255 - 183)*(1-nightPercent), "b": (255 - 172)*(1-nightPercent)}; nightSky = starMap; shadows = false; lightDirection = { "x": - x / normalize, "y": - y / normalize, "z": - z / normalize }; } else { // night sunsetPercent = 1; nightPercent = 1; sunColor = { "r": 255, "g": 255, "b": 255}; skyColor = { "r": 0, "g": 0, "b": 0}; nightSky = starMap; shadows = false; lightDirection = { "x": - x / normalize, "y": - y / normalize, "z": - z / normalize }; } var sunID = Entities.findEntitiesByName("Sun", position, 1000, false)[0]; var sunlightID = Entities.findEntitiesByName("Sunlight", position, 1000, false)[0]; // print("sun: " + sunlight); // print("sun: " + JSON.stringify(sunID)); //print("EntityID: " + entityID); //print (JSON.stringify(Entities.getEntityProperties(sunID))); var sunSize = 15 + (15 * Math.pow(sunsetPercent,3)); var result = Entities.editEntity(sunID, { "localPosition": { "x": x, "y": y, "z": z }, "dimensions": {"x": sunSize, "y": sunSize, "z": sunSize}, "color": sunColor }); Entities.editEntity(entityID, { "skybox": { "color": skyColor, "url": nightSky }, "keyLight": { "color": sunColor, "intensity": 1 - ((1-nightLight) * nightPercent), "direction": lightDirection, "castShadows": shadows }, "haze": { "hazeRange": 500, "hazeColor": hazeColor, "hazeEnableGlare": false, "hazeGlareColor": "#ffffff", "hazeGlareAngle": 20, "hazeAltitudeEffect": true, "hazeBaseRef": -100, "hazeCeiling": (100+200*(sunsetPercent)+((sunsetPercent < 0.9) ? 0 : 300 * Math.pow((sunsetPercent - 0.9)/0.9,2)))*(1-nightPercent), "hazeBackgroundBlend": 0.6 - (0.6 * sunsetPercent), }, "bloom" : { "bloomIntensity": 0.1 + (0.9 * Math.pow(sunsetPercent,3)), "bloomThreshold": 1, "bloomSize": -(0.05 + (9.95 * Math.pow(sunsetPercent,3))) } }); //print("result: " + result); var distanceLight = 0.10; Entities.editEntity(sunlightID, { "localPosition": { "x": -(distanceLight * x), "y": -(distanceLight * y) , "z": -(distanceLight * z) }, "color": sunColor, "intensity": 100000 // + (90000 * (1 - sunsetPercent)) }); // "#fd5e53" } // This is called every time a new object is created with this script return new MyObject() }) // Either use this or a return at the very end of the file.