/*
* The JTS Topology Suite is a collection of Java classes that
* implement the fundamental operations required to validate a given
* geo-spatial data set to a known topological specification.
*
* Copyright (C) 2001 Vivid Solutions
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.revolsys.geometry.operation.distance3d;
import com.revolsys.geometry.algorithm.RayCrossingCounter;
import com.revolsys.geometry.math.Plane3D;
import com.revolsys.geometry.math.Vector3D;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Location;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.Polygon;
import com.revolsys.geometry.model.impl.PointDoubleXY;
import com.revolsys.geometry.model.impl.PointDoubleXYZ;
/**
* Models a polygon lying in a plane in 3-dimensional Cartesian space.
* The polyogn representation is supplied
* by a {@link Polygon},
* containing coordinates with XYZ ordinates.
* 3D polygons are assumed to lie in a single plane.
* The plane best fitting the polygon coordinates is
* computed and is represented by a {@link Plane3D}.
*
* @author mdavis
*
*/
public class PlanarPolygon3D {
private static LineString project(final LineString seq, final int facingPlane) {
switch (facingPlane) {
case Plane3D.XY_PLANE:
return AxisPlaneCoordinateSequence.projectToXY(seq);
case Plane3D.XZ_PLANE:
return AxisPlaneCoordinateSequence.projectToXZ(seq);
default:
return AxisPlaneCoordinateSequence.projectToYZ(seq);
}
}
private static Point project(final Point p, final int facingPlane) {
switch (facingPlane) {
case Plane3D.XY_PLANE:
return new PointDoubleXY(p.getX(), p.getY());
case Plane3D.XZ_PLANE:
return new PointDoubleXY(p.getX(), p.getZ());
// Plane3D.YZ
default:
return new PointDoubleXY(p.getY(), p.getZ());
}
}
private int facingPlane = -1;
private final Plane3D plane;
private final Polygon poly;
public PlanarPolygon3D(final Polygon poly) {
this.poly = poly;
this.plane = findBestFitPlane(poly);
this.facingPlane = this.plane.closestAxisPlane();
}
/**
* Computes an average normal vector from a list of polygon coordinates.
* Uses Newell's method, which is based
* on the fact that the vector with components
* equal to the areas of the projection of the polygon onto
* the Cartesian axis planes is normal.
*
* @param line the sequence of coordinates for the polygon
* @return a normal vector
*/
private Vector3D averageNormal(final LineString line) {
final int vertexCount = line.getVertexCount();
double sumX = 0;
double sumY = 0;
double sumZ = 0;
for (int i = 0; i < vertexCount - 1; i++) {
final double x1 = line.getX(0);
final double y1 = line.getY(0);
final double z1 = line.getZ(0);
final double x2 = line.getX(1);
final double y2 = line.getY(1);
final double z2 = line.getZ(1);
sumX += (y1 - y2) * (z1 + z2);
sumY += (z1 - z2) * (x1 + x2);
sumZ += (x1 - x2) * (y1 + y2);
}
final double x = sumX / vertexCount;
final double y = sumY / vertexCount;
final double z = sumZ / vertexCount;
final Vector3D norm = Vector3D.newVector(x, y, z).normalize();
return norm;
}
/**
* Computes a point which is the average of all coordinates
* in a sequence.
* If the sequence lies in a single plane,
* the computed point also lies in the plane.
*
* @param seq a coordinate sequence
* @return a Point with averaged ordinates
*/
private Point averagePoint(final LineString line) {
final int vertexCount = line.getVertexCount();
double sumX = 0;
double sumY = 0;
double sumZ = 0;
for (int i = 0; i < vertexCount; i++) {
sumX += line.getCoordinate(i, Geometry.X);
sumY += line.getCoordinate(i, Geometry.Y);
sumZ += line.getCoordinate(i, Geometry.Z);
}
final double x = sumX / vertexCount;
final double y = sumY / vertexCount;
final double z = sumZ / vertexCount;
return new PointDoubleXYZ(x, y, z);
}
/**
* Finds a best-fit plane for the polygon,
* by sampling a few points from the exterior ring.
* <p>
* The algorithm used is Newell's algorithm:
* - a base point for the plane is determined from the average of all vertices
* - the normal vector is determined by
* computing the area of the projections on each of the axis planes
*
* @param poly the polygon to determine the plane for
* @return the best-fit plane
*/
private Plane3D findBestFitPlane(final Polygon poly) {
final LineString seq = poly.getShell();
final Point basePt = averagePoint(seq);
final Vector3D normal = averageNormal(seq);
return new Plane3D(normal, basePt);
}
public Plane3D getPlane() {
return this.plane;
}
public Polygon getPolygon() {
return this.poly;
}
public boolean intersects(final Point intPt) {
if (Location.EXTERIOR == locate(intPt, this.poly.getShell())) {
return false;
}
for (int i = 0; i < this.poly.getHoleCount(); i++) {
if (Location.INTERIOR == locate(intPt, this.poly.getHole(i))) {
return false;
}
}
return true;
}
public boolean intersects(final Point pt, final LineString ring) {
final LineString seq = ring;
final LineString seqProj = project(seq, this.facingPlane);
final Point ptProj = project(pt, this.facingPlane);
return Location.EXTERIOR != RayCrossingCounter.locatePointInRing(ptProj, seqProj);
}
private Location locate(final Point pt, final LineString ring) {
final LineString seq = ring;
final LineString seqProj = project(seq, this.facingPlane);
final Point ptProj = project(pt, this.facingPlane);
return RayCrossingCounter.locatePointInRing(ptProj, seqProj);
}
}