diff --git a/examples/images/moonTexture.png b/examples/images/moonTexture.png new file mode 100644 index 0000000..02da49f Binary files /dev/null and b/examples/images/moonTexture.png differ diff --git a/src/layer/starfield/MoonPosition.js b/src/layer/starfield/MoonPosition.js new file mode 100644 index 0000000..40cd78a --- /dev/null +++ b/src/layer/starfield/MoonPosition.js @@ -0,0 +1,178 @@ +/* + * Copyright 2015-2017 WorldWind Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import WorldWind from 'webworldwind-esa'; +const { + Angle, + ArgumentError, + Logger +} = WorldWind; + +/** + * Provides utilities for determining the Moon geographic and celestial location. + * @exports MoonPosition + */ +const MoonPosition = { + + /** + * Computes the geographic location of the moon for a given date + * @param {Date} date + * @throws {ArgumentError} if the date is missing + * @return {{latitude: Number, longitude: Number}} the geographic location + */ + getAsGeographicLocation: function (date) { + if (date instanceof Date === false) { + throw new ArgumentError( + Logger.logMessage(Logger.LEVEL_SEVERE, "MoonPosition", "getAsGeographicLocation", + "missingDate")); + } + + let celestialLocation = this.getAsCelestialLocation(date); + return this.celestialToGeographic(celestialLocation, date); + }, + + /** + * Computes the celestial location of the moon for a given julianDate + * @param {Date} date + * @throws {ArgumentError} if the date is missing + * @return {{declination: Number, rightAscension: Number}} the celestial location + */ + getAsCelestialLocation: function (date) { + if (date instanceof Date === false) { + throw new ArgumentError( + Logger.logMessage(Logger.LEVEL_SEVERE, "MoonPosition", "getAsCelestialLocation", + "missingDate")); + } + + let julianDate = this.computeJulianDate(date); + + //number of days (positive or negative) since Greenwich noon, Terrestrial Time, on 1 January 2000 (J2000.0) + let numDays = julianDate - 2451545; + + let eclipticLongitude = this.normalizeAngle360(218.316 + 13.176396 * numDays) * Angle.DEGREES_TO_RADIANS; + let meanAnomaly = this.normalizeAngle360(134.963 + 13.064993 * numDays) * Angle.DEGREES_TO_RADIANS; + let meanDistance = this.normalizeAngle360(93.272 + 13.229350 * numDays) * Angle.DEGREES_TO_RADIANS; + + let longitude = eclipticLongitude + 6.289 * Math.sin(meanAnomaly) * Angle.DEGREES_TO_RADIANS; + let latitude = 5.128 * Math.sin(meanDistance) * Angle.DEGREES_TO_RADIANS; + let obliquityOfTheEcliptic = (23.439 - 0.0000004 * numDays) * Angle.DEGREES_TO_RADIANS; + + let rightAscension = Math.atan2( Math.sin(longitude) * Math.cos(obliquityOfTheEcliptic) - Math.tan(latitude) * Math.sin(obliquityOfTheEcliptic) , Math.cos(longitude) ) * + Angle.RADIANS_TO_DEGREES; + + let declination = Math.asin( Math.sin(latitude) * Math.cos(obliquityOfTheEcliptic) + Math.cos(latitude) * Math.sin(obliquityOfTheEcliptic) * Math.sin(longitude) ) * + Angle.RADIANS_TO_DEGREES; + + + //compensate for atan result + // if (eclipticLongitude >= 90 && eclipticLongitude < 270) { + // rightAscension += 180; + // } + rightAscension = this.normalizeAngle360(rightAscension); + + return { + declination: declination, + rightAscension: rightAscension + }; + }, + + /** + * Converts from celestial coordinates (declination and right ascension) to geographic coordinates + * (latitude, longitude) for a given julian date + * @param {{declination: Number, rightAscension: Number}} celestialLocation + * @param {Date} date + * @throws {ArgumentError} if celestialLocation or julianDate are missing + * @return {{latitude: Number, longitude: Number}} the geographic location + */ + celestialToGeographic: function (celestialLocation, date) { + if (!celestialLocation) { + throw new ArgumentError( + Logger.logMessage(Logger.LEVEL_SEVERE, "MoonPosition", "celestialToGeographic", + "missingCelestialLocation")); + } + if (date instanceof Date === false) { + throw new ArgumentError( + Logger.logMessage(Logger.LEVEL_SEVERE, "MoonPosition", "celestialToGeographic", "missingDate")); + } + + let julianDate = this.computeJulianDate(date); + + //number of days (positive or negative) since Greenwich noon, Terrestrial Time, on 1 January 2000 (J2000.0) + let numDays = julianDate - 2451545; + + //Greenwich Mean Sidereal Time + let GMST = this.normalizeAngle360(280.46061837 + 360.98564736629 * numDays); + + //Greenwich Hour Angle + let GHA = this.normalizeAngle360(GMST - celestialLocation.rightAscension); + + let longitude = Angle.normalizedDegreesLongitude(-GHA); + + return { + latitude: celestialLocation.declination, + longitude: longitude + }; + }, + + /** + * Computes the julian date from a javascript date object + * @param {Date} date + * @throws {ArgumentError} if the date is missing + * @return {Number} the julian date + */ + computeJulianDate: function (date) { + if (date instanceof Date === false) { + throw new ArgumentError( + Logger.logMessage(Logger.LEVEL_SEVERE, "MoonPosition", "computeJulianDate", "missingDate")); + } + + let year = date.getUTCFullYear(); + let month = date.getUTCMonth() + 1; + let day = date.getUTCDate(); + let hour = date.getUTCHours(); + let minute = date.getUTCMinutes(); + let second = date.getUTCSeconds(); + + let dayFraction = (hour + minute / 60 + second / 3600) / 24; + + if (month <= 2) { + year -= 1; + month += 12; + } + + let A = Math.trunc(year / 100); + let B = 2 - A + Math.trunc(A / 4); + let JD0h = Math.trunc(365.25 * (year + 4716)) + Math.trunc(30.6001 * (month + 1)) + day + B - 1524.5; + + return JD0h + dayFraction; + }, + + /** + * Restricts an angle to the range [0, 360] degrees, wrapping angles outside the range. + * Wrapping takes place as though traversing the edge of a unit circle; + * angles less than 0 wrap back to 360, while angles greater than 360 wrap back to 0. + * + * @param {Number} degrees the angle to wrap in degrees + * + * @return {Number} the specified angle wrapped to [0, 360] degrees + */ + normalizeAngle360: function(degrees) { + let angle = degrees % 360; + return angle >= 0 ? angle : (angle < 0 ? 360 + angle : 360 - angle); + } + +}; + +export default MoonPosition; \ No newline at end of file diff --git a/src/layer/starfield/StarFieldLayer.js b/src/layer/starfield/StarFieldLayer.js old mode 100644 new mode 100755 index 5530c63..0014ff3 --- a/src/layer/starfield/StarFieldLayer.js +++ b/src/layer/starfield/StarFieldLayer.js @@ -4,6 +4,7 @@ import Celestial from './Celestial'; import StarFieldProgram from './StarFieldProgram'; import SunPosition from './SunPosition'; +import MoonPosition from './MoonPosition'; import WorldWind from 'webworldwind-esa'; const { @@ -63,9 +64,17 @@ class StarFieldLayer extends Layer { */ this.showSun = true; + /** + * Indicates weather to show or hide the Moon + * @type {Boolean} + * @default true + */ + this.showMoon = true; + //Documented in defineProperties below. - this._starDataSource = starDataSource || WorldWind.configuration.baseUrl + 'images/stars.json'; + this._starDataSource = starDataSource || WorldWind.configuration.baseUrl + 'data/stars.json'; this._sunImageSource = WorldWind.configuration.baseUrl + 'images/sunTexture.png'; + this._moonImageSource = WorldWind.configuration.baseUrl + 'images/moonTexture.png'; //Internal use only. //The MVP matrix of this layer. @@ -98,6 +107,10 @@ class StarFieldLayer extends Layer { this._sunPositionsCacheKey = ''; this._sunBufferView = new Float32Array(4); + //Internal use only. + this._moonPositionsCacheKey = ''; + this._moonBufferView = new Float32Array(4); + //Internal use only. this._MAX_GL_POINT_SIZE = 0; @@ -193,6 +206,19 @@ class StarFieldLayer extends Layer { this._sunImageSource = value; } + /** + * Url for the moon texture image. + * @memberof StarFieldLayer.prototype + * @type {URL} + */ + get moonImageSource() { + return this._moonImageSource; + } + + set moonImageSource(value) { + this._moonImageSource = value; + } + // Documented in superclass. doRender(dc) { if (dc.globe.is2D()) { @@ -216,6 +242,7 @@ class StarFieldLayer extends Layer { // Internal. Intentionally not documented. haveResources(dc) { let sunTexture = dc.gpuResourceCache.resourceForKey(this._sunImageSource); + let moonTexture = dc.gpuResourceCache.resourceForKey(this._moonImageSource); let planetTextures = this.planets.every(planet => { const texture = dc.gpuResourceCache.resourceForKey(planet.url); return !!texture; @@ -223,6 +250,7 @@ class StarFieldLayer extends Layer { return ( this._starData != null && sunTexture != null && + moonTexture != null && planetTextures ); } @@ -241,6 +269,11 @@ class StarFieldLayer extends Layer { gpuResourceCache.retrieveTexture(gl, this._sunImageSource); } + let moonTexture = gpuResourceCache.resourceForKey(this._moonImageSource); + if (!moonTexture) { + gpuResourceCache.retrieveTexture(gl, this._moonImageSource); + } + this.planets.forEach(planet => { const texture = gpuResourceCache.resourceForKey(planet.url); if (!texture) { @@ -267,6 +300,10 @@ class StarFieldLayer extends Layer { this.renderSun(dc); } + if (this.showMoon) { + this.renderMoon(dc); + } + if (this.showPlanets) { this.renderPlanets(dc); } @@ -425,6 +462,52 @@ class StarFieldLayer extends Layer { gl.drawArrays(gl.POINTS, 0, 1); } + // Internal. Intentionally not documented. + renderMoon(dc) { + let gl = dc.currentGlContext; + let program = dc.currentProgram; + let gpuResourceCache = dc.gpuResourceCache; + + if (!this._MAX_GL_POINT_SIZE) { + this._MAX_GL_POINT_SIZE = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE)[1]; + } + if (this.sunSize > this._MAX_GL_POINT_SIZE) { + Logger.log(Logger.LEVEL_WARNING, 'StarFieldLayer - sunSize is to big, max size allowed is: ' + + this._MAX_GL_POINT_SIZE); + } + + let moonCelestialLocation = MoonPosition.getAsCelestialLocation(this.time || new Date()); + + this._moonBufferView[0] = moonCelestialLocation.declination; + this._moonBufferView[1] = moonCelestialLocation.rightAscension; + this._moonBufferView[2] = Math.min(this.sunSize, this._MAX_GL_POINT_SIZE); + this._moonBufferView[3] = 1; + + if (!this._moonPositionsCacheKey) { + this._moonPositionsCacheKey = gpuResourceCache.generateCacheKey(); + } + let vboId = gpuResourceCache.resourceForKey(this._moonPositionsCacheKey); + if (!vboId) { + vboId = gl.createBuffer(); + gpuResourceCache.putResource(this._moonPositionsCacheKey, vboId, this._moonBufferView.length * 4); + gl.bindBuffer(gl.ARRAY_BUFFER, vboId); + gl.bufferData(gl.ARRAY_BUFFER, this._moonBufferView, gl.DYNAMIC_DRAW); + } + else { + gl.bindBuffer(gl.ARRAY_BUFFER, vboId); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._moonBufferView); + } + dc.frameStatistics.incrementVboLoadCount(1); + gl.vertexAttribPointer(0, 4, gl.FLOAT, false, 0, 0); + + program.loadFragMode(gl, program.FRAG_MODE_TEXTURE); + + let moonTexture = dc.gpuResourceCache.resourceForKey(this._moonImageSource); + moonTexture.bind(dc); + + gl.drawArrays(gl.POINTS, 0, 1); + } + // Internal. Intentionally not documented. endRendering(dc) { let gl = dc.currentGlContext;