Skip to content

Commit

Permalink
Add Ellipse interior/outline image support (#211)
Browse files Browse the repository at this point in the history
* Add texture capabilities

* Add Ellipse texture fill to Shapes Dash and Fill tutorial

* Refactor disparate texture coordinate matrix computation to AbstractShape
  • Loading branch information
zglueck authored Feb 1, 2018
1 parent 1469d47 commit 90cdd05
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 48 deletions.
19 changes: 7 additions & 12 deletions worldwind-tutorials/src/main/assets/shapes_dash_and_fill.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ <h1>Shapes Dash and Fill</h1>
<li>The westernmost Path specifies a dash pattern and factor.</li>
<li>The middle Path modifies the dash factor from the western example.</li>
<li>The easternmost Path follows terrain and uses a different pattern.</li>
<li>The northern Polygon specifies a repeating fill using an image.</li>
<li>The Ellipse specifies a repeating fill using an image.</li>
<li>The southern Polygon uses the same repeating fill, but follows terrain and specifies a dash pattern for the
outline.
</li>
Expand Down Expand Up @@ -107,18 +107,13 @@ <h3>ShapesDashAndFillFragment.java</h3>
path.setFollowTerrain(true);
layer.addRenderable(path);

// Create a polygon using an image as a repeating fill pattern.
positions = Arrays.asList(
Position.fromDegrees(50.0, -70.0, 1e5),
Position.fromDegrees(35.0, -85.0, 1e5),
Position.fromDegrees(35.0, -55.0, 1e5)
);
Polygon polygon = new Polygon(positions);
// Create an Ellipse using an image as a repeating fill pattern
Position ellipseCenter = new Position(40, -70.0, 1e5);
Ellipse ellipse = new Ellipse(ellipseCenter, 1.5e6, 800e3);
sa = new ShapeAttributes(thickenLine);
sa.setInteriorImageSource(ImageSource.fromResource(R.drawable.pattern_sample_houndstooth));
sa.setInteriorColor(new gov.nasa.worldwind.render.Color(1f, 1f, 1f, 1f));
polygon.setAttributes(sa);
layer.addRenderable(polygon);
ellipse.setAttributes(sa);
layer.addRenderable(ellipse);

// Create a surface polygon using an image as a repeating fill pattern and a dash pattern for the outline
// of the polygon.
Expand All @@ -128,7 +123,7 @@ <h3>ShapesDashAndFillFragment.java</h3>
Position.fromDegrees(10.0, -60.0, 0.0),
Position.fromDegrees(25.0, -55.0, 0.0)
);
polygon = new Polygon(positions);
Polygon polygon = new Polygon(positions);
sa = new ShapeAttributes(thickenLine);
sa.setInteriorImageSource(ImageSource.fromResource(R.drawable.pattern_sample_houndstooth));
sa.setOutlineImageSource(ImageSource.fromLineStipple(8, (short) 0xDFF6));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.layer.RenderableLayer;
import gov.nasa.worldwind.render.ImageSource;
import gov.nasa.worldwind.shape.Ellipse;
import gov.nasa.worldwind.shape.Path;
import gov.nasa.worldwind.shape.Polygon;
import gov.nasa.worldwind.shape.ShapeAttributes;
Expand Down Expand Up @@ -79,18 +80,13 @@ public WorldWindow createWorldWindow() {
path.setFollowTerrain(true);
layer.addRenderable(path);

// Create a polygon using an image as a repeating fill pattern.
positions = Arrays.asList(
Position.fromDegrees(50.0, -70.0, 1e5),
Position.fromDegrees(35.0, -85.0, 1e5),
Position.fromDegrees(35.0, -55.0, 1e5)
);
Polygon polygon = new Polygon(positions);
// Create an Ellipse using an image as a repeating fill pattern
Position ellipseCenter = new Position(40, -70.0, 1e5);
Ellipse ellipse = new Ellipse(ellipseCenter, 1.5e6, 800e3);
sa = new ShapeAttributes(thickenLine);
sa.setInteriorImageSource(ImageSource.fromResource(R.drawable.pattern_sample_houndstooth));
sa.setInteriorColor(new gov.nasa.worldwind.render.Color(1f, 1f, 1f, 1f));
polygon.setAttributes(sa);
layer.addRenderable(polygon);
ellipse.setAttributes(sa);
layer.addRenderable(ellipse);

// Create a surface polygon using an image as a repeating fill pattern and a dash pattern for the outline
// of the polygon.
Expand All @@ -100,7 +96,7 @@ public WorldWindow createWorldWindow() {
Position.fromDegrees(10.0, -60.0, 0.0),
Position.fromDegrees(25.0, -55.0, 0.0)
);
polygon = new Polygon(positions);
Polygon polygon = new Polygon(positions);
sa = new ShapeAttributes(thickenLine);
sa.setInteriorImageSource(ImageSource.fromResource(R.drawable.pattern_sample_houndstooth));
sa.setOutlineImageSource(ImageSource.fromLineStipple(8, (short) 0xDFF6));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
import gov.nasa.worldwind.PickedObject;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.geom.BoundingBox;
import gov.nasa.worldwind.geom.Matrix3;
import gov.nasa.worldwind.geom.Sector;
import gov.nasa.worldwind.geom.Vec3;
import gov.nasa.worldwind.render.AbstractRenderable;
import gov.nasa.worldwind.render.Color;
import gov.nasa.worldwind.render.RenderContext;
import gov.nasa.worldwind.render.Texture;
import gov.nasa.worldwind.util.WWMath;

public abstract class AbstractShape extends AbstractRenderable implements Attributable, Highlightable {
Expand Down Expand Up @@ -183,6 +185,16 @@ protected double cameraDistanceCartesian(RenderContext rc, float[] array, int co
return Math.sqrt(minDistance2);
}

protected Matrix3 computeRepeatingTexCoordTransform(Texture texture, double metersPerPixel, Matrix3 result) {
Matrix3 texCoordMatrix = result.setToIdentity();
texCoordMatrix.setScale(
1.0 / (texture.getWidth() * metersPerPixel),
1.0 / (texture.getHeight() * metersPerPixel));
texCoordMatrix.multiplyByMatrix(texture.getTexCoordTransform());

return texCoordMatrix;
}

protected abstract void reset();

protected abstract void makeDrawable(RenderContext rc);
Expand Down
102 changes: 88 additions & 14 deletions worldwind/src/main/java/gov/nasa/worldwind/shape/Ellipse.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@
import gov.nasa.worldwind.draw.DrawableShape;
import gov.nasa.worldwind.draw.DrawableSurfaceShape;
import gov.nasa.worldwind.geom.Location;
import gov.nasa.worldwind.geom.Matrix3;
import gov.nasa.worldwind.geom.Matrix4;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Range;
import gov.nasa.worldwind.geom.Vec3;
import gov.nasa.worldwind.render.BasicShaderProgram;
import gov.nasa.worldwind.render.BufferObject;
import gov.nasa.worldwind.render.ImageOptions;
import gov.nasa.worldwind.render.RenderContext;
import gov.nasa.worldwind.render.Texture;
import gov.nasa.worldwind.util.Logger;
import gov.nasa.worldwind.util.Pool;
import gov.nasa.worldwind.util.ShortArray;
Expand Down Expand Up @@ -81,6 +85,10 @@ public class Ellipse extends AbstractShape {
*/
protected static final int SIDE_RANGE = 2;

protected static final ImageOptions defaultInteriorImageOptions = new ImageOptions();

protected static final ImageOptions defaultOutlineImageOptions = new ImageOptions();

/**
* Simple interval count based cache of the keys for element buffers. Element buffers are dependent only on the
* number of intervals so the keys are cached here. The element buffer object itself is in the
Expand Down Expand Up @@ -145,12 +153,28 @@ public class Ellipse extends AbstractShape {

protected boolean isSurfaceShape;

protected double texCoord1d;

protected Vec3 texCoord2d = new Vec3();

protected Matrix3 texCoordMatrix = new Matrix3();

protected Matrix4 modelToTexCoord = new Matrix4();

protected double cameraDistance;

protected Vec3 prevPoint = new Vec3();

private static Position scratchPosition = new Position();

private static Vec3 scratchPoint = new Vec3();

static {
defaultInteriorImageOptions.wrapMode = WorldWind.REPEAT;
defaultOutlineImageOptions.resamplingMode = WorldWind.NEAREST_NEIGHBOR;
defaultOutlineImageOptions.wrapMode = WorldWind.REPEAT;
}

/**
* Constructs an ellipse with a null center position, and with major- and minor-radius both 0.0. This ellipse does
* not display until the center position is defined and the radii are both greater than 0.0.
Expand Down Expand Up @@ -378,7 +402,7 @@ public Ellipse setFollowTerrain(boolean followTerrain) {
* Indicates the maximum number of angular intervals that may be used to approximate this ellipse's geometry on
* screen.
*
* @return the maximum number of angular intervals
* @return the number of angular intervals
*/
public int getMaximumIntervals() {
return this.maximumIntervals;
Expand Down Expand Up @@ -435,6 +459,7 @@ protected void makeDrawable(RenderContext rc) {
drawable = DrawableSurfaceShape.obtain(pool);
drawState = ((DrawableSurfaceShape) drawable).drawState;
((DrawableSurfaceShape) drawable).sector.set(this.boundingSector);
this.cameraDistance = this.cameraDistanceGeographic(rc, this.boundingSector);
} else {
Pool<DrawableShape> pool = rc.getDrawablePool(DrawableShape.class);
drawable = DrawableShape.obtain(pool);
Expand Down Expand Up @@ -498,7 +523,21 @@ protected void drawInterior(RenderContext rc, DrawShapeState drawState) {
return;
}

drawState.texture(null);
// Configure the drawable to use the interior texture when drawing the interior.
if (this.activeAttributes.interiorImageSource != null) {
Texture texture = rc.getTexture(this.activeAttributes.interiorImageSource);
if (texture == null) {
texture = rc.retrieveTexture(this.activeAttributes.interiorImageSource, defaultInteriorImageOptions);
}
if (texture != null) {
double metersPerPixel = rc.pixelSizeAtDistance(this.cameraDistance);
this.computeRepeatingTexCoordTransform(texture, metersPerPixel, this.texCoordMatrix);
drawState.texture(texture);
drawState.texCoordMatrix(this.texCoordMatrix);
}
} else {
drawState.texture(null);
}

// Configure the drawable to display the shape's interior.
drawState.color(rc.pickMode ? this.pickColor : this.activeAttributes.interiorColor);
Expand All @@ -509,6 +548,7 @@ protected void drawInterior(RenderContext rc, DrawShapeState drawState) {

if (this.extrude) {
Range side = drawState.elementBuffer.ranges.get(SIDE_RANGE);
drawState.texture(null);
drawState.drawElements(GLES20.GL_TRIANGLE_STRIP, side.length(),
GLES20.GL_UNSIGNED_SHORT, side.lower * 2);
}
Expand All @@ -519,7 +559,21 @@ protected void drawOutline(RenderContext rc, DrawShapeState drawState) {
return;
}

drawState.texture(null);
// Configure the drawable to use the outline texture when drawing the outline.
if (this.activeAttributes.outlineImageSource != null) {
Texture texture = rc.getTexture(this.activeAttributes.outlineImageSource);
if (texture == null) {
texture = rc.retrieveTexture(this.activeAttributes.outlineImageSource, defaultOutlineImageOptions);
}
if (texture != null) {
double metersPerPixel = rc.pixelSizeAtDistance(this.cameraDistance);
this.computeRepeatingTexCoordTransform(texture, metersPerPixel, this.texCoordMatrix);
drawState.texture(texture);
drawState.texCoordMatrix(this.texCoordMatrix);
}
} else {
drawState.texture(null);
}

// Configure the drawable to display the shape's outline.
drawState.color(rc.pickMode ? this.pickColor : this.activeAttributes.outlineColor);
Expand All @@ -531,6 +585,9 @@ protected void drawOutline(RenderContext rc, DrawShapeState drawState) {

if (this.activeAttributes.drawVerticals && this.extrude) {
Range side = drawState.elementBuffer.ranges.get(SIDE_RANGE);
drawState.color(rc.pickMode ? this.pickColor : this.activeAttributes.outlineColor);
drawState.lineWidth(this.activeAttributes.outlineWidth);
drawState.texture(null);
drawState.drawElements(GLES20.GL_LINES, side.length(),
GLES20.GL_UNSIGNED_SHORT, side.lower * 2);
}
Expand All @@ -551,6 +608,9 @@ protected void assembleGeometry(RenderContext rc) {
// Determine whether the shape geometry must be assembled as Cartesian geometry or as goegraphic geometry.
this.isSurfaceShape = (this.altitudeMode == WorldWind.CLAMP_TO_GROUND) && this.followTerrain;

// Compute a matrix that transforms from Cartesian coordinates to shape texture coordinates.
this.determineModelToTexCoord(rc);

// Use the ellipse's center position as the local origin for vertex positions.
if (this.isSurfaceShape) {
this.vertexOrigin.set(this.center.longitude, this.center.latitude, this.center.altitude);
Expand All @@ -563,9 +623,7 @@ protected void assembleGeometry(RenderContext rc) {
int spineCount = computeNumberSpinePoints(this.activeIntervals); // activeIntervals must be even

// Clear the shape's vertex array. The array will accumulate values as the shapes's geometry is assembled.
// Determine the offset from the top and extruded vertices
this.vertexIndex = 0;
int arrayOffset = computeIndexOffset(this.activeIntervals) * VERTEX_STRIDE;
if (this.extrude && !this.isSurfaceShape) {
this.vertexArray = new float[(this.activeIntervals * 2 + spineCount) * VERTEX_STRIDE];
} else {
Expand All @@ -589,6 +647,8 @@ protected void assembleGeometry(RenderContext rc) {
minorArcRadians = this.majorRadius / globeRadius;
}

// Determine the offset from the top and extruded vertices
int arrayOffset = computeIndexOffset(this.activeIntervals) * VERTEX_STRIDE;
// Setup spine radius values
int spineIdx = 0;
double[] spineRadius = new double[spineCount];
Expand Down Expand Up @@ -637,7 +697,6 @@ protected static BufferObject assembleElements(int intervals) {

// Generate the top element buffer with spine
int interiorIdx = intervals;
int spinePoints = computeNumberSpinePoints(intervals);
int offset = computeIndexOffset(intervals);

// Add the anchor leg
Expand Down Expand Up @@ -697,23 +756,32 @@ protected static BufferObject assembleElements(int intervals) {
protected void addVertex(RenderContext rc, double latitude, double longitude, double altitude, int offset, boolean isExtrudedSkirt) {
int offsetVertexIndex = this.vertexIndex + offset;

Vec3 point = rc.geographicToCartesian(latitude, longitude, altitude, this.altitudeMode, scratchPoint);
Vec3 texCoord2d = this.texCoord2d.set(point).multiplyByMatrix(this.modelToTexCoord);

if (this.vertexIndex == 0) {
this.texCoord1d = 0;
this.prevPoint.set(point);
} else {
this.texCoord1d += point.distanceTo(this.prevPoint);
this.prevPoint.set(point);
}

if (this.isSurfaceShape) {
this.vertexArray[this.vertexIndex++] = (float) (longitude - this.vertexOrigin.x);
this.vertexArray[this.vertexIndex++] = (float) (latitude - this.vertexOrigin.y);
this.vertexArray[this.vertexIndex++] = (float) (altitude - this.vertexOrigin.z);
// reserved for future texture coordinate use
this.vertexArray[this.vertexIndex++] = 0;
this.vertexArray[this.vertexIndex++] = 0;
this.vertexArray[this.vertexIndex++] = 0;
this.vertexArray[this.vertexIndex++] = (float) texCoord2d.x;
this.vertexArray[this.vertexIndex++] = (float) texCoord2d.y;
this.vertexArray[this.vertexIndex++] = (float) this.texCoord1d;
} else {
Vec3 point = rc.geographicToCartesian(latitude, longitude, altitude, this.altitudeMode, scratchPoint);
this.vertexArray[this.vertexIndex++] = (float) (point.x - this.vertexOrigin.x);
this.vertexArray[this.vertexIndex++] = (float) (point.y - this.vertexOrigin.y);
this.vertexArray[this.vertexIndex++] = (float) (point.z - this.vertexOrigin.z);
// reserved for future texture coordinate use
this.vertexArray[this.vertexIndex++] = 0;
this.vertexArray[this.vertexIndex++] = 0;
this.vertexArray[this.vertexIndex++] = 0;
this.vertexArray[this.vertexIndex++] = (float) texCoord2d.x;
this.vertexArray[this.vertexIndex++] = (float) texCoord2d.y;
this.vertexArray[this.vertexIndex++] = (float) this.texCoord1d;

if (isExtrudedSkirt) {
point = rc.geographicToCartesian(latitude, longitude, 0, WorldWind.CLAMP_TO_GROUND, scratchPoint);
Expand All @@ -727,6 +795,12 @@ protected void addVertex(RenderContext rc, double latitude, double longitude, do
}
}

protected void determineModelToTexCoord(RenderContext rc) {
Vec3 point = rc.geographicToCartesian(this.center.latitude, this.center.longitude, this.center.altitude, this.altitudeMode, scratchPoint);
this.modelToTexCoord = rc.globe.cartesianToLocalTransform(point.x, point.y, point.z, this.modelToTexCoord);
this.modelToTexCoord.invertOrthonormal();
}

/**
* Calculate the number of times to split the edges of the shape for geometry assembly.
*
Expand Down
4 changes: 1 addition & 3 deletions worldwind/src/main/java/gov/nasa/worldwind/shape/Path.java
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,7 @@ protected void makeDrawable(RenderContext rc) {
}
if (texture != null) {
double metersPerPixel = rc.pixelSizeAtDistance(cameraDistance);
Matrix3 texCoordMatrix = this.texCoordMatrix.setToIdentity();
texCoordMatrix.setScale(1.0 / (texture.getWidth() * metersPerPixel), 1.0);
texCoordMatrix.multiplyByMatrix(texture.getTexCoordTransform());
this.computeRepeatingTexCoordTransform(texture, metersPerPixel, this.texCoordMatrix);
drawState.texture(texture);
drawState.texCoordMatrix(texCoordMatrix);
}
Expand Down
10 changes: 2 additions & 8 deletions worldwind/src/main/java/gov/nasa/worldwind/shape/Polygon.java
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,7 @@ protected void drawInterior(RenderContext rc, DrawShapeState drawState) {
}
if (texture != null) {
double metersPerPixel = rc.pixelSizeAtDistance(this.cameraDistance);
Matrix3 texCoordMatrix = this.texCoordMatrix.setToIdentity();
texCoordMatrix.setScale(
1.0 / (texture.getWidth() * metersPerPixel),
1.0 / (texture.getHeight() * metersPerPixel));
texCoordMatrix.multiplyByMatrix(texture.getTexCoordTransform());
this.computeRepeatingTexCoordTransform(texture, metersPerPixel, this.texCoordMatrix);
drawState.texture(texture);
drawState.texCoordMatrix(texCoordMatrix);
}
Expand Down Expand Up @@ -388,9 +384,7 @@ protected void drawOutline(RenderContext rc, DrawShapeState drawState) {
}
if (texture != null) {
double metersPerPixel = rc.pixelSizeAtDistance(this.cameraDistance);
Matrix3 texCoordMatrix = this.texCoordMatrix.setToIdentity();
texCoordMatrix.setScale(1.0 / (texture.getWidth() * metersPerPixel), 1.0);
texCoordMatrix.multiplyByMatrix(texture.getTexCoordTransform());
this.computeRepeatingTexCoordTransform(texture, metersPerPixel, this.texCoordMatrix);
drawState.texture(texture);
drawState.texCoordMatrix(texCoordMatrix);
}
Expand Down

0 comments on commit 90cdd05

Please sign in to comment.