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; } }