Congruence of deceleration

Yesterday’s post looked at vectors between two objects. Taking their x and y differences enabled us to derive the magnitude of vectors pointing to either one. The deceleration example in “Java Game Programming for Dummies” uses the dx and dy values of the existing vector before subtracting a scaled unit vector from it. With only one object, how do we find the vector?

We can project the object’s next position because we know its dx and dy values. We know that the object’s next position will be a new x- and y-coordinate that is dx values away across and dy values up or down (depending on sign). This ghost object is our second point.

Instead of adding the vector to exhibit repulsion behavior, the book’s example subtracts from the original vector. This leads to deceleration.

So things are nicely consistent conceptually. Using images is two chapters away!

Repulsion/attraction vectors

Reversing the order of subtraction transforms one behavior into another: a vector opposing becomes additive and a vector attracting pushes and brakes in turn. (gif)

repulse-attract

Or maybe it’s about “frame of reference.” From the perspective of the attractor, the vector formula is consistent:

tempVec.setVec(to.x - from.x, to.y - from.y); // vector from -> to

The repulsion is a vector to itself. The attraction is a vector to the unmoving object. Given two points, we can create predictably pointing vectors.

Magnitude is a different thing. The subtraction sets the vector magnitude – i.e., x and y will be updated by dx and dy, respectively – but it will teleport the object in the next frame. You need to take the unit vector, multiply it by some scale, and then add it to the object’s original vector.

Circle bounce

Set vector, multiply vector, add vector: ingredients to circle bounce (gif)

circle-wall

The golf sample in “Java Game Programming for Dummies” implements a ball resisting friction to a halt, but it’s not as simple as reversing dx and dy as in the square bounce. With a circle boundary, it’s easy to get the ball into a local minima between two points, which is not as realistic.

tempVector.setVec(x - ball.x, y - ball.y); // opposite vector

tempVector.mulVec((hbDist - distIn) / hbDist); // very small!

ball.vel.addVec(tempVector);

The second line, a vector multiplication, multiplies temporary vector dx and dy by 0.034 or so; it’s almost insigificant. I wonder how it could cause such a drastic change. It’s a multiplication of ~ 1/100, or 0.01-magnitude scaling.

Ah, okay. So the ball moves past the boundary in its update, and then I call for the boundary check. Between these calls, the ball is at least one tick past the circle (“a posteriori” collision). In the boundary check method, setVec() flips the vector for me:

ball position  x (boundary) vs. ball.x y (boundary) vs. ball.y
-------------  ----------------------- -----------------------
upper right    x < ball.x              y > ball.y
lower right    x < ball.x              y < ball.y
lower left     x > ball.x              y < ball.y
upper left     x > ball.x              y > ball.y

When the ball is high, its y position is smaller. So the (x – ball.x) and (y – ball.y) expressions flip the sign for us. Convenient.

Once the vector is flipped, we scale it by an infinitesimal amount – just enough to push it back into the boundary. On the next tick, ball update() will properly increment its x and y by the flipped vector, a magnitude in line with what we expect.

This post has been long; the next one will demonstrate this.

Reconstruct database data into a PivotTable

Pulling information out of a database and having a need to see it arranged likewise puts a peculiar strain on the end-user. Not so in analytic terms, but in the daily process of churning, rearranging and balancing ephemeral products, digital transcripts of a remote, tangible process. One of the first tasks in this context is getting the data out of denormalized form and back into a human-manipulable artifact.

I’ve covered PivotTable generation with VBScript already, so that should be straightforward: VBA macros executed from the command-line upon invisible spreadsheets, the EXCEL.EXE process the only clue of the magic behind. Once that’s finished, you can expand your sovereignty into domain-specific rules.

So with PivotTables, I’ve taken on a “if you can’t beat them, join them” tack: now their creation is the crux of further automation. If only someone could take Excel’s PivotTable four-quadrant interface and apply it to R cross-tabulation! That is a worthwhile problem to tackle.

Dynamic derring-do

Some conditionals are opaque unless I look at them in a different way.

if (hbDist < radius && hbDist > distIn) { ...

... && hbDist < distIn;

if (ball.sunk && hbDist > distIn) { ...

“hbDist” refers to hole-to-ball distance, or the distance from the center of a golf hole to the center of a golf ball – both modeled as circles. “radius” refers to the golf hole.

“distIn” is the difference between the golf hole radius and the golf ball’s radius. Neither change values, so this is a constant. Now we go to interpretation.

hbDist < radius

hbDist - radius < 0

I think of this as “hbDist gets smaller as the ball approaches the hole, until part of the ball is at the lip of the hole.” (At this point in the code, a vector toward the hole is applied to simulate the golf ball rolling in.) Without this inequality, the golf hole’s gravitational influence would not be limited to its lip; it would apply to the entire screen.

hbDist > distIn

hbDist - distIn > 0

Here I imagine “distIn” as a ruler of constant length extending from the center of the hole, but a little shorter than the actual radius of the hole; i.e., a hole with radius 10 and a ball with radius 3, conceptually, gives us a ruler of length 7. So hbDist gets smaller and smaller but the left side remains positive: the golf ball skirting the lip of the hole, but not quite sunk.

The last conditional describes the ball being in a “sunk” state, but also past the golf hole. How can the golf ball move past its cup? We are processing “a posteriori” collisions, after-the-fact collisions: to calculate how the ball should bounce, we allow it to “pass” the golf hole. (Visually, the graphics update fast enough that the golf ball does not actually appear to ignore the hole. Hopefully.)

Programmatic PivotTable with VBScript and VBA

VBA has a lot going for it:

  • It comes with Excel.
  • The IDE has Intellisense, breakpoints, built-in reference and integrated library (for including ADO database tasks, for example).
  • Immediate window for interpreted Visual Basic
  • Watch window for tracking variable state, including arrays and Dictionary
  • Event-driven, simple, fast REPL development
  • Backed by spreadsheet-in-memory

VBScript can be used as glue Рlike Command Prompt Рto leverage your existing code. You can create PivotTables from the command line!

See my gist for details.

Lunar resultant

The golf example demonstrates deceleration and momentum. It’s too easy to copy the code and call it a day, so I try to implement “morsel concepts” in different contexts. In this case, it was the moon and an astronaut. gif

moon

I couldn’t track the dx and dy being thrown around, even with the Vec2D encapsulation, so I initialized a zeroed Vec2D object called resultantVector. During each update(), resultantVector would apply both vectors to itself before modifying the y-pos.

public void update(Rectangle bounds) {
  resultantVector.addVec(gravityVector);  // dx = 0, dy = 0.4f

  if (thrusterActivated) 
    resultantVector.subVec(thrustVector); // dx = 0, dy = 1.0f

  addPos(resultantVector);  // the one place where y += dy

  // collision detection and reset resultantVector
  if (...) {
    y = just_above_the_ground;
    resultantVector.setVec(0, 0); // avoid accumulating
  }
}

The glorious unit vector

The nice thing about regular posts is laying out concepts in the minimally-ordered form of paragraphs of sentences, a kind of first-order organization, and revision being the courtesy of reinterpreting the initial intuition for something clearer, falls to later vagaries of time.

As far as I know, there is no primitive representation of a vector; that is, of a concept which has both a magnitude and a direction, which can be declared in the same way as the integer or even the String. Instead, a vector is deconstructed (or constructed from) two components: dx and dy, which “JGPD” declares as float, numbers representing the horizontal or vertical quantity with which the x and y positions of an object are updated.

x += dx;
y += dy;

There’s at least one exception, which is with the paddle in the Pong clone: your cursor’s x is the immediate location of the paddle redrawn – same as the opponent’s paddle, which updates its increment of x directly by 3.0f or -3.0f; otherwise, as in the ball, its position is updated by accumulating velocity.

The vector has magnitude and direction, but these properties come out of dx and dy. Suppose a 3-4-5 triangle: the vector’s magnitude is 5 and its direction is up and to the right. But you could have determined those effects from knowing two of the three numbers, which are the 4 and the 3, or the dx and dy, respectively.

The subsequent unit vector is a division upon the dx and dy numbers by the magnitude of the vector, a hypotenuse calculated as in the Pythagorean theorem:

dx = dx / Math.sqrt(dx * dx + dy * dy);
dy = dy / Math.sqrt(dx * dx + dy * dy);

A 3-4-5 triangle has its conditions set for dx = 0.8 and dy = 0.6. The hypotenuse of a triangle with those sides has length 1. Therein is our unit vector, magnitude 1, direction pre-destined. Supposedly, multiplying the dx and dy by the desired magnitude and subtracting it from the original (5) vector lets us modify the vector without changing its direction.

“… lets us modify the vector without changing its direction.” Concretely, it lets us slow down a golf ball even as it travels along the same linear path.

Keep numerical states binary

(If you plan to use binary comparisons.)

I updated the position of a ball in a state-driven update loop. The ball’s position should update only if we are in SERVE or RETURN state.

if ( (gameState & (SERVE|RETURN)) != 0)
  ball.move(bounds);  // bounds is a Rectangle of our world

Initially, I counted the states by 1, 2, …

int START = 1;
int ... = 2;
int ... = 3;
int SERVE = 4;
int RETURN = 5;

That non-power-of-2 five messes up the logic.

  00001 & (00100 | 00101)
= 00001 & (00101)
= 00101

00101 != 00000 TRUE

The body of the conditional occurs. The reference to Ball ball hasn’t been initialized yet, so we get a NullPointerException. If we use numeric states strictly in the powers of 2, we only get one 1s digit and the rest are zero. Then we can use this idea of “bit-masking.” Renumerating SERVE and RETURN as 8 and 16 gives us 001000 and 010000, respectively.

  00001 & (01000 | 10000)
= 00001 & (11000)
= 00000

With this change, the conditional doesn’t evaluate to true until game state is either SERVE or RETURN specifically.