From 92416ad1a47a932eb4be15f99291e200c03ac7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kr=C3=BCger?= Date: Thu, 20 Nov 2025 12:31:40 +0100 Subject: [PATCH 1/6] write angle information of TUIO20 message to TouchPointer instances --- Runtime/InputSources/Tuio20Input.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Runtime/InputSources/Tuio20Input.cs b/Runtime/InputSources/Tuio20Input.cs index f3ee9b54..78a6f72b 100644 --- a/Runtime/InputSources/Tuio20Input.cs +++ b/Runtime/InputSources/Tuio20Input.cs @@ -91,6 +91,7 @@ private void TuioUpdate(Tuio20Object tuio20Object) y = (1f - tuioPointer.Position.Y) * ScreenHeight }; touchPointer.Position = RemapCoordinates(screenPosition); + UpdatePointerProperties(touchPointer, tuioPointer); UpdatePointer(touchPointer); } @@ -141,5 +142,10 @@ private void UpdateObjectProperties(ObjectPointer pointer, Tuio20Token token) pointer.ObjectId = (int)token.ComponentId; pointer.Angle = token.Angle; } + + private void UpdatePointerProperties(TouchPointer pointer, Tuio20Pointer tuioData) + { + pointer.Rotation = tuioData.Angle; + } } } \ No newline at end of file From 40b775028e0b98ee6c4f251d7696f9cff5307fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kr=C3=BCger?= Date: Thu, 18 Dec 2025 17:17:42 +0100 Subject: [PATCH 2/6] add Rotation and PreviousRotation properties to IPointer --- Runtime/Pointers/FakePointer.cs | 17 +++++++++++++++++ Runtime/Pointers/IPointer.cs | 10 ++++++++++ Runtime/Pointers/Pointer.cs | 28 ++++++++++++++++++++++++++++ Runtime/Pointers/TouchPointer.cs | 6 ------ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/Runtime/Pointers/FakePointer.cs b/Runtime/Pointers/FakePointer.cs index f78aa06c..6be7ea81 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 a9e223ce..53a9be83 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 d00db78a..d3b43883 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 70c72e09..340bc870 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. /// From 6ca6d5f6a43a99b065719e030905545e8018b330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kr=C3=BCger?= Date: Thu, 18 Dec 2025 17:18:56 +0100 Subject: [PATCH 3/6] Improving the readability of two class instance declarations --- Runtime/Core/TouchManagerInstance.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Runtime/Core/TouchManagerInstance.cs b/Runtime/Core/TouchManagerInstance.cs index 72f6f441..4b64c2f6 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(); From 4430701f5f85850c89b7db242945ef3713d1f342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kr=C3=BCger?= Date: Thu, 18 Dec 2025 17:19:49 +0100 Subject: [PATCH 4/6] add pointer offset option to Tuio20Input --- Editor/InputSources/TuioInputEditor.cs | 13 +++++++++++++ Runtime/InputSources/Tuio20Input.cs | 25 +++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/Editor/InputSources/TuioInputEditor.cs b/Editor/InputSources/TuioInputEditor.cs index 22ec6e22..727f9d44 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/InputSources/Tuio20Input.cs b/Runtime/InputSources/Tuio20Input.cs index 78a6f72b..357223c5 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()) @@ -143,9 +148,25 @@ private void UpdateObjectProperties(ObjectPointer pointer, Tuio20Token token) 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 = tuioData.Angle; + 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 From 095013134f72a76308a57df2a7648af91bdbe8bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kr=C3=BCger?= Date: Thu, 18 Dec 2025 17:20:07 +0100 Subject: [PATCH 5/6] create SmoothedVector2 class --- Runtime/Utils/SmoothedVector2.cs | 94 +++++++++++++++++++++++++++ Runtime/Utils/SmoothedVector2.cs.meta | 2 + 2 files changed, 96 insertions(+) create mode 100644 Runtime/Utils/SmoothedVector2.cs create mode 100644 Runtime/Utils/SmoothedVector2.cs.meta diff --git a/Runtime/Utils/SmoothedVector2.cs b/Runtime/Utils/SmoothedVector2.cs new file mode 100644 index 00000000..4486ee0d --- /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 00000000..d2d6e923 --- /dev/null +++ b/Runtime/Utils/SmoothedVector2.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4cc14ed7b064fbc4db1afedee09cb812 \ No newline at end of file From 59f1e00e95441f9423ef93eff88ef393f8821b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kr=C3=BCger?= Date: Thu, 18 Dec 2025 17:20:42 +0100 Subject: [PATCH 6/6] add PointerDirection and PointerRotation accessors to TapGesture class --- Runtime/Gestures/TapGesture.cs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Runtime/Gestures/TapGesture.cs b/Runtime/Gestures/TapGesture.cs index 946fa716..15e0b753 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)