Latios-Framework
Latios-Framework copied to clipboard
2D: CVVS
2D: CVVS
While 2D is not my personal focus with this framework, I am not opposed to 2D features being added by the community, and am willing to help guide its development. Despite my general lack of interest these days, I have a lot of experience with the domain.
To kick off the exploration of 2D, the first thing to develop is the foundation of a 2D transform system. In 3D, we have QVVS. But 2D has an equivalent CVSS. Building out that foundational structure is what this task is all about!
Task is Prerequisite For
- 2D Transform System
- 2D Physics
- 2D Audio
- 2D Rendering
- 2D Skeletal Animation
Background
In 2D, there is only a single axis on which things can rotate. Ironically, this axis is the very axis that is ignored for position and scale.
Because there is only a single rotation axis, it can be represented as a single
angle in radians. But however memory efficient this is, it actually suffers from
a few annoying issues. One issue is that you always have to wrap around angles.
You could force a range of [0, 2π), but this makes small clockwise rotations
appear as large counter-clockwise rotations. The ranges [-π, π) or (-π, π] are
also valid, but you can no longer rely on modulus to reduce an arbitrary value
into this range. But even if you solve both of these problems, there is still
the major issue that transforming any point or vector but an angle requires
trigonometric operations. On the GPU, this isn’t a huge deal because GPUs have
special-function units that do sin()
and cos()
relatively efficiently. On
the CPU, things tend to be a little more expensive. And that’s not great when
that’s required for transform hierarchies, positioning and orienting raycasts,
spawning things relative to facing directions, or defining relative spaces for
colliders during collision detection. There’s a better way.
If you recall, the angle in radians is the distance traveled counter-clockwise around the unit circle. If you start at the right-most point on the unit circle, you can calculate the coordinates of the point you would end up at on the unit circle if you traveled the angle in radians. The formula as you know is:
x = cos(angle)
y = sin(angle)
Interestingly, these coordinates are also a unit vector from the origin to the unit circle at the specified angle.
What many people don’t realize is that you can multiply these vectors to add the angles. But this isn’t a dot product, cross product, nor component-wise product. This is a complex product.
The basic idea is that you treat the y-axis as the imaginary axis of the complex plane. And any coordinate or vector becomes a complex number. Then, you simply multiply the complex numbers. In code:
result.x = a.x * b.x - a.y * b.y;
result.y = a.x * b.y + a.y * b.x;
There’s some amazing properties to this. First off, unlike quaternions, the order of the multiplication does not matter. Second, if non-unit vectors are involved, this expression is true:
complexMul(a, b) = a.mag * b.mag * complexMul(a.norm, b.norm)
If you treat a
as a unit vector derived from an angle, and b
as some
arbitrary vector, you will find that complex multiplication will rotate b
while preserving the magnitude of b
. In fact, you can even substitute the
angle-to-vector formula for a, and you would end up with the well-known formula
for transforming an angle:
result.x = x * cos(angle) - y * sin(angle)
result.y = y * cos(angle) + x * sin(angle)
But now that you know the derivation, you may be able to see how you can skip the trigonometric functions in many cases. And naturally, it is a good idea to keep a rotation stored as a complex number. Where Q is for quaternion, C is for complex. Thus, we can derive our CVVS.
Like quaternions, wrap-around happens automatically for complex numbers. However, you occasionally need to normalize the complex numbers to correct for floating point errors, just like quaternions. Another similar property is that you can invert the rotation of a unit complex number by taking the conjugate of the complex number, which is just negating the imaginary (y) component.
Lastly, a complex number requires 8 bytes. A position in 2D also requires 8
bytes. Stretch requires 8 bytes, and scale requires 4 bytes. Just like with
QVVS, we can fill in 4 bytes with a worldIndex
(use-case defined value) for an
even 32 bytes.
Base Requirements
Implement the TransformCvvs
type and the static cvvs
class. Additionally,
implement utilities for converting between complex rotations and angles, and
converting between TransformCvvs
and TransformQvvs
which can either assume
an XY plane or an XZ plane.
You may want to add code in MathematicsExpansion
to define the complex
type
and implement mul()
and rotate()
methods.