Search Unity

Unable to apply rotation offset

Discussion in 'Scripting' started by Reahreic, Oct 20, 2020.

  1. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254
    I'm trying to get an offset between two rotations, but as soon as the object's shared parent is rotated the calculated delta changes even though they keep their relative rotations.

    EG:
    Parent Rotation: (0,0,0)
    Child A localRotation: (0,0,0)
    Child B localRotation: (0,90,0)
    Calculated delta: (0, 90, 0)

    Parent Rotation: (45,45,45)
    Child A localRotation: (0,0,0)
    Child B localRotation (0,90,0)
    Calculated delta: (346, 19, 36) < Should still read 0,90,0 as only parent was rotated and both children kept their relative orientations.

    In reality the code is slightly more complex than the above example as Child A's rotation is derived as a unit vector from one point to another within the same local space as Child B. The points are derived from world points and then converted to local points via Child B's .parent.InverseTransformPoint(point) to be stored as local points for a multitude of other purposes.

    I've tried using them as local points as well as converting them back to world space, leading me to believe that the issue is with my rotation calculation it'self.
    Code (CSharp):
    1. ;
    2.  
    3. private Quaternion GetRotationDelta(Vector3 pointA, Vector3 pointB, Transform sibling){
    4.     //The direction vector
    5.     Vector3 direction = (pointB - pointA).normalized;
    6.     //A rotation based on the direction that shares it's 'up' with the sibling transform
    7.     Quaternion targetRotation = Quaternion.LookRotation(direction , sibling.up);
    8.     //A rotation representing the difference between the target rotation and the sibling's rotation.
    9.     return targetRotation * Quaternion.Inverse(sibling.rotation);
    10. }
    Edit: Changed title to better reflect actual issue.
     
    Last edited: Oct 27, 2020
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,909
    Your decription says you're looking for "an offset between two rotations", but your code is treating the two vectors like positions and calculating the direction from one of them to the other... so I'm not sure exactly what you're looking for here? Could you explain what your goal is a bit more?

    Are you looking for a Quaternion that represents the rotation from one direction to another? If so that's just
    Quaternion.FromToRotation(directionA, directionB)
     
  3. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254
    The two points form a direction from A to B, i'm looking for the difference between a rotation derived from that direction and the rotation of a third object. The direction mathematically isn't a rotation so i'm using the third object's up to generate one in the same plane as the sibling. (Hence the LookRotation) I previously used FromToRotation but in doesn't maintain the same 'local-up' as the sibling object which becomes important later when adding the offset to a rotated sibling and keeping the same local-up, even when it points world-down.

    That said FromToRotation isn't maintaining the same value when the parent is rotated either so i'm currently tearing apart the logic that maintains the points. Although the points work correctly when converted to world-space and used in Handles.DrawLine or when converted from Handles.FreeMoveHandle to localSpace if moved.

    ===

    It's possible i went astray a while back and am going down a completely incorrect path. What i'm trying to do in the grand scheme is have two sceneView handles via Handles.FreeMoveHandle, one, an anchor 'attached' anywhere to a target gameObject, and the other a control point. When the control point is moved it's used to pivot the gameObject around the anchor. the anchor offset works just fine. In the attached demonstration .gif i just left the anchor at the cube's origin for simplicity. (The fact that the cube also rotates out of plane despite being in orthographic top view is another issue, but one i'm ignoring for now as in the final use it shouldn't matter)
     

    Attached Files:

  4. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254
    @PraetorBlue
    I re-created the functionality in it's most basic, stripped down form in a completely new project and am still unable to make it work.

    If i use FromToRotation to calculate the difference between the two directions then the transforms up flips to always point world space up which is undesirable Additionally parent rotations cause undesirable results in the child.

    if i use LookRoration and pass in the transforms localUp, then it can be rotated upside down no problem, but if the parent's X or Z values are changed all rotations break.

    Steps to recreate:
    1. Create an empty gameObject at the origin with no rotation.
    2. Create a cube as a child of the above gameObject at the origin with no rotation.
    3. Add the RotationTest class to the child.
    4. Select the cube and press the "Store Offset" Button
    5. With the cube selected, grab the scene view handle to the left of the cube at (-2,0,0,) and drag it's blue handle. (The cube rotates nicely maintaining the rotational offset between it's local forward and the vector from the control to the cube.)
    6. Select the cube and press the "Reset" button.
    7. Select the parent and set it's X or Z rotation to 10° in the inspector.
    8. Select the cube and press the "Store Offset" Button
    9. With the cube selected, grab the scene view handle to the left of the cube at (-2,0,0,) and drag it's blue handle. (The cube jumps then starts rotating out of plane. The desired behavior should be the same as if i glued a stick to a box and used that stick to rotate the box in any direction.)
    10. Mix and match various combinations of child and parent rotations with control point positions in full 3d space. (Remember to detatch, store the offset, the retatch when you want set a new control position relative to the cube)

    Object Class:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. namespace TestEnv {
    6.     public class RotationTest : MonoBehaviour {
    7.         //World Space Points
    8.         public Vector3 anchor = Vector3.zero;
    9.         public Vector3 control = Vector3.left * 2f;
    10.  
    11.         public Quaternion rotOffset = Quaternion.identity;
    12.  
    13.         //Allows the control to be moved in the editor without affecting the object
    14.         public bool detatchControl = false;
    15.         public void StoreOffset() {
    16.             Vector3 dir = (anchor - control).normalized;
    17.  
    18.             Quaternion lookRot = Quaternion.LookRotation(dir, transform.up);
    19.  
    20.             rotOffset = lookRot * Quaternion.Inverse(transform.rotation);
    21.         }
    22.  
    23.         public void UpdateRotation() {
    24.             Vector3 dir = (anchor - control).normalized;
    25.  
    26.             Quaternion lookRot = Quaternion.LookRotation(dir, transform.up);
    27.  
    28.             transform.rotation =  lookRot * Quaternion.Inverse(rotOffset);//Works until parent's X, or Z are changed
    29.         }
    30.     }
    31. }
    Editor Class:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEditor;
    5. using System;
    6.  
    7. namespace TestEnv {
    8.     [CustomEditor(typeof(RotationTest))]
    9.     public class RotationTestEditor : Editor {
    10.         RotationTest RT;
    11.  
    12.         SerializedProperty prop_anchor;
    13.         SerializedProperty prop_control;
    14.         SerializedProperty prop_rotOffset;
    15.         SerializedProperty prop_detatchControl;
    16.  
    17.         float zoomHandleSize = 0;
    18.         float handleSizeSize = 0.04f;
    19.  
    20.         void OnEnable() {
    21.             RT = (RotationTest)target;
    22.             prop_anchor = serializedObject.FindProperty("anchor");
    23.             prop_control = serializedObject.FindProperty("control");
    24.             prop_rotOffset = serializedObject.FindProperty("rotOffset");
    25.             prop_detatchControl = serializedObject.FindProperty("detatchControl");
    26.         }
    27.  
    28.         void OnSceneGUI() {
    29.             zoomHandleSize = handleSizeSize * SceneView.currentDrawingSceneView.camera.orthographicSize;
    30.  
    31.             //Draw Handle Line from control to anchor
    32.             Handles.color = Color.black;
    33.             Handles.DrawLine(RT.anchor, RT.control);
    34.  
    35.             //Draw line past anchor on same trajectory for easy visual projection
    36.             Handles.color = Color.gray;
    37.             Handles.DrawLine(RT.anchor, RT.anchor + (RT.anchor - RT.control).normalized);
    38.  
    39.             //Draw Control Handle
    40.             Vector3 newPos = Handles.PositionHandle(RT.control, Quaternion.identity);
    41.             if (RT.control != newPos) {
    42.                 RT.control = newPos;
    43.  
    44.                 if (!RT.detatchControl) {
    45.                     RT.UpdateRotation();
    46.                 }
    47.             }
    48.         }
    49.  
    50.         public override void OnInspectorGUI() {
    51.             serializedObject.Update();
    52.  
    53.             EditorGUILayout.PropertyField(prop_anchor);
    54.             EditorGUILayout.PropertyField(prop_control);
    55.             EditorGUILayout.LabelField("Rotation Offset: " + prop_rotOffset.quaternionValue.eulerAngles.ToString());
    56.             EditorGUILayout.PropertyField(prop_detatchControl);
    57.      
    58.             EditorGUILayout.Space();
    59.  
    60.             EditorGUILayout.BeginHorizontal();
    61.             {
    62.                 //Stores the rotational offset between the object's forward and the cotnrol's direction
    63.                 if (GUILayout.Button("Store Offset")) {
    64.                     RT.StoreOffset();
    65.                 }
    66.  
    67.                 //Resets all objects to test defaults
    68.                 if (GUILayout.Button("Reset")) {
    69.                     RT.anchor = Vector3.zero;
    70.                     RT.control = Vector3.left * 2f;
    71.  
    72.                     RT.detatchControl = false;
    73.  
    74.                     RT.transform.localRotation = Quaternion.identity;
    75.  
    76.                     RT.StoreOffset();
    77.                 }
    78.             }
    79.             EditorGUILayout.EndHorizontal();
    80.  
    81.             EditorGUILayout.Space();
    82.  
    83.             serializedObject.ApplyModifiedProperties();
    84.         }
    85.     }
    86. }
     
  5. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    I believe the main issue is that your control points are not always going to be co-planar with your object's axis of rotation and you aren't compensating for when they don't line up. you can compensate for this by flipping the arguments in lookrotation and then correcting the rotation by rotating 90 degrees down.

    Code (CSharp):
    1. Quaternion lookRot = Quaternion.LookRotation(transform.up,dir) * Quaternion.LookRotation(Vector3.down,Vector3.forward);
    since LookRotation always respects the 1st argument and only uses the 2nd as a hint for orientation. you can use it enforce it stick to an axis of rotation regardless where the direction points to. The first look rotation will cause it's Z axis to point where its y-axis was and its y-axis to point away from the control point, so the second lookrotation commands the object to rotate downwards 90 degrees, correcting y-axis and having the z-axis point as best it can to the control point while still remaining constrained to the axis of rotation.

    if however you want it to rotate while not constrained to the up axis of the transform.... then the issue is that you are using transform.up which is relative to the objects local up.

    You could also use transform.RotateAround() instead, which could simplify the code. Where point is the anchor, axis is the axis you want to rotate about, and angle is Vector3.Angle(controlPrev - anchor, controlCurrent - anchor).
     
  6. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    I'm already lost here because I don't understand why you're considering world space at all. If parent rotation should not affect anything, why not simple deduce the offset from the two local rotations? I.e. treat parent as world.

    I'm positive the solution is simple, but your description of a problem is pretty hard to follow. Instead of telling us what you deem as necessary steps, it would maybe be helpful to just tell us what the benefit for the user is supposed to be.
     
  7. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254
    I combined replies to two of you into a single post, let me know if that's not preferable.

    @orionsyndrome
    This is part of a internal tool for rigging lines with decorations where the control and anchor points are part of a series of cubic bezier segments and the gameObject is an item attached to any anchor point at any positional and rotational offset.

    When the bezier path is altered, the attached decoration responds accordingly by changing it's position, and it's rotation relative to one or two control points (anchors 0 and lstAnchors.Count-1 only have one control point where as anchors 1 through lstAnchors.Count-2 have two opposing control points). The path may or may not share the same parent as the object hence the control and anchor points being stored in world space. (I did have an iteration where points are converted to the decorations local space, but dropped that during refactoring)

    To complicate the system even more, a decoration can be attached to one or more anchors from either the same line, other lines and when an attached anchor or it's control point is moved, the decoration responds by not only updating it's own position and rotation (relative to the altered anchor), but the positions of the other attached anchors and their control points as well.

    All of the above i had working nicely until during testing it was discovered that lines rigged within prefabs and nested prefabs that didn't have a rotation of (0,0,0) at the time of decoration attachment would shift their decoration's rotation on initialization. Debugging lead me to uncover that at the core of the issue was this rotation problem which occurs during calculation of the decorations rotational offset and the updating of rotation if one or more parents had either an X, and Z rotation applied.

    ---

    @JoshuaMcKenzie
    The problem with using LookRotation in that manner (assuming i'm understanding you correctly) is that the axis of rotation changes depending on where the control point is relative to the cube at the time of storing it's offset as well as during interaction. Depending on the control's movement which is not always axis locked, the object may only need to rotate around the local up, or during another movement, could require a rotation around the local right. (anchor-control).normalized is the important direction, with up being secondary only to avoid local up always wanting to face world/parent up
     
  8. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    Then it sounds like you want to avoid using transform.up (which can flip out 2 frames in during update rotation as you are experiencing). When storing the offset store both the starting look direction (control - anchor) and transform.rotation. then while updating the rotation get the cross product of the starting look direction X current look direction and that will get you the axis of rotation you want to rotate by. Then get the angle of those same two look directions using Vector3.Angle(). finally multiply that stored transform rotation by Quaternion.AngleAxis and set that result to the current transform rotation.

    Code (CSharp):
    1.  
    2.  
    3.     public Vector3 anchor = Vector3.zero, control = -Vector3.right;
    4.  
    5.     private Quaternion storedRot = Quaternion.identity;
    6.     private Vector3 startLook = Vector3.zero;
    7.  
    8.     public void StoreOffset()
    9.     {
    10.         storedRot = transform.rotation;
    11.         startLook = (control-anchor).normalized;
    12.     }
    13.  
    14.     public void UpdateRotation()
    15.     {
    16.         Vector3 currentLook = (control - anchor).normalized;
    17.         Vector3 axis = Vector3.Cross(startLook,currentLook);
    18.         float angle = Vector3.Angle(startLook,currentLook);
    19.  
    20.  
    21.         tranform.rotation = storedRot * Quaternion.AngleAxis(angle,axis);
    22.     }
     
  9. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    If you have access to all interpolated cubic bezier points, and thus each segment is at your disposal, in world space coordinates, what stops you from solving this in a global manner? You can easily compute the global transform for a given segment, and then inversely apply its future parent's matrix before parenting it.

    I'm using Vector3.up in this code because I can't tell how your decorations are supposed to be oriented with respect to world horizon. (I've seen in the meanwhile that you're using sibling.up, we'll get to this later)
    Code (csharp):
    1. var segDir = (worldBezierPoint[i + 1] - worldBezierPoint[i]).normalized;
    2. decoration.rotation = Quaternion.LookRotation(segDir, Vector3.up);
    But now when I look at your OP I see the same thing that you've already tried and failed.
    However, you said that you have tried using world points, but in that case the rotation you get should be treated as world rotation, not a local one. So you don't need Quaternion.Inverse, because it matters how you apply this result, and this you haven't shown.

    If you apply LookRotation() based on world points to rotation, Unity will transform your points based on your parent automatically. The chances are that you've applied inverse twice, once in the context of your computation, and the second time in the context of what is the actual parent matrix.

    What is a sibling btw? Shouldn't you care about the parent instead?
    Go back to square one, you're making this more complicating than it this.

    First of all, make sure you're working with de facto world space coordinates.
    Compute the world space quaternion by using the relevant up vector in world space.
    Assign this back to decoration rotation. This rotation is not the same as localRotation, it is more expensive because it does the inverse of the parent's world transform for you (thus it's recursive).

    Now, if you attempt to change the parent's transform after this assignment, it will fail on its own, but this is due to how you've set up your hierarchy, and invalid local rotations will be applied down the branch, so you need to update the world rotation to reflect the change, and thus inversely affect the localRotation of the decorator.

    In simple words, you've made a transform hierarchy where there should be none. There is no practical reason for a decorator to assume its parent's transformations, if its local rotation is supposed to always compensate against the world rotation of its parent, only to keep own world rotation at rest.
     
    Last edited: Oct 22, 2020
  10. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254

    If I only rotate the control point fully around the Y or Z axis then this behaves correctly, so long as I store new offset values any time the parent's rotation is affected (It briefly spins when currentLook is within 5° of Vector3.left but that's a minor inconvenience for now).
    However, if I rotate the control point 90° around the Y axis, and then try to rotate the control point around the X axis, the object rolls around the currentLook axis in addition to pitching around the Cross product's axis.
     
  11. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254
    @orionsyndrome Sorry for the delayed reply, been working through several suggested approaches to no avail. It's embarrassing that i'm able to implement complex data structures for storing and transmitting user interaction data to a server for evaluation, but i can't overcome what should have been a 'simple' A = B + (C - D) calculation.

    I originally used world points and world rotation. Only to find that when i was configuring prefabs, some of which are nested prefabs, that upon assignment of the prefab to the primary parent object. The lines anchor points would jump as the prefabs world space and the local space of the primary parent weren't always aligned in position and rotation ultimately requiring conversion from a collection of world points to a collection of local points.

    I used sibling (previous test article naming for a decoration as the line and the decoration shared the parent) in order for the decorations up to not flip and always point world up when the control to anchor direction crossed Vector3.up or Vector3.down. Additionally as decorations can be located at literally any rotation relative to their point on the line, using world up would cause decorations that were placed at anything other than when their local up aligned with the world up (eg: on their side) to flip so their local up was pointing world up. This would occur when their anchor or it's control(s) were affected, triggering their onRespondToChange event. lines automatically monitor their own parents for transform changes in order to ensure that they update their points as required in the event that the parent changes. The dynamic mesh(s) that are ultimately generated, being a child of parent, automatically inherit any parental position and rotation changes and only need as far as i can see their relative rotations and positions updated, including any applied offsets from the line.

    Rotation is applied at a later stage during the onRespondToChange event that is dispatched when a line's points are altered. When dispatched, the decorations affected receive's their changed anchor position and changed direction and use that direction to calculate a new LookRotation taking into account the rotation offset of the decoration that was stored at the time of attachment as decorations aren't always aligned to the normal of the line segment

    I looked into this in my most recent down test article, i couldn't get it to work as desired using either world or local points, with or without taking the parent's rotation into account. the closest i came was in my previous reply to JoshuaMcKenzie where there was still an errant roll rotation that was introduced once two half rotations that were at right angles to each other were applied.

    I've re attacked this issue several different ways so far using world and local verified points and rotations and fear it may be beyond me. Parental transform changes are monitored already so i can trigger updates within the children as needed.

    From a hierarchy standpoint there's really only the parent container which has one or more lines and decorations as children. That parent can be a prefab or a nested prefab within other gameObjects depending on the use case but that's about it at it's simplest. Decorations aren't supposed to counter their parent's rotations, this is merely a method for automatically affecting a decorations position and rotation based on changes to the line.

    While it's actually for a cabling system, you could equate it to one of those charm bracelets. The charms (decorations) are attached to the bracelet (line) at points and while both move with the arm of the human (parent container) wearing them. Except that the decorations aren't loosely attached (no physics or gravity needed) and are instead "welded" to the anchor point on the line at an arbitrary angular offset.

    The pseudocode for what i'm trying to do is essentially:

    //Store rotation at time of initial attachment
    localOffsetRotation = InitialLocalDecorationRotation - initialLocalControlRotation

    //Update decoration's rotation when control's rotation is changed
    localDecorationRotation = localControlRotation + localOffsetRotation;


    I'm off tomorrow for a week to let my brain reset, first 'vacation' in about 2 years so if i don't respond please know that i appreciate your inputs as they're helping me evaluate what i'm missing and triggering thought processes.

    Edit: Typos, and grammar.
     
    Last edited: Oct 27, 2020
  12. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    of course, but you didn't get my remark there. if a child should not be auto-rotated with the parent i.e. imagine a bug on the windshield going through a curved segment of a road, then it needs not be a child.

    it's not a good setup if you can't solve a simple problem from 6 different approaches. and if that's indeed true, and you cannot find the culprit, maybe this is not a geometric problem, but something else affects your coordinates and rotations in some script you maybe didn't even know existed.

    if that's not true, then an error lies in how you're treating the world space, you're likely mixing up the world space with some other likely invalid space, and you need to find a way to simplify the setup, and make it work as suggested in just two lines of code, because there is no reason this would require any more than that. and you already have an intuition that should be the case.

    all in all, I'd go with proposal #2 and do it from scratch in a simpler project, learn what's what before applying this logic in a real world situation.

    regarding my first paragraph here, if you need to consider a parent for local coordinates or something, do it in code, without having to do build up an entire hierarchy. you can always transform your coordinates, rotation, and scale in code with regards to any other transform, regardless of their actual position in the hierarchy. the premise here is that you need to know what is going on.

    you can't just attack and reattack, with any code there is only a handful of character permutations that work and as many permutations as there are atoms in the known universe in total. this is why it's called a code, most likely.
     
  13. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254
    In the test articles, the decoration and line are both supposed to be affected by their common parent's rotation. Thereafter the line also imparts an additional rotation upon the decoration based on the line's control points. I didn't mean to imply that the decoration should exist in effective isolation.

    I had already created a new isolated project from scratch before opening the thread to rule out external factors. Literally only 1 test class with two Vector3 fields to represent the control and anchor positions and a minor custom editor for moving the control point via handle, draw a few colored lines, and two inspector buttons for reset and storing offset. This is standard practice for me when i run into an issue i can't resolve within a few hours. I build one of these clean slates for each suggestion I receive then test them using the same methodology.

    Using FromToRotation to store the difference between the initial control direction and the initial decoration forward, then using FromToRotation to generate a rotation from the initial decoration forward to the current control direction less the stored rotational offset results in a test article that fails the second test.

    Code (csharp):
    1.  
    2. /*Test 2 (World Space rotate around Z)*/
    3. //No Parent
    4. //Initial Rotation Euler (0°,0°,0°)
    5. //Initial Control Direction (1,0,0)
    6. //Expected rotation: (0°, 0°, -45°)
    7. //Resultant rotation: (30°, -35.264°, -54.736°)
    8.  
    9. public void StoreOffset() {
    10.     initialForward = transform.forward;
    11.     initialControlForward = (anchor - control).normalized;
    12.     rotationOffset = Quaternion.FromToRotation(initialForward , initialControlForward);
    13.     //Calculated Euler Rotational Delta: (0,90,0)
    14. }
    15.  
    16. public void UpdateRotation() {
    17.     //Control point moved from (-2,0,0) to (-1,1,0)
    18.      controlFwd = (anchor - control).normalized;
    19.     transform.rotation = Quaternion.FromToRotation(initialForward, controlFwd) * Quaternion.Inverse(rotationOffset);
    20. }
    21.  
     
  14. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    Okay, can you now please do this last approach step by step for me?
    Can you explain what are you doing and why exactly, at each step?

    And if you can muster an image for what you're trying to achieve graphically, and how you expect it to work, that would be really helpful, even if only sketched.

    I think I understood your goals, and the terminology you've used so far, but tying up your nomenclature to actual visual elements, will help out immensely in trying to decipher where the problem is.

    What I really expect you to do is something like this:
    - I have point A and point B, and I want to find a mid point between the two, so a I do (A+B) / 2, this is point C
    - etc. don't mind if my oversimplification is grotesque

    What I need to do is to debug your thinking process, to at least eliminate that side of the story. You seem to be making the same mistake over and over, and so you're either oblivious to some simple mistake you're making, or there is an error in some code somewhere. If we eliminate your thinking process in these clean slate experiments, we can move forward onto finding where the rest of the code misbehaves.
     
  15. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254
    As requested:

    I have a controlPoint and anchorPoint (Vector3) in world space. I also have an object which can reside at any level in the hierarchy. When the object is "assigned" to the anchor(transform.position = anchorPoint), the difference between its current rotation and a rotation representing the direction from controlPoint to the anchorPoint is stored as an offset.
    When the control point is subsequently moved, the object should rotate relative to the control point in order to maintain it's offset while respecting the object's local up as much as possible.

    The attached image shows expected behavior. I only illustrated one axis (top down), however the same motion concept applies regardless of where the control point is moved in 3D space.
    Visualization.jpg

    Maximum testing complexity is achieved with a random rotation of the cube's parent, a random rotation of the cube, and a random placement of the control point around the anchor point in 3D space prior to "attachment". The desired result is a relative rotation of the cube when the controlPoint is moved.

    I've exported the test scene based on post #4 as it was the closest i was able to get as a package and attached it as it'll be better than a wall of text in demonstrating the more complex rotations.
     

    Attached Files:

    Last edited: Nov 10, 2020