Page 6. An improved geometry application

To put all of our skills together, we end with a mathematical exploration of Pick's Theorem, which has to do with the relationship between the area of a triangle and the number of lattice points that it touches. the hypothesis for this theorem is that the triangle is drawn with all three vertices on lattice points. We can easily accomplish this by combining the fancy triangle drawing example shown on Page 3 with the "snap to grid" idea from Page 4. In addition, we can use the hitTestPoint idea from Page 5 to count the number of lattice points in the interior of our triangle. Here is the finished applet for you to play with before we delve into the code:

We set up the "board" in exactly the same way we did on Page 4, with the only difference being that we need three separate draggable points instead of just one.

 

//Part I -- Set up the board

 

var bWidth:Number = 400;

var bHeight:Number = 300;

var grid:Number = 50; // Size of the grid and number of lattice points in each direction

var dotsWide:Number = Math.ceil(bWidth/grid) - 1;

var dotsHigh:Number = Math.ceil(bHeight/grid) - 1;

 

var board:Sprite = new Sprite();

var triangle:Sprite = new Sprite();

var dragPt1:Sprite = new Sprite();

var dragPt2:Sprite = new Sprite();

var dragPt3:Sprite = new Sprite();

 

this.addChild(board);

board.addChild(triangle);

board.addChild(dragPt1);

board.addChild(dragPt2);

board.addChild(dragPt3);

 

board.graphics.lineStyle(1,0);

board.graphics.beginFill(0xCCCCCC);

board.graphics.drawRect(0,0,bWidth,bHeight);

board.graphics.endFill();

 

// Add a bunch of small circles to represent lattice points

board.graphics.beginFill(0x000000);

for (var i=1; i<=dotsHigh; i++) {

for (var j=1; j<=dotsWide; j++) {

board.graphics.drawCircle(j*grid,i*grid,2);

}

}

board.graphics.endFill();

board.x = 10;

board.y = 10;

dragPt1.graphics.lineStyle(1,0);

dragPt1.graphics.beginFill(0x00FF00,0.9);

dragPt1.graphics.drawCircle(0,0,5);

dragPt1.graphics.endFill();

dragPt1.x = goodX(50);

dragPt1.y = goodY(50);

 

dragPt2.graphics.lineStyle(1,0);

dragPt2.graphics.beginFill(0x00FF00,0.9);

dragPt2.graphics.drawCircle(0,0,5);

dragPt2.graphics.endFill();

dragPt2.x = goodX(50);

dragPt2.y = goodY(250);

 

dragPt3.graphics.lineStyle(1,0);

dragPt3.graphics.beginFill(0x00FF00,0.9);

dragPt3.graphics.drawCircle(0,0,5);

dragPt3.graphics.endFill();

dragPt3.x = goodY(150);

dragPt3.y = goodY(150);

 

The drag-and-drop functionality is handled the same way it was on Page 3, where we use the global variable "thisPt" to point to the object that gets the MOUSE_DOWN event (using the events currentTarget property) and then we move "thisPt" around based on the MOUSE_MOVE event. In addition to the helper function that redraws the triangle when the mouse is dragged, we also have a helper function that computes the values (i.e., triangle area and the number of interior lattice points and boundary lattice points) and updates the text boxes on the stage that display those values.

 

// Part II -- Add drag and drop functionality

 

var thisPt:Sprite = new Sprite();

 

dragPt1.addEventListener(MouseEvent.MOUSE_DOWN, startMove);

dragPt2.addEventListener(MouseEvent.MOUSE_DOWN, startMove);

dragPt3.addEventListener(MouseEvent.MOUSE_DOWN, startMove);

 

function startMove(evt:MouseEvent):void {

thisPt = Sprite(evt.currentTarget);

stage.addEventListener(MouseEvent.MOUSE_MOVE, pointMove);

}

 

function pointMove(e:MouseEvent):void {

thisPt.x = goodX(board.mouseX);

thisPt.y = goodY(board.mouseY);

drawTriangle();

countPoints();

e.updateAfterEvent();

}

stage.addEventListener(MouseEvent.MOUSE_UP, stopAll);

 

function stopAll(e:MouseEvent):void {

stage.removeEventListener(MouseEvent.MOUSE_MOVE, pointMove);

}

 

The helper functions called drawTriangle, goodX and goodY are a straightforward combination of those that appear on Pages 3 and 4 of this tutorial. The new helper function called countPoints illustrates a couple of new ideas worth talking about.

  1. We use the hitTestPoint method of the triangle sprite to count the number of lattice points that hit the triangle. Unfortunately, this method requires coordinates in absolute stage coordinates (instead of coordinates relative to the parent object, the board in this case); hence, we have to do a translation by adding This process gives us the total number of points on interior and boundary, so we have to do more to split this count into the two components.
  2. We use some math to count the number of lattice points on the boundary since the hitTestPoint method is not reliable when the graphic is a mere line as it is for the boundary segments of our triangle. It is easy to use the following fact: If a line segment between lattice points has a y-change (a.k.a., "rise") of S and an x-change (a.k.a., "run") of N, then the number of lattice points on the line segment is 1 + gcd(S,N).
  3. The gcd helper function is a typical recursive implementation. Note that we add some robustness to keep the function from being misused if someone places it in a program where negative input values could be used. This is not a concern in this particular application.
  4. If the area of the triangle is 0, then Pick's Theorem does not apply (since it would be wrong), so we have to provide a reasonable message to the user if this occurs.

/*

Part III -- Helper functions to count the number of lattice points within the triangle and on the boundary, to update the triangle graphic, and to stay within boundary / snap to grid. Note that the countPoints function needs its own helper function to compute the gcd (greatest common divisor) of two integers.

*/

function countPoints():void {

var c_total:int = 0;

var c_border:int = 0;

var i,j,ax,ay,bx,by,cx,cy:int;

var t_area:Number;

 

for (i=1; i<=dotsHigh; i++) {

for (j=1; j<=dotsWide; j++) {

if (triangle.hitTestPoint(board.x + j*grid, board.y + i*grid, true)) {

c_total++ ;

}

}

}

 

/*

Compute the number of lattice points on the boundary using gcd's of "rise" and "run" for each triangle edge.

*/

ax = int(Math.abs(dragPt1.x - dragPt2.x)/grid);

ay = int(Math.abs(dragPt1.y - dragPt2.y)/grid);

bx = int(Math.abs(dragPt1.x - dragPt3.x)/grid);

by = int(Math.abs(dragPt1.y - dragPt3.y)/grid);

cx = int(Math.abs(dragPt3.x - dragPt2.x)/grid);

cy = int(Math.abs(dragPt3.y - dragPt2.y)/grid);

 

c_border = gcd(ax,ay) + gcd(bx,by) + gcd(cx,cy);

t_area = c_total - c_border/2 - 1;

 

if (t_area == 0 ) {

txtInterior.text = "Degenerate";

txtBoundary.text = "Degenerate";

txtArea.text = "Degenerate";

}

else {

txtInterior.text = String(c_total - c_border);

txtBoundary.text = String(c_border);

txtArea.text = String(t_area);

}

}

 

function gcd(a:int, b:int):int {

if (a<0) {

return gcd(-a,b);

}

if (b<0) {

return gcd(a,-b);

}

if (a==0) {

return b;

}

if (b==0) {

return a;

}

if (a < b) {

return gcd(a, b%a);

}

else {

return gcd(b, a%b);

}

}

 

function drawTriangle():void {

triangle.graphics.clear();

triangle.graphics.lineStyle(2,0xAAAAFF);

triangle.graphics.beginFill(0xAAAAFF,0.75);

triangle.graphics.moveTo(dragPt1.x,dragPt1.y);

triangle.graphics.lineTo(dragPt2.x,dragPt2.y);

triangle.graphics.lineTo(dragPt3.x,dragPt3.y);

triangle.graphics.lineTo(dragPt1.x,dragPt1.y);

triangle.graphics.endFill();

}

 

function goodX(inX:Number):Number {

if (inX > grid*dotsWide) {

return grid*dotsWide;

}

 

if (inX < grid) {

return grid;

}

 

return grid*Math.round(inX/grid);

}

 

function goodY(inY:Number):Number {

if (inY > grid*dotsHigh) {

return grid*dotsHigh;

}

 

if (inY < grid) {

return grid;

}

 

return grid*Math.round(inY/grid);

}

 

Finally we add a couple of lines so that the triangle and count appears even before any dragging has taken place.

// Initial drawing and count

drawTriangle();

countPoints();

Download

Back to Basic Tutorials              Back to Flash and Math Home

We welcome your comments, suggestions, and contributions. Click the Contact Us link below and email one of us.

Adobe®, Flash®, ActionScript®, Flex® are registered trademarks of Adobe Systems Incorporated.