Skip to content

Commit 90cdd05

Browse files
authored
Add Ellipse interior/outline image support (#211)
* Add texture capabilities * Add Ellipse texture fill to Shapes Dash and Fill tutorial * Refactor disparate texture coordinate matrix computation to AbstractShape
1 parent 1469d47 commit 90cdd05

File tree

6 files changed

+117
-48
lines changed

6 files changed

+117
-48
lines changed

worldwind-tutorials/src/main/assets/shapes_dash_and_fill.html

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ <h1>Shapes Dash and Fill</h1>
2929
<li>The westernmost Path specifies a dash pattern and factor.</li>
3030
<li>The middle Path modifies the dash factor from the western example.</li>
3131
<li>The easternmost Path follows terrain and uses a different pattern.</li>
32-
<li>The northern Polygon specifies a repeating fill using an image.</li>
32+
<li>The Ellipse specifies a repeating fill using an image.</li>
3333
<li>The southern Polygon uses the same repeating fill, but follows terrain and specifies a dash pattern for the
3434
outline.
3535
</li>
@@ -107,18 +107,13 @@ <h3>ShapesDashAndFillFragment.java</h3>
107107
path.setFollowTerrain(true);
108108
layer.addRenderable(path);
109109

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

123118
// Create a surface polygon using an image as a repeating fill pattern and a dash pattern for the outline
124119
// of the polygon.
@@ -128,7 +123,7 @@ <h3>ShapesDashAndFillFragment.java</h3>
128123
Position.fromDegrees(10.0, -60.0, 0.0),
129124
Position.fromDegrees(25.0, -55.0, 0.0)
130125
);
131-
polygon = new Polygon(positions);
126+
Polygon polygon = new Polygon(positions);
132127
sa = new ShapeAttributes(thickenLine);
133128
sa.setInteriorImageSource(ImageSource.fromResource(R.drawable.pattern_sample_houndstooth));
134129
sa.setOutlineImageSource(ImageSource.fromLineStipple(8, (short) 0xDFF6));

worldwind-tutorials/src/main/java/gov/nasa/worldwindx/ShapesDashAndFillFragment.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import gov.nasa.worldwind.geom.Position;
1414
import gov.nasa.worldwind.layer.RenderableLayer;
1515
import gov.nasa.worldwind.render.ImageSource;
16+
import gov.nasa.worldwind.shape.Ellipse;
1617
import gov.nasa.worldwind.shape.Path;
1718
import gov.nasa.worldwind.shape.Polygon;
1819
import gov.nasa.worldwind.shape.ShapeAttributes;
@@ -79,18 +80,13 @@ public WorldWindow createWorldWindow() {
7980
path.setFollowTerrain(true);
8081
layer.addRenderable(path);
8182

82-
// Create a polygon using an image as a repeating fill pattern.
83-
positions = Arrays.asList(
84-
Position.fromDegrees(50.0, -70.0, 1e5),
85-
Position.fromDegrees(35.0, -85.0, 1e5),
86-
Position.fromDegrees(35.0, -55.0, 1e5)
87-
);
88-
Polygon polygon = new Polygon(positions);
83+
// Create an Ellipse using an image as a repeating fill pattern
84+
Position ellipseCenter = new Position(40, -70.0, 1e5);
85+
Ellipse ellipse = new Ellipse(ellipseCenter, 1.5e6, 800e3);
8986
sa = new ShapeAttributes(thickenLine);
9087
sa.setInteriorImageSource(ImageSource.fromResource(R.drawable.pattern_sample_houndstooth));
91-
sa.setInteriorColor(new gov.nasa.worldwind.render.Color(1f, 1f, 1f, 1f));
92-
polygon.setAttributes(sa);
93-
layer.addRenderable(polygon);
88+
ellipse.setAttributes(sa);
89+
layer.addRenderable(ellipse);
9490

9591
// Create a surface polygon using an image as a repeating fill pattern and a dash pattern for the outline
9692
// of the polygon.
@@ -100,7 +96,7 @@ public WorldWindow createWorldWindow() {
10096
Position.fromDegrees(10.0, -60.0, 0.0),
10197
Position.fromDegrees(25.0, -55.0, 0.0)
10298
);
103-
polygon = new Polygon(positions);
99+
Polygon polygon = new Polygon(positions);
104100
sa = new ShapeAttributes(thickenLine);
105101
sa.setInteriorImageSource(ImageSource.fromResource(R.drawable.pattern_sample_houndstooth));
106102
sa.setOutlineImageSource(ImageSource.fromLineStipple(8, (short) 0xDFF6));

worldwind/src/main/java/gov/nasa/worldwind/shape/AbstractShape.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
import gov.nasa.worldwind.PickedObject;
99
import gov.nasa.worldwind.WorldWind;
1010
import gov.nasa.worldwind.geom.BoundingBox;
11+
import gov.nasa.worldwind.geom.Matrix3;
1112
import gov.nasa.worldwind.geom.Sector;
1213
import gov.nasa.worldwind.geom.Vec3;
1314
import gov.nasa.worldwind.render.AbstractRenderable;
1415
import gov.nasa.worldwind.render.Color;
1516
import gov.nasa.worldwind.render.RenderContext;
17+
import gov.nasa.worldwind.render.Texture;
1618
import gov.nasa.worldwind.util.WWMath;
1719

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

188+
protected Matrix3 computeRepeatingTexCoordTransform(Texture texture, double metersPerPixel, Matrix3 result) {
189+
Matrix3 texCoordMatrix = result.setToIdentity();
190+
texCoordMatrix.setScale(
191+
1.0 / (texture.getWidth() * metersPerPixel),
192+
1.0 / (texture.getHeight() * metersPerPixel));
193+
texCoordMatrix.multiplyByMatrix(texture.getTexCoordTransform());
194+
195+
return texCoordMatrix;
196+
}
197+
186198
protected abstract void reset();
187199

188200
protected abstract void makeDrawable(RenderContext rc);

worldwind/src/main/java/gov/nasa/worldwind/shape/Ellipse.java

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@
1919
import gov.nasa.worldwind.draw.DrawableShape;
2020
import gov.nasa.worldwind.draw.DrawableSurfaceShape;
2121
import gov.nasa.worldwind.geom.Location;
22+
import gov.nasa.worldwind.geom.Matrix3;
23+
import gov.nasa.worldwind.geom.Matrix4;
2224
import gov.nasa.worldwind.geom.Position;
2325
import gov.nasa.worldwind.geom.Range;
2426
import gov.nasa.worldwind.geom.Vec3;
2527
import gov.nasa.worldwind.render.BasicShaderProgram;
2628
import gov.nasa.worldwind.render.BufferObject;
29+
import gov.nasa.worldwind.render.ImageOptions;
2730
import gov.nasa.worldwind.render.RenderContext;
31+
import gov.nasa.worldwind.render.Texture;
2832
import gov.nasa.worldwind.util.Logger;
2933
import gov.nasa.worldwind.util.Pool;
3034
import gov.nasa.worldwind.util.ShortArray;
@@ -81,6 +85,10 @@ public class Ellipse extends AbstractShape {
8185
*/
8286
protected static final int SIDE_RANGE = 2;
8387

88+
protected static final ImageOptions defaultInteriorImageOptions = new ImageOptions();
89+
90+
protected static final ImageOptions defaultOutlineImageOptions = new ImageOptions();
91+
8492
/**
8593
* Simple interval count based cache of the keys for element buffers. Element buffers are dependent only on the
8694
* number of intervals so the keys are cached here. The element buffer object itself is in the
@@ -145,12 +153,28 @@ public class Ellipse extends AbstractShape {
145153

146154
protected boolean isSurfaceShape;
147155

156+
protected double texCoord1d;
157+
158+
protected Vec3 texCoord2d = new Vec3();
159+
160+
protected Matrix3 texCoordMatrix = new Matrix3();
161+
162+
protected Matrix4 modelToTexCoord = new Matrix4();
163+
148164
protected double cameraDistance;
149165

166+
protected Vec3 prevPoint = new Vec3();
167+
150168
private static Position scratchPosition = new Position();
151169

152170
private static Vec3 scratchPoint = new Vec3();
153171

172+
static {
173+
defaultInteriorImageOptions.wrapMode = WorldWind.REPEAT;
174+
defaultOutlineImageOptions.resamplingMode = WorldWind.NEAREST_NEIGHBOR;
175+
defaultOutlineImageOptions.wrapMode = WorldWind.REPEAT;
176+
}
177+
154178
/**
155179
* Constructs an ellipse with a null center position, and with major- and minor-radius both 0.0. This ellipse does
156180
* not display until the center position is defined and the radii are both greater than 0.0.
@@ -378,7 +402,7 @@ public Ellipse setFollowTerrain(boolean followTerrain) {
378402
* Indicates the maximum number of angular intervals that may be used to approximate this ellipse's geometry on
379403
* screen.
380404
*
381-
* @return the maximum number of angular intervals
405+
* @return the number of angular intervals
382406
*/
383407
public int getMaximumIntervals() {
384408
return this.maximumIntervals;
@@ -435,6 +459,7 @@ protected void makeDrawable(RenderContext rc) {
435459
drawable = DrawableSurfaceShape.obtain(pool);
436460
drawState = ((DrawableSurfaceShape) drawable).drawState;
437461
((DrawableSurfaceShape) drawable).sector.set(this.boundingSector);
462+
this.cameraDistance = this.cameraDistanceGeographic(rc, this.boundingSector);
438463
} else {
439464
Pool<DrawableShape> pool = rc.getDrawablePool(DrawableShape.class);
440465
drawable = DrawableShape.obtain(pool);
@@ -498,7 +523,21 @@ protected void drawInterior(RenderContext rc, DrawShapeState drawState) {
498523
return;
499524
}
500525

501-
drawState.texture(null);
526+
// Configure the drawable to use the interior texture when drawing the interior.
527+
if (this.activeAttributes.interiorImageSource != null) {
528+
Texture texture = rc.getTexture(this.activeAttributes.interiorImageSource);
529+
if (texture == null) {
530+
texture = rc.retrieveTexture(this.activeAttributes.interiorImageSource, defaultInteriorImageOptions);
531+
}
532+
if (texture != null) {
533+
double metersPerPixel = rc.pixelSizeAtDistance(this.cameraDistance);
534+
this.computeRepeatingTexCoordTransform(texture, metersPerPixel, this.texCoordMatrix);
535+
drawState.texture(texture);
536+
drawState.texCoordMatrix(this.texCoordMatrix);
537+
}
538+
} else {
539+
drawState.texture(null);
540+
}
502541

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

510549
if (this.extrude) {
511550
Range side = drawState.elementBuffer.ranges.get(SIDE_RANGE);
551+
drawState.texture(null);
512552
drawState.drawElements(GLES20.GL_TRIANGLE_STRIP, side.length(),
513553
GLES20.GL_UNSIGNED_SHORT, side.lower * 2);
514554
}
@@ -519,7 +559,21 @@ protected void drawOutline(RenderContext rc, DrawShapeState drawState) {
519559
return;
520560
}
521561

522-
drawState.texture(null);
562+
// Configure the drawable to use the outline texture when drawing the outline.
563+
if (this.activeAttributes.outlineImageSource != null) {
564+
Texture texture = rc.getTexture(this.activeAttributes.outlineImageSource);
565+
if (texture == null) {
566+
texture = rc.retrieveTexture(this.activeAttributes.outlineImageSource, defaultOutlineImageOptions);
567+
}
568+
if (texture != null) {
569+
double metersPerPixel = rc.pixelSizeAtDistance(this.cameraDistance);
570+
this.computeRepeatingTexCoordTransform(texture, metersPerPixel, this.texCoordMatrix);
571+
drawState.texture(texture);
572+
drawState.texCoordMatrix(this.texCoordMatrix);
573+
}
574+
} else {
575+
drawState.texture(null);
576+
}
523577

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

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

611+
// Compute a matrix that transforms from Cartesian coordinates to shape texture coordinates.
612+
this.determineModelToTexCoord(rc);
613+
554614
// Use the ellipse's center position as the local origin for vertex positions.
555615
if (this.isSurfaceShape) {
556616
this.vertexOrigin.set(this.center.longitude, this.center.latitude, this.center.altitude);
@@ -563,9 +623,7 @@ protected void assembleGeometry(RenderContext rc) {
563623
int spineCount = computeNumberSpinePoints(this.activeIntervals); // activeIntervals must be even
564624

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

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

638698
// Generate the top element buffer with spine
639699
int interiorIdx = intervals;
640-
int spinePoints = computeNumberSpinePoints(intervals);
641700
int offset = computeIndexOffset(intervals);
642701

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

759+
Vec3 point = rc.geographicToCartesian(latitude, longitude, altitude, this.altitudeMode, scratchPoint);
760+
Vec3 texCoord2d = this.texCoord2d.set(point).multiplyByMatrix(this.modelToTexCoord);
761+
762+
if (this.vertexIndex == 0) {
763+
this.texCoord1d = 0;
764+
this.prevPoint.set(point);
765+
} else {
766+
this.texCoord1d += point.distanceTo(this.prevPoint);
767+
this.prevPoint.set(point);
768+
}
769+
700770
if (this.isSurfaceShape) {
701771
this.vertexArray[this.vertexIndex++] = (float) (longitude - this.vertexOrigin.x);
702772
this.vertexArray[this.vertexIndex++] = (float) (latitude - this.vertexOrigin.y);
703773
this.vertexArray[this.vertexIndex++] = (float) (altitude - this.vertexOrigin.z);
704774
// reserved for future texture coordinate use
705-
this.vertexArray[this.vertexIndex++] = 0;
706-
this.vertexArray[this.vertexIndex++] = 0;
707-
this.vertexArray[this.vertexIndex++] = 0;
775+
this.vertexArray[this.vertexIndex++] = (float) texCoord2d.x;
776+
this.vertexArray[this.vertexIndex++] = (float) texCoord2d.y;
777+
this.vertexArray[this.vertexIndex++] = (float) this.texCoord1d;
708778
} else {
709-
Vec3 point = rc.geographicToCartesian(latitude, longitude, altitude, this.altitudeMode, scratchPoint);
710779
this.vertexArray[this.vertexIndex++] = (float) (point.x - this.vertexOrigin.x);
711780
this.vertexArray[this.vertexIndex++] = (float) (point.y - this.vertexOrigin.y);
712781
this.vertexArray[this.vertexIndex++] = (float) (point.z - this.vertexOrigin.z);
713-
// reserved for future texture coordinate use
714-
this.vertexArray[this.vertexIndex++] = 0;
715-
this.vertexArray[this.vertexIndex++] = 0;
716-
this.vertexArray[this.vertexIndex++] = 0;
782+
this.vertexArray[this.vertexIndex++] = (float) texCoord2d.x;
783+
this.vertexArray[this.vertexIndex++] = (float) texCoord2d.y;
784+
this.vertexArray[this.vertexIndex++] = (float) this.texCoord1d;
717785

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

798+
protected void determineModelToTexCoord(RenderContext rc) {
799+
Vec3 point = rc.geographicToCartesian(this.center.latitude, this.center.longitude, this.center.altitude, this.altitudeMode, scratchPoint);
800+
this.modelToTexCoord = rc.globe.cartesianToLocalTransform(point.x, point.y, point.z, this.modelToTexCoord);
801+
this.modelToTexCoord.invertOrthonormal();
802+
}
803+
730804
/**
731805
* Calculate the number of times to split the edges of the shape for geometry assembly.
732806
*

worldwind/src/main/java/gov/nasa/worldwind/shape/Path.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,7 @@ protected void makeDrawable(RenderContext rc) {
214214
}
215215
if (texture != null) {
216216
double metersPerPixel = rc.pixelSizeAtDistance(cameraDistance);
217-
Matrix3 texCoordMatrix = this.texCoordMatrix.setToIdentity();
218-
texCoordMatrix.setScale(1.0 / (texture.getWidth() * metersPerPixel), 1.0);
219-
texCoordMatrix.multiplyByMatrix(texture.getTexCoordTransform());
217+
this.computeRepeatingTexCoordTransform(texture, metersPerPixel, this.texCoordMatrix);
220218
drawState.texture(texture);
221219
drawState.texCoordMatrix(texCoordMatrix);
222220
}

worldwind/src/main/java/gov/nasa/worldwind/shape/Polygon.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -349,11 +349,7 @@ protected void drawInterior(RenderContext rc, DrawShapeState drawState) {
349349
}
350350
if (texture != null) {
351351
double metersPerPixel = rc.pixelSizeAtDistance(this.cameraDistance);
352-
Matrix3 texCoordMatrix = this.texCoordMatrix.setToIdentity();
353-
texCoordMatrix.setScale(
354-
1.0 / (texture.getWidth() * metersPerPixel),
355-
1.0 / (texture.getHeight() * metersPerPixel));
356-
texCoordMatrix.multiplyByMatrix(texture.getTexCoordTransform());
352+
this.computeRepeatingTexCoordTransform(texture, metersPerPixel, this.texCoordMatrix);
357353
drawState.texture(texture);
358354
drawState.texCoordMatrix(texCoordMatrix);
359355
}
@@ -388,9 +384,7 @@ protected void drawOutline(RenderContext rc, DrawShapeState drawState) {
388384
}
389385
if (texture != null) {
390386
double metersPerPixel = rc.pixelSizeAtDistance(this.cameraDistance);
391-
Matrix3 texCoordMatrix = this.texCoordMatrix.setToIdentity();
392-
texCoordMatrix.setScale(1.0 / (texture.getWidth() * metersPerPixel), 1.0);
393-
texCoordMatrix.multiplyByMatrix(texture.getTexCoordTransform());
387+
this.computeRepeatingTexCoordTransform(texture, metersPerPixel, this.texCoordMatrix);
394388
drawState.texture(texture);
395389
drawState.texCoordMatrix(texCoordMatrix);
396390
}

0 commit comments

Comments
 (0)