Skip to content

Commit e546ee4

Browse files
committed
Add elevationAtLocation determination in the Globe.
Calculate correct GL near clip distance and limit Navigator to avoid terrain collisions. Fix lookAt point calculation for gesture begin - take terrain altitude in consideration. Add terrain elevation output into example application. Navigator tests were commented as it is now dependent on context.
1 parent 0dacfec commit e546ee4

8 files changed

Lines changed: 228 additions & 123 deletions

File tree

worldwind-examples/src/main/java/gov/nasa/worldwindx/GeneralGlobeActivity.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class GeneralGlobeActivity extends BasicGlobeActivity {
2929
// UI elements
3030
protected TextView latView;
3131
protected TextView lonView;
32+
protected TextView elevView;
3233
protected TextView altView;
3334
protected ImageView crosshairs;
3435
protected ViewGroup overlay;
@@ -60,6 +61,7 @@ protected void onCreate(Bundle savedInstanceState) {
6061
this.overlay.setVisibility(View.VISIBLE);
6162
this.latView = (TextView) findViewById(R.id.lat_value);
6263
this.lonView = (TextView) findViewById(R.id.lon_value);
64+
this.elevView = (TextView) findViewById(R.id.elev_value);
6365
this.altView = (TextView) findViewById(R.id.alt_value);
6466
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(this.crosshairs, "alpha", 0f).setDuration(1500);
6567
fadeOut.setStartDelay((long) 500);
@@ -135,6 +137,7 @@ protected void fadeCrosshairs() {
135137
protected void updateOverlayContents(LookAt lookAt, Camera camera) {
136138
latView.setText(formatLatitude(lookAt.latitude));
137139
lonView.setText(formatLongitude(lookAt.longitude));
140+
elevView.setText(formatElevaton(wwd.getGlobe().getElevationAtLocation(lookAt.latitude, lookAt.longitude)));
138141
altView.setText(formatAltitude(camera.altitude));
139142
}
140143

@@ -147,6 +150,7 @@ protected void updateOverlayColor(@WorldWind.NavigatorAction int eventAction) {
147150
int color = (eventAction == WorldWind.NAVIGATOR_STOPPED) ? 0xA0FFFF00 /*semi-transparent yellow*/ : Color.YELLOW;
148151
latView.setTextColor(color);
149152
lonView.setTextColor(color);
153+
elevView.setTextColor(color);
150154
altView.setTextColor(color);
151155
}
152156

@@ -160,6 +164,12 @@ protected String formatLongitude(double longitude) {
160164
return String.format("%7.3f°%s", (longitude * sign), (sign >= 0.0 ? "E" : "W"));
161165
}
162166

167+
protected String formatElevaton(double elevation) {
168+
return String.format("Alt: %,.0f %s",
169+
(elevation < 100000 ? elevation : elevation / 1000),
170+
(elevation < 100000 ? "m" : "km"));
171+
}
172+
163173
protected String formatAltitude(double altitude) {
164174
return String.format("Eye: %,.0f %s",
165175
(altitude < 100000 ? altitude : altitude / 1000),

worldwind-examples/src/main/res/layout/globe_content.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,23 @@
8282
android:padding="10dp"
8383
android:text="@string/spacer"/>
8484

85+
<TextView
86+
android:id="@+id/elev_value"
87+
android:layout_width="wrap_content"
88+
android:layout_height="wrap_content"
89+
android:shadowColor="#000000"
90+
android:shadowRadius="2"
91+
android:shadowDx="0"
92+
android:shadowDy="0"
93+
android:singleLine="true"/>
94+
95+
<TextView
96+
android:id="@+id/spacer3"
97+
android:layout_width="wrap_content"
98+
android:layout_height="wrap_content"
99+
android:padding="10dp"
100+
android:text="@string/spacer"/>
101+
85102
<TextView
86103
android:id="@+id/alt_value"
87104
android:layout_width="wrap_content"

worldwind/src/main/java/gov/nasa/worldwind/BasicFrameController.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ protected void renderTerrainPickedObject(RenderContext rc) {
6565
// picked object ID and the intersection position.
6666
if (rc.pickRay != null && rc.terrain.intersect(rc.pickRay, this.pickPoint)) {
6767
rc.globe.cartesianToGeographic(this.pickPoint.x, this.pickPoint.y, this.pickPoint.z, this.pickPos);
68-
this.pickPos.altitude = 0; // report the actual altitude, which may not lie on the terrain's surface
6968
rc.offerPickedObject(PickedObject.fromTerrain(pickedObjectId, this.pickPos));
7069
}
7170
}

worldwind/src/main/java/gov/nasa/worldwind/Navigator.java

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,29 @@ public class Navigator {
2828

2929
protected double roll;
3030

31-
private Camera scratchCamera = new Camera();
31+
private final Camera scratchCamera = new Camera();
3232

33-
private Matrix4 modelview = new Matrix4();
33+
private final Matrix4 modelview = new Matrix4();
3434

35-
private Matrix4 origin = new Matrix4();
35+
private final Matrix4 origin = new Matrix4();
3636

37-
private Vec3 originPoint = new Vec3();
37+
private final Vec3 originPoint = new Vec3();
3838

39-
private Position originPos = new Position();
39+
private final Position originPos = new Position();
4040

41-
private Line forwardRay = new Line();
41+
private final Line forwardRay = new Line();
4242

43-
public Navigator() {
43+
private final WorldWindow wwd;
44+
45+
private final static double COLLISION_THRESHOLD = 10.0; // 10m above surface
46+
47+
public Navigator(WorldWindow wwd) {
48+
if (wwd == null) {
49+
throw new IllegalArgumentException(
50+
Logger.logMessage(Logger.ERROR, "Navigator", "constructor", "missingWorldWindow"));
51+
}
52+
53+
this.wwd = wwd;
4454
}
4555

4656
public double getLatitude() {
@@ -193,16 +203,23 @@ public Matrix4 getAsViewingMatrix(Globe globe, Matrix4 result) {
193203

194204
protected LookAt cameraToLookAt(Globe globe, Camera camera, LookAt result) {
195205
this.cameraToViewingMatrix(globe, camera, this.modelview);
196-
this.modelview.extractEyePoint(this.forwardRay.origin);
197-
this.modelview.extractForwardVector(this.forwardRay.direction);
198206

199-
if (!globe.intersect(this.forwardRay, this.originPoint)) {
200-
double horizon = globe.horizonDistance(camera.altitude);
201-
this.forwardRay.pointAt(horizon, this.originPoint);
207+
// Pick terrain located behind the viewport center point
208+
PickedObject terrainPickedObject = wwd.pick(wwd.getViewport().width / 2f, wwd.getViewport().height / 2f).terrainPickedObject();
209+
if (terrainPickedObject != null) {
210+
// Use picked terrain position including approximate rendered altitude
211+
this.originPos.set(terrainPickedObject.getTerrainPosition());
212+
globe.geographicToCartesian(this.originPos.latitude, this.originPos.longitude, this.originPos.altitude, this.originPoint);
213+
globe.cartesianToLocalTransform(this.originPoint.x, this.originPoint.y, this.originPoint.z, this.origin);
214+
} else {
215+
// Center is outside the globe - use point on horizon
216+
this.modelview.extractEyePoint(this.forwardRay.origin);
217+
this.modelview.extractForwardVector(this.forwardRay.direction);
218+
this.forwardRay.pointAt(globe.horizonDistance(camera.altitude), this.originPoint);
219+
globe.cartesianToGeographic(this.originPoint.x, this.originPoint.y, this.originPoint.z, this.originPos);
220+
globe.cartesianToLocalTransform(this.originPoint.x, this.originPoint.y, this.originPoint.z, this.origin);
202221
}
203222

204-
globe.cartesianToGeographic(this.originPoint.x, this.originPoint.y, this.originPoint.z, this.originPos);
205-
globe.cartesianToLocalTransform(this.originPoint.x, this.originPoint.y, this.originPoint.z, this.origin);
206223
this.modelview.multiplyByMatrix(this.origin);
207224

208225
result.latitude = this.originPos.latitude;
@@ -247,6 +264,25 @@ protected Camera lookAtToCamera(Globe globe, LookAt lookAt, Camera result) {
247264
result.tilt = this.modelview.extractTilt();
248265
result.roll = lookAt.roll; // roll passes straight through
249266

267+
// Check if camera altitude is not under the surface
268+
double elevation = globe.getElevationAtLocation(result.latitude, result.longitude) * wwd.getVerticalExaggeration() + COLLISION_THRESHOLD;
269+
if(elevation > result.altitude) {
270+
// Set camera altitude above the surface
271+
result.altitude = elevation;
272+
// Compute new camera point
273+
globe.geographicToCartesian(result.latitude, result.longitude, result.altitude, originPoint);
274+
// Compute look at point
275+
globe.geographicToCartesian(lookAt.latitude, lookAt.longitude, lookAt.altitude, forwardRay.origin);
276+
// Compute normal to globe in look at point
277+
globe.geographicToCartesianNormal(lookAt.latitude, lookAt.longitude, forwardRay.direction);
278+
// Calculate tilt angle between new camera point and look at point
279+
originPoint.subtract(forwardRay.origin).normalize();
280+
double dot = forwardRay.direction.dot(originPoint);
281+
if (dot >= -1 || dot <= 1) {
282+
result.tilt = Math.toDegrees(Math.acos(dot));
283+
}
284+
}
285+
250286
return result;
251287
}
252288

worldwind/src/main/java/gov/nasa/worldwind/NavigatorEventSupport.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ public boolean handleMessage(Message msg) {
4141
}
4242
});
4343

44+
protected Handler moveHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
45+
@Override
46+
public boolean handleMessage(Message msg) {
47+
onNavigatorMoved();
48+
return false;
49+
}
50+
});
51+
4452
public NavigatorEventSupport(WorldWindow wwd) {
4553
if (wwd == null) {
4654
throw new IllegalArgumentException(
@@ -53,6 +61,7 @@ public NavigatorEventSupport(WorldWindow wwd) {
5361
public void reset() {
5462
this.lastModelview = null;
5563
this.stopHandler.removeMessages(0 /*what*/);
64+
this.moveHandler.removeMessages(0 /*what*/);
5665

5766
if (this.lastTouchEvent != null) {
5867
this.lastTouchEvent.recycle();
@@ -113,10 +122,14 @@ public void onFrameRendered(RenderContext rc) {
113122

114123
if (this.lastModelview == null) { // this is the first frame; copy the frame's modelview
115124
this.lastModelview = new Matrix4(rc.modelview);
125+
// Notify listeners with stopped event on first frame
126+
this.stopHandler.removeMessages(0 /*what*/);
127+
this.stopHandler.sendEmptyMessage(0 /*what*/);
116128
} else if (!this.lastModelview.equals(rc.modelview)) { // the frame's modelview has changed
117129
this.lastModelview.set(rc.modelview);
118130
// Notify the listeners of a navigator moved event.
119-
this.onNavigatorMoved();
131+
this.moveHandler.removeMessages(0 /*what*/);
132+
this.moveHandler.sendEmptyMessage(0/*what*/);
120133
// Schedule a navigator stopped event after a specified delay in milliseconds.
121134
this.stopHandler.removeMessages(0 /*what*/);
122135
this.stopHandler.sendEmptyMessageDelayed(0 /*what*/, this.stoppedEventDelay);

worldwind/src/main/java/gov/nasa/worldwind/WorldWindow.java

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public class WorldWindow extends GLSurfaceView implements Choreographer.FrameCal
7575

7676
protected double fieldOfView = 45;
7777

78-
protected Navigator navigator = new Navigator();
78+
protected Navigator navigator = new Navigator(this);
7979

8080
protected NavigatorEventSupport navigatorEvents = new NavigatorEventSupport(this);
8181

@@ -389,6 +389,10 @@ public void setRenderResourceCache(RenderResourceCache cache) {
389389
this.renderResourceCache = cache;
390390
}
391391

392+
public Viewport getViewport() {
393+
return this.viewport;
394+
}
395+
392396
/**
393397
* Determines the WorldWind objects displayed at a screen point. The screen point is interpreted as coordinates in
394398
* Android screen pixels relative to this View.
@@ -1039,21 +1043,26 @@ protected void computeViewingTransform(Matrix4 projection, Matrix4 modelview) {
10391043
double eyeAltitude = this.navigator.getAltitude();
10401044
double eyeHorizon = this.globe.horizonDistance(eyeAltitude);
10411045
double atmosphereHorizon = this.globe.horizonDistance(160000);
1042-
double near = eyeAltitude * 0.5;
1043-
double far = eyeHorizon + atmosphereHorizon;
10441046

1045-
// Computes the near clip distance that provides a minimum resolution at the far clip plane, based on the OpenGL
1046-
// context's depth buffer precision.
1047-
if (this.depthBits != 0) {
1048-
double maxDepthValue = (1 << this.depthBits) - 1;
1049-
double farResolution = 10.0;
1050-
double nearDistance = far / (maxDepthValue / (1 - farResolution / far) - maxDepthValue + 1);
1051-
// Use the computed near distance only when it's less than our default distance.
1052-
if (near > nearDistance) {
1053-
near = nearDistance;
1054-
}
1047+
// The far distance is set to the smallest value that does not clip the atmosphere.
1048+
double far = eyeHorizon + atmosphereHorizon;
1049+
if (far < 1e3) far = 1e3;
1050+
1051+
//The near distance is set to a large value that does not clip the globe's surface.
1052+
double maxDepthValue = (1L << this.depthBits) - 1L;
1053+
double farResolution = 10.0;
1054+
double near = far / (maxDepthValue / (1 - farResolution / far) - maxDepthValue + 1);
1055+
1056+
// Prevent the near clip plane from intersecting the terrain.
1057+
double distanceToSurface = this.navigator.getAltitude() - this.globe.getElevationAtLocation(this.navigator.getLatitude(), this.navigator.getLongitude()) * this.getVerticalExaggeration();
1058+
if (distanceToSurface > 0) {
1059+
double tanHalfFov = Math.tan(0.5 * Math.toRadians(this.fieldOfView));
1060+
double maxNearDistance = distanceToSurface / (2 * Math.sqrt(2 * tanHalfFov * tanHalfFov + 1));
1061+
if (near > maxNearDistance) near = maxNearDistance;
10551062
}
10561063

1064+
if (near < 1) near = 1;
1065+
10571066
// Compute a perspective projection matrix given the WorldWindow's viewport, field of view, and clip distances.
10581067
projection.setToPerspectiveProjection(this.viewport.width, this.viewport.height, this.fieldOfView, near, far);
10591068

worldwind/src/main/java/gov/nasa/worldwind/globe/Globe.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ public class Globe {
3232
*/
3333
protected GeographicProjection projection;
3434

35+
private final float[] scratchHeights = new float[1];
36+
37+
private final Sector scratchSector = new Sector();
38+
3539
/**
3640
* Constructs a globe with a specified reference ellipsoid and projection.
3741
*
@@ -320,4 +324,19 @@ public boolean intersect(Line line, Vec3 result) {
320324

321325
return this.projection.intersect(this, line, result);
322326
}
327+
328+
/**
329+
* Determine terrain altitude in specified geographic point from elevation model
330+
*
331+
* @param latitude location latitude
332+
* @param longitude location longitude
333+
*
334+
* @return Elevation in meters in specified location
335+
*/
336+
public double getElevationAtLocation(double latitude, double longitude) {
337+
// Use 1E-15 below because sector can not have zero deltas
338+
this.scratchSector.set(latitude, longitude, 1E-15, 1E-15);
339+
this.getElevationModel().getHeightGrid(this.scratchSector, 1, 1, this.scratchHeights);
340+
return this.scratchHeights[0];
341+
}
323342
}

0 commit comments

Comments
 (0)