src.gov.nasa.worldwindx.applications.antenna.AntennaModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwind Show documentation
Show all versions of worldwind Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwindx.applications.antenna;
import com.jogamp.common.nio.Buffers;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.terrain.Terrain;
import gov.nasa.worldwind.util.Logging;
import javax.media.opengl.*;
import javax.xml.stream.*;
import java.awt.*;
import java.awt.image.*;
import java.io.IOException;
import java.nio.*;
import java.util.List;
/**
* Models antenna gain. Gain values are drawn from an {@link Interpolator2D}. The value plotted is determined by the
* gain and this object's gain offset and gain scale: gain plotted = gain * gain scale + gain offset. Both the offset
* and the gain can be specified.
*
* @author tag
* @version $Id: AntennaModel.java 1171 2013-02-11 21:45:02Z dcollins $
*/
public class AntennaModel extends AbstractShape
{
public static final int DISPLAY_MODE_FILL = GL2.GL_FILL;
public static final int DISPLAY_MODE_LINE = GL2.GL_LINE;
public static final int DISPLAY_MODE_POINT = GL2.GL_POINT;
protected int nThetaIntervals;
protected int nPhiIntervals;
protected WWTexture texture;
protected Interpolator2D interpolator;
protected Position position = Position.ZERO;
protected Angle azimuth;
protected Angle elevationAngle;
protected double gainOffset = 0;
protected double gainScale = 1;
protected int nThetaPoints = 61;
protected int nPhiPoints = 121; // TODO: gap shows if nPhiPoints -1 does not evenly divide 360
/**
* This class holds globe-specific data for this shape. It's managed via the shape-data cache in {@link
* gov.nasa.worldwind.render.AbstractShape.AbstractShapeData}.
*/
protected static class ShapeData extends AbstractShapeData
{
protected FloatBuffer vertices;
protected FloatBuffer texCoords;
protected IntBuffer[] indices;
protected FloatBuffer normals;
/**
* Construct a cache entry using the boundaries of this shape.
*
* @param dc the current draw context.
* @param shape this shape.
*/
public ShapeData(DrawContext dc, AntennaModel shape)
{
super(dc, shape.minExpiryTime, shape.maxExpiryTime);
}
}
protected AbstractShapeData createCacheEntry(DrawContext dc)
{
return new ShapeData(dc, this);
}
/**
* Returns the current shape data cache entry.
*
* @return the current data cache entry.
*/
protected ShapeData getCurrent()
{
return (ShapeData) this.getCurrentData();
}
public AntennaModel(Interpolator2D interpolator)
{
this.interpolator = interpolator;
this.nThetaIntervals = this.nThetaPoints - 1;
this.nPhiIntervals = this.nPhiPoints - 1;
}
@Override
protected void initialize()
{
// Nothing unique to initialize.
}
public Position getPosition()
{
return position;
}
/**
* Specifies the position of this model's center.
*
* @param position the position of this model's center.
*/
public void setPosition(Position position)
{
if (position == null)
{
String message = Logging.getMessage("nullValue.PositionIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.position = position;
this.reset();
}
public Angle getAzimuth()
{
return azimuth;
}
/**
* Specifies an angle clockwise from north by which to rotate the model.
*
* @param azimuth the angle from north.
*/
public void setAzimuth(Angle azimuth)
{
this.azimuth = azimuth;
}
public Angle getElevationAngle()
{
return elevationAngle;
}
/**
* Specifies an angle to rotate the model vertically counterclockwise from the horizon. The rotation is a
* right-handed rotation relative to the X axis.
*
* @param elevationAngle the elevation angle.
*/
public void setElevationAngle(Angle elevationAngle)
{
this.elevationAngle = elevationAngle;
}
public double getGainOffset()
{
return gainOffset;
}
/**
* Specifies the gain offset in the formula: gain plotted = gain * gain scale + gain offset. The default gain offset
* is 0.
*
* @param gainOffset the gain offset.
*/
public void setGainOffset(double gainOffset)
{
this.gainOffset = gainOffset;
this.reset();
}
public double getGainScale()
{
return gainScale;
}
/**
* Specifies the gain scale in the formula: gain plotted = gain * gain scale + gain offset. The default gain scale
* is 1.
*
* @param gainScale the gain offset.
*/
public void setGainScale(double gainScale)
{
this.gainScale = gainScale;
this.reset();
}
public int getThetaResolution()
{
return this.nThetaPoints;
}
/**
* Specifies the number of plotted points in this model's north-south direction. The default is 61.
*
* @param numSPoints the number of plotted points in the north-south direction. NOTE: this value minus one must
* divide 360 evenly, as in 31, 61, 91, etc.
*/
public void setThetaResolution(int numSPoints)
{
this.nThetaPoints = numSPoints;
this.nThetaIntervals = numSPoints - 1;
this.reset();
}
public int getPhiResolution()
{
return this.nPhiPoints;
}
/**
* Specifies the number of plotted points in this model's longitudinal direction. The default is 121.
*
* @param numTPoints the number of plotted points in the longitudinal direction. NOTE: this value minus one must
* divide 360 evenly, as in 31, 61, 91, 181, etc.
*/
public void setPhiResolution(int numTPoints)
{
this.nPhiPoints = numTPoints;
this.nPhiIntervals = numTPoints - 1;
this.reset();
}
public double getRadius()
{
return this.interpolator.getMaxValue() + this.gainOffset;
}
public Position getReferencePosition()
{
return this.getPosition();
}
/**
* Specifies an image to use as the color ramp for the model. The image should modulate colors in its horizontal
* dimension and not modulate the colors in its vertical dimension. Minimum and maximum gain values are mapped
* respectively to the left-most and right-most color in the image. Intermediate gain values are mapped to the color
* at their position in the gain interval. The image should be a power of two in both dimensions.
*
* @param image the image containing the color ramp used to color the model.
*/
public void setColorRamp(BufferedImage image)
{
if (image != null)
this.texture = new BasicWWTexture(image, true);
}
public BufferedImage getColorRamp()
{
if (this.texture != null && this.texture.getImageSource() instanceof BufferedImage)
return (BufferedImage) this.texture.getImageSource();
return null;
}
public Extent getExtent(Globe globe, double verticalExaggeration)
{
// See if we've cached an extent associated with the globe.
Extent extent = super.getExtent(globe, verticalExaggeration);
if (extent != null)
return extent;
this.getCurrent().setExtent(new Sphere(globe.computePointFromPosition(this.getReferencePosition()),
this.getRadius()));
return this.getCurrent().getExtent();
}
public Sector getSector()
{
if (this.sector == null)
this.sector = null; // TODO
return this.sector;
}
protected boolean mustApplyTexture(DrawContext dc)
{
return true;
}
@Override
protected boolean shouldUseVBOs(DrawContext dc)
{
return false;
}
protected boolean mustRegenerateGeometry(DrawContext dc)
{
ShapeData shapeData = this.getCurrent();
if (shapeData.vertices == null)
return true;
if (this.getAltitudeMode() == WorldWind.ABSOLUTE
&& shapeData.getGlobeStateKey() != null
&& shapeData.getGlobeStateKey().equals(dc.getGlobe().getGlobeStateKey(dc)))
return false;
// Determine whether the reference point has changed. If it hasn't, then no other points need to change.
Vec4 rp = this.computePoint(dc.getTerrain(), this.getPosition());
if (shapeData.getReferencePoint() != null && shapeData.getReferencePoint().equals(rp))
return false;
return super.mustRegenerateGeometry(dc);
}
protected boolean doMakeOrderedRenderable(DrawContext dc)
{
if (!this.intersectsFrustum(dc))
return false;
this.makeVertices(dc);
ShapeData shapeData = this.getCurrent();
if (shapeData.indices == null)
this.makeIndices();
if (shapeData.normals == null)
this.makeNormals();
return true;
}
protected boolean isOrderedRenderableValid(DrawContext dc)
{
ShapeData shapeData = this.getCurrent();
return shapeData.vertices != null && shapeData.indices != null && shapeData.normals != null;
}
protected void doDrawOutline(DrawContext dc)
{
this.drawModel(dc, DISPLAY_MODE_LINE, !this.isHighlighted());
}
protected void doDrawInterior(DrawContext dc)
{
this.drawModel(dc, DISPLAY_MODE_FILL, true);
}
public void drawModel(DrawContext dc, int displayMode, boolean showTexture)
{
ShapeData shapeData = this.getCurrent();
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
if (this.texture == null)
this.makeTexture();
gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, displayMode);
if (!dc.isPickingMode() && showTexture)
{
gl.glEnable(GL.GL_TEXTURE_2D);
gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, shapeData.texCoords.rewind());
this.texture.bind(dc);
}
gl.glPushMatrix();
// Rotate to align with longitude.
gl.glRotated(this.getPosition().getLongitude().degrees, 0, 1, 0);
// Rotate to align with latitude.
gl.glRotated(Math.abs(90 - this.getPosition().getLatitude().degrees), 1, 0, 0);
// Apply the azimuth.
if (this.getAzimuth() != null)
gl.glRotated(-this.getAzimuth().degrees, 0, 1, 0);
// Apply the elevation angle.
if (this.getElevationAngle() != null)
gl.glRotated(this.getElevationAngle().degrees, 1, 0, 0);
gl.glVertexPointer(3, GL.GL_FLOAT, 0, shapeData.vertices.rewind());
if (!dc.isPickingMode() && this.mustApplyLighting(dc, null))
gl.glNormalPointer(GL.GL_FLOAT, 0, shapeData.normals.rewind());
for (IntBuffer iBuffer : shapeData.indices)
{
gl.glDrawElements(GL.GL_TRIANGLE_STRIP, iBuffer.limit(), GL.GL_UNSIGNED_INT, iBuffer.rewind());
}
gl.glPopMatrix();
if (!dc.isPickingMode())
gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
}
private void makeVertices(DrawContext dc)
{
ShapeData shapeData = this.getCurrent();
Vec4 rp = this.computePoint(dc.getTerrain(), this.getPosition());
if (shapeData.getReferencePoint() != null && shapeData.getReferencePoint().equals(rp))
return; // no need to regenerate the vertices
shapeData.setReferencePoint(rp);
int nVertices = (this.nThetaIntervals + 1) * (this.nPhiIntervals + 1);
shapeData.vertices = Buffers.newDirectFloatBuffer(3 * nVertices);
shapeData.texCoords = Buffers.newDirectFloatBuffer(2 * nVertices);
double rScale = 1 / (this.getMaxR() - this.getMinR()); // to keep texture coords in [0,1]. see comment below
double xMax = -Double.MAX_VALUE;
double yMax = -Double.MAX_VALUE;
double zMax = -Double.MAX_VALUE;
double dTheta = 180 / this.nThetaIntervals;
double dPhi = 360 / this.nPhiIntervals;
for (int it = 0; it <= this.nThetaIntervals; it++)
{
for (int ip = 0; ip <= this.nPhiIntervals; ip++)
{
double theta = it * dTheta;
double phi = ip * dPhi;
double t = theta * Math.PI / 180;
double p = phi * Math.PI / 180;
Double r = this.interpolator.getValue(theta, phi);
// Scale r to use full range of texture coordinates. Use 0 if r is undefined at these coordinates.
double s = r != null ? (r - this.getMinR()) * rScale : 0;
shapeData.texCoords.put((float) s).put(0);
// Scale and offset r per application's specifications. Use 0 if r is undefined at these coordinates.
double rScaled = r != null ? (r + this.gainOffset) * this.gainScale : 0;
double z = rScaled * Math.sin(t) * Math.cos(p);
double x = rScaled * Math.sin(t) * Math.sin(p);
double y = rScaled * Math.cos(t);
double xa = Math.abs(x);
double ya = Math.abs(y);
double za = Math.abs(z);
if (xa > xMax)
xMax = xa;
if (ya > yMax)
yMax = ya;
if (za > zMax)
zMax = za;
shapeData.vertices.put((float) x).put((float) y).put((float) z);
}
}
shapeData.setExtent(new Sphere(rp, Math.sqrt(xMax * xMax + yMax * yMax + zMax * zMax)));
}
private double getMinR()
{
Double minR = this.interpolator.getMinValue();
return minR != null ? minR : 0;
}
private double getMaxR()
{
Double maxR = this.interpolator.getMaxValue();
return maxR != null ? maxR : 1;
}
private void makeIndices()
{
ShapeData shapeData = this.getCurrent();
shapeData.indices = new IntBuffer[this.nThetaIntervals];
for (int j = 0; j < this.nThetaIntervals; j++)
{
shapeData.indices[j] = Buffers.newDirectIntBuffer(2 * this.nPhiIntervals + 2);
for (int i = 0; i <= this.nPhiIntervals; i++)
{
int k1 = i + j * (this.nPhiIntervals + 1);
int k2 = k1 + this.nPhiIntervals + 1;
shapeData.indices[j].put(k1).put(k2);
}
}
}
private void makeTexture()
{
BufferedImage image = new BufferedImage(240, 2, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) image.getGraphics();
for (int i = 0; i < image.getWidth(); i++)
{
g.setPaint(Color.getHSBColor((float) ((image.getWidth() - i) / 360d), 1f, 1f));
g.fillRect(i, 0, 1, 2);
}
this.texture = new BasicWWTexture(image, true);
}
protected void makeNormals()
{
ShapeData shapeData = this.getCurrent();
Vec4 vecA, vecB, vecC, vecD, vecX1, vecX2;
shapeData.normals = Buffers.newDirectFloatBuffer(shapeData.vertices.limit());
for (int j = 0; j <= this.nThetaIntervals; j++)
{
for (int i = 0; i <= this.nPhiIntervals; i++)
{
Vec4 vec0 = this.getVec(shapeData, i, j);
if (i == 0 && j == 0)
{
vecA = this.getVec(shapeData, i, j + 1).subtract3(vec0);
vecB = this.getVec(shapeData, i + 1, j).subtract3(vec0);
this.putVec(i, j, vecA.cross3(vecB).normalize3(), shapeData.normals);
}
else if (i == this.nPhiIntervals && j == 0)
{
vecA = this.getVec(shapeData, i - 1, j).subtract3(vec0);
vecB = this.getVec(shapeData, i, j + 1).subtract3(vec0);
this.putVec(i, j, vecA.cross3(vecB).normalize3(), shapeData.normals);
}
else if (i == 0 && j == this.nThetaIntervals)
{
vecA = this.getVec(shapeData, i + 1, j).subtract3(vec0);
vecB = this.getVec(shapeData, i, j - 1).subtract3(vec0);
this.putVec(i, j, vecA.cross3(vecB).normalize3(), shapeData.normals);
}
else if (i == this.nPhiIntervals && j == this.nThetaIntervals)
{
vecA = this.getVec(shapeData, i, j - 1).subtract3(vec0);
vecB = this.getVec(shapeData, i - 1, j).subtract3(vec0);
this.putVec(i, j, vecA.cross3(vecB).normalize3(), shapeData.normals);
}
else if (i == 0)
{
vecA = this.getVec(shapeData, i, j - 1).subtract3(vec0);
vecB = this.getVec(shapeData, i + 1, j).subtract3(vec0);
vecC = this.getVec(shapeData, i, j - 1).subtract3(vec0);
vecX1 = vecA.cross3(vecB).multiply3(0.5);
vecX2 = vecB.cross3(vecC).multiply3(0.5);
this.putVec(i, j, vecX1.add3(vecX2).normalize3(), shapeData.normals);
}
else if (i == this.nPhiIntervals)
{
vecA = this.getVec(shapeData, i, j - 1).subtract3(vec0);
vecB = this.getVec(shapeData, i - 1, j).subtract3(vec0);
vecC = this.getVec(shapeData, i, j + 1).subtract3(vec0);
vecX1 = vecA.cross3(vecB).multiply3(0.5);
vecX2 = vecB.cross3(vecC).multiply3(0.5);
this.putVec(i, j, vecX1.add3(vecX2).normalize3(), shapeData.normals);
}
else if (j == 0)
{
vecA = this.getVec(shapeData, i - 1, j).subtract3(vec0);
vecB = this.getVec(shapeData, i, j + 1).subtract3(vec0);
vecC = this.getVec(shapeData, i + 1, j).subtract3(vec0);
vecX1 = vecA.cross3(vecB).multiply3(0.5);
vecX2 = vecB.cross3(vecC).multiply3(0.5);
this.putVec(i, j, vecX1.add3(vecX2).normalize3(), shapeData.normals);
}
else if (j == this.nThetaIntervals)
{
vecA = this.getVec(shapeData, i + 1, j).subtract3(vec0);
vecB = this.getVec(shapeData, i, j - 1).subtract3(vec0);
vecC = this.getVec(shapeData, i - 1, j).subtract3(vec0);
vecX1 = vecA.cross3(vecB).multiply3(0.5);
vecX2 = vecB.cross3(vecC).multiply3(0.5);
this.putVec(i, j, vecX1.add3(vecX2).normalize3(), shapeData.normals);
}
else
{
vecA = this.getVec(shapeData, i, j - 1).subtract3(vec0);
vecB = this.getVec(shapeData, i - 1, j).subtract3(vec0);
vecC = this.getVec(shapeData, i, j + 1).subtract3(vec0);
vecD = this.getVec(shapeData, i + 1, j).subtract3(vec0);
vecX1 = vecA.cross3(vecB).multiply3(0.25);
vecX2 = vecB.cross3(vecC).multiply3(0.25);
Vec4 vecX3 = vecC.cross3(vecD).multiply3(0.25);
Vec4 vecX4 = vecD.cross3(vecA).multiply3(0.25);
this.putVec(i, j, vecX1.add3(vecX2).add3(vecX3).add3(vecX4).normalize3(), shapeData.normals);
}
}
}
}
protected Vec4 getVec(ShapeData shapeData, int i, int j)
{
int k = 3 * (j * this.nPhiIntervals + i);
float x = shapeData.vertices.get(k);
float y = shapeData.vertices.get(k + 1);
float z = shapeData.vertices.get(k + 2);
return new Vec4(x, y, z);
}
protected void putVec(int i, int j, Vec4 vec, FloatBuffer buffer)
{
int k = 3 * (j * this.nPhiIntervals + i);
buffer.put(k, (float) vec.getX());
buffer.put(k + 1, (float) vec.getY());
buffer.put(k + 2, (float) vec.getZ());
}
@Override
protected void fillVBO(DrawContext dc)
{
}
public void moveTo(Position position)
{
}
@Override
public List intersect(Line line, Terrain terrain) throws InterruptedException
{
return null;
}
@Override
public String isExportFormatSupported(String mimeType)
{
return Exportable.FORMAT_NOT_SUPPORTED;
}
@Override
protected void doExportAsKML(XMLStreamWriter xmlWriter) throws IOException, XMLStreamException
{
throw new UnsupportedOperationException("KML output not supported for AntennaModel");
}
}