/* 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. */ /* * AlgoRotatePoint.java * * Created on 24. September 2001, 21:37 */ package org.geogebra.common.kernel.algos; import org.geogebra.common.euclidian.EuclidianConstants; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.kernel.geos.GeoCurveCartesian; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunction; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoNumberValue; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.PointRotateable; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.prover.NoSymbolicParametersException; import org.geogebra.common.kernel.prover.polynomial.PPolynomial; import org.geogebra.common.kernel.prover.polynomial.PVariable; /** * * @author Markus */ public class AlgoRotatePoint extends AlgoTransformation implements SymbolicParametersBotanaAlgo { private GeoPointND Q; private PointRotateable out; private NumberValue angle; private GeoElement inGeo, outGeo, angleGeo; private PVariable[] botanaVars; private PPolynomial[] botanaPolynomials; /** * Creates new unlabeled point rotation algo * * @param cons * construction * @param A * rotated geo * @param angle * angle * @param Q * rotation center */ public AlgoRotatePoint(Construction cons, GeoElement A, GeoNumberValue angle, GeoPointND Q) { super(cons); this.angle = angle; this.Q = Q; angleGeo = angle.toGeoElement(); inGeo = A; outGeo = getResultTemplate(inGeo); if (outGeo instanceof PointRotateable) { out = (PointRotateable) outGeo; } setInputOutput(); compute(); if (inGeo.isGeoFunction()) { cons.registerEuclidianViewCE(this); } } @Override public Commands getClassName() { return Commands.Rotate; } @Override public int getRelatedModeID() { return EuclidianConstants.MODE_ROTATE_BY_ANGLE; } // for AlgoElement @Override protected void setInputOutput() { input = new GeoElement[3]; input[0] = inGeo; input[1] = angleGeo; input[2] = (GeoElement) Q; setOutputLength(1); setOutput(0, outGeo); setDependencies(); // done by AlgoElement } /** * Returns the rotated point * * @return rotated point */ @Override public GeoElement getResult() { return outGeo; } // calc rotated point @Override public final void compute() { if (inGeo.isGeoList()) { transformList((GeoList) inGeo, (GeoList) outGeo); return; } if (inGeo instanceof GeoFunction) { ((GeoFunction) inGeo) .toGeoCurveCartesian((GeoCurveCartesian) outGeo); } else { outGeo.set(inGeo); } if (!outGeo.isDefined()) { return; } out.rotate(angle, Q); if (inGeo.isLimitedPath()) { this.transformLimitedPath(inGeo, outGeo); } } @Override final public String toString(StringTemplate tpl) { // Michael Borcherds 2008-03-25 // simplified to allow better Chinese translation return getLoc().getPlain("ARotatedByAngleB", inGeo.getLabel(tpl), angleGeo.getLabel(tpl)); } @Override protected void setTransformedObject(GeoElement g, GeoElement g2) { inGeo = g; outGeo = g2; if (!(outGeo instanceof GeoList)) { out = (PointRotateable) outGeo; } } @Override protected GeoElement getResultTemplate(GeoElement geo) { if (geo instanceof GeoFunction) { return new GeoCurveCartesian(cons); } return super.getResultTemplate(geo); } @Override public double getAreaScaleFactor() { return 1; } @Override public PVariable[] getBotanaVars(GeoElementND geo) { return botanaVars; } @Override public PPolynomial[] getBotanaPolynomials(GeoElementND geo) throws NoSymbolicParametersException { /* * This polynomial cannot be cached, because the polynomial depends on * the rotation angle. If the user changes the angle, the polynomial * should be recomputed. Currently we cannot force recomputing a Botana * polynomial externally, thus here we simply recompute the polynomial * each time it is used. TODO: Consider caching the angle here (but not * the poly). */ // point to rotate GeoPoint A = (GeoPoint) Q; // point around the rotation is processed GeoPoint B = (GeoPoint) input[0]; if (A != null && B != null) { PVariable[] vA = A.getBotanaVars(A); PVariable[] vB = B.getBotanaVars(B); if (botanaVars == null) { botanaVars = new PVariable[8]; // A' - rotation point botanaVars[0] = new PVariable(kernel); botanaVars[1] = new PVariable(kernel); // A - point around the rotation is processed botanaVars[2] = vA[0]; botanaVars[3] = vA[1]; // B - point to rotate botanaVars[4] = vB[0]; botanaVars[5] = vB[1]; // t1 = sqrt(3) botanaVars[6] = new PVariable(kernel); // t2 = sqrt(2) botanaVars[7] = new PVariable(kernel); } /* * Currently some typical angles are implemented. 15/75/105/165 * degrees could also be easily done. TODO: See * https://en.wikipedia.org/wiki/ * Trigonometric_constants_expressed_in_real_radicals for a list of * possible angles with algebraic sin/cos values. * * In many cases we cannot distinguish between some directions, that * is, e.g. +60 and -60 are the same. See Zoltan's diss, p. 92 for * some basic descriptions why. (The full details are not disclosed * there but here in the comments below!) * * Note that in the non-distinguishable cases symmetry is not always * axial. For example, +45 and -135 are paired, because sin(45) and * cos(45) are simultaneously sqrt(2)/2=t. In this case we use * 2*t^2=1 and by solving (x,y)=(t,t) we get two points on the line * y=x (point symmetry, center in the origin). The same idea for +60 * and -60 is a bit different, here sin(60)=sqrt(3)/2=t and * cos(60)=1/2, and we use 4*t^2=3, by solving (x,y)=(1/2,t) which * yields points being symmetrical axially (the axis is the x-axis). * TODO: try to generalize this idea. * * Giac can actually compute e.g. cos(pi/10)=sqrt(2*sqrt(5)+10)/4 * and hopefully also a minimal polynomial can be computed for this. */ double angleDoubleVal = angle.getDouble(); double angleDoubleValDeg = angleDoubleVal / Math.PI * 180; int angleValDeg = (int) angleDoubleValDeg; if (!Kernel.isInteger(angleDoubleValDeg)) { // unhandled angle, not an integer degree throw new NoSymbolicParametersException(); } PPolynomial a1 = new PPolynomial(vA[0]); PPolynomial a2 = new PPolynomial(vA[1]); PPolynomial b1 = new PPolynomial(vB[0]); PPolynomial b2 = new PPolynomial(vB[1]); PPolynomial a_1 = new PPolynomial(botanaVars[0]); PPolynomial a_2 = new PPolynomial(botanaVars[1]); PPolynomial t1 = new PPolynomial(botanaVars[6]); PPolynomial t2 = new PPolynomial(botanaVars[7]); angleValDeg %= 360; if (angleValDeg < 0) { angleValDeg += 360; // be non-negative } // rotate by 0 degrees if (angleValDeg == 0) { botanaPolynomials = new PPolynomial[2]; botanaPolynomials[0] = a_1.subtract(a1).subtract(b1).add(a1); botanaPolynomials[1] = a_2.subtract(a2).subtract(b2).add(a2); return botanaPolynomials; } // rotate by 180 or -180 degrees else if (angleValDeg == 180) { botanaPolynomials = new PPolynomial[2]; botanaPolynomials[0] = a_1.subtract(a1).add(b1).subtract(a1); botanaPolynomials[1] = a_2.subtract(a2).add(b2).subtract(a2); return botanaPolynomials; } // rotate by 90 degrees else if (angleValDeg == 90) { botanaPolynomials = new PPolynomial[2]; botanaPolynomials[0] = a_1.subtract(a1).add(b2).subtract(a2); botanaPolynomials[1] = a_2.subtract(a2).subtract(b1).add(a1); return botanaPolynomials; } // rotate by -90 degrees else if (angleValDeg == 270) { botanaPolynomials = new PPolynomial[2]; botanaPolynomials[0] = a_1.subtract(a1).subtract(b2).add(a2); botanaPolynomials[1] = a_2.subtract(a2).add(b1).subtract(a1); return botanaPolynomials; } /* * TODO: Many parts of the following could be handled at the same * time. */ // rotate by 30 or 150 degrees else if (angleValDeg == 30 || angleValDeg == 150) { botanaPolynomials = new PPolynomial[3]; botanaPolynomials[0] = t1.multiply(t1) .subtract(new PPolynomial(3)); PPolynomial p1 = new PPolynomial(2).multiply(a_1) .subtract(new PPolynomial(2).multiply(a1)).add(b2) .subtract(a2); PPolynomial p2 = b1.subtract(a1); PPolynomial p3 = t1.multiply(p2); botanaPolynomials[1] = p1.subtract(p3); PPolynomial p4 = new PPolynomial(2).multiply(a_2) .subtract(new PPolynomial(2).multiply(a2)).subtract(b1) .add(a1); PPolynomial p5 = b2.subtract(a2); PPolynomial p6 = t1.multiply(p5); botanaPolynomials[2] = p4.subtract(p6); return botanaPolynomials; } // rotate by -30 or -150 degrees else if (angleValDeg == 330 || angleValDeg == 210) { botanaPolynomials = new PPolynomial[3]; botanaPolynomials[0] = t1.multiply(t1) .subtract(new PPolynomial(3)); PPolynomial p1 = new PPolynomial(2).multiply(a_1) .subtract(new PPolynomial(2).multiply(a1)).subtract(b2) .add(a2); PPolynomial p2 = b1.subtract(a1); PPolynomial p3 = t1.multiply(p2); botanaPolynomials[1] = p1.subtract(p3); PPolynomial p4 = new PPolynomial(2).multiply(a_2) .subtract(new PPolynomial(2).multiply(a2)).add(b1) .subtract(a1); PPolynomial p5 = b2.subtract(a2); PPolynomial p6 = t1.multiply(p5); botanaPolynomials[2] = p4.subtract(p6); return botanaPolynomials; } // rotate by -45 or 135 degrees else if (angleValDeg == 315 || angleValDeg == 135) { botanaPolynomials = new PPolynomial[3]; botanaPolynomials[0] = t2.multiply(t2) .subtract(new PPolynomial(2)); PPolynomial p1 = new PPolynomial(2).multiply(a_1) .subtract(new PPolynomial(2).multiply(a1)); PPolynomial p2 = b1.subtract(a1).add(b2).subtract(a2); botanaPolynomials[1] = p1.subtract(t2.multiply(p2)); PPolynomial p3 = new PPolynomial(2).multiply(a_2) .subtract(new PPolynomial(2).multiply(a2)); PPolynomial p4 = b2.subtract(a2).subtract(b1).add(a1); botanaPolynomials[2] = p3.subtract(t2.multiply(p4)); return botanaPolynomials; } // rotate by 45 or -135 degrees else if (angleValDeg == 45 || angleValDeg == 225) { botanaPolynomials = new PPolynomial[3]; botanaPolynomials[0] = t2.multiply(t2) .subtract(new PPolynomial(2)); PPolynomial p1 = new PPolynomial(2).multiply(a_1) .subtract(new PPolynomial(2).multiply(a1)); PPolynomial p2 = b1.subtract(a1).subtract(b2).add(a2); botanaPolynomials[1] = p1.subtract(t2.multiply(p2)); PPolynomial p3 = new PPolynomial(2).multiply(a_2) .subtract(new PPolynomial(2).multiply(a2)); PPolynomial p4 = b1.subtract(a1).add(b2).subtract(a2); botanaPolynomials[2] = p3.subtract(t2.multiply(p4)); return botanaPolynomials; } // rotate by +-60 degrees else if (angleValDeg == 60 || angleValDeg == 300) { botanaPolynomials = new PPolynomial[3]; botanaPolynomials[0] = t1.multiply(t1) .subtract(new PPolynomial(3)); PPolynomial p1 = new PPolynomial(2).multiply(a_1) .subtract(new PPolynomial(2).multiply(a1)).subtract(b1) .add(a1); PPolynomial p2 = b2.subtract(a2); botanaPolynomials[1] = p1.subtract(t1.multiply(p2)); PPolynomial p3 = new PPolynomial(2).multiply(a_2) .subtract(new PPolynomial(2).multiply(a2)).subtract(b2) .add(a2); PPolynomial p4 = a1.subtract(b1); botanaPolynomials[2] = p3.subtract(t1.multiply(p4)); return botanaPolynomials; } // rotate by +-120 degrees else if (angleValDeg == 120 || angleValDeg == 240) { botanaPolynomials = new PPolynomial[3]; botanaPolynomials[0] = t1.multiply(t1) .subtract(new PPolynomial(3)); PPolynomial p1 = new PPolynomial(2).multiply(a_1) .subtract(new PPolynomial(2).multiply(a1)).add(b1) .subtract(a1); PPolynomial p2 = b2.subtract(a2); botanaPolynomials[1] = p1.subtract(t1.multiply(p2)); PPolynomial p3 = new PPolynomial(2).multiply(a_2) .subtract(new PPolynomial(2).multiply(a2)).add(b2) .subtract(a2); PPolynomial p4 = a1.subtract(b1); botanaPolynomials[2] = p3.subtract(t1.multiply(p4)); return botanaPolynomials; } // unhandled angle throw new NoSymbolicParametersException(); } throw new NoSymbolicParametersException(); } }