/* Copyright (C) 2001, 2008 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. */ package gov.nasa.worldwind.util; import gov.nasa.worldwind.geom.*; import gov.nasa.worldwind.globes.Globe; import gov.nasa.worldwind.util.Logging; /** * Contains methods to resolve ray intersections with the terrain. * @author Patrick Murris * @version $Id: RayCastingSupport.java 4936 2008-04-06 21:04:47Z patrickmurris $ */ public class RayCastingSupport { private static double defaultSampleLength = 100; // meters private static double defaultPrecision = 10; // meters /** * Compute the intersection Position of the globe terrain with the ray starting * at origin in the given direction. Uses default sample length and result precision. * @param globe the globe to intersect with. * @param origin origin of the ray. * @param direction direction of the ray. * @return the Position found or null. */ public static Position intersectRayWithTerrain(Globe globe, Vec4 origin, Vec4 direction) { return intersectRayWithTerrain(globe, origin, direction, defaultSampleLength, defaultPrecision); } /** * Compute the intersection Position of the globe terrain with the ray starting * at origin in the given direction. Uses the given sample length and result precision. * @param globe the globe to intersect with. * @param origin origin of the ray. * @param direction direction of the ray. * @param sampleLength the sampling step length in meters. * @param precision the maximum sampling error in meters. * @return the Position found or null. */ public static Position intersectRayWithTerrain(Globe globe, Vec4 origin, Vec4 direction, double sampleLength, double precision) { if (globe == null) { String msg = Logging.getMessage("nullValue.GlobeIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (origin == null || direction == null) { String msg = Logging.getMessage("nullValue.Vec4IsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (sampleLength < 0) { String msg = Logging.getMessage("generic.ArgumentOutOfRange", sampleLength); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (precision < 0) { String msg = Logging.getMessage("generic.ArgumentOutOfRange", precision); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } Position pos = null; direction = direction.normalize3(); // Check whether we intersect the globe at it's highest elevation Intersection inters[] = globe.intersect(new Line(origin, direction), globe.getMaxElevation()); if (inters != null) { // Sort out intersection points and direction Vec4 p1 = inters[0].getIntersectionPoint(); Vec4 p2 = null; if (p1.subtract3(origin).dot3(direction) < 0) p1 = null; // wrong direction if (inters.length == 2) { p2 = inters[1].getIntersectionPoint(); if (p2.subtract3(origin).dot3(direction) < 0) p2 = null; // wrong direction } if (p1 == null && p2 == null) // both points in wrong direction return null; if (p1 != null && p2 != null) { // Outside sphere move to closest point if (origin.distanceTo3(p1) > origin.distanceTo3(p2)) { // switch p1 and p2 Vec4 temp = p2; p2 = p1; p1 = temp; } } else { // single point in right direction: inside sphere p2 = p2 == null ? p1 : p2; p1 = origin; } // Sample between p1 and p2 Vec4 point = intersectSegmentWithTerrain(globe, p1, p2, sampleLength, precision); if (point != null) pos = globe.computePositionFromPoint(point); } return pos; } /** * Compute the intersection Vec4 point of the globe terrain with a line segment * defined between two points. Uses the default sample length and result precision. * @param globe the globe to intersect with. * @param p1 segment start point. * @param p2 segment end point. * @return the Vec4 point found or null. */ public static Vec4 intersectSegmentWithTerrain(Globe globe, Vec4 p1, Vec4 p2) { return intersectSegmentWithTerrain(globe, p1, p2, defaultSampleLength, defaultPrecision); } /** * Compute the intersection Vec4 point of the globe terrain with the a segment * defined between two points. Uses the given sample length and result precision. * @param globe the globe to intersect with. * @param p1 segment start point. * @param p2 segment end point. * @param sampleLength the sampling step length in meters. * @param precision the maximum sampling error in meters. * @return the Vec4 point found or null. */ public static Vec4 intersectSegmentWithTerrain(Globe globe, Vec4 p1, Vec4 p2, double sampleLength, double precision) { if (globe == null) { String msg = Logging.getMessage("nullValue.GlobeIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (p1 == null || p2 == null) { String msg = Logging.getMessage("nullValue.Vec4IsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (sampleLength < 0) { String msg = Logging.getMessage("generic.ArgumentOutOfRange", sampleLength); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (precision < 0) { String msg = Logging.getMessage("generic.ArgumentOutOfRange", precision); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } Vec4 point = null; // Sample between p1 and p2 Line ray = new Line(p1, p2.subtract3(p1).normalize3()); double rayLength = p1.distanceTo3(p2); double sampledDistance = 0; Vec4 sample = p1; Vec4 lastSample = null; while (sampledDistance <= rayLength) { Position samplePos = globe.computePositionFromPoint(sample); if (samplePos.getElevation() <= globe.getElevation(samplePos.getLatitude(), samplePos.getLongitude())) { // Below ground, intersection found point = sample; break; } if (sampledDistance >= rayLength) break; // break after last sample // Keep sampling lastSample = sample; sampledDistance = Math.min(sampledDistance + sampleLength, rayLength); sample = ray.getPointAt(sampledDistance); } // Recurse for more precision if needed if (point != null && sampleLength > precision && lastSample != null) point = intersectSegmentWithTerrain(globe, lastSample, point, sampleLength / 10, precision); return point; } }