search

Quick Tip: How to Debug an AS3 Error #1203

In this Quick Tip, we’ll discuss the confusing AS3 error 1203, “no constructor found”, which crops up without provocation and is rather unfortunately worded. We’ll examine the scenario in which it will appear, and how to fix it.


Step 1: An Obligatory But Somewhat Useless Description of Error 1203

First, here is the language of the error:

1203: No default constructor found in base class flash.display:BitmapData.

It’s difficult to break this down into plain English. So let’s next move on to a scenario that produces this error.


Step 2: A Busted Project

You can find this project in the download package, in the folder “busted“. It consists of the following (which you can use to re-create this project if you prefer a hands-on approach):

  1. A FLA named Busted.fla. It is empty, but is linked to the document class described next.
  2. A document class named Busted.as. This extends Sprite and simply creates an instance of the class described next, and adds it to the stage.
    The contents of the class are as follows:

        package {
            import flash.display.*;
            public class Busted extends Sprite {
                public function Busted() {
                    var bmp:Bitmap = new Bitmap(new BustedBitmap());
                    addChild(bmp);
                }
            }
        }
    

    The BustedBitmap class extends BitmapData, and so we use it to feed a Bitmap object, which is then added to the stage.

  3. Another class file named BustedBitmap.as. As mentioned previously, this extends BitmapData, and its purpose is to be a BitmapData that automatically supplies itself with some Perlin noise. Its contents are:

        package {
            import flash.display.*;
            public class BustedBitmap extends BitmapData {
                public function BustedBitmap() {
                    perlinNoise(100, 100, 2, 1024, false, false, 7, true);
                }
            }
        }
    

    So, in the BustedBitmap constructor, we call perlinNoise() and thus we automatically have something interesting in the pixel data, so we can see something right away when displaying on the stage.

Now, go ahead and test the movie. You will get Error 1203, pointed at line 4 of BustedBitmap.as. And line 4 is…


public function BustedBitmap() {

Step 3: What’s All This, Then?

So what happened? The big thing to keep in mind is that we’re writing a class that is a subclass of another class (BitmapData in this case). Also keep in mind that when a class is instantiated, the constructor is called as part of the “new” process.

If you do not actually write a constructor, ActionScript will imply one for you. It will look like this:

public function [NameOfClass]() {

}

Where “[NameOfClass]” will match the name of your class.

Similarly, if you write a subclass, and don’t call super() in it, then ActionScript will imply that call for you, as well. It will look like this:

public function [NameOfSubclass]() {
    super();
    // Other explicit code in subclass constructor
}

Note that it happens on the first line, and is simply a call to super with no arguments.

Finally, we get to the root of the problem. If your subclass omits an explicit call to super, and your superclass’s constructor has required parameters, then your implicit call to super does not supply the required parameters. You’d think that an argument mismatch error could catch this, but consider that since there isn’t any code actually written to make the mismatch, ActionScript can only complain that the lack of a default constructor.

In our example, BustedBitmap extend BitmapData, which has a constructor defined in the documentation as so:

BitmapData(width:int, height:int, transparent:Boolean = true, fillColor:uint = 0xFFFFFFFF)

The first two parameters are required, and we’re not supplying them.


Step 4: It’s as Simple As…

The solution is, once you understand the problem, extremely simple. You need to explicitly call the super constructor, at least if there are required parameters. In other words, in our BustedBitmap class, this following highlighted line will fix the problem:

package {
    import flash.display.*;
    public class BustedBitmap extends BitmapData {
        public function BustedBitmap() {
            super(600, 400);
            perlinNoise(100, 100, 2, 1024, false, false, 7, true);
        }
    }
}

And that will do it.

As a teachable moment, I’d like to add that, when extending another class, it’s generally a good idea to include the explicit call to super. Even if there are no parameters at all, required or otherwise (as in Sprite or MovieClip), an explicit call can declare intention and awareness of the nature of the subclass. More practically, explicit calls give you an opportunity to run some code prior to the call to super, which may be helpful in certain situations.


Step 5: …And That Is All

Yet another error demystified; the challenge for this one is that the real cause of the error is quite buried. Once you become handy with a shovel, though, the fix is easy enough.

Thanks for reading, and be ready for more debugging tips.



View full post on Activetuts+

Quick Tip: Implementing the Bubble Sort in AS3

In this Quick Tip, I’ll show you how and why the Bubble Sort algorithm works, and how to implement it in AS3. You’ll end up with a class that you can use in any Flash project, for sorting any array.


Final Result Preview

Here’s a simple demo of the result of the bubble sort algorithm:

Of course, this SWF doesn’t prove much on its own! Grab the source files and you can edit the input array yourself.


Step 1: Creating the BubbleSort Class

As this algorithm will be used more than once, it’s a good idea to create a class for it, so that we can easily use it in any AS3 project:

Set up a basic Flash project, and inside the project folder create a file BubbleSort.as. (We will create a tester file here too, so we can test it.)

If you dont know how to work with classes please check this tutorial: How to Use a Document Class in Flash.

We wont need the constructor, so get rid of it! Your class should look like this:

package
{

	public class BubbleSort
	{

	}

}

Step 2: How the Algorithm Works

This algorithm is not the fastest or most efficient method of sorting a series of numbers, but it is the easiest one to understand.

This image sums it up; at every stage, each pair of numbers are compared, starting from the end, and swapped (by means of a “spare” temp variable) if they are in the wrong order.

Once all consecutive pairs have been checked, this guarantees that the number at the start is the largest number in the sequence; we then repeat, checking every pair of numbers apart from the number at the start. Once all consecutive pairs have been checked, we know that the first two numbers in the sequence are in the correct order (they are the largest and second-largest). We keep going until we’ve put every number in the correct order.

descending bubble sort

It’s called the “bubble sort” because, on each pass through the array, the biggest number “floats” to the top of the array, like a bubble in water.

Lets start writing the code. We’ll call the main function bsort():

package
{

	public class BubbleSort
	{
		public function bsort(arr:Array,sortType:String):Array
		{
			var temp:String;
			if(sortType.toLocaleLowerCase() == "descending")
			{

			}
			else if(sortType.toLocaleLowerCase() == "ascending")
			{

			}
			else
				throw new Error("You have a typo when Calling the bsort() function, please use 'ascending' or 'descending' for sortType!");

			return arr;
		}

	}

}

The function gets two parameters. The first parameter, arr, will be the array to be sorted; the second paramter, sortType will be used to decide if the user wants the array to be sorted in ascending or descending order.

In the function we declare a temp variable which will hold the elements of the array in case we need to swap the two elements. You may wonder why it is not a Number. It’s because our class will be able to handle String arrays too, sorting them alphabetically; we can convert numbers to strings and back again, but we can’t convert strings to numbers and back again, so we use a String for this variable, just to be safe.

We use an if-else block to split our code into two branches, depending on which direction the user wants to sort in. (If the user does not provide a valid choice, the program will fire an error.)

The difference between the code in either branch will be just one character: either < or >.

Let’s write the algorithm. We start with the descending part:

package
{

	public class BubbleSort
	{
		public function bsort(arr:Array,sortType:String):Array
		{
			var temp:String;
			if(sortType.toLocaleLowerCase() == "descending")
			{
				for(var i:uint=0; i < arr.length; i++)
				{
					for(var j:uint=arr.length-1; j > i; j--)
					{

					}
				}
			}
			else if(sortType.toLocaleLowerCase() == "ascending")
			{

			}
			else
				throw new Error("You have a typo when Calling the bsort() function, please use 'ascending' or 'descending' for sortType!");

			return arr;
		}

	}

}

As you can see, we’re using nested for loops. One goes from the first element to the last element of the array; the other goes backwards.

Let’s inspect the inner “j” loop first. As the earlier diagram shows, we begin by comparing the last two elements of the array, which are arr[j-1] and arr[j] (in the first iteration). If arr[j-1] is less than arr[j] they need to be swapped.

In either case we subtract one from j (through the “j--” call in line 131), which changes which pair of numbers will be compared on the next loop.

j starts at a value of arr.length-1, and ends with a value of 1, meaning that the inner for loop checks every consecutive pair, starting with the last pair (where j equals arr.length-1) and ending with the first pair (where j equals 1).

Now let’s look at the outer “i” loop. Once all the pairs have been checked and swapped as necessary, i is increased (through the “i++” call in line 129). This means that, the next time round, j will start at arr.length-1 again, but end at 2 this time – meaning that the first pair in the sequence will not be checked or swapped. This is exactly what we want, since we know that the first number is in the correct position.

As it goes on, eventually there will be only two elements that need to be checked in the inner loop. Once they’re done, we know we’ve sorted the array!

Here’s what that looks like in code:

				for(var i:uint=0; i<arr.length;i++)
				{
					for(var j:uint=arr.length-1; j > i; j--)
					{
						if (arr[j-1] < arr[j])
						{
							temp = arr[j-1];
							arr[j-1] = arr[j];
							arr[j] = temp;
						}
					}
				}

And the algorithm is ready!

Now we can use the same logic to create the ascending sort:

We need only to change the comparison operator in the if block of the inner loop:

package
{

	public class BubbleSort
	{

		public function bsort(arr:Array,sortType:String):Array
		{
			var temp:String;
			if(sortType.toLocaleLowerCase() == "descending")
			{
				for(var i:uint=0; i<arr.length;i++)
				{
					for(var j:uint=arr.length-1; j > i; j--)
					{
						if (arr[j-1] < arr[j])
						{
							temp = arr[j-1];
							arr[j-1] = arr[j];
							arr[j] = temp;
						}
					}
				}
			}
			else if(sortType.toLocaleLowerCase() == "ascending")
			{
				for(var k:uint=0; k<arr.length;k++)
				{
					for(var l:uint=arr.length-1; l > k; l--)
					{
						if (arr[l-1] > arr[l])
						{
							temp = arr[l-1];
							arr[l-1] = arr[l];
							arr[l] = temp;
						}
					}
				}
			}
			else
			{
				throw new Error("You have a typo when Calling the bsort() function, please use 'ascending' or 'descending' for sortType!");
			}

			return arr;
		}
	}

}

Step 3: Creating the Tester Application

Create a new flash file, tester.fla, in the same folder as BubbleSort.as. Create two dynamic text fields, name one input_arr and the other one output_arr.

tutorial tester.fla look

After creating the appearance, we must create and link the document class.

Create a file Tester.as and link this to tester.fla

Tester properties Document Class

Now we can finally use our class in the Tester.as:

package
{
	import BubbleSort;
	import flash.display.MovieClip;

	public class Tester extends MovieClip {

		private var bs:BubbleSort = new BubbleSort();

		public function Tester() {
			var ar:Array = [5,7,9,8,1,3,6,2,4,5,0];
			input_arr.text = ar.toString();
			ar = bs.bsort(ar,"descending");
			output_arr.text = ar.toString();
		}
	}
}

In this line, we call the bsort() function of our variable bs (which is an instance of BubbleSort):

ar = bs.bsort(ar,"ascending");

This function returns an array, so we can assign this as the new value of our original input array.

Save everything and try your work out.


Conclusion

In this tutorial we created a function to help us sort an Array. We could improve the efficiency; for more on that, you can read Wikipedia – Bubble Sort

If you really wish to see how fast this algorithm is compared to the other options (like quicksort), take a look at sorting-algorithms.com.



View full post on Activetuts+

Quick Tip: Collision Reaction Between a Circle and a Line Segment

In the previous Quick Tips, we’ve looked at collision detection – essentially, detecting that two shapes have overlapped. Now, we’re ready to look at collision reaction: making something happen due to a collision. In this Quick Tip, we’ll look at the reactions of reflection and sliding.


Final Result Preview

Let’s look at the end result we’ll achieve at the end of this tutorial. Each Flash demo has a restart button; click it to reset the position of the circles at the top of stage.

The first demo shows off reflection:

The second shows sliding:


Step 1: The Reflection Formula

I’ve run through this topic several rounds with students, and experience has taught me that the head-on approach of explaining vector math to freshers results in blank faces and confused minds. So instead of putting up a Math lecture here, I shall refer those who are interested in investigating this topic further to Wolfram’s page on reflection.

Here, I shall simplify my explanations with diagrams below. Recall vector addition:

Vector addition

Now, observe the diagram below. A is the circle’s velocity before a collision, and A’ is its velocity after the collision.

Reflection

It’s obvious that A' = A + 2 V(Ap), where V(Ap) represents the vector with a magnitude of Ap, in the direction of the left normal. (You can see this by following the dashed lines.)

In order to obtain V(Ap), we shall project A onto the left normal.


Step 2: Implementation

Here comes the ActionScript implementation of reflection. I’ve highlighted the important parts. Line 67 – 69 is to calculate V(Ap) (v_leftNormSeg2) and line 70 implements the formula. You may refer to the full Actionscript under Reaction1.as.

(You should recognise most of the code from the previous Quick Tip.)

private function refresh(e:Event):void {
	for (var i:int = 0; i < circles.length; i++) {

		//calculating line's perpendicular distance to ball
		var c1_circle:Vector2D = new Vector2D(circles[i].x - x1, circles[i].y - y1);
		var c1_circle_onNormal:Number = c1_circle.projectionOn(leftNormal);
		var c1_circle_onLine:Number = c1_circle.projectionOn(line); 

		//if collision happened, undo movement
		if (Math.abs(c1_circle_onNormal) <= circles[i].radius
			&& line.dotProduct(c1_circle) > 0
			&& c1_circle_onLine < line.getMagnitude()){

			//redefine velocity
			var v_leftNormSeg2:Vector2D = leftNormal.clone();
			var leftNormSeg2_mag:Number = Math.abs(velos[i].projectionOn(leftNormal))
			v_leftNormSeg2.setMagnitude(leftNormSeg2_mag);
			velos[i] = velos[i].add(v_leftNormSeg2.multiply(2));
		}
	circles[i].x += velos[i].x;
	circles[i].y += velos[i].y;
	}
}

Step 3: An Interactive Version

Take note that this reflection formula is applicable to line of any gradient. In fact, you can program your line to be adjustable at runtime and see it reflecting circles like the Flash presentation below. Just click and drag near the lower end of the to redefine it.


Step 4: Sliding Along Line

The concept of sliding along the line is almost identical to reflection. Observe the diagram below.

Sliding along

The vector of slide is A' = A + V(Ap) with V(Ap) representing a vector with magnitude of Ap. Again, to obtain Ap we shall project A onto the left normal.

Note that as the circle is sliding along the line, it is colliding with the line. Of course, collision points differ among circles that collide onto line, so some overlap the line as they move along it. This doesn’t look good, so we’ll have to reposition them.


Step 5: Redefine Location

Now, let’s reposition circles on the line while maintaining their contact with line. Refer to the diagram below.

Reposition circle on line

An important variable to calculate is the projection of A along line. The radius of circle is readily available, and we already have B, so we can form the vectors of B and C. Adding the two will give us A, the exact location to reposition circle. Simple!

Vector calculation of the exact location

The Flash presentation below is coded according to the mentioned idea. But there is one problem: the circles jitter along the line.

There’s one final detail we missed. Diagram above shows magnitude of C should be equivalent to radius of circle. However, this will position circle back above the line. Since there’s no collision detected there, the circle will fall onto the line again, which in turn will flag the collision detection and cause the circle to be repositioned.

This cycle will repeat until the is past the end of the line segment; the visual result of this cycle is the jittering effect.

The solution to this problem is to set the magnitude of C to slightly less than the radius of the circle: (radius of circle - 1), say. Observe the Flash demo below which uses this idea:


Step 6: Implementation

So here’s the important ActionScript snippet for sliding along the line. I’ve highlighted the important parts.

private function refresh(e:Event):void {
	for (var i:int = 0; i < circles.length; i++) {

		//calculating line's perpendicular distance to ball
		var c1_circle:Vector2D = new Vector2D(circles[i].x - x1, circles[i].y - y1);
		var c1_circle_onNormal:Number = c1_circle.projectionOn(leftNormal);
		var c1_circle_onLine:Number = c1_circle.projectionOn(line); 

		//check for collision
		if (Math.abs(c1_circle_onNormal) <= circles[i].radius){

			//check if within segment
			//if within segment, reposition and recalculate velocity
			if (line.dotProduct(c1_circle) > 0 && c1_circle_onLine < line.getMagnitude()) {

				//repostion circle
				var v_lineSeg:Vector2D = line.clone();
				v_lineSeg.setMagnitude(c1_circle_onLine);
				var v_leftNormSeg1:Vector2D = leftNormal.clone();
				v_leftNormSeg1.setMagnitude(circles[i].radius - 1);
				//v_leftNormSeg1.setMagnitude(circles[i].radius); //uncomment this to check out the error: jittering effect

				var reposition:Vector2D = v_lineSeg.add(v_leftNormSeg1)
				circles[i].x = x1+reposition.x;
				circles[i].y = y1+reposition.y;

				//redefine velocity
				var v_leftNormSeg2:Vector2D = leftNormal.clone();
				var leftNormSeg2_mag:Number = Math.abs(velos[i].projectionOn(leftNormal))
				v_leftNormSeg2.setMagnitude(leftNormSeg2_mag);
				var veloAlongLine:Vector2D = velos[i].add(v_leftNormSeg2);

				circles[i].x += veloAlongLine.x;
				circles[i].y += veloAlongLine.y;
			}

			//if not in segment (e.g. slide out of segment), continue to fall down
			else {
				circles[i].x += velos[i].x;
				circles[i].y += velos[i].y;
			}
		}

		//No collision in the first place, fall down
		else {
			circles[i].x += velos[i].x;
			circles[i].y += velos[i].y;
		}
	}
}

Conclusion

Hope this is helpful. Thanks for reading. Prompt me if there are questions, and I’ll see you next Quick Tip.



View full post on Activetuts+

Quick Tip: Use FZip to Open Zip Files Within AS3

In this tutorial I will introduce you to FZip, an AS3 Library that lets you open zip files inside your Flash projects. This can save a lot of bandwidth; in this tutorial we will load an 2.5MB zip file which contains 9.3MB worth of assets.


Final Result Preview

Let’s take a look at the final result we will be working towards. Click here to open a SWF that will in turn load a zip file full of images, and display them in a tiled grid.

(The blurring visible on some icons is due to Flash automatically attempting to scale them up to 32x32px, even though those particular images are 16x16px.)


Step 1: Getting the Library and Zip Archive

You will need to grad a copy of the FZip library from Claus Wahlers’ github.

Extract the library. Inside the src folder there is a folder named “deng”; copy this folder to the folder where you will store your FLA.

Next we need a zip archive to work with. I choose the WooFunction icon set, available for free from woothemes.com.

Save this to the same directory where you will store your FLA.


Step 2: Create New Flash Document

Open a new FLA and give it the following properties:

  • Size: 550x400px
  • Background Color: White

Save this as fzip.fla.


Step 3: Add Components to Stage

Go to Window > Components and drag a TileList component to the stage.

Under “Component Parameters” set the following properties:

  • columnCount: 16
  • columnWidth: 32
  • rowCount: 8
  • rowHeight:32

Give the TileList the instance name imageTileList, and set the following properties in the “Position and Size” panel:

  • X: 20
  • Y: 68
  • W: 100
  • H: 100

Next select the Text Tool and make sure the following properties are set in the “Character” panel:

  • Size: 50pt
  • Color: Black

Now drag a TextField onto the stage, and give it the instance name imagesLoaded. Make sure the TextField is set to “Classic Text” and “Dynamic Text”, respectively, and set the following properties:

  • X: 54
  • Y: 161
  • W: 454
  • H: 60

Step 4: Create new AS3 Document

Go to File > New and choose “Actionscript File”.

Save this file as Main.as.


Step 5: Package, Imports and Constructor

Inside Main.as add the following:

private function demonstrate():void
package  {

	import flash.display.MovieClip;
	import deng.fzip.FZip;
	import deng.fzip.FZipFile;
	import flash.display.Loader;
	import flash.net.URLRequest;
	import flash.events.*;
	import  fl.controls.TileList;
	import fl.data.DataProvider;

	public class Main extends MovieClip {

		public function Main() {
			setupZip();
		}
	 }
}

Here we imported the classes we will need for this tutorial, and set up the Main() constructor function.


Step 6: Add Variables

Define the following variables above public function Main():

private var zip:FZip; // Instance of FZIP class
private var numFiles:int = 0; //Number of files
private var numFilesLoaded:int = 0; //Number of files loaded
private var done:Boolean = false; //Done processing zip archive?
private var tileListDp:DataProvider = new DataProvider();//Data provider for the TileList

Here we add some variables we will need throughout the tutorial. See the comments for their usage.


Step 7: setupZip()

Add the following new function below Main():

private function setupZip():void{
	zip = new FZip();
	zip.addEventListener(Event.OPEN, onOpen);
	zip.addEventListener(Event.COMPLETE, onComplete);
	zip.load(new URLRequest("wootheme.zip"));	//change this to match your zip file's URL
	imageTileList.visible = false;
}

Here we create a new instance of the FZip class, add two event listeners, and load our zip file. Last, we set imageTileList to be invisible (We don’t want it to show until all the images from the zip have loaded).


Step 8: onOpen()

Add the following new function beneath the setupFzip() function you entered above:

private function onOpen(evt:Event):void {
	addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

This function gets called when the zip archive has been opened. Here we add an ENTER_FRAME event listener.

Step 9: onComplete()

Add the following code new function beneath the onOpen() function you entered in the step above.

private function onComplete(evt:Event):void {
	done = true;
}

This function gets called when there are no more files to process from the zip archive.


Step 10: onEnterFrame()

Add the following beneath the onComplete() function you entered above. This function will be triggered every frame after the zip file has been opened:

private function onEnterFrame(evt:Event):void {
    //Only load 32 files per frame, to save processing power
	for(var i:uint = 0; i < 32; i++) {
		// any new files available?
		if(zip.getFileCount() > numFiles) {
			//yes so get it
			var file:FZipFile = zip.getFileAt(numFiles);
			// is this a png in the icons folder?
			if(file.filename.indexOf("woofunction-icons") == 0 && file.filename.indexOf(".png") != -1) {
				var loader:Loader = new Loader();
				loader.loadBytes(file.content);
				tileListDp.addItem({source:loader});
				numFilesLoaded++;
			}
			numFiles++;
		} else {
			// no new files available
			// check if we're done
			if(done) {
				removeEventListener(Event.ENTER_FRAME, onEnterFrame);
				removeChild(imagesLoaded);
				imageTileList.visible = true;
				imageTileList.dataProvider = tileListDp;
			}
			//Exit the Loop
			break;
		}
	}
	imagesLoaded.text = numFilesLoaded + " Images Loaded";
}

Here’s the meat of the code.

Since this is running every frame, we’ll place an artificial restriction on the number of files within the archive that we deal with at once. That’s the purpose of the for-loop.

zip.getFileCount() reveals how many files are in the zip; numFiles stores how many files we’ve dealt with so far. So, line 5 checks whether there are still more files to deal with.

If there aren’t any files left, we skip to line 17 and just do some basic clearup: remove the ENTER_FRAME listener, remove the “loading” text field , make the tile list visible, and link it to the data.

If there are files left, we get the next one in our list using numFiles as an index. We then check whether it’s a PNG from the icons folder; since we know the structure of the zip beforehand, we can cheat and just check whether the file’s name and path contains “woofunction-icons” and “.png”.

To get the image from the zip and into a DisplayObject, we can use a Loader. This class is often used to load an image from a URL, but here we’re using its loadBytes() method to get the data from the ByteArray created by FZip.

Since Loader extends DisplayObject, we can just add it straight to the TileList’s DataProvider. Then we increment both numFilesLoaded and numFiles.

Why do we have two integers to keep track of how many files are loaded? Well, numFiles keeps count of all the files we’ve examined from the zip, whereas numFilesLoaded keeps count specifically of the image files that we’ve loaded into the DataProvider. It’s the latter variable that we use to update the “loading” text at the end of the function.


Conclusion

FZIP is an amazing little utility to save some loading time and bandwidth. I hope you’ve found this tutorial useful, and thanks for reading!



View full post on Activetuts+

Quick Tip: Collision Detection Between a Circle and a Line Segment

We covered collision detection between an infinite line and circle in our previous Quick Tip. However, the issue that arose was that the line extends further than the visible line segment; in fact, it extends into a hyperplane. In this Quick Tip, we shall limit our collision detection to that of a line segment only.


Final Result Preview

We shall work toward this result:

Click the Restart button to reposition the circles at the top of the stage.


Step 1: Two Approaches

There are numerous approaches to limit collision detection to within a line segment. We shall look at two approaches this time. The first approach is a little more rigorous mathematically than the second one, but these are concepts which, if you grasp successfully, will surely benefit you in the future. Both approaches manipulate the dot product’s characteristic of being a measure of how parallel two given vectors are.

Let’s have a look at the first approach. Suppose A and B are vectors. If A and B are parallel – or at least pointing in the same direction – the dot product between A and B will produce a positive number. If A and B are pointing directly opposite each other – or at least pointing in opposing directions – the dot product between A and B will produce a negative number. If A and B are orthogonal (forming 90° to each other) then the dot product will produce 0.

The diagram below summarises this description.

Dot product as a measure of parallelism between vectors.

Step 2: Relate Dot Product to Conditions

We’ll need to form vectors B and C from both ends of the line segment so that their dot product with the line segment’s vector, A, can determine whether the circle is within the segment.

Observe the diagram below. If the circle is within the segment, then the value of the dot product between A and B is positive and that between A and C is negative.

Using dot product to define a segment.

The diagram below shows how the dot product changes depending on whether the circle is beyond or within the line segment. Note the differences in the value of the dot product.

A summary of all conditions.

Also note that “within the line segment” does not mean that the circle is necessarily intersecting the line segment, just that it falls within the two thin lines on the diagram above.

So when collision occurs between line and circle, as we have seen in the previous Quick Tip, we have to further investigate whether the circle is positioned within the line segment. If it is, then we know for sure that there is a genuine intersection.


Step 3: Implementation

Step 2 explained the concept we use to restrict collision detection to be within the line segment. However, there’s still a flaw in the precision. You see, the area defined is a little tilted; we should aim to use the area defined according to the diagram below.

reorient the region.

This is easy: we simply calculate D as the horizontal projection of A. Then, instead of using A, we use D to dot product with B and C. All the conditions as explained in Step 2 still stand, but instead of a tilted segment, we have defined a vertical area.

This correction can be visually appreciated if the circle is large; if the circle were small, its center would be so close to the line that this visual flaw would be hard to detect, so we could get away with using that slightly tilted area and save ourselves some processing power.

Nevertheless, I’ll try to do things the correct way. You can pick your approach by modifying the condition slightly.


Step 4: Implementation

The first Actionscript snippet here sets up vector D (v_line_onX)

//Att2: getting the horizontal vector
var line_onX:Number = line.projectionOn(new Vector2D(1, 0));
v_line_onX = new Vector2D(1, 0);
v_line_onX.setMagnitude(line_onX);

Note: We’re using classes from my previous tutorials here. Vector2D was introduced in Gravity in Action, but you don’t need to read that to use the class, it’s included in the source download.

The second Actionscript snippet here sets up B (c1_circle) and C (c2_circle) and checks for the collision and whether the circle is inside the segment or not.

private function refresh(e:Event):void {
	for (var i:int = 0; i < circles.length; i++) {

		//calculating line's perpendicular distance to ball
		var c1_circle:Vector2D = new Vector2D(circles[i].x - x1, circles[i].y - y1);
		var c1_circle_onNormal:Number = c1_circle.projectionOn(leftNormal);

		//Att2: get vector from c2 to circle
		var c2_circle:Vector2D = new Vector2D(circles[i].x - x2, circles[i].y - y2);

		circles[i].y += 2;

		if (
			c1_circle_onNormal <= circles[i].radius
			&& v_line_onX.dotProduct(c1_circle) > 0
			&& v_line_onX.dotProduct(c2_circle) < 0
		){
			//if collision happened, undo movement
			circles[i].y -= 2;
		}
	}
}

Step 5: The Result

Here’s the result for the first approach. Click on the button to reset positions of all circles to the top of stage.


Step 6: Second Approach

The second approach is much simpler. I’ll try to work backwards from the end this time around.

Observe the diagram below. The line segment is from c1 to c2. It’s clear that collide1 and collide3 are both outside the line segment, and that only collide2 is within the line segment.

Analysing the collision conditions.

Let v1, v2 and v3 be vectors from c1 to respective circles. Only v2 and v3 are parallel – or at least pointing in similar directions to the line vector (c1 to c2). By checking for a positive value in the dot product between the line vector and each of those vectors from c1 to the corresponding circle centers (v1, v2, v3), we can easily determine that collide1 is beyond the line segment. In other words, c1 . v1 < 0.

Analysing the collision conditions.

Next, we shall devise a method to determine that collide3 is outside of the line segment. This should be easy. It's obvious that v3's projection along the line vector will exceed the length of line segment. We shall use this characteristic to weed off collide3.

So let me summarise the second approach:

  • First we check for an intersection between the infinite line and the circle.
  • If there is an intersection, further investigate the following to determine whether it happens within the line segment:
    • Check that a positive value is produced when we take the dot product of the vector from c1 to circle and the line vector, and
    • Check that the magnitude of the projection of the vector along the line vector is shorter than the line segment's length.

Step 7: Implementation

Here's the ActionScript implementation of the above:

private function refresh(e:Event):void {
	for (var i:int = 0; i < circles.length; i++) {

		//calculating line's perpendicular distance to ball
		var c1_circle:Vector2D = new Vector2D(circles[i].x - x1, circles[i].y - y1);
		var c1_circle_onNormal:Number = c1_circle.projectionOn(leftNormal);

		//Att2: getting the relevant vectors
		var c1_circle_onLine:Number = c1_circle.projectionOn(line);

		circles[i].y += 2;

		if (
			Math.abs(c1_circle_onNormal) <= circles[i].radius
			&& line.dotProduct(c1_circle) > 0
			&& c1_circle_onLine < line.getMagnitude()
		){
			//if collision happened, undo movement
			circles[i].y -= 2;
		}
	}
}

Step 8: The Result

Essentially, it will produce the same result as the previous but since there is a few lines of code shorter in the second approach, I guess its better.

Conclusion

Hope this has helped. Thanks for reading. Next up, we'll look at collision reaction.



View full post on Activetuts+

Page 1 of 2012345...1020...Last »
top