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