Articles Archive
Articles Search
Director Wiki
 

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.

Patrick McClellan is Director Online's co-founder. Pat is Vice President, Managing Director for Jack Morton Worldwide, a global experiential marketing company. He is responsible for the San Francisco office, which helps major technology clients to develop marketing communications programs to reach enterprise and consumer audiences.

Copyright 1997-2024, Director Online. Article content copyright by respective authors.