Update on the maxscript controllers

A small post to say that I managed to write some fairy nice code that links my joysticks to bones, allowing me to specify angle limits. It all works as planned in my tests at home, but breaks on certain real world hierarchies.

This is something I need to figure out before I post a breakdown of it, but for those interested, the code is available here:

http://forums.cgsociety.org/showthread.php?f=98&t=563367

Creating controller joysticks in Maxscript – Part 1

Where I work, I deal with facial rigging for characters. The setup of bones, animating the faces, and the loading and saving of facial animations isn’t the easiest thing in the world, and Max doesn’t seem to provide an easy way to load facial animation from one facial setup to another.

What you can do however is bypass the actual bones by performing all the animation on a set of controllers, and then saving their animation. Since the controllers add a layer of abstraction, you can essentially transfer animation from almost any facial rig to any other, as long as you have the same controllers. I decided to use Joystick controllers, since they give a nice visual representation of the animation.

So, how to go about this? Since it’s something that is going to be used a lot, scripting it makes sense. I’ve scripted dozens of workflow tools at work, so I knew the benefits (especially when dealing with potentially hundreds of models). I’ve not built anything quite so complex before however, and my Maxscripting knowledge is still quite low, so I knew I’d have to learn new techniques. Fortunately when I got really stuck I was able to get some help from Maxscript forums such as CGTalk and Autodesk Area.

I broke it down into two distinct areas – building the controllers, and linking the controllers to the bones. This post concerns the joysticks – how I built them, and what I did to try and make them extendable for my future use, or for other people. I’ll built the joysticks in a function, so that I can call it with a few commands and get a custom joystick out the other end.

200711241517

I wanted 3 types of joysticks – Square, Vertical and Horizontal, but they are all the same really, just with different dimensions. They are simply a rounded box with a controller circle and a caption, all done via max shapes.

Creating a rectangle in Maxscript is simple:

Rectangle length:10 width:10

But that’s not quite good enough. I don’t want the joysticks to be 10×10 units, especially since I want to be able to create 3 shapes. Also, I’m working at a very small scale at the minute, but may want to change the size of the joysticks at any time. This is where variables come in – some are kept inside the function, some are passed in. Also, I define a universal scale multiplier so that people can make their joysticks 10 times bigger, or 50 times bigger, or whatever they wish

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

-- Function that builds a joystick
fn createJoystick jsn jstyle jpos = ()

When I call the function, I pass in three variables: jsn is the joystick name, used to give all its parts a name, and to add the caption; jstyle is the style, where 1 is square, 2 is vertical and 3 is horizontal; finally jpos is the position I want the joystick to initially appear at on screen.

When we get inside the function, I declare some variables that will allow me to draw all the parts of the joystick. I multiply these by my size multiplier (which being 1 in my script does nothing).

local jsl = conjsl = 0.1 * sizemulti as float		-- bounding box length and constraint
local jsw = conjsw = 0.1 * sizemulti as float	-- bounding box width and constraint
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

With these variables, drawing the parts is easy – lets draw the rectangle and give it some nice rounded corners.

Rectangle length:jsl width:jsw cornerRadius:jscor position:jpos name: ("JSB_" + jsn)

Just a couple of other things – Max draws shapes in a top down view, so we can use transform to change that. Also, lets set the colour of the shape, and also assign this rectangle to a variables called jsbox to make it easy to select later. Please note that the >>> are added here for formatting only, to show where the line was too long for the website. Where you see these, the two lines should be one long line. Let’s continue!

-- 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

Phew! That looks much harder than it actually was, just to get a nice rectangle on the screen, but it makes the following bit much easier. I’m going to skip a few of the steps here, and show you how I created the circle and the text – it should all make sense based on the stuff above. I just create a couple of shapes, parent them to the box (so they move when it does), and set their initial position relative to the box.

The >>> indicates a line split over two for formatting purposes.

-- 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)
 >>(linebreak here) 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)
 >>(linebreak here) 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]

That builds a nice square joystick, but you’ll remember I wanted to be able to build 1 dimensional joysticks too that were only for horizontal or for vertical movement. This is actually simple, all I need to to is to change the width or the height of the bounding box before I draw it. I already pass in a variable called jstyle which indicates the type of joystick I want, so lets deal with that next.

As I mentioned, the variable jstyle is a number, where 1 is square, 2 is vertical and 3 is horizontal, so I can use an if statement to alter a few variables if it’s not square.

-- 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
)

Quite simple – if the jstyle variable == 2, then it’s a vertical joystick, so set the width of the box to be narrow, and set to width constraint conjsw to be 0 – you can’t move the controller side to side. Obviously the horizontal stick uses the same method, but length instead of width.

At this stage I was happy – I could draw joysticks on the screen, and make them look how I wanted. But I ran into my first problem – how to stop the controller circles leaving the rectangle? This was going to be hard. I had a look around the Maxscript docs, but got stuck, then posted on CGTalk, whereupon I was recommend to try the float_limit() command. After some head banking, I eventually got it to work.

The float_limit command is essentially just that – a limit you can put on a floating point number. You create a float_limit object, assign it to a controller, then tell it what the upper and lower limits are. With my square controllers I’d need an upper and lower limit for both X and Y, and I’ve already explained that I’m treating my horizontal and vertical controllers in the same way as my square ones, and I’m simply setting their length or width to 0.

Limiting the controllers is based on the size of the bounding box, so the upper limit is 1/2 the size of the box, and the lower limit is also half the size (but a negative number). So, the code to limit my controllers.

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)

That’s all the parts of the joystick working together, but it’s all in a function because I plan to call the function in script to create as many joysticks as I need, placing them where I want them. However, most people will want a nice little interface, so I decided that I’d knock up a quick one. This is basic, but does the job, even if just to test the building of joysticks.

200711251354


The code is VERY simple – a box to type in the name, a joystick style, and a button to build it. They all get created at 1,0,2 in world space in this UI, but the joystick is automatically selected so that you can move it.

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

So, thats was all the parts, I hope you found it useful. In the next part, I’ll discuss linking the controllers to bones, and even to other controllers. I’ve finally managed to get it all working, but need to tidy up the code.

I’ll leave you with the full script:

-- 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)
 >>(linebreak here) 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)
 >>(linebreak here) 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

One final thing to note – my sticks return a range of -0.05 to +0.05, so you’ll want to normalise them to return a value from -1 to +1.

-- 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
)

Constructing a mesh for better deformation

There are many ways to construct a mesh, but if you want it to deform well, there are certain topologies that will give you better results.

Just go and read what Ancient-Pig has written in his Ancient-Pig’s basic deformation tutorial. He’s nailed it.

links for 2007-09-17

Book – Digital Lighting and Rendering

I can’t remember how I found out about this book – I’m sure it was recommended to me. Anyway, I’ve had this book for 4 or 5 years now (first edition), and it is easily my most borrowed book at work. Jeremy Birn is currently a Lighting Technical Director at Pixar Animation Studios, (he’s worked on Cars and The Incredibles), so you can be assured he knows his stuff.

The book itself is non-application specific, but deals with light theory (did you know that red things appear closer than blue things, so you can add artificial depth to a scene just by the choice of your lighting colours?), and it’s worth the money if you simply want to learn how to use a simple 3 point light setup for your portfolio.

Anyway, the book is now in its second edition, with a massive amount of new content, so there really is no excuse for me not buying it.

“Digital Lighting and Rendering” (Jeremy Birn)

Technorati Tags: , , ,

links for 2007-09-12

How long does it take to build and texture a model? 1 Week? 1 month?

Asked of me on Game-artists forums a few weeks ago (it seems to have gone kaput).

How long does it take to build and texture a model? 1 Week? 1 month?

Some models take a long time, some don’t. Some come together quickly, and others are a struggle to get right.

The time taken depends on the models importance in the game, and I don’t think I have ever worked on a game where you sat down, built and textured a model in a few days and had it be final. Most of the time your model will have to be altered (for artistic, animation or technial reasons), and in almost all cases you’ll need to have the assets approved, often by several people. You always revisit to adjust and polish geometry (often for deformation), texture maps, shader values, rigging, skinning.

I could build a game ready 5k model then unwrap it, texture it with diffuse, specular and normal maps, then rig its body and face in a week. If I was building it by reusing and changing a head from one model, a torso from another, legs from another I could probably construct two a week. These would be good enough to go into a game, but would still require more work – but spending a month on a model isn’t necessarily the best option. Spending 4 weeks on it would be a better use of time.

But 4 weeks and a month are the same thing, are they not?

Spending a month on one asset is quite boring. How about spending a block of 2 weeks on it, then another week a few months later adjusting it, then a few months later going back for a week of polishing? That’s a much more likely scenario – and in my opinion it’s also a more effective method that a solid 30 days non-stop.

Technorati Tags: ,

Videogame terminology – What is a SKU?

SKU – Stock Keeping Unit

I few years ago when working at another company, we got emails from management telling us how well we were doing – “And we will be releasing XXX on 4 SKUs”. I had no idea what a SKU was, I asked, and was told it was Stock Keeping Unit. Right, but what does that really mean?

It’s basically a barcode – something that makes it easy for shops to track different versions of the same product.

SKU – Games: game, per platform, per packaging.

If you were to release a game on the Xbox 360 and the PlayStation 3, that would be 2 SKUs. If you were to release it on the PC as a standard
edition and a deluxe edition, that would be 2 SKUs. It’s the same product (more or less), but just gets tracked differently.

Now, I’m also assuming that region releases get their own SKUs too, so an NTSC and a PAL release count as 2 SKUs, but I can’t confirm that. However it works, I’ll still be buying Skate next week.