package org.geogebra.common.geogebra3D.kernel3D.algos;
import java.util.Collection;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPoint3D;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPolygon3D;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPolyhedron;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPolyhedronNet;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoSegment3D;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.algos.GetCommand;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.commands.Commands;
import org.geogebra.common.kernel.geos.ChangeableCoordParent;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.kernel.geos.GeoPolygon;
import org.geogebra.common.kernel.kernelND.GeoSegmentND;
/**
* Algo that compute the net for a polyhedron
*
* @author Vincent
*
*/
public abstract class AlgoPolyhedronNet extends AlgoElement3D {
protected GeoPolyhedron p;
protected NumberValue v;
private GeoNumeric vNum = null;
protected OutputHandler<GeoPolyhedronNet> outputNet;
protected int bottomPointsLength;
/** points generated as output */
protected OutputHandler<GeoPoint3D> outputPointsBottom, outputPointsSide,
outputPointsTop;
protected OutputHandler<GeoSegment3D> outputSegmentsBottom,
outputSegmentsSide, outputSegmentsTop;
protected OutputHandler<GeoPolygon3D> outputPolygonsBottom,
outputPolygonsSide, outputPolygonsTop;
/**
* @param c
* construction
*/
public AlgoPolyhedronNet(Construction c, String[] labels, GeoPolyhedron p,
NumberValue v) {
super(c);
this.p = p;
this.v = v;
vNum = ChangeableCoordParent.getGeoNumeric(v);
outputNet = new OutputHandler<GeoPolyhedronNet>(
new elementFactory<GeoPolyhedronNet>() {
@Override
public GeoPolyhedronNet newElement() {
GeoPolyhedronNet p1 = new GeoPolyhedronNet(cons);
p1.setParentAlgorithm(AlgoPolyhedronNet.this);
return p1;
}
});
outputNet.adjustOutputSize(1);
outputPointsBottom = createOutputPoints();
outputPointsSide = createOutputPoints();
outputPointsTop = createOutputPoints();
outputPolygonsBottom = createOutputPolygons();
outputPolygonsSide = createOutputPolygons();
outputPolygonsTop = createOutputPolygons();
outputSegmentsBottom = createOutputSegments();
outputSegmentsSide = createOutputSegments();
outputSegmentsTop = createOutputSegments();
bottomPointsLength = p.getBottomFace().getPointsLength();
createNet(bottomPointsLength);
input = new GeoElement[] { p, (GeoElement) v };
for (int i = 0; i < input.length; i++) {
input[i].addAlgorithm(this);
}
getNet().createFaces();
setOutput(bottomPointsLength);
// set labels
setLabels(labels);
update();
updateOutputSegmentsAndPolygonsParentAlgorithms();
}
private void setLabels(String[] labels) {
if (labels == null || labels.length <= 1) {
getNet().initLabels(labels);
} else {
getNet().setAllLabelsAreSet(true);
int n = getPointLengthFromLabelsLength(labels.length);
if (n > bottomPointsLength) {
adjustOutputSize(n, false); // augment output for labels
}
for (int i = 0; i < labels.length; i++) {
getOutput(i).setLabel(labels[i]);
}
}
}
protected abstract int getPointLengthFromLabelsLength(int length);
protected abstract void createNet(int n);
protected abstract void setOutputSideTop(int n, GeoPolygon3D polygon,
int step, GeoSegmentND[] segments);
private void setOutput(int n) {
GeoPolyhedronNet net = getNet();
Collection<GeoPolygon3D> faces = net.getFacesCollection();
int step = 1;
for (GeoPolygon polygon : faces) {
GeoSegmentND[] segments = polygon.getSegments();
if (step == 1) { // bottom
outputPolygonsBottom.addOutput((GeoPolygon3D) polygon, false);
for (int i = 0; i < segments.length; i++) {
outputSegmentsBottom.addOutput((GeoSegment3D) segments[i],
false);
}
} else {// sides and top
setOutputSideTop(n, (GeoPolygon3D) polygon, step, segments);
}
step++;
}
refreshOutput();
}
private OutputHandler<GeoPoint3D> createOutputPoints() {
return new OutputHandler<GeoPoint3D>(new elementFactory<GeoPoint3D>() {
@Override
public GeoPoint3D newElement() {
GeoPoint3D p1 = new GeoPoint3D(cons);
p1.setCoords(0, 0, 0, 1);
p1.setParentAlgorithm(AlgoPolyhedronNet.this);
getNet().addPointCreated(p1);
p1.setLabelVisible(false);
return p1;
}
});
}
private OutputHandler<GeoSegment3D> createOutputSegments() {
return new OutputHandler<GeoSegment3D>(
new elementFactory<GeoSegment3D>() {
@Override
public GeoSegment3D newElement() {
GeoSegment3D s = new GeoSegment3D(cons);
// s.setParentAlgorithm(AlgoPolyhedron.this);
return s;
}
});
}
private OutputHandler<GeoPolygon3D> createOutputPolygons() {
return new OutputHandler<GeoPolygon3D>(
new elementFactory<GeoPolygon3D>() {
@Override
public GeoPolygon3D newElement() {
GeoPolygon3D p = new GeoPolygon3D(cons);
// p.setParentAlgorithm(AlgoPolyhedron.this);
setChangeableCoordParent(p);
return p;
}
}) {
@Override
public void addOutput(GeoPolygon3D polygon,
boolean setDependencies) {
setChangeableCoordParent(polygon);
super.addOutput(polygon, setDependencies);
}
};
}
final void setChangeableCoordParent(GeoPolygon3D polygon) {
ChangeableCoordParent.setPolyhedronNet(polygon, vNum, p);
}
/**
* @param point
* point to rotate
* @param pointCoords
* coordinates of this point
* @param projectCoords
* coordinates of the projected point on bottom face
* @param o
* coordinates of the origin of the rotation line
* @param vs
* direction of the rotation line
* @param f
* value of the cursor used in the rotation
* @param fd
* direction of the bottom face
* @param dist
* distance between point and projected point
* @param test
* value (XOR)
*/
protected void rotate(GeoPoint3D point, Coords pointCoords,
Coords projectCoords, Coords o, Coords vs, double f, Coords fd,
double dist, boolean test) {
Coords v2 = projectCoords.sub(o);
double d2 = pointCoords.distLine(o, vs);
double angle;
if (Kernel.isEqual(dist, d2)) {
angle = Math.PI / 2;
} else {
angle = Math.asin(dist / d2);
}
if (test ^ (v2.crossProduct(vs).dotproduct(fd) < 0)) { // top point is
// inside bottom
// face
angle = Math.PI - angle;
}
point.rotate(f * angle, o, vs);
}
/**
* compute with f value for opening, and bottom points
*
* @param f
* value for opening
* @param bottomPolygon
* bottom face of the pyramid/prism
* @param points
* bottom points
*/
protected abstract void compute(double f, GeoPolygon bottomPolygon,
Coords[] points);
/**
* adjust output for n bottom points
*
* @param n
* new bottom points length
*/
protected void adjustOutputSize(int n) {
if (n > outputPointsBottom.size()) { // augment output points bottom
outputPointsBottom.adjustOutputSize(n, false);
}
}
/**
* adjust output for n bottom points
*
* @param n
* new bottom points length
* @param setLabels
* says if we want to set labels here (must be false on net
* creation)
*/
abstract protected void adjustOutputSize(int n, boolean setLabels);
/**
* update bottom face for new length
*
* @param newBottomPointsLength
* new bottom points length
*/
protected void updateBottom(int newBottomPointsLength) {
GeoPolygon polygon = outputPolygonsBottom.getElement(0);
GeoPoint3D[] points = new GeoPoint3D[newBottomPointsLength];
GeoSegment3D[] segments = new GeoSegment3D[newBottomPointsLength];
for (int i = 0; i < newBottomPointsLength; i++) {
points[i] = outputPointsBottom.getElement(i);
segments[i] = outputSegmentsBottom.getElement(i);
}
polygon.modifyInputPoints(points);
polygon.setSegments(segments);
polygon.calcArea();
}
@Override
public void compute() {
if (!p.isDefined()) {
setUndefined();
return;
}
double f = v.getDouble();
if (Kernel.isGreater(f, 1) || Kernel.isGreater(0, f)) {
setUndefined();
return;
}
// update bottom points
GeoPolygon bottomFace = p.getBottomFace();
if (bottomFace.isConvex()) {
getNet().setDefined();
Coords[] points = getPointsCoords(bottomFace);
adjustOutputSize(points.length);
outputPointsBottom.setLabels(null);
for (int i = 0; i < points.length; i++) {
outputPointsBottom.getElement(i).setCoords(points[i]);
}
compute(f, bottomFace, points);
} else {
setUndefined();
}
}
private void setUndefined() {
getNet().setUndefined();
outputPointsBottom.setUndefined();
outputPointsSide.setUndefined();
outputPointsTop.setUndefined();
}
/**
*
* @param polygon
* polygon
* @return 3D coords of all points
*/
protected static final Coords[] getPointsCoords(GeoPolygon polygon) {
int l = polygon.getPointsLength();
Coords[] points = new Coords[l];
for (int i = 0; i < l; i++) {
points[i] = polygon.getPoint3D(i);
}
return points;
}
@Override
public GetCommand getClassName() {
return Commands.Net;
}
/**
* @return the polyhedron
*/
public GeoPolyhedronNet getNet() {
return outputNet.getElement(0);
}
/**
* force update for segments and polygons at creation
*/
private void updateOutputSegmentsAndPolygonsParentAlgorithms() {
outputSegmentsBottom.updateParentAlgorithm();
outputSegmentsSide.updateParentAlgorithm();
outputPolygonsBottom.updateParentAlgorithm();
outputPolygonsSide.updateParentAlgorithm();
if (p.getType() == GeoPolyhedron.TYPE_PRISM) {
outputSegmentsTop.updateParentAlgorithm();
outputPolygonsTop.updateParentAlgorithm();
}
}
@Override
public int getRelatedModeID() {
return EuclidianConstants.MODE_NET;
}
}