Script Controllers – Part 1: Manually Creating Script Controllers
To help us build cityscapes and exotic structures, we recently created a tool called the Building Maker, which lets us quickly extrude splines, and stack the resulting shapes. An important feature of the Building Maker is that it lets us modify the height of each section of a building, while making sure that each shape remains stacked. In this part, we’ll look at how to stack shapes manually. In the following parts, we’ll cover how to automate this process in MaxScript. At first glance, the most straightforward way to stack shapes would be to create a sequence of attachment constraints, and attach each shape to the highest face. However, as we walk through this manual process, we’ll discover why using attachment constraints in MaxScript might not be the most convenient solution for our problem. Let’s start by stacking the following three cylinders. Select the middle cylinder, and go to Animation>Constraints>Attachment Constraint. Pick the base cylinder, and press Set Position. Drag the mouse along the surface of the base cylinder to place the middle cylinder. We’ll repeat the same process for the top cylinder. Now, when we change the height of the middle cylinder, the top one stays above it. Likewise, modifying the height of the base cylinder will also move the stacked cylinders above it. Open the Motion panel, and spin the face value up and down. This face value pertains to the shape’s triangular faces, instead of its polygonal faces. We can reveal these triangles by converting it to a mesh, and making the selected edges visible. When we go back to the Motion panel, we can see how the attached shape jumps to each triangular face. In fact, this reveals the first problem with attachment constraints – if we wanted to automate this workflow in MaxScript, we would need to compute the appropriate face index to attach each shape. In addition, the shape’s position would then be described using the face’s barycentric coordinates. However, the bigger problem with constraints is adaptively controlling which shape is supporting the one above it, similarly to the following structure. Notice how the growing stack of rectangles increases the height of the structure, and that the height of the roof is determined by the tallest column. Using attachment constraints, we would have to devise a clever way to dynamically switch each constraint’s weight based on the tallest shape. As a simpler alternative, we’ll use script controllers to achieve this result. Let’s open a new scene and create three boxes. We want to assign a script controller to the green box’s z-position to keep it above either of the tallest cubes below it. With the top box selected, open the motion tab. Expand the “Position” track. You’ll notice that the Z Position is being driven by a Bezier Float controller, which allows for smoothly keyed curves in the Track View Curve Editor. With the Z Position controller selected, press the following button, and select “Float Script”. In the new Script Controller window, we need to start by assigning the controller’s incoming dependencies. This means that when one of the dependencies is changed, a message will be propagated to the script controller, forcing it to update the green box’s z-position. In this case, the z-position of the green box will depend on two variables: the height of the purple box, and the height of the yellow box. Let’s create a variable for the purple box’s height. With the purpleBox variable selected, press the “Assign Track” button. Select the purple box’s height track. If you aren’t sure which box is which, you can select the box in the viewport, which will highlight the appropriate item in the tree. When you press OK, you’ll notice that the purpleBox’s variable is now set to $Box001.Height. Now that we have our purpleBox variable set to the height’s track, we can replace the current Expression with “purpleBox” When you press “Evaluate”, the height of the purple box will now drive the z-position of the green box. So far, we could have done this via parameter wiring, however the distinction comes when we add the yellow box’s height to the controller. Press “Assign Track” to assign it to the yellow box’s height track. To take the yellow box’s height into account, we’ll need to modify our expression. Replace the expression with the following line. Press Evaluate. When you change the height of either box, the green box should now stay above the tallest box. In the expression we’ve just written, “amax” is a MaxScript function, which returns the largest element in the input array, denoted by the pound symbol and the paired parentheses. This input array contains two elements, namely the height tracks of the purple and yellow boxes. Since we’re only using the height values to drive the z-position of the green box, moving either of the boxes will have no effect on its position. To fix that, we can link all the boxes to a common helper object. With the boxes properly linked, we can move them as a single group, and continue adjusting their heights. In the next part, we’ll look at how to create a script controller in MaxScript.