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