001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/ogcwebservices/wpvs/j3d/ViewPoint.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036 package org.deegree.ogcwebservices.wpvs.j3d;
037
038 import javax.media.j3d.Transform3D;
039 import javax.vecmath.Point3d;
040 import javax.vecmath.Vector3d;
041
042 import org.deegree.framework.log.ILogger;
043 import org.deegree.framework.log.LoggerFactory;
044 import org.deegree.model.crs.CoordinateSystem;
045 import org.deegree.model.spatialschema.Envelope;
046 import org.deegree.model.spatialschema.GeometryException;
047 import org.deegree.model.spatialschema.GeometryFactory;
048 import org.deegree.model.spatialschema.Position;
049 import org.deegree.model.spatialschema.Surface;
050 import org.deegree.model.spatialschema.WKTAdapter;
051 import org.deegree.ogcwebservices.wpvs.operation.GetView;
052
053 /**
054 * This class represents the view point for a WPVS request. That is, it represents the point where the observer is at,
055 * and looking to a target point. An angle of view must be also given.
056 *
057 * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
058 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
059 *
060 * @author last edited by: $Author: mschneider $
061 * @version $Revision: 18195 $ $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
062 */
063 public class ViewPoint {
064
065 private static final ILogger LOG = LoggerFactory.getLogger( ViewPoint.class );
066
067 private static final double rad90 = Math.toRadians( 90 );
068
069 private static final double rad180 = Math.toRadians( 180 );
070
071 private static final double rad270 = Math.toRadians( 270 );
072
073 private static final double rad360 = Math.toRadians( 360 );
074
075 private CoordinateSystem crs;
076
077 private Point3d observerPosition;
078
079 private Point3d pointOfInterest;
080
081 private Point3d[] footprint;
082
083 private Point3d[] fakeFootprint;
084
085 private Point3d[] oldFootprint;
086
087 private double angleOfView = 0;
088
089 private double yaw = 0;
090
091 private double pitch = 0;
092
093 private double terrainDistanceToSeaLevel = 0;
094
095 private double viewerToPOIDistance = 0;
096
097 private double farClippingPlane = 0;
098
099 private Transform3D simpleTransform = null;
100
101 private Transform3D viewMatrix = null;
102
103 /**
104 * @return the oldFootprint.
105 */
106 public Point3d[] getOldFootprint() {
107 return oldFootprint;
108 }
109
110 /**
111 * @return the fakeFootprint.
112 */
113 public Point3d[] getFakeFootprint() {
114 return fakeFootprint;
115 }
116
117 /**
118 * Creates a new instance of ViewPoint_Impl
119 *
120 * @param yaw
121 * rotation on the Z-Axis in radians of the viewer
122 * @param pitch
123 * rotation on the X-Axis in radians
124 * @param viewerToPOIDistance
125 * from the point of interest to the viewersposition
126 * @param pointOfInterest
127 * the point of interest
128 * @param angleOfView
129 * @param farClippingPlane
130 * where the view ends
131 * @param distanceToSealevel
132 * @param crs
133 * The Coordinatesystem in which the given reside
134 */
135 public ViewPoint( double yaw, double pitch, double viewerToPOIDistance, Point3d pointOfInterest,
136 double angleOfView, double farClippingPlane, double distanceToSealevel, CoordinateSystem crs ) {
137 this.yaw = yaw;
138 this.pitch = pitch;
139
140 this.angleOfView = angleOfView;
141 this.pointOfInterest = pointOfInterest;
142
143 this.viewerToPOIDistance = viewerToPOIDistance;
144
145 this.farClippingPlane = farClippingPlane;
146
147 this.terrainDistanceToSeaLevel = distanceToSealevel;
148
149 this.crs = crs;
150
151 simpleTransform = new Transform3D();
152
153 viewMatrix = new Transform3D();
154 observerPosition = new Point3d();
155
156 footprint = new Point3d[4];
157 fakeFootprint = new Point3d[4];
158 oldFootprint = new Point3d[4];
159 calcObserverPosition();
160
161 }
162
163 /**
164 * @param request
165 * a server request.
166 */
167 public ViewPoint( GetView request ) {
168 this( request.getYaw(), request.getPitch(), request.getDistance(), request.getPointOfInterest(),
169 request.getAngleOfView(), request.getFarClippingPlane(), 0, request.getCrs() );
170 }
171
172 /**
173 * @param request
174 * a server request.
175 * @param distanceToSeaLevel
176 */
177 public ViewPoint( GetView request, double distanceToSeaLevel ) {
178 this( request.getYaw(), request.getPitch(), request.getDistance(), request.getPointOfInterest(),
179 request.getAngleOfView(), request.getFarClippingPlane(), distanceToSeaLevel, request.getCrs() );
180 }
181
182 /**
183 * Calculates the observers position for a given pointOfInterest, distance and view direction( as semi polar
184 * coordinates, yaw & pitch ). also recalculating the viewmatrix and the footprint, for they are affected by the
185 * change of position.
186 *
187 */
188 private void calcObserverPosition() {
189
190 double z = Math.sin( pitch ) * this.viewerToPOIDistance;
191
192 double groundLength = Math.sqrt( ( viewerToPOIDistance * viewerToPOIDistance ) - ( z * z ) );
193 double x = 0;
194 double y = 0;
195 // -1-> if yaw is null, we're looking to the north
196 if ( yaw >= 0 && yaw < rad90 ) {
197 x = -1 * ( Math.sin( yaw ) * groundLength );
198 y = -1 * ( Math.cos( yaw ) * groundLength );
199 } else if ( yaw >= rad90 && yaw < rad180 ) {
200 double littleYaw = yaw - rad90;
201 y = Math.sin( littleYaw ) * groundLength;
202 x = -1 * ( Math.cos( littleYaw ) * groundLength );
203 } else if ( yaw >= rad180 && yaw < rad270 ) {
204 double littleYaw = yaw - rad180;
205 x = Math.sin( littleYaw ) * groundLength;
206 y = Math.cos( littleYaw ) * groundLength;
207 } else if ( yaw >= rad270 && yaw < rad360 ) {
208 double littleYaw = yaw - rad270;
209 y = -1 * ( Math.sin( littleYaw ) * groundLength );
210 x = Math.cos( littleYaw ) * groundLength;
211 }
212
213 observerPosition.x = pointOfInterest.x + x;
214 observerPosition.y = pointOfInterest.y + y;
215 observerPosition.z = pointOfInterest.z + z;
216
217 calculateViewMatrix();
218 calcFootprint();
219 }
220
221 /**
222 * Calculates the field of view aka footprint, the corner points of the intersection of the field of view with the
223 * terrain as follows, <br/>
224 * <ul>
225 * <li> f[0] = farclippingplane right side fo viewDirection </li>
226 * <li> f[1] = farclippingplane left side fo viewDirection </li>
227 * <li> f[2] = nearclippingplane right side fo viewDirection, note it can be behind the viewPosition </li>
228 * <li> f[3] = nearclippingplane left side fo viewDirection, note it can be behind the viewPosition </li>
229 * </ul>
230 * <br/> the are rotated and translated according to the simpleTranform
231 *
232 */
233 private void calcFootprint() {
234
235 // make the aov a little bigger, therefor the footprint is larger and no visual errors can
236 // be seen at the sides of the view (at the expense of a little larger/more requests)
237 double halfAngleOfView = ( angleOfView + ( Math.toRadians( 6 ) ) ) * 0.5;
238 if ( halfAngleOfView >= ( rad90 * 0.5 ) ) {
239 halfAngleOfView = rad90 * 0.5;
240 }
241 if ( Math.abs( ( halfAngleOfView + rad90 ) % rad180 ) < 0.000001 ) {
242 LOG.logError( "The angle of view can't be a multiple of rad180" );
243 return;
244 }
245
246 double heightAboveGround = observerPosition.z - ( pointOfInterest.z - terrainDistanceToSeaLevel );
247 if ( heightAboveGround < 0 ) { // beneath the ground
248 LOG.logError( "the Observer is below the terrain" );
249 return;
250 }
251
252 if ( pitch >= 0 ) { // the eye is looking down on the poi
253
254 // caluclate the viewFrustums farClippinplane points
255 double otherCornerOffset = farClippingPlane * Math.sin( halfAngleOfView );
256 double yCornerOffset = farClippingPlane * Math.cos( halfAngleOfView );
257
258 // farclippin plane top right
259 Point3d topRight = new Point3d( otherCornerOffset, otherCornerOffset, -yCornerOffset );
260 viewMatrix.transform( topRight );
261 footprint[0] = findIntersectionWithTerrain( new Vector3d( topRight ) );
262
263 // farclippin plane top left
264 Point3d topLeft = new Point3d( -otherCornerOffset, otherCornerOffset, -yCornerOffset );
265 viewMatrix.transform( topLeft );
266 footprint[1] = findIntersectionWithTerrain( new Vector3d( topLeft ) );
267
268 // farclippin plane bottom right
269 Point3d bottomRight = new Point3d( otherCornerOffset, -otherCornerOffset, -yCornerOffset );
270 viewMatrix.transform( bottomRight );
271 footprint[2] = findIntersectionWithTerrain( new Vector3d( bottomRight ) );
272
273 // farclippin plane bottom left
274 Point3d bottomLeft = new Point3d( -otherCornerOffset, -otherCornerOffset, -yCornerOffset );
275 viewMatrix.transform( bottomLeft );
276 footprint[3] = findIntersectionWithTerrain( new Vector3d( bottomLeft ) );
277
278 } else {
279 // TODO looking up to the poi
280 }
281 simpleTransform.rotZ( rad360 - yaw );
282 // translate to the viewersposition.
283 simpleTransform.setTranslation( new Vector3d( observerPosition.x, observerPosition.y,
284 ( pointOfInterest.z - terrainDistanceToSeaLevel ) ) );
285 }
286
287 /**
288 * For all points (x,y,z) on a plane (the terrain), the following equation defines the plane: <code>
289 * --> ax + by + cz + d = 0
290 * - (a, b, c) the normal vector of the plane, here it is (0, 0, 1)
291 * - d the offset of the plane (terrainDistanceToSeaLevel)
292 * </code>
293 * a ray can be parametrized as follows: <code>
294 * R(s) = eye + s * normalized_Direction
295 * -s is a scaling vector,
296 * </code>
297 * The intersection of each ray going from the eye through the farclippinplane's cornerpoints with the terrain can
298 * be calculated as follows: <code>
299 * s= (a*eye_x + b*eye_y + c*eye_z + d ) / -1* (a*norm_dir + b*norm_dir + c*norm_dir)
300 * </code>
301 * if the denominator == 0, we are parrallel (or strifing) the plane in either case no real intersection. if s < 0
302 * or s > 1 the intersection is outside the ray_length. Applying the found s to the ray's equation results in the
303 * intersectionpoint.
304 *
305 * @param farClippingplaneCorner
306 * one the corners of the farclippingplane of the viewfrustum
307 * @return the intersection point with the given ray (observerposition and a farclippingplane cornerpoint) with the
308 * terrain)
309 */
310 private Point3d findIntersectionWithTerrain( final Vector3d farClippingplaneCorner ) {
311 final Vector3d rayDir = new Vector3d( farClippingplaneCorner );
312 rayDir.sub( observerPosition );
313 final double planeDir = -terrainDistanceToSeaLevel;
314 final double numerator = -( observerPosition.z + planeDir );
315
316 if ( Math.abs( rayDir.z ) < 0.0001f ) {
317 // Ray is paralell to plane
318 return new Point3d( farClippingplaneCorner.x, farClippingplaneCorner.y, terrainDistanceToSeaLevel );
319 }
320 // Find distance to intersection
321 final double s = numerator / rayDir.z;
322
323 // If the value of s is out of [0; 1], the intersection liese before or after the line
324 if ( s < 0.0f ) {
325 return new Point3d( farClippingplaneCorner.x, farClippingplaneCorner.y, terrainDistanceToSeaLevel );
326 }
327 if ( s > 1.0f ) {
328 return new Point3d( farClippingplaneCorner.x, farClippingplaneCorner.y, terrainDistanceToSeaLevel );
329 }
330 // Finally a real intersection
331 return new Point3d( observerPosition.x + ( s * rayDir.x ), observerPosition.y + ( s * rayDir.y ),
332 terrainDistanceToSeaLevel );
333
334 }
335
336 /**
337 * Sets the viewMatrix according to the given yaw, pitch and the calculated observerPosition.
338 */
339 private void calculateViewMatrix() {
340 viewMatrix.setIdentity();
341 viewMatrix.lookAt( observerPosition, pointOfInterest, new Vector3d( 0, 0, 1 ) );
342 viewMatrix.invert();
343
344 }
345
346 /**
347 * @return true if the near clippingplane is behind the viewposition.
348 */
349 public boolean isNearClippingplaneBehindViewPoint() {
350 if ( pitch > 0 ) { // the eye is looking down on the poi
351 // pitch equals angle between upper and viewaxis, angleOfView is centered around the
352 // viewaxis
353 double angleToZ = pitch + ( angleOfView * 0.5 );
354 if ( Math.abs( angleToZ - rad90 ) > 0.00001 ) {
355 // footprint front border distance
356 if ( angleToZ > rad90 ) {
357 return true;
358 }
359 }
360 } else {
361 // TODO looking up to the poi
362 }
363
364 return false;
365 }
366
367 /**
368 *
369 * @return the field of view of the observer in radians
370 */
371 public double getAngleOfView() {
372 return angleOfView;
373 }
374
375 /**
376 * @param aov
377 * the field of view of the observer in radians
378 */
379 public void setAngleOfView( double aov ) {
380 this.angleOfView = aov;
381 calcFootprint();
382 }
383
384 /**
385 *
386 * @return the horizontal direction in radians the observer looks
387 */
388 public double getYaw() {
389 return yaw;
390 }
391
392 /**
393 *
394 * @param yaw
395 * the horizontal direction in radians the observer looks
396 */
397 public void setYaw( double yaw ) {
398 this.yaw = yaw;
399 calcObserverPosition();
400 }
401
402 /**
403 * @return vertical direction in radians the observer looks
404 */
405 public double getPitch() {
406 return pitch;
407 }
408
409 /**
410 * @param pitch
411 * the vertical direction in radians the observer looks
412 *
413 */
414 public void setPitch( double pitch ) {
415 this.pitch = ( pitch % rad90 );
416 calcObserverPosition();
417 }
418
419 /**
420 * @return Returns the distanceToSeaLevel of the terrain beneath the viewpoint.
421 */
422 public double getTerrainDistanceToSeaLevel() {
423 return terrainDistanceToSeaLevel;
424 }
425
426 /**
427 * @param distanceToSeaLevel
428 * of the terrain beneath the viewpoint
429 */
430 public void setTerrainDistanceToSeaLevel( double distanceToSeaLevel ) {
431 this.terrainDistanceToSeaLevel = distanceToSeaLevel;
432 calcFootprint();
433 }
434
435 /**
436 * @return the position of the observer, the directions he looks and his field of view in radians
437 *
438 */
439 public Point3d getObserverPosition() {
440 return observerPosition;
441 }
442
443 /**
444 * @param observerPosition
445 * the position of the observer, the directions he looks and his field of view in radians
446 *
447 */
448 public void setObserverPosition( Point3d observerPosition ) {
449 this.observerPosition = observerPosition;
450 calcFootprint();
451 calculateViewMatrix();
452 }
453
454 /**
455 * @return the point of interest to which the viewer is looking
456 */
457 public Point3d getPointOfInterest() {
458 return pointOfInterest;
459 }
460
461 /**
462 * @param pointOfInterest
463 * the directions the observer looks and his field of view in radians
464 *
465 */
466 public void setPointOfInterest( Point3d pointOfInterest ) {
467 this.pointOfInterest = pointOfInterest;
468 calcObserverPosition();
469 }
470
471 /**
472 * The footprint in object space: <br/>f[0] = (FarclippingPlaneRight) = angleOfView/2 + viewDirection.x,
473 * farclippingplaneDistance, distanceToSealevel <br/>f[1] = (FarclippingPlaneLeft) = angleOfView/2 -
474 * viewDirection.x, farclippingplaneDistance, distanceToSealevel <br/>f[2] = (NearclippingPlaneRight) =
475 * angleOfView/2 + viewDirection.x, nearclippingplaneDistance, distanceToSealevel <br/>f[3] =
476 * (NearclippingPlaneLeft) = angleOfView/2 - viewDirection.x, nearclippingplaneDistance, distanceToSealevel
477 *
478 * @return footprint or rather the field of view
479 */
480 public Point3d[] getFootprint() {
481 return footprint;
482 }
483
484 /**
485 * @param distanceToSeaLevel
486 * the new height for which the footprint should be calculated
487 * @return footprint or rather the field of view
488 */
489 public Point3d[] getFootprint( double distanceToSeaLevel ) {
490 this.terrainDistanceToSeaLevel = distanceToSeaLevel;
491 calcFootprint();
492 return footprint;
493 }
494
495 @Override
496 public String toString() {
497 StringBuffer sb = new StringBuffer();
498 sb.append( "observerPosition: " + observerPosition + "\n" );
499 sb.append( "targetPoint: " + pointOfInterest + "\n" );
500 sb.append( "distance: " + this.viewerToPOIDistance + "\n" );
501 sb.append( "footprint: " );
502 sb.append( footprint[0] + ", " );
503 sb.append( footprint[1] + ", " );
504 sb.append( footprint[2] + ", " );
505 sb.append( footprint[3] + "\n" );
506 sb.append( "aov: " + Math.toDegrees( angleOfView ) + "\n" );
507 sb.append( "yaw: " + Math.toDegrees( yaw ) + "\n" );
508 sb.append( "pitch: " + Math.toDegrees( pitch ) + "\n" );
509 sb.append( "distanceToSeaLevel: " + terrainDistanceToSeaLevel + "\n" );
510 sb.append( "farClippingPlane: " + farClippingPlane + "\n" );
511
512 return sb.toString();
513 }
514
515 /**
516 * @return Returns the farClippingPlane.
517 */
518 public double getFarClippingPlane() {
519 return farClippingPlane;
520 }
521
522 /**
523 * @return Returns a new transform3D Object which contains the transformations to place the viewers Position and his
524 * yaw viewing angle relativ to the 0,0 coordinates and the poi.
525 */
526 public Transform3D getSimpleTransform() {
527 return new Transform3D( simpleTransform );
528 }
529
530 /**
531 * @param transform
532 * The transform to set.
533 */
534 public void setSimpleTransform( Transform3D transform ) {
535 this.simpleTransform = transform;
536 }
537
538 /**
539 * @return Returns the viewMatrix.
540 */
541 public Transform3D getViewMatrix() {
542 return viewMatrix;
543 }
544
545 /**
546 * @return the viewerToPOIDistance value.
547 */
548 public double getViewerToPOIDistance() {
549 return viewerToPOIDistance;
550 }
551
552 /**
553 * @param viewerToPOIDistance
554 * An other viewerToPOIDistance value.
555 */
556 public void setViewerToPOIDistance( double viewerToPOIDistance ) {
557 this.viewerToPOIDistance = viewerToPOIDistance;
558 calcObserverPosition();
559 }
560
561 /**
562 *
563 * @return the Footprint as a Surface (bbox)
564 */
565 public Surface getVisibleArea() {
566 double minX = Double.MAX_VALUE;
567 double minY = Double.MAX_VALUE;
568 double maxX = Double.MIN_VALUE;
569 double maxY = Double.MIN_VALUE;
570 for ( Point3d point : footprint ) {
571 if ( point.x < minX )
572 minX = point.x;
573 if ( point.x > maxX )
574 maxX = point.x;
575 if ( point.y < minY )
576 minY = point.y;
577 if ( point.y > maxY )
578 maxY = point.y;
579 }
580 Envelope env = GeometryFactory.createEnvelope( minX, minY, maxX, maxY, crs );
581 Surface s = null;
582 try {
583 s = GeometryFactory.createSurface( env, crs );
584 } catch ( GeometryException e ) {
585 e.printStackTrace();
586 }
587 return s;
588 }
589
590 /**
591 * @return A String representation of the Footprint, so that it can be easily used in another programm e.g. deejump
592 * @throws GeometryException
593 * if the footprint could not be transformed to wkt.
594 */
595 public String getFootPrintAsWellKnownText()
596 throws GeometryException {
597 Position[] pos = new Position[footprint.length + 1];
598
599 for ( int i = 0; i < footprint.length; ++i ) {
600 Point3d point = footprint[i];
601 pos[i] = GeometryFactory.createPosition( point.x, point.y, point.z );
602 }
603 Point3d point = footprint[0];
604 pos[footprint.length] = GeometryFactory.createPosition( point.x, point.y, point.z );
605
606 return WKTAdapter.export( GeometryFactory.createSurface( pos, null, null, crs ) ).toString();
607 }
608
609 /**
610 * @return the ObserverPosition as a well known text String.
611 * @throws GeometryException
612 * if the conversion fails.
613 */
614 public String getObserverPositionAsWKT()
615 throws GeometryException {
616 return WKTAdapter.export(
617 GeometryFactory.createPoint( observerPosition.x, observerPosition.y,
618 observerPosition.z, crs ) ).toString();
619 }
620
621 /**
622 * @return the crs value.
623 */
624 public CoordinateSystem getCrs() {
625 return crs;
626 }
627
628 }