package org.geogebra.common.geogebra3D.kernel3D.algos; 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.GeoSegment3D; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.algos.AlgoPolygonRegularND; import org.geogebra.common.kernel.arithmetic.NumberValue; 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.GeoPointND; /** * @author ggb3D * * Creates a new GeoPolyhedron * */ public abstract class AlgoPolyhedronPoints extends AlgoPolyhedron { private GeoPointND[] bottomPoints; protected GeoPointND[] points; private GeoPointND topPoint; protected GeoPolygon bottom; protected NumberValue height; protected boolean bottomAsInput = false; protected int bottomPointsLength = -1; protected OutputHandler<GeoSegment3D> outputSegmentsBottom, outputSegmentsSide, outputSegmentsTop; protected OutputHandler<GeoPolygon3D> outputPolygonsBottom, outputPolygonsSide, outputPolygonsTop; private class OutputPolygonsHandler extends OutputHandler<GeoPolygon3D> { public OutputPolygonsHandler() { super(new elementFactory<GeoPolygon3D>() { @Override public GeoPolygon3D newElement() { GeoPolygon3D p = new GeoPolygon3D(cons); // p.setParentAlgorithm(AlgoPolyhedron.this); if (heightChangeableCoordParent != null) { p.setChangeableCoordParent(heightChangeableCoordParent); } return p; } }); } @Override public void addOutput(GeoPolygon3D geo, boolean setDependencies) { if (heightChangeableCoordParent != null) { geo.setChangeableCoordParent(heightChangeableCoordParent); } super.addOutput(geo, setDependencies); } } @Override protected OutputHandler<GeoPolygon3D> createOutputPolygonsHandler() { return new OutputPolygonsHandler(); } private class OutputSegmentsHandler extends OutputHandler<GeoSegment3D> { public OutputSegmentsHandler() { super(new elementFactory<GeoSegment3D>() { @Override public GeoSegment3D newElement() { GeoSegment3D s = new GeoSegment3D(cons); if (heightChangeableCoordParent != null) { s.setChangeableCoordParentIfNull( heightChangeableCoordParent); } return s; } }); } @Override public void addOutput(GeoSegment3D geo, boolean setDependencies) { if (heightChangeableCoordParent != null) { geo.setChangeableCoordParentIfNull(heightChangeableCoordParent); } super.addOutput(geo, setDependencies); } } @Override protected OutputHandler<GeoSegment3D> createOutputSegmentsHandler() { return new OutputSegmentsHandler(); } private class OutputPointsHandler extends OutputHandler<GeoPoint3D> { public OutputPointsHandler() { super(new PointFactory() { @Override public GeoPoint3D newElement() { GeoPoint3D ret = super.newElement(); if (heightChangeableCoordParent != null) { ret.setChangeableCoordParentIfNull( heightChangeableCoordParent); } return ret; } }); } @Override public void addOutput(GeoPoint3D geo, boolean setDependencies) { if (heightChangeableCoordParent != null) { geo.setChangeableCoordParentIfNull(heightChangeableCoordParent); } super.addOutput(geo, setDependencies); } } @Override protected OutputHandler<GeoPoint3D> createOutputPointsHandler() { return new OutputPointsHandler(); } // /////////////////////////////////////////// // POLYHEDRON OF DETERMINED TYPE // ////////////////////////////////////////// /** * creates a polyhedron regarding vertices * * @param c * construction * @param labels * @param points */ public AlgoPolyhedronPoints(Construction c, String[] labels, GeoPointND[] points) { super(c); initCoords(); bottomPoints = new GeoPointND[points.length - 1]; for (int i = 0; i < points.length - 1; i++) { bottomPoints[i] = points[i]; } setTopPoint(points[points.length - 1]); shift = 1; // output points are shifted of 1 to input points (one less) createPolyhedron(); // input : inputPoints or list of faces input = new GeoElement[points.length]; for (int i = 0; i < points.length; i++) { input[i] = (GeoElement) points[i]; } addAlgoToInput(); updateOutputPoints(); createFaces(); setOutput(); // compute(); setLabels(labels); update(); // force update segments and polygons updateOutputSegmentsAndPolygonsParentAlgorithms(); } /** * creates a polyhedron regarding bottom face and top vertex * * @param c * construction * @param labels * @param polygon * @param point */ public AlgoPolyhedronPoints(Construction c, String[] labels, GeoPolygon polygon, GeoPointND point) { super(c); initCoords(); bottom = polygon; bottomAsInput = true; setTopPoint(point); shift = 1; // output points are shifted of 1 to input points (one less) createPolyhedron(); // input : inputPoints or list of faces input = new GeoElement[2]; input[0] = bottom; input[1] = (GeoElement) topPoint; addAlgoToInput(); updateOutputPoints(); createFaces(); setOutput(); setLabels(labels); update(); // force update segments and polygons updateOutputSegmentsAndPolygonsParentAlgorithms(); } @Override protected void createOutputSegments() { outputSegmentsBottom = createOutputSegmentsHandler(); outputSegmentsSide = createOutputSegmentsHandler(); outputSegmentsTop = createOutputSegmentsHandler(); } @Override protected void createOutputPolygons() { outputPolygonsBottom = createOutputPolygonsHandler(); outputPolygonsSide = createOutputPolygonsHandler(); outputPolygonsTop = createOutputPolygonsHandler(); } /** * * @param labels * labels */ protected void setLabels(String[] labels) { if (labels == null || labels.length <= 1) { polyhedron.initLabels(labels); } else { augmentOutputSize(labels.length); for (int i = 0; i < labels.length; i++) { getOutput(i).setLabel(labels[i]); } polyhedron.setAllLabelsAreSet(true); } } /** * augment the output size if needed (in case of undefined but labelled * outputs) * * @param length * labels length */ protected void augmentOutputSize(int length) { int n = getSideLengthFromLabelsLength(length); // Application.debug("n="+n+",length="+length); if (n > outputSegmentsSide.size()) { if (getBottom() .getParentAlgorithm() instanceof AlgoPolygonRegularND) { AlgoPolygonRegularND algo = (AlgoPolygonRegularND) getBottom() .getParentAlgorithm(); // if no sufficient bottom points, force augment outputs for // AlgoPolygonRegular int nOld = algo.getCurrentPointsLength(); if (nOld < n) { algo.compute(n); updateOutput(n); algo.compute(nOld); } else { updateOutput(n); } } else { // bottom polygon is a set() polygon, so force augment its // points and segments length GeoPolygon polygon = getBottom(); int nOld = polygon.getPointsLength(); if (nOld < n) { polygon.setPointsAndSegmentsLength(n); updateOutput(n); polygon.setPointsAndSegmentsLength(nOld); } updateOutput(n); } } } /** * * @param length * labels length * @return side segments length */ abstract protected int getSideLengthFromLabelsLength(int length); /** * creates a polyhedron regarding bottom face and top vertex * * @param c * construction * @param labels * @param polygon * @param height */ public AlgoPolyhedronPoints(Construction c, String[] labels, GeoPolygon polygon, NumberValue height) { super(c); // create ChangeableCoordParent if possible GeoNumeric changeableHeight = ChangeableCoordParent .getGeoNumeric(height); if (changeableHeight != null) { heightChangeableCoordParent = new ChangeableCoordParent( changeableHeight, polygon); } initCoords(); bottom = polygon; bottomAsInput = true; this.height = height; shift = 0; // output points correspond to input points outputPoints.augmentOutputSize(1, false); setTopPoint(outputPoints.getElement(0)); createPolyhedron(); // input : inputPoints or list of faces input = new GeoElement[2]; input[0] = bottom; input[1] = (GeoElement) height; addAlgoToInput(); updateOutputPoints(); createFaces(); setOutput(); // GeoNumeric changeableHeight = ChangeableCoordParent // .getGeoNumeric(height); // if (changeableHeight != null) { // heightChangeableCoordParent = new ChangeableCoordParent( // changeableHeight, bottom); // for (GeoPolygon p : polyhedron.getPolygons()) { // p.setChangeableCoordParent(heightChangeableCoordParent); // } // } setLabels(labels); update(); // force update segments and polygons updateOutputSegmentsAndPolygonsParentAlgorithms(); } ChangeableCoordParent heightChangeableCoordParent = null; /** * init Coords values */ protected void initCoords() { // none here } /** * translate all output points */ abstract protected void updateOutputPoints(); /** * update output segments and parents algorithms */ abstract protected void updateOutputSegmentsAndPolygonsParentAlgorithms(); /** * create the polyhedron (faces and edges) */ final private void createPolyhedron() { GeoPointND[] bottomPoints1 = getBottomPoints(); if (bottomPoints1 == null) { // force polygon regular to have at least 3 points if (getBottom() .getParentAlgorithm() instanceof AlgoPolygonRegularND) { AlgoPolygonRegularND algo = (AlgoPolygonRegularND) getBottom() .getParentAlgorithm(); algo.compute(3); bottomPoints1 = getBottomPoints(); createPolyhedron(bottomPoints1); algo.compute(2); } } else { createPolyhedron(bottomPoints1); } } /** * create the polyhedron (faces and edges) with given bottom points * * @param bottomPoints1 * bottom points */ protected abstract void createPolyhedron(GeoPointND[] bottomPoints1); /** * update output * * @param newBottomPointsLength * new bottom points length */ protected abstract void updateOutput(int newBottomPointsLength); /** * sets the bottom of the polyhedron * * @param polyhedron * @return bottom key (if one) */ protected void setBottom(GeoPolyhedron polyhedron) { if (bottom != null) { polyhedron.addPolygonLinked(bottom); } else { GeoPointND[] bottomPoints1 = getBottomPoints(); polyhedron.startNewFace(); for (int i = 0; i < bottomPoints1.length; i++) { polyhedron.addPointToCurrentFace(bottomPoints1[i]); } polyhedron.endCurrentFace(); } } protected GeoPolygon getBottom() { if (bottom != null) { return bottom; } return outputPolygonsBottom.getElement(0); } // /////////////////////////////////////////// // END OF THE CONSTRUCTION // ////////////////////////////////////////// private int shift; /** * shift used when first top point is input * * @return 1 when first top point is input, 0 else */ protected int getShift() { return shift; } /** * pre computation * * @return true if the polyhedron is defined */ public boolean preCompute() { // check if bottom points length has changed (e.g. with regular polygon) if (bottomAsInput) { if (!getBottom().isDefined()) { polyhedron.setUndefined(); return false; } polyhedron.setDefined(); updateOutput(bottom.getPointsLength()); if (height == null && !getBottom().wasInitLabelsCalled()) { updateOutputSegmentsAndPolygonsParentAlgorithms(); } } // else updateOutputPoints(); // update height and volume double h; if (height != null) { h = height.getDouble(); } else { h = getTopPoint().getInhomCoordsInD3().distPlaneOriented( getBottomPoints()[0].getInhomCoordsInD3(), getBottom().getDirectionInD3()); } updateVolume(Math.abs(h)); polyhedron.setOrientedHeight(h); // if prism/pyramid is down-oriented, reverse normals for blending if (height != null) { polyhedron.setReverseNormalsForDrawing(height.getDouble() < 0); } return true; } /** * updates the polyhedron's volume * * @param height * height */ protected void updateVolume(double height) { // calc bottom area if needed if (!bottomAsInput) { ((GeoPolygon3D) getBottom()).updateCoordSys(); getBottom().calcArea(); } } /** * * @return bottom points */ protected GeoPointND[] getBottomPoints() { if (bottom != null) { return bottom.getPointsND(); } return bottomPoints; } /** * * @return top point */ protected GeoPointND getTopPoint() { return topPoint; } private void setTopPoint(GeoPointND p) { topPoint = p; } // ///////////////////////////////////////////////////// // FOR PREVIEWABLE // ///////////////////////////////////////////////////// /** * set visibility of output other than points * * @param visible * flag */ public void setOutputOtherEuclidianVisible(boolean visible) { for (int i = 0; i < outputSegmentsBottom.size(); i++) { outputSegmentsBottom.getElement(i).setEuclidianVisible(visible); } for (int i = 0; i < outputSegmentsSide.size(); i++) { outputSegmentsSide.getElement(i).setEuclidianVisible(visible); } for (int i = 0; i < outputSegmentsTop.size(); i++) { outputSegmentsTop.getElement(i).setEuclidianVisible(visible); } for (int i = 0; i < outputPolygonsBottom.size(); i++) { outputPolygonsBottom.getElement(i).setEuclidianVisible(visible, false); } for (int i = 0; i < outputPolygonsSide.size(); i++) { outputPolygonsSide.getElement(i).setEuclidianVisible(visible, false); } for (int i = 0; i < outputPolygonsTop.size(); i++) { outputPolygonsTop.getElement(i).setEuclidianVisible(visible, false); } } /** * notify kernel update of output other than points */ public void notifyUpdateOutputOther() { for (int i = 0; i < outputSegmentsBottom.size(); i++) { getKernel().notifyUpdate(outputSegmentsBottom.getElement(i)); } for (int i = 0; i < outputSegmentsSide.size(); i++) { getKernel().notifyUpdate(outputSegmentsSide.getElement(i)); } for (int i = 0; i < outputSegmentsTop.size(); i++) { getKernel().notifyUpdate(outputSegmentsTop.getElement(i)); } for (int i = 0; i < outputPolygonsBottom.size(); i++) { getKernel().notifyUpdate(outputPolygonsBottom.getElement(i)); } for (int i = 0; i < outputPolygonsSide.size(); i++) { getKernel().notifyUpdate(outputPolygonsSide.getElement(i)); } for (int i = 0; i < outputPolygonsTop.size(); i++) { getKernel().notifyUpdate(outputPolygonsTop.getElement(i)); } } /** * set output points invisible (use for previewable) * * @param visible * flag */ public void setOutputPointsEuclidianVisible(boolean visible) { for (int i = 0; i < outputPoints.size(); i++) { outputPoints.getElement(i).setEuclidianVisible(visible); } } /** * notify kernel update of output points */ public void notifyUpdateOutputPoints() { for (int i = 0; i < outputPoints.size(); i++) { getKernel().notifyUpdate(outputPoints.getElement(i)); } } /** * * @return top face */ public GeoPolygon getTopFace() { return outputPolygonsTop.getElement(0); } /** * * @param i * side id * @return i-th side of the prism/pyramid */ public GeoPolygon3D getSide(int i) { return outputPolygonsSide.getElement(i); } public NumberValue getHeight() { return height; } @Override final protected boolean isFirstInputPointVisible() { GeoElement point = (GeoElement) getBottomPoints()[0]; return point.isEuclidianVisible() && point.isLabelSet(); } @Override final protected boolean isFirstInputPointLabelVisible() { return ((GeoElement) getBottomPoints()[0]).getLabelVisible(); } }