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