package org.geogebra.common.geogebra3D.kernel3D.geos; import org.apache.commons.math3.analysis.UnivariateFunction; import org.geogebra.common.geogebra3D.kernel3D.transform.MirrorableAtPlane; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.DistanceFunction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.PathMover; import org.geogebra.common.kernel.PathMoverGeneric; import org.geogebra.common.kernel.PathParameter; import org.geogebra.common.kernel.Matrix.CoordMatrix4x4; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.algos.AlgoMacro; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.Function; import org.geogebra.common.kernel.arithmetic.FunctionVariable; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.arithmetic.ValueType; import org.geogebra.common.kernel.geos.Dilateable; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.Transformable; import org.geogebra.common.kernel.geos.Translateable; import org.geogebra.common.kernel.kernelND.GeoCoordSys2D; import org.geogebra.common.kernel.kernelND.GeoCurveCartesianND; import org.geogebra.common.kernel.kernelND.GeoDirectionND; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoLineND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.kernelND.RotateableND; import org.geogebra.common.kernel.optimization.ExtremumFinderI; import org.geogebra.common.kernel.roots.RealRootUtil; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.plugin.Operation; /** * Class for cartesian curves in 3D * * @author mathieu * */ public class GeoCurveCartesian3D extends GeoCurveCartesianND implements RotateableND, Translateable, MirrorableAtPlane, Transformable, Dilateable { private CoordMatrix4x4 tmpMatrix4x4; /** * empty constructor (for ConstructionDefaults3D) * * @param c * construction */ public GeoCurveCartesian3D(Construction c) { super(c, 3, null); } /** * common constructor * * @param c * construction * @param fun * functions * @param point * point expression (may be null) */ public GeoCurveCartesian3D(Construction c, Function fun[], ExpressionNode point) { super(c, fun, point); } /** * copy constructor * * @param curve * original */ public GeoCurveCartesian3D(GeoCurveCartesian3D curve) { super(curve.cons, 3, null); set(curve); } @Override public Function getFun(int i) { return fun[i]; } /** * @param t * parameter * @return derivative at given point */ public Coords evaluateTangent(double t) { updateDerivatives(); Coords v = new Coords(3); for (int i = 0; i < 3; i++) { v.set(i + 1, funD1[i].value(t)); } return v.normalized(); } @Override public void evaluateCurve(double t, double[] out) { for (int i = 0; i < 3; i++) { out[i] = fun[i].value(t); } } @Override public double[] newDoubleArray() { return new double[3]; } /** * @param t * curve parameter * @return resulting coords */ public Coords evaluateCurve3D(double t) { return new Coords(fun[0].value(t), fun[1].value(t), fun[2].value(t), 1); } /** * Returns the curvature at the specified point * * @param t * parameter */ @Override public double evaluateCurvature(double t) { updateDerivatives(); Coords D1 = new Coords(3); Coords D2 = new Coords(3); for (int i = 0; i < 3; i++) { D1.set(i + 1, funD1[i].value(t)); } for (int i = 0; i < 3; i++) { D2.set(i + 1, funD2[i].value(t)); } // compute curvature using the formula k = |f'' x f'| / |f'|^3 Coords cross = D1.crossProduct(D2); // Log.debug(cross.norm() / Math.pow(D1.norm(), 3)); return cross.norm() / Math.pow(D1.norm(), 3); } private void updateDerivatives() { int dim = 3; funD1 = new Function[dim]; funD2 = new Function[dim]; for (int i = 0; i < dim; i++) { funD1[i] = getFun(i).getDerivative(1, true); funD2[i] = getFun(i).getDerivative(2, true); } } @Override public GeoElement copy() { return new GeoCurveCartesian3D(this); } @Override public boolean isEqual(GeoElementND Geo) { // TODO Auto-generated method stub return false; } @Override public void set(GeoElementND geo) { if (!(geo instanceof GeoCurveCartesianND)) { return; } GeoCurveCartesianND geoCurve = (GeoCurveCartesianND) geo; // fun = new Function[3]; for (int i = 0; i < 2; i++) { fun[i] = new Function(geoCurve.getFun(i), kernel); // Application.debug(fun[i].toString()); } if (geoCurve.isGeoElement3D()) { fun[2] = new Function(geoCurve.getFun(2), kernel); } else { // t -> (x,y,0) 2D curve fun[2] = new Function(new ExpressionNode(kernel, 0), new FunctionVariable(kernel, "t")); } startParam = geoCurve.getMinParameter(); endParam = geoCurve.getMaxParameter(); isDefined = geoCurve.isDefined(); // macro OUTPUT if (geo.getConstruction() != cons && isAlgoMacroOutput()) { if (!geo.isIndependent()) { // this object is an output object of AlgoMacro // we need to check the references to all geos in its function's // expression AlgoMacro algoMacro = (AlgoMacro) getParentAlgorithm(); for (int i = 0; i < 3; i++) { algoMacro.initFunction(fun[i]); } } } // distFun = new ParametricCurveDistanceFunction(this); } @Override public boolean showInAlgebraView() { // TODO Auto-generated method stub return true; } @Override protected boolean showInEuclidianView() { // TODO Auto-generated method stub return true; } @Override public GeoClass getGeoClassType() { return GeoClass.CURVE_CARTESIAN3D; } @Override public Coords getLabelPosition() { return Coords.O; // TODO } @Override public boolean isGeoElement3D() { return true; } @Override public boolean hasFillType() { return false; } // //////////////// // TRACE // //////////////// private boolean trace; @Override public boolean isTraceable() { return true; } @Override public void setTrace(boolean trace) { this.trace = trace; } @Override public boolean getTrace() { return trace; } @Override public void rotate(NumberValue r, GeoPointND S) { if (tmpMatrix4x4 == null) { tmpMatrix4x4 = new CoordMatrix4x4(); } SurfaceTransform.rotate(fun, kernel, r, S, tmpMatrix4x4); } @Override public void rotate(NumberValue r) { if (tmpMatrix4x4 == null) { tmpMatrix4x4 = new CoordMatrix4x4(); } SurfaceTransform.rotate(fun, kernel, r, tmpMatrix4x4); } @Override public void rotate(NumberValue r, GeoPointND S, GeoDirectionND orientation) { if (tmpMatrix4x4 == null) { tmpMatrix4x4 = new CoordMatrix4x4(); } SurfaceTransform.rotate(fun, kernel, r, S, orientation, tmpMatrix4x4); } @Override public void rotate(NumberValue r, GeoLineND line) { if (tmpMatrix4x4 == null) { tmpMatrix4x4 = new CoordMatrix4x4(); } SurfaceTransform.rotate(fun, kernel, r, line, tmpMatrix4x4); } @Override public double[] getDefinedInterval(double a, double b) { return getDefinedInterval(a, b, fun[0], fun[1], fun[2]); } /** * @param a * start parameter * @param b * end parameter * @param funX * x(t) * @param funY * y(t) * @param funZ * z(t) * @return an interval within [a, b] where the funX, funY, funZ are defined. * */ static public double[] getDefinedInterval(double a, double b, UnivariateFunction funX, UnivariateFunction funY, UnivariateFunction funZ) { // compute interval for x(t) double[] interval = RealRootUtil.getDefinedInterval(funX, a, b); // compute interval for y(t) and update interval RealRootUtil.updateDefinedIntervalIntersecting(funY, a, b, interval); // compute interval for z(t) and update interval RealRootUtil.updateDefinedIntervalIntersecting(funZ, a, b, interval); return interval; } /** * @param a * start parameter * @param b * end parameter * @param funX * x(t) * @param funY * y(t) * @param funZ * z(t) * @param fun * additionnal function for view for plane * @return an interval within [a, b] where the funX, funY, funZ are defined. * */ static public double[] getDefinedInterval(double a, double b, UnivariateFunction funX, UnivariateFunction funY, UnivariateFunction funZ, UnivariateFunction fun) { // compute interval for x(t), y(t), z(t) double[] interval = getDefinedInterval(a, b, funX, funY, funZ); // compute interval for fun(t) and update interval RealRootUtil.updateDefinedIntervalIntersecting(fun, a, b, interval); return interval; } // ///////////////////////////////////// // PATH // ///////////////////////////////////// @Override public boolean isClosedPath() { return false; } @Override public PathMover createPathMover() { return new PathMoverGeneric(this); } @Override public void pointChanged(GeoPointND P) { // get closest parameter position on curve PathParameter pp = P.getPathParameter(); double t = getClosestParameter(P, pp.t); pp.t = t; pathChanged(P, false); } @Override public boolean isOnPath(GeoPointND PI, double eps) { if (PI.getPath() == this) { return true; } // get closest parameter position on curve PathParameter pp = PI.getPathParameter(); double t = getClosestParameter(PI, pp.t); Coords coords = PI.getInhomCoordsInD3(); boolean onPath = Math.abs(fun[0].value(t) - coords.getX()) <= eps && Math.abs(fun[1].value(t) - coords.getY()) <= eps && Math.abs(fun[2].value(t) - coords.getZ()) <= eps; return onPath; } @Override public void pathChanged(GeoPointND PI) { // if kernel doesn't use path/region parameters, do as if point changed // its coords pathChanged(PI, !getKernel().usePathAndRegionParameters(PI)); } private void pathChanged(GeoPointND P, boolean changePoint) { // if kernel doesn't use path/region parameters, do as if point changed // its coords if (changePoint) { pointChanged(P); return; } PathParameter pp = P.getPathParameter(); if (pp.t < startParam) { pp.t = startParam; } else if (pp.t > endParam) { pp.t = endParam; } // calc point for given parameter P.setCoords(evaluateCurve3D(pp.t), false); } @Override public void updateDistanceFunction() { if (distFun == null) { distFun = new CurveCartesian3DDistanceFunction(this); } } /** * Returns the parameter value t where this curve has minimal distance to * point P. * * @param startValue * an interval around startValue is specially investigated * @param P * point to which the distance is minimized * @return optimal parameter value t */ @Override public double getClosestParameter(GeoPointND P, double startValue) { double startVal = startValue; if (distFun == null) { distFun = new CurveCartesian3DDistanceFunction(this); } distFun.setDistantPoint(P); // check if P is on this curve and has the right path parameter already if (P.getPath() == this) { // point A is on curve c, take its parameter PathParameter pp = P.getPathParameter(); double pathParam = pp.t; if (distFun.value(pathParam) < Kernel.MIN_PRECISION * Kernel.MIN_PRECISION) { return pathParam; } // if we don't have a startValue yet, let's take the path parameter // as a guess if (Double.isNaN(startVal)) { startVal = pathParam; } } // first sample distFun to find a start intervall for ExtremumFinder double step = (endParam - startParam) / CLOSEST_PARAMETER_SAMPLES; double minVal = distFun.value(startParam); double minParam = startParam; double t = startParam; for (int i = 0; i < CLOSEST_PARAMETER_SAMPLES; i++) { t = t + step; double ft = distFun.value(t); if (ft < minVal) { // found new minimum minVal = ft; minParam = t; } } // use interval around our minParam found by sampling // to find minimum // Math.max/min removed and ParametricCurveDistanceFunction modified // instead double left = minParam - step; double right = minParam + step; ExtremumFinderI extFinder = kernel.getExtremumFinder(); double sampleResult = extFinder.findMinimum(left, right, distFun, Kernel.MIN_PRECISION); sampleResult = adjustRange(sampleResult); // if we have a valid startParam we try the interval around it too // however, we don't check the same interval again if (!Double.isNaN(startVal) && (startVal < left || right < startVal)) { // Math.max/min removed and ParametricCurveDistanceFunction modified // instead left = startVal - step; right = startVal + step; double startValResult = extFinder.findMinimum(left, right, distFun, Kernel.MIN_PRECISION); startValResult = adjustRange(startValResult); if (distFun .value(startValResult) < distFun.value(sampleResult) + Kernel.MIN_PRECISION / 2) { return startValResult; } } return sampleResult; } /** * allow a curve like Curve[sin(t), cos(t), t, 0, 12*2pi] to "join up" * properly at 0 and 12*2pi * * @param startValResult * @return startValResult adjusted to be in range [startParam, endParam] if * it's just outside */ private double adjustRange(double startValResult) { if (startValResult < startParam) { return startValResult + (endParam - startParam); } if (startValResult > endParam) { return startValResult - (endParam - startParam); } return startValResult; } // ///////////////////////////////////// // DISTANCE FUNCTION // ///////////////////////////////////// private static class CurveCartesian3DDistanceFunction implements DistanceFunction { private Coords distCoords, distDirection; private GeoCurveCartesian3D curve; /** * Creates a function for evaluating squared distance of (px,py) from * curve (px and py must be entered using a setter) * * @param curve * curve */ public CurveCartesian3DDistanceFunction(GeoCurveCartesian3D curve) { this.curve = curve; } /** * Sets the point to be used in the distance function * * @param p * point */ @Override public void setDistantPoint(GeoPointND p) { if (p.isGeoElement3D()) { GeoPoint3D p3D = (GeoPoint3D) p; if (p3D.hasWillingCoords()) { distCoords = p3D.getWillingCoords(); } else { distCoords = p3D.getInhomCoordsInD3(); } distDirection = p3D.getWillingDirection(); // maybe undefined } else { distCoords = p.getInhomCoordsInD3(); distDirection = null; } } /** * Returns the square of the distance between the currently set distance * point and this curve at parameter position t */ @Override public double value(double t) { Coords eval = curve.evaluateCurve3D(t); if (distDirection == null || !distDirection.isDefined()) { return eval.squareDistance3(distCoords); } return eval.squareDistLine3(distCoords, distDirection); } } // /////////////////////////////////// // TRANSLATE // /////////////////////////////////// @Override public void translate(Coords v) { // current expressions for (int i = 0; i < 3; i++) { ExpressionNode expr = fun[i].deepCopy(kernel).getExpression(); ExpressionNode trans = expr.plus(v.get(i + 1)); fun[i].setExpression(trans); } } @Override public boolean isTranslateable() { return true; } // ////////////////////// // MIRROR // ////////////////////// @Override public void mirror(Coords Q) { dilate(new MyDouble(kernel, -1.0), Q); } @Override public void mirror(GeoLineND line) { SurfaceTransform.mirror(fun, kernel, line); } @Override public void mirror(GeoCoordSys2D plane) { SurfaceTransform.mirror(fun, kernel, plane); } // ////////////////////// // DILATE // ////////////////////// @Override public void dilate(NumberValue ratio, Coords P) { translate(P.mul(-1)); for (int i = 0; i < 3; i++) { ExpressionNode expr = fun[i].deepCopy(kernel).getExpression(); fun[i].setExpression(new ExpressionNode(kernel, ratio, Operation.MULTIPLY, expr)); } translate(P); } @Override public void clearCasEvalMap(String string) { // TODO Auto-generated method stub } @Override public boolean isFunctionInX() { return false; } /* * public GeoVec2D evaluateCurve(double t) { double z = * getFun(2).evaluate(t); if (Double.isNaN(z) || Double.isInfinite(z) || * !Kernel.isZero(z)){ // won't be visible in 2D view return new * GeoVec2D(this.kernel, Double.NaN, Double.NaN); } return new * GeoVec2D(this.kernel, getFun(0).evaluate(t), getFun(1).evaluate(t)); } */ @Override public double distanceMax(double[] p1, double[] p2) { return Math.max( Math.max(Math.abs(p1[0] - p2[0]), Math.abs(p1[1] - p2[1])), Math.abs(p1[2] - p2[2])); } /** * eg f(t) for 3D Curve * * @param t * parameter * @return 3D Point */ @Override public Geo3DVec evaluateCurve(double t) { return new Geo3DVec(this.kernel, getFun(0).value(t), getFun(1).value(t), getFun(2).value(t)); } @Override protected GeoCurveCartesianND newGeoCurveCartesian(Construction cons1) { return new GeoCurveCartesian3D(cons1); } @Override final public HitType getLastHitType() { return HitType.ON_BOUNDARY; } @Override public ValueType getValueType() { return ValueType.PARAMETRIC3D; } @Override public UnivariateFunction getUnivariateFunctionX() { // TODO Auto-generated method stub return null; } @Override public UnivariateFunction getUnivariateFunctionY() { // TODO Auto-generated method stub return null; } @Override protected Coords pointToCoords(GeoPointND geoPointND) { return geoPointND.getInhomCoordsInD3(); } }