This blog desperately needs more posts, but I hardly have any subjects to write at length about. And this is where wonderfl comes to the rescue – some of those codes are so complicated that one could give two hours talk about them. This code is that kind of code, and tonight I am going to explain just one small part of it – drawing numbered billiard balls.
Once upon a time Drew Cummins wrote continuous collision detection example and shared it on wonderfl. More than two years later I made this fork adding interactivity for it to resemble the game of billiards. This fork was not that different from original code, because I never had enough inspiration to build on it. Until another half of year later when 李志 made new fork incorporating interesting code to draw numbered billiard balls by 浩. It simulates rolling by sliding TextField under circular mask in the direction of ball movement. That works great, except it does not account for 3D distortions and also is a bit slow because of all that display list and masking business. That was the inspiration I was looking for and, after some failed attempts, I came up with better method which is now used in a number of forks there.
A word from our sponsor
This method will probably be featured in Box2D tutorial on soon to go live code orchestra site, which is why I have to write an explanation for it, one way or another. For those who missed the news – code orchestra is the new name of realaxy actionscript editor I used to post about. Before you ask, I do not have ETA for it. Because of its new “live coding” feature refactoring as well as some other issues originally planned release date was missed. Twice. But, good things come to those who wait, right?
Now, where was I?
Oh yeah – billiard balls. Numbered billiard balls. I will skip the part about coloured circular gradient, since it is obvious and well-documented, and jump straight to drawing numbers. There are two key parts to it – skewing number texture to fake 3D and rolling the ball. Let’s go over one at a time.
For the best performance you want to draw the number in single drawRect call. For that you need to provide correct fill transformation matrix to preceding beginBitmapFill. But what should that matrix be? Before you can answer that, you need to remember that transformation matrix columns are actually coordinate axes vectors of transformed object – in this case, number texture. So, to come up with formulas for your matrix, you need to consider how these vectors behave. I did it in old-fashioned way using pencil and paper, but for you I have put this animation
together to illustrate the way these vectors should change depending on the offset from ball center r (rx, ry). As you can see, the vectors coincide with r‘s coordinate axes in the center (forming identity transformation matrix) and then are gradually squashed towards ball edge. All you need to do now is to come up with expressions for vectors x and y values as functions of rx and ry, so that they behave like that. There may be many ways to do it, some more accurate than others, but this one worked for me:
x = 1 – rx * rx; x = 0 – rx * ry;
y = 0 – rx * ry; y = 1 – ry * ry;
Rolling the ball
This part deals with the question how does one calculate rx and ry values for the ball in order to fake the rolling. Go on, take your pencil and try to figure out accurate expressions for that – I’d love to see those, actually. But again, we are not in math class, so it will be whatever works good enough. Remember that 浩 solved this by tracking the previous ball position (you can see this method at line 177 here, rx and ry are num.x and num.y). This is good solution, but it has that critical flaw, so I thought why don’t we make it better and use current ball position only? To get there, imagine the ball of radius R rolled horizontally: it should make one full cycle over 2πR span, so it seems that cos (x / R) is good candidate for rx. Turns out it is, except for half of period it goes in wrong direction, so we have to fix it by multiplying it with -1 there. Then, if we do the same for y, there will be areas where rx²+ry²>1 – by coincidence in these areas we can switch to sines and have it working again. Voila! We now have rx and ry as simple functions of x and y (you can see this at lines 145 to 160 here). The downside is that your balls “roll” slower than they should moving diagonally, but that is barely noticeable.
Taking it to GPU