Nesting Pivot object causes parent to shoot up into space
-
Hello guys,
I have a python tag that sits on a plane old camera object. The tag has some user data controls for position, rotation etc. along with the option to use a custom pivot object (e.g.
c4d.BaseObject(c4d.Onull)
) to rotate the camera around.If no custom pivot object is supllied I use the world origin i.e. a unit matrix
c4d.Matrix()
as the pivot.While this all works I noticed some odd behaviour when I nest the custom pivot object as a child object of the camera. Things start to get really weird then. I move the nested pivot object and the camera object starts flying all over the place.
I suspect that I either ran into some kind of logical limitation or that my math is not correct.
While I know that my question is probably way beyond support I still wonder if someone could be so kind and spare some time to help me out here.
Please find below the code I'm using along with the scene file.
Cheers,
Sebastianfrom typing import Optional import c4d doc: c4d.documents.BaseDocument # The document evaluating this tag op: c4d.BaseTag # The Python scripting tag flags: int # c4d.EXECUTIONFLAGS priority: int # c4d.EXECUTIONPRIORITY tp: Optional[c4d.modules.thinkingparticles.TP_MasterSystem] # Particle system thread: Optional[c4d.threading.BaseThread] # The thread executing this tag def main() -> None: # Called when the tag is executed. It can be called multiple time per frame. Similar to TagData.Execute. # Write your code here pivot = op[c4d.ID_USERDATA,1] pivot_rotation_heading = op[c4d.ID_USERDATA,3] pivot_rotation_pitch = op[c4d.ID_USERDATA,4] pivot_rotation_banking = op[c4d.ID_USERDATA,5] camera_position_x = op[c4d.ID_USERDATA,13] camera_position_y = op[c4d.ID_USERDATA,17] camera_position_z = op[c4d.ID_USERDATA,18] camera_rotation_heading = op[c4d.ID_USERDATA,6] camera_rotation_pitch = op[c4d.ID_USERDATA,7] camera_rotation_banking = op[c4d.ID_USERDATA,8] camera = op.GetObject() if not camera or not camera.CheckType(c4d.Ocamera): return # Get the global matrix of the pivot. # If there is no pivot object supplied use the unit matrix. pivot_matrix = pivot.GetMg() if pivot is not None else c4d.Matrix() # Construct a matrix to set our camera object to. camera_matrix = ( pivot_matrix * c4d.utils.MatrixRotY(pivot_rotation_heading) * c4d.utils.MatrixRotX(pivot_rotation_pitch) * c4d.utils.MatrixRotZ(pivot_rotation_banking) * c4d.utils.MatrixMove( c4d.Vector(camera_position_x, camera_position_y, camera_position_z) ) * c4d.utils.MatrixRotY(camera_rotation_heading) * c4d.utils.MatrixRotX(camera_rotation_pitch) * c4d.utils.MatrixRotZ(camera_rotation_banking) ) camera.SetMg(camera_matrix)
-
Hello @HerrMay,
Thank you for reaching out to us. Let us look at your two setups:
- Setup that works fine: The transform of the pivot object is disjunct from the transform of the camera object.
- Setup that does not work: The transform of the pivot object is not disjunct from the transform of the camera object.
In your code you then do this:
pivot_matrix = pivot.GetMg() if pivot is not None else c4d.Matrix() camera_matrix = ( pivot_matrix * ... ) camera.SetMg(camera_matrix)
So, above we established that in your second setup the transform of pivot depends on the transform of camera. And here you make the transform of camera dependent on pivot, creating the feedback loop you consider weird.
The "working" solution is basically doing this:
pivot := {USER_VALUE} camera = pivot + 42 Output sequence for USER_VALUE: {0, 1, 2, 3} -> [42, 43, 44, 45]
While the other one is doing this:
camera := 0 pivot := camera + {USER_VALUE} camera = pivot + 42 Output sequence for USER_VALUE {0, 1, 2, 3} -> [42, 85, 129, 174]
pivot
is here dependent oncamera
and you have therefore this recursive quality, what you describe as weird. The second step computes for example as42 (value from the last step) + 1(USER_VALUE) + 42 (the constant in the camera equation)
.You can never use the value of a parameter x in an equation to drive the parameter x with an expression (i.e., tag), as you will give up the interpolation qualities of that parameter with that.
For some things like for example particle simulations, plugin authors make the deliberate choice to give up the interpolateability of a parameter, i.e., you cannot scrub the timeline to jump back and forth between particle states (without caching them) and instead must build a value one after the other based on its previous value.
That you consider your result weird (due to the unintended feedback loop) is only the minor problem. Worse is that you give up interpolateability, you cannot rewind things with this approach.
Cheers,
Ferdinand -
Hi @ferdinand,
as always - thanks for your explanation. Thats what I thought, I got myself in a race condition there. I feel more and more stupid with these questions I ask.
However, I wonder what could be a viable solution then? I mean, there must be a better way, right? As one simply can not stop a user from nesting the pivot object under the camera.
I hacked that rig together because I thought it would be nice to have a tag based solution for an orbit camera. Instead of always nesting various null objects to drive rotations and what not.
I thought about cloning that pivot into a virtual document and reading its matrix from there and use that as a pivot matrix. Didn't make much difference. The race condition was still there, though not as hefty.
Cheers,
Sebastian -
Hello @HerrMay,
What to do here depends a bit on what you want to achieve, if your first setup works for you, you could just use that.
I do not really understand why the pivot should be a child of the camera in the first place, because the camera is meant to be set to the transform of the pivot anyways (plus some extra steps I am ignoring here). So, they are already effectively in a parent-child relation.
In the end you must decide: Should the pivot lead the camera or vice versa? There is no technicality which can save you from that choice
If you want these two just to be bundled up, you could simply inverse the relation, which won't be a problem, because there you would simply enforce the parent child relation and would not have two contradicting operations, this feedback loop.
And in your code, you would then completely ignore the pivot thing, you would not even need the link field anymore, and simply operate in the local space of the parent to do your spiny thing:
-
Hi @ferdinand,
Eureka! Of course, how simple.
This bevaiour is just what I wanted all long. Man, sometimes you don't see the hand in front of your face.Thanks for helping me @ferdinand. You're simply golden.
P. S. I should really internalize these damn matrices already.
Cheers,
Sebastian -