/*
* @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
}
}