Making it Feel Real: Math and fabric.js
This is part 3 of an ongoing series that details our team’s journey to creating disasteroids.com for NodeKnockout 2012, a 48 hour coding competition that showcases projects built on node.js. You can find part one here and part two here.
REALIZING THE WORLD:
With Bundy putting together the physics and world state and Travis crafting the design assets, one of the remaining tasks was fitting the two together in a way that looked good and made sense. In essence, we needed to create the world that the user sees. This process fell fairly neatly into two main pieces:
- Adjusting the state of the physical box2d objects so that they behaved in an intuitive way.
- Fitting design assets (mechs, missiles, asteroids) to client-side view objects and updating those view objects with the proper state data to display them correctly.
MATHEMATICS OF ALIGNMENT:
Warning, mild math ahead: We had the physics engine running fairly early in the competition, and we quickly realized that the objects we created behaved like one would expect objects to behave in space. When objects were created and set into motion, they would maintain their orientation until they collided with something. When they collided, the involved bodies would careen off into space spinning wildly until they landed on an asteroid, often sideways, upside down, or somewhere in between. This caused two visual problems:
- When a player jumped, it was very unlikely that they would land on their feet.
- When a missile was fired, it didn’t always point in the direction it was traveling.
Thankfully, box2d has a method for bodies called SetAngle(), which allowed us to set the orientation of any body at any point in time. All that remained to solve these issues was a little bit of trigonometry:
The nonsensical chicken-scratches you see above were just the desperate musings of a math major who took trigonometry way too long ago. The actual calculations behind these alignment solutions were really quite simple. Let’s examine the player alignment problem first.
If we want to ensure that a player flying through space lands on their feet on an asteroid, at the point of impact we need to adjust the angle of the player object such that its angle corresponds to the angle defined by the player body’s relative position to the center of the asteroid. If that didn’t make much sense, here’s a diagram to help illustrate the idea:
We can find the angle, theta, by using the triangle formed by the coordinates of the centers of the player and asteroid bodies. Since we have the opposite and adjacent sides of the triangle relative to theta, we can use the inverse tangent to solve for theta:
Now that we have a method for alignment, we need to decide when to align. We chose to define a distance from the asteroid, or a “Radius of calculation” to govern this rule. When a player falls within the radius, it constantly recalculates its new angle and aligns itself until it comes to rest:
Perfect, right? Almost. We just need to account for multiple asteroids. We don’t want the player aligning to any old asteroid on the field. What we did was ensure that the player was only aligning to the nearest asteroid surface. This was calculated with the standard formula for distance. Once implemented, this solution worked pretty well, although unfortunately, some bugs still do exist as players are landing on their heads every now and then.
Now, using the same principles, aligning the missile is pretty easy. We just want to point the missile in the direction it’s going. Box2d has a method that allows us get the linear velocity vector of a body, basically how fast and how much in the x and y directions a body is moving. With these x and y values we can draw a similar triangle to one for the player angle, and determine the angle from the inverse tangent of the relationship of the sides:
That pretty much covers the math involved with object alignment. I will mention that determining the firing angle of the missiles involved some trigonometry as well, but the calculations were similar enough to omit here. Overall the math is simple, involving only triangles and polar coordinate conversion, but it’s the application of the formulas that give the world its proper look and feel.
ENOUGH MATH, BACK TO THE SHINY STUFF:
Now that the objects are behaving properly on the backend we need to make sure that they are represented properly on the frontend. Rather than drawing directly to HTML5 canvas, we chose to use a framework that managed the drawing for us. We landed upon fabric.js. It allowed us to define objects and render SVG (Scaleable Vector Graphics) based on an object’s x position, y position, angle, and scale. Once we created the data models to receive and hold this state data from the server, rendering the objects was as simple as updating the fabric object instances. Then each object was re-rendered with each draw cycle.
Fabric.js was a fantastic find that saved us a lot of time. It removed a lot of the headache of rendering which allowed us to focus on other aspects of the projects. Even more, it played nice with other canvas rendering systems like particle effects, which Josh will detail in our next post.
Part 4: Particle Effects Adding Texture To Your Canvas Game
If you enjoyed disasteroids.com or this post, please consider throwing us a vote on our team’s page. It requires Facebook authentication to ensure that there isn’t rampant cheating, but the awesome NodeKnockout organizers @visnup and @gerad would never use your information in a malicious way. They’ve been crazy enough to organize this event for the 3rd year now despite the operational headaches of putting together such a massive event.