001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/ogcwebservices/wpvs/j3d/ViewPoint.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstraße 19
030     53177 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041     
042     ---------------------------------------------------------------------------*/
043    package org.deegree.ogcwebservices.wpvs.j3d;
044    
045    import javax.media.j3d.Transform3D;
046    import javax.vecmath.Point3d;
047    import javax.vecmath.Vector3d;
048    
049    import org.deegree.framework.log.ILogger;
050    import org.deegree.framework.log.LoggerFactory;
051    import org.deegree.model.crs.CoordinateSystem;
052    import org.deegree.model.spatialschema.Envelope;
053    import org.deegree.model.spatialschema.GeometryException;
054    import org.deegree.model.spatialschema.GeometryFactory;
055    import org.deegree.model.spatialschema.Position;
056    import org.deegree.model.spatialschema.Surface;
057    import org.deegree.model.spatialschema.WKTAdapter;
058    import org.deegree.ogcwebservices.wpvs.operation.GetView;
059    
060    /**
061     * This class represents the view point for a WPVS request. That is, it represents the point where
062     * the observer is at, and looking to a target point. An angle of view must be also given.
063     * 
064     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
065     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
066     * 
067     * @author last edited by: $Author: apoth $
068     * @version $Revision: 9345 $ $Date: 2007-12-27 17:22:25 +0100 (Do, 27 Dez 2007) $
069     */
070    public class ViewPoint {
071    
072        private static final ILogger LOG = LoggerFactory.getLogger( ViewPoint.class );
073    
074        private static final double rad90 = Math.toRadians( 90 );
075    
076        private static final double rad180 = Math.toRadians( 180 );
077    
078        private static final double rad270 = Math.toRadians( 270 );
079    
080        private static final double rad360 = Math.toRadians( 360 );
081    
082        private CoordinateSystem crs;
083    
084        private Point3d observerPosition;
085    
086        private Point3d pointOfInterest;
087    
088        private Point3d[] footprint;
089    
090        private Point3d[] fakeFootprint;
091    
092        private Point3d[] oldFootprint;
093    
094        private double angleOfView = 0;
095    
096        private double yaw = 0;
097    
098        private double pitch = 0;
099    
100        private double terrainDistanceToSeaLevel = 0;
101    
102        private double viewerToPOIDistance = 0;
103    
104        private double farClippingPlane = 0;
105    
106        private Transform3D simpleTransform = null;
107    
108        private Transform3D viewMatrix = null;
109    
110        /**
111         * @return the oldFootprint.
112         */
113        public Point3d[] getOldFootprint() {
114            return oldFootprint;
115        }
116    
117        /**
118         * @return the fakeFootprint.
119         */
120        public Point3d[] getFakeFootprint() {
121            return fakeFootprint;
122        }
123    
124        /**
125         * Creates a new instance of ViewPoint_Impl
126         * 
127         * @param yaw
128         *            rotation on the Z-Axis in radians of the viewer
129         * @param pitch
130         *            rotation on the X-Axis in radians
131         * @param viewerToPOIDistance
132         *            from the point of interest to the viewersposition
133         * @param pointOfInterest
134         *            the point of interest
135         * @param angleOfView
136         * @param farClippingPlane
137         *            where the view ends
138         * @param distanceToSealevel
139         * @param crs
140         *            The Coordinatesystem in which the given reside
141         */
142        public ViewPoint( double yaw, double pitch, double viewerToPOIDistance,
143                          Point3d pointOfInterest, double angleOfView, double farClippingPlane,
144                          double distanceToSealevel, CoordinateSystem crs ) {
145            this.yaw = yaw;
146            this.pitch = pitch;
147    
148            this.angleOfView = angleOfView;
149            this.pointOfInterest = pointOfInterest;
150    
151            this.viewerToPOIDistance = viewerToPOIDistance;
152    
153            this.farClippingPlane = farClippingPlane;
154    
155            this.terrainDistanceToSeaLevel = distanceToSealevel;
156            
157            this.crs = crs;
158    
159            simpleTransform = new Transform3D();
160    
161            viewMatrix = new Transform3D();
162            observerPosition = new Point3d();
163    
164            footprint = new Point3d[4];
165            fakeFootprint = new Point3d[4];
166            oldFootprint = new Point3d[4];
167            calcObserverPosition();
168    
169        }
170    
171        /**
172         * @param request
173         *            a server request.
174         */
175        public ViewPoint( GetView request ) {
176            this( request.getYaw(), request.getPitch(), request.getDistance(),
177                  request.getPointOfInterest(), request.getAngleOfView(),
178                  request.getFarClippingPlane(), 0, request.getCrs() );
179        }
180    
181        /**
182         * @param request
183         *            a server request.
184         * @param distanceToSeaLevel
185         */
186        public ViewPoint( GetView request, double distanceToSeaLevel ) {
187            this( request.getYaw(), request.getPitch(), request.getDistance(),
188                  request.getPointOfInterest(), request.getAngleOfView(),
189                  request.getFarClippingPlane(), distanceToSeaLevel, request.getCrs() );
190        }
191    
192        /**
193         * Calculates the observers position for a given pointOfInterest, distance and view direction(
194         * as semi polar coordinates, yaw & pitch ). also recalculating the viewmatrix and the
195         * footprint, for they are affected by the change of position.
196         * 
197         */
198        private void calcObserverPosition() {
199    
200            double z = Math.sin( pitch ) * this.viewerToPOIDistance;
201    
202            double groundLength = Math.sqrt( ( viewerToPOIDistance * viewerToPOIDistance ) - ( z * z ) );
203            double x = 0;
204            double y = 0;
205            // -1-> if yaw is null, we're looking to the north
206            if ( yaw >= 0 && yaw < rad90 ) {
207                x = -1 * ( Math.sin( yaw ) * groundLength );
208                y = -1 * ( Math.cos( yaw ) * groundLength );
209            } else if ( yaw >= rad90 && yaw < rad180 ) {
210                double littleYaw = yaw - rad90;
211                y = Math.sin( littleYaw ) * groundLength;
212                x = -1 * ( Math.cos( littleYaw ) * groundLength );
213            } else if ( yaw >= rad180 && yaw < rad270 ) {
214                double littleYaw = yaw - rad180;
215                x = Math.sin( littleYaw ) * groundLength;
216                y = Math.cos( littleYaw ) * groundLength;
217            } else if ( yaw >= rad270 && yaw < rad360 ) {
218                double littleYaw = yaw - rad270;
219                y = -1 * ( Math.sin( littleYaw ) * groundLength );
220                x = Math.cos( littleYaw ) * groundLength;
221            }
222    
223            observerPosition.x = pointOfInterest.x + x;
224            observerPosition.y = pointOfInterest.y + y;
225            observerPosition.z = pointOfInterest.z + z;
226    
227            calculateViewMatrix();
228            calcFootprint();
229        }
230    
231        /**
232         * Calculates the field of view aka footprint, the corner points of the intersection of the
233         * field of view with the terrain as follows, <br/>
234         * <ul>
235         * <li> f[0] = farclippingplane right side fo viewDirection </li>
236         * <li> f[1] = farclippingplane left side fo viewDirection </li>
237         * <li> f[2] = nearclippingplane right side fo viewDirection, note it can be behind the
238         * viewPosition </li>
239         * <li> f[3] = nearclippingplane left side fo viewDirection, note it can be behind the
240         * viewPosition </li>
241         * </ul>
242         * <br/> the are rotated and translated according to the simpleTranform
243         * 
244         */
245        private void calcFootprint() {
246    
247            // make the aov a little bigger, therefor the footprint is larger and no visual errors can
248            // be seen at the sides of the view (at the expense of a little larger/more requests)
249            double halfAngleOfView = ( angleOfView + ( Math.toRadians( 6 ) ) ) * 0.5;
250            if( halfAngleOfView >= (rad90*0.5) ){
251                halfAngleOfView = rad90*0.5;
252            }
253            if ( Math.abs( ( halfAngleOfView + rad90 ) % rad180 ) < 0.000001 ) {
254                LOG.logError( "The angle of view can't be a multiple of rad180" );
255                return;
256            }
257    
258            double heightAboveGround = observerPosition.z
259                                       - ( pointOfInterest.z - terrainDistanceToSeaLevel );
260            if ( heightAboveGround < 0 ) { // beneath the ground
261                LOG.logError( "the Observer is below the terrain" );
262                return;
263            }
264    
265            if ( pitch >= 0 ) { // the eye is looking down on the poi
266    
267                // caluclate the viewFrustums farClippinplane points
268                double otherCornerOffset = farClippingPlane * Math.sin( halfAngleOfView );
269                double yCornerOffset = farClippingPlane * Math.cos( halfAngleOfView );
270    
271                // farclippin plane top right
272                Point3d topRight = new Point3d( otherCornerOffset, otherCornerOffset, -yCornerOffset );
273                viewMatrix.transform( topRight );
274                footprint[0] = findIntersectionWithTerrain( new Vector3d( topRight ) );
275    
276                // farclippin plane top left
277                Point3d topLeft = new Point3d( -otherCornerOffset, otherCornerOffset, -yCornerOffset );
278                viewMatrix.transform( topLeft );
279                footprint[1] = findIntersectionWithTerrain( new Vector3d( topLeft ) );
280    
281                // farclippin plane bottom right
282                Point3d bottomRight = new Point3d( otherCornerOffset, -otherCornerOffset,
283                                                   -yCornerOffset );
284                viewMatrix.transform( bottomRight );
285                footprint[2] = findIntersectionWithTerrain( new Vector3d( bottomRight ) );
286    
287                // farclippin plane bottom left
288                Point3d bottomLeft = new Point3d( -otherCornerOffset, -otherCornerOffset,
289                                                  -yCornerOffset );
290                viewMatrix.transform( bottomLeft );
291                footprint[3] = findIntersectionWithTerrain( new Vector3d( bottomLeft ) );
292    
293            } else {
294                // TODO looking up to the poi
295            }
296            simpleTransform.rotZ( rad360 - yaw );
297            // translate to the viewersposition.
298            simpleTransform.setTranslation( new Vector3d(
299                                                          observerPosition.x,
300                                                          observerPosition.y,
301                                                          ( pointOfInterest.z - terrainDistanceToSeaLevel ) ) );
302        }
303    
304        /**
305         * For all points (x,y,z) on a plane (the terrain), the following equation defines the plane:
306         * <code> 
307         * --> ax + by + cz + d = 0
308         * - (a, b, c) the normal vector of the plane, here it is (0, 0, 1)
309         * - d the offset of the plane (terrainDistanceToSeaLevel)
310         * </code>
311         * a ray can be parametrized as follows: <code>
312         * R(s) = eye + s * normalized_Direction
313         * -s is a scaling vector, 
314         * </code>
315         * The intersection of each ray going from the eye through the farclippinplane's cornerpoints
316         * with the terrain can be calculated as follows: <code>
317         * s= (a*eye_x + b*eye_y + c*eye_z + d ) / -1* (a*norm_dir + b*norm_dir + c*norm_dir)
318         * </code>
319         * if the denominator == 0, we are parrallel (or strifing) the plane in either case no real
320         * intersection. if s < 0 or s > 1 the intersection is outside the ray_length. Applying the
321         * found s to the ray's equation results in the intersectionpoint.
322         * 
323         * @param farClippingplaneCorner
324         *            one the corners of the farclippingplane of the viewfrustum
325         * @return the intersection point with the given ray (observerposition and a farclippingplane
326         *         cornerpoint) with the terrain)
327         */
328        private Point3d findIntersectionWithTerrain( final Vector3d farClippingplaneCorner ) {
329            final Vector3d rayDir = new Vector3d( farClippingplaneCorner );
330            rayDir.sub( observerPosition );
331            final double planeDir = -terrainDistanceToSeaLevel;
332            final double numerator = -( observerPosition.z + planeDir );
333    
334            if ( Math.abs( rayDir.z ) < 0.0001f ) {
335                // Ray is paralell to plane
336                return new Point3d( farClippingplaneCorner.x, farClippingplaneCorner.y,
337                                    terrainDistanceToSeaLevel );
338            }
339            // Find distance to intersection
340            final double s = numerator / rayDir.z;
341    
342            // If the value of s is out of [0; 1], the intersection liese before or after the line
343            if ( s < 0.0f ) {
344                return new Point3d( farClippingplaneCorner.x, farClippingplaneCorner.y,
345                                    terrainDistanceToSeaLevel );
346            }
347            if ( s > 1.0f ) {
348                return new Point3d( farClippingplaneCorner.x, farClippingplaneCorner.y,
349                                    terrainDistanceToSeaLevel );
350            }
351            // Finally a real intersection
352            return new Point3d( observerPosition.x + ( s * rayDir.x ), observerPosition.y
353                                                                       + ( s * rayDir.y ),
354                                terrainDistanceToSeaLevel );
355    
356        }
357    
358        /**
359         * Sets the viewMatrix according to the given yaw, pitch and the calculated observerPosition.
360         */
361        private void calculateViewMatrix() {
362            viewMatrix.setIdentity();
363            viewMatrix.lookAt( observerPosition, pointOfInterest, new Vector3d( 0, 0, 1 ) );
364            viewMatrix.invert();
365    
366        }
367    
368        /**
369         * @return true if the near clippingplane is behind the viewposition.
370         */
371        public boolean isNearClippingplaneBehindViewPoint() {
372            if ( pitch > 0 ) { // the eye is looking down on the poi
373                // pitch equals angle between upper and viewaxis, angleOfView is centered around the
374                // viewaxis
375                double angleToZ = pitch + ( angleOfView * 0.5 );
376                if ( Math.abs( angleToZ - rad90 ) > 0.00001 ) {
377                    // footprint front border distance
378                    if ( angleToZ > rad90 ) {
379                        return true;
380                    }
381                }
382            } else {
383                // TODO looking up to the poi
384            }
385    
386            return false;
387        }
388    
389        /**
390         * 
391         * @return the field of view of the observer in radians
392         */
393        public double getAngleOfView() {
394            return angleOfView;
395        }
396    
397        /**
398         * @param aov
399         *            the field of view of the observer in radians
400         */
401        public void setAngleOfView( double aov ) {
402            this.angleOfView = aov;
403            calcFootprint();
404        }
405    
406        /**
407         * 
408         * @return the horizontal direction in radians the observer looks
409         */
410        public double getYaw() {
411            return yaw;
412        }
413    
414        /**
415         * 
416         * @param yaw
417         *            the horizontal direction in radians the observer looks
418         */
419        public void setYaw( double yaw ) {
420            this.yaw = yaw;
421            calcObserverPosition();
422        }
423    
424        /**
425         * @return vertical direction in radians the observer looks
426         */
427        public double getPitch() {
428            return pitch;
429        }
430    
431        /**
432         * @param pitch
433         *            the vertical direction in radians the observer looks
434         * 
435         */
436        public void setPitch( double pitch ) {
437            this.pitch = ( pitch % rad90 );
438            calcObserverPosition();
439        }
440    
441        /**
442         * @return Returns the distanceToSeaLevel of the terrain beneath the viewpoint.
443         */
444        public double getTerrainDistanceToSeaLevel() {
445            return terrainDistanceToSeaLevel;
446        }
447    
448        /**
449         * @param distanceToSeaLevel
450         *            of the terrain beneath the viewpoint
451         */
452        public void setTerrainDistanceToSeaLevel( double distanceToSeaLevel ) {
453            this.terrainDistanceToSeaLevel = distanceToSeaLevel;
454            calcFootprint();
455        }
456    
457        /**
458         * @return the position of the observer, the directions he looks and his field of view in
459         *         radians
460         * 
461         */
462        public Point3d getObserverPosition() {
463            return observerPosition;
464        }
465    
466        /**
467         * @param observerPosition
468         *            the position of the observer, the directions he looks and his field of view in
469         *            radians
470         * 
471         */
472        public void setObserverPosition( Point3d observerPosition ) {
473            this.observerPosition = observerPosition;
474            calcFootprint();
475            calculateViewMatrix();
476        }
477    
478        /**
479         * @return the point of interest to which the viewer is looking
480         */
481        public Point3d getPointOfInterest() {
482            return pointOfInterest;
483        }
484    
485        /**
486         * @param pointOfInterest
487         *            the directions the observer looks and his field of view in radians
488         * 
489         */
490        public void setPointOfInterest( Point3d pointOfInterest ) {
491            this.pointOfInterest = pointOfInterest;
492            calcObserverPosition();
493        }
494    
495        /**
496         * The footprint in object space: <br/>f[0] = (FarclippingPlaneRight) = angleOfView/2 +
497         * viewDirection.x, farclippingplaneDistance, distanceToSealevel <br/>f[1] =
498         * (FarclippingPlaneLeft) = angleOfView/2 - viewDirection.x, farclippingplaneDistance,
499         * distanceToSealevel <br/>f[2] = (NearclippingPlaneRight) = angleOfView/2 + viewDirection.x,
500         * nearclippingplaneDistance, distanceToSealevel <br/>f[3] = (NearclippingPlaneLeft) =
501         * angleOfView/2 - viewDirection.x, nearclippingplaneDistance, distanceToSealevel
502         * 
503         * @return footprint or rather the field of view
504         */
505        public Point3d[] getFootprint() {
506            return footprint;
507        }
508    
509        /**
510         * @param distanceToSeaLevel
511         *            the new height for which the footprint should be calculated
512         * @return footprint or rather the field of view
513         */
514        public Point3d[] getFootprint( double distanceToSeaLevel ) {
515            this.terrainDistanceToSeaLevel = distanceToSeaLevel;
516            calcFootprint();
517            return footprint;
518        }
519    
520        @Override
521        public String toString() {
522            StringBuffer sb = new StringBuffer();
523            sb.append( "observerPosition: " + observerPosition + "\n" );
524            sb.append( "targetPoint: " + pointOfInterest + "\n" );
525            sb.append( "distance: " + this.viewerToPOIDistance + "\n" );
526            sb.append( "footprint: ");
527            sb.append( footprint[0] + ", " );
528            sb.append( footprint[1] + ", " );
529            sb.append( footprint[2] + ", " );
530            sb.append( footprint[3] + "\n" );
531            sb.append( "aov: " + Math.toDegrees( angleOfView ) + "\n" );
532            sb.append( "yaw: " + Math.toDegrees( yaw ) + "\n" );
533            sb.append( "pitch: " + Math.toDegrees( pitch ) + "\n" );
534            sb.append( "distanceToSeaLevel: " + terrainDistanceToSeaLevel + "\n" );
535            sb.append( "farClippingPlane: " + farClippingPlane + "\n" );
536            
537            return sb.toString();
538        }
539    
540        /**
541         * @return Returns the farClippingPlane.
542         */
543        public double getFarClippingPlane() {
544            return farClippingPlane;
545        }
546    
547        /**
548         * @return Returns a new transform3D Object which contains the transformations to place the
549         *         viewers Position and his yaw viewing angle relativ to the 0,0 coordinates and the
550         *         poi.
551         */
552        public Transform3D getSimpleTransform() {
553            return new Transform3D( simpleTransform );
554        }
555    
556        /**
557         * @param transform
558         *            The transform to set.
559         */
560        public void setSimpleTransform( Transform3D transform ) {
561            this.simpleTransform = transform;
562        }
563    
564        /**
565         * @return Returns the viewMatrix.
566         */
567        public Transform3D getViewMatrix() {
568            return viewMatrix;
569        }
570    
571        /**
572         * @return the viewerToPOIDistance value.
573         */
574        public double getViewerToPOIDistance() {
575            return viewerToPOIDistance;
576        }
577    
578        /**
579         * @param viewerToPOIDistance
580         *            An other viewerToPOIDistance value.
581         */
582        public void setViewerToPOIDistance( double viewerToPOIDistance ) {
583            this.viewerToPOIDistance = viewerToPOIDistance;
584            calcObserverPosition();
585        }
586    
587        /**
588         * 
589         * @return the Footprint as a Surface (bbox)
590         */
591        public Surface getVisibleArea() {
592            double minX = Double.MAX_VALUE;
593            double minY = Double.MAX_VALUE;
594            double maxX = Double.MIN_VALUE;
595            double maxY = Double.MIN_VALUE;
596            for ( Point3d point : footprint ) {
597                if ( point.x < minX )
598                    minX = point.x;
599                if ( point.x > maxX )
600                    maxX = point.x;
601                if ( point.y < minY )
602                    minY = point.y;
603                if ( point.y > maxY )
604                    maxY = point.y;
605            }
606            Envelope env = GeometryFactory.createEnvelope( minX, minY, maxX, maxY, crs );
607            Surface s = null;
608            try {
609                s = GeometryFactory.createSurface( env, crs );
610            } catch ( GeometryException e ) {
611                e.printStackTrace();
612            }
613            return s;
614        }
615    
616        /**
617         * @return A String representation of the Footprint, so that it can be easily used in another
618         *         programm e.g. deejump
619         * @throws GeometryException
620         *             if the footprint could not be transformed to wkt.
621         */
622        public String getFootPrintAsWellKnownText()
623                                throws GeometryException {
624            Position[] pos = new Position[footprint.length + 1];
625    
626            for ( int i = 0; i < footprint.length; ++i ) {
627                Point3d point = footprint[i];
628                pos[i] = GeometryFactory.createPosition( point.x, point.y, point.z );
629            }
630            Point3d point = footprint[0];
631            pos[footprint.length] = GeometryFactory.createPosition( point.x, point.y, point.z );
632    
633            return WKTAdapter.export( GeometryFactory.createSurface( pos, null, null, crs ) ).toString();
634        }
635    
636        /**
637         * @return the ObserverPosition as a well known text String.
638         * @throws GeometryException
639         *             if the conversion fails.
640         */
641        public String getObserverPositionAsWKT()
642                                throws GeometryException {
643            return WKTAdapter.export(
644                                      GeometryFactory.createPoint( observerPosition.x,
645                                                                   observerPosition.y,
646                                                                   observerPosition.z, crs ) ).toString();
647        }
648    
649        /**
650         * @return the crs value.
651         */
652        public CoordinateSystem getCrs() {
653            return crs;
654        }
655    
656    }