I think that this morning I’ll make the little circle drive around the big one. I could be wrong. (Added in Post: We get a test to run, but don’t get to the drawing.)

The plan this morning will do to modify the program so that the little circle can drive around the inside of the big one, as nature intended. There’s just the tiniest bit of math to doing that, and I plan to draw a picture for you as well. It’ll be ready for next time.

The Math

If a wheel rotates by an angle a, it will roll a distance r*a, where r is its radius. Why is that? Because the circumference of a circle is, as we all remember, two pi times r, and, which most of us may not remember, two pi is an angle in “radians”, equal to 360 degrees.

Since our wheel is driving without slippage along a larger circle, it will also trace a path of distance r*a on the larger circle. From the viewpoint of the larger circle, that path subtends an arc of some angle, and that angle, B will be such that d = R*B, where R is the radius of the big circle.

So

R*B = r*a, and therefore
B = r*a/R
B = d/R

Given that angle, we know that, from the viewpoint of the big circle, when the little one has rolled due to its angle a, we’ll see the circle as having moved through an angle B, so when the little circle asks us where to move, we can take its current position and rotate it by the B angle and that should be the answer.

One caveat: between the complexities of thinking about circles and spinning, and the complexities of the screen coordinates, I’m not sure which way the thing will roll. My guess is that it will roll counter-clockwise. Let’s find out.

The Plan

I’m going to do the move on touch, not on the clock, so that I can better see on the screen what has happened.

I don’t see anything that I want to test in TDD style. I may regret this. I suppose we could test the angle calculation. Let’s try that.

A child circle requests its new position from its parent like this:

function Circle:getNewPosition()
    self.x, self.y = self.owner:moveMe(self, 0)
end

And that’s done like this right now, which is wrong:

function Circle:moveMe(child,d)
    local x = self.x
    local y = self.y - self.radius + child.radius
    return x,y
end

That was just ad hoc code to paste us to the bottom of our parent. (Remind me to rename owner to parent.)

Let’s write a TDD test for moveMe. We’ll make the big circle radius 100, the small 50. We’ll roll the small one 90 degrees (pi/2 radians), which should move it 45 degrees in the big one. If we start the small one at 0,0, it should, I think, move to 50,50. That’s the test I’ll write anyway.

        _:test("moveMe gets correct answer", function()
            local big = Circle(100) -- no owner
            local small = Circle(50, big)
            local d = 50*math.pi/2 -- 90 degrees
            big:moveMe(small, d)
            _:expect(small.x).is(50)
            _:expect(small.y).is(50)
        end)

Test will not run correctly now. It gets 0 for both. I can live with that. Let’s code moveMe.

Hm. All I want to know is my angle A, given D, so by the reasoning above, A = d/R, my radius.

Arrgh, another issue, I’m not thinking clearly. The little circle can’t be at 0,0. His coordinates have to be relative to the big circle. So his starting position, at the bottom, is x = 0, y = -50 relative to the big circle? And surely he won’t be at 50,50, he’ll be at a distance of 50 from the center, or about (35.35, -35.35). Fix the test, and let’s just print and see what happens.

In addition, I’m calling moveMe, which returns the values, doesn’t store them. So my test is:

        _:test("moveMe gets correct answer", function()
            local big = Circle(100) -- no owner
            local small = Circle(50, big)
            small.x = 0
            small.y = -50
            local d = 50*math.pi/2 -- 90 degrees
            local x,y = big:moveMe(small, d)
            _:expect(x).is(35.35, .01)
            _:expect(y).is(-35.35, .01)
        end)

And my code is:

function Circle:moveMe(child,d)
    local myAngle = d/self.radius
    local newPos = vec2(child.x, child.y):rotate(myAngle)
    return newPos.x, newPos.y
end

And my test runs green. Whee. It’s Sunday, and I’m out of time. Let me sum up and try to figure out what happened and what’s next.

Summary

My biggest mistake was not drawing a picture and putting numbers on it. Once I scribbled something on a handy card, it was clear that I wasn’t thinking clearly about what the coordinates should be, relative to whom. And this issue isn’t resolved.

As presently built, the drawing expects our objects to have absolute coordinates, and this calculation is done in relative coordinates. We need to resolve that issue before anything is going to work both in math and on screen.

We’ll sort it out next time. I think what we should do is express the relationships in relative terms when we create the circles, and give them all absolute positions thereafter. But I’m not sure.

I think what we’ll have to do is get it working, and then look at the code and see where to use absolute coordinates and where to use relative. That will also bring up the question of whether to use screen transformations or do the math ourselves.

First, make it work. Then, make it right.

We’re on the way … we computed a move correctly!

See you next time!