package org.geogebra.common.geogebra3D.kernel3D.algos;
import java.util.ArrayList;
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;
public class AlgoPolyhedronNetConvex extends AlgoElement3D {
protected GeoPolyhedron p;
protected NumberValue v;
private GeoPolygon bottomFace;
protected int iBottom; // number of the polygon used as bottom
private GeoSegmentND[] userCutSegments;
protected OutputHandler<GeoPolyhedronNet> outputNet;
/** points generated as output */
protected OutputHandler<GeoPoint3D> outputPointsNet;
int pointsCounter = 0; // counter of the current number of points created in
// the net
protected OutputHandler<GeoSegment3D> outputSegments;
protected OutputHandler<GeoPolygon3D> outputPolygons;
static class SegmentInfo {
int segmentParent1;
int segmentParent2;
int pointIndex1 = -1;
int pointIndex2 = -1;
boolean userCut = false;
}
static class PolygonInfoElement {
int linkSegNumber;
int rank;
int segShift;
ArrayList<Integer> pointIndex = new ArrayList<Integer>();
}
private ArrayList<ArrayList<Integer>> netMap = new ArrayList<ArrayList<Integer>>();
private ArrayList<PolygonInfoElement> polygonInfo = new ArrayList<PolygonInfoElement>();
private ArrayList<ArrayList<Integer>> polygonChildSegsList = new ArrayList<ArrayList<Integer>>();
protected ArrayList<GeoSegmentND> segmentList = new ArrayList<GeoSegmentND>();
protected ArrayList<SegmentInfo> segmentInfoList = new ArrayList<SegmentInfo>();
private boolean netComplete = true;
/**
* @param c
* construction
*/
public AlgoPolyhedronNetConvex(Construction c, String[] labels,
GeoPolyhedron p, NumberValue v, GeoPolygon bottomFace,
GeoSegmentND[] pivotSegments) {
super(c);
projCoord = new Coords(4);
this.p = p;
this.v = v;
this.bottomFace = bottomFace;
this.userCutSegments = pivotSegments;
// set input
int pivotSegmentsLength = 0;
if (pivotSegments != null) {
pivotSegmentsLength = pivotSegments.length;
}
if (bottomFace == null) {
input = new GeoElement[2 + pivotSegmentsLength];
input[0] = p;
input[1] = (GeoElement) v;
for (int i = 0; i < pivotSegmentsLength; i++) {
input[2 + i] = (GeoElement) pivotSegments[i];
}
} else {
input = new GeoElement[3 + pivotSegmentsLength];
input[0] = p;
input[1] = (GeoElement) v;
input[2] = bottomFace;
for (int i = 0; i < pivotSegmentsLength; i++) {
input[3 + i] = (GeoElement) pivotSegments[i];
}
}
for (int i = 0; i < input.length; i++) {
input[i].addAlgorithm(this);
}
outputNet = new OutputHandler<GeoPolyhedronNet>(
new elementFactory<GeoPolyhedronNet>() {
@Override
public GeoPolyhedronNet newElement() {
GeoPolyhedronNet p1 = new GeoPolyhedronNet(cons);
p1.setParentAlgorithm(AlgoPolyhedronNetConvex.this);
return p1;
}
});
outputNet.adjustOutputSize(1);
outputPointsNet = createOutputPoints();
outputPolygons = createOutputPolygons();
outputSegments = createOutputSegments();
netFacesCompute();
if (iBottom != -1) {
if (netComplete) {
GeoPolyhedronNet net = getNet();
GeoNumeric num = ChangeableCoordParent.getGeoNumeric(v);
Collection<GeoPolygon3D> faces = net.getFacesCollection();
for (GeoPolygon3D polygon : faces) {
ChangeableCoordParent.setPolyhedronNet(polygon, num, p);
outputPolygons.addOutput(polygon, false);
}
for (GeoSegment3D segment : net.getSegments3D()) {
outputSegments.addOutput(segment, false);
}
}
refreshOutput();
// set labels
setLabels(labels);
update();
updateOutputSegmentsAndPolygonsParentAlgorithms();
}
}
private void netFacesCompute() {
setSegmentsToFacesLink(p);
if (iBottom != -1) {
netComplete = makeNetMap(p);
if (netComplete) {
getNet().clearIndexes();
createNet();
// create faces
getNet().createFaces();
}
}
}
/**
*
* @param p
* : polyhedron
*/
private void setSegmentsToFacesLink(GeoPolyhedron p) {
segmentInfoList.clear();
segmentList.clear();
polygonChildSegsList.clear();
// set iBottom as first face if bottomFace null
if (bottomFace != null) {
iBottom = -1; // correct value set below
} else {
iBottom = 0;
}
GeoPolygon[] polygonList = p.getFaces();
for (int iP = 0; iP < polygonList.length; iP++) {
ArrayList<Integer> segsList = new ArrayList<Integer>();
GeoPolygon thisPolygon = polygonList[iP];
if (iBottom < 0) { // user selected bottom face not yet found
if (bottomFace.isEqual(thisPolygon)) {
iBottom = iP;
}
}
for (GeoSegmentND thisSegment : thisPolygon.getSegments()) {
// search for thisSegment in the segment list
boolean found = false;
for (int i = 0; i < segmentList.size(); i++) {
if (segmentList.get(i).isEqual(thisSegment)) {
found = true;
// add the second polygon parent to thisSegment
segmentInfoList.get(i).segmentParent2 = iP;
segsList.add(i);
break;
}
}
if (!found) {
// add thisSegment to the segmentList
segmentList.add(thisSegment);
// add iP as the first polygon parent to thisSegment
SegmentInfo newSegParent = new SegmentInfo();
newSegParent.segmentParent1 = iP;
// search if thisSegment is selected by the user as a cut
if (userCutSegments != null) {
for (GeoSegmentND pSeg : userCutSegments) {
if (pSeg.isEqual(thisSegment)) {
newSegParent.userCut = true;
// Log.debug("===== Cut found -----
// "+thisSegment);
}
}
}
segmentInfoList.add(newSegParent);
segsList.add(segmentInfoList.size() - 1);
}
}
polygonChildSegsList.add(segsList);
}
}
/**
*
* @param p1
* : polyhedron
*/
private boolean makeNetMap(GeoPolyhedron p1) {
netMap.clear();
polygonInfo.clear();
// create the netmap of the polyhedron
// each polygon is referred to its father number (-1 if it has no
// father) and then its sons if it has any
GeoPolygon[] polygonList = p1.getFaces();
for (int iP = 0; iP < polygonList.length; iP++) {
ArrayList<Integer> linkedPolygonList = new ArrayList<Integer>();
netMap.add(linkedPolygonList);
PolygonInfoElement infoElt = new PolygonInfoElement();
polygonInfo.add(infoElt);
}
netMap.get(iBottom).add(-1); // this one has no parent
polygonInfo.get(iBottom).rank = 0; // rank is 0
polygonInfo.get(iBottom).linkSegNumber = -1; // no segment to rotate
// around
boolean newFaceFound = true; // boolean to test of impossibility to join
// all the faces (with a bad user cut)
int nbFoundedFaces = 1;
int maxRank = 0;
while ((nbFoundedFaces < polygonList.length) && (newFaceFound)) {
maxRank++;
newFaceFound = false;
for (int iP = 0; iP < polygonList.length; iP++) {
if ((netMap.get(iP).size() == 1)
&& (polygonInfo.get(iP).rank) < maxRank) { // if this
// polygon
// has been
// found but
// is not
// yet
// connected
// to a son
for (int iSeg : polygonChildSegsList.get(iP)) {
// only if this segment is not cut by the user
if (!segmentInfoList.get(iSeg).userCut) {
// select the child polygon (parent1 or 2 of the
// seg)
int iChildPoly = 0;
if (segmentInfoList
.get(iSeg).segmentParent1 == iP) {
iChildPoly = segmentInfoList
.get(iSeg).segmentParent2;
} else {
iChildPoly = segmentInfoList
.get(iSeg).segmentParent1;
}
if (netMap.get(iChildPoly).size() == 0) { // if this
// poly
// is
// not
// yet
// connected
// to
// the
// net
// set its father as iP
netMap.get(iChildPoly).add(iP);
polygonInfo.get(iChildPoly).rank = maxRank;
polygonInfo
.get(iChildPoly).linkSegNumber = iSeg;
nbFoundedFaces++;
newFaceFound = true;
// set it as a new iP child
netMap.get(iP).add(iChildPoly);
}
}
}
}
}
}
return newFaceFound;
}
@Override
public void compute() {
if (!p.isDefined()) {
setUndefined();
return;
}
netFacesCompute();
if (iBottom == -1) {
setUndefined();
return;
}
if (!netComplete) {
setUndefined();
return;
}
double f = v.getDouble();
if (Kernel.isGreater(f, 1) || Kernel.isGreater(0, f)) {
setUndefined();
return;
}
getNet().setDefined();
// update net points
for (int iPoly = 0; iPoly < polygonInfo.size(); iPoly++) {
GeoPolygon currentFace = p.getFace(iPoly);
Coords[] points = getPointsCoords(currentFace);
int iBegin = 2;
if (iPoly == iBottom) {
iBegin = 0;
}
for (int i = iBegin; i < points.length; i++) {
outputPointsNet
.getElement(polygonInfo.get(iPoly).pointIndex.get(i))
.setCoords(points[(i + polygonInfo.get(iPoly).segShift)
% (points.length)]);
}
}
// update area
getNet().setArea(p.getArea());
// rotate faces by recursive call
rotateFace(iBottom, f);
}
private Coords projCoord;
private ArrayList<Integer> rotateFace(int iFace, double fUnsigned) {
double f = fUnsigned;
ArrayList<Integer> pointsToRotate = new ArrayList<Integer>();
// recursive call
for (int i = 1; i < netMap.get(iFace).size(); i++) {
pointsToRotate.addAll(rotateFace(netMap.get(iFace).get(i), f));
}
if (iFace != iBottom) {
// add points index to the list
for (int index = 2; index < polygonInfo.get(iFace).pointIndex
.size(); index++) {
pointsToRotate
.add(polygonInfo.get(iFace).pointIndex.get(index));
}
// face
GeoPolygon face = p.getFace(netMap.get(iFace).get(0));
// rotation angle
GeoPoint3D facePoint = outputPointsNet
.getElement(polygonInfo.get(iFace).pointIndex.get(2));
Coords cCoord = facePoint.getInhomCoordsInD3();
cCoord.projectPlane(face.getCoordSys().getMatrixOrthonormal(),
projCoord);
double dist = projCoord.distance(cCoord);
Coords o = (outputPointsNet
.getElement(polygonInfo.get(iFace).pointIndex.get(1)))
.getInhomCoordsInD3();
Coords o1 = segmentList.get(polygonInfo.get(iFace).linkSegNumber)
.getStartPoint().getInhomCoordsInD3();
Coords vs = segmentList.get(polygonInfo.get(iFace).linkSegNumber)
.getDirectionInD3();
int sgn = 1;
if (Kernel.isGreater(o1.distance(o), 0)) {
sgn = -1;
}
Coords faceDirection = face.getDirectionInD3();
if (face.isConvexInverseDirection()) {
f *= -1;
sgn *= -1;
}
Coords v2 = projCoord.sub(o);
double d2 = cCoord.distLine(o, vs);
double angle;
if (Kernel.isEqual(dist, d2)) {
angle = Math.PI / 2;
} else {
angle = Math.asin(dist / d2);
}
if (sgn * v2.crossProduct(vs).dotproduct(faceDirection) < 0) { // top
// point
// is
// inside
// bottom
// face
angle = Math.PI - angle;
}
// rotate the points of the list
for (int iPoint = 0; iPoint < pointsToRotate.size(); iPoint++) {
facePoint = outputPointsNet
.getElement(pointsToRotate.get(iPoint));
facePoint.rotate(f * sgn * angle, o, vs);
}
}
return pointsToRotate;
}
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(AlgoPolyhedronNetConvex.this);
getNet().addPointCreated(p1);
p1.setLabelVisible(false);
return p1;
}
});
}
/**
* @return the polyhedron
*/
public GeoPolyhedronNet getNet() {
return outputNet.getElement(0);
}
@Override
public GetCommand getClassName() {
return Commands.Net;
}
protected void createNet() {
GeoPolyhedronNet net = getNet();
// Number of points needed in the net
int iNetPoints = 0;
for (int i = 0; i < p.getFacesSize(); i++) {
iNetPoints = iNetPoints + p.getFace(i).getPointsLength();
if (i != iBottom) {
iNetPoints -= 2;
}
}
outputPointsNet.adjustOutputSize(iNetPoints, false);
outputPointsNet.setLabels(null);
// create bottom face and recursive call for child faces
pointsCounter = 0;
createFace(iBottom);
// create faces
for (int pNum = 0; pNum < polygonInfo.size(); pNum++) {
net.startNewFace();
for (int i : polygonInfo.get(pNum).pointIndex) {
net.addPointToCurrentFace(outputPointsNet.getElement(i));
}
net.endCurrentFace();
}
// Log.debug("Points list for each polygon");
// for (int pNum=0;pNum<polygonInfo.size();pNum++){
// Log.debug(pNum+": "+polygonInfo.get(pNum).pointIndex);
// }
}
private void createFace(int faceNumber) {
int linkSegNumber = polygonInfo.get(faceNumber).linkSegNumber;
ArrayList<Integer> currentPolygonSegList = polygonChildSegsList
.get(faceNumber);
if (linkSegNumber != -1) {
int linkSegIndex;
// -1 until the link segment is found
for (linkSegIndex = 0; (currentPolygonSegList
.get(linkSegIndex) != linkSegNumber); linkSegIndex++) {
if (currentPolygonSegList.get(linkSegIndex) != linkSegNumber) {
segmentInfoList.get(currentPolygonSegList
.get(linkSegIndex)).pointIndex1 = -1;
segmentInfoList.get(currentPolygonSegList
.get(linkSegIndex)).pointIndex2 = -1;
}
}
polygonInfo.get(faceNumber).segShift = linkSegIndex;
// link segment found //warning: the seg is seen in the reverse
// order of the parent polygon
if (linkSegIndex == 0) {// seg is the first of the list
segmentInfoList.get(currentPolygonSegList
.get(1)).pointIndex1 = segmentInfoList
.get(currentPolygonSegList.get(0)).pointIndex1;
segmentInfoList.get(
currentPolygonSegList.get(currentPolygonSegList.size()
- 1)).pointIndex2 = segmentInfoList
.get(currentPolygonSegList
.get(0)).pointIndex2;
} else {
segmentInfoList.get(currentPolygonSegList
.get((linkSegIndex + 1) % currentPolygonSegList
.size())).pointIndex1 = segmentInfoList
.get(currentPolygonSegList
.get(linkSegIndex)).pointIndex1;
segmentInfoList.get(currentPolygonSegList
.get(linkSegIndex - 1)).pointIndex2 = segmentInfoList
.get(currentPolygonSegList
.get(linkSegIndex)).pointIndex2;
}
// reverse the linkseg
int temp = segmentInfoList
.get(currentPolygonSegList.get(linkSegIndex)).pointIndex1;
segmentInfoList.get(currentPolygonSegList
.get(linkSegIndex)).pointIndex1 = segmentInfoList
.get(currentPolygonSegList
.get(linkSegIndex)).pointIndex2;
segmentInfoList.get(
currentPolygonSegList.get(linkSegIndex)).pointIndex2 = temp;
// -1 until the end of the list
for (int linkSegIndex2 = linkSegIndex
+ 1; linkSegIndex2 < currentPolygonSegList
.size(); linkSegIndex2++) {
if (linkSegIndex2 > linkSegIndex + 1) {
segmentInfoList.get(currentPolygonSegList
.get(linkSegIndex2)).pointIndex1 = -1;
}
if ((linkSegIndex2 < currentPolygonSegList.size() - 1)
|| (linkSegIndex != 0)) {
segmentInfoList.get(currentPolygonSegList
.get(linkSegIndex2)).pointIndex2 = -1;
}
}
// second turn -> create needed points
for (int segNumber = 0; segNumber < currentPolygonSegList
.size(); segNumber++) {
if (segmentInfoList.get(currentPolygonSegList
.get(segNumber)).pointIndex1 == -1) {
segmentInfoList.get(currentPolygonSegList
.get(segNumber)).pointIndex1 = pointsCounter;
// notice it is the second point of the precedent segment
if (segNumber > 0) {
segmentInfoList.get(currentPolygonSegList.get(
(segNumber - 1))).pointIndex2 = pointsCounter;
} else {
segmentInfoList.get(currentPolygonSegList
.get((currentPolygonSegList.size()
- 1))).pointIndex2 = pointsCounter;
}
pointsCounter++;
}
if (segmentInfoList.get(currentPolygonSegList
.get(segNumber)).pointIndex2 == -1) {
segmentInfoList.get(currentPolygonSegList
.get(segNumber)).pointIndex2 = pointsCounter;
// notice it is the first point of the next segment
segmentInfoList.get(currentPolygonSegList
.get((segNumber + 1) % (currentPolygonSegList
.size()))).pointIndex1 = pointsCounter;
pointsCounter++;
}
}
// create the pointIndex list for this face
for (int segNumber = polygonInfo
.get(faceNumber).segShift; segNumber < polygonInfo
.get(faceNumber).segShift
+ currentPolygonSegList.size(); segNumber++) {
polygonInfo.get(faceNumber).pointIndex.add(segmentInfoList
.get(currentPolygonSegList.get((segNumber)
% (currentPolygonSegList.size()))).pointIndex1);
}
} else { // bottom face
int segNumber;
for (segNumber = 0; segNumber < currentPolygonSegList
.size(); segNumber++) {
// create the second point of the segment
segmentInfoList.get(currentPolygonSegList
.get(segNumber)).pointIndex2 = pointsCounter;
// notice it is the second point of the precedent segment
segmentInfoList.get(currentPolygonSegList
.get((segNumber + 1) % (currentPolygonSegList
.size()))).pointIndex1 = pointsCounter;
pointsCounter++;
}
// create the pointIndex list for this face
for (segNumber = polygonInfo
.get(faceNumber).segShift; segNumber < polygonInfo
.get(faceNumber).segShift
+ currentPolygonSegList.size(); segNumber++) {
polygonInfo.get(faceNumber).pointIndex.add(segmentInfoList
.get(currentPolygonSegList.get((segNumber)
% (currentPolygonSegList.size()))).pointIndex1);
}
}
// recursive call
for (int childPolygonIndex = 1; childPolygonIndex < netMap
.get(faceNumber).size(); childPolygonIndex++) {
createFace(netMap.get(faceNumber).get(childPolygonIndex));
}
}
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 p1 = new GeoPolygon3D(cons);
// p.setParentAlgorithm(AlgoPolyhedron.this);
return p1;
}
});
}
private void setLabels(String[] labels) {
if (labels == null || labels.length <= 1) {
getNet().initLabels(labels);
} else {
getNet().setAllLabelsAreSet(true);
for (int i = 0; i < labels.length; i++) {
getOutput(i).setLabel(labels[i]);
}
}
}
/**
* force update for segments and polygons at creation
*/
private void updateOutputSegmentsAndPolygonsParentAlgorithms() {
outputSegments.updateParentAlgorithm();
outputPolygons.updateParentAlgorithm();
}
/**
*
* @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;
}
private void setUndefined() {
getNet().setUndefined();
outputPointsNet.setUndefined();
}
@Override
public int getRelatedModeID() {
return EuclidianConstants.MODE_NET;
}
}