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    }