Notifications
Clear all

[Closed] Simulating links between objects?

Say I have two objects, $Box1 and $Box2.
-When I select and move or rotate $Box1, I want $Box2 to follow it around as if linked to it.
-When I select $Box2, I want the same thing only reversed.
-When neither object is selected I don’t want either object to be affected by the other.

Obviously, in the context of an animation, actually changing the heirarchy of the objects along the way is not going to give me the desired results. This is pretty simple to do if both objects have exactly the same transformation, but once there is an offset I don’t know how to make it do what I want.

Also, in more practical terms, this should be something that can work for more than just 2 objects (i.e. if there are 5 objects, I want to be able to make them all follow whichever is selected at the time).

I’m up for using anything that works, whether scripted controllers, callbacks, dummy objects, etc.

What’s the best way to achieve this effect?

29 Replies
1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

list controllers for both rotation and position

– 1st: free transform
– 2nd: script

wire weights of these two channels as (weight1 = not weight 2)

use a selection change event (or your own UI) to set mode (#follow1/#follow2/#free) to handle the weights.

that only thing that i see… all other will be loop-dependent or doesn’t keep the animation.

Edit:
Okay, I have it up to the part where the weights are wired. I’m still not sure what to do next though. First off all, I’m going to need some way to store the offsets… I guess I can create dummy objects? And second, how do I make script controllers that /don’t/ result in circular dependencies?

Please disregard the following, not sure why it didn’t click initially…

Old message:

Hm, I’m a little confused by that.

So far I have a test scene set up with the 2 boxes,with the position and rotation controllers set to list controllers, and a script controller as the second controller in the list.

Now, the part I don’t quite get is, what am I supposed to do with the weights? I’m not quite sure how to wire them like that…

no… this is wrong direction.

And second, how do I make script controllers that /don’t/ result in circular dependencies?

PEN is a big pro in this matter. you can find some samples on his site and on this forum. when i have a time i will post my snippet.
but the basic idea is to use the NodeTransformMonitor. see details in the mxs help

I assume that NodeTransformMonitor is the answer to both of my questions?

Also, now that I’m looking, the documentation only seems to have a few paragraphs total on NodeTranformMonitor, and there’s really not anything there in terms of usage examples

Here is my suggestion, I might be missing something though because I’m not sure I completely understood what you meant about the other neighbors.

So what I did is this:

  1. create a point helper.
  2. add a script controller to one of it’s tracks like for instance the ‘center marker’.
  3. add a custom attribute to the script controller by selecting it, pressing the right mouse button and selecting ‘Add/Edit parameters…’ from the list.
  4. add a parameter of type String and set it’s name to “offset”.
  5. in the script controller editor add 2 variables named ‘a’ and ‘b’.
  6. set ‘a’ to point to the node of $Box001 and ‘b’ to point to the node of $Box002.
  7. enter this code:
if this.offset.count != 0 and a.isSelected and not b.isSelected then
	b.transform = (execute this.offset) * a.transform

this.offset = (b.transform * inverse a.transform) as string

0

and evaluate.
8. copy the point helper and edit it’s script controller so that ‘a’ will point to $Box002 and ‘b’ will point to $Box001. and we’re done!

This process can be generalized to deal with more then 2 objects easily.

Hey Tz,

Aside from the CA, that’s actually pretty similar to something I tried before. Unfortunately, it has the same issue as I was getting with my solution, which is that it ends up creating keys on every frame for the “child” object so long as the other one is selected. (I guess I didn’t clarify that this was not desired.)

I will need it set up to create a keyframe on whichever object is set as the “child” object -only- if a keyframe has been created on the “parent” object.

So I’m guessing it will need to check for keys on the “parent” object at each frame, and function with “animate off” unless it finds one? The problem being, how to do that without messing up all the -existing- keyframes?

Hm, okay, I have at least a temporary workable solution, let me know if you have a better one.

I’ve already got a couple of functions which I’ll be using with this project anyway:


 global getObjFrames
 fn collectObjFrames obj =
 (
 local objFrames = #()
 fn getObjFrames &objFrames track =
 (
 if isController track.controller do
 (
 join objFrames (for k in track.controller.keys collect k.time.frame as integer)
 objFrames = sort (makeUniqueArray objFrames)
 for i = 1 to track.numSubs do try (getObjFrames &objFrames track[i]) catch()
 )
 ) -- end objFrames function
 getObjFrames &objFrames obj
 
 objFrames
 )
 
 fn deleteKeyFrame track frame =
 (
 if isController track.controller do
 (
 try
 (
 for k = 1 to track.controller.keys.count where
 track.controller.keys[k].time == frame do
 deleteKey track.controller k
 ) catch()
 )
 
 for i = 1 to track.numSubs do deleteKeyFrame track[i] frame
 ) -- end deleteKeyFrame function

So, with those in place, I’m able to set up the following:

if this.offset.count != 0 and a.isSelected and not b.isSelected then
 b.transform = (execute this.offset) * a.transform
 this.offset = (b.transform * inverse a.transform) as string
 
 aFrames = collectObjFrames a
 bFrames = collectObjFrames b
 for i in bFrames where findItem aFrames i == 0 and i != currentTime do
 deleteKeyframe b i
 0

It’s perhaps not the most elegant solution in the world, and I’m sure that one of you guys will be able to do much better.

here is a snippet (setup and samples of use):


 delete objects
 b1 = box name:"bro1" width:10 length:10 height:10 wirecolor:green
 b2 = box name:"bro2" width:10 length:10 height:10 wirecolor:orange
 (
 	centerPivot #(b1,b2)
 	
 	fn addBrotherPosition source target fc: sc: =
 	(
 		if fc == unsupplied then fc = Boolean_Float()
 		if sc == unsupplied then sc = Boolean_Float()
 		
 		p = source.position.controller = createinstance Position_List
 		s = p.available.controller = Position_Script()
 		p.setname p.count "Linked Position"		
 		p.weight[p.count].controller = sc
 		p.weight[p.count] = 0
 
 		s.addobject "source" (NodeTransformMonitor node:source)  
 		s.addobject "target" (NodeTransformMonitor node:target)  
 		s.setexpression "source.position.controller[2].value * target.transform"
 
 		p.available.controller = Position_XYZ()
 		p.setname p.count "Free Position"		
 		p.active = p.count
 		p.weight[p.count].controller = fc
 		
 		p
 	)
 	fn addBrotherRotation source target fc: sc: =
 	(
 		if fc == unsupplied then fc = Boolean_Float()
 		if sc == unsupplied then sc = Boolean_Float()
 
 		p = source.rotation.controller = createinstance Rotation_List
 		s = p.available.controller = Rotation_Script()
 		p.setname p.count "Linked Rotation"		
 		p.weight[p.count].controller = sc
 		p.weight[p.count] = 0
 
 		s.addobject "source" (NodeTransformMonitor node:source)  
 		s.addobject "target" (NodeTransformMonitor node:target)  
 		s.setexpression "source.rotation.controller[2].value * target.transform.rotation"
 
 		p.available.controller = Euler_XYZ()
 		p.setname p.count "Free Rotation"		
 		p.active = p.count
 		p.weight[p.count].controller = fc
 		
 		p
 	)
 	disablerefmsgs()
 	w1 = Boolean_Float()
 	w2 = Boolean_Float()
 	p1 = addBrotherPosition b1 b2 free:on fc:w1 sc:w2
 	r1 = addBrotherRotation b1 b2 free:on fc:w1 sc:w2
 
 	w1 = Boolean_Float()
 	w2 = Boolean_Float()
 	p2 = addBrotherPosition b2 b1 free:off fc:w1 sc:w2
 	r2 = addBrotherRotation b2 b1 free:off fc:w1 sc:w2
 	enablerefmsgs()
 )
 -- both free
 (
 	move b1 [-20,0,0]
 	rotate b1 (eulerangles -30 -30 -30) 
 
 	move b2 [20,0,0]
 	rotate b2 (eulerangles 30 30 30) 
 	select b2
 )
 /*
 -- first drives second:
 (
 	p1.weight[1] = p2.weight[2] = 0
 	p1.weight[2] = p2.weight[1] = 1
 	select b1
 )
 
 --second drives first:
 (
 	p1.weight[2] = p2.weight[1] = 0
 	p1.weight[1] = p2.weight[2] = 1
 	select b2
 )
 
 -- second free:
 (
 	p2.weight[1] = 0
 	p2.weight[2] = 1
 	select b2
 )
 
 -- second slave:
 (
 	p2.weight[2] = 0
 	p2.weight[1] = 1
 	select b2
 )
 */
 /*
 b1.rotation.controller[2].value = (quat 1)
 b1.rotation.controller[2].value = (eulerangles 45 0 0)
 
 b2.rotation.controller[2].value = (quat 1)
 b2.rotation.controller[2].value = (eulerangles 45 0 0)
 */
 

ask question if you have any…

denis: Hm, it doesn’t seem to switch between the different setups automatically, and I’m not sure what I’m supposed to do with the commented code?

it can’t switch automatically because i didn’t setup it. i’ve wired (actually i just set the same controllers) weight controllers for rotation and position.
when you set 0 to rotation it sets automatically 0 to position and vice versa.

i updated the snippet… try to run it and use the commented samples to setup different modes…


 delete objects
 b1 = box name:"bro1" width:10 length:10 height:10 wirecolor:green
 b2 = box name:"bro2" width:10 length:10 height:10 wirecolor:orange
 (
 	centerPivot #(b1,b2)
 	
 	fn addBrotherPosition source target fc: sc: =
 	(
 		if fc == unsupplied then fc = Boolean_Float()
 		if sc == unsupplied then sc = Boolean_Float()
 		
 		p = source.position.controller = createinstance Position_List
 		s = p.available.controller = Position_Script()
 		p.setname p.count "Linked Position"		
 		p.weight[p.count].controller = sc
 		p.weight[p.count] = 0
 
 		s.addobject "source" (NodeTransformMonitor node:source)  
 		s.addobject "target" (NodeTransformMonitor node:target)  
 		s.setexpression "source.position.controller[2].value * target.transform"
 
 		p.available.controller = Position_XYZ()
 		p.setname p.count "Free Position"		
 		p.active = p.count
 		p.weight[p.count].controller = fc
 		
 		p
 	)
 	fn addBrotherRotation source target fc: sc: =
 	(
 		if fc == unsupplied then fc = Boolean_Float()
 		if sc == unsupplied then sc = Boolean_Float()
 
 		p = source.rotation.controller = createinstance Rotation_List
 		s = p.available.controller = Rotation_Script()
 		p.setname p.count "Linked Rotation"		
 		p.weight[p.count].controller = sc
 		p.weight[p.count] = 0
 
 		s.addobject "source" (NodeTransformMonitor node:source)  
 		s.addobject "target" (NodeTransformMonitor node:target)  
 		s.setexpression "source.rotation.controller[2].value * target.transform.rotation"
 
 		p.available.controller = Euler_XYZ()
 		p.setname p.count "Free Rotation"		
 		p.active = p.count
 		p.weight[p.count].controller = fc
 		
 		p
 	)
 )
 
 disablerefmsgs()
 w1 = Boolean_Float()
 w2 = Boolean_Float()
 p1 = addBrotherPosition b1 b2 free:on fc:w1 sc:w2
 r1 = addBrotherRotation b1 b2 free:on fc:w1 sc:w2
 
 w1 = Boolean_Float()
 w2 = Boolean_Float()
 p2 = addBrotherPosition b2 b1 free:off fc:w1 sc:w2
 r2 = addBrotherRotation b2 b1 free:off fc:w1 sc:w2
 enablerefmsgs()
 
 -- both free
 (
 	move b1 [-20,0,0]
 	rotate b1 (eulerangles -30 -30 -30) 
 
 	move b2 [20,0,0]
 	rotate b2 (eulerangles 30 30 30) 
 	select b2
 )
 /*	first drives second:
 (
 	p1.weight[1] = p2.weight[2] = 0
 	p1.weight[2] = p2.weight[1] = 1
 	select b1
 )
 */
 
 /*	second drives first:
 (
 	p1.weight[2] = p2.weight[1] = 0
 	p1.weight[1] = p2.weight[2] = 1
 	select b2
 )
 */
 
 /*	second free:
 (
 	p2.weight[1] = 0
 	p2.weight[2] = 1
 	select b2
 )
 */
 
 /*	second slave:
 (
 	p2.weight[2] = 0
 	p2.weight[1] = 1
 	select b2
 )
 */
 
 
 /*
 b1.rotation.controller[2].value = (quat 1)
 b1.rotation.controller[2].value = (eulerangles 45 0 0)
 
 b2.rotation.controller[2].value = (quat 1)
 b2.rotation.controller[2].value = (eulerangles 45 0 0)
 */
 
Page 1 / 3