Archive for 2010

Part IVc – Intermezzo Automatic Mesh Density Reduction

Friday, May 14th, 2010
One of the things I wanted to try was to automatically reduce the mesh density of a plane based on it’s current size. So instead of determining beforehand that a plane has to have 6 by 6 subdivision, you would simply say something like make sure no subdivision is bigger than 30 x 30 pixels.

In effect it’s a very simple LOD modifier or Level of Size modifier if you will.

Is this usable? Well there are a number of considerations:

* if the basic mesh density is low, the reduction is really noticable (the texture shifts), so that’s a downside, you do have to use a fairly high density to start with, maybe higher then you would need without the reduction, since the subtle texture wobbling is less noticable than abrupts texture shifts
* if the speed is high you hardly notice the reduction (visually I mean, you do notice the performance gain), so that’s good, but then again if the speed is high, why use a high subdivision in the first place? Even the speed is variable, better to base the reduction on that
* the fact that you don’t have to specify the grid density is nice, we only have to set a maximum to prevent a large plane from hogging the processor

With these considerations in mind I do think this is promising for several reasons:

* we can combine the autoreduction feature with a maximum density based on the plane’s speed (although the object actually moving the plane is better fit to take care of that, otherwise the plane would have to have a notion of it’s own speed, which I am not in favor of). Another option is to have a) a minimum and maximum density (to ensure a miminum level of quality and prevent the CPU from hogging), b) a basic coarseness which will reduce the density as the plane transforms, c) a coarseness modifier based on the plane speed which is different from simply lowering the maximum, but lowering the maximum might make for a better overall quality, we’ll simply have to try
* it will greatly reduce the number of clipping tests performed
* the cube demo already has a very small plane to start with, so the effect isn’t as huge as when you would have a 600 x 600 pixel image, the reduction would be much more effective in a plane that big

I also found a subtle bug in the DistortedPlane class, basically when you enlarge the density, the former corner point references are still stored in the interpolation grid, overwritten with the wrong values which results in a self modifying mesh. Not cool, another 2 hours of my life I’ll never get back :) , but it is fixed in v4.

Download the new version with example sources here: 3d Distorted With Autodensity (97).

Next time I’ll look into creating version 1 of a simple panorama using these same principles. See the 3d Panorama in Actionscript 2 article series for that!

Part IVb – Intermezzo Magic Cube

Monday, May 10th, 2010

After some minor adjustments to the DistortedPlane class it was possible to render more than one plane at a time (the interpolatedPoints array was static…). Class has now been extended with a front and backside material, and here is a small demonstration of putting a number of planes together.

The material was a quick paintjob done in Photoshop on my wacom.

One major thing is that I found the solution to a certain problem: the player locks up when drawing certain bitmapfills using smooth set to true. The fix is to also set repeat to true. Hope that fixes it for you as well.

Click to activate!

Download the sources here: 3d Cube (87).

Part IV – Perspective Plane Rendering

Saturday, May 8th, 2010

We are getting closer and closer to generating an actual cubic panorama. In case you just jumped on in from elsewhere, I’m working on a project in which I have to create a cubic panorama with hotspots that light up and it has to be in AS2, so this is my journal on the way there. In the previous post we learned how to split a plane into multiple segments to reduce the texture distortion errors. We also mentioned that perspective is not the same as distortion. I played around a bit with perspective in the GridRider intermezzo post, which I will convert into a full game as soon as I’m done with these tutorials. After yet another short night with caffeine and Volbeat to keep me awake, it’s 10 AM on a rainy Sunday morning, good excuse to post the next bit!

To clarify the difference between perspective and distortion check out the following image (and this post):

Distortion vs Perspective

So in order to add perspective to a plane I made some adjustments to the DistortedPlane class. And not all changes were required by the addition of perspective, rather I ran into some issues when creating the GridRider prototype, so I included the extensions to fix those issues as well.

Scaling textures

First change I made was that I wanted to able to use a tiling texture. This required the ability to scale the bitmap on the plane.

As you might recall from a previous post the size of a piece of bitmap was derived using the following formulas:

var lInvPieceWidth:Number = lXSegments/_bitmap.width;
var lInvPieceHeight:Number = lYSegments/_bitmap.height;

But now I want my texture to be smaller so that I can tile. This is the same as using a larger bitmap with copies of the original, so imagine I want the texture to tile twice in the x and y direction, I could create a bitmap called _bitmap2 with width = _bitmap.width*2 and height=_bitmap.height*2:

var lInvPieceWidth:Number = lXSegments/(_bitmap.width*2);
var lInvPieceHeight:Number = lYSegments/(_bitmap.height*2);

Another way to put this is that I’m scaling the texture down by (sx = 0.5, sy = 0.5). This amounts to:

var lInvPieceWidth:Number = (lXSegments * _sx)/_bitmap.width ;
var lInvPieceHeight:Number = (lYSegments * _sy)/_bitmap.height;

Enable the “Animate texture scale” in the interactive gadget to see this in action. Best to keep ‘repeat’ on when scaling down the texture.

Translating textures

Second thing I thought I needed for the GridRoad prototype (but it turned out that wasn’t the way to go), was translating the texture with respect to the plane. To see what happens to our equations we simply need to input in1.x+dx and in1.y+dy instead of in1.x and in1.y. Putting those values through our equations shows that almost everything stays the same (not shown here, but you can try it for yourself), with the exceptions of the tx and ty equations (which is kind of to be expected I guess).

If we look in the previous post, we reached a point where we reduced the tx/ty equations to:

matrix.tx = out1.x – (matrix.a * in1.x) – (matrix.c * in1.y);
matrix.ty = out1.y – (matrix.b * in1.x) – (matrix.d * in1.y);

And we saw that the coordinates for in1 were (x * piecewidth, y * pieceheight).
Looking at tx only (ty goes the same way), we get:

matrix.tx = out1.x – (matrix.a * (x * piecewidth + dx)) – (matrix.c * (y * pieceheight + dy));

However we ‘optimized’ our equations, so we have to write this a little differently:

lMatrix.tx = lULx – ((lLRx – lLLx) * x) - (lMatrix.a * lDx) – ((lLLx – lULx) * y) – (lMatrix.c) * lDy;
lMatrix.ty = lULy – ((lLRy – lLLy) * x) - (lMatrix.b * lDx) – ((lLLy – lULy) * y) – (lMatrix.d) * lDy;

where (matrix.a * (x * piecewidth + dx)) is written here as ((lLRx – lLLx) * x) – (lMatrix.a * lDx), since
matrix.a was (out2.x-out3.x)/piecewidth; and out2x is lLRx and out3.x is lLLx in our setup.

But I’m starting to lose you, in fact the only thing you have to remember is that when you take the starting equations for mapping an arbitrary triangle to an arbitrary triangle and incorporate the added dx and dy you will eventually get this.

Check out how this works by enabling the “Animate texture offset” in the interactive gadget to see this in action. Best to keep ‘repeat’ on when translating the texture.

Rotating textures

I didn’t need this yet, and although the math probably isn’t that complex, after all I think the key is probably to first transform the bitmap points and then interpolate between them, the calculations will get more involved and thus slower. Translation and scaling came at a pretty low cost, so we’ll leave it at that.

Separating interpolation from rendering

Due to the fact that the points could stay the same now while the texture is being transformed, I didn’t want to re-interpolate the points all the time before rendering. So we split the render method in a separate interpolation section and render section and added some optimizations to check if interpolation was really required.

Direct access to the interpolation array

Although not always recommendable, direct access to the interpolation array does provide greater power over what you can do while rendering the plane. Use with caution or not at all ;) .

Perspective projection

The probably most difficult part: the perspective rendering of the planes. Well first of all without going into perspective projection: 3d projection requires 3d coordinates. Up until the part where we actually project the 3d coordinates, we don’t have to change that much: instead of an x and y to interpolate, we have to interpolate an x, y and z.

However I didn’t want to turn the DistortedPlane class into something that wouldn’t allow simple 2d distortion anymore, so I did the following:

  • instead of Points the class now uses Point3D objects.
  • if you pass a Z coordinate for all 4 corners of the plane, 3d perspective projection is used (and you should set the 3d properties)
  • if any of the Z coordinates is missing we revert to simply 2d image distortion

So if we are using 3d projection, all z coordinates are interpolated as well.

The last step before rendering is projecting these interpolated coordinates. Basically projection is just what it says, you have a point with a certain z, but you want to render it on a 2d plane at depth f. If z == f there is no projection, since the point is at the plane you want to project in on already. If the z is twice as far as the projection plane, the point where a line to that point intersects the projection plane will be twice as close to the focal point.

That’s the short version, a much better explanation can be found here.

So basically the projection formulas are:

ProjX = -(farplaneDistance/z) * x
ProjY = -(farplaneDistance/z) * y

This assumes objects in front of the camera have a negative z, which seems to be normal in 3d projections since the objects are on ‘the other side’ of the camera.

So you see as z gets bigger, x and y get closer to 0. If you want them to get closer to FocalX and FocalY, use this instead:

ProjX = -(farplaneDistance/z) * (x – focalX) + focalX;
ProjY = -(farplaneDistance/z) * (y – focalY) + focalY;

So what should you use for your farplane distance? For now whatever looks good. I’ll get back to this later when we are discussing field of view calculations.

One other thing I added was some sort of clipping (drag the clip handles in the interactive example). The idea with clipping is that we only render segments if at least one corner is within the clipping rectangle. Better material mapping would be good too (backface culling, two sided materials), but I’ll keep that for another post.

Download the sources: 3d Distortedplane Class V2 (115)

You might notice I used nearplane and farplane, but should have used nearplane, farplane and projectionplane, I will fix this in a next version.

Part IIIb – Intermezzo GridRider

Thursday, May 6th, 2010

Just a small test while working on planes in perspective. Hacked together but performs ok for AS2, runs at about 20% CPU. Click to start and turn the volume up!

Part III – Plane rendering

Wednesday, May 5th, 2010
    

You can download the DistortedPlane class and examples here Distortedplane (98).

In the previous example we saw how to render arbitrary triangles, and we showed an example of how to reduce the equations in right-angled cases. In this post we are going to look into rendering a plane. We will do so without any perspective correction for the z-axis, and as mentioned previously distortion is not perspective. (A good treaty on this can be found here http://zehfernando.com/2010/the-best-drawplane-distortimage-method-ever/ (which might provide a solution with respect to the perspective correction as well, but I’m still looking into that)).

So distorting a plane instead of a triangle. This has been done before and more than once, by Ruben, Sandy, Phillipe van Kessel and probably more. In other words, I have no illusions that we are doing something new here. However you shouldn’t let yourself be held back by things that have been done before.

The reason I’m doing it again and documenting it here, is because I want to try to make it understandable how the plane works and show it’s actually not that complex. Another reason is to document the process for myself. I’m learning a lot from it, and it gives me a reference point to compare other implementations to.

Anyway, back to the work at hand. Dividing a plane is the same as cutting a lot of triangles from it. In the previous post we saw how to map arbitrary triangles from a bitmap to an arbitrary triangle on screen, and now we are simply going to do that a lot of times:

Plane rendering

So we are taking the unoptimized equations for mapping arbitrary triangles from the previous post:

var dIn32x:Number = in3.x – in2.x;
var dIn13x:Number = in1.x – in3.x;
var dIn21x:Number = in2.x – in1.x;
var dIn32y:Number = in3.y – in2.y;
var dIn13y:Number = in1.y – in3.y;
var dIn21y:Number = in2.y – in1.y;

var denomAB:Number = 1/((in1.x * dIn32y) + (in2.x * dIn13y) + (in3.x * dIn21y));
var denomCD:Number = 1/((in1.y * dIn32x) + (in2.y * dIn13x) + (in3.y * dIn21x));

var matrix:Matrix = new Matrix();
matrix.a = ((out1.x * dIn32y) + (out2.x * dIn13y) + (out3.x * dIn21y)) * denomAB;
matrix.b = ((out1.y * dIn32y) + (out2.y * dIn13y) + (out3.y * dIn21y)) * denomAB;
matrix.c = ((out1.x * dIn32x) + (out2.x * dIn13x) + (out3.x * dIn21x)) * denomCD;
matrix.d = ((out1.y * dIn32x) + (out2.y * dIn13x) + (out3.y * dIn21x)) * denomCD;
matrix.tx = out1.x – (matrix.a * in1.x) – (matrix.c * in1.y);
matrix.ty = out1.y – (matrix.b * in1.x) – (matrix.d * in1.y);

and modify them to accomplish the division of the plane into smaller rectangles, which are divided into triangles.

Below one such rectangle is shown. If you consistently apply the formula above to the triangles in this quad, it doesn’t matter which way you divide the triangles, but I’ve chosen upper-left to lower-right. In addition, I’ve added UL = Upper-Left, UR = Upper-Right, LL = Lower-Left and LR=Lower-Right to each corner. After that you can arbitrarily choose the in and out points, as long as they are in the same order. In order words if in1 represents the Upper-Left corner in the piece of the bitmap you are going to map, out1 should represent the Upper-Left corner in the triangle you are going to map it to:

Triangle map

So what you can see is that for the upper right triangle OUT1,OUT2, OUT3 and IN1, IN2, IN3 are mapped to UL, LR, UR respectively and for the bottom one it’s UL, LR, and LL. IN3 and OUT3 appear twice in the image, representing different points, once for the top and one for the bottom triangle, to clarify:

Quad division into triangles

In our first image our plane was subdivided in an 8 by 8 grid. This means the following holds for a rectangular piece from the source bitmap:

piecewidth = bitmapwidth/8
pieceheight = bitmapheight/8

In other words:

piecewidth = bitmapwidth/gridXCount
pieceheight = bitmapheight/gridYCount

Looking back at dIn32x = in3.x – in2.x, dIn13x = in1.x – in3.x, etc and in the image above at the lower left triangle (the IN-b coordinates), we see the following holds:

var dIn32x:Number = in3.x – in2.x => -piecewidth
var dIn13x:Number = in1.x – in3.x => 0
var dIn21x:Number = in2.x – in1.x => piecewidth
var dIn32y:Number = in3.y – in2.y => 0
var dIn13y:Number = in1.y – in3.y => -pieceheight
var dIn21y:Number = in2.y – in1.y => pieceheight

This means our denomAB and denomAC will become:

var denomAB:Number = 1/((in2.x * -pieceheight) + (in3.x * pieceheight));
var denomCD:Number = 1/((in1.y * -piecewidth) + (in3.y * piecewidth));

So what are our in1.x/y in2.x/y etc coordinates?

Well assume we are going through the grid to draw the rectangles/triangles, this will look something like:

for (var y:Number = 0; y < gridYCount; y++) {
	for (var x:Number = 0; x < gridXCount; x++) {
			//1. calculate coordinates and matrix lowerleft triangle
			//2. draw lowerleft triangle
			//3. repeat 1 & 2 for upperright triangle
			//4. now we have drawn a transformed rectangle
	}
}

So we see our In1 coordinate is simply (x*piecewidth, y*pieceheight), In2 is ((x+1)*piecewidth, (y+1)*pieceheight) and In3b is (x*piecewidth, (y+1)*pieceheight).

Putting this into denomAB will give us:
var denomAB:Number = 1/(((x+1)*piecewidth * -pieceheight) + (x*piecewidth * pieceheight));
var denomCD:Number = 1/((y*pieceheight * -piecewidth) + ((y+1)*pieceheight * piecewidth));

So if we define pieceArea as piecewidth*pieceheight we get:

var denomAB:Number = 1/(((x+1)*-pieceArea) + (x*pieceArea));
var denomCD:Number = 1/((y*-pieceArea) + ((y+1)*pieceArea));

Is the same as:
var denomAB:Number = -1/pieceArea;
var denomCD:Number = 1/pieceArea;

Inserting what we've got so far into:

var matrix:Matrix = new Matrix();
matrix.a = ((out1.x * dIn32y) + (out2.x * dIn13y) + (out3.x * dIn21y)) * denomAB;
matrix.b = ((out1.y * dIn32y) + (out2.y * dIn13y) + (out3.y * dIn21y)) * denomAB;
matrix.c = ((out1.x * dIn32x) + (out2.x * dIn13x) + (out3.x * dIn21x)) * denomCD;
matrix.d = ((out1.y * dIn32x) + (out2.y * dIn13x) + (out3.y * dIn21x)) * denomCD;
matrix.tx = out1.x - (matrix.a * in1.x) - (matrix.c * in1.y);
matrix.ty = out1.y - (matrix.b * in1.x) - (matrix.d * in1.y);

will give us:

var matrix:Matrix = new Matrix();
matrix.a = ((out2.x * -pieceheight) + (out3.x * pieceheight)) * denomAB;
matrix.b = ((out2.y * -pieceheight) + (out3.y * pieceheight)) * denomAB;
matrix.c = ((out1.x * -piecewidth) + (out3.x * piecewidth)) * denomCD;
matrix.d = ((out1.y * -piecewidth) + (out3.y * piecewidth)) * denomCD;
matrix.tx = out1.x - (matrix.a * x*piecewidth) - (matrix.c * y * pieceheight);
matrix.ty = out1.y - (matrix.b * x*piecewidth) - (matrix.d * y * pieceheight);

Simplifying this further gives us:

matrix.a = (out2.x - out3.x) * pieceheight/ pieceArea;
matrix.b = (out2.y -out3.y) * pieceheight /pieceArea;
matrix.c = (out3.x - out1.x) * piecewidth / pieceArea;
matrix.d = (out3.y - out1.y) * piecewidth /pieceArea;
matrix.tx = out1.x - (matrix.a * x*piecewidth) - (matrix.c * y * pieceheight);
matrix.ty = out1.y - (matrix.b * x*piecewidth) - (matrix.d * y * pieceheight);

And further:

matrix.a = (out2.x-out3.x)/piecewidth;
matrix.b = (out2.y-out3.y)/piecewidth;
matrix.c = (out3.x-out1.x)/pieceheight;
matrix.d = (out3.y-out1.y)/pieceheight;
matrix.tx = out1.x - (matrix.a * x * piecewidth) - (matrix.c * y * pieceheight);
matrix.ty = out1.y - (matrix.b * x * piecewidth) - (matrix.d * y * pieceheight);

And even further:
matrix.a = (out2.x-out3.x)/piecewidth;
matrix.b = (out2.y-out3.y)/piecewidth;
matrix.c = (out3.x-out1.x)/pieceheight;
matrix.d = (out3.y-out1.y)/pieceheight;
matrix.tx = out1.x - ((out2.x-out3.x) * x) - ((out3.x-out1.x) * y);
matrix.ty = out1.y - ((out2.x-out3.x) * x) - ((out3.y-out1.y) * y);

We could store the out pairs into variables, but the storing and lookup probably costs just as much as calculating it again.

A slight rewrite might save a bit more, using invPieceWidth = 1/lPieceWidth and invPieceHeight = 1/lPieceHeight, or in other words:

invPieceWidth = gridXCount/bitmapwidth
invPieceHeight = gridYCount/bitmapheight

And then:
matrix.a = (out2.x-out3.x);
matrix.b = (out2.y-out3.y);
matrix.c = (out3.x-out1.x);
matrix.d = (out3.y-out1.y);
matrix.tx = out1.x - (matrix.a * x) - (matrix.c * y);
matrix.ty = out1.y - (matrix.b * x) - (matrix.d * y);
matrix.a *= invPieceWidth;
matrix.b *= invPieceWidth;
matrix.c *= invPieceHeight;
matrix.d *= invPieceHeight;

But as it turns out this is slower then simply keeping it as we had.

So we can do that for the other triangle as well, I'll leave that up to you. One thing we haven't talked about though are the coordinates of the out points. What are the coordinates for the out points?

Take a look at the next image:

interpolation across 4 points

All the colored dots are out points. So for a specific triangle, the dots on the corners of a triangle represent the triangle's out points. When rendering a plane we have to provide the coordinates for it's four corner points. Those points are represented by the green dots.

Now we have to interpolate the coordinates for all the other points. We do that by simply first interpolating all coordinates on the top row between the upper-left and upper-right point, and then all the coordinates on the bottom row between the lower-left and lower-right point.

Remember that linear interpolation between two values is done through this simple formula, with t a value from 0-1: (1-t)*startvalue+t*endvalue

When we have all the coordinates for the top and bottom rows, we can interpolate all the coordinates in between, for example if we pick the top middle coordinate and the bottom middle coordinate, we can interpolate all the red dots etc.

Of course you don't have to do it this way, you can also directly interpolate the value of a single point, but pre-calculating them this way is faster. If you do want to interpolate them directly, you get an equation for 4 points and two interpolation values. Assume you have pUL, pUR, pLL, pLR and ix and iy, where ix and iy are the x and y interpolation values.
First interpolate the upper and lower values, to get a top interpolated value and a bottom interpolated value:

pTop.x = (1-ix)*pUL.x+ix*pUR.x;
pTop.y = (1-ix)*pUL.y+ix*pUR.y;
pBottom.x = (1-ix)*pLL.x+ix*pLR.x;
pBottom.y = (1-ix)*pLL.y+ix*pLR.y;

and then interpolate these over y where pIP is the interpolated point:

pIP.x = (1-iy)*pTop.x-iy*pBottom.x;
pIP.y = (1-iy)*pTop.y-iy*pBottom.y;

Put together:
pIP.x = (1-iy)*((1-ix)*pUL.x+ix*pUR.x)-iy*((1-ix)*pLL.x+ix*pLR.x);
pIP.y = (1-iy)*((1-ix)*pUL.y+ix*pUR.y)-iy*((1-ix)*pLL.y+ix*pLR.y);

But as said I split this in a couple of steps, to avoid re-interpolation for all the non edge points.

So what did we cover so far?

  • using the basic arbitrary triangle mapping formulas
  • calculating the coordinates of the IN and OUT points through interpolation and simplifying our formulas based on that
  • the basics of the render loop

Now in order to get this code as fast as possible I did some other things:

  • only use local variables
  • precalculate anything that being used more than twice (!).
  • create NO objects during rendering

One other thing I tried with no success was converting the point coordinates to a linked quad list so I could unroll the for loops. Another thing I tried was looping using while (x--) and while (y--) instead of incrementing for loops, but there was practically no difference in performance, at least no consistent improvement.

I ran some tests again Sandy's DistortImage class, and preliminary tests show about a 45 to 50% speed improvement. I haven't ran it against any of the other distort classes since my main concern was simply that I wouldn't write something that was way slower. That it is almost twice as fast is simply a bonus, but since I do not completely grasp the distortimage class there might be some hidden stuff that I'm not taking care of.
(Which is just my way of saying 'Use at your own risk' :) ).

You can download the DistortedPlane class and examples here Distortedplane (98).
If you haven't done so try out the two interactive examples at the top of this post.

Which source license applies?

Tuesday, May 4th, 2010

I have little time and much to do, so the last thing I want to do is repeat myself ;) . As you might have noticed most source samples have been put together quickly to demonstrate a certain idea with documentation but without any license info.

So in case you are wondering, UNLESS explicitly otherwise specified in a blogpost or the source code for an example, the sources available on this site can be used free of charge, and without attribution (but feel free to do so). Of course this excludes sources for purchased products, and excludes the right to use sources acquired through decompilation of any files for which the sources weren’t provided (which hopefully is near 0%).

More formally:

/**
* This software is released under the MIT License.
*
* Copyright (c) InnerDriveStudios, JC Wichman
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the “Software”), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

Part II – Arbitrary bitmapfill transforms

Sunday, May 2nd, 2010

Recap

In the first post in this series, I discussed and explained matrix transforms and how they could be used to map a bitmap into an arbitrarily drawn triangle. However we saw that if we are ever going to render larger planes in 3d, mapping a single bitmap onto a single rectangle (or 2 triangles) will not cut it, since in order to counter the gruesome texture distortion created by the affine transforms, we will have to subdivide a plane into smaller planes. Note that this is still not perspective correct, it is simply a distorted bitmap with less texture distortion through subdivision. A grid that respects the perspective or rather z-depth of the vertices is up for another post.

Subdividing the plane means we end up with smaller rectangles, that contain smaller pieces of the texture (bitmap). So we are no longer mapping a complete bitmap to a rectangle or triangle, but just pieces of that bitmap.

subdivision sample

Looking back at the first post, we derived the transform for a complete bitmap fill using the following formulas:

a = (p1.x-p0.x)/width
b = (p1.y-p0.y)/width
c = (p2.x-p0.x)/height
d = (p2.y-p0.y)/height
tx = p0.x
ty = p0.y

To be precise we derived these formulas by looking at the a, b, c, d, tx and ty required to map the P1 (0,0) P2 (bitmap_width, 0) and P3 (0, bitmap_height) coordinates of the bitmap to a triangle at P1’ (x1’,y1’), P2’ (x2’,y2’) and P3’ (x3’, y3’).

Inserting all these points into the equation:

P(x,y) => (a*x+c*y+tx, b*x+d*y+ty) => P’(x’,y’)

allowed us to derive the aforementioned formulas.

However we can see now that if we map other points than the (0,0), (bitmap_width,0) and (0, bitmap_height) points, these equations are going to be more complex, since we won’t be able to extract the tx and ty solely based on the (0,0) point.

Before we continue let’s call the points in the bitmap the input points for our transformation, and the points of the drawn and filled triangle the output points. We could call them Pn and Pn’, but I feel in1.x and out1.x are easier to distinguish than P1.x and P1’.x.

complete mapping
(mapping a complete bitmap, with the in points at the corner of the bitmap to arbitrary outpoints)

So although we only need to map rectangles (or rather right angled triangles) within the bitmap to arbitrary triangles, let us take a detour in which we map an arbitrary triangle from the source bitmap to an arbitrary drawn triangle filled with this bitmap data. In other words let us examine transforming one triangle into another triangle and derive more general cases from there. Although a bit more work, it provides us with a more powerful tool if we ever want to perform more complex texture mapping, so why not do it right away.

Arbitrary triangle mapping

arbitrary mapping
(mapping an arbitrary triangle from a bitmap to an arbitrary triangle fill)

More precisely, given:

in1(x,y) => (a*in1.x + c*in1.y + tx, b*in1.x + d*in1.y + ty) => out1(x,y)
in2(x,y) => (a*in2.x + c*in2.y + tx, b*in2.x + d*in2.y + ty) => out2(x,y)
in3(x,y) => (a*in3.x + c*in3.y + tx, b*in3.x + d*in3.y + ty) => out3(x,y)

what are the values of a,b,c,d,tx and ty?

Let’s put this in a slightly different form yet again:

a*in1.x + c*in1.y + tx = out1.x
b*in1.x + d*in1.y + ty = out1.y
a*in2.x + c*in2.y + tx = out2.x
b*in2.x + d*in2.y + ty = out2.y
a*in3.x + c*in3.y + tx = out3.x
b*in3.x + d*in3.y + ty = out3.y

So given these equations, if we can solve these for a, b, c, d, tx and ty, we have found the matrix to map the in points to the out points.

Rewriting and sorting gives us:

tx = out1.x – a*in1.x – c*in1.y
tx = out2.x – a*in2.x – c*in2.y
tx = out3.x – a*in3.x – c*in3.y

ty = out1.y – b*in1.x – d*in1.y
ty = out2.y – b*in2.x – d*in2.y
ty = out3.y – b*in3.x – d*in3.y

We have now pairs we can compare. We can take any two pairs, but let’s take tx for the 1-2 pair first with => denoting ‘follows from’:

out1.x – a*in1.x – c*in1.y = out2.x – a*in2.x – c*in2.y =>
a*in2.x – a*in1.x = out2.x – out1.x + c*in1.y – c*in2.y =>
a = (out2.x – out1.x + c*(in1.y – in2.y)) / (in2.x – in1.x)

So now we’ve found a but we are not done yet, since a is defined in terms of c. Plugging a back into what we’ve got so far is not going to work since (and feel free to try this for yourself) we will end up with 0=0, which although undoubtedly true in this universe, isn’t very useful.

So we take the pair 1-3 and plug the a we’ve found back into that. For 1-2 we found:

a = (out2.x – out1.x + c*(in1.y – in2.y)) / (in2.x – in1.x)

Logically 1-3 will give us:

a = (out3.x – out1.x + c*(in1.y – in3.y)) / (in3.x – in1.x)

This in turn provides us two different values for a we can compare:

(out2.x – out1.x + c*(in1.y – in2.y)) / (in2.x – in1.x)
=
(out3.x – out1.x + c*(in1.y – in3.y)) / (in3.x – in1.x)

Multiplying by (in2.x – in1.x)* (in3.x – in1.x) gives us:

(out2.x – out1.x + c*(in1.y – c*in2.y)) * (in3.x – in1.x)
=
(out3.x – out1.x + c*(in1.y – c*in3.y)) * (in2.x – in1.x)

Rewriting this:

(out2.x – out1.x)* (in3.x – in1.x) + c*(in1.y – in2.y)* (in3.x – in1.x)
=
(out3.x – out1.x) * (in2.x – in1.x) + c*(in1.y – in3.y) * (in2.x – in1.x)

which is the same as (combining the c’s and non c’s)

c*(in1.y – in2.y)* (in3.x – in1.x) – c*(in1.y – in3.y) * (in2.x – in1.x) =
(out3.x – out1.x) * (in2.x – in1.x) – (out2.x – out1.x)* (in3.x – in1.x)

in which we can isolate c:

c * ((in1.y – in2.y)* (in3.x – in1.x) – (in1.y – in3.y) * (in2.x – in1.x)) =
(out3.x – out1.x) * (in2.x – in1.x) – (out2.x – out1.x)* (in3.x – in1.x)

finally giving us:

c = ((out3.x – out1.x) * (in2.x – in1.x) – (out2.x – out1.x)* (in3.x – in1.x)) /
((in1.y – in2.y)* (in3.x – in1.x) – (in1.y – in3.y) * (in2.x – in1.x))

Now as a last step we are going to expand this and factor it again on different values:

Expanding:

c = (out3.x*in2.x – out3.x*in1.x – out1.x*in2.x + out1.x*in1.x – out2.x*in3.x + out2.x*in1.x + out1.x*in3.x – out1.x*in1.x) / (in1.y * in3.x – in1.y*in1.x – in2.y*in3.x + in2.y*in1.x – in1.y*in2.x + in1.y*in1.x + in3.y*in2.x – in3.y * in1.x)

Removing out1.x*in1.x – out1.x*in1.x and – in1.y*in1.x + in1.y*in1.x:

c = (out3.x*in2.x – out3.x*in1.x – out1.x*in2.x – out2.x*in3.x + out2.x*in1.x + out1.x*in3.x) / (in1.y * in3.x – in2.y*in3.x + in2.y*in1.x – in1.y*in2.x + in3.y*in2.x – in3.y * in1.x)

And now factoring out the Out fields:

c = (out1.x*(in3.x-in2.x)+out2.x*(in1.x-in3.x)+out3.x*(in2.x – in1.x)) /
(in1.y *(in3.x-in2.x)+in2.y*(in1.x-in3.x)+in3.y*(in2.x – in1.x)))

So now we have c, which means a is found quickly as well:

a = (out2.x – out1.x + c*(in1.y – in2.y)) / (in2.x – in1.x)

So (we might think) we don’t have to expand the whole equation again, since c is known at this point. We do have to repeat this process for b and d however. This is easily done now that we know how. It irks however to start with c, so instead let’s assume we solved for a and b, and define c and d in terms of them.
After solving a,b,c,d, the tx and ty follow directly from:

tx = out1.x –a*in1.x – c*in1.y
ty = out1.y – b*in1.x – d*in1.y

Putting this all together, refactoring and simplifying the equations so that we reduce the number of computations, we can say that given 3 In points and 3 Out points the transformation from In to Out points is given by:

var dIn21x:Number = in2.x – in1.x;
var dIn32y:Number = in3.y – in2.y;
var dIn13y:Number = in1.y – in3.y;
var dIn21y:Number = in2.y – in1.y;

var denomAB:Number = 1/((in1.x * dIn32y) + (in2.x * dIn13y) + (in3.x * dIn21y));

var matrix:Matrix = new Matrix();
matrix.a = ((out1.x * dIn32y) + (out2.x * dIn13y) + (out3.x * dIn21y)) * denomAB;
matrix.b = ((out1.y * dIn32y) + (out2.y * dIn13y) + (out3.y * dIn21y)) * denomAB;
matrix.c = ((matrix.a * dIn21x) + out1.x – out2.x) / -dIn21y;
matrix.d = ((matrix.b * -dIn21x) + out2.y – out1.y) / dIn21y;
matrix.tx = out1.x – (matrix.a * in1.x) – (matrix.c * in1.y);
matrix.ty = out1.y – (matrix.b * in1.x) – (matrix.d * in1.y);

But although we nicely reduced these equations, we see that c and d are NaNs when point 1 and 2 are on the same y. That can’t be right! So although these equations work when they aren’t, we’ve got ourselves a problem.
The only way around that is to rewrite the equations to a less reduced form:

var dIn32x:Number = in3.x – in2.x;
var dIn13x:Number = in1.x – in3.x;
var dIn21x:Number = in2.x – in1.x;
var dIn32y:Number = in3.y – in2.y;
var dIn13y:Number = in1.y – in3.y;
var dIn21y:Number = in2.y – in1.y;

var denomAB:Number = 1/((in1.x * dIn32y) + (in2.x * dIn13y) + (in3.x * dIn21y));
var denomCD:Number = 1/((in1.y * dIn32x) + (in2.y * dIn13x) + (in3.y * dIn21x));

var matrix:Matrix = new Matrix();
matrix.a = ((out1.x * dIn32y) + (out2.x * dIn13y) + (out3.x * dIn21y)) * denomAB;
matrix.b = ((out1.y * dIn32y) + (out2.y * dIn13y) + (out3.y * dIn21y)) * denomAB;
matrix.c = ((out1.x * dIn32x) + (out2.x * dIn13x) + (out3.x * dIn21x)) * denomCD;
matrix.d = ((out1.y * dIn32x) + (out2.y * dIn13x) + (out3.y * dIn21x)) * denomCD;
matrix.tx = out1.x – (matrix.a * in1.x) – (matrix.c * in1.y);
matrix.ty = out1.y – (matrix.b * in1.x) – (matrix.d * in1.y);

Looking when denomAB or denomCD are NaNs now, show us that all the in points have to be on a straight line for that to happen (which sounds reasonable).

Below is a simple example which shows what we’ve got so far in practice. A UV mapping tool :) (about 1% done that is), unlike the previous images, this one is interactive, drag the handles:

Download this example: Map Example (77).

So with these equations in hand we can start to look at the more general cases.

Right triangle mapping

Let’s only evaluate cases that are actually meaningful. The equations we have found so far are meaningful since they allow arbitrary uvw mapping. Another meaningful variant is where we want to map a right triangle from the source bitmap to a destination triangle (which is usually not right sided). This is meaningful because a lot of times our textures will be rectangular and we will be cutting pieces with right angles from them.

Looking at this triangle:

in1 — in2
|
|
in3

We see in1.x-in3.x = 0 and in1.y – in2.y = 0 and in3.y-in2.y = in3.y-in1.y this gives us:

var dIn32x:Number = in3.x – in2.x;
var dIn13x:Number = 0;
var dIn21x:Number = -dIn32x;
var dIn32y:Number = in3.y – in2.y;
var dIn13y:Number = -dIn32y;
var dIn21y:Number = 0;

var denomAB:Number = 1/((in1.x * dIn32y) + (in2.x * dIn13y) + (in3.x * 0));
var denomCD:Number = 1/((in1.y * dIn32x) + (in2.y * 0) + (in3.y * dIn21x));

var matrix:Matrix = new Matrix();
matrix.a = ((out1.x * dIn32y) + (out2.x * dIn13y) + (out3.x * 0)) * denomAB;
matrix.b = ((out1.y * dIn32y) + (out2.y * dIn13y) + (out3.y * 0)) * denomAB;
matrix.c = ((out1.x * dIn32x) + (out2.x * 0) + (out3.x * dIn21x)) * denomCD;
matrix.d = ((out1.y * dIn32x) + (out2.y * 0) + (out3.y * dIn21x)) * denomCD;
matrix.tx = out1.x – (matrix.a * in1.x) – (matrix.c * in1.y);
matrix.ty = out1.y – (matrix.b * in1.x) – (matrix.d * in1.y);

Which is, staying in actionscript modus:

var dIn32x:Number = in3.x – in2.x;
var dIn32y:Number = in3.y – in2.y;

var denomAB:Number = 1/((in1.x * dIn32y) – (in2.x * dIn32y) );
var denomCD:Number = 1/((in1.y * dIn32x) – (in3.y * dIn32x));

var matrix:Matrix = new Matrix();
matrix.a = ((out1.x * dIn32y) – (out2.x * dIn32y)) * denomAB;
matrix.b = ((out1.y * dIn32y) – (out2.y * dIn32y) ) * denomAB;
matrix.c = ((out1.x * dIn32x) – (out3.x * dIn32x)) * denomCD;
matrix.d = ((out1.y * dIn32x) – (out3.y * dIn32x)) * denomCD;
matrix.tx = out1.x – (matrix.a * in1.x) – (matrix.c * in1.y);
matrix.ty = out1.y – (matrix.b * in1.x) – (matrix.d * in1.y);

Rewriting this with a further reduction/simplification:

var dIn32x:Number = in3.x – in2.x;
var dIn32y:Number = in3.y – in2.y;

var denomAB:Number = 1/(dIn32y * (in1.x-in2.x));
var denomCD:Number = 1/(dIn32x * (in1.y-in3.y));

var matrix:Matrix = new Matrix();
matrix.a = (dIn32y*(out1.x-out2.x)) * denomAB;
matrix.b = (dIn32y*(out1.y-out2.y)) * denomAB;
matrix.c = (dIn32x*(out1.x-out3.x)) * denomCD;
matrix.d = (dIn32x*(out1.y-out3.y)) * denomCD;
matrix.tx = out1.x – (matrix.a * in1.x) – (matrix.c * in1.y);
matrix.ty = out1.y – (matrix.b * in1.x) – (matrix.d * in1.y);

And the last step, eliminating the dIn32x/y’s:

var denomAB:Number = 1/ (in1.x-in2.x);
var denomCD:Number = 1/ (in1.y-in3.y);

var matrix:Matrix = new Matrix();
matrix.a = (out1.x-out2.x) * denomAB;
matrix.b = (out1.y-out2.y) * denomAB;
matrix.c = (out1.x-out3.x) * denomCD;
matrix.d = (out1.y-out3.y) * denomCD;
matrix.tx = out1.x – (matrix.a * in1.x) – (matrix.c * in1.y);
matrix.ty = out1.y – (matrix.b * in1.x) – (matrix.d * in1.y);

BUT note that this will only work for triangles out of a bitmap with a right triangle between in1-in2 and in1-in3. If you create the angle between different points, you will have to write other optimized equations (or swap the in values so that in1-in2 and in1-in3 form a right triangle, and swap the out points accordingly). You can also see how some of the x’s and y’s of the input values are simply ignored, since these equations ‘assume’ these ignored x’s and y’s are equal to other x’s and y’s that are kept in the equations.

Complete bitmap mapping

So the next simplification on our list is mapping a complete bitmap to a triangle/rectangle. If our equations are correct this will result in:

a = (p1.x-p0.x)/width
b = (p1.y-p0.y)/width
c = (p2.x-p0.x)/height
d = (p2.y-p0.y)/height
tx = p0.x
ty = p0.y

since that is what we found when previously deriving this through another way.

If our complete bitmap is used, with right angles that is (so we continue our previous context), the following holds:

in3.x – in2.x = -bw
in3.y – in2.y = bh
in1.x-in2.x = -bw
in1.y-in3.y = -bh
in1.x = in1.y = 0

Where bw and bh are the bitmap width and height respectively. This results in:

var dIn32x:Number = -bh;
var dIn32y:Number = bh;

var denomAB:Number = 1/(dIn32y * (-bw));
var denomCD:Number = 1/(dIn32x * (-bh));

var matrix:Matrix = new Matrix();
matrix.a = (dIn32y*(out1.x-out2.x)) * denomAB;
matrix.b = (dIn32y*(out1.y-out2.y)) * denomAB;
matrix.c = (dIn32x*(out1.x-out3.x)) * denomCD;
matrix.d = (dIn32x*(out1.y-out3.y)) * denomCD;
matrix.tx = out1.x;
matrix.ty = out1.y;

Simplifying further gives us:

var denomAB:Number = 1/(bh * (-bw));
var denomCD:Number = 1/(-bh * (-bh));

var matrix:Matrix = new Matrix();
matrix.a = (bh*(out1.x-out2.x)) * denomAB;
matrix.b = (bh*(out1.y-out2.y)) * denomAB;
matrix.c = (-bh*(out1.x-out3.x)) * denomCD;
matrix.d = (-bh*(out1.y-out3.y)) * denomCD;
matrix.tx = out1.x;
matrix.ty = out1.y;

And further:

var denomAB:Number = 1/(bh * (-bw));
var denomCD:Number = 1/(-bh * (-bh));

var matrix:Matrix = new Matrix();
matrix.a = (bh*(out1.x-out2.x)) / (bh * -bw);
matrix.b = (bh*(out1.y-out2.y)) / (bh * -bw);
matrix.c = (-bh*(out1.x-out3.x)) /(-bh * (-bh);
matrix.d = (-bh*(out1.y-out3.y)) /(-bh * (-bh);
matrix.tx = out1.x;
matrix.ty = out1.y;

Which is:

var matrix:Matrix = new Matrix();
matrix.a = (out2.x-out1.x) / bw;
matrix.b = (out2.y-out1.y) / bw;
matrix.c = (out3.x-out1.x) / bh;
matrix.d = (out3.y-out1.y) /bh;
matrix.tx = out1.x;
matrix.ty = out1.y;

Which is exactly what we already derived in the first post (although we used p0-p2 instead of out1 to out3).

Identity mapping

So one last not so useful insight: mapping a complete bitmap to a rectangle of the same size without any transformations. For that transformation the following holds:

out2.x-out1.x = bw
out2.y-out1.y = 0;
out3.x-out1.x = 0;
out3.y-out1.y = bh;
out1.x = out1.y = 0;

Inserting them in the equations above results in the identity matrix. Isn’t that beautiful!

So this concludes our post on how we are going to map texture coordinates to subdivided plane coordinates. Rendering an actual plane will be up in the next post!

Part Ib – Butterfly intermezzo

Wednesday, April 28th, 2010

In the previous post we talked about affine transforms and splitting rectangles into triangles. We only talked about one of the simplest scenarios: mapping a complete rectangular area of a bitmap into a drawn triangle/rectangle. The formulas for mapping an arbitrary triangle of a source bitmap into an arbitraty drawn triangle are a bit more involved and we will discuss them in the next post. But even with two triangles we can create something simple to create a feel of 3d. Far from being a correct implementation, this example experiments with a 3d butterfly based on two sources images (the front and back of a butterfly).

The ‘model’ exists of two triangles, one for each wing, which are folded and rotated using a couple of sinuses. The resulting coords are used to generate the transform matrices for the bitmap fills on the wings. Which bitmap is used to fill a triangle is decided using a cross product of the wing vectors, to check whether we are seeing the top or the bottom of the wing. Since there is no real perspective it is sometimes hard to see which side is in front, but nonetheless this opens up fun possibilities for a children’s game where you can draw a butterfly and then have it fly off your paper into a garden or something like that (where 100% correct perspective wouldn’t matter anyway).

One thing I’ve noted is that when you use smooth bitmapfills without explicitly closing the filled polygon the flash player will sometimes hang / lock up for seconds at a time. Not sure what causes this, and I hope closing the polygon is the ‘real’ fix.

Here is the complete example with sourcecode included: Butterfly2 (87). It’s a Flash IDE FlashDevelop project, meaning you can edit it in FlashDevelop but have to export it from the Flash IDE. If you don’t have a flash ide, setup the butterfly swf as injection swf and specify another output.

Part I – Affine Transforms

Sunday, April 25th, 2010

Also known as:

  • fake 3d using bitmapfills
  • transforming bitmapfills

For a project I’m currently working on I have to create a textured cube with clickable hotspots (aka a panorama) that can light up. Easy enough in Papervision, if not for the fact that the performance needs to be very high and oh yes one minor detail it has to be in AS2.
The client will be able to load 6 cubesides in an editor, draw hotspots on each side and press render, which will result in a 3d panorama, in which hotspots are going to light up as you mouse over them. Ah yes did I mention this was AS2?

A good reason to finally dive into the world of 3D myself. Although this provides us with a huge range of items to discuss and play with, in this post I want to focus on the basics of one single subject: rendering a textured plane in 3d. I’ll get to the rotating/projecting etc bits later, can’t have too much coolness in one post ;) .


I’ll try to refer to existing material to save myself some time, so I might be skipping through some stuff very quickly while focussing in on other.

As you might already know, Actionscript 2 doesn’t have 3d built in. It doesn’t have perspective distortion built in either. It does however allow you to scale, skew, translate and rotate clips using a so called transform matrix. We can use this fact to simulate/fake 3d. So how do you simulate 3d by skewing, scaling and rotating rectangular pictures? Seb Lee-Delisle explains the concepts very well. So before continuing read http://sebleedelisle.com/3d-example-files/3d-presentation-texture-maps/ first.

Welcome back. Understanding the concepts is a bit different from actually implementing it though, but it’s halfway there. Before I said that 3d is faked in flash by scaling, skewing and rotating clips, however it is usually created by matrix transforming bitmap fills instead of movieclips and using those bitmap fills on dynamically drawn triangles (using the flash drawing API). Nonetheless matrix transforming movieclips is a good way to start to learn this stuff and we’ll get to the actual triangle rendering later.

So before I get to explaining how a plane is split into rectangles, how rectangles are split into triangles, and how triangles are filled with a bitmap matching their orientation to produce the illusion of 3d perspective, we are first going to look into transforming movieclips to get a good feeling for the effect a transform matrix has on a movieclip.
I advice you not to look at the Flash Matrix API Docs, since aside from the fact that it contains errors, is pretty limited. Senocular has provided a much better resource at http://www.senocular.com/flash/tutorials/transformmatrix/, but it might be a bit overwhelming to read through. If you are short on time I would advise you to read at least these parts (but I’ll give a quick recap as well):

  • Matrices and the Transform Matrix, on what a matrix is and how matrix multiplication works
  • Applying Transformations

If you scroll down to ‘The ActionScript Matrix Class’ there is an interactive plaything just above that, or you can use the one below to play with the matrix interactively. Just note that there is much more information on the senocular page than what you need to know for this section.

Download this example: Skewing (110)

The images in the example above are from the flash docs, where the red arrow points out the error in the documentation. So replace b with c, and SkY with SkX and then you are good to go. (So b represents SkY and c represents SkX).

If you have skipped the senocular post about the transform matrix, or glossed over, I’ll just give you a quick recap/empirical take on the matrix.
First off, note that all the values in the numeric steppers are taken directly from the clip’s transform matrix by reading out MC_rectangle.transform.matrix.a/b/c/d/tx/ty.

The rectangle has four points with the local coordinates P0(0,0), P1(100,0), P2(0,100) and P3(100,100). Note that the rectangle is located at position (300, 70) on the screen (believe me it’s true ;) ), so the global coordinates for those points are P0′(300,70), P1′(400,70), P2′(300,170) and P3′(400,170).

So how did the transform matrix turn those local coordinates into those global coordinates? (An easy question if you have read and understood the senocular page):

  • first when working with translating matrices we usually add a w component of 1 (unless it’s a normal vector, but not for now)
  • then we perform all the calculations, for each point Pn we get Pn’ by performing matrix multiplication


Transforming Pn to Pn’

TRANSFORMMATRIX x Pn = Pn’

which is:

a c tx		PnX			PnX'
b d ty  * 	PnY 	=		PnY'
0 0 1		1			PnW'

which is, written differently again using the definition of matrix multiplication (if you are used to matrices, you’ll do this in your head instead of writing it out in cumbersome equations, but anyway):

PnX' = a*PnX + c*PnY + tx*1
PnY' = b*PnX + d*PnY + ty*1
PnW' = 0*PnX + 0*PnY + 1*1

which is simplified:

PnX' = a*PnX + c*PnY + tx
PnY' = b*PnX + d*PnY + ty
PnW' = 1

or more condensed:

Pn' (PnX, PnY) = (a*PnX + c*PnY + tx, b*PnX + d*PnY + ty)

So let’s try this for our example, where we had P0(0,0), P1(100,0), P2(0,100) and P3(100,100), with a=1, d=1, b=c=0, tx=300, ty=70.
(a*PnX + c*PnY + tx, b*PnX + d*PnY + ty) becomes (PnX + 300, PnY + 70) if we fill in the a,b,c,d, tx & ty:

P0' (0,0) = (P0X + 300, P0Y + 70) = (0 + 300, 0 + 70) = (300,70)
P1' (100,0) = (P1X + 300, P1Y + 70) = (100 + 300, 0 + 70)= (400,70)
P2' (0,100) = (P2X + 300, P2Y + 70) = (0 + 300, 100 + 70)=(300,170)
P3' (100,100) = (P3X + 300, P3Y + 70) = (100 + 300, 100 + 70) = (400,	170)

And we have a winner!

Just for practice and to get a good grip on the matrix, let’s try a couple different ones. Note that what we have just tested is that applying the matrix to the local coordinates does indeed result in the clip as we see in on the screen. Looking at the meaning of the different elements we saw that the x and y scale where 1 (a and b), so there was no scaling, b & c are 0, so no skewing and tx and ty where 300 and 70 respectively, which did indeed result in the translation of each point by dx=300 and dy=70.

So we proved tx & ty directly affect the translation. Let’s play with scaling now then. According to our matrix docs, a influences the x scaling and d the y scaling, so let’s set a to 0.5 and d to 2. Our rectangle starts at (300,70) and has dimensions 100×100, so x scaling by 0.5 and y scaling it with 2 should give us a rectangle at (300,70) (since the translation doesn’t change) with dimensions 50×200. So intuitively the transformed local coordinates should be:

P0(0,0), P1(50,0), P2(0,200) and P3(50,200)

and thus the transformed global coordinates (by translating them over 300×70):

P0(0+300,0+70), P1(50+300,0+70), P2(0+300,200+70) and P4(50+300,200+70) =
P0(300,70), P1(350,70), P2(300,270) and P3(350,270)

Well let’s enter our a,b,c,d,tx,ty into our Pn’ calculations formula’s and see that these are indeed the coordinates we get by using this matrix:
a=0.5, b=c=0, d=2, tx=300, ty=70, so (a*PnX + c*PnY + tx, b*PnX + d*PnY + ty) becomes (0.5*PnY+300, 2*PnY+70).

P0' (0,0)= (0.5*P0Y+300, 2*P0Y+70) = (0.5*0+300, 2*0+70) =(300,70)
P1' (100,0)= (0.5*P1Y+300, 2*P1Y+70) = (0.5*100+300, 2*0+70) =(350,70)
P2' (0,100) = (0.5*P2Y+300, 2*P2Y+70) = (0.5*0+300, 2*100+70) =(300,270)
P3' (100,100) = (0.5*P3Y+300, 2*P3Y+70) = (0.5*100+300, 2*100+70)=(350,270)

We see the a & d elements indeed perform the scaling operation.

Last of the 3 basic operations is skewing, which is achieved by modifying the b & c elements. So refer to the gadget once again, and set the elements to
a = 1, b = 0, c = 1, d = 1, tx = 300, ty=70. You see visually that P2 is now aligned with P1 on the x coord (although by a trick of the eye it doesn’t seem that way). Another thing to note is that skewing changes the x coordinates of points based on their difference in y coordinates. P0 and P1 have the same y coordinates, so their delta x remains unchanged. P2 and P3 have an y that is 100 more than P0 and P1 so their x’s are moved 100 pixels * the c factor with respect the x’s of P0 and P1.

The fact that the delta x of points with the same y doesn’t change by skewing over x and the delta y of points with the same x doesn’t change by skewing over y is an important one which we’ll get back to later.

So intuitively where did our P0(0,0), P1(100,0), P2(0,100) and P3(100,100) end up? Looking in the interactive tool we can see P0 and P1 remained stationary while P2 and P3 where translated by 100 over x to (100,100) and (200,100) respectively. In global coordinates P0(300,70), P1(400,70), P2(400,170) and P3(500,170). Verifying this through (a*PnX + c*PnY + tx, b*PnX + d*PnY + ty), with a=1, b=0, c=1, d=1, tx=300 and ty=70 gives us (PnX + PnY + 300, PnY + 70):

P0' (0,0) = (P0X + P0Y + 300, P0Y + 70) = (0+0+300, 0+70) = (300,70)
P1' (100,0) = (P1X + P1Y + 300, P1Y + 70) = (100+0+300,0+70) = (400,70)
P2' (0,100) = (P2X + P2Y + 300, P2Y + 70) = (0+100+300, 100+70)= (400, 170)
P3' (100,100) = (P3X + P3Y + 300, P3Y + 70) = (100+100+300, 100+70)= (500, 170)

As an exercise for you try to pick values which allow you to intuitively/visually see what the coordinates of the transformed points are, then use the (a*PnX + c*PnY + tx, b*PnX + d*PnY + ty) formula to verify that that is indeed correct.

One thing I haven’t touched upon is the rotate method. Mathematically rotation is defined through a matrix using sinuses and cosinuses. I’m not going into details about that right now, if you want to know more about rotation take a look at http://en.wikipedia.org/wiki/Transformation_matrix. The only thing I’d like to show you is that it is indeed possible to rotate a movieclip by altering it’s transformation matrix (which is actually exactly how flash does it when you set the _rotation property on a clip).

For example set the properties back to a=1, b=c=0, d=1, tx=300 and ty=70.

Now in order, keep clicking:

  • b until it is 1
  • c until it is -1
  • d until it is 0
  • a until it is 0

Voila rotation by 90 degrees! Although this might be hard to grasp if you have no experience with rotation matrices, and even harder to see how scaling and skewing accomplishes this, the easiest way to grasp this is to let go of the fact that a and d scale and b and c skew, since as you might have noticed after applying the above transforms a & d skew and b & c scale!
So it’s easier to accept that a, b, c, d transform vertices and when modifying a single property in the identity matrix, a has the effect of … b of .. etc, but might have other effects if any of the other properties have been modified. Back to rotation then, to understand how a-d influence the rotation of an object, you have to understand the formula’s for rotating points in 2d space. A good overview (& proof!) is given in http://www.petesqbsite.com/sections/tutorials/tuts/relsoft3d/Chapter2/Chapter2.htm, “Section V. 2d Rotation”. Once you understand that check out http://en.wikipedia.org/wiki/Transformation_matrix#Rotation which shows how to write these 2d rotation formulas in matrix form, and scroll a bit down to affine transforms, where you find the matrix we’ve been talking about here.

But ENOUGH about rotation since we are not going to use it anyway for now.

When we are going to draw triangles later, we are going to do so through the Flash Drawing API (moveTo, lineTo, etc). The texture of the triangles will be realized through bitmapfills (beginBitmapFill). We will have to match the bitmapfill for those triangles in a certain way in order to provide the illusion of 3d. If you wish you can refer back to http://sebleedelisle.com/3d-example-files/3d-presentation-texture-maps/ on what we are going to do exactly. The basics for that technique is being able to match a transform, or rather deduct a transform from a number of given vertices (points). It doesn’t matter whether you are transforming a bitmapfill or a movieclip, let’s just say bitmap equals movieclip at this point. Senocular has provided an interactive tool that demonstrates this principle, right at the bottom of http://www.senocular.com/flash/tutorials/transformmatrix/. You can see that we even only need 3 points to do so, which is great, since 3 points make a triangle which is exactly what we are going to need later on.

Although you can download the cube example from his page and check out the formula’s, I’ll try to explain and show to you what the formulas are and why.
Refer back to the matrix gadget above. Note that no matter what you change, P0 stays at the same location. This is logical looking at (a*PnX + c*PnY + tx, b*PnX + d*PnY + ty) since P0X and P0Y are zero, so P0 (0,0) = (a*0 + c*0 + tx, b*0 + d*0 + ty) = (tx, ty);

In other words:

matrix.tx = p0.x;
matrix.ty = p0.y;

Now change a, and look at P1′ for example: P1′ (100,0) = (a*P1X + c*P1Y + tx, b*P1X + d*P1Y + ty) = (100a + tx, 100b+ty).

So we have:

p0.x = tx;
p0.y = ty;
p1.x = 100a+tx;
p1.y = 100b+ty;

Rewriting this gives us (using p0.x and p0.y as tx and ty in the p1 equations):

p1.x = 100a+p0.x
p1.y = 100b+p0.y

100a = p1.x-p0.x;
100b = p1.y-p0.y;

a = (p1.x-p0.x)/100;
b = (p1.y-p0.y)/100;

If we had used width and height instead of a 100 and 0, we would see that this is actually:

a = (p1.x-p0.x)/width
b = (p1.y-p0.y)/width

Intuitively this seems correct, if we look at A as the xscale factor again, imagine our original clip is a 100 pixls wide, but the distance between it’s upperleft and upperright point is 200, then apparently it’s scaled by a factor 2, so a = (200-0)/100 = 2.

B is the y skewing, as we mentioned earlier, coordinates with the same x will have the y displacement, coordinates with the same y will have the same x displacement. In other words, the difference in displacement on one axis is directly tied to the distance between those points on the other axis.
So if P1 has an x 100 higher than P0, it’s y displacement will be b*100 (try it out in the interactive tool). Which is exactly the same as saying b = (p1.y-p0.y)/width. But more officially, let’s derive the other formulas using P2.

As you have guessed by now, we can do the same for P2′ (0,100):

P2' (0,100) =
P2' (0, height) =
(a*P2X + c*P2Y + tx, b*P2X + d*P2Y + ty) =
(a*0 + c*height + tx, b*0 + d*height + ty) =
(c*height + tx, d*height + ty).

So:

p0.x = tx;
p0.y = ty;
p2.x = height*c+tx;
p2.y = height*d+ty;

p2.x = height*c+p0.x;
p2.y = height*d+p0.y;

c = (p2.x-p0.x)/height
d = (p2.y-p0.y)/height

Combine this with what we already had and the matrix becomes:

a = (p1.x-p0.x)/width
b = (p1.y-p0.y)/width
c = (p2.x-p0.x)/height
d = (p2.y-p0.y)/height
tx = p0.x
ty =p0.y

But wait you say, you are setting P1.y and P2.x to zero! Yes indeed, remember the tx and ty. When distorting a square clip, P0 will always be (tx, ty) and thus it’s local coordinates (0,0) and hence P1.y and P2.x will always be zero (locally).

That settles it then! We’ve got the calculations for all matrix values based on three points P0, P1 and P2, which form a triangle. As mentioned before you can see this in action at the bottom of the senocular page if you’d like to play with it.

Now remember what I said about bitmapfills being transformed the same way while triangles are drawn and rendered using the drawing API. The example below shows this principle. Choose a bitmap and play with the points. Still in AS2, but easy to migrate to AS3. Note that the original bitmaps chosen on the left have different sizes. The equations don’t become that different however, we simply plug in the bitmap’s size in the equation instead of a movieclip size! Not so fundamentally different from transforming movieclips at all.

Download the code for this example: Bitmapfill1 (109)

Although we could implement another cube here, I leave that as an exercise for you, take a look at senoculars cube and the bitmapfill method downloadable above and have a blast. The major difference is probably that the sides are no longer interactive, but we’ll fix that in the future I promise ;) .

Up to this point we’ve shown that we can deduct point number P3 based on P0, P1, P2 (see the source code of the example), and that we can generate a matrix based on three points, and that with it we can render a triangle with a bitmap. As shown in Seb Lee-Delisle’s presentation we need partly independent triangles however, so I’ve updated the example slightly:

Download the code for this example: Bitmapfill2 (114)

Now you can alter 4 handles. Things to note are that to keep the equations simple, we’ve split the rectangle in an upperright triangle and lowerright triangle so that they have P0 in common. In addition although we can now freely distort the square, this is only the first step. The rendered distortion bitmap is not in perspective, and you can see how awkward the shield and grid look. Of course now that the fourth handle is free and not locked to the position of P0, P1, and P2 you can do things like fold the triangles (when P1 to P2 don’t intersect P0-P3 in the middle).

All that is content for another post though, so check back soon for the next article in this series on how to create a 3d panorama in ActionScript 2 (no worries I will migrate it to AS3 as last step). Next time we will look into not rendering a bitmap into a triangle, but rendering a specific part of a bitmap into a triangle, which is the first step on our way to texturemapping.

Copy FlashDevelop output to another location

Wednesday, April 21st, 2010

Just a small tip if you want to copy the output of your FlashDevelop build to another location:

  • open project settings
  • Go to post build section
  • paste: c:\windows\system32\cmd.exe /k copy “$(OutputDir)\$(OutputName)” “c:\temp\”
  • replace c:\temp\ with a path of your choice

If your FlashDevelop version hangs on the build because of this, place a batchfile in your project root called buildActions.bat and call that one instead.

For example the post build becomes: $(ProjectDir) buildActions.bat “$(OutputDir)\$(OutputName)” and you can do whatever you want from the batchfile.