package org.geogebra.common.geogebra3D.input3D;
import org.geogebra.common.awt.GPoint;
import org.geogebra.common.awt.GPointWithZ;
import org.geogebra.common.euclidian3D.Input3DConstants;
import org.geogebra.common.euclidian3D.Mouse3DEvent;
import org.geogebra.common.geogebra3D.euclidian3D.EuclidianController3D;
import org.geogebra.common.geogebra3D.euclidian3D.EuclidianView3D;
import org.geogebra.common.kernel.Matrix.CoordMatrix;
import org.geogebra.common.kernel.Matrix.CoordMatrix4x4;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.Matrix.Quaternion;
import org.geogebra.common.main.settings.EuclidianSettings3D;
/**
* class for specific 3D inputs
*
* @author mathieu
*
*/
abstract public class Input3D implements Input3DConstants {
public enum DeviceType {
HAND, PEN
}
public enum OutOfField {
LEFT, RIGHT, FAR, NEAR, BOTTOM, TOP, NO, NEVER, YES
}
private Coords mouse3DScenePosition, mouse3DDirection;
/**
* simple constructor
*/
public Input3D() {
// 3D mouse position on screen (screen coords)
inputPositionOnScreen = new double[2];
// 3D mouse position
mouse3DPosition = new Coords(3);
startMouse3DPosition = new Coords(3);
mouse3DScenePosition = new Coords(4);
mouse3DScenePosition.setW(1);
// 3D mouse direction
if (hasMouseDirection()) {
mouse3DDirection = new Coords(4);
} else {
mouse3DDirection = null;
}
// 3D mouse orientation
mouse3DOrientation = new Quaternion();
startMouse3DOrientation = new Quaternion();
rotV = new Coords(4);
toSceneRotMatrix = new CoordMatrix4x4();
// buttons
wasRightReleased = true;
wasLeftReleased = true;
wasThirdButtonReleased = true;
}
/**
*
* @return device type
*/
abstract public DeviceType getDeviceType();
/**
* Center is center of the screen, unit is pixels
*
* @return input position
*/
abstract public double[] getInputPosition();
// /**
// *
// * @return 2D mouse x position
// */
// public float getMouse2DX();
//
// /**
// *
// * @return 2D mouse y position
// */
// public float getMouse2DY();
//
// /**
// *
// * @return 2D mouse factor
// */
// public float getMouse2DFactor();
/**
*
* @return input orientation (as quaternion)
*/
abstract public double[] getInputOrientation();
/**
* Center is center of the screen, unit is pixels
*
* @return glasses position (two eyes center)
*/
abstract public double[] getGlassesPosition(int i);
/**
*
* @return eye separation
*/
abstract public double getEyeSeparation();
/**
*
* @return true if right button is pressed
*/
abstract public boolean isRightPressed();
/**
*
* @return true if left button is pressed
*/
abstract public boolean isLeftPressed();
/**
*
* @return true if third button is pressed
*/
abstract public boolean isThirdButtonPressed();
/**
*
* @return true if one button is pressed
*/
abstract public boolean isButtonPressed();
/**
*
* @return true if the input use depth for hitting
*/
abstract public boolean useInputDepthForHitting();
/**
*
* @return true if the input use a robot to controll 2D mouse
*/
abstract public boolean useMouseRobot();
/**
*
* @param view3D
* 3D view
* @param mouse3DPosition
* current 3D mouse position
* @return true if input3D has mouse on 3D view
*/
abstract public boolean hasMouse(EuclidianView3D view3D,
Coords mouse3DPosition);
/**
*
* @param view3D
* 3D view
* @return true if input3D has mouse on 3D view
*/
abstract public boolean hasMouse(EuclidianView3D view3D);
/**
*
* @return true if 3D input is currently (possibly) using 2D mouse
*/
abstract public boolean currentlyUseMouse2D();
/**
* set left button is pressed
*
* @param flag
* flag
*/
abstract public void setHasCompletedGrabbingDelay(boolean flag);
/**
*
* @return if hand input has completed grabbing delay
*/
abstract public boolean hasCompletedGrabbingDelay();
/**
* calc position of 3D mouse on 3D view
*
* @param absolutePos
* position from input
* @param panelPos
* position for panel
* @param screenHalfWidth
* screen half width
* @param screenHalfHeight
* screen half height
* @param panelPositionX
* panel x position on screen
* @param panelPositionY
* panel y position on screen
* @param panelDimW
* panel width
* @param panelDimH
* panel height
*/
abstract public void setPositionXYOnPanel(double[] absolutePos,
Coords panelPos, double screenHalfWidth, double screenHalfHeight,
int panelPositionX, int panelPositionY, int panelDimW,
int panelDimH);
/**
*
* @return true if we use z offset to make the scene out of the screen
*/
abstract public boolean useScreenZOffset();
/**
*
* @return true if it uses stereo buffers
*/
abstract public boolean isStereoBuffered();
/**
*
* @return true if uses polarized monitor with interlaced lines
*/
abstract public boolean useInterlacedPolarization();
/**
*
* @return true if using completing delay (e.g. for hand tracking -- no
* button)
*/
abstract public boolean useCompletingDelay();
/**
*
* @return true if the input has direction for mouse
*/
abstract public boolean hasMouseDirection();
/**
*
* @return input direction
*/
abstract public double[] getInputDirection();
/**
* update values
*
* @param panelPosition
* TODO
* @param panelDimension
* TODO
*
* @return true if the update worked
*/
abstract public boolean update();
/**
*
* @return true if input uses quaternions for rotate
*/
abstract public boolean useQuaternionsForRotate();
/**
* @return true if stereo glasses are detected
*
*/
abstract public boolean wantsStereo();
/**
*
* @return default rotation angle for Oz
*/
abstract public double getDefaultRotationOz();
/**
*
* @return default rotation angle for xOy
*/
abstract public double getDefaultRotationXOY();
/**
* Inputs tracking head don't need to store eye position
*
* @return true if we need to store stereo infos
*/
abstract public boolean shouldStoreStereoToXML();
/**
*
* @return true if this input needs gray background to minimize ghost effect
*/
abstract public boolean needsGrayBackground();
/**
*
* @return true if this input uses head tracking
*/
abstract public boolean useHeadTracking();
/**
*
* @return true if input uses hand grabbing
*/
abstract public boolean useHandGrabbing();
/**
*
* @return out of field type
*/
abstract public OutOfField getOutOfField();
/**
* exit
*/
abstract public void exit();
/**
* says that 3D input position leads to mouse cursor on physical screen
*/
abstract public void setPositionOnScreen();
/**
* says that 3D input position leads to mouse cursor off physical screen
*/
abstract public void setPositionOffScreen();
/**
*
* @return true if zSpace
*/
abstract public boolean isZSpace();
/**
*
* @param settings
* TODO
*/
abstract public void setSpecificSettings(EuclidianSettings3D settings);
protected Coords[] glassesPosition;
protected double screenHalfWidth, screenHalfHeight;
protected int panelWidth, panelHeight, panelX, panelY;
protected EuclidianView3D view3D;
public void init(EuclidianView3D view3D) {
this.view3D = view3D;
// glasses position
glassesPosition = new Coords[2];
for (int i = 0; i < 2; i++) {
glassesPosition[i] = new Coords(3);
}
}
public void setScreenHalfDimensions(double halfWidth, double halfHeight) {
screenHalfWidth = halfWidth;
screenHalfHeight = halfHeight;
}
public double getScreenHalfWidth() {
return screenHalfWidth;
}
public double getScreenHalfHeight() {
return screenHalfHeight;
}
public void setPanel(int width, int height, int x, int y) {
panelWidth = width;
panelHeight = height;
panelX = x;
panelY = y;
}
public int getPanelWidth() {
return panelWidth;
}
public int getPanelHeight() {
return panelHeight;
}
public int getPanelX() {
return panelX;
}
public int getPanelY() {
return panelY;
}
protected void setGlassesPosition() {
for (int i = 0; i < 2; i++) {
double[] pos = getGlassesPosition(i);
setPositionXYOnPanel(pos, glassesPosition[i]);
glassesPosition[i].setZ(pos[2]);
}
}
public void updateHeadTracking() {
// eyes : set position only if we use glasses
if (useHeadTracking() && view3D
.getProjection() == EuclidianView3D.PROJECTION_GLASSES) {
// set glasses position from tracker data
setGlassesPosition();
// Log.debug("\n"+glassesPosition);
// Log.debug(input3D.getGlassesPosition()[2]+"");
// if (eyeSepIsNotSet){
view3D.setEyes(glassesPosition[0].getX(), glassesPosition[0].getY(),
glassesPosition[1].getX(), glassesPosition[1].getY());
// eyeSepIsNotSet = false;
// }
view3D.setProjectionPerspectiveEyeDistance(
glassesPosition[0].getZ(), glassesPosition[1].getZ());
}
}
public void setPositionXYOnPanel(double[] absolutePos, Coords panelPos) {
setPositionXYOnPanel(absolutePos, panelPos, screenHalfWidth,
screenHalfHeight, panelX, panelY, panelWidth, panelHeight);
}
private double[] inputPositionOnScreen;
protected int onScreenX, onScreenY;
public void updateOnScreenPosition() {
if (hasMouseDirection()) { // project position on
// screen
double dz = getInputDirection()[2];
if (dz < 0) {
double t = -getInputPosition()[2] / dz;
inputPositionOnScreen[0] = getInputPosition()[0]
+ t * getInputDirection()[0];
inputPositionOnScreen[1] = getInputPosition()[1]
+ t * getInputDirection()[1];
}
} else {
inputPositionOnScreen[0] = getInputPosition()[0];
inputPositionOnScreen[1] = getInputPosition()[1];
}
// init to center panel
onScreenX = getPanelX() + getPanelWidth() / 2;
onScreenY = getPanelY() + getPanelHeight() / 2;
// check if pointer is on screen
int x1 = onScreenX + (int) (inputPositionOnScreen[0]);
int y1 = onScreenY - (int) (inputPositionOnScreen[1]);
if (x1 >= 0 && x1 <= getScreenHalfWidth() * 2 && y1 >= 0
&& y1 <= getScreenHalfHeight() * 2) {
onScreenX = x1;
onScreenY = y1;
setPositionOnScreen();
// Log.debug("onS: " + x1 + "," + y1);
} else {
setPositionOffScreen();
// Log.debug("NOT onS: " + x1 + "," + y1);
}
}
public int getOnScreenX() {
return onScreenX;
}
public int getOnScreenY() {
return onScreenY;
}
private Coords mouse3DPosition;
public Coords getMouse3DPosition() {
return mouse3DPosition;
}
public void updateMousePosition() {
setPositionXYOnPanel(getInputPosition(), mouse3DPosition);
mouse3DPosition.setZ(getInputPosition()[2] - view3D.getScreenZOffset());
}
public boolean hasMouse() {
return hasMouse(view3D, mouse3DPosition);
}
private boolean wasRightReleased;
private boolean wasLeftReleased;
private boolean wasThirdButtonReleased;
private Coords tmpCoords = new Coords(3), tmpCoords2 = new Coords(3),
tmpCoords3 = new Coords(3);
private Coords startMouse3DPosition;
private void processThirdButtonPress() {
if (wasThirdButtonReleased) {
setMouse3DPositionShifted(startMouse3DPosition);
view3D.rememberOrigins();
} else {
getShiftForMouse3D(tmpCoords);
tmpCoords.setAdd3(tmpCoords, mouse3DPosition);
tmpCoords.setSub(tmpCoords, startMouse3DPosition);
tmpCoords2.setMul(view3D.getToSceneMatrix(), tmpCoords.val);
view3D.setCoordSystemFromMouse3DMove(tmpCoords2);
}
}
/**
* get shift in beam direction about 150px
*
* @param ret
* result
*/
private void getShiftForMouse3D(Coords ret) {
ret.set(getInputDirection());
ret.mulInside(150);
}
private void setMouse3DPositionShifted(Coords ret) {
getShiftForMouse3D(tmpCoords2);
tmpCoords2.setAdd(mouse3DPosition, tmpCoords2);
ret.set(tmpCoords2);
}
/**
* get mouse 3D position for translate view
*
* @param ret
* coords set
*/
public void getMouse3DPositionShifted(Coords ret) {
getShiftForMouse3D(tmpCoords);
tmpCoords.setAdd3(tmpCoords, mouse3DPosition);
ret.setMul(view3D.getToSceneMatrix(), tmpCoords.val);
ret.setW(0.0);
ret.addInside(view3D.getToSceneMatrix().getOrigin());
}
private void processRightPress() {
if (wasRightReleased) { // process first press : remember mouse start
if (useQuaternionsForRotate()) {
startRightPressQuaternions();
} else {
startRightPress();
}
} else { // process mouse drag
if (useQuaternionsForRotate()) {
processRightDragQuaternions();
} else {
processRightDrag();
}
}
}
private Coords vx, vz;
private void startRightPressQuaternions() {
startMouse3DPosition.set(mouse3DPosition);
view3D.rememberOrigins();
view3D.setStartPos(startMouse3DPosition);
storeOrientation();
// to-the-right screen vector in scene coords
vx = toSceneRotMatrix.mul(Coords.VX);
}
private double angleOld;
private EuclidianController3D getEuclidianController() {
return (EuclidianController3D) view3D.getEuclidianController();
}
private void startRightPress() {
// automatic rotation
if (view3D.isRotAnimated()) {
view3D.stopAnimation();
getEuclidianController().setViewRotationOccured(true);
}
getEuclidianController()
.setTimeOld(view3D.getApplication().getMillisecondTime());
getEuclidianController().setAnimatedRotSpeed(0);
angleOld = 0;
// start values
startMouse3DPosition.set(mouse3DPosition);
view3D.rememberOrigins();
vz = view3D.getRotationMatrix().getVz();
}
private Coords rightDragElevation = new Coords(3);
private void processRightDrag() {
tmpCoords.setValues(mouse3DPosition, 3);
rightDragElevation.setMul(vz, tmpCoords.dotproduct(vz));
tmpCoords2.setSub(tmpCoords, rightDragElevation);
tmpCoords.setMul(vz, startMouse3DPosition.dotproduct(vz));
tmpCoords.setSub(startMouse3DPosition, tmpCoords);
tmpCoords3.setCrossProduct(tmpCoords, tmpCoords2);
double c = tmpCoords.dotproduct(tmpCoords2);
double s = tmpCoords3.calcNorm();
double angle = Math.atan2(s, c) * 180 / Math.PI;
if (tmpCoords3.dotproduct(vz) > 0) {
angle *= -1;
}
view3D.shiftRotAboutZ(angle);
double time = view3D.getApplication().getMillisecondTime();
getEuclidianController().setAnimatedRotSpeed((angleOld - angle)
/ (time - getEuclidianController().getTimeOld()));
((EuclidianController3D) view3D.getEuclidianController())
.setTimeOld(time);
angleOld = angle;
}
private void processRightDragQuaternions() {
// rotation
calcCurrentRot();
CoordMatrix rotMatrix = getCurrentRotMatrix();
// Log.debug("\n"+rot);
// rotate view vZ
Coords vZrot = rotMatrix.getVz();
// Log.debug("\n"+vZrot);
Coords vZ1 = (vZrot.sub(vx.mul(vZrot.dotproduct(vx)))).normalize(); // project
// the
// rotation
// to
// keep
// vector
// plane
// orthogonal
// to
// the
// screen
Coords vZp = Coords.VZ.crossProduct(vZ1); // to get angle (vZ,vZ1)
// rotate screen vx
Coords vxRot = rotMatrix.mul(vx);
Coords vx1 = (vxRot.sub(vZ1.mul(vxRot.dotproduct(vZ1)))).normalize(); // project
// in
// plane
// orthogonal
// to
// vZ1
Coords vxp = vx.crossProduct(vx1); // to get angle (vx,vx1)
// rotation around x (screen)
double rotX = Math.asin(vxp.norm()) * 180 / Math.PI;
// Log.debug("rotX="+rotX+", vx1.dotproduct(vx) =
// "+vx1.dotproduct(vx)+", vxp.dotproduct(vZ1) = "+vxp.dotproduct(vZ1));
if (vx1.dotproduct(vx) < 0) { // check if rotX should be > 90degrees
rotX = 180 - rotX;
}
if (vxp.dotproduct(vZ1) > 0) { // check if rotX should be negative
rotX = -rotX;
}
// rotation around z (scene)
double rotZ = Math.asin(vZp.norm()) * 180 / Math.PI;
// Log.debug("rotZ="+rotZ+", vZp.dotproduct(vx) =
// "+vZp.dotproduct(vx)+", Coords.VZ.dotproduct(vZ1) = "+vZ1.getZ());
if (vZ1.getZ() < 0) { // check if rotZ should be > 90degrees
rotZ = 180 - rotZ;
}
if (vZp.dotproduct(vx) < 0) { // check if rotZ should be negative
rotZ = -rotZ;
}
// Log.debug("rotZ="+rotZ);
// set the view
view3D.setCoordSystemFromMouse3DMove(startMouse3DPosition,
mouse3DPosition, rotX, rotZ);
/*
* // USE FOR CHECK 3D MOUSE ORIENTATION // use file
* leonar3do-rotation2.ggb GeoVector3D geovx = (GeoVector3D)
* getKernel().lookupLabel("vx");
* geovx.setCoords(toSceneRotMatrix.mul(Coords.VX).normalize());
* geovx.updateCascade(); GeoVector3D vy = (GeoVector3D)
* getKernel().lookupLabel("vy");
* vy.setCoords(toSceneRotMatrix.mul(Coords.VY).normalize());
* vy.updateCascade(); GeoVector3D vz = (GeoVector3D)
* getKernel().lookupLabel("vz");
* vz.setCoords(toSceneRotMatrix.mul(Coords.VZ).normalize());
* vz.updateCascade();
*
*
* GeoAngle a = (GeoAngle) getKernel().lookupLabel("angle"); GeoVector3D
* v = (GeoVector3D) getKernel().lookupLabel("v");
* a.setValue(2*Math.acos(rot.getScalar()));
* v.setCoords(rot.getVector()); a.updateCascade(); v.updateCascade();
*
* GeoText text = (GeoText) getKernel().lookupLabel("text");
* text.setTextString ("az = "+rotZ+"degrees\n"+"ax = "
* +rotX+"degrees\n"+ "vxp.dotproduct(vZ1)="
* +vxp.dotproduct(vZ1)+"\nvx1.dotproduct(vx)="+vx1.dotproduct(vx) +
* "\nvZp.dotproduct(vx) = "+vZp.dotproduct(vx) ); text.update();
* getKernel().notifyRepaint();
*/
}
private Quaternion mouse3DOrientation, startMouse3DOrientation;
private Coords rotV;
private CoordMatrix startOrientationMatrix;
private CoordMatrix4x4 toSceneRotMatrix;
private void storeOrientation() {
startMouse3DOrientation.set(mouse3DOrientation);
startOrientationMatrix = startMouse3DOrientation.getRotMatrix();
toSceneRotMatrix.set(view3D.getUndoRotationMatrix());
}
private Quaternion currentRot;
/**
* calc current rotation
*/
public void calcCurrentRot() {
currentRot = startMouse3DOrientation.leftDivide(mouse3DOrientation);
// get the relative quaternion and rotation matrix in scene coords
rotV.set(startOrientationMatrix.mul(currentRot.getVector()));
currentRot.setVector(toSceneRotMatrix.mul(rotV));
}
/**
*
* @return current/start rotation as a matrix
*/
public CoordMatrix getCurrentRotMatrix() {
return currentRot.getRotMatrix();
}
/**
*
* @return current rotation quaternion
*/
protected Quaternion getCurrentRotQuaternion() {
return currentRot;
}
public void handleButtons() {
if (isThirdButtonPressed()) { // process 3rd
// button
processThirdButtonPress();
wasThirdButtonReleased = false;
wasRightReleased = true;
wasLeftReleased = true;
} else if (isRightPressed()) { // process right
// press
processRightPress();
wasRightReleased = false;
wasLeftReleased = true;
wasThirdButtonReleased = true;
} else if (isLeftPressed()) { // process left
// press
if (wasLeftReleased) {
startMouse3DPosition.set(mouse3DPosition);
storeOrientation();
view3D.getEuclidianController().wrapMousePressed(mouse3DEvent);
} else {
// no capture in desktop
view3D.getEuclidianController().wrapMouseDragged(mouse3DEvent,
false);
}
wasRightReleased = true;
wasLeftReleased = false;
wasThirdButtonReleased = true;
} else if (hasCompletedGrabbingDelay()) { // use
// hand
// dragging
if (wasLeftReleased) {
startMouse3DPosition.set(mouse3DPosition);
storeOrientation();
} else {
// no capture in desktop
view3D.getEuclidianController().wrapMouseDragged(mouse3DEvent,
false);
}
wasRightReleased = true;
wasLeftReleased = false;
wasThirdButtonReleased = true;
} else {
// process button release
if (!wasRightReleased || !wasLeftReleased
|| !wasThirdButtonReleased) {
view3D.getEuclidianController().wrapMouseReleased(mouse3DEvent);
}
// process move
view3D.getEuclidianController().wrapMouseMoved(mouse3DEvent);
wasRightReleased = true;
wasLeftReleased = true;
wasThirdButtonReleased = true;
}
}
/**
*
* @return elevation for cursor when right-drag
*/
public Coords getRightDragElevation() {
return rightDragElevation;
}
private GPointWithZ mouse3DLoc = new GPointWithZ();
private Mouse3DEvent mouse3DEvent;
final public void updateMouse3DEvent() {
mouse3DLoc = new GPointWithZ(
getPanelWidth() / 2 + (int) mouse3DPosition.getX(),
getPanelHeight() / 2 - (int) mouse3DPosition.getY(),
(int) mouse3DPosition.getZ());
mouse3DEvent = view3D.createMouse3DEvent(mouse3DLoc);
// mouse direction
if (hasMouseDirection()) {
mouse3DDirection.setMul(view3D.getUndoRotationMatrix(),
getInputDirection());
mouse3DScenePosition.set(getMouse3DPosition());
view3D.toSceneCoords3D(mouse3DScenePosition);
view3D.getCompanion().updateStylusBeamForMovedGeo();
}
// mouse orientation
mouse3DOrientation.set(getInputOrientation());
// Log.debug("\nstart: "+startMouse3DOrientation+"\ncurrent:
// "+mouse3DOrientation);
}
public GPoint getMouseLoc() {
return mouse3DLoc;
}
public boolean wasRightReleased() {
return wasRightReleased;
}
public void setWasRightReleased(boolean flag) {
wasRightReleased = flag;
}
public boolean wasLeftReleased() {
return wasLeftReleased;
}
public void setWasLeftReleased(boolean flag) {
wasLeftReleased = flag;
}
/**
*
* @return 3D mouse position (scene coords)
*/
public Coords getMouse3DScenePosition() {
return mouse3DScenePosition;
}
/**
*
* @return 3D mouse direction
*/
public Coords getMouse3DDirection() {
return mouse3DDirection;
}
public Coords getStartMouse3DPosition() {
return startMouse3DPosition;
}
}