package gov.nasa.worldwind.layers; import gov.nasa.worldwind.geom.*; import gov.nasa.worldwind.layers.RenderableLayer; import gov.nasa.worldwind.render.DrawContext; import gov.nasa.worldwind.util.Logging; import javax.media.opengl.*; import java.awt.*; import java.io.*; /** * Renders a star background based on a subset of ESA Hipparcos catalog. * * @author Patrick Murris * @version $Id: StarsLayer.java 5178 2008-04-25 21:51:20Z patrickmurris $ */ public class StarsLayer extends RenderableLayer { // TODO: make configurable protected String starsFileName = "config/Hipparcos_Stars_Mag6x5044.tsv"; private int glListId = -1; // GL list id private float brightness = 1f; // Brightness multiplier private boolean rebuild = false; // True if need to rebuild GL list private double radius = 6356752 * 10; // Earth radius x 10 private Angle longitudeOffset = Angle.ZERO; // Star sphere rotation longitude private Angle latitudeOffset = Angle.ZERO; // Star sphere rotation latitude /** * A RenderableLayer that displays a star background */ public StarsLayer() { this.setName(Logging.getMessage("layers.Earth.StarsLayer.Name")); } /** * A RenderableLayer that displays a star background * * @param starsFileName the path and filename of the star catalog file */ public StarsLayer(String starsFileName) { this.setName(Logging.getMessage("layers.Earth.StarsLayer.Name")); this.setStarsFileName(starsFileName); } // Public properties /** * Get the path and filename of the stars catalog file. */ public String getStarsFileName() { return this.starsFileName; } /** * Set the path and filename of the stars catalog file. * * @param fileName the path and filename */ public void setStarsFileName(String fileName) { if (fileName == null || fileName.length() == 0) { String message = Logging.getMessage("nullValue.FilePathIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.starsFileName = fileName; this.rebuild = true; } /** * Get the actual brightness multiplier. * * @return brightness */ public float getBrightness() { return this.brightness; } /** * Set the brightness multiplier. eg : 1.0f = no change, 0.5f = darker, 2.0f = brighter. * * @param brightness the brightness multiplier */ public void setBrightness(float brightness) { this.brightness = Math.abs(brightness); this.rebuild = true; } /** * Get the star sphere radius. * * @return the star sphere radius in meter. */ public double getRadius() { return this.radius; } /** * Set the star sphere radius in meter. * * @param radius the radius in meter. */ public void setRadius(double radius) { this.radius = Math.abs(radius); this.rebuild = true; } /** * Returns the latitude offset or relative tilt for the star sphere. * * @return the latitude offset. */ public Angle getLatitudeOffset() { return this.latitudeOffset; } /** * Sets the latitude offset or relative tilt of the star sphere. * * @param offset the latitude offset. */ public void setLatitudeOffset(Angle offset) { if (offset == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.latitudeOffset = offset; } /** * Returns the longitude offset or rotation of the star sphere. * * @return the longitude offset. */ public Angle getLongitudeOffset() { return this.longitudeOffset; } /** * Sets the longitude offset or rotation of the star sphere. * * @param offset the longitude offset. */ public void setLongitudeOffset(Angle offset) { if (offset == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.longitudeOffset = offset; } @Override public void doRender(DrawContext dc) { GL gl = dc.getGL(); boolean attribsPushed = false; boolean modelviewPushed = false; boolean projectionPushed = false; // Load or reload stars if needed if (this.glListId == -1 || this.rebuild) { if (this.glListId != -1) gl.glDeleteLists(this.glListId, 1); this.loadStars(dc); // Create glList this.rebuild = false; } // Still no stars to render ? if (this.glListId == -1) return; try { // GL set up // Save GL state /* gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT | GL.GL_POLYGON_BIT | GL.GL_TEXTURE_BIT | GL.GL_ENABLE_BIT | GL.GL_CURRENT_BIT); */ gl.glPushAttrib(GL.GL_ENABLE_BIT | GL.GL_CURRENT_BIT | GL.GL_POLYGON_BIT); attribsPushed = true; gl.glDisable(GL.GL_TEXTURE_2D); // no textures gl.glDisable(GL.GL_DEPTH_TEST); // no depth testing // Set far clipping far enough - is this the right way to do it ? gl.glMatrixMode(GL.GL_PROJECTION); gl.glPushMatrix(); projectionPushed = true; gl.glLoadIdentity(); double ditanceFromOrigin = dc.getView().getEyePoint().getLength3(); //noinspection UnnecessaryLocalVariable double near = ditanceFromOrigin; double far = this.radius + ditanceFromOrigin; dc.getGLU().gluPerspective(dc.getView().getFieldOfView().degrees, dc.getView().getViewport().getWidth() / dc.getView().getViewport().getHeight(), near, far); // Rotate sphere gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPushMatrix(); modelviewPushed = true; gl.glRotatef((float) this.longitudeOffset.degrees, 0.0f, 1.0f, 0.0f); gl.glRotatef((float) -this.latitudeOffset.degrees, 1.0f, 0.0f, 0.0f); // Draw gl.glCallList(this.glListId); } finally { // Restore GL state if (modelviewPushed) { gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPopMatrix(); } if (projectionPushed) { gl.glMatrixMode(GL.GL_PROJECTION); gl.glPopMatrix(); } if (attribsPushed) gl.glPopAttrib(); } } /** * Read stars catalog file and draw into a glList * * @param dc the current DrawContext */ private void loadStars(DrawContext dc) { GL gl = dc.getGL(); this.glListId = gl.glGenLists(1); gl.glNewList(this.glListId, GL.GL_COMPILE); this.drawStarsFromFile(dc); gl.glEndList(); } /** * Read the current star catalog file and draw GL points * * @param dc the current DrawContext */ private void drawStarsFromFile(DrawContext dc) { // Read star catalog and draw GL gl = dc.getGL(); try { InputStream starsStream = this.getClass().getResourceAsStream("/" + this.starsFileName); if (starsStream == null) { File starsFile = new File(this.starsFileName); if (starsFile.exists()) { starsStream = new FileInputStream(starsFile); } } if (starsStream == null) // TODO: logger error return; // BufferedInputStream bis = new BufferedInputStream(starsStream); // DataInputStream starsReader = new DataInputStream(bis); BufferedReader starsReader = new BufferedReader(new InputStreamReader(starsStream)); String line; int idxRAhms = 2; // Catalog field indices int idxDEdms = 3; int idxVmag = 4; int idxBV = 5; double longitude; double latitude; boolean isData = false; gl.glBegin(GL.GL_POINTS); while ((line = starsReader.readLine()) != null) { if (line.length() < 3) continue; if (line.substring(0, 1).equals("#")) continue; if (isData) // Star data here { // Split data in ';' separated values String[] starData = line.trim().split(";"); String RAhms, DEdms, Vmag, BV; RAhms = starData[idxRAhms]; // Right Asc in H, min, sec "00 01 35.85" DEdms = starData[idxDEdms]; // Declinaison Degre min sec "-77 03 55.1" Vmag = starData[idxVmag]; // Apparent magnitude " 4.78" // B-V spectral color " 1.254" (may be missing) BV = idxBV < starData.length ? starData[idxBV] : ""; // compute RAhms into longitude double RAh = Double.parseDouble(RAhms.substring(0, 2)); double RAm = Double.parseDouble(RAhms.substring(3, 5)); double RAs = Double.parseDouble(RAhms.substring(6)); longitude = (RAh * 15) + (RAm * .25) + (RAs * 0.0041666) - 180; // compute DEdms into latitude String DEsign = DEdms.substring(0, 1); double DEd = Double.parseDouble(DEdms.substring(1, 3)); double DEm = Double.parseDouble(DEdms.substring(4, 6)); double DEs = Double.parseDouble(DEdms.substring(7)); latitude = DEd + (DEm / 60) + (DEs / 3600); if (DEsign.equals("-")) latitude *= -1; // compute aparent magnitude -1.5 - 10 to grayscale 0 - 255 double VM = Double.parseDouble(Vmag); double Vdec = 255 - ((VM + 1.5) * 255 / 10); Vdec *= this.brightness; // boost luminosity if (Vdec > 255) Vdec = 255; Vdec /= 255; // scale back to 0.0 - 1.0 // convert B-V -0.5 - 4 for rgb color select double BVdec = 0; try { BVdec = Double.parseDouble(BV); } catch (Exception e) { BVdec = 0; } // Star color Color color = BVColor(BVdec); //gl.glColor3f((float)Vdec, (float)Vdec, (float)Vdec); // grayscale gl.glColor3f((float) color.getRed() / 255f * (float) Vdec, (float) color.getGreen() / 255f * (float) Vdec, (float) color.getBlue() / 255f * (float) Vdec); // B-V color // Place vertex for point star Vec4 pos = SphericalToCartesian(latitude, longitude, this.radius); gl.glVertex3d(pos.getX(), pos.getY(), pos.getZ()); } // Data starting next line if (line.substring(0, 3).equals("---")) isData = true; } gl.glEnd(); starsReader.close(); } catch (IOException e) { // TODO: Log proper message //String message = WorldWind.retrieveErrMsg("generic.IOExceptionWhileLoadingData"); String message = "IOException while loading stars data from " + this.starsFileName; Logging.logger().severe(message); } catch (Exception e) { String message = "Error while loading stars data from " + this.starsFileName; Logging.logger().severe(message); } } /** * Converts position in spherical coordinates (lat/lon/radius) to cartesian (XYZ) coordinates. * * @param latitude Latitude in decimal degrees * @param longitude Longitude in decimal degrees * @param radius Radius * @return the corresponding Point */ private static Vec4 SphericalToCartesian(double latitude, double longitude, double radius) { latitude *= Math.PI / 180.0f; longitude *= Math.PI / 180.0f; double radCosLat = radius * Math.cos(latitude); return new Vec4( radCosLat * Math.sin(longitude), radius * Math.sin(latitude), radCosLat * Math.cos(longitude)); } /** * Returns the corresponding B-V color * * @param BV the star B-V decimal value (-.5 .. 4) * @return the corresponding Color */ private static Color BVColor(double BV) { // TODO: interpolate between values if (BV < 0) return new Color(.635f, .764f, .929f); // Light blue else if (BV < .5) return new Color(1f, 1f, 1f); // White else if (BV < 1) return new Color(1f, .984f, .266f); // Yellow else if (BV < 1.5) return new Color(.964f, .725f, .0784f); // Orange else return new Color(.921f, .376f, .0392f); // Redish } public void dispose() { if (this.glListId < 0) return; GLContext glc = GLContext.getCurrent(); if (glc == null) return; glc.getGL().glDeleteLists(this.glListId, 1); this.glListId = -1; } @Override public String toString() { return this.getName(); } }