Dreaming381 / Latios-Framework

A Unity DOTS framework for my personal projects
Other
896 stars 76 forks source link

2D: CVVS #46

Open Dreaming381 opened 2 months ago

Dreaming381 commented 2 months ago

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

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.