/* Copyright (C) 2001, 2006 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. */ package gov.nasa.worldwind.render; import com.sun.opengl.util.j2d.TextRenderer; import gov.nasa.worldwind.Disposable; import gov.nasa.worldwind.exception.WWRuntimeException; import gov.nasa.worldwind.geom.Angle; import gov.nasa.worldwind.geom.Frustum; import gov.nasa.worldwind.geom.Vec4; import gov.nasa.worldwind.geom.Position; import gov.nasa.worldwind.globes.SectorGeometryList; import gov.nasa.worldwind.util.Logging; import javax.media.opengl.GL; import javax.media.opengl.glu.GLU; import java.awt.*; import java.awt.geom.Rectangle2D; import java.util.HashMap; import java.util.Map; import java.util.Iterator; /** * @author dcollins * @version $Id: GeographicTextRenderer.java 5028 2008-04-11 19:50:38Z tgaskins $ */ public class GeographicTextRenderer implements Disposable { private Map<Font, TextRenderer> textRenderers = new HashMap<Font, TextRenderer>(); private TextRenderer lastTextRenderer = null; private final GLU glu = new GLU(); private static final Font DEFAULT_FONT = Font.decode("Arial-PLAIN-12"); private static final Color DEFAULT_COLOR = Color.white; public GeographicTextRenderer() { } public void dispose() { for (TextRenderer textRenderer : textRenderers.values()) { if (textRenderer != null) textRenderer.dispose(); } this.textRenderers.clear(); } public void render(DrawContext dc, Iterable<GeographicText> text) { this.drawMany(dc, text); } public void render(DrawContext dc, GeographicText text, Vec4 textPoint) { if (!isTextValid(text, false)) return; this.drawOne(dc, text, textPoint); } private void drawMany(DrawContext dc, Iterable<GeographicText> textIterable) { if (dc == null) { String msg = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().fine(msg); throw new IllegalArgumentException(msg); } if (textIterable == null) { String msg = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().fine(msg); throw new IllegalArgumentException(msg); } if (dc.getVisibleSector() == null) return; SectorGeometryList geos = dc.getSurfaceGeometry(); if (geos == null) return; Iterator<GeographicText> iterator = textIterable.iterator(); if (!iterator.hasNext()) return; Frustum frustumInModelCoords = dc.getView().getFrustumInModelCoordinates(); double horizon = dc.getView().computeHorizonDistance(); while (iterator.hasNext()) { GeographicText text = iterator.next(); if (!isTextValid(text, true)) continue; if (!text.isVisible()) continue; Angle lat = text.getPosition().getLatitude(); Angle lon = text.getPosition().getLongitude(); if (!dc.getVisibleSector().contains(lat, lon)) continue; Vec4 textPoint = geos.getSurfacePoint(lat, lon, text.getPosition().getElevation()); if (textPoint == null) continue; double eyeDistance = dc.getView().getEyePoint().distanceTo3(textPoint); if (eyeDistance > horizon) continue; if (!frustumInModelCoords.contains(textPoint)) continue; dc.addOrderedRenderable(new OrderedText(text, textPoint, eyeDistance)); } } private void drawOne(DrawContext dc, GeographicText text, Vec4 textPoint) { if (dc == null) { String msg = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().fine(msg); throw new IllegalArgumentException(msg); } if (dc.getView() == null) { String msg = Logging.getMessage("nullValue.ViewIsNull"); Logging.logger().fine(msg); throw new IllegalArgumentException(msg); } if (dc.getVisibleSector() == null) return; SectorGeometryList geos = dc.getSurfaceGeometry(); if (geos == null) return; if (!text.isVisible()) return; if (textPoint == null) { if (text.getPosition() == null) return; Angle lat = text.getPosition().getLatitude(); Angle lon = text.getPosition().getLongitude(); if (!dc.getVisibleSector().contains(lat, lon)) return; textPoint = geos.getSurfacePoint(lat, lon, text.getPosition().getElevation()); if (textPoint == null) return; } double horizon = dc.getView().computeHorizonDistance(); double eyeDistance = dc.getView().getEyePoint().distanceTo3(textPoint); if (eyeDistance > horizon) return; if (!dc.getView().getFrustumInModelCoordinates().contains(textPoint)) return; dc.addOrderedRenderable(new OrderedText(text, textPoint, eyeDistance)); } private static boolean isTextValid(GeographicText text, boolean checkPosition) { if (text == null || text.getText() == null) return false; //noinspection RedundantIfStatement if (checkPosition && text.getPosition() == null) return false; return true; } private class OrderedText implements OrderedRenderable { GeographicText text; Vec4 point; double eyeDistance; OrderedText(GeographicText text, Vec4 point, double eyeDistance) { this.text = text; this.point = point; this.eyeDistance = eyeDistance; } public double getDistanceFromEye() { return this.eyeDistance; } public void render(DrawContext dc) { GeographicTextRenderer.this.beginRendering(dc); try { GeographicTextRenderer.this.drawText(dc, this); // Draw as many as we can in a batch to save ogl state switching. while (dc.getOrderedRenderables().peek() instanceof OrderedText) { OrderedText ot = (OrderedText) dc.getOrderedRenderables().poll(); GeographicTextRenderer.this.drawText(dc, ot); } } catch (WWRuntimeException e) { Logging.logger().log(java.util.logging.Level.SEVERE, "generic.ExceptionWhileRenderingText", e); } catch (Exception e) { Logging.logger().log(java.util.logging.Level.SEVERE, "generic.ExceptionWhileRenderingText", e); } finally { GeographicTextRenderer.this.endRendering(dc); } } public void pick(DrawContext dc, java.awt.Point pickPoint) { } } private final int[] viewportArray = new int[4]; private void beginRendering(DrawContext dc) { GL gl = dc.getGL(); int attribBits = GL.GL_ENABLE_BIT // for enable/disable changes | GL.GL_COLOR_BUFFER_BIT // for alpha test func and ref, and blend | GL.GL_CURRENT_BIT // for current color | GL.GL_DEPTH_BUFFER_BIT // for depth test, depth func, and depth mask | GL.GL_TRANSFORM_BIT // for modelview and perspective | GL.GL_VIEWPORT_BIT; // for depth range gl.glPushAttrib(attribBits); gl.glGetIntegerv(GL.GL_VIEWPORT, viewportArray, 0); gl.glMatrixMode(GL.GL_PROJECTION); gl.glPushMatrix(); gl.glLoadIdentity(); glu.gluOrtho2D(0, viewportArray[2], 0, viewportArray[3]); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPushMatrix(); gl.glLoadIdentity(); gl.glMatrixMode(GL.GL_TEXTURE); gl.glPushMatrix(); gl.glLoadIdentity(); // Enable the depth test but don't write to the depth buffer. gl.glEnable(GL.GL_DEPTH_TEST); gl.glDepthMask(false); // Suppress polygon culling. gl.glDisable(GL.GL_CULL_FACE); // Suppress any fully transparent image pixels gl.glEnable(GL.GL_ALPHA_TEST); gl.glAlphaFunc(GL.GL_GREATER, 0.001f); } private void endRendering(DrawContext dc) { if (this.lastTextRenderer != null) { this.lastTextRenderer.end3DRendering(); this.lastTextRenderer = null; } GL gl = dc.getGL(); gl.glMatrixMode(GL.GL_PROJECTION); gl.glPopMatrix(); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPopMatrix(); gl.glMatrixMode(GL.GL_TEXTURE); gl.glPopMatrix(); gl.glPopAttrib(); } private Vec4 drawText(DrawContext dc, OrderedText uText) { if (uText.point == null) { String msg = Logging.getMessage("nullValue.PointIsNull"); Logging.logger().fine(msg); return null; } GeographicText geographicText = uText.text; final CharSequence charSequence = geographicText.getText(); if (charSequence == null) return null; final Vec4 screenPoint = dc.getView().project(uText.point); if (screenPoint == null) return null; Font font = geographicText.getFont(); if (font == null) font = DEFAULT_FONT; TextRenderer textRenderer = this.textRenderers.get(font); if (textRenderer == null) textRenderer = this.initializeTextRenderer(font); if (textRenderer != this.lastTextRenderer) { if (this.lastTextRenderer != null) this.lastTextRenderer.end3DRendering(); textRenderer.begin3DRendering(); this.lastTextRenderer = textRenderer; } this.setDepthFunc(dc, uText, screenPoint); Rectangle2D textBound = textRenderer.getBounds(charSequence); float x = (float) (screenPoint.x - textBound.getWidth() / 2d); float y = (float) (screenPoint.y); Color color = geographicText.getColor(); if (color == null) color = DEFAULT_COLOR; Color background = geographicText.getBackgroundColor(); if (background != null) { textRenderer.setColor(background); textRenderer.draw3D(charSequence, x + 1, y - 1, 0, 1); } textRenderer.setColor(color); textRenderer.draw3D(charSequence, x, y, 0, 1); textRenderer.flush(); return screenPoint; } @SuppressWarnings({"UnusedDeclaration"}) private void setDepthFunc(DrawContext dc, OrderedText uText, Vec4 screenPoint) { GL gl = dc.getGL(); //if (uText.text.isAlwaysOnTop()) //{ // gl.glDepthFunc(GL.GL_ALWAYS); // return; //} Position eyePos = dc.getView().getEyePosition(); if (eyePos == null) { gl.glDepthFunc(GL.GL_ALWAYS); return; } double altitude = eyePos.getElevation(); if (altitude < (dc.getGlobe().getMaxElevation() * dc.getVerticalExaggeration())) { double depth = screenPoint.z - (8d * 0.00048875809d); depth = depth < 0d ? 0d : (depth > 1d ? 1d : depth); gl.glDepthFunc(GL.GL_LESS); gl.glDepthRange(depth, depth); } //else if (screenPoint.z >= 1d) //{ // gl.glDepthFunc(GL.GL_EQUAL); // gl.glDepthRange(1d, 1d); //} else { gl.glDepthFunc(GL.GL_ALWAYS); } } private TextRenderer initializeTextRenderer(Font font) { TextRenderer textRenderer = new TextRenderer(font, true, true); textRenderer.setUseVertexArrays(false); TextRenderer oldTextRenderer; oldTextRenderer = this.textRenderers.put(font, textRenderer); if (oldTextRenderer != null) oldTextRenderer.dispose(); return textRenderer; } }