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 }