/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation.
*/
package org.geogebra.common.kernel.algos;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.Matrix.CoordSys;
import org.geogebra.common.kernel.commands.Commands;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.geos.GeoPolygon;
import org.geogebra.common.kernel.kernelND.GeoDirectionND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.kernel.kernelND.GeoSegmentND;
import org.geogebra.common.plugin.GeoClass;
/**
* Creates a Polygon from a given list of points or point array.
*
* @author Markus Hohenwarter
*/
public class AlgoPolygon extends AlgoElement implements PolygonAlgo {
/** input */
protected GeoPointND[] points;
/** alternative input */
protected GeoList geoList;
/** output */
protected GeoPolygon poly;
/** /2D coord sys used for 3D */
protected CoordSys cs2D;
/** polyhedron (when segment is part of), used for 3D */
protected GeoElement polyhedron;
/** normal direction, used for 3D */
protected GeoDirectionND direction;
public AlgoPolygon(Construction cons, String[] labels, GeoList geoList) {
this(cons, labels, null, geoList);
}
public AlgoPolygon(Construction cons, String[] labels,
GeoPointND[] points) {
this(cons, labels, points, null);
}
public AlgoPolygon(Construction cons, String[] labels, GeoPointND[] points,
boolean createSegments) {
this(cons, labels, points, null, null, createSegments, null, null);
}
protected AlgoPolygon(Construction cons, String[] labels,
GeoPointND[] points, GeoList geoList) {
this(cons, labels, points, geoList, null, true, null, null);
}
/**
* @param cons
* the construction
* @param points
* vertices of the polygon
* @param geoList
* list of vertices of the polygon (alternative to points)
* @param cs2D
* for 3D stuff : GeoCoordSys2D
* @param createSegments
* says if the polygon has to creates its edges (3D only)
* @param polyhedron
* polyhedron (when segment is part of), used for 3D
* @param direction
* normal direction, used for 3D
*/
protected AlgoPolygon(Construction cons, GeoPointND[] points,
GeoList geoList, CoordSys cs2D, boolean createSegments,
GeoElement polyhedron, GeoDirectionND direction) {
super(cons);
this.points = points;
this.geoList = geoList;
this.cs2D = cs2D;
this.polyhedron = polyhedron;
this.direction = direction;
// make sure that this helper algorithm is updated right after its
// parent polygon
if (polyhedron != null) {
setUpdateAfterAlgo(polyhedron.getParentAlgorithm());
}
// poly = new GeoPolygon(cons, points);
createPolygon(createSegments);
// compute polygon points
compute();
setInputOutput(); // for AlgoElement
}
/**
* @param cons
* the construction
* @param labels
* names of the polygon and the segments
* @param points
* vertices of the polygon
* @param geoList
* list of vertices of the polygon (alternative to points)
* @param cs2D
* for 3D stuff : GeoCoordSys2D
* @param createSegments
* says if the polygon has to creates its edges (3D only)
* @param polyhedron
* polyhedron (when segment is part of), used for 3D
* @param direction
* normal direction, used for 3D
*/
protected AlgoPolygon(Construction cons, String[] labels,
GeoPointND[] points, GeoList geoList, CoordSys cs2D,
boolean createSegments, GeoElement polyhedron,
GeoDirectionND direction) {
this(cons, points, geoList, cs2D, createSegments, polyhedron,
direction);
// G.Sturr 2010-3-14: Do not label segments or points for polygons
// formed by a geolist.
// (current code cannot handle sequences of variable length)
// poly.initLabels(labels);
if (geoList == null) {
poly.initLabels(labels);
} else {
if (labels != null) {
poly.setLabel(labels[0]);
}
}
// END G.Sturr
}
/**
* create the polygon
*
* @param createSegments
* says if the polygon has to creates its edges (3D only)
*/
protected void createPolygon(boolean createSegments) {
poly = new GeoPolygon(this.cons, this.points);
}
@Override
public Commands getClassName() {
return Commands.Polygon;
}
@Override
public int getRelatedModeID() {
return EuclidianConstants.MODE_POLYGON;
}
/**
* Update point array of polygon using the given array list
*
*/
protected void updatePointArray() {
// check if we have a point list
if (!geoList.getElementType().equals(GeoClass.POINT)
&& !geoList.getElementType().equals(GeoClass.POINT3D)) {
poly.setUndefined();
return;
}
// remember old number of points
int oldPointsLength = points == null ? 0 : points.length;
// create new points array
int size = geoList.size();
points = new GeoPointND[size];
for (int i = 0; i < size; i++) {
points[i] = (GeoPointND) geoList.get(i);
}
poly.setPointsAndSegments(points);
if (oldPointsLength != points.length) {
setOutput();
}
}
protected GeoElement[] createEfficientInput() {
GeoElement[] efficientInput;
if (geoList != null) {
// list as input
efficientInput = new GeoElement[1];
efficientInput[0] = geoList;
} else {
// points as input
efficientInput = new GeoElement[points.length];
for (int i = 0; i < points.length; i++) {
efficientInput[i] = (GeoElement) points[i];
}
}
return efficientInput;
}
/**
* modify input points
*
* @param newPoints
* new input points
*/
public void modifyInputPoints(GeoPointND[] newPoints) {
for (int i = 0; i < input.length; i++) {
input[i].removeAlgorithm(this);
}
points = newPoints;
poly.setPoints(points, null, false); // don't recreate segments
setInputOutput();
compute();
}
// for AlgoElement
@Override
protected void setInputOutput() {
// efficient inputs are points or list
GeoElement[] efficientInput = createEfficientInput();
// add polyhedron to inputs
if (polyhedron == null) {
input = efficientInput;
} else {
input = new GeoElement[efficientInput.length + 1];
for (int i = 0; i < efficientInput.length; i++) {
input[i] = efficientInput[i];
}
input[efficientInput.length] = polyhedron;
}
setEfficientDependencies(input, efficientInput);
// set output after, to avoid segments to have this to parent algo
setOutput();
// parent of output
poly.setParentAlgorithm(this);
cons.addToAlgorithmList(this);
}
private void setOutput() {
GeoSegmentND[] segments = poly.getSegments();
int size = 1;
if (segments != null && polyhedron == null && geoList == null) {// if
// from
// polyhedron,
// segments
// are
// output
// of
// algo
// for
// the
// polyhedron
size += segments.length;
}
super.setOutputLength(size);
super.setOutput(0, poly);
if (polyhedron == null && geoList == null) {// if from polyhedron,
// segments are output of
// algo for the polyhedron
for (int i = 0; i < size - 1; i++) {
super.setOutput(i + 1, (GeoElement) segments[i]);
}
}
}
@Override
protected void removeOutput() {
if (polyhedron == null) {
// dependent objects
super.removeOutput();
}
}
@Override
public void update() {
// compute output from input
compute();
super.getOutput(0).update();
}
public GeoPolygon getPoly() {
return poly;
}
public GeoPointND[] getPoints() {
return points;
}
public GeoElement getPolyhedron() {
return polyhedron;
}
@Override
public void remove() {
if (removed) {
return;
}
super.remove();
// if polygon is part of a polyhedron, remove it
if (polyhedron != null) {
polyhedron.remove();
}
}
@Override
public void compute() {
// AbstractApplication.printStacktrace("");
if (geoList != null) {
updatePointArray();
}
calcArea();
// update region coord sys
poly.updateRegionCS();
}
protected StringBuilder sb;
/**
* Returns the area of a polygon given by points P, negative if clockwise
* changed name from calcArea as we need the sign when calculating the
* centroid Michael Borcherds 2008-01-26 TODO Does not work if polygon is
* self-entrant
*
* @param points2
* array of points
* @return directed area
*/
final static public double calcAreaWithSign(GeoPointND[] points2) {
if (points2 == null || points2.length < 2) {
return Double.NaN;
}
int i = 0;
for (; i < points2.length; i++) {
if (points2[i].isInfinite()) {
return Double.NaN;
}
}
// area = 1/2 | det(P[i], P[i+1]) |
int last = points2.length - 1;
double sum = 0;
for (i = 0; i < last; i++) {
sum += GeoPoint.det((GeoPoint) points2[i],
(GeoPoint) points2[i + 1]);
}
sum += GeoPoint.det((GeoPoint) points2[last], (GeoPoint) points2[0]);
return sum / 2.0; // positive (anticlockwise points) or negative
// (clockwise)
}
/**
* Calculates the centroid of this polygon and writes the result to the
* given point. algorithm at
* http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/ TODO Does not
* work if polygon is self-entrant
*
* @param centroid
* point to store result
*/
public static void calcCentroid(double[] centroid, double signedArea,
GeoPointND[] points2) {
if (Double.isNaN(signedArea) || Double.isInfinite(signedArea)) { // ||
// points2
// ==
// null
// ||
// points2.length
// ==
// 0)
// {
centroid[0] = Double.NaN;
return;
}
double xsum = 0;
double ysum = 0;
double factor = 0;
for (int i = 0; i < points2.length; i++) {
factor = pointsClosedX(i, points2) * pointsClosedY(i + 1, points2)
- pointsClosedX(i + 1, points2) * pointsClosedY(i, points2);
xsum += (pointsClosedX(i, points2) + pointsClosedX(i + 1, points2))
* factor;
ysum += (pointsClosedY(i, points2) + pointsClosedY(i + 1, points2))
* factor;
}
centroid[0] = xsum;
centroid[1] = ysum;
centroid[2] = 6.0 * signedArea;
// getArea
// includes
// the +/-
// to
// compensate
// for
// clockwise/anticlockwise
}
private static double pointsClosedX(int i, GeoPointND[] points2) {
// pretend array has last element==first element
if (i == points2.length) {
// return points[0].inhomX; else return points[i].inhomX;
return ((GeoPoint) points2[0]).inhomX;
}
return ((GeoPoint) points2[i]).inhomX;
}
private static double pointsClosedY(int i, GeoPointND[] points2) {
// pretend array has last element==first element
if (i == points2.length) {
// return points[0].inhomY; else return
// points[i].inhomY;
return ((GeoPoint) points2[0]).inhomY;
}
return ((GeoPoint) points2[i]).inhomY;
}
protected void createStringBuilder(StringTemplate tpl) {
if (sb == null) {
sb = new StringBuilder();
} else {
sb.setLength(0);
}
String label;
// G.Sturr: get label from geoList (2010-3-15)
if (geoList != null) {
label = geoList.getLabel(tpl);
} else {
// use point labels
int last = points.length - 1;
for (int i = 0; i < last; i++) {
sb.append(points[i].getLabel(tpl));
sb.append(", ");
}
sb.append(points[last].getLabel(tpl));
label = sb.toString();
sb.setLength(0);
}
sb.append(getLoc().getPlain("PolygonA", label));
}
@Override
final public String toString(StringTemplate tpl) {
createStringBuilder(tpl);
return sb.toString();
}
@Override
public void calcArea() {
GeoPointND[] points2d = poly.getPoints();
// compute area
poly.setArea(calcAreaWithSign(points2d));
}
private double[] tmp3;
@Override
public void calcCentroid(GeoPoint p) {
GeoPointND[] points2d = poly.getPoints();
if (tmp3 == null) {
tmp3 = new double[3];
}
calcCentroid(tmp3, poly.getAreaWithSign(), points2d);
if (Double.isNaN(tmp3[0])) {
p.setUndefined();
} else {
p.setCoords(tmp3[0], tmp3[1], tmp3[2]);
}
}
}