Recently we have seen how we can create our own types to encapsulate physical concepts such as position, velocity, timespan and more. We developed all the types needed for movement in one and two dimensions, however so far we have not talked about rotation.
Today we will look at how to implement the types necessary to also gain the advantages of type-safety when it comes to the rotation and orientation of an object.
We will consider only the two dimensional case, since it is relatively straight forward. However, a similar approach could be taken in three dimensions as well, though it would significantly complicate the mathematics.
Angles and directions
When discussing our implementation of two and higher dimensional types I made the case that despite the two having the same unit, it makes sense to split the concepts of absolute position, and relative difference between positions into two different types.
It makes sense to do the same when it comes to angles and orientations, and in fact the distinction may be even clearer here.
Each object we can rotate in two dimensions clearly has a property representing its orientation – the absolute direction in which it is facing. Instead of being infinite like a vector space, this space is periodic (with a period of 360 degrees), so we want to use a type that enforces this property automatically without us having to thing about it.
A relative angle on the other hand is a proper one dimensional vector space that can exceed the boundaries of the periodic angle space.
Consider how a value could represent the angular movement (i.e. rotation) of an object over the last 10 seconds. Since an object could have easily spun around two times in that time interval, the relative angle difference is in this interpretation 720 degrees.
This concept makes even more sense when we extrapolate it to angular velocity and acceleration further on: Clearly an object can spin so fast that its angular velocity exceeds 360 degrees/second. However, I am getting ahead of myself.
As it happens, I have discussed a possible implementation for exactly these two types:
Angle. The implementation of
Direction2 uses a backing integer and exploits the overflow behaviour of integer arithmetic to naturally exhibit the periodic nature of a proper direction type at no additional cost – and with very fine and evenly distributed accuracy of representation, a fact not to be underestimated.
Unit and dimension of angular types
It may at first be unclear why we would use the two already implemented types. Our goal here is to achieve type-safety of physical quantities. The above types however are merely mathematical representations and have no actual physical unit associated with them. Contrary to this, our speed type for example used the unit of distance/time.
Angles are a special case. Depending on the definition, an Angle can be seen as the percentage or the full circle that it spans, or as the length of the arc, compared to the radius.
Note how both the arc and radius would be measured with the same distance unit. Therefore, the unit of angles is distance/distance. It is a dimensionless unit.
This means that we might as well use the same types for our purposes here, instead of reimplementing them entirely.
More importantly, the types as they are do not come with any operations we would like to catch as invalid (like most implementations of Vector2 types for example).
While being a dimensionless unit, of course angles do not actually have no dimension in the vector sense. They form one dimensional spaces as outlined above.
Velocity and Acceleration
Having settled the differences between absolute directions and relative angles, we should add the additional types we need in order to work with the original types in our physical framework.
Similar as with the types handling locations and movement, we will want to add types representing the speed and acceleration of rotation:
As explained above, these form simple one dimensional vector spaces with no further constraints and are as such easily implemented with a backing floating point value.
Angle itself, they thus also have the associated vector space operations of addition, subtraction and scaling.
Additionally, the types interact through integration and differentiation, i.e. multiplication with or dividing by a time span.
The full list of operations we will have to implement is as follows.
// vector space types, AngularVelocity and AngularAcceleration // at the exactly of AngularVelocity angularVelocity + angularVelocity = angularVelocity angularVelocity - angularVelocity = angularVelocity angularVelocity * scaler = angularVelocity // integration angularVelocity * timespan = angle angularAcceleration * timespan = angularVelocity // differentiation angle / timespan = angularVelocity angularVelocity / timespan = angularAcceleration
Of course there are a few more operations we can add, but these form the basics that allow us to use these types instead of simple floating point numbers to get a type-safe representation for our angular movement.
Interesting interactions of
There is another interesting set of interesting interactions that are are both plausible and physically well defined, so that we could add them to our list of operations.
These interactions rely on how
Direction2, despite being a wrapper around a single value only makes sense in a two dimensional space.
By combining it with a one dimensional physical type, we can construct a directed value of the same type, but in the two dimensional plane.
For example, given a direction and a speed, we could construct a two dimensional velocity that corresponds movement in that direction and that speed.
The same operations exists for each of out spacial unit types as follows.
direction2 * unit = difference2 direction2 * speed = velocity2 direction2 * acceleration = acceleration2
Whether it is a good idea to implement these operations using the multiplication operator may be a matter of personal taste.
Personally I like to be able to write code as follows.
Acceleration2 acceleration = this.facingDirection * this.forwardAcceleration;
I think this usage of the operator is clear enough, and much less cumbersome than using a method or constructor instead. Further I consider it clear in meaning and not ambiguous despite not being exactly mathematically rigorous – however I am curious to see if others agree.
Today we saw how we can implement type-safe representations of directions, as well as relative angles and angular velocities and accelerations. This completes this mini series on type-safe representations of physical types for two dimensional games.
I hope you found this interesting, and if you did consider sharing this post on your favourite social media.
Enjoy the pixels!