    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }