In recorded animation, model variables are set from stored values. A few uses are recording human motion (mouse movement) and recording incremental animation. Recording incremental animation would then let you play it back with the kind of control parametric animation gives. You could...
In this demo, resource geometry is separated from the 3D hierarchy so that the same resource can be used multiple times in the 3D world. One advantage to this is animating the resource will animate all appearances of it—the three discs in the demo below all use the same disc resource.
Drag off objects to rotate group 3D Resource Objects - source movie
The resources in this demo are programmed as trees, and consist of a root node with the faces as its children. To use a resource in the 3D world, the “resource” property of a 3Dnode is made a reference to the root of a resource tree:
With this arrangement rendering involves traversing the 3D world tree, and when a 3Dnode uses a resource, traversing the resource tree and rendering its nodes.
Use of Inheritance
The nodes in the two types of trees in this demo, 3D world tree and resource trees, both need tree-related properties and methods but also need properties and methods unique to each type of tree. For example, nodes in the 3D world tree need a resource property to point to a resource, while nodes in a resource tree need a list of vectors to store geometry. Programming this using inheritance is a good way to go.
To do that, a script called “treeNode” is programmed as a generic tree node, with parent and child properties and methods for managing a tree. This script serves as the ancestor for 3Dnode and resNode, the two types of nodes that make up the 3D world tree and resource trees, respectively. The 3Dnode and resNode scripts contain the properties and methods unique to their respective tree types.
Inheritance is used again for the scripts that automate creation of different resources geometries such as cylinders, planes, cubes, etc. These scripts inherit the resNode script and function as the root of the resource trees. (They also inherit treeNode via resNode).
Animating the Resource
This animation uses a “spring system” which is a bunch of objects that act on each other with spring forces (as if they were connected by springs). The objects in this case are the vectors that specify the outer edge of the disc resource. These vectors are stored in the edgeVerts list of the resDisc object.
Using forces means using the physics model (position, velocity, acceleration):
The disc resource lies in the x-y plane so the wave is created by animating the z coordinates of the edge vectors.
Three spring forces act on each vector, all given by the spring formula (restPosition – position). The first pulls the vector back to its initial position. The second two cause the vectors on either side to pull the vector in their direction, which creates the wave.
The velocity is dampened so the wave eventually dies out.
The wave is started by setting the velocity of a vector (zVelo[i]) to a non-zero number.
•What would happen if only the first spring force was used? What if only the last two were used?
This animation can be done in Shockwave 3D by animating a mesh vertexList.
This demo shows how a 3D hierarchy is built using a tree data structure. It shows how world transforms are calculated from relative transforms, some object-oriented ideas, and how to generate XML from a tree. It has one directional light and a fixed camera.
Drag on object to scale, drag off objects to rotate group 3D World Hierarchy -source movie
Trees are used to model 3D worlds because they reflect how real-world objects are related to each other—objects are composed of smaller objects, which are composed of still smaller objects, etc. In tree terms, an object is the parent of the objects it is made of, which are its children. Each object is programmed as a node in the tree data structure:
In addition, moving an object moves all the parts it is made of. This is accomplished in the 3D model by storing the orientation of each object as an offset of its parent’s orientation. Moving the parent will then move all its children. The absolute orientation of any object in the 3D world is thus determined by a combination of the relative orientations of all its ancestors up to the root of the 3D hierarchy.
In Lingo, the transform data type can be used to store the orientation of each node relative to its parent. It also supports operations that simplify the combining of orientations.
For example, multiplying the transforms of the nodes from the root node to a descendant node gives the world orientation of the descendant node. This 3Dnode method shows how it is done recursively, where trans is the node’s parent-relative transform:
on getWorldTransform() if parent = voidthen return trans –root return parent.getWorldTransform() * trans end
Calculating a node’s world transform recursively
For more on the transform data type, see Director’s Help.
Object Geometry
In Shockwave 3D, the geometry of a visible object is determined by what is called a “resource” which can be used at any number of nodes in the hierarchy.
In this demo there is one type of geometry, the plane, which is rendered using the quad property. Instead of using separate resource objects, resource information is included in each node. Each node is either rendered as a plane or is not visible.
Keeping resource information in the tree has limitations (ie can’t reuse), but simplifies the code for this introductory demo. 3D Resource Objects shows how to program separate resource geometry.
Overview of the code All the code necessary for creating and rendering the 3D world is contained in the two scripts “3Dnode” and “3Drender”. The 3D hierarchy is first built with nodes created from the “3Dnode” script. The root node of the tree is then passed to a new 3Drender object which starts rendering the tree.
This set up process is shown in the setup() handler of the movie script, and is similar to how a Shockwave 3D world is built:
–build 3D tree
root = script(“3dnode”).new(“root”)
cube1 = script(“3dcube”).new(“cube1″,“txtr”,vector(40,40,40))
cube2 = script(“3dcube”).new(“cube2″,“txtr”,vector(40,40,40))
cube3 = script(“3dcube”).new(“cube3″,“txtr”,vector(40,40,40))
The “3Dcube” script shows how a 3Dnode can be extended to make geometry creation easier. 3Dcube isn’t a resource, it is a 3Dnode with a few extra properties and methods added to automate cube creation (notice 3Dcube’s ancestor is 3Dnode).
3Dnode Most of the properties and methods of 3Dnode are tree-related methods for adding, finding, and removing nodes. In addition it has four vectors, a transform, and a sprite which store the node’s 3D-related properties.
If a cast member is passed when creating a new 3Dnode, the node will be rendered as a quad (sprite with quad property set). If no member is passed, the node won’t be rendered and only acts to group the nodes beneath it in the tree.
The getWorldTransform() method shows how world transforms are calculated. The world transform of each node is the product of the parent-relative transforms of the nodes in the path from the root to the node.
The toXml() method recursively generates an XML representation of the tree. This is a simple version that lists the names of the nodes, useful for testing and debugging. The method can be modified to write out enough of the 3D world information that the world can be saved as XML and recreated later.
on toXML(me, _tab) if _tab = voidthen _tab = “”
str = return & _tab & “<” & name if child.count = 0then
str = str & “/>” else
str = str & “>” repeat with c = 1 to child.count
str = str & child[c].toXML(_tab & ” “) end repeat
str = str & return & _tab & “</” & name & “>” end if
return str end
Encoding the tree as XML
3Dcube
3Dcube extends 3Dnode to make creation and manipulation of cubes easier. Since 3Dcube’s ancestor is 3Dnode, it inherits the properties and methods of 3Dnode. It can be considered a 3Dnode with some extra properties and methods for making cubes.
When a new 3Dcube is created, it automatically creates the 3Dnodes for the cube faces and adds them as its children. The dimensions of the cube are determined by the vector passed to the new() method.
The way it is programmed in the demo shows how object-oriented techniques can be used to make cube manipulation easier. Notice that changing a cube vertex (cuVert) will alter all three faces that share that vertex, because those faces all point to the same vector object for that cube vertex.
One note about altering a cube vertex. The statement
cuNode.cuVert.LTF = cuNode.cuVert.LTF * 2
looks like it should stretch the left-top-front corner of the cube, but doesn’t. What happens is it creates a new vector object (vector() * 2) and stores it in cuNode.cuVert.LTF. This doesn’t affect the vector object that the faces point to. To do that, operate on the x, y, and z values of the vector object separately:
cuNode.cuVert.LTF.x = cuNode.cuVert.LTF.x * 2
etc.
After the cube is created, calling setCorners() will resize the cube to the dimensions of the passed vector.
3Drender
This object provides the render method, and stores the camera and light information. The render method traverses the tree and sets the properties of each node’s sprite. The sprite rendering is taken from The Quad Property, with lighting, z-axis blocking, and clipping added.
Instead of using each node’s getWorldTransform() method, the world transforms are calculated as the tree is traversed. If getWorldTransform() were used each node’s world transform would be recalculated for each node in its sub-tree, which is less efficient.
Things To Try New geometries
Extend 3Dnode (a la 3Dcube) to make different geometries.
Separate resources from 3Dnodes
To reuse resources, separate the resource information from the 3D hierarchy. Each resource would be a separate tree, and each 3Dnode would have a resource property that would point to the root of a resource tree (or void).
This would allow the same resource to be used more than once in the 3D hierarchy, and modifying the resource would modify all appearances of it in the 3D world. 3D Resource Objects shows how to program separate resource objects.
Mobile camera
This can be done by performing one more transform in rendering, based on the camera’s transform. For an example see 3D Camera Movement.
The Space Viewport shows how model/display separation makes panning/zooming of a 2D ‘camera’ possible, and also an object-oriented design for implementing it. This design can be built upon to make a variety of games or ‘edutainment.’
Object-Oriented Design for the Model
The model for this simple universe consists of variables that store all aspects of the universe as well as the algorithms that act on those variables. For example, the position of each star and sun as well as the algorithm that makes the ship move.
Organizing these variables and algorithms into a number of scripts that work together is an exercise in object-oriented design. The variables are the properties of objects, and the algorithms are written as methods of objects. (Make sure you’re familiar with the material in Object Oriented Fundamentals.)
The Objects
Each type of element in the universe is programmed as a script (class). So there is one for suns, one for stars, and one for the ship. In addition, there is a script for the universe itself which holds all the elements.
Script “universe”
property width – width of universe (in universe units) property height – height of universe (in universe units) property elements – contains all objects in universe property ship – ship object
on new(me)
width = 250.0
height = 250.0
elements = [] repeat with i = 1to100
elements.add(script(“star”).new(width, height)) end repeat
repeat with i = 1to7
elements.add(script(“sun”).new(width, height)) end repeat
ship = script(“ship”).new(me)
elements.add(ship) return me
end
Use of Inheritance
When you take a look at the properties that the stars, suns, and ship need you notice that they all have some in common. For one, they all need to store their position within the universe, because each is a universe element. These common properties can be inherited from one ancestor script. Inheritance indicates an ‘is a’ relationship.
So in this demo the star, sun, and ship scripts all have as an ancestor the universeElement script. This saves having to duplicate common properties and methods in each of these scripts, which becomes more significant as more scripts and functionality are added to the model.
Public and Private Methods
The methods of the objects are divided into ‘public’ and ‘private’ sections. Public methods are meant to be called from outside the object, while private methods are only called from within. In Lingo this makes it easier to tell how an object is to be used. In formal OO languages like Java these declarations are an integral part of the language.
The Model
The model for this simple universe consists of:
•Dimensions of the 2D space
•Position and dimensions of each element in the universe
•Algorithm to animate ship
•Algorithm to animate suns
Not really much to it. The ship movement algorithm comes from Direction using Sine & Cosine: of Force and Acceleration. In this demo, however, the ship’s position, velocity, and acceleration values are relative to the model coordinate system rather than the screen.
The model coordinate system is not explicitly programmed; there isn’t a block of code that you can point to and say ‘this is the model coordinate system.’ But it is implied in that the properties of the elements in the universe are relative to the same coordinate system and handled as such. These include position, size, velocity, acceleration, etc.
It is also implied in the functions that map model coordinates to screen coordinates. These functions are part of the rendering process, so here I’ll just note that these functions are written so that the model coordinate system is similar to the stage coordinate system. That is, all the universe elements lie in the lower-right quadrant with the positive-y axis downward. This simplifies rendering functions.
In this demo, positions within the model coordinate system use the 3D vector data type even though only the x and y coordinates are used. This makes it easier to differentiate between model versus screen coordinates (which uses point data type) and also makes it easier to later add depth to the model if desired.
Rendering
The bulk of the code in the demo deals with rendering, not the model. The scripts for the universe elements contain code for both model and rendering. The rendering code consists of anything relating to the sprite that represents the model element.
The render script renders the model by setting the location and other properties of the sprites that represent the elements in the model.
The Camera
The ‘camera’ in this demo consists of a location in the model (camVec) and a zoom level (zoomLevel). These values are used in mapping universe coordinates to screen coordinates, with the camera location centered in the view area.
The camera is constrained in a few ways. If ‘follow’ is turned on, the camera follows the ship by simply setting the camera location equal to the ship location. Imagine how much more complicated this would be to do if a model/display technique was not used!
A minimum zoom level keeps the camera from zooming out to where the universe is smaller than the viewport. And the camera’s location is kept a certain margin from the edge of the universe so that space outside the universe is not seen within the viewport. The size of this margin changes with the zoomLevel.
Mapping between coordinate systems
The universe coordinate system is mapped to both the viewport and the radar. In addition, the viewport is mapped to the universe in order to get the universe dimensions for the ‘view area’ box in the radar. And the radar is mapped to the universe in order to position the camera based on a click on the radar.
The mapping functions consist of ’shifting and scaling’. The scaling is done by multiplying by the ratio of the dimensions of one area to another. The shifting is done by adding the difference between the positions of the upper left corner of each area. For the universe that is (0,0), for the viewport and radar it is the stage coordinates of the upper left corner of each.
The methods that contain the mapping functions [uniToView() uniToRadar() viewToUni() radarToUni()] are only called from within the render script and so may be considered ‘private’ methods. However, as complexity is added it is conceivable that other scripts may need access to these methods and so I made them public.
Rendering to the Viewport
Rendering is done on two levels. First, the render script sets properties common to all universe elements. Then it calls each element’s own render() method:
on renderToView() repeat with element in universe.elements
element.sp.loc = uniToView(element.posVec)
element.sp.height = element.height * zoomLevel
element.sp.width = element.width * zoomLevel
element.render() end repeat
end
renderToView() method of “render” script
This way each type of element can make some custom modifications to its sprite, such as the ship and suns setting rotation.
Notice that the sp property and render() method of each element in universe.elements is accessed even though the element variable can be a variety of object types. The element variable points to ship, star, and sun objects in turn. It works because these objects have common properties and methods by inheriting them from the universeElement script (class). This is known in OO jargon as ‘polymorphism’.
This is the reason there is an empty render() method defined in the universeElement script. Scripts that inherit from universeElement can override this method, as do sun and ship scripts. But they don’t need to, as shown by the star script. In either case the call to the render() method of the object is valid.
Rendering to the Radar
This consists of setting the location of the radar ship dot, by making use of the uniToRadar() mapping method.
Also on the radar is the ‘view area’ box, but placing and sizing this box is not technically a part of rendering since the box doesn’t represent anything in the universe model. Instead, the box shows the relationship between the Viewport and the universe as specified by the camera.
To set the box, first the Viewport rectangle is mapped to universe coordinates and then those universe coordinates are mapped to the radar:
on renderToRadar()
spRadarShip.loc = uniToRadar(universe.ship.posVec)
Miscellaneous Single Starfield Image
Another way to program the starfield, which is less processor intensive, is to use one image for the whole starfield rather than using a sprite for each star. Add a “starfield” element to the universe model with a script like this:
propertyancestor
on new(me, uniW, uniH)
me.ancestor = script(“universeElement”).new()
me.posVec = vector(uniW/2, uniH/2, 0)
me.height = uniH
me.width = uniW
me.sp.member = “starFieldMember” return me
end
Tree Data Structure for the Model
For simplicity in this demo, the universe object uses a list to store the elements in the universe. But what if, for example, you wanted to have moons circling planets which circle suns? Or you have several elements you want to animate as a group? Using a tree to store the universe elements makes this type of animation much easier.
This is the technique used for 3D worlds, and it can be used very similarly for 2D. See 3D World Hierarchy for more on using trees in this way. A few of the primary differences between using a list and a tree are:
•instead of iterating through the list, you’ll traverse the tree recursively
•each element’s position is relative to its parent, so its absolute position is found
algorithmically
I’d stick with using the transform data type as in the 3D quad demos. In 2D there will be just a transform to specify position, dropping the vectors used for the quad corners. A bonus is you get rotation and scale built in to the transform type. Take sprite rotation from the transform rotation vector z value (assuming you are working in 2D xy plane), and scale width/height from transform scale vector xy values.
A Rendering Alternative
In this demo, each universe element has a dedicated sprite. When the element is first created it puppets the sprite, sets a few of its properties, and uses it throughout the program.
A different way is to puppet the sprites and set the sprite properties for all the elements each time the universe is rendered, first releasing the sprites used for the last render. This takes more computation but is a cleaner render technique and is a better solution in some cases.
For example, say you had 10 universe models to view alternately with each using 150 sprites to render. This would be easy to do, just instantiate 10 universe objects and set the renderObj.universe property to the one you wanted to view. However, if the sprites are dedicated as in this demo you’d need 1500 sprites puppeted simultaneously. So this alternate way of rendering would be the better solution in this case.
Posted by admin | Posted in Animation | Posted on 25-02-2010
0
In recorded animation, model variables are set from stored values. A few uses are recording human motion (mouse movement) and recording incremental animation. Recording incremental animation would then let you play it back with the kind of control parametric animation gives. You could also get ahold of some motion-capture data and massage it into a form that can be used for playback within Director.
The recording of the animation may be done as part of the program, or done beforehand. If it is done beforehand, the data needs to be saved, most conveniently in a field or text cast member, and then retrieved at run-time. For saving lists in text form, see the Lingo functions string() and value().
Playback of recorded motion is essentially a parametric animation, with the parameter driving an index into the list of recorded values.
This demo allows the user to record a piece of incremental animation, and then play it back with a slider driver. The animation is a combination of collision and general gravity.
on startRecording()
recordList = [:]
repeat with sp in instanceList
recordList[sp] = []
end repeat
state = #recording
end
on record()
repeat with sp in instanceList
recordList[sp].add([#x: sp.x, #y: sp.y])
end repeat
end
on playBack(p) — p:0->1
totalPos = recordList[1].count
index = integer(p * (totalPos – 1) + 1) –index:1->totalPos
repeat with sp in instanceList
pos = recordList[sp][index]
sp.x = pos.x
sp.y = pos.y
end repeat
end
Methods from script “modelManager”
In this demo the recording is done by a script adapted from the modelRate script in Independent Model Rate, and renamed modelManager. The three methods above show how it does recording and playback.
Notice that the variables recorded are the animation model variables, not the sprite properties. This is generally the best way to go, for several reasons:
less information to record. For example, in 3D the x, y, and z coordinates are used to set more than three sprite properties. Of course, this means that during playback a rendering algorithm will still be needed.
more importantly, at playback you still have control over rendering.
The variables are recorded once each movie frame in this demo, rather than once each model frame. In this demo the model rate is about 20 times the movie rate. If you wanted to do “super slo-mo” playback, you could record the variables based on model frames, and at slow playback you’ll get a smoother motion.
To record the movement of the mouse, use a script that keeps its x and y animation model variables at the mouse location, and pass the script instance to modelManager’s addInstance() method.
Miscellaneous Points recordList
This is the property list used by modelManager to record the x and y variables of each recorded object. The unusual thing about it is that the keys in the list are object references. recordList associates each object reference with the linear list of recorded position settings for that object.
Usually in sample code the keys are symbols, but the property list can be used to associate values of any data type with each other. This is generally called a hash table, and most likely property lists are implemented in Director using a hash table of some sort.
If recordList is saved in a text cast member, retrieving the list using value() will not recreate object references. To save recordList, use something like sprite numbers for the keys instead of references.
Animation Anomalies
If you watch the animation long enough you’ll notice some interesting anomalies in the behavior of the objects. When I have time I’ll try to pinpoint what is happening. There may or may not be a perfect fix.
Things To Try
Make the playback “instant replay” style by using a time driver instead of a slider to control the playback. You might use a slider to control the speed of the replay.
Posted by admin | Posted in Lingo | Posted on 20-02-2010
0
The sprite.quad property is a list of four points that specify the screen coordinates of the corners of a sprite. If the quad property is set in Lingo, Director will distort the sprite accordingly. This allows the rendering of 3D rotation of a sprite.
Drag on the screen to spin the object Quad property – source movie
property sp
property verts
property trans
property rotVelo
on beginsprite(me)
sp = sprite(me.spritenum)
verts = []
verts[1] = vector(-1,1,0)
verts[2] = vector(1,1,0)
verts[3] = vector(1,-1,0)
verts[4] = vector(-1,-1,0)
trans = transform()
trans.scale = vector(150,150,1)
rotVelo = vector(.3,.2,0)
end
on exitframe()
modelFrame()
render()
end
on modelFrame()
trans.rotate(rotVelo)
end
on render()
eyez = 500
ptList = []
repeat with v = 1 to 4
vec = trans * verts[v]
persp = eyez / (vec.z + eyez)
pt = point(vec.x * persp, vec.y * persp) + stageCenter
ptList.add(pt)
end repeat
sp.quad = ptList
end
The Model
The animation model in this demo consists of four vectors, a transform, and a rotational velocity. The four vectors specify the corners of the object, and are analogous to a “model resource”. The transform is used to specify the scale, translation, and rotation of the object. Each model frame, the transform is rotated by a vector that specifies rotational velocity.
The model could have been stored in four vectors that specify 3D world coordinates, without using a transform. But keeping the resource information and transform separate makes transforming easier, and makes it easier to animate the corners with respect to each other.
•How would you change the “registration point” of the plane?
Rendering
Multiplying each resource vector by the transform gives the 3D world coordinates of the corners. The perspective equation is then used to map the 3D points to 2D. The 2D points are put in a list which is used to set the sprite’s quad property. The order of the points is clockwise from upper-left for right side up facing the viewer, which is the reason for the order of the resource vectors.
The next section shows how to combine quads to make 3D objects.
This demo builds on the first version by giving universe elements a depth and rendering with some depth cues, and also storing the universe model in a tree rather than a list.
Depth & Perspective
The field of action is still two dimensional, but adding depth makes the visuals more interesting. It’s pretty simple to do. Each element is given a z coordinate which is used for perspective and for blendlevel (haze) and locz (z-axis blocking).
Notice how the method uniToView() which maps universe to screen coordinates now makes use of the perspective value. First the offset from the camera is found (vec – camVec), then perspective is applied, then zoom and shifting:
Tree Structure for the Model
Instead of being stored in a list, the objects of the universe are now stored as a tree. The universe object is the tree root, and each object’s position is relative to its parent. The circling planets show how the tree structure simplifies the programming—the planet script doesn’t need to take into account how the sun fits into the rest of the scene. For more on using a tree and transforms in animation see 3D World and the other 3D quad demos.
Migrating to a tree structure was made much easier by inheritance. Simply making the treeNode script the ancestor of universeElement gave all the model objects the properties and methods of a tree node.
This demo is a relatively rare example of the use in Lingo of a series of inheritance, where a script’s ancestor itself has an ancestor. For example, ship inherits universeElement which inherits treeNode. Does it make a difference if universeElement and treeNode are reversed in the order? It wouldn’t to the ship script, but there are reasons to arrange it the way it is. Abstractly, according to the “is a” relationship of inheritance, universeElement is a treeNode but treeNode is not a universeElement. Practically, if you wanted a node just for grouping in the model you can now just use a universeElement object (because it inherits from treeNode).
Intro
Data structures store data in a way that reflects some relationship between the data, and is easily useable in a program. Lingo provides some built-in data structures such as lists. This section describes how data structures are created using objects, and some of their common uses in animation. Make sure you’re familiar with the material in Object-Oriented Fundamentals.
The two types of structures covered here are lists and trees, which are both very common and useful. Lingo provides an implementation of lists already; this section shows how to make lists using objects as an intro to making trees.
Uses for lists are fairly obvious. Some uses for trees:
working with XML, HTML, etc, which are hierarchical document formats
hierarchical displays (such as a file system display)
decision trees
and many others
Terminology
An object data structure is made of objects that are created from the same script (class). When these objects are assembled into a data structure they are often called nodes of the data structure.
Each node will have properties that are references to other nodes. These object references are what bind the data structure together and are called pointers. How the nodes are arranged using these pointers determines the type of data structure created.
In the diagrams of this section, a square represents a node and an arrow represents a pointer.
Lists
A list is a sequence of nodes. Each node has a pointer to the next node in the list. The nodes in this diagram are created from a script called “listNode”.
To store data in a list, each node has a property or properties that store the data.
The script shown below can be used for the nodes of a list data structure. It has methods for adding a node to the end of the list, and for getting a node at a given position. The property key stores some type of data.
property nextNode
property key
on new(me, _key)
key = _key
return me
end
on addNode(me, _key)
if nextNode = void then
nextNode = script(“listNode”).new(_key)
else
nextNode.addNode(_key)
end if
end
on getNodeAt(me, index)
if index = 1 then return me
if nextNode = void then return void –index out of range
return nextNode.getNodeAt(index – 1)
end if
script “listNode”
Recursion
It might appear strange that the addNode() and getNodeAt() methods in the listNode script actually call themselves. This programming technique is known as recursion, and is essential for making efficient use of lists and trees.
To understand what is happening, trace the execution from when the method is called on the first node in the list. Each time the method calls itself it moves one node down the list, which is called “traversing” the list. Eventually, the execution reaches what is called a base case at which point there are no further recursive method calls.
In the addNode() method the base case is nextNode = void, which means the end of the list has been reached and the new node can be added.
In the getNodeAt() method there are two base cases. Either the desired node is reached (index = 1) or the end of the list is reached before finding the desired node (nextNode = void). When the getNodeAt() method reaches its base case, the result is returned back through the stack of recursive method calls.
Recursion is useful for working with lists because a list is a recursive data structure. That is, a list is defined in terms of itself. The recursive definition of a list is: “a list is a node followed by a list, or void.”
Try writing recursive methods for the listNode script that:
delete the last node in the list
delete a node at a given position
add a node at a given position
return the node whose key matches a given value
return the number of nodes in the list
return a string that contains the keys of the nodes
return the sum of the keys of the nodes (if key is numeric)
Some of these methods are easier to write if each node also has a pointer to the previous node in the list. Such a list is called a “double-linked” list. A listNode script that contains the methods above can be downloaded here. It doesn’t support deleting the first node in the list for the sake of simplicity (how would you do it? Hint: it requires a list implementation using two scripts).
Trees
A tree is also a recursive data structure. It’s similar to a list, except that it can have more than one “next node”. Usually it is drawn vertically to emphasize its heirarchical nature, with next nodes called ‘children’ and the previous node called the ‘parent’.
The top node of the tree is usually called the ‘root’, and is the only node that has no parent. The bottom nodes, which have no children, are called ‘leaves’. Following successive parent pointers from a node will eventually reach the root, and the nodes on this path are the ancestors of the node (not to be confused with the script ancestor property used for inheritance).
Each node in the tree is the root of a sub-tree. A sub-tree can be described as “the sub-tree rooted at this node”.
property parent
property child
property a –numerical
on new(me, _a)
child = []
a = _a
return me
end
on addChild(me, node)
child.add(node)
node.parent = me
end
on addTo(me, b)
a = a + b
repeat with ch in child
ch.addTo(b)
end repeat
end
on sumTree()
sum = a
repeat with ch in child
sum = sum + ch.sumTree()
end repeat
return sum
end
Sample tree node script that stores a single number
The script above can be used for the nodes of a tree that stores a single number at each node. Like the methods that traverse the list data structure, a recursive method can be written to traverse and process a tree data structure. The example methods addTo() and sumTree() both traverse the tree.
n.addTo(num) increments by num the value in each node in the sub-tree rooted at n. If n is the root of the tree, all nodes in the tree are incremented.
n.sumTree() returns the sum of the values stored in the nodes in the sub-tree rooted at n. If n is the root of the tree, the sum of all nodes in the tree is returned.
These recursive methods visit the nodes in the tree in this order:
This is called a ‘depth-first’ traversal since it travels to the bottom of the tree first, rather than processing one level before going to the next.
Here is another tree node script that shows some more recursive tree methods
Posted by admin | Posted in Blogging | Posted on 13-04-2009
0
Animation Math in Lingo™Animation Math in Lingo™
Welcome
This is a guide to programming animation with Director’s Lingo. It’s a series that progresses in textbook fashion from simple to advanced animation programming. The three types of generating animation covered are incremental, parametric, and recorded. In addition there are some sections on important topics like model/display separation, object-oriented programming, and 3D concepts.
Starting Up
No previous knowledge of animation techniques is assumed, although you should be familiar with lingo basics: handlers, variables, repeat loops, if/then, sprite properties like loch/locv, attaching scripts to sprites, and how to get around in the Director environment.
Sample Code
Code from the demos may be reused freely for educational purposes. For use in commercial products, see the licensing page. A few of the more labor-intensive scripts are downloadable for a small charge, which includes commercial license. A note about copy/paste of code off the pages—some invisible characters cause script errors in Director, so it’s best to get the code from the source movie.