/* * @author Valentin Simonov / http://va.lent.in/ */ using System.Collections.Generic; using TouchScript.Layers; using TouchScript.Utils.Geom; using TouchScript.Pointers; using UnityEngine; #if TOUCHSCRIPT_DEBUG using System.Collections; using TouchScript.Debugging.GL; #endif namespace TouchScript.Gestures.TransformGestures.Base { /// /// Abstract base classfor two-point transform gestures. /// public abstract class TwoPointTransformGestureBase : TransformGestureBase { #region Constants #endregion #region Events #endregion #region Public properties /// /// Gets or sets minimum distance between 2 points in cm for gesture to begin. /// /// Minimum distance. public virtual float MinScreenPointsDistance { get { return minScreenPointsDistance; } set { minScreenPointsDistance = value; updateMinScreenPointsDistance(); } } #endregion #region Private variables /// /// in pixels for internal use. /// protected float minScreenPointsPixelDistance; /// /// squared in pixels for internal use. /// protected float minScreenPointsPixelDistanceSquared; /// /// Translation buffer. /// protected Vector2 screenPixelTranslationBuffer; /// /// Rotation buffer. /// protected float screenPixelRotationBuffer; /// /// Angle buffer. /// protected float angleBuffer; /// /// Screen space scaling buffer. /// protected float screenPixelScalingBuffer; /// /// Scaling buffer. /// protected float scaleBuffer; [SerializeField] private float minScreenPointsDistance = 0.5f; #endregion #region Unity methods /// protected override void OnEnable() { base.OnEnable(); updateMinScreenPointsDistance(); } #endregion #region Gesture callbacks #if TOUCHSCRIPT_DEBUG /// protected override void pointersPressed(IList pointers) { base.pointersPressed(pointers); if (!(pointersNumState == PointersNumState.PassedMaxThreshold || pointersNumState == PointersNumState.PassedMinMaxThreshold)) drawDebugDelayed(getNumPoints()); } #endif /// protected override void pointersUpdated(IList pointers) { base.pointersUpdated(pointers); var projectionParams = activePointers[0].ProjectionParams; var dP = deltaPosition = Vector3.zero; var dR = deltaRotation = 0; var dS = deltaScale = 1f; #if TOUCHSCRIPT_DEBUG drawDebugDelayed(getNumPoints()); #endif if (pointersNumState != PointersNumState.InRange) return; var translationEnabled = (Type & TransformGesture.TransformType.Translation) == TransformGesture.TransformType.Translation; var rotationEnabled = (Type & TransformGesture.TransformType.Rotation) == TransformGesture.TransformType.Rotation; var scalingEnabled = (Type & TransformGesture.TransformType.Scaling) == TransformGesture.TransformType.Scaling; // one pointer or one cluster (points might be too close to each other for 2 clusters) if (getNumPoints() == 1 || (!rotationEnabled && !scalingEnabled)) { if (!translationEnabled) return; // don't look for translates if (!relevantPointers1(pointers)) return; // translate using one point dP = doOnePointTranslation(getPointPreviousScreenPosition(0), getPointScreenPosition(0), projectionParams); } else { // Make sure that we actually care about the pointers moved. if (!relevantPointers2(pointers)) return; var newScreenPos1 = getPointScreenPosition(0); var newScreenPos2 = getPointScreenPosition(1); // Here we can't reuse last frame screen positions because points 0 and 1 can change. // For example if the first of 3 fingers is lifted off. var oldScreenPos1 = getPointPreviousScreenPosition(0); var oldScreenPos2 = getPointPreviousScreenPosition(1); var newScreenDelta = newScreenPos2 - newScreenPos1; if (newScreenDelta.sqrMagnitude > minScreenPointsPixelDistanceSquared) { if (rotationEnabled) { if (isTransforming) { dR = doRotation(oldScreenPos1, oldScreenPos2, newScreenPos1, newScreenPos2, projectionParams); } else { float d1, d2; // Find how much we moved perpendicular to the line (oldScreenPos1, oldScreenPos2) TwoD.PointToLineDistance2(oldScreenPos1, oldScreenPos2, newScreenPos1, newScreenPos2, out d1, out d2); screenPixelRotationBuffer += (d1 - d2); angleBuffer += doRotation(oldScreenPos1, oldScreenPos2, newScreenPos1, newScreenPos2, projectionParams); if (screenPixelRotationBuffer * screenPixelRotationBuffer >= screenTransformPixelThresholdSquared) { isTransforming = true; dR = angleBuffer; } } } if (scalingEnabled) { if (isTransforming) { dS *= doScaling(oldScreenPos1, oldScreenPos2, newScreenPos1, newScreenPos2, projectionParams); } else { var oldScreenDelta = oldScreenPos2 - oldScreenPos1; var newDistance = newScreenDelta.magnitude; var oldDistance = oldScreenDelta.magnitude; screenPixelScalingBuffer += newDistance - oldDistance; scaleBuffer *= doScaling(oldScreenPos1, oldScreenPos2, newScreenPos1, newScreenPos2, projectionParams); if (screenPixelScalingBuffer * screenPixelScalingBuffer >= screenTransformPixelThresholdSquared) { isTransforming = true; dS = scaleBuffer; } } } if (translationEnabled) { if (dR == 0 && dS == 1) dP = doOnePointTranslation(oldScreenPos1, newScreenPos1, projectionParams); else dP = doTwoPointTranslation(oldScreenPos1, oldScreenPos2, newScreenPos1, newScreenPos2, dR, dS, projectionParams); } } else if (translationEnabled) { // points are too close, translate using one point dP = doOnePointTranslation(oldScreenPos1, newScreenPos1, projectionParams); } } if (dP != Vector3.zero) transformMask |= TransformGesture.TransformType.Translation; if (dR != 0) transformMask |= TransformGesture.TransformType.Rotation; if (dS != 1) transformMask |= TransformGesture.TransformType.Scaling; if (transformMask != 0) { if (State == GestureState.Possible) setState(GestureState.Began); switch (State) { case GestureState.Began: case GestureState.Changed: deltaPosition = dP; deltaRotation = dR; deltaScale = dS; setState(GestureState.Changed); resetValues(); break; } } } /// protected override void reset() { base.reset(); screenPixelTranslationBuffer = Vector2.zero; screenPixelRotationBuffer = 0f; angleBuffer = 0; screenPixelScalingBuffer = 0f; scaleBuffer = 1f; #if TOUCHSCRIPT_DEBUG clearDebug(); #endif } #endregion #region Protected methods /// /// Calculates rotation. /// /// Finger one old screen position. /// Finger two old screen position. /// Finger one new screen position. /// Finger two new screen position. /// Layer projection parameters. /// Angle in degrees. protected virtual float doRotation(Vector2 oldScreenPos1, Vector2 oldScreenPos2, Vector2 newScreenPos1, Vector2 newScreenPos2, ProjectionParams projectionParams) { return 0; } /// /// Calculates scaling. /// /// Finger one old screen position. /// Finger two old screen position. /// Finger one new screen position. /// Finger two new screen position. /// Layer projection parameters. /// Multiplicative delta scaling. protected virtual float doScaling(Vector2 oldScreenPos1, Vector2 oldScreenPos2, Vector2 newScreenPos1, Vector2 newScreenPos2, ProjectionParams projectionParams) { return 1; } /// /// Calculates single finger translation. /// /// Finger old screen position. /// Finger new screen position. /// Layer projection parameters. /// Delta translation vector. protected virtual Vector3 doOnePointTranslation(Vector2 oldScreenPos, Vector2 newScreenPos, ProjectionParams projectionParams) { return Vector3.zero; } /// /// Calculated two finger translation with respect to rotation and scaling. /// /// Finger one old screen position. /// Finger two old screen position. /// Finger one new screen position. /// Finger two new screen position. /// Calculated delta rotation. /// Calculated delta scaling. /// Layer projection parameters. /// Delta translation vector. protected virtual Vector3 doTwoPointTranslation(Vector2 oldScreenPos1, Vector2 oldScreenPos2, Vector2 newScreenPos1, Vector2 newScreenPos2, float dR, float dS, ProjectionParams projectionParams) { return Vector3.zero; } /// /// Gets the number of points. /// /// Number of points. protected virtual int getNumPoints() { return NumPointers; } /// /// Checks if there are pointers in the list which matter for the gesture. /// /// List of pointers. /// true if there are relevant pointers; false otherwise. protected virtual bool relevantPointers1(IList pointers) { // We care only about the first pointer var count = pointers.Count; for (var i = 0; i < count; i++) { if (pointers[i] == activePointers[0]) return true; } return false; } /// /// Checks if there are pointers in the list which matter for the gesture. /// /// List of pointers. /// true if there are relevant pointers; false otherwise. protected virtual bool relevantPointers2(IList pointers) { // We care only about the first and the second pointers var count = pointers.Count; for (var i = 0; i < count; i++) { var pointer = pointers[i]; if (pointer == activePointers[0] || pointer == activePointers[1]) return true; } return false; } /// /// Returns screen position of a point with index 0 or 1 /// /// The index. protected virtual Vector2 getPointScreenPosition(int index) { return activePointers[index].Position; } /// /// Returns previous screen position of a point with index 0 or 1 /// /// The index. protected virtual Vector2 getPointPreviousScreenPosition(int index) { return activePointers[index].PreviousPosition; } #if TOUCHSCRIPT_DEBUG protected virtual void clearDebug() { GLDebug.RemoveFigure(debugID); GLDebug.RemoveFigure(debugID + 1); GLDebug.RemoveFigure(debugID + 2); if (debugCoroutine != null) StopCoroutine(debugCoroutine); debugCoroutine = null; } protected void drawDebugDelayed(int touchPoints) { if (debugCoroutine != null) StopCoroutine(debugCoroutine); debugCoroutine = StartCoroutine(doDrawDebug(touchPoints)); } protected virtual void drawDebug(int touchPoints) { if (!DebugMode) return; var color = State == GestureState.Possible ? Color.red : Color.green; switch (touchPoints) { case 1: GLDebug.DrawSquareScreenSpace(debugID, getPointScreenPosition(0), 0f, debugPointerSize, color, float.PositiveInfinity); GLDebug.RemoveFigure(debugID + 1); GLDebug.RemoveFigure(debugID + 2); break; default: var newScreenPos1 = getPointScreenPosition(0); var newScreenPos2 = getPointScreenPosition(1); GLDebug.DrawSquareScreenSpace(debugID, newScreenPos1, 0f, debugPointerSize, color, float.PositiveInfinity); GLDebug.DrawSquareScreenSpace(debugID + 1, newScreenPos2, 0f, debugPointerSize, color, float.PositiveInfinity); GLDebug.DrawLineWithCrossScreenSpace(debugID + 2, newScreenPos1, newScreenPos2, .5f, debugPointerSize * .3f, color, float.PositiveInfinity); break; } } private IEnumerator doDrawDebug(int touchPoints) { yield return new WaitForEndOfFrame(); drawDebug(touchPoints); } #endif #endregion #region Private functions private void updateMinScreenPointsDistance() { minScreenPointsPixelDistance = minScreenPointsDistance * touchManager.DotsPerCentimeter; minScreenPointsPixelDistanceSquared = minScreenPointsPixelDistance * minScreenPointsPixelDistance; } #endregion } }