package org.geogebra.common.geogebra3D.kernel3D.geos;
import java.util.ArrayList;
import org.geogebra.common.geogebra3D.kernel3D.transform.MirrorableAtPlane;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.MatrixTransformable;
import org.geogebra.common.kernel.Path;
import org.geogebra.common.kernel.PathParameter;
import org.geogebra.common.kernel.Matrix.CoordMatrix;
import org.geogebra.common.kernel.Matrix.CoordMatrix4x4;
import org.geogebra.common.kernel.Matrix.CoordMatrixUtil;
import org.geogebra.common.kernel.Matrix.CoordSys;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.geos.Dilateable;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.Traceable;
import org.geogebra.common.kernel.geos.Transformable;
import org.geogebra.common.kernel.geos.Translateable;
import org.geogebra.common.kernel.kernelND.GeoCoordSys;
import org.geogebra.common.kernel.kernelND.GeoCoordSys1DInterface;
import org.geogebra.common.kernel.kernelND.GeoCoordSys2D;
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;
/**
* 1D linear object in space (segment, line, ...)
*
*/
public abstract class GeoCoordSys1D extends GeoElement3D
implements Path, GeoLineND, GeoCoordSys, GeoCoordSys1DInterface,
Translateable, MatrixTransformable, Traceable, RotateableND,
MirrorableAtPlane, Transformable, Dilateable {
/** coord system */
protected CoordSys coordsys;
/** start point */
protected GeoPointND startPoint;
/** end point */
protected GeoPointND endPoint;
private boolean isIntersection;
/**
* @param c
* construction
*/
public GeoCoordSys1D(Construction c) {
this(c, false);
}
/**
* @param c
* construction
* @param isIntersection
* whetherthis is intersection line
*/
public GeoCoordSys1D(Construction c, boolean isIntersection) {
super(c);
this.isIntersection = isIntersection;
// moved from GeoElement's constructor
// must be called from the subclass, see
// http://benpryor.com/blog/2008/01/02/dont-call-subclass-methods-from-a-superclass-constructor/
setConstructionDefaults(); // init visual settings
coordsys = new CoordSys(1);
}
/**
* @param c
* construction
* @param O
* start point
* @param V
* direction
*/
public GeoCoordSys1D(Construction c, Coords O, Coords V) {
this(c);
setCoord(O, V);
}
/**
* @param c
* construction
* @param O
* start point
* @param I
* end point
*/
public GeoCoordSys1D(Construction c, GeoPointND O, GeoPointND I) {
this(c, O, I, false);
}
/**
* @param c
* construction
* @param O
* start point
* @param I
* end point
* @param isIntersection
* true for intersection lines
*/
public GeoCoordSys1D(Construction c, GeoPointND O, GeoPointND I,
boolean isIntersection) {
this(c, isIntersection);
setCoord(O, I);
}
@Override
public boolean isDefined() {
return coordsys.isDefined();
}
@Override
public void setUndefined() {
coordsys.setUndefined();
}
/**
* set the matrix to [(I-O) O]
*
* @param a_O
* start point
* @param a_I
* end point
*/
public void setCoordFromPoints(Coords a_O, Coords a_I) {
setCoord(a_O, a_I.sub(a_O));
}
/**
* set the matrix to [V O]
*
* @param o
* start point
* @param v
* direction
*/
public void setCoord(Coords o, Coords v) {
coordsys.resetCoordSys();
coordsys.addPoint(o);
coordsys.addVector(v);
coordsys.makeOrthoMatrix(false, false);
}
/**
* set the line to pass through (pointX, pointY)
*
* @param pointX
* x coord
* @param pointY
* y coord
*/
@Override
final public void setLineThrough(double pointX, double pointY) {
setCoord(new Coords(pointX, pointY, 0, 1), getDirectionInD3());
}
/**
* set coords to origin O and vector (I-O). If I (or O) is infinite, I is
* used as direction vector.
*
* @param O
* origin point
* @param I
* unit point
* @return true if one point is null or infinite
*/
public boolean setCoord(GeoPointND O, GeoPointND I) {
startPoint = O;
endPoint = I;
if ((O == null) || (I == null)) {
return true;
}
if (I.isInfinite()) {
if (O.isInfinite()) {
setUndefined(); // TODO infinite line
} else {
setCoord(O.getInhomCoordsInD3(), I.getCoordsInD3());
}
return true;
} else if (O.isInfinite()) {
setCoord(I.getInhomCoordsInD3(), O.getCoordsInD3());
return true;
} else {
setCoord(O.getInhomCoordsInD3(),
I.getInhomCoordsInD3().sub(O.getInhomCoordsInD3()));
return false;
}
}
/**
* @param geo
* other system
*/
public void setCoord(GeoCoordSys1D geo) {
setCoord(geo.getCoordSys().getOrigin(), geo.getCoordSys().getVx());
}
@Override
public void set(GeoElementND geo) {
if (geo instanceof GeoCoordSys1D) {
if (!geo.isDefined()) {
setUndefined();
} else {
setCoord((GeoCoordSys1D) geo);
}
} else if (geo instanceof GeoLineND) {
if (!geo.isDefined()) {
setUndefined();
} else {
setCoord(((GeoLineND) geo).getStartPoint(),
((GeoLineND) geo).getEndPoint());
}
}
}
/**
* @param cons1
* construction for the copy
* @return a new instance of the proper GeoCoordSys1D (GeoLine3D,
* GeoSegment3D, ...)
*/
abstract protected GeoCoordSys1D create(Construction cons1);
@Override
final public GeoCoordSys1D copy() {
GeoCoordSys1D geo = create(cons);
geo.set(this);
geo.setCoord(this);
return geo;
}
/**
* returns matrix corresponding to segment joining l1 to l2, using
* getLineThickness()
*/
/*
* public GgbMatrix getSegmentMatrix(double l1, double l2){
*
*
*
* return GgbMatrix4x4.subSegmentX(getMatrix4x4(), l1, l2); }
*/
/**
* returns the point at position lambda on the coord sys
*
* @param lambda
* path parameter (0 for stat point)
* @return the point at position lambda on the coord sys
*/
public Coords getPoint(double lambda) {
return coordsys.getPoint(lambda);
}
/**
* returns the point at position lambda on the coord sys in the dimension
* given
*
* @param dimension
* dimension
* @param lambda
* path parameter
* @return the point at position lambda on the coord sys
*/
@Override
public Coords getPointInD(int dimension, double lambda) {
Coords v = getPoint(lambda);
// Application.debug("v("+lambda+")=\n"+v+"\no=\n"+coordsys.getOrigin()+"\nVx=\n"+coordsys.getVx()+"\ncoordsys=\n"+coordsys.getMatrixOrthonormal());
switch (dimension) {
case 3:
return v;
case 2:
return new Coords(v.getX(), v.getY(), v.getW());
default:
return null;
}
}
/** @return cs unit */
public double getUnit() {
return getCoordSys().getVx().norm();
}
@Override
public Coords getMainDirection() {
return getCoordSys().getMatrixOrthonormal().getVx();
}
@Override
public Coords getDirectionForEquation() {
return getCoordSys().getVx();
}
// Path3D interface
@Override
public boolean isPath() {
return true;
}
@Override
public void pointChanged(GeoPointND P) {
double t = getParamOnLine(P);
if (t < getMinParameter()) {
t = getMinParameter();
} else if (t > getMaxParameter()) {
t = getMaxParameter();
}
// set path parameter
PathParameter pp = P.getPathParameter();
pp.setT(t);
// udpate point using pathChanged
P.setCoords(getPoint(t), false);
}
/**
* @param P
* point in space
* @return path parameter of P's projection on line
*/
public double getParamOnLine(GeoPointND P) {
boolean done = false;
double t = 0;
if (((GeoElement) P).isGeoElement3D()) {
if (((GeoPoint3D) P).hasWillingCoords()) {
if (((GeoPoint3D) P).hasWillingDirection()) {
// project willing location using willing direction
// GgbVector[] project =
// coordsys.getProjection(P.getWillingCoords(),
// P.getWillingDirection());
if (tmpCoords1 == null) {
tmpCoords1 = Coords.createInhomCoorsInD3();
}
t = ((GeoPoint3D) P).getWillingCoords()
.projectedParameterOnLineWithDirection(
coordsys.getOrigin(), coordsys.getVx(),
((GeoPoint3D) P).getWillingDirection(),
tmpCoords1);
done = true;
} else {
// project current point coordinates
// Application.debug("ici\n
// getWillingCoords=\n"+P.getWillingCoords()+"\n
// matrix=\n"+getMatrix().toString());
Coords preDirection = ((GeoPoint3D) P).getWillingCoords()
.sub(coordsys.getOrigin())
.crossProduct(coordsys.getVx());
if (preDirection.equalsForKernel(0,
Kernel.STANDARD_PRECISION)) {
preDirection = coordsys.getVy();
}
if (tmpCoords1 == null) {
tmpCoords1 = Coords.createInhomCoorsInD3();
}
t = ((GeoPoint3D) P).getWillingCoords()
.projectedParameterOnLineWithDirection(
coordsys.getOrigin(),
coordsys.getVx(), preDirection
.crossProduct4(coordsys.getVx()),
tmpCoords1);
done = true;
}
}
}
if (!done) {
// project current point coordinates
// Application.debug("project current point coordinates");
Coords preDirection = P.getInhomCoordsInD3()
.sub(coordsys.getOrigin()).crossProduct(coordsys.getVx());
if (preDirection.equalsForKernel(0, Kernel.STANDARD_PRECISION)) {
preDirection = coordsys.getVy();
}
if (tmpCoords1 == null) {
tmpCoords1 = Coords.createInhomCoorsInD3();
}
t = P.getInhomCoordsInD3().projectedParameterOnLineWithDirection(
coordsys.getOrigin(), coordsys.getVx(),
preDirection.crossProduct4(coordsys.getVx()), tmpCoords1);
}
return t;
}
@Override
public void pathChanged(GeoPointND P) {
// if kernel doesn't use path/region parameters, do as if point changed
// its coords
if (!getKernel().usePathAndRegionParameters(P)) {
pointChanged(P);
return;
}
PathParameter pp = P.getPathParameter();
P.setCoords(getPoint(pp.getT()), false);
}
@Override
public boolean isOnPath(GeoPointND PI, double eps) {
if (PI.getPath() == this) {
return true;
}
return isOnPath(PI.getCoordsInD3(), eps);
}
@Override
public boolean isOnPath(Coords coords, double eps) {
return isOnFullLine(coords, eps);
}
@Override
public boolean isOnFullLine(Coords p, double eps) {
Coords cross;
if (Kernel.isZero(p.getW())) {// infinite point : check direction
cross = p.crossProduct(getDirectionInD3());
return cross.equalsForKernel(0, Kernel.MIN_PRECISION);
}
// standard case
Coords d = getDirectionInD3().normalized();
Coords v = p.sub(getStartInhomCoords());
Coords n = v.sub(d.mul(v.dotproduct(d)));
return n.dotproduct(n) < eps * eps;
}
@Override
public boolean respectLimitedPath(Coords coords, double eps) {
return true;
}
// //////////////////////////////////
//
/**
* return true if x is a valid coordinate (eg 0<=x<=1 for a segment)
*
* @param x
* coordinate
* @return true if x is a valid coordinate (eg 0<=x<=1 for a segment)
*/
abstract public boolean isValidCoord(double x);
// //////////////////////////////////
// XML
// //////////////////////////////////
/**
* returns all class-specific xml tags for saveXML
*/
@Override
protected void getXMLtags(StringBuilder sb) {
super.getXMLtags(sb);
// line thickness and type
getLineStyleXML(sb);
}
@Override
public CoordSys getCoordSys() {
return coordsys;
}
@Override
public Coords getLabelPosition() {
return coordsys.getPoint(0.5);
}
@Override
public Coords getCartesianEquationVector(CoordMatrix m) {
if (m == null) {
return CoordMatrixUtil.lineEquationVector(getCoordSys().getOrigin(),
getCoordSys().getVx());
}
return CoordMatrixUtil.lineEquationVector(getCoordSys().getOrigin(),
getCoordSys().getVx(), m);
}
@Override
public Coords getStartInhomCoords() {
return getCoordSys().getOrigin().getInhomCoordsInSameDimension();
}
/**
* @return inhom coords of the end point
*/
@Override
public Coords getEndInhomCoords() {
return getCoordSys().getPoint(1).getInhomCoordsInSameDimension();
}
@Override
public Coords getDirectionInD3() {
return getCoordSys().getVx();
}
/**
*
* @return start point
*/
@Override
public GeoPointND getStartPoint() {
return startPoint;
}
/**
*
* @return "end" point
*/
@Override
public GeoPointND getEndPoint() {
return endPoint;
}
/**
*
* @return true if is an intersection curve
*/
public boolean isIntersection() {
return isIntersection;
}
@Override
final public boolean isTranslateable() {
return true;
}
@Override
final public void translate(Coords v) {
Coords o = getCoordSys().getOrigin();
o.addInside(v);
setCoord(o, getCoordSys().getVx());
}
// ///////////////////////////////////
// POINTS ON COORD SYS
// ///////////////////////////////////
/** list of points on this line */
protected ArrayList<GeoPointND> pointsOnLine;
/**
* Returns a list of points that this line passes through. May return null.
*
* @return list of points that this line passes through.
*/
public final ArrayList<GeoPointND> getPointsOnLine() {
return pointsOnLine;
}
/**
* Sets a list of points that this line passes through. This method should
* only be used by AlgoMacro.
*
* @param points
* list of points that this line passes through
*/
public final void setPointsOnLine(ArrayList<GeoPointND> points) {
pointsOnLine = points;
}
@Override
public final void addPointOnLine(GeoPointND p) {
if (pointsOnLine == null) {
pointsOnLine = new ArrayList<GeoPointND>();
}
if (!pointsOnLine.contains(p)) {
pointsOnLine.add(p);
}
}
/**
* Calculates the distance between this line and line g.
*
* @param g
* line
* @return distance between lines
*/
@Override
final public double distance(GeoLineND g) {
double dist;
Coords cVector = this.getDirectionInD3()
.crossProduct(g.getDirectionInD3());
Coords diffPoints = this.getPointInD(3, 0)
.getInhomCoordsInSameDimension()
.sub(g.getPointInD(3, 0).getInhomCoordsInSameDimension());
if (cVector.isZero()) { // two lines are parallel
Coords n = diffPoints.crossProduct(this.getDirectionInD3())
.crossProduct(this.getDirectionInD3());
dist = Math.abs(diffPoints.dotproduct(n.normalize()));
} else {
dist = Math.abs(diffPoints.dotproduct(cVector.normalize()));
}
return dist;
}
@Override
public void setToImplicit() {
// TODO Auto-generated method stub
}
@Override
public void setToExplicit() {
// TODO Auto-generated method stub
}
@Override
public void setToParametric(String parameter) {
// TODO Auto-generated method stub
}
// ///////////////////////////
// MATRIX TRANSFORMABLE
// ///////////////////////////
@Override
public boolean isMatrixTransformable() {
return true;
}
@Override
public void matrixTransform(double a00, double a01, double a10,
double a11) {
if (tmpMatrix4x4 == null) {
tmpMatrix4x4 = CoordMatrix4x4.Identity();
} else {
tmpMatrix4x4.set(1, 3, 0);
tmpMatrix4x4.set(1, 4, 0);
tmpMatrix4x4.set(2, 3, 0);
tmpMatrix4x4.set(2, 4, 0);
tmpMatrix4x4.set(3, 1, 0);
tmpMatrix4x4.set(3, 2, 0);
tmpMatrix4x4.set(3, 3, 0);
tmpMatrix4x4.set(3, 4, 0);
tmpMatrix4x4.set(4, 1, 0);
tmpMatrix4x4.set(4, 2, 0);
tmpMatrix4x4.set(4, 3, 0);
tmpMatrix4x4.set(4, 4, 1);
}
tmpMatrix4x4.set(1, 1, a00);
tmpMatrix4x4.set(1, 2, a01);
tmpMatrix4x4.set(2, 1, a10);
tmpMatrix4x4.set(2, 2, a11);
setCoord(tmpMatrix4x4.mul(getCoordSys().getOrigin()),
tmpMatrix4x4.mul(getCoordSys().getVx()));
}
private CoordMatrix4x4 tmpMatrix4x4;
@Override
public void matrixTransform(double a00, double a01, double a02, double a10,
double a11, double a12, double a20, double a21, double a22) {
if (tmpMatrix4x4 == null) {
tmpMatrix4x4 = CoordMatrix4x4.Identity();
} else {
tmpMatrix4x4.set(1, 4, 0);
tmpMatrix4x4.set(2, 4, 0);
tmpMatrix4x4.set(3, 4, 0);
tmpMatrix4x4.set(4, 1, 0);
tmpMatrix4x4.set(4, 2, 0);
tmpMatrix4x4.set(4, 3, 0);
tmpMatrix4x4.set(4, 4, 1);
}
tmpMatrix4x4.set(1, 1, a00);
tmpMatrix4x4.set(1, 2, a01);
tmpMatrix4x4.set(1, 3, a02);
tmpMatrix4x4.set(2, 1, a10);
tmpMatrix4x4.set(2, 2, a11);
tmpMatrix4x4.set(2, 3, a12);
tmpMatrix4x4.set(3, 1, a20);
tmpMatrix4x4.set(3, 2, a21);
tmpMatrix4x4.set(3, 3, a22);
setCoord(tmpMatrix4x4.mul(getCoordSys().getOrigin()),
tmpMatrix4x4.mul(getCoordSys().getVx()));
}
// ////////////////
// 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;
}
// //////////////////
// ROTATE
// //////////////////
@Override
public void rotate(NumberValue phiValue) {
Coords o = getCoordSys().getOrigin();
double z = o.getZ();
/*
* if (!Kernel.isZero(z)){ setUndefined(); return; }
*/
Coords v = getCoordSys().getVx();
double vz = v.getZ();
/*
* if (!Kernel.isZero(vz)){ setUndefined(); return; }
*/
double phi = phiValue.getDouble();
double cos = Math.cos(phi);
double sin = Math.sin(phi);
double x = o.getX();
double y = o.getY();
double w = o.getW();
Coords oRot = new Coords(x * cos - y * sin, x * sin + y * cos, z, w);
double vx = v.getX();
double vy = v.getY();
double vw = v.getW();
Coords vRot = new Coords(vx * cos - vy * sin, vx * sin + vy * cos, vz,
vw);
setCoord(oRot, vRot);
}
@Override
final public void rotate(NumberValue phiValue, GeoPointND point) {
Coords o = getCoordSys().getOrigin();
double z = o.getZ();
/*
* if (!Kernel.isZero(z)){ setUndefined(); return; }
*/
Coords v = getCoordSys().getVx();
double vz = v.getZ();
/*
* if (!Kernel.isZero(vz)){ setUndefined(); return; }
*/
double phi = phiValue.getDouble();
double cos = Math.cos(phi);
double sin = Math.sin(phi);
double x = o.getX();
double y = o.getY();
double w = o.getW();
Coords Q = point.getInhomCoords();
double qx = w * Q.getX();
double qy = w * Q.getY();
Coords oRot = new Coords((x - qx) * cos + (qy - y) * sin + qx,
(x - qx) * sin + (y - qy) * cos + qy, z, w);
double vx = v.getX();
double vy = v.getY();
double vw = v.getW();
Coords vRot = new Coords(vx * cos - vy * sin, vx * sin + vy * cos, vz,
vw);
setCoord(oRot, vRot);
}
private Coords tmpCoords1, tmpCoords2;
final private void rotate(NumberValue phiValue, Coords o1, Coords vn) {
if (vn.isZero()) {
setUndefined();
return;
}
Coords vn2 = vn.normalized();
Coords point = getCoordSys().getOrigin();
if (tmpCoords1 == null) {
tmpCoords1 = Coords.createInhomCoorsInD3();
}
point.projectLine(o1, vn, tmpCoords1, null); // point projected on the
// axis
Coords v1 = point.sub(tmpCoords1); // axis->point of the line
Coords v = getCoordSys().getVx(); // direction of the line
double phi = phiValue.getDouble();
double cos = Math.cos(phi);
double sin = Math.sin(phi);
// new line origin
Coords v2 = vn2.crossProduct4(v1);
Coords oRot = tmpCoords1.addInsideMul(v1, cos).addInsideMul(v2, sin);
// new line direction
v2 = vn2.crossProduct4(v);
v1 = v2.crossProduct4(vn2);
Coords vRot = v1.mul(cos).addInsideMul(v2, sin).addInsideMul(vn2,
v.dotproduct(vn2));
setCoord(oRot, vRot);
}
@Override
public void rotate(NumberValue phiValue, GeoPointND S,
GeoDirectionND orientation) {
Coords o1 = S.getInhomCoordsInD3();
Coords vn = orientation.getDirectionInD3();
rotate(phiValue, o1, vn);
}
@Override
public void rotate(NumberValue phiValue, GeoLineND line) {
Coords o1 = line.getStartInhomCoords();
Coords vn = line.getDirectionInD3();
rotate(phiValue, o1, vn);
}
// //////////////////////
// MIRROR
// //////////////////////
@Override
public void mirror(Coords Q) {
Coords o = getCoordSys().getOrigin().mul(-1);
o.addInside(Q.mul(2));
setCoord(o, getCoordSys().getVx().mul(-1));
}
@Override
public void mirror(GeoLineND line) {
Coords o1 = line.getStartInhomCoords();
Coords vn = line.getDirectionInD3();
Coords point = getCoordSys().getOrigin();
if (tmpCoords1 == null) {
tmpCoords1 = Coords.createInhomCoorsInD3();
}
point.projectLine(o1, vn, tmpCoords1, null); // point projected on the
// line
point.mulInside(-1);
point.addInsideMul(tmpCoords1, 2);
double l = vn.getNorm();
Coords v = getCoordSys().getVx();
setCoord(point, vn.copy().mulInside(2 * v.dotproduct(vn) / (l * l))
.addInsideMul(v, -1));
}
@Override
public void mirror(GeoCoordSys2D plane) {
Coords point = getCoordSys().getOrigin();
// point projected on the plane
if (tmpCoords1 == null) {
tmpCoords1 = Coords.createInhomCoorsInD3();
}
point.projectPlane(plane.getCoordSys().getMatrixOrthonormal(),
tmpCoords1);
point.mulInside(-1);
point.addInside(tmpCoords1.mulInside(2));
Coords vn = plane.getDirectionInD3().normalized();
Coords v = getCoordSys().getVx();
if (tmpCoords2 == null) {
tmpCoords2 = new Coords(4);
}
setCoord(point, tmpCoords1.setAdd(v,
tmpCoords2.setMul(vn, -2 * v.dotproduct(vn))));
}
// //////////////////////
// DILATE
// //////////////////////
@Override
public void dilate(NumberValue rval, Coords S) {
double r = rval.getDouble();
Coords o = getCoordSys().getOrigin().mul(r);
o.addInside(S.mul(1 - r));
setCoord(o, getCoordSys().getVx().mul(r));
}
@Override
public void setToUser() {
// TODO Auto-generated method stub
}
@Override
public void setToGeneral() {
// no general line type in 3D
}
}