Wednesday, April 20, 2005

4-20-2005 Pushing and Pulling

I think I've got 'pushing' and 'pulling' ironed out. Both activities are based upon the Barslund repulsion algorithm that I picked up from levitated.

The basic algorithm, as implemented in Flash, is:

xDif = _root._xmouse-this._x;
yDif = _root._ymouse-this._y;
distance = Math.sqrt(xDif*xDif+yDif*yDif);
tempX = this._x-(force/distance)*(xDif/distance);
tempY = this._y-(force/distance)*(yDif/distance);
this._x=(homeX-this._x)/2+tempX;
this._y=(homeY-this._y)/2+tempY;

The target object is checked against the mouse coordinates for both the distance and the orientation. Force is a constant set up earlier in the code, for the demo this was set to something like 70. homeX and homeY in the demo are the 'base' position of the object - the result of this equation is to have the object squish away when the mouse gets close to it, orbiting around a fixed point.

I modified this equation extensively, making a method called scoot:
public function scoot(pusherX, pusherY){
xDif = pusherX - this._x;
yDif = pusherY - this._y;
distance = Math.sqrt(xDif*xDif+yDif*yDif);
if((distance <> 10)){
vx -= ((force/distance)*(xDif/distance)) / 15;
vy -= ((force/distance)*(yDif/distance)) / 15;
}
}
The core is similar, but the results are very different. pusherX and pusherY are the coordinates of a pushing object, which in Luminance are the tracking nodes. Every frame, the screen object is moved by its vx and vy values (v is short for velocity). When a node comes within range, in this case less than 120 pixels, it starts to influence the screen object's movement values (vx and vy). The closer the node is, the greater the effect. In this example, I have the alteration (((force/distance)*(yDif/distance))/15) being subtracted from the object's velocity, which causes the object to be 'pushed' from the tracking node; counterintuitive I know, but that's how it works out. Adding the alteration value to the velocity results in a 'pulling' behavior, which can be useful for other segments of Luminance.

I'm not totally certain that I need to divide the alteration value at the end (((force/distance)*(yDif/distance))/15). Without this operation, objects repelled by nodes tended to be flung away at a really high velocity. I think I can simplify the calculation by just reducing the force constant (right now it is set to 125). I'll do that in some later experiments.

To-Do:
  • Figure out how to sort arrays by length. This will let me mod the proximity engine to figure out which cell has the highest population in/around it, which would tell us where to point the eyeball in the opening segment.
  • Come up with a distance checking algorithm to check the neighbors returned by the proximity engine. Pushing and pulling are still a little flaky - nodes are shifting faster than the engine seems to keep up.

Monday, April 18, 2005

4-18-2005 Other Updates

Whoa, been a while.
Been busy, nevertheless. First things first:

Everything has been converted to Actionscript 2.0. All of the screen objects I come across (window tiles, tracking nodes, etc.) are now instantiated from base classes that extend the basic movieClip class. We've already seen substantial improvements in development potential, as well as performance and object management.

The conversion of the tracking nodes to objects was highly successful. I have a few base behaviors assigned to the tracking node primitive, described in the external class file trackingNode.as. At movie startup, a set of 40 tracking nodes are instantiated on the stage, with references to the nodes stored in a master list called nodeList. A function in the main movie parses the incoming XML data and invokes a movement method called setCoord on appropriate nodes, which takes care of moving them around the screen. Another behavior tells each node to hide off of the stage when it gets identical data too many times in a row.

When a node is de-assigned in EyesWeb, its coordinates are not unassigned, they are simply left as their last value. This meant that when EyesWeb lost tracking on a given point in the previous setup, it would appear to get stuck on the stage in Flash, holding position until it was reassigned (the actual explanation is a little more complicated but this is a good enough description of the behavior).

The approach involving setting up listeners in flash to handle object updates didn't exactly pan out, though I managed to get around it. Most of the objects that I have built so far are sort of flaky when their internal 'onEnterFrame' scripts are involved. Directly interacting with an object from the main frame script seems to trigger its internal behavior, though - I'll have to do more experimentation to get more reliable results.

4-18-2005 - the Proximity Manager

The grid-based proximity sensor class by Grant Skinner is implemented and working fine. Caught a bit of a break on that one, though it took some careful study, as Mr. Skinner is a bit stingy on comments in this particular bit of code. It works thusly:

  • A proximity manager object is instantiated. This object is described in the external file ProximityManager.as. When instantiated, a grid size is specified - this size value should multiply evenly into the dimensions of the stage for best results.
  • The proximity manager keeps track of managed objects in a two dimensional array, effectively creating a sort of virtual grid on the stage. When an object is attached to this proximity manager through its addItem method, its coordinates are mathematically approximated to a single cell on this grid, and a reference to that object is stored in that cell of the array.
  • The business end of the proximity manager is its getNeighbours method. The main frame script invokes this method, passing it a reference to (get this) any movie clip in existence on the stage. The manager figures out where the referenced clip would be on its virtual grid, then it builds up a list of all of the managed clips in that cell and all of the neighboring cells. After that, it returns an array full of movie clip references (the neighbors) to whatever called getNeighbours.

We can do other things with this neighbor list once it is received. For example, distance calculations can get somewhat CPU-intensive, especially when you are doing a lot of them (we use the Pythagorean Theorem to determine distance, so we are calculating a lot of square roots). Using Skinner's proximity manager, we can filter out more distant objects using simple, computationally cheap division operations. This saves CPU time for more explicit distance calculations, and ultimately more artwork on the screen.

It should be noted that the grid size specified is pretty important. Let's say you want a 'shark' object to interact whenever it gets within 300 pixels of a 'fish' object. If the grid size is only set to, say, 25, this means that the shark will only really see the fish when it gets inside of 50 pixels. Let me illustrate this a bit better:

Let's say you have three fish swimming around on the stage:



The proximity manager will track references of these objects in a virtual grid, visualized here:



Let's say a shark is added to the stage, so that it looks like this:



Let's say the frame script calls the getNeighbours method of the manager, sending a reference to the shark clip as its parameter. The proximity manager checks all of the surrounding cells, ignoring the rest.



A list of tracked objects in these surrounding cells is built, and sent back as the list of neighbors. In this case, it would return 'fish3' as the only neighbor of 'shark.' If the shark were in the next cell up, the manager would return both 'fish1' and 'fish3' as its neighbors.


Part of the problem, though, is that the cell size is only 40 pixels across. If the shark fell into the lower left corner of one cell, and a fish into the upper right corner of the upper-right neighboring cell, the distance between them would be about 114 pixels. This becomes a problem if I want the shark to react when it is within 200 pixels of a fish; its "sense" range is a square 120 pixels across.

The solution, of course, is to increase the grid size, so that a fish within 200 pixels of the shark will never be outside of the proximity mask. Increasing the grid to this size, though, means that there will be a bit of a dead zone beyond this radius where fish can be returned as neighbors but will technically be over 200 pixels away.



The next step in the solution, then, is to run actual distance calculations on the neighbors returned by the manager, then do some stuff based on whatever passes that test.

Ultimately, this means that the proximity manager is in place to augment distance calculations, not substitute for them.