/*
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.kernel.Construction;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.geos.GeoPolygon;
import org.geogebra.common.kernel.geos.GeoSegment;
import org.geogebra.common.util.clipper.Clipper;
import org.geogebra.common.util.clipper.Clipper.ClipType;
import org.geogebra.common.util.clipper.Clipper.PolyFillType;
import org.geogebra.common.util.clipper.Clipper.PolyType;
import org.geogebra.common.util.clipper.DefaultClipper;
import org.geogebra.common.util.clipper.Path;
import org.geogebra.common.util.clipper.Paths;
import org.geogebra.common.util.clipper.Point.DoublePoint;
/**
*
* Input: Two polygons
*
* Output: Polygon that is the result of an intersection, union or difference
* operation on the input polygons.
*
* @author G.Sturr 2010-3-14, Modified by Thilina 20-05-2015 using clipper
* library
*
*/
public abstract class AlgoPolygonOperation extends AlgoElement {
/** first input polygon */
protected GeoPolygon inPoly0;
/** second input polygon */
protected GeoPolygon inPoly1;
/** output polygons */
protected OutputHandler<GeoPolygon> outputPolygons;
/** output points */
protected OutputHandler<GeoPoint> outputPoints;
/** output segments */
protected OutputHandler<GeoSegment> outputSegments;
private Path subject;
private Path clip;
private Paths solution;
/**
* whether labels were suppressed during constructor; in such case never
* label outputs.
**/
private boolean silent;
/** operation type */
protected PolyOperation operationType;
/** output labels */
protected String[] labels;
/** operation type */
public enum PolyOperation {
/** intersection */
INTERSECTION,
/** union */
UNION,
/** difference */
DIFFERENCE,
/** xor */
XOR
}
/**
* special constructor without operation type for CmdDifference. after this
* constructor initiatePolyOperation() must be called
*
* @param cons
* construction
* @param labels
* labels
* @param inPoly0
* input polygon 1
* @param inPoly1
* input polygon 2
*
*/
public AlgoPolygonOperation(Construction cons, String[] labels,
GeoPolygon inPoly0, GeoPolygon inPoly1) {
super(cons);
this.inPoly0 = inPoly0;
this.inPoly1 = inPoly1;
this.labels = labels;
silent = cons.isSuppressLabelsActive();
}
/**
* after the constructor with no operation type, this method should be
* called for successful completion of the constructor
*
* after calling this call setInputOutput() for setting input output
* dependencies
*
* @param opType
* the enum type of operation INTERSECTION, UNION, DIFFERENCE,
* XOR
*/
public void initiatePolyOperation(PolyOperation opType) {
this.operationType = opType;
subject = new Path(inPoly0.getPointsLength());
clip = new Path(inPoly1.getPointsLength());
solution = new Paths();
createOutput();
setInputOutput();
compute();
// set labels
if (labels == null) {
outputPolygons.setLabels(null);
outputPoints.setLabels(null);
outputSegments.setLabels(null);
} else {
int labelsLength = labels.length;
if (labelsLength > 1) {
// set default
outputPolygons.setLabels(null);
outputSegments.setLabels(null);
outputPoints.setLabels(null);
} else if (labels.length == 1 && labels[0] != null
&& !labels[0].equals("")) {
outputPolygons.setIndexLabels(labels[0]);
}
}
update();
}
/**
* common constructor with outputsizes
*
* @param cons
* construction
* @param labels
* labels
* @param inPoly0
* input polygon 1
* @param inPoly1
* input polygon 2
* @param operationType
* the enum type of operation INTERSECTION, UNION, DIFFERENCE,XOR
* @param outputSizes
* output size (if initial occurrence null)
*/
public AlgoPolygonOperation(Construction cons, String[] labels,
GeoPolygon inPoly0, GeoPolygon inPoly1, PolyOperation operationType) {
super(cons);
this.operationType = operationType;
this.inPoly0 = inPoly0;
this.inPoly1 = inPoly1;
this.labels = labels;
subject = new Path(inPoly0.getPointsLength());
clip = new Path(inPoly1.getPointsLength());
solution = new Paths();
silent = cons.isSuppressLabelsActive();
createOutput();
}
/**
* @param outputSizes
* output sizes from XML
*/
protected void initialize(int[] outputSizes) {
setInputOutput();
// We do compute() TWICE in the constructor (for some reason)
// for this one we don't have the labels set yet, so do it silently
compute(false);
// set labels
if (labels == null) {
outputPolygons.setLabels(null);
outputPoints.setLabels(null);
outputSegments.setLabels(null);
} else {
int labelsLength = labels.length;
if (labelsLength > 1) {
// Log.debug("\nici :
// "+outputSizes[0]+","+outputSizes[1]+","+outputSizes[2]);
if (outputSizes != null) {
// set output sizes
outputPolygons.adjustOutputSize(outputSizes[0], false);
outputPoints.adjustOutputSize(outputSizes[1], false);
outputSegments.adjustOutputSize(outputSizes[2], false);
// set labels
int i1 = 0;
int i2 = 0;
while (i1 < outputSizes[0]) {
outputPolygons.getElement(i1).setLabel(labels[i2]);
i1++;
i2++;
}
i1 = 0;
while (i1 < outputSizes[1]) {
outputPoints.getElement(i1).setLabel(labels[i2]);
i1++;
i2++;
}
i1 = 0;
while (i1 < outputSizes[2]) {
outputSegments.getElement(i1).setLabel(labels[i2]);
i1++;
i2++;
}
} else {
// set default
outputPolygons.setLabels(null);
outputSegments.setLabels(null);
outputPoints.setLabels(null);
}
} else if (labels.length == 1 && labels[0] != null
&& !labels[0].equals("")) {
outputPolygons.setIndexLabels(labels[0]);
}
}
update();
}
@Override
protected void getCmdOutputXML(StringBuilder sb, StringTemplate tpl) {
sb.append("\t<outputSizes val=\"");
sb.append(outputPolygons.size());
sb.append(",");
sb.append(outputPoints.size());
sb.append(",");
sb.append(outputSegments.size());
sb.append("\"");
sb.append("/>\n");
// common method
super.getCmdOutputXML(sb, tpl);
}
/**
* create outputHandlers for output polygons, points, and segments and
* initiate them
*/
private final void createOutput() {
outputPolygons = new OutputHandler<GeoPolygon>(
new elementFactory<GeoPolygon>() {
@Override
public GeoPolygon newElement() {
GeoPolygon p = new GeoPolygon(cons, true);
p.setParentAlgorithm(AlgoPolygonOperation.this);
if (outputPolygons.size() > 0) {
p.setAllVisualProperties(
outputPolygons.getElement(0), false);
}
p.setViewFlags(inPoly0.getViewSet());
p.setNotFixedPointsLength(true);
return p;
}
});
outputPolygons.adjustOutputSize(1, false);
outputPoints = new OutputHandler<GeoPoint>(
new elementFactory<GeoPoint>() {
@Override
public GeoPoint newElement() {
GeoPoint newPoint = new GeoPoint(cons);
newPoint.setCoords(0, 0, 1);
newPoint.setParentAlgorithm(AlgoPolygonOperation.this);
newPoint.setAuxiliaryObject(true);
newPoint.setViewFlags(inPoly0.getViewSet());
return newPoint;
}
});
outputPoints.adjustOutputSize(1, false);
outputSegments = new OutputHandler<GeoSegment>(
new elementFactory<GeoSegment>() {
@Override
public GeoSegment newElement() {
GeoSegment segment = (GeoSegment) outputPolygons
.getElement(0)
.createSegment(cons, outputPoints.getElement(0),
outputPoints.getElement(0), true);
segment.setAuxiliaryObject(true);
segment.setViewFlags(inPoly0.getViewSet());
return segment;
}
});
}
@Override
protected void setInputOutput() {
input = new GeoElement[2];
input[0] = inPoly0;
input[1] = inPoly1;
// set dependencies
for (int i = 0; i < input.length; i++) {
input[i].addAlgorithm(this);
}
cons.addToAlgorithmList(this);
setDependencies();
}
@Override
public void compute() {
compute(!silent);
}
private void compute(boolean updateLabels) {
// add subject polygon
subject.clear();
for (int i = 0; i < inPoly0.getPointsLength(); i++) {
DoublePoint point = convert(inPoly0.getPoint(i));
subject.add(point);
}
// add clip polygon
clip.clear();
for (int i = 0; i < inPoly1.getPointsLength(); i++) {
DoublePoint point = convert(inPoly1.getPoint(i));
clip.add(point);
}
// initializing clipper
DefaultClipper clipper = new DefaultClipper(Clipper.STRICTLY_SIMPLE);
clipper.addPath(clip, PolyType.CLIP, true);
clipper.addPath(subject, PolyType.SUBJECT, true);
boolean solutionValid = false;
solution.clear();
// calculating output polygons
switch (operationType) {
default:
case INTERSECTION:
solutionValid = clipper.execute(ClipType.INTERSECTION, solution,
PolyFillType.EVEN_ODD, PolyFillType.EVEN_ODD);
break;
case UNION:
solutionValid = clipper.execute(ClipType.UNION, solution,
PolyFillType.EVEN_ODD, PolyFillType.EVEN_ODD);
break;
case DIFFERENCE:
solutionValid = clipper.execute(ClipType.DIFFERENCE, solution,
PolyFillType.EVEN_ODD, PolyFillType.EVEN_ODD);
break;
case XOR:
solutionValid = clipper.execute(ClipType.XOR, solution,
PolyFillType.EVEN_ODD, PolyFillType.EVEN_ODD);
break;
}
// assign output calculated using clipper library to appropriately
if (!solutionValid) { // if there is no output
outputPolygons.adjustOutputSize(1, false);
outputPoints.adjustOutputSize(1, false);
outputSegments.adjustOutputSize(1, false);
} else {
// adjust output sizes
outputPolygons.adjustOutputSize(solution.size(), false);
int pointCount = 0;
for (Path path : solution) {
pointCount += path.size();
}
outputPoints.adjustOutputSize(pointCount, false);
outputSegments.adjustOutputSize(pointCount, false);
// assigning coords to output points
pointCount = 0;
for (Path path : solution) {
for (int i = 0; i < path.size(); i++) {
GeoPoint point = outputPoints.getElement(pointCount);
DoublePoint calcPoint = path.get(i);
point.setCoords(calcPoint.getX(), calcPoint.getY(), 1);
pointCount++;
}
}
if (updateLabels) {
outputPoints.updateLabels();
}
GeoPoint[] points = new GeoPoint[pointCount];
points = outputPoints.getOutput(points);
int pointIndex = 0;
int polygonIndex = 0;
int segmentIndex = 0;
for (Path path : solution) {
GeoPolygon polygon = outputPolygons.getElement(polygonIndex);
polygonIndex++;
GeoPoint[] polyPoints = new GeoPoint[path.size()];
GeoSegment[] polySegments = new GeoSegment[path.size()];
for (int i = 0; i < path.size(); i++) {
GeoSegment segment = outputSegments
.getElement(segmentIndex);
GeoPoint A = points[pointIndex + i];
GeoPoint B = points[pointIndex + (i + 1) % path.size()];
segment.setStartPoint(A);
segment.setEndPoint(B);
((AlgoJoinPointsSegmentInterface) segment
.getParentAlgorithm()).modifyInputPoints(A, B);
segment.update();
segment.calcLength();
polyPoints[i] = points[pointIndex + i];
polySegments[i] = segment;
segmentIndex++;
}
pointIndex += path.size();
// assign points to poly without creating segments
polygon.setPoints(polyPoints, null, false);
polygon.setSegments(polySegments);
polygon.calcArea();
}
if (updateLabels) {
outputSegments.updateLabels();
outputPolygons.updateLabels();
}
}
for (int i = 0; i < outputPolygons.size(); i++) {
outputPolygons.getElement(i).updateRegionCS();
}
}
private static DoublePoint convert(GeoPoint point) {
return new DoublePoint(point.getX() / point.getZ(),
point.getY() / point.getZ());
}
}