Spinning virtual objects
October 19, 1998
by Pat McClellan
Dear Multimedia Handyman,
Let us say that you have a virtual object that you want to spin around using four buttons (left, right, up, down) and the virtual object is created by a series of flat images of its different angles. When you hold the mouse down on any of these buttons you want the images to be swapped out so the object appears to spin in that directions until you let the mouse up. If it's held down, the object continues to turn 360 degrees, or if you just click down once, the object will turn only one notch at a time. Is there a pure lingo answer to this?
Richard Dunn
Richard,
It's easy to create a behavior which will cycle through images of an object spinning on a single axis. It gets a little trickier to add a cycle for a second axis... but we'll get it done.
First let's look at the concept behind a single axis cycle. The idea is that when a sprite is told to move to the next image, it adds 1 to its memberNum. That works fine until you reach the last image in the sequence (when you've run out of "next" images), so you'd need to test to see if the image is the last image in the sequence and if so, reset the image to the first in the sequence. Likewise, when the sprite receives the message to go to the previous image, it will subtract 1 from its memberNum. Again, you'd need to test to see if the image is the first in the sequence, and if so reset it to the last image.
So how the heck do we add the cycle for a second axis? Let's talk about how the images would appear in our cast. Assume that we have the object rotating on the vertical in 30 degree increments. That means 12 images are required to rotate the full 360. Now, we'll need 12 images for each incremental rotation on the horizontal axis. For this example, let's say that the object rotates in 40 degree increments on the horizontal axis -- giving us 9 steps in a rotation. Multiply 9 times 12 to yield a total of 108 cast members. For visualization purposes, adjust your cast window so that you have all 108 members displayed -- 12 columns across by 9 rows down. Moving from one member to the next across each row should rotate the object on the vertical axis. Moving down each column should rotate the object on the horizontal axis.
For my demo movie, I didn't create 3D renderings. Instead, I simply created images corresponding to the rotation on a clock face. To simulate the second axis, I changed colors from one row to the next.
Incrementing the images moving from one column to the next is as simple as adding 1 to the memberNum. However, moving from one row up or down to the next will require us to add or subtract 12. Twelve is the actual number for our example -- more generically speaking, we need to add or subtract the number of columns.
Now, let's write some behaviors. I'll write one behavior which can be applied to any of the four buttons. Since up, down, left and right relate to what images you are portraying, I'll use different names for the buttons which are more function related. Let's call them ColumnPlus, ColumnMinus, RowPlus, and RowMinus. When the ColumnPlus button is pressed, for example, it will send out a message to cycle to the image one column to the right. Likewise, RowMinus will send the message to swap in the image one row up.
-- cycleSwap Button Behavior -- copyright © 1998 ZZP Online, LLC -- attach to the buttons property pButton -- which button property pButtonDown -- button down state on getPropertyDescriptionList me set pdlist to [:] set buttons = [#RowPlus, #RowMinus, #ColumnPlus, ¬ #ColumnMinus] addprop pdlist, #pButton, [#comment:"Which Button",¬ #format:#symbol, #default:1, #range:buttons] return pdlist end getPropertyDescriptionList on beginSprite me set pButtonDown = FALSE end on mouseDown me set pButtonDown = TRUE end on mouseUp me set pButtonDown = FALSE end on mouseUpOutside me set pButtonDown = FALSE end mouseUpOutside on exitFrame me if pButtonDown then sendAllSprites(#cycleSwap, pButton) end if end
Notice in the button behavior, mouseDown and mouseUp change the value for the pButtonDown property -- and that's all. The "action" happens in the exitFrame handler, which checks to see if pButtonDown is TRUE, and if so, it sends out the message. This allows us to control the frequency of those messages. The alternative is to have the message command in a repeat while stillDown loop. This approach might be better for giving smoother, faster animation, but you don't have any much control over the frequency of the repeat. (Additionally, repeat loops prevent background processes from being serviced, so we like to avoid them.)
Also notice that the button behavior above doesn't know or care how many rows or columns of images are involved. All it does is send out a generic message, calling for the cycleSwap handler and telling which button was pressed.
We'll write a second behavior which we'll attach to the sprite that cycles the images. It assumes that you have started with the very first image in your sequence (row 1, column 1). This behavior will have to know how many rows and columns of images there are (pColCount and pRowCount), as well as which row and column are currently displayed (pRow, pCol). Each time this behavior receives a message from the buttons, we'll use a case statement to decide whether to increment or decrement pRow or pCol. Then, we'll use a formula to translate pRow and pCol into the correct memberNum for display.
-- cycleSwap Sprite Behavior -- copyright © 1998 ZZP Online, LLC -- attach to the sprite which will cycle property pColCount, pRowCount property pSprite, pMemNum, pCastNum property pRow, pCol on getPropertyDescriptionList me set pdlist to [:] addprop pdlist, #pColCount, [#comment:"How many ¬ columns?", #format:#integer, #default:12] addprop pdlist, #pRowCount, [#comment:"How many ¬ rows?", #format:#integer, #default:12] return pdlist end getPropertyDescriptionList on beginSprite me set pSprite = the spriteNum of me set pMemNum = the memberNum of sprite pSprite set pCastNum = the castLibNum of sprite pSprite set pRow = 1 set pCol = 1 end on cycleSwap me, whichButton case whichButton of #RowPlus: if pRow = pRowCount then set pRow = 1 else set pRow = pRow + 1 end if #RowMinus: if pRow = 1 then set pRow = pRowCount else set pRow = pRow - 1 end if #ColumnPlus: if pCol = pColCount then set pCol = 1 else set pCol = pCol + 1 end if #ColumnMinus: if pCol = 1 then set pCol = pColCount else set pCol = pCol - 1 end if end case set newMem = pMemNum + (pCol - 1) + ((pRow - 1) ¬ * pColCount) set the member of sprite pSprite to member newMem ¬ of castLib pCastNum end
The case statement is straightforward, so I won't go into that. Let's look at the set newMem line. The formula starts with the original memberNum and adds to it any additional columns which have been incremented (pCol - 1). The minus 1 compensates for the fact that our starting member was in column 1, not 0. To add rows, we must multiply incremented rows (pRow - 1) by the number of columns (pColcount). That gives us the new memberNum.
There's the code... I'm sure your graphics will better represent the functionality! Good luck with your program.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.