diff --git a/Editor/InputSources/TuioInputEditor.cs b/Editor/InputSources/TuioInputEditor.cs index 22ec6e222..727f9d44f 100644 --- a/Editor/InputSources/TuioInputEditor.cs +++ b/Editor/InputSources/TuioInputEditor.cs @@ -15,6 +15,7 @@ internal sealed class TuioInputEditor : InputSourceEditor private SerializedProperty _connectionType; private SerializedProperty _port; private SerializedProperty _ipAddress; + private SerializedProperty _pointerOffset; protected override void OnEnable() { @@ -24,6 +25,14 @@ protected override void OnEnable() _connectionType = serializedObject.FindProperty("_connectionType"); _port = serializedObject.FindProperty("_port"); _ipAddress = serializedObject.FindProperty("_ipAddress"); + if(target is Tuio20Input) + { + _pointerOffset = serializedObject.FindProperty("_pointerOffset"); + } + else + { + _pointerOffset = null; + } } public override void OnInspectorGUI() @@ -32,6 +41,10 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(_connectionType); EditorGUILayout.PropertyField(_port); EditorGUILayout.PropertyField(_ipAddress); + if (_pointerOffset != null) + { + EditorGUILayout.PropertyField(_pointerOffset); + } serializedObject.ApplyModifiedProperties(); base.OnInspectorGUI(); } diff --git a/Runtime/Core/TouchManagerInstance.cs b/Runtime/Core/TouchManagerInstance.cs index 72f6f4414..4b64c2f60 100644 --- a/Runtime/Core/TouchManagerInstance.cs +++ b/Runtime/Core/TouchManagerInstance.cs @@ -236,11 +236,19 @@ public IList PressedPointers private HashSet pointersRemoved = new HashSet(); private HashSet pointersCancelled = new HashSet(); - private static ObjectPool> pointerListPool = new ObjectPool>(2, - () => new List(10), null, (l) => l.Clear()); - - private static ObjectPool> intListPool = new ObjectPool>(3, () => new List(10), null, - (l) => l.Clear()); + private static ObjectPool> pointerListPool = new ObjectPool>( + capacity: 2, + actionNew: () => new List(10), + actionOnGet: null, + actionOnRelease: (l) => l.Clear() + ); + + private static ObjectPool> intListPool = new ObjectPool>( + capacity: 3, + actionNew: () => new List(10), + actionOnGet: null, + actionOnRelease: (l) => l.Clear() + ); private int nextPointerId = 0; private object pointerLock = new object(); diff --git a/Runtime/Gestures/TapGesture.cs b/Runtime/Gestures/TapGesture.cs index 946fa7162..15e0b753d 100644 --- a/Runtime/Gestures/TapGesture.cs +++ b/Runtime/Gestures/TapGesture.cs @@ -113,6 +113,19 @@ public float CombinePointersInterval set { combinePointersInterval = value; } } + public Vector2 PointerDirection + { + get => direction.Value; + } + + public float PointerRotation + { + get + { + return direction.ToAngle(); + } + } + #endregion #region Private variables @@ -146,6 +159,7 @@ public float CombinePointersInterval private TimedSequence pointerSequence = new TimedSequence(); private CustomSampler gestureSampler; + private SmoothedVector2 direction = new(10); #endregion @@ -207,29 +221,35 @@ protected override void pointersPressed(IList pointers) if (NumPointers == pointers.Count) { + var pointer = pointers[0]; + // the first ever pointer if (tapsDone == 0) { - startPosition = pointers[0].Position; + startPosition = pointer.Position; if (timeLimit < float.PositiveInfinity) StartCoroutine("wait"); + direction.Reset(); + direction.Update(pointer.Rotation); } else if (tapsDone >= numberOfTapsRequired) // Might be delayed and retapped while waiting { reset(); - startPosition = pointers[0].Position; + startPosition = pointer.Position; if (timeLimit < float.PositiveInfinity) StartCoroutine("wait"); + direction.Update(pointer.Rotation); } else { if (distanceLimit < float.PositiveInfinity) { - if ((pointers[0].Position - startPosition).sqrMagnitude > distanceLimitInPixelsSquared) + if ((pointer.Position - startPosition).sqrMagnitude > distanceLimitInPixelsSquared) { setState(GestureState.Failed); gestureSampler.End(); return; } } + direction.Update(pointer.Rotation); } } if (pointersNumState == PointersNumState.PassedMinThreshold) diff --git a/Runtime/InputSources/Tuio20Input.cs b/Runtime/InputSources/Tuio20Input.cs index f3ee9b548..357223c52 100644 --- a/Runtime/InputSources/Tuio20Input.cs +++ b/Runtime/InputSources/Tuio20Input.cs @@ -14,6 +14,9 @@ public sealed class Tuio20Input : TuioInput { private TuioClient _client; private Tuio20Processor _processor; + + [SerializeField] private float _pointerOffset = 0; + protected override void Init() { if (IsInitialized) return; @@ -58,7 +61,9 @@ private void TuioAdd(Tuio20Object tuio20Object) x = tuioPointer.Position.X * ScreenWidth, y = (1f - tuioPointer.Position.Y) * ScreenHeight }; - TouchToInternalId.Add(tuioPointer.SessionId, AddTouch(screenPosition)); + var touchPointer = AddTouch(screenPosition); + InitPointerProperties(touchPointer, tuioPointer); + TouchToInternalId.Add(tuioPointer.SessionId, touchPointer); } if (tuio20Object.ContainsNewTuioToken()) @@ -91,6 +96,7 @@ private void TuioUpdate(Tuio20Object tuio20Object) y = (1f - tuioPointer.Position.Y) * ScreenHeight }; touchPointer.Position = RemapCoordinates(screenPosition); + UpdatePointerProperties(touchPointer, tuioPointer); UpdatePointer(touchPointer); } @@ -141,5 +147,26 @@ private void UpdateObjectProperties(ObjectPointer pointer, Tuio20Token token) pointer.ObjectId = (int)token.ComponentId; pointer.Angle = token.Angle; } + + + private void InitPointerProperties(TouchPointer pointer, Tuio20Pointer tuioData) + { + pointer.INTERNAL_InitRotation(ShiftAngle(tuioData.Angle, _pointerOffset)); + } + + private void UpdatePointerProperties(TouchPointer pointer, Tuio20Pointer tuioData) + { + pointer.Rotation = ShiftAngle(tuioData.Angle, _pointerOffset); + } + + private static float ShiftAngle(float angle, float offset) + { + const float TWO_PI = 2 * Mathf.PI; + float result = angle + offset; + result = result % TWO_PI; + if (result < 0) + result += TWO_PI; + return result; + } } } \ No newline at end of file diff --git a/Runtime/Pointers/FakePointer.cs b/Runtime/Pointers/FakePointer.cs index f78aa06ca..6be7ea813 100644 --- a/Runtime/Pointers/FakePointer.cs +++ b/Runtime/Pointers/FakePointer.cs @@ -37,6 +37,12 @@ public class FakePointer : IPointer /// public Vector2 PreviousPosition { get; private set; } + /// + public float Rotation { get; set; } + + /// + public float PreviousRotation { get; private set; } + #endregion #region Constructors @@ -50,6 +56,17 @@ public FakePointer(Vector2 position) : this() Position = position; } + /// + /// Initializes a new instance of the class. + /// + /// The position. + /// The rotation in radians. + public FakePointer(Vector2 position, float rotation) : this() + { + Position = PreviousPosition = position; + Rotation = PreviousRotation = rotation; + } + /// /// Initializes a new instance of the class. /// diff --git a/Runtime/Pointers/IPointer.cs b/Runtime/Pointers/IPointer.cs index a9e223cef..53a9be83e 100644 --- a/Runtime/Pointers/IPointer.cs +++ b/Runtime/Pointers/IPointer.cs @@ -44,6 +44,16 @@ public interface IPointer /// Vector2 PreviousPosition { get; } + /// + /// Rotation of the pointer in radians. + /// + float Rotation { get; set; } + + /// + /// Previous Rotation of the pointer in radians. + /// + float PreviousRotation { get; } + /// /// Gets or sets pointer flags: /// Note: setting this property doesn't immediately change its value, the value actually changes during the next TouchManager update phase. diff --git a/Runtime/Pointers/Pointer.cs b/Runtime/Pointers/Pointer.cs index d00db78af..d3b438838 100644 --- a/Runtime/Pointers/Pointer.cs +++ b/Runtime/Pointers/Pointer.cs @@ -213,6 +213,15 @@ public Vector2 Position /// public Vector2 PreviousPosition { get; private set; } + public float Rotation + { + get { return rotation; } + set { newRotation = value; } + } + + /// + public float PreviousRotation { get; private set; } + /// public uint Flags { get; set; } @@ -237,6 +246,7 @@ public ProjectionParams ProjectionParams private LayerManagerInstance layerManager; private int refCount = 0; private Vector2 position, newPosition; + private float rotation, newRotation; private HitData pressData, overData; private bool overDataIsDirty = true; @@ -274,6 +284,8 @@ public virtual void CopyFrom(Pointer target) Buttons = target.Buttons; position = target.position; newPosition = target.newPosition; + rotation = target.rotation; + newRotation = target.newRotation; PreviousPosition = target.PreviousPosition; } @@ -313,6 +325,8 @@ public override string ToString() BinaryUtils.ToBinaryString(Flags, builder, 8); builder.Append(", position: "); builder.Append(Position); + builder.Append(", rotation: "); + builder.Append(Rotation); builder.Append(")"); return builder.ToString(); } @@ -340,6 +354,7 @@ internal virtual void INTERNAL_Init(int id) { Id = id; PreviousPosition = position = newPosition; + PreviousRotation = rotation = newRotation; } internal virtual void INTERNAL_Reset() @@ -347,6 +362,7 @@ internal virtual void INTERNAL_Reset() Id = INVALID_POINTER; INTERNAL_ClearPressData(); position = newPosition = PreviousPosition = Vector2.zero; + rotation = newRotation = PreviousRotation = 0; Flags = 0; Buttons = PointerButtonState.Nothing; overDataIsDirty = true; @@ -362,6 +378,13 @@ internal virtual void INTERNAL_UpdatePosition() { PreviousPosition = position; position = newPosition; + INTERNAL_UpdateRotation(); + } + + internal virtual void INTERNAL_UpdateRotation() + { + PreviousRotation = rotation; + rotation = newRotation; } internal void INTERNAL_Retain() @@ -387,6 +410,11 @@ internal void INTERNAL_ClearPressData() refCount = 0; } + internal void INTERNAL_InitRotation(float rot) + { + PreviousRotation = rotation = newRotation = rot; + } + #endregion } } \ No newline at end of file diff --git a/Runtime/Pointers/TouchPointer.cs b/Runtime/Pointers/TouchPointer.cs index 70c72e096..340bc870c 100644 --- a/Runtime/Pointers/TouchPointer.cs +++ b/Runtime/Pointers/TouchPointer.cs @@ -27,12 +27,6 @@ public class TouchPointer : Pointer #region Public properties - /// - /// Gets or sets the touch's rotation. - /// - /// Rotation in radians. - public float Rotation { get; set; } - /// /// Gets or sets the touch's pressure. /// diff --git a/Runtime/Utils/SmoothedVector2.cs b/Runtime/Utils/SmoothedVector2.cs new file mode 100644 index 000000000..4486ee0d0 --- /dev/null +++ b/Runtime/Utils/SmoothedVector2.cs @@ -0,0 +1,94 @@ +using UnityEngine; + +namespace TouchScript.Utils +{ + public class SmoothedVector2 + { + private Vector2 _value = Vector2.zero; + private bool _initialized = false; + private readonly float _baseWeight; + private readonly int _maxSamples; + private int _count; + + public Vector2 Value => _value; + + /// + /// Approximate number of samples to smooth over (e.g. 5–30) + /// + public SmoothedVector2(int maxSamples) + { + _maxSamples = Mathf.Max(1, maxSamples); + _baseWeight = 2f / (_maxSamples + 1f); + _count = 0; + } + + /// + /// Updates the filter with a new angle (radians) + /// and returns a smoothed normalized direction vector. + /// + public Vector2 Update(float angleRad) + { + Vector2 input = new Vector2( + Mathf.Cos(angleRad), + Mathf.Sin(angleRad) + ); + return Update(input); + } + + /// + /// Updates the filter with a new vector + /// and returns a smoothed normalized direction vector. + /// + public Vector2 Update(Vector2 input) + { + if (input.sqrMagnitude <= 1e-8f) + return _value; + + input = input.normalized; + + if (!_initialized) + { + _value = input; + _initialized = true; + _count = 1; + return _value; + } + + // Exponential moving average + _count++; + float weight = ComputeWeight(_count); + _value = weight * input + (1f - weight) * _value; + + // Normalize to stay on unit circle + if (_value.SqrMagnitude() > 1e-8f) + _value = _value.normalized; + + return _value; + } + + /// + /// returns the angle representation of the smoothed normalized direction vector. + /// The range is from 0 to 2 PI + /// + public float ToAngle() + { + float angle = Mathf.Atan2(_value.y, _value.x); + const float TWO_PI = 2 * Mathf.PI; + if (angle < 0f) angle += TWO_PI; + return angle; + } + + public void Reset() + { + _initialized = false; + _count = 0; + _value = Vector2.zero; + } + + float ComputeWeight(int sampleCount) + { + float scale = Mathf.Min(1f, sampleCount / (float) _maxSamples); + return _baseWeight * scale; + } + } +} diff --git a/Runtime/Utils/SmoothedVector2.cs.meta b/Runtime/Utils/SmoothedVector2.cs.meta new file mode 100644 index 000000000..d2d6e9239 --- /dev/null +++ b/Runtime/Utils/SmoothedVector2.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4cc14ed7b064fbc4db1afedee09cb812 \ No newline at end of file