/*
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.
*/
package org.geogebra.common.geogebra3D.kernel3D.algos;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPoint3D;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoQuadric3D;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.commands.Commands;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.kernelND.GeoLineND;
import org.geogebra.common.kernel.kernelND.GeoQuadricND;
import org.geogebra.common.main.App;
/**
*
* @author Markus
*/
public class AlgoIntersectLineQuadric3D extends AlgoIntersect3D {
private GeoLineND g; // input
private GeoQuadricND q; // input
private GeoPoint3D[] D; // D: old points;
/** output points permutation of Q according to D */
protected GeoPoint3D[] P;
/** new output points, not yet permuted */
protected GeoPoint3D[] Q;
private int intersectionType;
/**
*
* @param cons
* construction
* @param label
* output label
* @param g
* line
* @param q
* auadric
*/
AlgoIntersectLineQuadric3D(Construction cons, String label, GeoLineND g,
GeoQuadric3D q) {
this(cons, g, q);
GeoElement.setLabels(label, P); // TODO change to P
}
/**
*
* @param cons
* construction
* @param labels
* output labels
* @param g
* line
* @param q
* auadric
*/
AlgoIntersectLineQuadric3D(Construction cons, String[] labels, GeoLineND g,
GeoQuadric3D q) {
this(cons, g, q);
GeoElement.setLabels(labels, P); // TODO change to P
}
@Override
public Commands getClassName() {
return Commands.Intersect;
}
@Override
public int getRelatedModeID() {
return EuclidianConstants.MODE_INTERSECT;
}
/**
*
* @param cons
* construction
* @param g
* line
* @param q
* auadric
*/
AlgoIntersectLineQuadric3D(Construction cons, GeoLineND g, GeoQuadricND q) {
super(cons);
this.g = g;
this.q = q;
P = new GeoPoint3D[2];
Q = new GeoPoint3D[2];
D = new GeoPoint3D[2];
for (int i = 0; i < 2; i++) {
P[i] = new GeoPoint3D(cons);
Q[i] = new GeoPoint3D(cons);
D[i] = new GeoPoint3D(cons);
}
initForNearToRelationship();
computeNoPermutation();
if (cons.getApplication()
.fileVersionBefore(App.getSubValues("5.0.281.0"))) {
// was not permuted at that time
permuted = false;
} else {
if (Q[1].isDefined() && !Q[0].isDefined()) {
permuted = true;
} else {
permuted = false;
}
}
setPermutation();
setInputOutput(); // for AlgoElement
}
private boolean permuted;
// for AlgoElement
@Override
public void setInputOutput() {
input = new GeoElement[2];
input[0] = (GeoElement) g;
input[1] = q;
setOutput(P);
noUndefinedPointsInAlgebraView();
setDependencies(); // done by AlgoElement
}
@Override
public final GeoPoint3D[] getIntersectionPoints() {
return P;
}
@Override
protected GeoPoint3D[] getLastDefinedIntersectionPoints() {
return D;
}
/**
*
* @return line input
*/
GeoLineND getLine() {
return g;
}
/**
*
* @return conic input
*/
GeoQuadricND getQuadric() {
return q;
}
@Override
public final String toString(StringTemplate tpl) {
return getLoc().getPlain("IntersectionPointOfAB", q.getLabel(tpl),
((GeoElement) g).getLabel(tpl));
}
/** INTERSECTION TYPE: producing line */
public static final int INTERSECTION_PRODUCING_LINE = 1;
/** INTERSECTION TYPE: asymptotic line */
public static final int INTERSECTION_ASYMPTOTIC_LINE = 2;
/** INTERSECTION TYPE: meeting line */
public static final int INTERSECTION_MEETING_LINE = 3;
/** INTERSECTION TYPE: tangent line */
public static final int INTERSECTION_TANGENT_LINE = 4;
/** INTERSECTION TYPE: secant line */
public static final int INTERSECTION_SECANT_LINE = 5;
/** INTERSECTION TYPE: passing line */
public static final int INTERSECTION_PASSING_LINE = 6;
@Override
public void compute() {
computeNoPermutation();
setPermutation();
}
final private void computeNoPermutation() {
// g: X' = p + tv (X' is inhom coords)
// q: XAX = 0 (the second X is transposed; X = (X',1) is hom coords)
// we have to solve
// u t^2 + 2b t + w = 0
// where
// u = v.S.v
// b = p.S.v + a.v
// w = evaluate(p)
// precalc S.v for u and b
double[] m = q.getFlatMatrix();
double v1 = g.getDirectionInD3().getX();
double v2 = g.getDirectionInD3().getY();
double v3 = g.getDirectionInD3().getZ();
double Sv1 = m[0] * v1 + m[4] * v2 + m[5] * v3;
double Sv2 = m[4] * v1 + m[1] * v2 + m[6] * v3;
double Sv3 = m[5] * v1 + m[6] * v2 + m[2] * v3;
double p1 = g.getStartInhomCoords().getX();
double p2 = g.getStartInhomCoords().getY();
double p3 = g.getStartInhomCoords().getZ();
double u = v1 * Sv1 + v2 * Sv2 + v3 * Sv3;
double b = g.getStartInhomCoords().getX() * Sv1
+ g.getStartInhomCoords().getY() * Sv2
+ g.getStartInhomCoords().getZ() * Sv3 + m[7] * v1 + m[8] * v2
+ m[9] * v3;
double w = p1 * (m[0] * p1 + m[4] * p2 + m[5] * p3 + m[7])
+ p2 * (m[4] * p1 + m[1] * p2 + m[6] * p3 + m[8])
+ p3 * (m[5] * p1 + m[6] * p2 + m[2] * p3 + m[9]) + m[7] * p1
+ m[8] * p2 + m[9] * p3 + m[3];
if (Kernel.isZero(u)) {// no quadratic term
if (Kernel.isZero(b)) {// no linear term: 0 t = -w
if (Kernel.isZero(w)) { // whole line is contained in q
Q[0].setUndefined();
Q[1].setUndefined();
intersectionType = INTERSECTION_PRODUCING_LINE;
} else { // w != 0, Asymptote
Q[0].setUndefined();
Q[1].setUndefined();
intersectionType = INTERSECTION_ASYMPTOTIC_LINE;
}
} else { // b != 0, t = -w/ (2b)
double t0 = -w / (2.0 * b);
if (b < 0) {
Q[0].setCoords(g.getPointInD(3, t0));
Q[1].setUndefined();
} else { // b > 0
Q[0].setUndefined();
Q[1].setCoords(g.getPointInD(3, t0));
}
intersectionType = INTERSECTION_MEETING_LINE;
}
} else { // u != 0
double dis = b * b - u * w;
if (Kernel.isZero(dis)) {// Tangent
double t1 = -b / u;
Q[0].setCoords(g.getPointInD(3, t1));
Q[1].setCoords(Q[0].getCoords());
intersectionType = INTERSECTION_TANGENT_LINE;
} else { // two solutions
if (dis > 0) {
dis = Math.sqrt(dis);
// For accuracy, if b > 0 then we choose
// t1 = -(b+dis) / u
// t2 = (-b + dis) / u = w / -(b+dis)
// if b < 0 then we choose
// t1 = (-b - dis) / u = w / (-b+dis) = w / -(b-dis)
// t2 = -(b-dis) / u
boolean swap = b < 0.0;
if (swap) {
dis = -dis;
}
double n = -(b + dis);
double t1 = swap ? w / n : n / u;
double t2 = swap ? n / u : w / n;
Q[0].setCoords(g.getPointInD(3, t1));
Q[1].setCoords(g.getPointInD(3, t2));
intersectionType = INTERSECTION_SECANT_LINE;
} else { // dis < 0, no solution
Q[0].setUndefined();
Q[1].setUndefined();
intersectionType = INTERSECTION_PASSING_LINE;
}
}
}
for (int i = 0; i < 2; i++) {
checkIsOnLine(Q[i]);
}
}
final private void setPermutation() {
if (permuted) {
P[0].setCoordsFromPoint(Q[1]);
P[1].setCoordsFromPoint(Q[0]);
} else {
P[0].setCoordsFromPoint(Q[0]);
P[1].setCoordsFromPoint(Q[1]);
}
}
private void checkIsOnLine(GeoPoint3D p) {
if (!p.isDefined()) {
return;
}
if (!g.respectLimitedPath(p.getCoords(), Kernel.MIN_PRECISION)) {
p.setUndefined();
}
}
/** @return intersection type (tangent, asymptote, ...) */
public int getIntersectionType() {
return intersectionType;
}
/**
* Returns the index in output[] of the intersection point that is closest
* to the coordinates (xRW, yRW) TODO: move to an interface
*/
/*
* int getClosestPointIndex(double xRW, double yRW, CoordMatrix4x4 mat) {
* GeoPoint3D[] P = getIntersectionPoints(); double x, y, lengthSqr, mindist
* = Double.POSITIVE_INFINITY; int minIndex = 0; for (int i = 0; i <
* P.length; i++) { Coords toSceneInhomCoords =
* mat.mul(P[i].getCoords().getCoordsLast1()).getInhomCoords(); x =
* (toSceneInhomCoords.getX() - xRW); y = (toSceneInhomCoords.getY() - yRW);
* lengthSqr = x * x + y * y; if (lengthSqr < mindist) { mindist =
* lengthSqr; minIndex = i; } }
*
* return minIndex; }
*/
@Override
public final void initForNearToRelationship() {
// TODO
}
@Override
protected GeoElement getOutputForCmdXML(int i) {
// we need to anticipate next loading in what order points will be
// re-created to make them correctly labeled
// * if permuted we need to set P[1] first
// * if first point set is undefined but not second point, we need to
// permute them since on next load they will be
if (permuted) {
if (P[0].isDefined()) {
// else: P[0] and P[1] are defined, so remove permutation
if (P[1].isDefined()) {
return P[1 - i];
}
// else: P[0] is defined and not P[1], so keep permutation
return super.getOutputForCmdXML(i);
}
// P[0] is undefined
if (P[1].isDefined()) {
// P[1] is defined and not P[0], so remove permutation
return P[1 - i];
}
// else
if (P[1].isLabelSet()) {
// P[1] is undefined but labeled and P[0] undefined, so remove
// permutation
return P[1 - i];
}
// only P[0] is labeled, so keep permutation
return super.getOutputForCmdXML(i);
}
//// else: not permuted
if (!P[0].isDefined() && P[1].isDefined()) {
// P[1] is defined and not P[0], so permute
return P[1 - i];
}
// else: P[0] is defined (or P[1] is undefined): keep as is
return super.getOutputForCmdXML(i);
}
}