001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 }