Notifications
Clear all

[Closed] Scripting controllers – constrain circle to bounding box?

I know this can be done, but I’m utterly stuck at the minute.

I’m building a rigging tool helper for our pipeline, so that animations can be abstracted from the actual bones and saved/loaded instead onto a set of joystick controllers.

I’ve built a function that builds a joystick, but I have NO IDEA how to make the controller circle stay within the the rectangular bounding box. The bounding box is the parent of the circle, so I’m starting with the assumption that I need to use

in coordsys parent

to report the position of the circle, and clamp it if it goes outside the boundingbox.

So, I’ve got a circle, “cc”, and a bounding box “cb”. I’ve got variables for the size of the joystick bounding box (jsl and jsl for length and width), so I assume that I’ll need to query the x,y position of the circle in relation to the box.

But…I’m stuck.

Any pointers?

10 Replies

Hi Rick, try using the “float_limit()” controller. You can also wire the limits of the controller to the width and height of the box.

i.e. If the box have 10 units height, the up limit of the circle will be the “height/2”

hope it helps
See ya

Hi Rick!

Fabiomussarela is a good starting point.

Otherwise you will have to make a number of calculations based on the distance that the
circle control has moved from the center of the parent, taking into account the size of the both the circle control and it’s parent.

It can be done, but setting up can be tricky.

Shane

Cheers for the fast reply! I’m at home at the minute, but I’ll give that a shot when I get in tomorrow. After clearing bugs, obviously.

Still at home, having a wee look at the online docs – float_limit does allow for limits, but can I lock it to x and y? How to I specify a position lock?

Taken from Autodesk:
<float_limit>.lower_limit Float default: -10000.0

For a horizontal controller, the position would range from -jsw to +jsw (by default on my joystick, that’s -0.1 to +0.1), so I’d use

<float_limit>.lower_limit Float default: -jsw
and do the same for an upper of course:
<float_limit>.upper_limit Float default: jsw

Since my joystick controller allows me to create vertical, horizontal AND square controllers, I’m wondering if this will work for the square controller?

Perhaps for square it doesn’t matter, since x and y are the same size anyway.

Not having max in front of me is annoying…

In the end, I gave up on the DependsOn and FloatLimit, and ripped off another script that uses IK.

So firstly, credit for the constraints goes to :
– Rigging Tools 0.45
– Alden Filion / pDe
alden@aldenfilion.com

And now the rest of the joystick creation script. We work on a small scale, where 1 unit = 1 metre, hence the multiplier on line 1 that scales everything down by a factor of 10.

I’ve tried to abstract it to a function as much as possible, as I’ll like be calling the function a dozen times to build a complete rig with no UI. The UI is there solely to test the function.


-- Generic Joystick creation script with test UI.
-- Rick Stirling
-- November 2007


-- A universal scaler for different sized joysticks
global sizemulti = 0.1


-- Function that builds a joystick
fn createJoystick jsn jstyle jpos =
(
		-- setup our default sizes
		jsl = 1 * sizemulti as float				-- bounding box length
		jsw = 1 * sizemulti as float				-- bounding box width
		jscor = 0.1 * sizemulti as float 			-- rounded corners
		jscir = 0.1 * sizemulti as float			-- and the circle size
		jscap = 0.2 * sizemulti as float			-- caption text size
		jstxsp = jsl - 0.3 * sizemulti as float		-- caption text offset

		
		-- The size of the bounding box will depend on the style chosen, so adjust variables now
		if jstyle == 2 then 
		(
			-- Vertical style stick
			jsw = 0.2 as float
			jscor = 0.1 as float

		)
			
		else if jstyle == 3 then 
		(
			-- Horizontal style stick
			jsl = 0.2 as float
			jscor = 0.1 as float
		)


		
		-- create bounding box for the joystick, keep it selected		 
		jsbox = Rectangle length:jsl width:jsw cornerRadius:jscor position:jpos name: ("JSB_" + jsn) wirecolor:(color 250 230 100) transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0]) isselected:on
		cb = jsbox 	-- punt this to a temp variable to select it later if I need to. cb means current box
		
		-- add the caption, parent it to the bounding box  
		jscaption = Text text: jsn size: jscap wirecolor:(color 20 20 255) name: ("JSCaption_" + jsn) transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0])
		jscaption.parent = jsbox
		in coordsys parent jscaption.position = [0,jstxsp,0] 
		
		-- and create the circle controller, parent it to the bounding box 
		jscircle = Circle radius: (jscir) name: ("JS_Circle_" + jsn) wirecolor:(color 20 20 255) transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0]) 
		jscircle.parent = jsbox
		cc = jscircle 	-- punt this to a temp variable to select it later. cc means current circle.
		in coordsys parent jscircle.position = [0,0,0]
		
		
		--in coordsys parent jscircle.position = [jsl/2,jsw/2,0]
		
		
		-- now that we have the controller bits built, we need to limit the controller
		-- circle to the bounding box edges. 
		-- can't figure it our with dependsOn, so I'll use IK
		-- NIK code from Alden Filion / pDe
		
		NIK = iksys.ikchain cb cc "IKHISolver"

		NIK.name = jsn + "_IKChain"

		hide NIK

		cb.transform.controller.RotXActive = off
		cb.transform.controller.RotYActive = off
		cb.transform.controller.RotZActive = off

		cc.transform.controller.PosXActive = on
		cc.transform.controller.PosYActive = on
		cc.transform.controller.PosXLimited = on
		cc.transform.controller.PosXLLimited = on
		cc.transform.controller.PosXULimited = on
		cc.transform.controller.PosYLimited = on
		cc.transform.controller.PosYLLimited = on	
		cc.transform.controller.PosYULimited = on
	
	
		-- set up the limits for square style controller, modifiy if needed
		cc.transform.controller.PosXLLimit = -(jsw/2)
		cc.transform.controller.PosXULimit = (jsw/2)
		cc.transform.controller.PosYLLimit = -(jsl/2)
		cc.transform.controller.PosYULimit = (jsl/2)
	
		if (jstyle == 2) then
		(
			-- Vertical style, no X movement
			cc.transform.controller.PosXLLimit = 0
			cc.transform.controller.PosXULimit = 0
		)

		if (jstyle == 3) then
		(
			-- Horizontal style, no Y movement
			cc.transform.controller.PosYLLimit = 0
			cc.transform.controller.PosYULimit = 0
		)
		
		select cb -- Select the parent of the joystick
)



rollout crig "Control Rig builder" width:163 height:175
(	


	-- UI here
	group "Create New Joystick" 
	(
		Edittext jsn "Joystick Name: "
		RadioButtons jstype labels:#("Square", "Vertical", "Horizontal")
		Button crjstick "Create Joystick"
	)

	
	
	on crjstick pressed do 
	(
		-- call the joystick creation function with the UI data
		jsname = jsn.text as string
		jstyle = jstype.state as integer
		jpos = [1,0,2] -- the position on screen. This can overridden later, perhaps by mouse click.

		createJoystick jsname jstyle jpos
	)

) -- rollout end


createDialog crig 250 400 

Back to float_limit() – I really wanted to get a handle on this, so I’ve ben chipping away, and I nailed it. It’s a much more elegant solution, so thank you for suggesting it.

The actual constraining fr x and y is now done with float limits and wire parameters, thusly:


xfl=float_limit()
jscircle.pos.controller.x_position.controller=xfl
paramWire.connect jsbox.baseObject[#width] xfl.limits[#upper_limit] ((conjsw/2) as string)
paramWire.connect jsbox.baseObject[#width] xfl.limits[#lower_limit] ((-conjsw/2) as string)
		
yfl=float_limit()
 jscircle.pos.controller.y_position.controller=yfl
 paramWire.connect jsbox.baseObject[#length] yfl.limits[#upper_limit] ((conjsl/2) as string)
paramWire.connect jsbox.baseObject[#length] yfl.limits[#lower_limit] ((-conjsl/2) as string)

I’ve tidied the script in other areas too – the function doen’t select the joystick anymore, it returns it instead, so I can call the function as a constructor instead. I killed unwanted variables, and I think it’s really nice – but I’ll take any feedback.

The paraWire drove me mad for while until I figured out I had to convert my float to a string.

Feel free to use/edit/steal.



-- Generic Joystick creation script with test UI.
-- Rick Stirling
-- November 2007


-- A universal scaler for different sized joysticks
global sizemulti = 1


-- Function that builds a joystick
fn createJoystick jsn jstyle jpos =
(
		-- setup our default sizes
		local jsl = conjsl = 0.1 * sizemulti as float		-- bounding box length
		local jsw = conjsw = 0.1 * sizemulti as float		-- bounding box width
		local jscor = 0.01 * sizemulti as float 			-- rounded corners
		local jscir = 0.01 * sizemulti as float				-- and the circle size
		local jscap = 0.02 * sizemulti as float				-- caption text size
		local jstxsp = jsl - 0.03 * sizemulti as float		-- caption text offset

		-- The size of the bounding box will depend on the style chosen, so adjust variables now
		if jstyle == 2 then 
		(
			-- Vertical style stick
			jsw = 0.02 as float
			jscor = 0.01 as float
			conjsw=0
		)
			
		else if jstyle == 3 then 
		(
			-- Horizontal style stick
			jsl = 0.02 as float
			jscor = 0.01 as float
			conjsl=0
		)


		
		-- create bounding box for the joystick, keep it selected		 
		jsbox = Rectangle length:jsl width:jsw cornerRadius:jscor position:jpos name: ("JSB_" + jsn) wirecolor:(color 250 230 100) transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0]) isselected:on
		
		-- add the caption, parent it to the bounding box  
		jscaption = Text text: jsn size: jscap wirecolor:(color 20 20 255) name: ("JSCaption_" + jsn) transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0])
		jscaption.parent = jsbox
		in coordsys parent jscaption.position = [0,jstxsp,0] 
		
		-- and create the circle controller, parent it to the bounding box 
		jscircle = Circle radius: (jscir) name: ("JS_Circle_" + jsn) wirecolor:(color 20 20 255) transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0]) 
		jscircle.parent = jsbox
		in coordsys parent jscircle.position = [0,0,0]
		
		
		-- now that we have the controller bits built, we need to limit the controller
		-- circle to the bounding box edges. 
		-- use float limits based on cgtalk ideas
		

		xfl=float_limit()
		jscircle.pos.controller.x_position.controller=xfl
		paramWire.connect jsbox.baseObject[#width] xfl.limits[#upper_limit] ((conjsw/2) as string)
		paramWire.connect jsbox.baseObject[#width] xfl.limits[#lower_limit] ((-conjsw/2) as string)
		
		yfl=float_limit()
		jscircle.pos.controller.y_position.controller=yfl
		paramWire.connect jsbox.baseObject[#length] yfl.limits[#upper_limit] ((conjsl/2) as string)
		paramWire.connect jsbox.baseObject[#length] yfl.limits[#lower_limit] ((-conjsl/2) as string)

		return jsbox
)



rollout crig "Control Rig builder" width:163 height:175
(	


	-- UI here
	group "Create New Joystick" 
	(
		Edittext jsn "Joystick Name: "
		RadioButtons jstype labels:#("Square", "Vertical", "Horizontal")
		Button crjstick "Create Joystick"
	)

	
	
	on crjstick pressed do 
	(
		-- call the joystick creation function with the UI data
		jsname = jsn.text as string
		jstyle = jstype.state as integer
		jpos = [1,0,2] -- the position on screen. This can overridden later, perhaps by mouse click.

		seljoy = createJoystick jsname jstyle jpos
	)

) -- rollout end


createDialog crig 250 400 


Something else useful – a normalise function for the joystick. Since I create them at a small scale, and normally you’ll want them to be in the range -1 to +1, this function takes a controller as input, queries the parent bounding box, and return a multiplier for X and Y

– normalise joystick
– get the size of the boundingbox, create a normalise multiplier
fn joystick_normalise stickcontrol =
(
nmx = 1/($.parent.width)*2
nmy = 1/($.parent.length)*2

 xyra = [nmx, nmy]
 return xyra

)

I’ve figured out part two, so I thought I may as well share – linking controllers to bones, and controllers to controllers.

My only currnet sticking point is that I can’t seem to link a controller to two other controllers that control bones – so if I have a left eye controller and a right eye controller, trying to make a parent controller for those two ails.

Anyway, functions plus examples



-- a function to wire joystick controllers to bones
fn wire_control_bone thecontrol caxis nrmlzr thebone baxis anglelimit = 
(
	-- multiply normaliser with angle limit
	theamount = nrmlzr * anglelimit
	
	-- Get the current base rotations of the bone.
	tr = in coordsys world thebone.transform.rotation as eulerangles 
	
	rx = tr.x
	ry = tr.y
	rz = tr.z
	angleoffset = rx -- pick one to initialise the variable
	
	-- which axis is the controller changing on?
	-- 1 = x axis, 2= y axis
	if caxis == 1 then
	(
		pwcontrol = thecontrol.pos.controller.X_Position.controller[#Limited_Controller__Bezier_Float]
	)
	
	else if caxis == 2 then
	(
		pwcontrol = thecontrol.pos.controller.Y_Position.controller[#Limited_Controller__Bezier_Float]
	)
	
	-- which axis is the bone to rotate on?
	-- 1 = x axis, 2= y axis, 3 = z axis
	if baxis == 1 then
	(
		pwtarget = thebone.rotation.controller[#X_Rotation]
		angleoffset = rx
	)	
	
	else if baxis == 2 then
	(
		pwtarget = thebone.rotation.controller[#Y_Rotation]
		angleoffset = ry
	)	
	
	else if baxis == 3 then
	(
		pwtarget = thebone.rotation.controller[#Z_Rotation]
		angleoffset = rz
	)		
	
	-- setup the rotation expression
	controlexp = "(Limited_Controller__Bezier_Float * degtorad " + (theamount as string) + ")"
	-- add the rotational offset
	controlexp = controlexp + " + degtorad " + (angleoffset as string)
	
	-- do the connecting
	paramWire.connect pwcontrol pwtarget controlexp

)


-- allow a controller to control other controllers
fn wire_control_control Cinput ciaxis ctarget ctaxis theamount=
(

	-- which axis is the controller changing on?
	-- 1 = x axis, 2= y axis
	if ciaxis == 1 then
	(
		pwcontrol = Cinput.pos.controller.X_Position.controller[#Limited_Controller__Bezier_Float]
	)
	
	else if ciaxis == 2 then
	(
		pwcontrol = Cinput.pos.controller.Y_Position.controller[#Limited_Controller__Bezier_Float]
	)
	
	-- which axis is the target changing on?
	-- 1 = x axis, 2= y axis
	if ctaxis == 1 then
	(
		pwtarget = ctarget.pos.controller.X_Position.controller[#Limited_Controller__Bezier_Float]
	)	
	
	else if ctaxis == 2 then
	(
		pwtarget = ctarget.pos.controller.Y_Position.controller[#Limited_Controller__Bezier_Float]
	)	
	
	-- setup the rotation expression
	controlexp = "(Limited_Controller__Bezier_Float * " + (theamount as string) + ")"

	
	-- do the connecting
	paramWire.connect pwcontrol pwtarget controlexp

)




-- 1 = x axis, 2= y axis, 3 = z axis
x = 1
y = 2
z = 3



-- eyes l/r to eye controller left, multiply by 1
--wire_control_control $'JS_Circle_Eyes' x $'JS_Circle_Left_Eye' x 1
--wire_control_control $'JS_Circle_Eyes' y $'JS_Circle_Left_Eye' y 1
--wire_control_control $'JS_Circle_Eyes' x $'JS_Circle_Right Eye' x 1
--wire_control_control $'JS_Circle_Eyes' y $'JS_Circle_Right Eye' y 1

-- left eye L/R
wire_control_bone $'JS_Circle_Left_Eye' x 20 $'FB_L_Eye' z 45

-- left eye U/D
wire_control_bone $'JS_Circle_Left_Eye' y 20 $'FB_L_Eye' y -45

-- right eye L/R
wire_control_bone $'JS_Circle_Right Eye' x 20 $FB_R_Eye z 45

-- right eye U/D
wire_control_bone $'JS_Circle_Right Eye' y 20 $'FB_R_Eye' y -45

Thanks for the share! I’m enjoying your adventure!

Shane

Page 1 / 2