package org.geogebra.common.geogebra3D.input3D;
import java.util.ArrayList;
import java.util.TreeSet;
import org.geogebra.common.awt.GPoint;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.euclidian.EuclidianController;
import org.geogebra.common.euclidian.Hits;
import org.geogebra.common.euclidian.draw.DrawPoint;
import org.geogebra.common.euclidian.event.AbstractEvent;
import org.geogebra.common.euclidian.event.PointerEventType;
import org.geogebra.common.geogebra3D.euclidian3D.EuclidianController3D;
import org.geogebra.common.geogebra3D.euclidian3D.EuclidianController3DCompanion;
import org.geogebra.common.geogebra3D.euclidian3D.EuclidianView3D;
import org.geogebra.common.geogebra3D.input3D.EuclidianViewInput3DCompanion.StationaryCoords;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPlane3D;
import org.geogebra.common.geogebra3D.kernel3D.geos.GeoPoint3D;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.Matrix.CoordMatrix4x4;
import org.geogebra.common.kernel.Matrix.CoordSys;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.plugin.EuclidianStyleConstants;
/**
* Euclidian controller creator for 3D controller with 3D input
*
* @author mathieu
*
*/
public class EuclidianControllerInput3DCompanion extends
EuclidianController3DCompanion {
static final private int DISTANCE_THRESHOLD = 6;
static final private double COS_THRESHOLD = Math.sin(Math.PI * 7.5 / 180);
private Input3D input3D;
/**
* constructor
*
* @param ec
* controller
*/
public EuclidianControllerInput3DCompanion(EuclidianController ec) {
super(ec);
}
public void setInput3D(Input3D input3D) {
this.input3D = input3D;
}
private EuclidianViewInput3DCompanion getViewCompanion() {
return (EuclidianViewInput3DCompanion) ec.getView().getCompanion();
}
@Override
protected GeoPoint3D createNewFreePoint(boolean complex) {
if (input3D.currentlyUseMouse2D()) {
return super.createNewFreePoint(complex);
}
GeoPoint3D point3D = ((EuclidianView3D) ec.getView()).getCursor3D();
point3D.setPath(null);
point3D.setRegion(null);
Coords coords;
if (input3D.hasMouseDirection()) {
coords = Coords.createInhomCoorsInD3();
double beamLength;
if (ec.getMode() == EuclidianConstants.MODE_MOVE) {
beamLength = 1000;
} else {
beamLength = 400;
}
beamLength /= ((EuclidianView3D) ec.getView()).getScale();
getViewCompanion()
.getStylusBeamEnd(coords,
beamLength);
getView().setZNearest(-beamLength);
} else {
coords = ((EuclidianView3D) ec.getView()).getPickPoint(
ec.getMouseLoc())
.copyVector();
((EuclidianView3D) ec.getView()).toSceneCoords3D(coords);
}
checkPointCapturingXYThenZ(coords);
point3D.setCoords(coords);
return point3D;
}
@Override
public void movePoint(boolean repaint, AbstractEvent event) {
if (input3D.currentlyUseMouse2D() || (input3D
.hasMouseDirection() && !ec.movedGeoPoint
.isIndependent())) {
super.movePoint(repaint, event);
} else {
Coords v = new Coords(4);
if (input3D.hasMouseDirection()) {
getViewCompanion().getStylusBeamEnd(v, startZNearest);
v.setSub(v, movedGeoPointStartCoords);
} else {
v.set(input3D.getMouse3DPosition()
.sub(input3D.getStartMouse3DPosition()));
((EuclidianView3D) ec.getView()).toSceneCoords3D(v);
}
Coords coords = movedGeoPointStartCoords.add(v);
checkPointCapturingXYThenZ(coords);
ec.movedGeoPoint.setCoords(coords, true);
ec.movedGeoPoint.updateCascade();
if (ec.getMoveMode() == EuclidianController.MOVE_POINT
&& ec.movedGeoPoint.isGeoElement3D()
&& !ec.movedGeoPoint.isPointOnPath()
&& !ec.movedGeoPoint.hasRegion()) {
// update point decorations
((EuclidianView3D) ec.getView()).updatePointDecorations();
}
if (input3D.hasCompletedGrabbingDelay()) {
long time = System.currentTimeMillis();
StationaryCoords stationaryCoords = getViewCompanion()
.getStationaryCoords();
stationaryCoords.setCoords(
ec.movedGeoPoint.getInhomCoordsInD3(), time);
if (stationaryCoords.hasLongDelay(time)) {
releaseGrabbing();
}
}
}
}
private static class StickyPoint implements Comparable<StickyPoint> {
public GeoPointND point;
public double distance;
public StickyPoint(GeoPointND point, double distance) {
this.point = point;
this.distance = distance;
}
public double getDistanceAbs() {
return Math.abs(distance);
}
@Override
public int compareTo(StickyPoint sp) {
// check distance
if (Kernel.isGreater(sp.getDistanceAbs(), this.getDistanceAbs())) {
return -1;
}
if (Kernel.isGreater(this.getDistanceAbs(), sp.getDistanceAbs())) {
return 1;
}
// check construction index
if (this.point.getConstructionIndex() < sp.point
.getConstructionIndex()) {
return -1;
}
if (this.point.getConstructionIndex() > sp.point
.getConstructionIndex()) {
return 1;
}
return 0;
}
@Override
public boolean equals(Object sp) {
if (sp instanceof StickyPoint) {
return compareTo((StickyPoint) sp) == 0;
}
return false;
}
@Override
public int hashCode() {
return Double.hashCode(distance) ^ point.hashCode();
}
}
private static class StickyPointForDirection
implements
Comparable<StickyPointForDirection> {
public StickyPoint sp;
public double distanceOrtho;
public double distanceOrigin;
public StickyPointForDirection(StickyPoint origin, StickyPoint sp,
double distanceOrigin) {
this.sp = sp;
this.distanceOrtho = sp.distance - origin.distance;
this.distanceOrigin = distanceOrigin;
}
public double getCosAbs() {
return Math.abs(distanceOrtho / distanceOrigin);
}
@Override
public int compareTo(StickyPointForDirection spd) {
// compare cosinus
if (Kernel.isGreater(Math.abs(spd.distanceOrtho * distanceOrigin),
Math.abs(distanceOrtho * spd.distanceOrigin))) {
return -1;
}
if (Kernel.isGreater(Math.abs(distanceOrtho * spd.distanceOrigin), Math
.abs(spd.distanceOrtho * distanceOrigin))) {
return 1;
}
// check construction index
if (this.sp.point.getConstructionIndex() < spd.sp.point
.getConstructionIndex()) {
return -1;
}
if (this.sp.point.getConstructionIndex() > spd.sp.point
.getConstructionIndex()) {
return 1;
}
return 0;
}
@Override
public boolean equals(Object spd) {
if (spd instanceof StickyPointForDirection) {
return compareTo((StickyPointForDirection) spd) == 0;
}
return false;
}
@Override
public int hashCode() {
return Double.hashCode(distanceOrtho)
^ Double.hashCode(distanceOrigin) ^ sp.hashCode();
}
}
private TreeSet<StickyPoint> stickyPoints;
private TreeSet<StickyPointForDirection> stickyPointsForDirection;
private boolean stickToPoints() {
return ec.getView().getPointCapturingMode() == EuclidianStyleConstants.POINT_CAPTURING_AUTOMATIC;
}
@Override
protected void movePlane(boolean repaint, AbstractEvent event) {
if (input3D.currentlyUseMouse2D()) {
super.movePlane(repaint, event);
} else {
Coords v = new Coords(4);
if (input3D.hasMouseDirection() && !input3D
.currentlyUseMouse2D()) {
getViewCompanion().getStylusBeamEnd(v, startZNearest);
v.setSub(v, movedGeoPointStartCoords);
} else {
v.set(input3D.getMouse3DPosition()
.sub(input3D.getStartMouse3DPosition()));
((EuclidianView3D) ec.getView()).toSceneCoords3D(v);
}
final GeoPlane3D plane = movedGeoPlane;
plane.setCoordSys(movedGeoPlaneStartCoordSys);
input3D.calcCurrentRot();
plane.rotate(input3D.getCurrentRotMatrix(),
movedGeoPointStartCoords);
plane.translate(v);
if (stickToPoints()) {
// check sticky points
if (stickyPoints == null) {
stickyPoints = new TreeSet<StickyPoint>();
} else {
stickyPoints.clear();
}
for (GeoPointND point : stickyPointsList) {
StickyPoint sp = new StickyPoint(point,
plane.distanceWithSign(point));
stickyPoints.add(sp);
}
double scale = ((EuclidianView3D) ec.getView()).getScale();
Coords origin = null, secondPoint = null, thirdPoint = null;
int step = 0;
if (!stickyPoints.isEmpty()) {
StickyPoint sp = stickyPoints.pollFirst();
if (checkDistanceToStickyPoint(sp.getDistanceAbs(), scale,
DISTANCE_THRESHOLD)) {
origin = sp.point.getInhomCoordsInD3();
step++;
// Log.debug("============== " + sp.point);
// check directions
if (!stickyPoints.isEmpty()) {
if (stickyPointsForDirection == null) {
stickyPointsForDirection = new TreeSet<StickyPointForDirection>();
} else {
stickyPointsForDirection.clear();
}
for (StickyPoint sp2 : stickyPoints) {
double distanceOrigin = sp2.point
.distance(sp.point);
// prevent same points
if (!Kernel.isZero(distanceOrigin)) {
stickyPointsForDirection
.add(new StickyPointForDirection(
sp, sp2, distanceOrigin));
}
}
if (!stickyPointsForDirection.isEmpty()) {
StickyPointForDirection spd2 = stickyPointsForDirection
.pollFirst();
// Log.debug("spd2 : " + spd2.getCosAbs());
if (spd2.getCosAbs() < COS_THRESHOLD) {
secondPoint = spd2.sp.point
.getInhomCoordsInD3();
step++;
if (!stickyPointsForDirection.isEmpty()) {
StickyPointForDirection spd3 = stickyPointsForDirection
.pollFirst();
// Log.debug("spd3 : " +
// spd3.getCosAbs());
if (spd3.getCosAbs() < COS_THRESHOLD) {
thirdPoint = spd3.sp.point
.getInhomCoordsInD3();
step++;
}
}
}
}
}
} else {
step = -1;
// Log.error("TOO FAR (first point)");
}
switch (step) {
default:
// do nothing
break;
case 1: // only origin
plane.getCoordSys().updateToContainPoint(origin);
break;
case 2: // origin and second point
plane.getCoordSys().updateContinuousPointVx(origin,
secondPoint.sub(origin));
break;
case 3: // origin and two points
CoordSys cs = new CoordSys(2);
cs.addPoint(origin);
cs.addPoint(secondPoint);
cs.addPoint(thirdPoint);
if (cs.isMadeCoordSys()) {
cs.makeOrthoMatrix(false, false);
cs.makeEquationVector();
plane.getCoordSys().updateContinuous(cs);
} else {
plane.getCoordSys().updateContinuousPointVx(origin,
secondPoint.sub(origin));
}
break;
}
}
}
// update
plane.setDefinition(null);
plane.updateCascade();
if (input3D.hasCompletedGrabbingDelay()) {
long time = System.currentTimeMillis();
StationaryCoords stationaryCoords = getViewCompanion()
.getStationaryCoords();
stationaryCoords.setCoords(movedGeoPointStartCoords, v, time);
if (stationaryCoords.hasLongDelay(time)) {
releaseGrabbing();
}
}
}
}
/**
* release current grabbing
*/
public void releaseGrabbing() {
input3D.setHasCompletedGrabbingDelay(false);
ec.getApplication().getSelectionManager().clearSelectedGeos(true);
ec.endOfWrapMouseReleased(new Hits(), false, false,
PointerEventType.TOUCH);
}
private static boolean checkDistanceToStickyPoint(double d, double scale,
int threshold) {
return d * scale < DrawPoint.getSelectionThreshold(threshold);// point.getPointSize()
// +
// threshold;
}
@Override
protected boolean specificPointCapturingAutomatic() {
return ((EuclidianController3D) ec).isZSpace()
&& !input3D.currentlyUseMouse2D();
}
private double startZNearest;
protected Coords movedGeoPointStartCoords = new Coords(0, 0, 0, 1);
@Override
protected void updateMovedGeoPointStartValues(Coords coords,
GeoPointND movedGeoPoint, CoordMatrix4x4 currentPlane) {
if (input3D.currentlyUseMouse2D()) {
super.updateMovedGeoPointStartValues(coords, movedGeoPoint,
currentPlane);
} else {
movedGeoPointStartCoords.set(coords);
if (input3D.hasMouseDirection()) {
startZNearest = getViewCompanion().getZNearest();
}
}
}
protected GeoPlane3D movedGeoPlane;
protected CoordSys movedGeoPlaneStartCoordSys;
private Coords movedGeoStartPosition;
protected ArrayList<GeoPointND> stickyPointsList;
/**
* set plane to move
*
* @param geo
* moved geo
*/
public void setMovedGeoPlane(GeoElement geo) {
movedGeoPlane = (GeoPlane3D) geo;
if (movedGeoPlaneStartCoordSys == null) {
movedGeoPlaneStartCoordSys = new CoordSys(2);
}
movedGeoPlaneStartCoordSys.set(movedGeoPlane.getCoordSys());
if (movedGeoStartPosition == null) {
movedGeoStartPosition = new Coords(4);
}
movedGeoStartPosition.set(input3D.getMouse3DPosition());
((EuclidianController3D) ec).updateMovedGeoPointStartValues(
getView().getCursor3D().getInhomCoordsInD(3));
getView().setDragCursor();
// set sticky points
if (stickyPointsList == null) {
stickyPointsList = new ArrayList<GeoPointND>();
} else {
stickyPointsList.clear();
}
for (GeoElement geo1 : geo.getConstruction()
.getGeoSetConstructionOrder()) {
if (geo1.isGeoPoint() && geo1.isVisibleInView3D()
&& !geo1.isChildOf(geo)) {
stickyPointsList.add((GeoPointND) geo1);
}
}
}
@Override
final protected boolean handleMovedElementFreePlane(
GeoElement movedGeoElement) {
if (movedGeoElement.isGeoPlane()) {
setMovedGeoPlane(movedGeoElement);
return true;
}
return false;
}
@Override
public void setMouseLocation(AbstractEvent event) {
if (input3D.currentlyUseMouse2D()) {
super.setMouseLocation(event);
} else {
ec.mouseLoc = event.getPoint();
}
}
@Override
protected void setMouseOrigin(GeoPoint3D point, GPoint mouseLoc) {
if (input3D.hasMouseDirection()
&& !input3D.currentlyUseMouse2D()) {
point.setWillingCoords(input3D.getMouse3DScenePosition());
} else {
super.setMouseOrigin(point, mouseLoc);
}
}
@Override
public double getPointCapturingPercentage() {
return 2 * super.getPointCapturingPercentage();
}
}