diff --git a/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md b/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md index c69f115..b3b1137 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md +++ b/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- `Handle.Targets` (`IReadOnlyCollection`) exposes the actual manipulated objects, and + `TransformGroup.Targets` / `TransformHandleManager.GetTargets(handle)` provide the same — there + was previously no public way to ask a handle which objects it controls. +- `Handle.Pivot` is the honest name for the manipulation pivot (the ghost transform the handle + moves around). + +### Deprecated +- `Handle.Target` is now `[Obsolete]`. It returns the manipulation pivot (ghost), not the selected + object — a long-standing source of confusion. Use `Handle.Pivot` for the pivot or + `Handle.Targets` for the manipulated objects. The member keeps working until the next major. + ## [3.2.0] - 2026-06-11 ### Added diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Documentation~/index.md b/Packages/com.orkunmanap.runtime-transform-handles/Documentation~/index.md index 07e26c6..d6b248d 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Documentation~/index.md +++ b/Packages/com.orkunmanap.runtime-transform-handles/Documentation~/index.md @@ -62,13 +62,15 @@ Handles require a dedicated physics layer (default name `TransformHandle`). Crea | `void ChangeHandleSpace(Handle, Space)` | Set a handle's space and re-center the ghost. | | `void ChangeHandlePivot(TransformGroup, bool originToCenter)` | Toggle pivot vs. bounds-center origin. | | `Camera MainCamera` | Camera used for raycasting (settable; falls back to `Camera.main`). | +| `IReadOnlyCollection GetTargets(Handle)` | The objects a handle manipulates (live, read-only). | | `TransformHandleSettings Settings` | Optional settings asset (overrides serialized defaults). | ### `Handle` | Member | Description | |--------|-------------| -| `Transform Target` | Manipulated transform (**read-only**; set via `CreateHandle`/`Enable`). | +| `IReadOnlyCollection Targets` | The manipulated objects (**read-only**, live view). | +| `Transform Pivot` | The manipulation pivot — the ghost the handle moves around (**read-only**; not your object). | | `Camera HandleCamera` | Camera for screen math (**read-only**; set when enabled). | | `HandleType Type` | Property; assigning rebuilds the child handles. | | `HandleAxes Axes` | Property; assigning rebuilds the child handles. | @@ -79,6 +81,9 @@ Handles require a dedicated physics layer (default name `TransformHandle`). Crea | events `OnInteractionStartEvent` / `OnInteractionEvent` / `OnInteractionEndEvent` / `OnHandleDestroyedEvent` | `Action`. Inspector-friendly `*UnityEvent` mirrors exist. | | `void ApplySettings(TransformHandleSettings)` | Apply scale/appearance from a settings asset. | +> `Handle.Target` is `[Obsolete]` — it returns the **pivot**, not the selected object. Use `Pivot` +> for the pivot or `Targets` for the manipulated objects. +> > The camelCase spellings of these members (`target`, `type`, `space`, `axes`, `snappingType`, > `positionSnap`, `rotationSnap`, `scaleSnap`, `autoScale`, `handleCamera`, `mainCamera`) and the > `ChangeHandleType`/`ChangeHandleSpace`/`ChangeAxes` methods are `[Obsolete]` shims — they keep diff --git a/Packages/com.orkunmanap.runtime-transform-handles/README.md b/Packages/com.orkunmanap.runtime-transform-handles/README.md index 6dd23d4..0297568 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/README.md +++ b/Packages/com.orkunmanap.runtime-transform-handles/README.md @@ -55,8 +55,8 @@ public class SimpleExample : MonoBehaviour void CreateHandleForObject(Transform target) { Handle handle = TransformHandleManager.Instance.CreateHandle(target); - // Note: handle.Target is the internal manipulation pivot (the group's ghost), not your - // object. Capture your own `target` reference for anything object-specific. + // handle.Pivot is the manipulation pivot (the group's ghost); handle.Targets are the + // actual objects being manipulated. handle.OnInteractionStartEvent += _ => Debug.Log("Started: " + target.name); handle.OnInteractionEndEvent += _ => Debug.Log("Finished: " + target.name); } diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/Handle.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/Handle.cs index 870709a..ea4ecd9 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/Handle.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/Handle.cs @@ -86,12 +86,32 @@ public class Handle : MonoBehaviour /// UnityEvent fired when handle is destroyed. Configure in Inspector. public HandleUnityEvent OnHandleDestroyedUnityEvent => onHandleDestroyed; - /// The target transform being manipulated. Read-only; set via . - public Transform Target { get; private set; } + /// + /// The manipulation pivot — the ghost transform the handle moves/rotates/scales around. + /// This is NOT the user's selected object; for those, use . + /// Read-only; set via . + /// + public Transform Pivot { get; private set; } + + /// + /// The transforms this handle manipulates (the actual selected objects). Read-only, + /// reflects the current group membership; empty until the handle is enabled with a target + /// or while it is not managed by a . + /// + public System.Collections.Generic.IReadOnlyCollection Targets => + Manager != null ? Manager.GetTargets(this) : System.Array.Empty(); + + /// + /// The manipulation pivot (ghost). Misleadingly named — it returns the pivot, not the + /// selected object(s). Use for the pivot and for + /// the manipulated objects. + /// + [Obsolete("Use Pivot for the manipulation pivot, or Targets for the manipulated objects. Target returns the pivot (ghost), not your selected object.")] + public Transform Target => Pivot; - /// - [Obsolete("Use Target instead.")] - public Transform target => Target; + /// + [Obsolete("Use Pivot instead.")] + public Transform target => Pivot; [SerializeField, FormerlySerializedAs("axes")] private HandleAxes _axes = HandleAxes.XYZ; /// Active axes for the handle. Assigning rebuilds the child handles. @@ -248,7 +268,7 @@ protected virtual void LateUpdate() /// The transform to manipulate. public virtual void Enable(Transform targetTransform) { - Target = targetTransform; + Pivot = targetTransform; transform.position = targetTransform.position; CreateHandles(); @@ -259,7 +279,7 @@ public virtual void Enable(Transform targetTransform) /// public virtual void Disable() { - Target = null; + Pivot = null; Clear(); } @@ -322,12 +342,12 @@ public virtual void ChangeAxes(HandleAxes handleAxes) protected virtual void UpdateHandleTransformation() { - if (!Target) return; + if (!Pivot) return; - transform.position = Target.position; + transform.position = Pivot.position; if (Space == Space.Self || Type == HandleType.Scale) { - transform.rotation = Target.rotation; + transform.rotation = Pivot.rotation; } else { diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/HandleBase.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/HandleBase.cs index 3ff0ffa..5aa430a 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/HandleBase.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/HandleBase.cs @@ -99,7 +99,7 @@ public virtual void EndInteraction() protected Vector3 GetRotatedAxis(Vector3 axis) { return ParentHandle.Space == Space.Self - ? ParentHandle.Target.rotation * axis + ? ParentHandle.Pivot.rotation * axis : axis; } diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Position/PositionAxis.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Position/PositionAxis.cs index 2045e01..8195bdd 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Position/PositionAxis.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Position/PositionAxis.cs @@ -94,7 +94,7 @@ public override void Interact(Vector3 previousPosition) if (Mathf.Abs(_axis.z) > 0.5f) position.z = SnapUtils.Snap(position.z, snapping.z); } - ParentHandle.Target.position = position; + ParentHandle.Pivot.position = position; base.Interact(previousPosition); } @@ -104,7 +104,7 @@ public override void StartInteraction(Vector3 hitPoint) { base.StartInteraction(hitPoint); - _startPosition = ParentHandle.Target.position; + _startPosition = ParentHandle.Pivot.position; var rAxis = GetRotatedAxis(_axis); diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Position/PositionPlane.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Position/PositionPlane.cs index c10a2de..01b26c3 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Position/PositionPlane.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Position/PositionPlane.cs @@ -96,7 +96,7 @@ public override void Interact(Vector3 previousPosition) if (Mathf.Abs(axis.z) > 0.5f) position.z = SnapUtils.Snap(position.z, snapping.z); } - ParentHandle.Target.position = position; + ParentHandle.Pivot.position = position; base.Interact(previousPosition); } @@ -106,7 +106,7 @@ public override void StartInteraction(Vector3 hitPoint) { var rPerp = GetRotatedAxis(_perp); - var position = ParentHandle.Target.position; + var position = ParentHandle.Pivot.position; _plane = new Plane(rPerp, position); var ray = _handleCamera.ScreenPointToRay(InputWrapper.MousePosition); diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Rotation/RotationAxis.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Rotation/RotationAxis.cs index 6c67001..e9674b0 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Rotation/RotationAxis.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Rotation/RotationAxis.cs @@ -59,7 +59,7 @@ public override void Interact(Vector3 previousPosition) } var hitPoint = cameraRay.GetPoint(hitT); - var hitDirection = (hitPoint - ParentHandle.Target.position).normalized; + var hitDirection = (hitPoint - ParentHandle.Pivot.position).normalized; var x = Vector3.Dot(hitDirection, _tangent); var y = Vector3.Dot(hitDirection, _biTangent); var angleRadians = Mathf.Atan2(y, x); @@ -73,12 +73,12 @@ public override void Interact(Vector3 previousPosition) if (ParentHandle.Space == Space.Self) { - ParentHandle.Target.localRotation = _startRotation * Quaternion.AngleAxis(angleDegrees, _axis); + ParentHandle.Pivot.localRotation = _startRotation * Quaternion.AngleAxis(angleDegrees, _axis); } else { var invertedRotatedAxis = Quaternion.Inverse(_startRotation) * _axis; - ParentHandle.Target.rotation = _startRotation * Quaternion.AngleAxis(angleDegrees, invertedRotatedAxis); + ParentHandle.Pivot.rotation = _startRotation * Quaternion.AngleAxis(angleDegrees, invertedRotatedAxis); } // Reuse a single Mesh instead of allocating a new one each frame. CreateArc would @@ -98,21 +98,21 @@ public override void StartInteraction(Vector3 hitPoint) base.StartInteraction(hitPoint); _startRotation = ParentHandle.Space == Space.Self - ? ParentHandle.Target.localRotation - : ParentHandle.Target.rotation; + ? ParentHandle.Pivot.localRotation + : ParentHandle.Pivot.rotation; _rotatedAxis = ParentHandle.Space == Space.Self ? _startRotation * _axis : _axis; - _axisPlane = new Plane(_rotatedAxis, ParentHandle.Target.position); + _axisPlane = new Plane(_rotatedAxis, ParentHandle.Pivot.position); var cameraRay = _handleCamera.ScreenPointToRay(InputWrapper.MousePosition); var startHitPoint = _axisPlane.Raycast(cameraRay, out var hitT) ? cameraRay.GetPoint(hitT) : _axisPlane.ClosestPointOnPlane(hitPoint); - _tangent = (startHitPoint - ParentHandle.Target.position).normalized; + _tangent = (startHitPoint - ParentHandle.Pivot.position).normalized; _biTangent = Vector3.Cross(_rotatedAxis, _tangent); } diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Scale/ScaleAxis.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Scale/ScaleAxis.cs index 7057543..ba69d6e 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Scale/ScaleAxis.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Scale/ScaleAxis.cs @@ -94,7 +94,7 @@ public override void Interact(Vector3 previousPosition) { if (_handleCamera == null) return; - var position = ParentHandle.Target.position; + var position = ParentHandle.Pivot.position; var direction = GetRotatedAxis(_axis); var handleSize = HandleTransformUtility.GetHandleSize(position, _handleCamera); var lineTranslation = HandleTransformUtility.CalcLineTranslation( @@ -127,7 +127,7 @@ public override void Interact(Vector3 previousPosition) Delta = dist - 1f; var scale = Vector3.Scale(_startScale, _axis * Delta + Vector3.one); - ParentHandle.Target.localScale = scale; + ParentHandle.Pivot.localScale = scale; base.Interact(previousPosition); } @@ -136,7 +136,7 @@ public override void Interact(Vector3 previousPosition) public override void StartInteraction(Vector3 hitPoint) { base.StartInteraction(hitPoint); - _startScale = ParentHandle.Target.localScale; + _startScale = ParentHandle.Pivot.localScale; _startMousePosition = InputWrapper.MousePosition; } diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Scale/ScaleGlobal.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Scale/ScaleGlobal.cs index 9e0b1d6..96c02f6 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Scale/ScaleGlobal.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/HandleComponents/Scale/ScaleGlobal.cs @@ -39,7 +39,7 @@ public override void Interact(Vector3 previousPosition) var camera = ParentHandle.HandleCamera; if (camera == null) return; - var position = ParentHandle.Target.position; + var position = ParentHandle.Pivot.position; var handleSize = HandleTransformUtility.GetHandleSize(position, camera); var lineTranslation = HandleTransformUtility.CalcLineTranslation( _startMousePosition, @@ -60,7 +60,7 @@ public override void Interact(Vector3 previousPosition) } Delta = dist; - ParentHandle.Target.localScale = _startScale + Vector3.Scale(_startScale, _axis) * Delta; + ParentHandle.Pivot.localScale = _startScale + Vector3.Scale(_startScale, _axis) * Delta; base.Interact(previousPosition); } @@ -69,7 +69,7 @@ public override void Interact(Vector3 previousPosition) public override void StartInteraction(Vector3 hitPoint) { base.StartInteraction(hitPoint); - _startScale = ParentHandle.Target.localScale; + _startScale = ParentHandle.Pivot.localScale; _startMousePosition = InputWrapper.MousePosition; var camera = ParentHandle.HandleCamera; diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformGroup.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformGroup.cs index 0e1eee4..cb62a58 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformGroup.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformGroup.cs @@ -25,6 +25,12 @@ public class TransformGroup /// Gets the set of transforms in this group. internal HashSet Transforms { get; } + /// + /// The transforms in this group as a read-only, live view (the actual manipulated objects, + /// not the pivot). Do not cache across add/remove. + /// + public IReadOnlyCollection Targets => Transforms; + /// Gets the mapping of transforms to their mesh renderers (if any). internal Dictionary RenderersMap { get; } diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs index 5312c51..f623d70 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs @@ -414,6 +414,20 @@ public void RemoveTarget(Transform target, Handle handle) group.GroupGhost.UpdateGhostTransform(averagePosRotScale); } + /// + /// Returns the transforms manipulated by — the actual selected + /// objects, not the pivot. The result is a read-only, live view of the handle's current + /// group (do not cache it across add/remove); empty if the handle is not managed here. + /// + /// The handle to query. + public IReadOnlyCollection GetTargets(Handle handle) + { + if (handle == null) throw new ArgumentNullException(nameof(handle)); + if (_handleGroupMap != null && _handleGroupMap.TryGetValue(handle, out var group)) + return group.Transforms; + return System.Array.Empty(); + } + protected virtual void Update() { if (!_handleActive) return; diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Tests/Runtime/HandleInteractionTests.cs b/Packages/com.orkunmanap.runtime-transform-handles/Tests/Runtime/HandleInteractionTests.cs index 08093bc..c4ccb18 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Tests/Runtime/HandleInteractionTests.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Tests/Runtime/HandleInteractionTests.cs @@ -138,8 +138,8 @@ public void UpdateGroupPosition_moves_every_target_by_the_change() var a = NewObject("a").transform; var b = NewObject("b", new Vector3(2f, 0f, 0f)).transform; var handle = _manager.CreateHandleFromList(new List { a, b }); - var ghost = handle.Target.GetComponent(); - Assert.IsNotNull(ghost, "handle.Target must be the group's ghost pivot"); + var ghost = handle.Pivot.GetComponent(); + Assert.IsNotNull(ghost, "handle.Pivot must be the group's ghost pivot"); var change = new Vector3(1f, 2f, 3f); _manager.UpdateGroupPosition(ghost, change); diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Tests/Runtime/TransformHandleManagerTests.cs b/Packages/com.orkunmanap.runtime-transform-handles/Tests/Runtime/TransformHandleManagerTests.cs index 84bc46e..231d825 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Tests/Runtime/TransformHandleManagerTests.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Tests/Runtime/TransformHandleManagerTests.cs @@ -55,8 +55,33 @@ public void CreateHandle_returns_handle_for_single_target() var handle = _manager.CreateHandle(target); Assert.IsNotNull(handle); - Assert.IsNotNull(handle.Target, "handle.Target is the ghost pivot, not the user object"); - Assert.AreNotSame(target, handle.Target); + Assert.IsNotNull(handle.Pivot, "handle.Pivot is the manipulation pivot (ghost), not the user object"); + Assert.AreNotSame(target, handle.Pivot); + } + + [Test] + public void Targets_contains_the_manipulated_object_and_Pivot_is_separate() + { + var target = NewObject("target").transform; + var handle = _manager.CreateHandle(target); + + CollectionAssert.Contains(handle.Targets, target, "Targets must expose the selected object"); + Assert.AreEqual(1, handle.Targets.Count); + // Pivot is the ghost; the manipulated object is not. (Reference identity, not NUnit + // collection equality, which is unreliable on UnityEngine.Object's fake-null operator.) + Assert.AreNotSame(target, handle.Pivot, "the pivot is not the manipulated object"); + Assert.IsNotNull(handle.Pivot.GetComponent(), "Pivot is the ghost pivot"); + Assert.IsNull(target.GetComponent(), "the manipulated object is not a ghost"); + } + + [Test] + public void Targets_reflects_multi_select_membership() + { + var a = NewObject("a").transform; + var b = NewObject("b", new Vector3(2f, 0f, 0f)).transform; + var handle = _manager.CreateHandleFromList(new List { a, b }); + + CollectionAssert.AreEquivalent(new[] { a, b }, handle.Targets); } [Test] diff --git a/README.md b/README.md index 688451e..b604092 100644 --- a/README.md +++ b/README.md @@ -109,8 +109,8 @@ public class SimpleExample : MonoBehaviour // Create a handle for a single object Handle handle = _manager.CreateHandle(target); - // Subscribe to events. Note: handle.Target is the internal manipulation pivot (the - // group's ghost), not your object — capture your own `target` for anything object-specific. + // Subscribe to events. handle.Pivot is the manipulation pivot (the group's ghost); + // handle.Targets are the actual objects being manipulated. handle.OnInteractionStartEvent += _ => Debug.Log("Started manipulating: " + target.name); handle.OnInteractionEndEvent += _ => Debug.Log("Finished manipulating: " + target.name); }