001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wpvs/j3d/ViewPoint.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2006 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: bezema $
068 * @version $Revision: 6259 $ $Date: 2007-03-20 10:15:15 +0100 (Di, 20 Mär 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 /**
306 * For all points (x,y,z) on a plane (the terrain), the following equation defines the plane:
307 * <code>
308 * --> ax + by + cz + d = 0
309 * - (a, b, c) the normal vector of the plane, here it is (0, 0, 1)
310 * - d the offset of the plane (terrainDistanceToSeaLevel)
311 * </code>
312 * a ray can be parametrized as follows: <code>
313 * R(s) = eye + s * normalized_Direction
314 * -s is a scaling vector,
315 * </code>
316 * The intersection of each ray going from the eye through the farclippinplane's cornerpoints
317 * with the terrain can be calculated as follows: <code>
318 * s= (a*eye_x + b*eye_y + c*eye_z + d ) / -1* (a*norm_dir + b*norm_dir + c*norm_dir)
319 * </code>
320 * if the denominator == 0, we are parrallel (or strifing) the plane in either case no real
321 * intersection. if s < 0 or s > 1 the intersection is outside the ray_length. Applying the
322 * found s to the ray's equation results in the intersectionpoint.
323 *
324 * @param farClippingplaneCorner
325 * one the corners of the farclippingplane of the viewfrustum
326 * @return the intersection point with the given ray (observerposition and a farclippingplane
327 * cornerpoint) with the terrain)
328 */
329 private Point3d findIntersectionWithTerrain( final Vector3d farClippingplaneCorner ) {
330 final Vector3d rayDir = new Vector3d( farClippingplaneCorner );
331 rayDir.sub( observerPosition );
332 final double planeDir = -terrainDistanceToSeaLevel;
333 final double numerator = -( observerPosition.z + planeDir );
334
335 if ( Math.abs( rayDir.z ) < 0.0001f ) {
336 // Ray is paralell to plane
337 return new Point3d( farClippingplaneCorner.x, farClippingplaneCorner.y,
338 terrainDistanceToSeaLevel );
339 }
340 // Find distance to intersection
341 final double s = numerator / rayDir.z;
342
343 // If the value of s is out of [0; 1], the intersection liese before or after the line
344 if ( s < 0.0f ) {
345 return new Point3d( farClippingplaneCorner.x, farClippingplaneCorner.y,
346 terrainDistanceToSeaLevel );
347 }
348 if ( s > 1.0f ) {
349 return new Point3d( farClippingplaneCorner.x, farClippingplaneCorner.y,
350 terrainDistanceToSeaLevel );
351 }
352 // Finally a real intersection
353 return new Point3d( observerPosition.x + ( s * rayDir.x ), observerPosition.y
354 + ( s * rayDir.y ),
355 terrainDistanceToSeaLevel );
356
357 }
358
359 /**
360 * Sets the viewMatrix according to the given yaw, pitch and the calculated observerPosition.
361 */
362 private void calculateViewMatrix() {
363 viewMatrix.setIdentity();
364 viewMatrix.lookAt( observerPosition, pointOfInterest, new Vector3d( 0, 0, 1 ) );
365 viewMatrix.invert();
366
367 }
368
369 /**
370 * @return true if the near clippingplane is behind the viewposition.
371 */
372 public boolean isNearClippingplaneBehindViewPoint() {
373 if ( pitch > 0 ) { // the eye is looking down on the poi
374 // pitch equals angle between upper and viewaxis, angleOfView is centered around the
375 // viewaxis
376 double angleToZ = pitch + ( angleOfView * 0.5 );
377 if ( Math.abs( angleToZ - rad90 ) > 0.00001 ) {
378 // footprint front border distance
379 if ( angleToZ > rad90 ) {
380 return true;
381 }
382 }
383 } else {
384 // TODO looking up to the poi
385 }
386
387 return false;
388 }
389
390 /**
391 *
392 * @return the field of view of the observer in radians
393 */
394 public double getAngleOfView() {
395 return angleOfView;
396 }
397
398 /**
399 * @param aov
400 * the field of view of the observer in radians
401 */
402 public void setAngleOfView( double aov ) {
403 this.angleOfView = aov;
404 calcFootprint();
405 }
406
407 /**
408 *
409 * @return the horizontal direction in radians the observer looks
410 */
411 public double getYaw() {
412 return yaw;
413 }
414
415 /**
416 *
417 * @param yaw
418 * the horizontal direction in radians the observer looks
419 */
420 public void setYaw( double yaw ) {
421 this.yaw = yaw;
422 calcObserverPosition();
423 }
424
425 /**
426 * @return vertical direction in radians the observer looks
427 */
428 public double getPitch() {
429 return pitch;
430 }
431
432 /**
433 * @param pitch
434 * the vertical direction in radians the observer looks
435 *
436 */
437 public void setPitch( double pitch ) {
438 this.pitch = ( pitch % rad90 );
439 calcObserverPosition();
440 }
441
442 /**
443 * @return Returns the distanceToSeaLevel of the terrain beneath the viewpoint.
444 */
445 public double getTerrainDistanceToSeaLevel() {
446 return terrainDistanceToSeaLevel;
447 }
448
449 /**
450 * @param distanceToSeaLevel
451 * of the terrain beneath the viewpoint
452 */
453 public void setTerrainDistanceToSeaLevel( double distanceToSeaLevel ) {
454 this.terrainDistanceToSeaLevel = distanceToSeaLevel;
455 calcFootprint();
456 }
457
458 /**
459 * @return the position of the observer, the directions he looks and his field of view in
460 * radians
461 *
462 */
463 public Point3d getObserverPosition() {
464 return observerPosition;
465 }
466
467 /**
468 * @param observerPosition
469 * the position of the observer, the directions he looks and his field of view in
470 * radians
471 *
472 */
473 public void setObserverPosition( Point3d observerPosition ) {
474 this.observerPosition = observerPosition;
475 calcFootprint();
476 calculateViewMatrix();
477 }
478
479 /**
480 * @return the point of interest to which the viewer is looking
481 */
482 public Point3d getPointOfInterest() {
483 return pointOfInterest;
484 }
485
486 /**
487 * @param pointOfInterest
488 * the directions the observer looks and his field of view in radians
489 *
490 */
491 public void setPointOfInterest( Point3d pointOfInterest ) {
492 this.pointOfInterest = pointOfInterest;
493 calcObserverPosition();
494 }
495
496 /**
497 * The footprint in object space: <br/>f[0] = (FarclippingPlaneRight) = angleOfView/2 +
498 * viewDirection.x, farclippingplaneDistance, distanceToSealevel <br/>f[1] =
499 * (FarclippingPlaneLeft) = angleOfView/2 - viewDirection.x, farclippingplaneDistance,
500 * distanceToSealevel <br/>f[2] = (NearclippingPlaneRight) = angleOfView/2 + viewDirection.x,
501 * nearclippingplaneDistance, distanceToSealevel <br/>f[3] = (NearclippingPlaneLeft) =
502 * angleOfView/2 - viewDirection.x, nearclippingplaneDistance, distanceToSealevel
503 *
504 * @return footprint or rather the field of view
505 */
506 public Point3d[] getFootprint() {
507 return footprint;
508 }
509
510 /**
511 * @param distanceToSeaLevel
512 * the new height for which the footprint should be calculated
513 * @return footprint or rather the field of view
514 */
515 public Point3d[] getFootprint( double distanceToSeaLevel ) {
516 this.terrainDistanceToSeaLevel = distanceToSeaLevel;
517 calcFootprint();
518 return footprint;
519 }
520
521 @Override
522 public String toString() {
523 StringBuffer sb = new StringBuffer();
524 sb.append( "observerPosition: " + observerPosition + "\n" );
525 sb.append( "targetPoint: " + pointOfInterest + "\n" );
526 sb.append( "footprint: " + "\n" );
527 sb.append( footprint[0] + "\n" );
528 sb.append( footprint[1] + "\n" );
529 sb.append( footprint[2] + "\n" );
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 return sb.toString();
537 }
538
539 /**
540 * @return Returns the farClippingPlane.
541 */
542 public double getFarClippingPlane() {
543 return farClippingPlane;
544 }
545
546 /**
547 * @return Returns a new transform3D Object which contains the transformations to place the
548 * viewers Position and his yaw viewing angle relativ to the 0,0 coordinates and the
549 * poi.
550 */
551 public Transform3D getSimpleTransform() {
552 return new Transform3D( simpleTransform );
553 }
554
555 /**
556 * @param transform
557 * The transform to set.
558 */
559 public void setSimpleTransform( Transform3D transform ) {
560 this.simpleTransform = transform;
561 }
562
563 /**
564 * @return Returns the viewMatrix.
565 */
566 public Transform3D getViewMatrix() {
567 return viewMatrix;
568 }
569
570 /**
571 * @return the viewerToPOIDistance value.
572 */
573 public double getViewerToPOIDistance() {
574 return viewerToPOIDistance;
575 }
576
577 /**
578 * @param viewerToPOIDistance
579 * An other viewerToPOIDistance value.
580 */
581 public void setViewerToPOIDistance( double viewerToPOIDistance ) {
582 this.viewerToPOIDistance = viewerToPOIDistance;
583 calcObserverPosition();
584 }
585
586 /**
587 *
588 * @return the Footprint as a Surface (bbox)
589 */
590 public Surface getVisibleArea() {
591 double minX = Double.MAX_VALUE;
592 double minY = Double.MAX_VALUE;
593 double maxX = Double.MIN_VALUE;
594 double maxY = Double.MIN_VALUE;
595 for ( Point3d point : footprint ) {
596 if ( point.x < minX )
597 minX = point.x;
598 if ( point.x > maxX )
599 maxX = point.x;
600 if ( point.y < minY )
601 minY = point.y;
602 if ( point.y > maxY )
603 maxY = point.y;
604 }
605 Envelope env = GeometryFactory.createEnvelope( minX, minY, maxX, maxY, crs );
606 Surface s = null;
607 try {
608 s = GeometryFactory.createSurface( env, crs );
609 } catch ( GeometryException e ) {
610 e.printStackTrace();
611 }
612 return s;
613 }
614
615 /**
616 * @return A String representation of the Footprint, so that it can be easily used in another
617 * programm e.g. deejump
618 * @throws GeometryException
619 * if the footprint could not be transformed to wkt.
620 */
621 public String getFootPrintAsWellKnownText()
622 throws GeometryException {
623 Position[] pos = new Position[footprint.length + 1];
624
625 for ( int i = 0; i < footprint.length; ++i ) {
626 Point3d point = footprint[i];
627 pos[i] = GeometryFactory.createPosition( point.x, point.y, point.z );
628 }
629 Point3d point = footprint[0];
630 pos[footprint.length] = GeometryFactory.createPosition( point.x, point.y, point.z );
631
632 return WKTAdapter.export( GeometryFactory.createSurface( pos, null, null, crs ) ).toString();
633 }
634
635 /**
636 * @return the ObserverPosition as a well known text String.
637 * @throws GeometryException
638 * if the conversion fails.
639 */
640 public String getObserverPositionAsWKT()
641 throws GeometryException {
642 return WKTAdapter.export(
643 GeometryFactory.createPoint( observerPosition.x,
644 observerPosition.y,
645 observerPosition.z, crs ) ).toString();
646 }
647
648 /**
649 * @return the crs value.
650 */
651 public CoordinateSystem getCrs() {
652 return crs;
653 }
654
655 }