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 }