feat(host): vendor PyroWave + minimal Granite subset as crates/pyrowave-sys

Phase 0 of design/pyrowave-codec-plan.md — the opt-in wired-LAN ultra-low-
latency codec. Vendored at upstream 509e4f88 (API 0.4.0, Granite 44362775,
volk + vulkan-headers pins in PUNKTFUNK-VENDOR.txt), pruned to the 6.6 MB
the standalone no-renderer build needs; scripts/vendor-pyrowave.sh
reproduces the tree (a pin bump is protocol-affecting, plan §4.2).

build.rs drives the wrapper CMakeLists (static archives incl. a static
C-API lib upstream only ships shared) + bindgen over pyrowave.h; Linux and
Windows only, empty stub elsewhere (Apple gets a native Metal port, §4.7).
Offline-safe by construction: no network, no system lib, vendored Vulkan
headers — same model as the opus dep (flatpak builder has no network).

Phase-0 validation on .21 (RTX 5070 Ti, driver 610.43.03):
- upstream pyrowave-c-test + interop test (incl. dmabuf/DRM-modifier
  Vulkan<->Vulkan) pass, from the pristine AND the pruned tree
- GPU kernel times at ~1.6 bpp noise: encode/decode 0.090/0.042 ms @800p,
  0.146/0.067 @1080p, 0.226/0.103 @1440p, 0.477/0.201 @4K — order of
  magnitude under NVENC's 1-2 ms retrieve, CBR lands within ~100 B of
  target
- cargo test -p pyrowave-sys green (static link + API-version pin check)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 00:35:10 +02:00
parent 1b73361372
commit 4c3b11445c
396 changed files with 140058 additions and 0 deletions
@@ -0,0 +1,12 @@
add_granite_internal_lib(granite-math
math.hpp math.cpp
frustum.hpp frustum.cpp
aabb.cpp aabb.hpp
render_parameters.hpp
interpolation.cpp interpolation.hpp
muglm/muglm.cpp muglm/muglm.hpp
muglm/muglm_impl.hpp muglm/matrix_helper.hpp
transforms.cpp transforms.hpp
simd.hpp simd_headers.hpp)
target_include_directories(granite-math PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
@@ -0,0 +1,56 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "aabb.hpp"
#include <float.h>
namespace Granite
{
AABB AABB::transform(const mat4 &m) const
{
vec3 m0 = vec3(FLT_MAX);
vec3 m1 = vec3(-FLT_MAX);
for (unsigned i = 0; i < 8; i++)
{
vec3 c = get_corner(i);
vec4 t = m * vec4(c, 1.0f);
vec3 v = t.xyz();
m0 = min(v, m0);
m1 = max(v, m1);
}
return AABB(m0, m1);
}
vec3 AABB::get_coord(float dx, float dy, float dz) const
{
return mix(minimum.v3, maximum.v3, vec3(dx, dy, dz));
}
void AABB::expand(const AABB &aabb)
{
minimum.v3 = min(minimum.v3, aabb.minimum.v3);
maximum.v3 = max(maximum.v3, aabb.maximum.v3);
}
}
@@ -0,0 +1,101 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "math.hpp"
#include "muglm/muglm_impl.hpp"
namespace Granite
{
class AABB
{
public:
AABB(vec3 minimum_, vec3 maximum_)
{
minimum.v4 = vec4(minimum_, 1.0f);
maximum.v4 = vec4(maximum_, 1.0f);
}
AABB() = default;
vec3 get_coord(float dx, float dy, float dz) const;
AABB transform(const mat4 &m) const;
void expand(const AABB &aabb);
const vec3 &get_minimum() const
{
return minimum.v3;
}
const vec3 &get_maximum() const
{
return maximum.v3;
}
const vec4 &get_minimum4() const
{
return minimum.v4;
}
const vec4 &get_maximum4() const
{
return maximum.v4;
}
vec4 &get_minimum4()
{
return minimum.v4;
}
vec4 &get_maximum4()
{
return maximum.v4;
}
vec3 get_corner(unsigned i) const
{
float x = i & 1 ? maximum.v3.x : minimum.v3.x;
float y = i & 2 ? maximum.v3.y : minimum.v3.y;
float z = i & 4 ? maximum.v3.z : minimum.v3.z;
return vec3(x, y, z);
}
vec3 get_center() const
{
return minimum.v3 + (maximum.v3 - minimum.v3) * vec3(0.5f);
}
float get_radius() const
{
return 0.5f * distance(minimum.v3, maximum.v3);
}
private:
union
{
vec3 v3;
vec4 v4;
} minimum, maximum;
};
}
@@ -0,0 +1,423 @@
# Modified SQUAD for non-uniform timestamps
The SQUAD algorithm is a well-known algorithm for smooth interpolation of rotations.
The standard and simple algorithm for rotation is SLERP,
which ensures constant angular velocity over a given interpolation segment.
However, the flaw of SLERP for camera interpolation is that the angular velocity
is not continuous, and it will abruptly change on a new segment.
This problem is solved by SQUAD, but in its naive implementation, the length of
each segment must be uniform, otherwise the derivation fails.
I spent some time studying the underlying math and derived a formula that works for
non-uniform timestamps as well.
## The standard runtime algorithm
In SQUAD, each key-frame point is represented as
a quaternion q<sub>k</sub> at timestamp t<sub>k</sub>.
At each timestamp, we also pre-compute a
helper control point q<sup>c</sup><sub>k</sub>,
which derivation will be explored further below.
We are given the implementation:
squad<sub>k</sub>(t) = slerp(slerp(q<sub>k</sub>, q<sub>k+1</sub>, t),
slerp(q<sup>c</sup><sub>k</sub>, q<sup>c</sup><sub>k+1</sub>, t),
2t(1 - t))
t is given here in the range [0, 1), and is computed by:
t = (T - t<sub>k</sub>) / (t<sub>k+1</sub> - t<sub>k</sub>)
where T is the global time for which to evaluate.
An animation clip is stitched together by many such splines, one for each k.
### Analyze the expression
To perform further calculus on the squad(t) function, we can simplify to scalars.
If we assume for the purposes of analysis that all
the rotations have the same axis of rotation, we can
rewrite squad(t) to a linear interpolation of rotation angle &#952;, which
each key-frame now represents:
squad<sub>k</sub>(t) = lerp(lerp(&#952;<sub>k</sub>, &#952;<sub>k+1</sub>, t),
lerp(&#952;<sup>c</sup><sub>k</sub>, &#952;<sup>c</sup><sub>k+1</sub>, t),
2t(1 - t))
All these lerps are trivial expressions:
lerp(a, b, t) = (1 - t)a + tb
We can expand the expression and compute their first and second order derivatives.
v<sub>k</sub>(t) =
(-3 + 8t - 6t<sup>2</sup>) &#952;<sub>k</sub> +
(-1 - 4t + 6t<sup>2</sup>) &#952;<sub>k+1</sub> +
(2 - 8t + 6t<sup>2</sup>) &#952;<sup>c</sup><sub>k</sub> +
(4t - 6t<sup>2</sup>) &#952;<sup>c</sup><sub>k+1</sub>
a<sub>k</sub>(t) =
(8 - 12t) &#952;<sub>k</sub> +
(-4 + 12t) &#952;<sub>k+1</sub> +
(-8 + 12t) &#952;<sup>c</sup><sub>k</sub> +
(4 - 12t) &#952;<sup>c</sup><sub>k+1</sub>
It is important to note here that we derive with respect to the spline local parameter t.
To obtain the absolute angular velocity and acceleration at time t, we need to apply chain rules:
V<sub>k</sub>(t) = v<sub>k</sub>(t) (dt / dT) = v<sub>k</sub>(t) / d<sub>k</sub>
where d<sub>k</sub> = t<sub>k+1</sub> - t<sub>k</sub>,
and V<sub>k</sub>(t) is absolute angular velocity, d&#952; / dT.
Similarly, A<sub>k</sub>(t) = a<sub>k</sub>(t) / d<sub>k</sub><sup>2</sup> is
absolute angular acceleration, d<sup>2</sup>&#952; / (dT)<sup>2</sup>. When d<sub>k</sub> is constant,
all uses of d<sub>k</sub> cancel out,
and this is the assumption various algorithms online make.
To ensure first order continuity we must satisfy
V<sub>k</sub>(1) = V<sub>k+1</sub>(0), or alternatively if d<sub>k</sub> is constant,
v<sub>k</sub>(1) = v<sub>k+1</sub>(0). Similarly, if we want to ensure continuous second order derivative, we must
satisfy A<sub>k</sub>(1) = A<sub>k+1</sub>(0).
**v<sub>k+1</sub>(0)** =
-3&#952;<sub>k+1</sub> +
1&#952;<sub>k+2</sub> +
2&#952;<sup>c</sup><sub>k+1</sub> +
0&#952;<sup>c</sup><sub>k+2</sub> =\
**(&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>) -
2(&#952;<sub>k+1</sub> - &#952;<sup>c</sup><sub>k+1</sub>)**
**v<sub>k</sub>(1)** =
-1&#952;<sub>k</sub> +
3&#952;<sub>k+1</sub> +
0&#952;<sup>c</sup><sub>k</sub> -
2&#952;<sup>c</sup><sub>k+1</sub> =\
**(&#952;<sub>k+1</sub> - &#952;<sub>k</sub>) +
2(&#952;<sub>k+1</sub> - &#952;<sup>c</sup><sub>k+1</sub>)**
**a<sub>k+1</sub>(0)** =
8&#952;<sub>k+1</sub> -
4&#952;<sub>k+2</sub> -
8&#952;<sup>c</sup><sub>k+1</sub> +
4&#952;<sup>c</sup><sub>k+2</sub> =\
**8(&#952;<sub>k+1</sub> - &#952;<sup>c</sup><sub>k+1</sub>) -
4(&#952;<sub>k+2</sub> - &#952;<sup>c</sup><sub>k+2</sub>)**
**a<sub>k</sub>(1)** =
-4&#952;<sub>k</sub> +
8&#952;<sub>k+1</sub> +
4&#952;<sup>c</sup><sub>k</sub> -
8&#952;<sup>c</sup><sub>k+1</sub> =\
**8(&#952;<sub>k+1</sub> - &#952;<sup>c</sup><sub>k+1</sub>) -
4(&#952;<sub>k</sub> - &#952;<sup>c</sup><sub>k</sub>)**
Based on these expressions, we can already intuit what the relationship
between the control points and the key-frame points are. The difference expresses
acceleration. With positive acceleration, the control points lags behind the key-frame, and vice versa.
Looking at the velocity expressions, with positive acceleration, we also get larger velocity at t = 1 compared to t = 0,
as expected.
To satisfy the velocity equations, we need to choose v<sub>k+1</sub>(0) = v<sub>k</sub>(1), so\
(&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>) -
2(&#952;<sub>k+1</sub> - &#952;<sup>c</sup><sub>k+1</sub>) =
(&#952;<sub>k+1</sub> - &#952;<sub>k</sub>) +
2(&#952;<sub>k+1</sub> - &#952;<sup>c</sup><sub>k+1</sub>)\
(&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>) -
(&#952;<sub>k+1</sub> - &#952;<sub>k</sub>) =
4(&#952;<sub>k+1</sub> - &#952;<sup>c</sup><sub>k+1</sub>)\
((&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>) -
(&#952;<sub>k+1</sub> - &#952;<sub>k</sub>)) / 4 =
&#952;<sub>k+1</sub> - &#952;<sup>c</sup><sub>k+1</sub>\
&#952;<sub>k+1</sub> - ((&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>) -
(&#952;<sub>k+1</sub> - &#952;<sub>k</sub>)) / 4 =
**&#952;<sup>c</sup><sub>k+1</sub>**
(&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>) - (&#952;<sub>k+1</sub> - &#952;<sub>k</sub>)
is quite recognizable and intuitive.
This is the discrete measurement of acceleration at t<sub>k+1</sub>.
For simplicity of notation, we introduce the local delta,
&#916;<sub>k</sub> = &#952;<sub>k</sub> - &#952;<sup>c</sup><sub>k</sub>.
We can now rewrite the equations in a more digestable form:
v<sub>k+1</sub>(0) =
(&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>) - 2&#916;<sub>k+1</sub>
v<sub>k</sub>(1) =
(&#952;<sub>k+1</sub> - &#952;<sub>k</sub>) + 2&#916;<sub>k+1</sub>
a<sub>k+1</sub>(0) =
8&#916;<sub>k+1</sub> - 4&#916;<sub>k+2</sub>
a<sub>k</sub>(1) =
8&#916;<sub>k+1</sub> - 4&#916;<sub>k</sub>
This equation will only yield a continuous acceleration if
&#916;<sub>k</sub> = &#916;<sub>k+2</sub>, which is not guaranteed.
However, we have the nice property that a constant acceleration will
yield a constant a<sub>k</sub>(t) for any k equal to 4&#916;<sub>k</sub>.
As we deduced earlier, &#916;<sub>k</sub> is 1/4th the measured discrete acceleration,
so everything checks out. Continuous acceleration is a nice property,
but not required for smooth camera motion.
## Going back to the quaternion domain
We have found expressions for the control points, but the derivation
has been happening in the angular domain, we need to work with quaternions.
Here, articles online will usually begin talking about logarithms and exponential functions of quaternions
which at first glance is pure non-sense, but it is actually fairly intuitive.
It took me a while to understand what the hell the article authors were smoking at first.
The insight is that **multiplying** two quaternions **adds** their rotational angles.
This is exactly the same as complex numbers, where multiplying two complex numbers
add their angles.
For logarithms of quaternions to work, we need to convert them to a number where
adding the results will function similarly to angular addition. Taking the exponent
should give us back the result.
q<sub>a</sub>q<sub>b</sub> = exp(ln(q<sub>a</sub>q<sub>b</sub>)) =
exp(ln(q<sub>a</sub>) + ln(q<sub>b</sub>))
As an aside, this extension also allows us to reason about powers of quaternions, since
ln(q<sub>a</sub><sup>c</sup>) = c&sdot;ln(q<sub>a</sub>).
The logarithm for a unit quaternion is computed as:
```
// vec3 quat_log(q)
if (abs(q.w) > 0.9999f)
return vec3(0.0f);
else
return normalize(q.as_vec4().xyz()) * acos(q.w);
```
The main confusion for me here is that quaternion multiplication does not commute,
but here the logarithm additions do. Subtraction might make more sense ...
```c++
// compute_inner_control_point_delta(q, delta)
quat inv_q1 = conjugate(q1);
quat delta_k = inv_q1 * q2; // q2 - q1
quat delta_k_minus1 = inv_q1 * q0; // q0 - q1 = -(q1 - q0)
vec3 delta_k_log = quat_log(delta_k);
vec3 delta_k_minus1_log = quat_log(delta_k_minus1);
vec3 delta = 0.25f * (delta_k_log + delta_k_minus1_log);
return delta;
```
The multiplication order seems somewhat arbitrary,
and I cannot prove exactly why we have to do it like this, but I cribbed
this part from the web. This document so far is trying to justify how it works.
At the very least, we can see similarities with the original derivation.
Here, `delta_k` and `delta_k_minus1` measure the velocities between key-frames.
Multiplying is "addition", but multiplying by conjugate is "subtraction".
By subtracting in the log-domain we can get an angular differential and measure acceleration.
As expected, we also take 1/4th since the delta is 1/4th measured acceleration.
This delta is later used to construct the control point q<sup>c</sup><sub>k</sub>.
```c++
// compute_inner_control_point(q, delta)
// Subtraction in angular domain.
return q * quat_exp(-delta);
```
which maps to the definition we made:
&#952;<sub>k</sub> - &#952;<sup>c</sup><sub>k</sub> =
&#916;<sub>k</sub> \
**&#952;<sup>c</sup><sub>k</sub> =
&#952;<sub>k</sub> - &#916;<sub>k</sub>**
To my great surprise, this is actually delightfully simple.
The logarithm is a vec3 where the direction is axis of rotation,
and length is the angle &#952;. This is what allows us to add/subtract the rotations
together. This style of expressing rotation is basically how we would
express torque in physics, nice!
The exponent just inverts what the log did.
We recover the angle by taking length of vector
and rebuilding the quaternion from that.
```
// quat quat_exp(q)
float l = dot(q, q);
if (l < 0.000001f)
{
return quat(1.0f, 0.0f, 0.0f, 0.0f);
}
else
{
float vlen = length(q);
vec3 v = normalize(q) * sin(vlen);
return quat(cos(vlen), v);
}
```
## Non-uniform time deltas d<sub>k</sub>
This is where it gets spicy and where I was initially stumped when
attempting to implement SQUAD. When constructing arbitrary camera paths,
it is helpful to be able to place key-frames at any timestamp.
What unfortunately happens now is that velocities are no longer
continuous over a spline boundary, because the splines are now swept at varying
rates. We will need to re-derive the control points,
based on V<sub>k</sub>(t), not v<sub>k</sub>(t), in angular domain.
V<sub>k+1</sub>(0) =
((&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>) - 2&#916;<sub>k+1</sub>) / d<sub>k+1</sub>
V<sub>k</sub>(1) =
((&#952;<sub>k+1</sub> - &#952;<sub>k</sub>) + 2&#916;<sub>k+1</sub>) / d<sub>k</sub>
To be able to solve this, we need to consider that &#916;<sub>k+1</sub> need
not be a single value.
When evaluating spline k and k + 1, it can take different values as needed.
This gives rise to the "incoming" and "outgoing" control points.
The incoming control point delta is used when evaluating the previous spline.
V<sub>k+1</sub>(0) =
((&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>) - 2&#916;<sup>o</sup><sub>k+1</sub>) / d<sub>k+1</sub>
V<sub>k</sub>(1) =
((&#952;<sub>k+1</sub> - &#952;<sub>k</sub>) + 2&#916;<sup>i</sup><sub>k+1</sub>) / d<sub>k</sub>
The superscript o and i denote outgoing and incoming respectively.
We can now solve this equation directly, similar to the derivation we did for constant d<sub>k</sub> earlier.
((&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>) - 2&#916;<sup>o</sup><sub>k+1</sub>) / d<sub>k+1</sub> =
((&#952;<sub>k+1</sub> - &#952;<sub>k</sub>) + 2&#916;<sup>i</sup><sub>k+1</sub>) / d<sub>k</sub>
(&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>) / d<sub>k+1</sub> -
2&#916;<sup>o</sup><sub>k+1</sub> / d<sub>k+1</sub> =
(&#952;<sub>k+1</sub> - &#952;<sub>k</sub>) / d<sub>k</sub> +
2&#916;<sup>i</sup><sub>k+1</sub> / d<sub>k</sub>
(&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>) / d<sub>k+1</sub> -
(&#952;<sub>k+1</sub> - &#952;<sub>k</sub>) / d<sub>k</sub> -
2&#916;<sup>o</sup><sub>k+1</sub> / d<sub>k+1</sub> =
2&#916;<sup>i</sup><sub>k+1</sub> / d<sub>k</sub>
If we let the ratio r be d<sub>k</sub> / d<sub>k+1</sub>, we get
&#916;<sup>i</sup><sub>k+1</sub> =
((&#952;<sub>k+2</sub> - &#952;<sub>k+1</sub>)(d<sub>k</sub> / d<sub>k+1</sub>) -
(&#952;<sub>k+1</sub> - &#952;<sub>k</sub>)) / 2 -
&#916;<sup>o</sup><sub>k+1</sub>(d<sub>k</sub> / d<sub>k+1</sub>)
Which shows that we can actually select the outgoing control point rather freely,
and we can then use this formula to compensate the difference in
d<sub>k</sub> in the incoming control point.
For the outgoing control point, we should modify the acceleration
computation to be aware of different step rates. Basically,
we normalize the discrete velocities in terms of global time T.
There might be better ways of computing this, but, meh.
From empiric testing, the result is pretty accurate.
```
quat inv_q1 = conjugate(q1);
quat delta_k = inv_q1 * q2; // q2 - q1
quat delta_k_minus1 = inv_q1 * q0; // q0 - q1 = -(q1 - q0)
vec3 delta_k_log = quat_log(delta_k);
vec3 delta_k_minus1_log = quat_log(delta_k_minus1);
// We sample velocity at the center of the segment when taking the difference.
// Future sample is at t = +1/2 dt
// Past sample is at t = -1/2 dt
float segment_time = 0.5f * (dt0 + dt1);
vec3 absolute_accel = (delta_k_log / dt1 + delta_k_minus1_log / dt0) / segment_time;
vec3 delta = (0.25f * dt1 * dt1) * absolute_accel;
```
```
// Computed from snippet above
vec3 outgoing = tmp_spline_deltas[i];
float dt0 = new_linear_timestamps[i] - new_linear_timestamps[i - 1];
float dt1 = i + 1 < n ? (new_linear_timestamps[i + 1] - new_linear_timestamps[i]) : dt0;
float t_ratio = dt0 / dt1;
const quat &q0 = new_linear_values[i - 1];
const quat &q1 = new_linear_values[i];
const quat &q2 = i + 1 < n ? new_linear_values[i + 1] : q1;
quat q12 = conjugate(q1) * q2;
quat q10 = conjugate(q1) * q0; // This is implicitly negated.
vec3 delta_q12 = quat_log(q12);
vec3 delta_q10 = quat_log(q10);
vec3 incoming = 0.5f * (t_ratio * delta_q12 + delta_q10) - t_ratio * outgoing;
spline_data[3 * spline + 0] = q1 * quat_exp(-incoming);
spline_data[3 * spline + 1] = q1;
spline_data[3 * spline + 2] = q1 * quat_exp(-outgoing);
```
Each key-frame gets 3 values. This is very similar to the
CUBICSPLINE formulation used in glTF.
When evalulating the spline in runtime we look at indices
3 * k + {1, 2, 3, 4}.
### Modified SQUAD function
squad<sub>k</sub>(t) = slerp(slerp(q<sub>k</sub>, q<sub>k+1</sub>, t),
slerp(q<sub>k</sub>&#183;quatExp(-&#916;<sup>o</sup><sub>k</sub>),
q<sub>k+1</sub>&#183;quatExp(-&#916;<sup>i</sup><sub>k+1</sub>), t),
2t(1 - t))
&#916; is in the log domain as we computed above with outgoing and incoming deltas.
## Verification
While doing this work, I also made a test bench of sorts to evaluate the results.
I tested 4 different scenarios with scalars.
- Interpolate quadratic function with even timestamps. Should be 100% exact.
- Interpolate quadratic function with uneven timestamps. Will have some error.
- Interpolate cubic function with even timestamps. Expect some errors due to non-constant acceleration.
- Interpolate cubic function with uneven timestamps. Expect some errors due to non-constant acceleration.
We want to validate:
- Average error of reference function f(t) and interpolated result.
- Continuity of measured first derivative (and second derivative).
### Quadratic
f(t) = 0.5t - 0.25t<sup>2</sup>
#### Even timestamps
Key frames placed at t = {0, 0.5, 1.0, 2.0, 2.5, 3.0}.\
Perfect result. As expected.
#### Uneven timestamps
Key frames placed at t = {0, 1.0, 1.8, 2.1, 2.9, 3.0, 4.2, 4.3, 5.0, 6.0}.\
Average error: 0.008141\
Continuous first derivative, discontinuous second derivative.
### Cubic
f(t) = 0.5t - 0.25t<sup>2</sup> + 0.25t<sup>3</sup>
#### Even timestamps
Key frames placed at t = {0, 0.5, 1.0, 2.0, 2.5, 3.0}.\
Average error: 0.00195\
Continuous first derivative, discontinuous second derivative.
#### Uneven timestamps
Key frames placed at t = {0, 0.5, 0.9, 1.1, 1.4, 1.5, 2.1, 2.2, 2.5, 3.0}.\
Average error: 0.008285\
Continuous first derivative, discontinuous second derivative.
### Summary
The more even timestamps we have, the more accurate the spline becomes.
The error is also quite acceptable, and we see continuous first derivative,
which is the critical part to get right.
@@ -0,0 +1,155 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "frustum.hpp"
namespace Granite
{
// For reference, should always use SIMD-version.
bool Frustum::intersects_slow(const AABB &aabb) const
{
for (auto &plane : planes)
{
bool intersects_plane = false;
for (unsigned i = 0; i < 8; i++)
{
if (dot(vec4(aabb.get_corner(i), 1.0f), plane) >= 0.0f)
{
intersects_plane = true;
break;
}
}
if (!intersects_plane)
return false;
}
return true;
}
bool Frustum::intersects_sphere(const AABB &aabb) const
{
vec4 center(aabb.get_center(), 1.0f);
float radius = aabb.get_radius();
for (auto &plane : planes)
if (dot(plane, center) < -radius)
return false;
return true;
}
static constexpr float FarClipInfiniteClamp = 1e-10f;
vec3 Frustum::get_coord(float dx, float dy, float dz) const
{
dz = 1.0f - dz;
bool infinite_z = inv_view_projection[3][3] == 0.0f;
if (infinite_z)
dz = muglm::max<float>(dz, FarClipInfiniteClamp);
vec4 clip = vec4(2.0f * dx - 1.0f, 2.0f * dy - 1.0f, dz, 1.0f);
clip = inv_view_projection * clip;
return clip.xyz() / clip.w;
}
vec4 Frustum::get_bounding_sphere(const mat4 &inv_projection, const mat4 &inv_view)
{
// Make sure that radius is numerically stable throughout, since we use that as a snapping factor potentially.
// Use the inverse projection to create the radius.
const auto get_coord = [&](float x, float y, float z) -> vec3 {
vec4 clip = vec4(x, y, z, 1.0f);
clip = inv_projection * clip;
return clip.xyz() / clip.w;
};
vec3 center_near = get_coord(0.0f, 0.0f, 0.0f);
vec3 center_far = get_coord(0.0f, 0.0f, 1.0f);
vec3 near_pos = get_coord(-1.0f, -1.0f, 0.0f);
vec3 far_pos = get_coord(+1.0f, +1.0f, 1.0f);
float C = length(center_far - center_near);
float N = dot(near_pos - center_near, near_pos - center_near);
float F = dot(far_pos - center_far, far_pos - center_far);
// Solve the equation:
// n^2 + x^2 == f^2 + (C - x)^2 =>
// N + x^2 == F + C^2 - 2Cx + x^2.
// x = (F - N + C^2) / 2C
float center_distance = (F - N + C * C) / (2.0f * C);
float radius = muglm::sqrt(center_distance * center_distance + N);
vec3 view_space_center = center_near + center_distance * normalize(center_far - center_near);
vec3 center = (inv_view * vec4(view_space_center, 1.0f)).xyz();
return vec4(center, radius);
}
void Frustum::build_planes(const mat4 &inv_view_projection_)
{
inv_view_projection = inv_view_projection_;
bool infinite_z = inv_view_projection[3][3] == 0.0f;
float far_clip_z = infinite_z ? FarClipInfiniteClamp : 0.0f;
const vec4 tln(-1.0f, -1.0f, 1.0f, 1.0f);
const vec4 bln(-1.0f, +1.0f, 1.0f, 1.0f);
const vec4 blf(-1.0f, +1.0f, far_clip_z, 1.0f);
const vec4 trn(+1.0f, -1.0f, 1.0f, 1.0f);
const vec4 trf(+1.0f, -1.0f, far_clip_z, 1.0f);
const vec4 brn(+1.0f, +1.0f, 1.0f, 1.0f);
const vec4 brf(+1.0f, +1.0f, far_clip_z, 1.0f);
const vec4 c(0.0f, 0.0f, 0.5f, 1.0f);
const auto project = [](const vec4 &v) {
return v.xyz() / vec3(v.w);
};
vec3 TLN = project(inv_view_projection * tln);
vec3 BLN = project(inv_view_projection * bln);
vec3 BLF = project(inv_view_projection * blf);
vec3 TRN = project(inv_view_projection * trn);
vec3 TRF = project(inv_view_projection * trf);
vec3 BRN = project(inv_view_projection * brn);
vec3 BRF = project(inv_view_projection * brf);
vec4 center = inv_view_projection * c;
vec3 l = normalize(cross(BLF - BLN, TLN - BLN));
vec3 r = normalize(cross(TRF - TRN, BRN - TRN));
vec3 n = normalize(cross(BLN - BRN, TRN - BRN));
vec3 f = normalize(cross(TRF - BRF, BLF - BRF));
vec3 t = normalize(cross(TLN - TRN, TRF - TRN));
vec3 b = normalize(cross(BRF - BRN, BLN - BRN));
planes[0] = vec4(l, -dot(l, BLN));
planes[1] = vec4(r, -dot(r, TRN));
planes[2] = vec4(n, -dot(n, BRN));
planes[3] = infinite_z ? vec4(0.0f) : vec4(f, -dot(f, BRF));
planes[4] = vec4(t, -dot(t, TRN));
planes[5] = vec4(b, -dot(b, BRN));
// Winding order checks.
for (auto &p : planes)
if (dot(center, p) < 0.0f)
p = -p;
}
}
@@ -0,0 +1,50 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "math.hpp"
#include "aabb.hpp"
namespace Granite
{
class Frustum
{
public:
void build_planes(const mat4& inv_view_projection);
bool intersects_sphere(const AABB &aabb) const;
bool intersects_slow(const AABB &aabb) const;
vec3 get_coord(float dx, float dy, float dz) const;
static vec4 get_bounding_sphere(const mat4 &inv_projection, const mat4 &inv_view);
const vec4 *get_planes() const
{
return planes;
}
private:
vec4 planes[6];
mat4 inv_view_projection;
};
}
@@ -0,0 +1,45 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "interpolation.hpp"
namespace Granite
{
float catmull_rom_spline(float c0, float c1, float c2, float c3, float phase)
{
float phase2 = phase * phase;
float phase3 = phase2 * phase;
return c1 +
(c2 - c0) * 0.5f * phase +
(c0 - (2.5f * c1) + (2.0f * c2) - (0.5f * c3)) * phase2 +
((-0.5f * c0) + (1.5f * c1) - (1.5f * c2) + (0.5f * c3)) * phase3;
}
// Computes the analytic derivative of the spline dFd(phase).
float catmull_rom_spline_gradient(float c0, float c1, float c2, float c3, float phase)
{
float phase2 = phase * phase;
return (c2 - c0) * 0.5f +
(c0 - (2.5f * c1) + (2.0f * c2) - (0.5f * c3)) * 2.0f * phase +
((-0.5f * c0) + (1.5f * c1) - (1.5f * c2) + (0.5f * c3)) * 3.0f * phase2;
}
}
@@ -0,0 +1,29 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
namespace Granite
{
float catmull_rom_spline(float c0, float c1, float c2, float c3, float phase);
float catmull_rom_spline_gradient(float c0, float c1, float c2, float c3, float phase);
}
@@ -0,0 +1,33 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "math.hpp"
#include "muglm/muglm_impl.hpp"
namespace Granite
{
void quantize_color(uint8_t *v, const vec4 &color)
{
for (unsigned i = 0; i < 4; i++)
v[i] = uint8_t(muglm::round(muglm::clamp(color[i] * 255.0f, 0.0f, 255.0f)));
}
}
@@ -0,0 +1,31 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "muglm/muglm.hpp"
namespace Granite
{
using namespace muglm;
void quantize_color(uint8_t *v, const vec4 &color);
}
@@ -0,0 +1,52 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "muglm.hpp"
#include <limits>
namespace muglm
{
mat4 mat4_cast(const quat &q);
mat_affine mat_affine_cast(const quat &q);
mat3 mat3_cast(const quat &q);
mat4 translate(const vec3 &v);
mat4 scale(const vec3 &v);
mat_affine translate_affine(const vec3 &v);
mat_affine scale_affine(const vec3 &v);
mat2 inverse(const mat2 &m);
mat3 inverse(const mat3 &m);
mat4 inverse(const mat4 &m);
float determinant(const mat2 &m);
float determinant(const mat3 &m);
constexpr float InfiniteFarPlane = std::numeric_limits<float>::max();
mat4 perspective(float fovy, float aspect, float near, float far);
mat4 frustum(float left, float right, float bottom, float top, float near, float far);
// Orthogonal projection cannot have infinite far-plane.
mat4 ortho(float left, float right, float bottom, float top, float near, float far);
void decompose(const mat4 &m, vec3 &scale, quat &rot, vec3 &trans);
}
@@ -0,0 +1,460 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "matrix_helper.hpp"
#include "muglm_impl.hpp"
#include "simd_headers.hpp"
namespace muglm
{
mat3 mat3_cast(const quat &q_)
{
auto &q = q_.as_vec4();
mat3 res(1.0f);
float qxx = q.x * q.x;
float qyy = q.y * q.y;
float qzz = q.z * q.z;
float qxz = q.x * q.z;
float qxy = q.x * q.y;
float qyz = q.y * q.z;
float qwx = q.w * q.x;
float qwy = q.w * q.y;
float qwz = q.w * q.z;
res[0][0] = 1.0f - 2.0f * (qyy + qzz);
res[0][1] = 2.0f * (qxy + qwz);
res[0][2] = 2.0f * (qxz - qwy);
res[1][0] = 2.0f * (qxy - qwz);
res[1][1] = 1.0f - 2.0f * (qxx + qzz);
res[1][2] = 2.0f * (qyz + qwx);
res[2][0] = 2.0f * (qxz + qwy);
res[2][1] = 2.0f * (qyz - qwx);
res[2][2] = 1.0f - 2.0f * (qxx + qyy);
return res;
}
mat4 mat4_cast(const quat &q)
{
return mat4(mat3_cast(q));
}
mat_affine mat_affine_cast(const quat &q)
{
return mat_affine(mat3_cast(q));
}
mat4 translate(const vec3 &v)
{
return mat4(
vec4(1.0f, 0.0f, 0.0f, 0.0f),
vec4(0.0f, 1.0f, 0.0f, 0.0f),
vec4(0.0f, 0.0f, 1.0f, 0.0f),
vec4(v, 1.0f));
}
mat4 scale(const vec3 &v)
{
return mat4(
vec4(v.x, 0.0f, 0.0f, 0.0f),
vec4(0.0f, v.y, 0.0f, 0.0f),
vec4(0.0f, 0.0f, v.z, 0.0f),
vec4(0.0f, 0.0f, 0.0f, 1.0f));
}
mat_affine translate_affine(const vec3 &v)
{
return mat_affine(
vec4(1.0f, 0.0f, 0.0f, v.x),
vec4(0.0f, 1.0f, 0.0f, v.y),
vec4(0.0f, 0.0f, 1.0f, v.z));
}
mat_affine scale_affine(const vec3 &v)
{
return mat_affine(
vec4(v.x, 0.0f, 0.0f, 0.0f),
vec4(0.0f, v.y, 0.0f, 0.0f),
vec4(0.0f, 0.0f, v.z, 0.0f));
}
float determinant(const mat2 &m)
{
return m[0][0] * m[1][1] - m[1][0] * m[0][1];
}
mat2 inverse(const mat2 &m)
{
float OneOverDeterminant = 1.0f / determinant(m);
mat2 Inverse(
vec2(m[1][1] * OneOverDeterminant,
-m[0][1] * OneOverDeterminant),
vec2(-m[1][0] * OneOverDeterminant,
m[0][0] * OneOverDeterminant));
return Inverse;
}
float determinant(const mat3 &m)
{
return m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2])
- m[1][0] * (m[0][1] * m[2][2] - m[2][1] * m[0][2])
+ m[2][0] * (m[0][1] * m[1][2] - m[1][1] * m[0][2]);
}
mat3 inverse(const mat3 &m)
{
float OneOverDeterminant = 1.0f / determinant(m);
mat3 Inverse;
Inverse[0][0] = +(m[1][1] * m[2][2] - m[2][1] * m[1][2]) * OneOverDeterminant;
Inverse[1][0] = -(m[1][0] * m[2][2] - m[2][0] * m[1][2]) * OneOverDeterminant;
Inverse[2][0] = +(m[1][0] * m[2][1] - m[2][0] * m[1][1]) * OneOverDeterminant;
Inverse[0][1] = -(m[0][1] * m[2][2] - m[2][1] * m[0][2]) * OneOverDeterminant;
Inverse[1][1] = +(m[0][0] * m[2][2] - m[2][0] * m[0][2]) * OneOverDeterminant;
Inverse[2][1] = -(m[0][0] * m[2][1] - m[2][0] * m[0][1]) * OneOverDeterminant;
Inverse[0][2] = +(m[0][1] * m[1][2] - m[1][1] * m[0][2]) * OneOverDeterminant;
Inverse[1][2] = -(m[0][0] * m[1][2] - m[1][0] * m[0][2]) * OneOverDeterminant;
Inverse[2][2] = +(m[0][0] * m[1][1] - m[1][0] * m[0][1]) * OneOverDeterminant;
return Inverse;
}
mat4 inverse(const mat4 &m)
{
float Coef00 = m[2][2] * m[3][3] - m[3][2] * m[2][3];
float Coef02 = m[1][2] * m[3][3] - m[3][2] * m[1][3];
float Coef03 = m[1][2] * m[2][3] - m[2][2] * m[1][3];
float Coef04 = m[2][1] * m[3][3] - m[3][1] * m[2][3];
float Coef06 = m[1][1] * m[3][3] - m[3][1] * m[1][3];
float Coef07 = m[1][1] * m[2][3] - m[2][1] * m[1][3];
float Coef08 = m[2][1] * m[3][2] - m[3][1] * m[2][2];
float Coef10 = m[1][1] * m[3][2] - m[3][1] * m[1][2];
float Coef11 = m[1][1] * m[2][2] - m[2][1] * m[1][2];
float Coef12 = m[2][0] * m[3][3] - m[3][0] * m[2][3];
float Coef14 = m[1][0] * m[3][3] - m[3][0] * m[1][3];
float Coef15 = m[1][0] * m[2][3] - m[2][0] * m[1][3];
float Coef16 = m[2][0] * m[3][2] - m[3][0] * m[2][2];
float Coef18 = m[1][0] * m[3][2] - m[3][0] * m[1][2];
float Coef19 = m[1][0] * m[2][2] - m[2][0] * m[1][2];
float Coef20 = m[2][0] * m[3][1] - m[3][0] * m[2][1];
float Coef22 = m[1][0] * m[3][1] - m[3][0] * m[1][1];
float Coef23 = m[1][0] * m[2][1] - m[2][0] * m[1][1];
vec4 Fac0(Coef00, Coef00, Coef02, Coef03);
vec4 Fac1(Coef04, Coef04, Coef06, Coef07);
vec4 Fac2(Coef08, Coef08, Coef10, Coef11);
vec4 Fac3(Coef12, Coef12, Coef14, Coef15);
vec4 Fac4(Coef16, Coef16, Coef18, Coef19);
vec4 Fac5(Coef20, Coef20, Coef22, Coef23);
vec4 Vec0(m[1][0], m[0][0], m[0][0], m[0][0]);
vec4 Vec1(m[1][1], m[0][1], m[0][1], m[0][1]);
vec4 Vec2(m[1][2], m[0][2], m[0][2], m[0][2]);
vec4 Vec3(m[1][3], m[0][3], m[0][3], m[0][3]);
vec4 Inv0(Vec1 * Fac0 - Vec2 * Fac1 + Vec3 * Fac2);
vec4 Inv1(Vec0 * Fac0 - Vec2 * Fac3 + Vec3 * Fac4);
vec4 Inv2(Vec0 * Fac1 - Vec1 * Fac3 + Vec3 * Fac5);
vec4 Inv3(Vec0 * Fac2 - Vec1 * Fac4 + Vec2 * Fac5);
vec4 SignA(+1, -1, +1, -1);
vec4 SignB(-1, +1, -1, +1);
mat4 Inverse(Inv0 * SignA, Inv1 * SignB, Inv2 * SignA, Inv3 * SignB);
vec4 Row0(Inverse[0][0], Inverse[1][0], Inverse[2][0], Inverse[3][0]);
vec4 Dot0(m[0] * Row0);
float Dot1 = (Dot0.x + Dot0.y) + (Dot0.z + Dot0.w);
float OneOverDeterminant = 1.0f / Dot1;
return Inverse * OneOverDeterminant;
}
void decompose(const mat4 &m, vec3 &scale, quat &rotation, vec3 &trans)
{
vec4 rot;
// Make a lot of assumptions.
// We don't need skew, nor perspective.
// Isolate translation.
trans = m[3].xyz();
vec3 cols[3];
cols[0] = m[0].xyz();
cols[1] = m[1].xyz();
cols[2] = m[2].xyz();
scale.x = length(cols[0]);
scale.y = length(cols[1]);
scale.z = length(cols[2]);
// Isolate scale.
cols[0] /= scale.x;
cols[1] /= scale.y;
cols[2] /= scale.z;
vec3 pdum3 = cross(cols[1], cols[2]);
if (dot(cols[0], pdum3) < 0.0f)
{
scale = -scale;
cols[0] = -cols[0];
cols[1] = -cols[1];
cols[2] = -cols[2];
}
int i, j, k = 0;
float root, trace = cols[0].x + cols[1].y + cols[2].z;
if (trace > 0.0f)
{
root = sqrt(trace + 1.0f);
rot.w = 0.5f * root;
root = 0.5f / root;
rot.x = root * (cols[1].z - cols[2].y);
rot.y = root * (cols[2].x - cols[0].z);
rot.z = root * (cols[0].y - cols[1].x);
}
else
{
static const int Next[3] = {1, 2, 0};
i = 0;
if (cols[1].y > cols[0].x) i = 1;
if (cols[2].z > cols[i][i]) i = 2;
j = Next[i];
k = Next[j];
root = sqrt(cols[i][i] - cols[j][j] - cols[k][k] + 1.0f);
rot[i] = 0.5f * root;
root = 0.5f / root;
rot[j] = root * (cols[i][j] + cols[j][i]);
rot[k] = root * (cols[i][k] + cols[k][i]);
rot.w = root * (cols[j][k] - cols[k][j]);
}
rotation = quat(rot);
}
mat4 ortho(float left, float right, float bottom, float top, float near, float far)
{
mat4 result(1.0f);
result[0][0] = 2.0f / (right - left);
result[1][1] = 2.0f / (top - bottom);
result[3][0] = -(right + left) / (right - left);
result[3][1] = -(top + bottom) / (top - bottom);
result[2][2] = 1.0f / (far - near);
result[3][2] = 1.0f + near / (far - near);
result[0].y *= -1.0f;
result[1].y *= -1.0f;
result[2].y *= -1.0f;
result[3].y *= -1.0f;
return result;
}
mat4 frustum(float left, float right, float bottom, float top, float near, float far)
{
mat4 result(0.0f);
result[0][0] = (2.0f * near) / (right - left);
result[1][1] = (2.0f * near) / (top - bottom);
result[2][0] = (right + left) / (right - left);
result[2][1] = (top + bottom) / (top - bottom);
// Inverse Z
if (far == InfiniteFarPlane)
{
result[3][2] = -near;
}
else
{
result[2][2] = -1.0f - far / (near - far);
result[3][2] = -(far * near) / (near - far);
}
result[2][3] = -1.0f;
// Y-flip so we don't have to bother with negative viewport heights.
result[0].y *= -1.0f;
result[1].y *= -1.0f;
result[2].y *= -1.0f;
result[3].y *= -1.0f;
return result;
}
mat4 perspective(float fovy, float aspect, float near, float far)
{
float tanHalfFovy = tan(fovy / 2.0f);
mat4 result(0.0f);
result[0][0] = 1.0f / (aspect * tanHalfFovy);
result[1][1] = 1.0f / (tanHalfFovy);
// Inverse Z
if (far == InfiniteFarPlane)
{
result[3][2] = near;
}
else
{
result[2][2] = -1.0f - far / (near - far);
result[3][2] = -(far * near) / (near - far);
}
result[2][3] = -1.0f;
// Y-flip so we don't have to bother with negative viewport heights.
result[0].y *= -1.0f;
result[1].y *= -1.0f;
result[2].y *= -1.0f;
result[3].y *= -1.0f;
return result;
}
void transpose(mat4 &dst, const mat4 &src)
{
#if __SSE__
__m128 r0 = _mm_loadu_ps(src[0].data);
__m128 r1 = _mm_loadu_ps(src[1].data);
__m128 r2 = _mm_loadu_ps(src[2].data);
__m128 r3 = _mm_loadu_ps(src[3].data);
_MM_TRANSPOSE4_PS(r0, r1, r2, r3);
_mm_storeu_ps(dst[0].data, r0);
_mm_storeu_ps(dst[1].data, r1);
_mm_storeu_ps(dst[2].data, r2);
_mm_storeu_ps(dst[3].data, r3);
#elif defined(__ARM_NEON)
float32x4x4_t a = vld4q_f32(src[0].data);
vst1q_f32(dst[0].data, a.val[0]);
vst1q_f32(dst[1].data, a.val[1]);
vst1q_f32(dst[2].data, a.val[2]);
vst1q_f32(dst[3].data, a.val[3]);
#else
dst = transpose(src);
#endif
}
void transpose_to_affine(vec4 dst[3], const mat4 &src)
{
#if __SSE__
__m128 r0 = _mm_loadu_ps(src[0].data);
__m128 r1 = _mm_loadu_ps(src[1].data);
__m128 r2 = _mm_loadu_ps(src[2].data);
__m128 r3 = _mm_loadu_ps(src[3].data);
_MM_TRANSPOSE4_PS(r0, r1, r2, r3);
_mm_storeu_ps(dst[0].data, r0);
_mm_storeu_ps(dst[1].data, r1);
_mm_storeu_ps(dst[2].data, r2);
#elif defined(__ARM_NEON)
float32x4x4_t a = vld4q_f32(src[0].data);
vst1q_f32(dst[0].data, a.val[0]);
vst1q_f32(dst[1].data, a.val[1]);
vst1q_f32(dst[2].data, a.val[2]);
#else
mat4 m = transpose(src);
for (int i = 0; i < 3; i++)
dst[i] = m[i];
#endif
}
void transpose_from_affine(mat4 &dst, const vec4 src[3])
{
#if __SSE__
__m128 r0 = _mm_loadu_ps(src[0].data);
__m128 r1 = _mm_loadu_ps(src[1].data);
__m128 r2 = _mm_loadu_ps(src[2].data);
__m128 r3 = _mm_set_ps(1, 0, 0, 0);
_MM_TRANSPOSE4_PS(r0, r1, r2, r3);
_mm_storeu_ps(dst[0].data, r0);
_mm_storeu_ps(dst[1].data, r1);
_mm_storeu_ps(dst[2].data, r2);
_mm_storeu_ps(dst[3].data, r3);
#elif defined(__ARM_NEON)
alignas(16) static const float r3_data[] = { 0, 0, 0, 1 };
float32x4_t r0 = vld1q_f32(src[0].data);
float32x4_t r1 = vld1q_f32(src[1].data);
float32x4_t r2 = vld1q_f32(src[2].data);
float32x4_t r3 = vld1q_f32(r3_data);
float32x4x4_t r = { r0, r1, r2, r3 };
vst4q_f32(dst[0].data, r);
#else
mat4 m = transpose(src);
for (int i = 0; i < 3; i++)
dst[i] = m[i];
#endif
}
void mat_affine::to_mat4(muglm::mat4 &m) const
{
transpose_from_affine(m, vec);
}
mat4 mat_affine::to_mat4() const
{
mat4 m;
to_mat4(m);
return m;
}
float mat_affine::get_uniform_scale() const
{
return length(vec[0].xyz());
}
vec3 mat_affine::get_translation() const
{
// this * vec4(0, 0, 0, 1)
return { vec[0].w, vec[1].w, vec[2].w };
}
vec3 mat_affine::get_forward() const
{
// this * vec4(0, 0, -1, 0).
return { -vec[0].z, -vec[1].z, -vec[2].z };
}
vec3 mat_affine::get_right() const
{
return { vec[0].x, vec[1].x, vec[2].x };
}
vec3 mat_affine::get_up() const
{
return { vec[0].y, vec[1].y, vec[2].y };
}
}
@@ -0,0 +1,993 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
namespace muglm
{
template <typename T> struct tvec2;
template <typename T> struct tvec3;
template <typename T> struct tvec4;
template <typename T> struct tmat2;
template <typename T> struct tmat3;
template <typename T> struct tmat4;
template <typename T>
struct tvec2
{
tvec2() = default;
tvec2(const tvec2 &) = default;
tvec2 &operator=(const tvec2 &) = default;
explicit inline tvec2(T v) noexcept
{
x = v;
y = v;
}
template <typename U>
explicit inline tvec2(const tvec2<U> &u) noexcept
{
x = T(u.x);
y = T(u.y);
}
inline tvec2(T x_, T y_) noexcept
{
x = x_;
y = y_;
}
union
{
T data[2];
struct
{
T x, y;
};
};
inline T &operator[](size_t index)
{
return data[index];
}
inline const T &operator[](size_t index) const
{
return data[index];
}
inline tvec2 xx() const;
inline tvec2 xy() const;
inline tvec2 yx() const;
inline tvec2 yy() const;
inline tvec3<T> xxx() const;
inline tvec3<T> xxy() const;
inline tvec3<T> xyx() const;
inline tvec3<T> xyy() const;
inline tvec3<T> yxx() const;
inline tvec3<T> yxy() const;
inline tvec3<T> yyx() const;
inline tvec3<T> yyy() const;
inline tvec4<T> xxxx() const;
inline tvec4<T> xxxy() const;
inline tvec4<T> xxyx() const;
inline tvec4<T> xxyy() const;
inline tvec4<T> xyxx() const;
inline tvec4<T> xyxy() const;
inline tvec4<T> xyyx() const;
inline tvec4<T> xyyy() const;
inline tvec4<T> yxxx() const;
inline tvec4<T> yxxy() const;
inline tvec4<T> yxyx() const;
inline tvec4<T> yxyy() const;
inline tvec4<T> yyxx() const;
inline tvec4<T> yyxy() const;
inline tvec4<T> yyyx() const;
inline tvec4<T> yyyy() const;
};
template <typename T>
struct tvec3
{
tvec3() = default;
tvec3(const tvec3 &) = default;
tvec3 &operator=(const tvec3 &) = default;
template <typename U>
explicit inline tvec3(const tvec3<U> &u) noexcept
{
x = T(u.x);
y = T(u.y);
z = T(u.z);
}
inline tvec3(const tvec2<T> &a, T b) noexcept
{
x = a.x;
y = a.y;
z = b;
}
inline tvec3(T a, const tvec2<T> &b) noexcept
{
x = a;
y = b.x;
z = b.y;
}
explicit inline tvec3(T v) noexcept
{
x = v;
y = v;
z = v;
}
inline tvec3(T x_, T y_, T z_) noexcept
{
x = x_;
y = y_;
z = z_;
}
union
{
T data[3];
struct
{
T x, y, z;
};
};
inline T &operator[](size_t index)
{
return data[index];
}
inline const T &operator[](size_t index) const
{
return data[index];
}
inline tvec2<T> xx() const;
inline tvec2<T> xy() const;
inline tvec2<T> xz() const;
inline tvec2<T> yx() const;
inline tvec2<T> yy() const;
inline tvec2<T> yz() const;
inline tvec2<T> zx() const;
inline tvec2<T> zy() const;
inline tvec2<T> zz() const;
inline tvec3<T> xxx() const;
inline tvec3<T> xxy() const;
inline tvec3<T> xxz() const;
inline tvec3<T> xyx() const;
inline tvec3<T> xyy() const;
inline tvec3<T> xyz() const;
inline tvec3<T> xzx() const;
inline tvec3<T> xzy() const;
inline tvec3<T> xzz() const;
inline tvec3<T> yxx() const;
inline tvec3<T> yxy() const;
inline tvec3<T> yxz() const;
inline tvec3<T> yyx() const;
inline tvec3<T> yyy() const;
inline tvec3<T> yyz() const;
inline tvec3<T> yzx() const;
inline tvec3<T> yzy() const;
inline tvec3<T> yzz() const;
inline tvec3<T> zxx() const;
inline tvec3<T> zxy() const;
inline tvec3<T> zxz() const;
inline tvec3<T> zyx() const;
inline tvec3<T> zyy() const;
inline tvec3<T> zyz() const;
inline tvec3<T> zzx() const;
inline tvec3<T> zzy() const;
inline tvec3<T> zzz() const;
inline tvec4<T> xxxx() const;
inline tvec4<T> xxxy() const;
inline tvec4<T> xxxz() const;
inline tvec4<T> xxyx() const;
inline tvec4<T> xxyy() const;
inline tvec4<T> xxyz() const;
inline tvec4<T> xxzx() const;
inline tvec4<T> xxzy() const;
inline tvec4<T> xxzz() const;
inline tvec4<T> xyxx() const;
inline tvec4<T> xyxy() const;
inline tvec4<T> xyxz() const;
inline tvec4<T> xyyx() const;
inline tvec4<T> xyyy() const;
inline tvec4<T> xyyz() const;
inline tvec4<T> xyzx() const;
inline tvec4<T> xyzy() const;
inline tvec4<T> xyzz() const;
inline tvec4<T> xzxx() const;
inline tvec4<T> xzxy() const;
inline tvec4<T> xzxz() const;
inline tvec4<T> xzyx() const;
inline tvec4<T> xzyy() const;
inline tvec4<T> xzyz() const;
inline tvec4<T> xzzx() const;
inline tvec4<T> xzzy() const;
inline tvec4<T> xzzz() const;
inline tvec4<T> yxxx() const;
inline tvec4<T> yxxy() const;
inline tvec4<T> yxxz() const;
inline tvec4<T> yxyx() const;
inline tvec4<T> yxyy() const;
inline tvec4<T> yxyz() const;
inline tvec4<T> yxzx() const;
inline tvec4<T> yxzy() const;
inline tvec4<T> yxzz() const;
inline tvec4<T> yyxx() const;
inline tvec4<T> yyxy() const;
inline tvec4<T> yyxz() const;
inline tvec4<T> yyyx() const;
inline tvec4<T> yyyy() const;
inline tvec4<T> yyyz() const;
inline tvec4<T> yyzx() const;
inline tvec4<T> yyzy() const;
inline tvec4<T> yyzz() const;
inline tvec4<T> yzxx() const;
inline tvec4<T> yzxy() const;
inline tvec4<T> yzxz() const;
inline tvec4<T> yzyx() const;
inline tvec4<T> yzyy() const;
inline tvec4<T> yzyz() const;
inline tvec4<T> yzzx() const;
inline tvec4<T> yzzy() const;
inline tvec4<T> yzzz() const;
inline tvec4<T> zxxx() const;
inline tvec4<T> zxxy() const;
inline tvec4<T> zxxz() const;
inline tvec4<T> zxyx() const;
inline tvec4<T> zxyy() const;
inline tvec4<T> zxyz() const;
inline tvec4<T> zxzx() const;
inline tvec4<T> zxzy() const;
inline tvec4<T> zxzz() const;
inline tvec4<T> zyxx() const;
inline tvec4<T> zyxy() const;
inline tvec4<T> zyxz() const;
inline tvec4<T> zyyx() const;
inline tvec4<T> zyyy() const;
inline tvec4<T> zyyz() const;
inline tvec4<T> zyzx() const;
inline tvec4<T> zyzy() const;
inline tvec4<T> zyzz() const;
inline tvec4<T> zzxx() const;
inline tvec4<T> zzxy() const;
inline tvec4<T> zzxz() const;
inline tvec4<T> zzyx() const;
inline tvec4<T> zzyy() const;
inline tvec4<T> zzyz() const;
inline tvec4<T> zzzx() const;
inline tvec4<T> zzzy() const;
inline tvec4<T> zzzz() const;
};
template <typename T>
struct tvec4
{
tvec4() = default;
tvec4(const tvec4 &) = default;
tvec4 &operator=(const tvec4 &) = default;
template <typename U>
explicit inline tvec4(const tvec4<U> &u) noexcept
{
x = T(u.x);
y = T(u.y);
z = T(u.z);
w = T(u.w);
}
inline tvec4(const tvec2<T> &a, const tvec2<T> &b) noexcept
{
x = a.x;
y = a.y;
z = b.x;
w = b.y;
}
inline tvec4(const tvec3<T> &a, T b) noexcept
{
x = a.x;
y = a.y;
z = a.z;
w = b;
}
inline tvec4(T a, const tvec3<T> &b) noexcept
{
x = a;
y = b.x;
z = b.y;
w = b.z;
}
inline tvec4(const tvec2<T> &a, T b, T c) noexcept
{
x = a.x;
y = a.y;
z = b;
w = c;
}
inline tvec4(T a, const tvec2<T> &b, T c) noexcept
{
x = a;
y = b.x;
z = b.y;
w = c;
}
inline tvec4(T a, T b, const tvec2<T> &c) noexcept
{
x = a;
y = b;
z = c.x;
w = c.y;
}
explicit inline tvec4(T v) noexcept
{
x = v;
y = v;
z = v;
w = v;
}
inline tvec4(T x_, T y_, T z_, T w_) noexcept
{
x = x_;
y = y_;
z = z_;
w = w_;
}
inline T &operator[](size_t index)
{
return data[index];
}
inline const T &operator[](size_t index) const
{
return data[index];
}
union
{
T data[4];
struct
{
T x, y, z, w;
};
};
inline tvec2<T> xx() const;
inline tvec2<T> xy() const;
inline tvec2<T> xz() const;
inline tvec2<T> xw() const;
inline tvec2<T> yx() const;
inline tvec2<T> yy() const;
inline tvec2<T> yz() const;
inline tvec2<T> yw() const;
inline tvec2<T> zx() const;
inline tvec2<T> zy() const;
inline tvec2<T> zz() const;
inline tvec2<T> zw() const;
inline tvec2<T> wx() const;
inline tvec2<T> wy() const;
inline tvec2<T> wz() const;
inline tvec2<T> ww() const;
inline tvec3<T> xxx() const;
inline tvec3<T> xxy() const;
inline tvec3<T> xxz() const;
inline tvec3<T> xxw() const;
inline tvec3<T> xyx() const;
inline tvec3<T> xyy() const;
inline tvec3<T> xyz() const;
inline tvec3<T> xyw() const;
inline tvec3<T> xzx() const;
inline tvec3<T> xzy() const;
inline tvec3<T> xzz() const;
inline tvec3<T> xzw() const;
inline tvec3<T> xwx() const;
inline tvec3<T> xwy() const;
inline tvec3<T> xwz() const;
inline tvec3<T> xww() const;
inline tvec3<T> yxx() const;
inline tvec3<T> yxy() const;
inline tvec3<T> yxz() const;
inline tvec3<T> yxw() const;
inline tvec3<T> yyx() const;
inline tvec3<T> yyy() const;
inline tvec3<T> yyz() const;
inline tvec3<T> yyw() const;
inline tvec3<T> yzx() const;
inline tvec3<T> yzy() const;
inline tvec3<T> yzz() const;
inline tvec3<T> yzw() const;
inline tvec3<T> ywx() const;
inline tvec3<T> ywy() const;
inline tvec3<T> ywz() const;
inline tvec3<T> yww() const;
inline tvec3<T> zxx() const;
inline tvec3<T> zxy() const;
inline tvec3<T> zxz() const;
inline tvec3<T> zxw() const;
inline tvec3<T> zyx() const;
inline tvec3<T> zyy() const;
inline tvec3<T> zyz() const;
inline tvec3<T> zyw() const;
inline tvec3<T> zzx() const;
inline tvec3<T> zzy() const;
inline tvec3<T> zzz() const;
inline tvec3<T> zzw() const;
inline tvec3<T> zwx() const;
inline tvec3<T> zwy() const;
inline tvec3<T> zwz() const;
inline tvec3<T> zww() const;
inline tvec3<T> wxx() const;
inline tvec3<T> wxy() const;
inline tvec3<T> wxz() const;
inline tvec3<T> wxw() const;
inline tvec3<T> wyx() const;
inline tvec3<T> wyy() const;
inline tvec3<T> wyz() const;
inline tvec3<T> wyw() const;
inline tvec3<T> wzx() const;
inline tvec3<T> wzy() const;
inline tvec3<T> wzz() const;
inline tvec3<T> wzw() const;
inline tvec3<T> wwx() const;
inline tvec3<T> wwy() const;
inline tvec3<T> wwz() const;
inline tvec3<T> www() const;
inline tvec4 xxxx() const;
inline tvec4 xxxy() const;
inline tvec4 xxxz() const;
inline tvec4 xxxw() const;
inline tvec4 xxyx() const;
inline tvec4 xxyy() const;
inline tvec4 xxyz() const;
inline tvec4 xxyw() const;
inline tvec4 xxzx() const;
inline tvec4 xxzy() const;
inline tvec4 xxzz() const;
inline tvec4 xxzw() const;
inline tvec4 xxwx() const;
inline tvec4 xxwy() const;
inline tvec4 xxwz() const;
inline tvec4 xxww() const;
inline tvec4 xyxx() const;
inline tvec4 xyxy() const;
inline tvec4 xyxz() const;
inline tvec4 xyxw() const;
inline tvec4 xyyx() const;
inline tvec4 xyyy() const;
inline tvec4 xyyz() const;
inline tvec4 xyyw() const;
inline tvec4 xyzx() const;
inline tvec4 xyzy() const;
inline tvec4 xyzz() const;
inline tvec4 xyzw() const;
inline tvec4 xywx() const;
inline tvec4 xywy() const;
inline tvec4 xywz() const;
inline tvec4 xyww() const;
inline tvec4 xzxx() const;
inline tvec4 xzxy() const;
inline tvec4 xzxz() const;
inline tvec4 xzxw() const;
inline tvec4 xzyx() const;
inline tvec4 xzyy() const;
inline tvec4 xzyz() const;
inline tvec4 xzyw() const;
inline tvec4 xzzx() const;
inline tvec4 xzzy() const;
inline tvec4 xzzz() const;
inline tvec4 xzzw() const;
inline tvec4 xzwx() const;
inline tvec4 xzwy() const;
inline tvec4 xzwz() const;
inline tvec4 xzww() const;
inline tvec4 xwxx() const;
inline tvec4 xwxy() const;
inline tvec4 xwxz() const;
inline tvec4 xwxw() const;
inline tvec4 xwyx() const;
inline tvec4 xwyy() const;
inline tvec4 xwyz() const;
inline tvec4 xwyw() const;
inline tvec4 xwzx() const;
inline tvec4 xwzy() const;
inline tvec4 xwzz() const;
inline tvec4 xwzw() const;
inline tvec4 xwwx() const;
inline tvec4 xwwy() const;
inline tvec4 xwwz() const;
inline tvec4 xwww() const;
inline tvec4 yxxx() const;
inline tvec4 yxxy() const;
inline tvec4 yxxz() const;
inline tvec4 yxxw() const;
inline tvec4 yxyx() const;
inline tvec4 yxyy() const;
inline tvec4 yxyz() const;
inline tvec4 yxyw() const;
inline tvec4 yxzx() const;
inline tvec4 yxzy() const;
inline tvec4 yxzz() const;
inline tvec4 yxzw() const;
inline tvec4 yxwx() const;
inline tvec4 yxwy() const;
inline tvec4 yxwz() const;
inline tvec4 yxww() const;
inline tvec4 yyxx() const;
inline tvec4 yyxy() const;
inline tvec4 yyxz() const;
inline tvec4 yyxw() const;
inline tvec4 yyyx() const;
inline tvec4 yyyy() const;
inline tvec4 yyyz() const;
inline tvec4 yyyw() const;
inline tvec4 yyzx() const;
inline tvec4 yyzy() const;
inline tvec4 yyzz() const;
inline tvec4 yyzw() const;
inline tvec4 yywx() const;
inline tvec4 yywy() const;
inline tvec4 yywz() const;
inline tvec4 yyww() const;
inline tvec4 yzxx() const;
inline tvec4 yzxy() const;
inline tvec4 yzxz() const;
inline tvec4 yzxw() const;
inline tvec4 yzyx() const;
inline tvec4 yzyy() const;
inline tvec4 yzyz() const;
inline tvec4 yzyw() const;
inline tvec4 yzzx() const;
inline tvec4 yzzy() const;
inline tvec4 yzzz() const;
inline tvec4 yzzw() const;
inline tvec4 yzwx() const;
inline tvec4 yzwy() const;
inline tvec4 yzwz() const;
inline tvec4 yzww() const;
inline tvec4 ywxx() const;
inline tvec4 ywxy() const;
inline tvec4 ywxz() const;
inline tvec4 ywxw() const;
inline tvec4 ywyx() const;
inline tvec4 ywyy() const;
inline tvec4 ywyz() const;
inline tvec4 ywyw() const;
inline tvec4 ywzx() const;
inline tvec4 ywzy() const;
inline tvec4 ywzz() const;
inline tvec4 ywzw() const;
inline tvec4 ywwx() const;
inline tvec4 ywwy() const;
inline tvec4 ywwz() const;
inline tvec4 ywww() const;
inline tvec4 zxxx() const;
inline tvec4 zxxy() const;
inline tvec4 zxxz() const;
inline tvec4 zxxw() const;
inline tvec4 zxyx() const;
inline tvec4 zxyy() const;
inline tvec4 zxyz() const;
inline tvec4 zxyw() const;
inline tvec4 zxzx() const;
inline tvec4 zxzy() const;
inline tvec4 zxzz() const;
inline tvec4 zxzw() const;
inline tvec4 zxwx() const;
inline tvec4 zxwy() const;
inline tvec4 zxwz() const;
inline tvec4 zxww() const;
inline tvec4 zyxx() const;
inline tvec4 zyxy() const;
inline tvec4 zyxz() const;
inline tvec4 zyxw() const;
inline tvec4 zyyx() const;
inline tvec4 zyyy() const;
inline tvec4 zyyz() const;
inline tvec4 zyyw() const;
inline tvec4 zyzx() const;
inline tvec4 zyzy() const;
inline tvec4 zyzz() const;
inline tvec4 zyzw() const;
inline tvec4 zywx() const;
inline tvec4 zywy() const;
inline tvec4 zywz() const;
inline tvec4 zyww() const;
inline tvec4 zzxx() const;
inline tvec4 zzxy() const;
inline tvec4 zzxz() const;
inline tvec4 zzxw() const;
inline tvec4 zzyx() const;
inline tvec4 zzyy() const;
inline tvec4 zzyz() const;
inline tvec4 zzyw() const;
inline tvec4 zzzx() const;
inline tvec4 zzzy() const;
inline tvec4 zzzz() const;
inline tvec4 zzzw() const;
inline tvec4 zzwx() const;
inline tvec4 zzwy() const;
inline tvec4 zzwz() const;
inline tvec4 zzww() const;
inline tvec4 zwxx() const;
inline tvec4 zwxy() const;
inline tvec4 zwxz() const;
inline tvec4 zwxw() const;
inline tvec4 zwyx() const;
inline tvec4 zwyy() const;
inline tvec4 zwyz() const;
inline tvec4 zwyw() const;
inline tvec4 zwzx() const;
inline tvec4 zwzy() const;
inline tvec4 zwzz() const;
inline tvec4 zwzw() const;
inline tvec4 zwwx() const;
inline tvec4 zwwy() const;
inline tvec4 zwwz() const;
inline tvec4 zwww() const;
inline tvec4 wxxx() const;
inline tvec4 wxxy() const;
inline tvec4 wxxz() const;
inline tvec4 wxxw() const;
inline tvec4 wxyx() const;
inline tvec4 wxyy() const;
inline tvec4 wxyz() const;
inline tvec4 wxyw() const;
inline tvec4 wxzx() const;
inline tvec4 wxzy() const;
inline tvec4 wxzz() const;
inline tvec4 wxzw() const;
inline tvec4 wxwx() const;
inline tvec4 wxwy() const;
inline tvec4 wxwz() const;
inline tvec4 wxww() const;
inline tvec4 wyxx() const;
inline tvec4 wyxy() const;
inline tvec4 wyxz() const;
inline tvec4 wyxw() const;
inline tvec4 wyyx() const;
inline tvec4 wyyy() const;
inline tvec4 wyyz() const;
inline tvec4 wyyw() const;
inline tvec4 wyzx() const;
inline tvec4 wyzy() const;
inline tvec4 wyzz() const;
inline tvec4 wyzw() const;
inline tvec4 wywx() const;
inline tvec4 wywy() const;
inline tvec4 wywz() const;
inline tvec4 wyww() const;
inline tvec4 wzxx() const;
inline tvec4 wzxy() const;
inline tvec4 wzxz() const;
inline tvec4 wzxw() const;
inline tvec4 wzyx() const;
inline tvec4 wzyy() const;
inline tvec4 wzyz() const;
inline tvec4 wzyw() const;
inline tvec4 wzzx() const;
inline tvec4 wzzy() const;
inline tvec4 wzzz() const;
inline tvec4 wzzw() const;
inline tvec4 wzwx() const;
inline tvec4 wzwy() const;
inline tvec4 wzwz() const;
inline tvec4 wzww() const;
inline tvec4 wwxx() const;
inline tvec4 wwxy() const;
inline tvec4 wwxz() const;
inline tvec4 wwxw() const;
inline tvec4 wwyx() const;
inline tvec4 wwyy() const;
inline tvec4 wwyz() const;
inline tvec4 wwyw() const;
inline tvec4 wwzx() const;
inline tvec4 wwzy() const;
inline tvec4 wwzz() const;
inline tvec4 wwzw() const;
inline tvec4 wwwx() const;
inline tvec4 wwwy() const;
inline tvec4 wwwz() const;
inline tvec4 wwww() const;
};
template <typename T>
struct tmat2
{
tmat2() = default;
tmat2(const tmat2 &) = default;
tmat2 &operator=(const tmat2 &) = default;
explicit inline tmat2(T v) noexcept
{
vec[0] = tvec2<T>(v, T(0));
vec[1] = tvec2<T>(T(0), v);
}
inline tmat2(const tvec2<T> &a, const tvec2<T> &b) noexcept
{
vec[0] = a;
vec[1] = b;
}
inline tvec2<T> &operator[](size_t index)
{
return vec[index];
}
inline const tvec2<T> &operator[](size_t index) const
{
return vec[index];
}
private:
tvec2<T> vec[2];
};
template <typename T>
struct tmat3
{
tmat3() = default;
tmat3(const tmat3 &) = default;
tmat3 &operator=(const tmat3 &) = default;
explicit inline tmat3(T v) noexcept
{
vec[0] = tvec3<T>(v, T(0), T(0));
vec[1] = tvec3<T>(T(0), v, T(0));
vec[2] = tvec3<T>(T(0), T(0), v);
}
inline tmat3(const tvec3<T> &a, const tvec3<T> &b, const tvec3<T> &c) noexcept
{
vec[0] = a;
vec[1] = b;
vec[2] = c;
}
explicit inline tmat3(const tmat4<T> &m) noexcept
{
for (int col = 0; col < 3; col++)
for (int row = 0; row < 3; row++)
vec[col][row] = m[col][row];
}
inline tvec3<T> &operator[](size_t index)
{
return vec[index];
}
inline const tvec3<T> &operator[](size_t index) const
{
return vec[index];
}
private:
tvec3<T> vec[3];
};
template <typename T>
struct tmat4
{
tmat4() = default;
tmat4(const tmat4 &) = default;
tmat4 &operator=(const tmat4 &) = default;
explicit inline tmat4(T v) noexcept
{
vec[0] = tvec4<T>(v, T(0), T(0), T(0));
vec[1] = tvec4<T>(T(0), v, T(0), T(0));
vec[2] = tvec4<T>(T(0), T(0), v, T(0));
vec[3] = tvec4<T>(T(0), T(0), T(0), v);
}
explicit inline tmat4(const tmat3<T> &m) noexcept
{
vec[0] = tvec4<T>(m[0], T(0));
vec[1] = tvec4<T>(m[1], T(0));
vec[2] = tvec4<T>(m[2], T(0));
vec[3] = tvec4<T>(T(0), T(0), T(0), T(1));
}
inline tmat4(const tvec4<T> &a, const tvec4<T> &b, const tvec4<T> &c, const tvec4<T> &d) noexcept
{
vec[0] = a;
vec[1] = b;
vec[2] = c;
vec[3] = d;
}
inline tvec4<T> &operator[](size_t index)
{
return vec[index];
}
inline const tvec4<T> &operator[](size_t index) const
{
return vec[index];
}
private:
tvec4<T> vec[4];
};
using uint = uint32_t;
using vec2 = tvec2<float>;
using vec3 = tvec3<float>;
using vec4 = tvec4<float>;
using mat2 = tmat2<float>;
using mat3 = tmat3<float>;
using mat4 = tmat4<float>;
using dvec2 = tvec2<double>;
using dvec3 = tvec3<double>;
using dvec4 = tvec4<double>;
using dmat2 = tmat2<double>;
using dmat3 = tmat3<double>;
using dmat4 = tmat4<double>;
using ivec2 = tvec2<int32_t>;
using ivec3 = tvec3<int32_t>;
using ivec4 = tvec4<int32_t>;
using uvec2 = tvec2<uint32_t>;
using uvec3 = tvec3<uint32_t>;
using uvec4 = tvec4<uint32_t>;
using u16vec2 = tvec2<uint16_t>;
using u16vec3 = tvec3<uint16_t>;
using u16vec4 = tvec4<uint16_t>;
using i16vec2 = tvec2<int16_t>;
using i16vec3 = tvec3<int16_t>;
using i16vec4 = tvec4<int16_t>;
using u8vec2 = tvec2<uint8_t>;
using u8vec3 = tvec3<uint8_t>;
using u8vec4 = tvec4<uint8_t>;
using i8vec2 = tvec2<int8_t>;
using i8vec3 = tvec3<int8_t>;
using i8vec4 = tvec4<int8_t>;
using bvec2 = tvec2<bool>;
using bvec3 = tvec3<bool>;
using bvec4 = tvec4<bool>;
void transpose(mat4 &dst, const mat4 &src);
void transpose_to_affine(vec4 dst[3], const mat4 &src);
struct mat_affine
{
mat_affine() = default;
mat_affine(const mat_affine &) = default;
mat_affine &operator=(const mat_affine &) = default;
explicit inline mat_affine(const mat4 &m)
{
transpose_to_affine(vec, m);
}
explicit inline mat_affine(const mat3 &m)
: vec{{m[0][0], m[1][0], m[2][0], 0.0f},
{m[0][1], m[1][1], m[2][1], 0.0f},
{m[0][2], m[1][2], m[2][2], 0.0f}}
{
}
explicit inline mat_affine(float v)
: vec{{v, 0, 0, 0}, {0, v, 0, 0}, {0, 0, v, 0}}
{
}
inline mat_affine(const vec4 &r0, const vec4 &r1, const vec4 &r2)
: vec{r0, r1, r2}
{
}
inline vec4 &operator[](size_t index)
{
return vec[index];
}
inline const vec4 &operator[](size_t index) const
{
return vec[index];
}
float get_uniform_scale() const;
vec3 get_translation() const;
// Based on identity view pointing to -Z axis, Y up.
vec3 get_forward() const;
vec3 get_right() const;
vec3 get_up() const;
mat4 to_mat4() const;
void to_mat4(mat4 &m) const;
inline mat3 to_mat3() const
{
return {
vec3(vec[0][0], vec[1][0], vec[2][0]),
vec3(vec[0][1], vec[1][1], vec[2][1]),
vec3(vec[0][2], vec[1][2], vec[2][2]),
};
}
private:
vec4 vec[3];
};
struct quat : private vec4
{
quat() = default;
quat(const quat &) = default;
quat(float w_, float x_, float y_, float z_)
: vec4(x_, y_, z_, w_)
{}
explicit inline quat(const vec4 &v)
: vec4(v)
{}
inline quat(float w_, const vec3 &v_)
: vec4(v_, w_)
{}
inline const vec4 &as_vec4() const
{
return *static_cast<const vec4 *>(this);
}
quat &operator=(const quat &) = default;
using vec4::x;
using vec4::y;
using vec4::z;
using vec4::w;
};
template <typename T> constexpr inline T pi() { return T(3.1415926535897932384626433832795028841971); }
template <typename T> constexpr inline T half_pi() { return T(0.5) * pi<T>(); }
template <typename T> constexpr inline T one_over_root_two() { return T(0.7071067811865476); }
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,251 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "muglm_impl.hpp"
#include "matrix_helper.hpp"
#include "simd.hpp"
#include <assert.h>
#include <stdlib.h>
#include "aabb.hpp"
using namespace muglm;
#define MATH_ASSERT(x) do { \
if (!bool(x)) \
abort(); \
} while(0)
template <typename T>
static void assert_equal_epsilon(const T &a, const T &b, float epsilon = 0.0001f)
{
MATH_ASSERT(all(lessThanEqual(abs(a - b), T(epsilon))));
}
template <typename T>
static void assert_equal(const T &a, const T &b)
{
MATH_ASSERT(all(equal(a, b)));
}
static void assert_equal_epsilon(const mat2 &a, const mat2 &b, float epsilon = 0.0001f)
{
assert_equal_epsilon(a[0], b[0], epsilon);
assert_equal_epsilon(a[1], b[1], epsilon);
}
static void assert_equal_epsilon(const mat3 &a, const mat3 &b, float epsilon = 0.0001f)
{
assert_equal_epsilon(a[0], b[0], epsilon);
assert_equal_epsilon(a[1], b[1], epsilon);
assert_equal_epsilon(a[2], b[2], epsilon);
}
static void assert_equal_epsilon(const mat4 &a, const mat4 &b, float epsilon = 0.0001f)
{
assert_equal_epsilon(a[0], b[0], epsilon);
assert_equal_epsilon(a[1], b[1], epsilon);
assert_equal_epsilon(a[2], b[2], epsilon);
assert_equal_epsilon(a[3], b[3], epsilon);
}
static void assert_equal_epsilon(const mat_affine &a, const mat_affine &b, float epsilon = 0.0001f)
{
assert_equal_epsilon(a[0], b[0], epsilon);
assert_equal_epsilon(a[1], b[1], epsilon);
assert_equal_epsilon(a[2], b[2], epsilon);
}
static void assert_equal(const mat2 &a, const mat2 &b)
{
assert_equal_epsilon(a, b, 0.0f);
}
static void assert_equal(const mat3 &a, const mat3 &b)
{
assert_equal_epsilon(a, b, 0.0f);
}
static void assert_equal(const mat4 &a, const mat4 &b)
{
assert_equal_epsilon(a, b, 0.0f);
}
static void test_mat2()
{
mat2 m(vec2(2.0f, 0.5f), vec2(8.0f, 4.0f));
mat2 inv_m = inverse(m);
mat2 mul0 = m * inv_m;
mat2 mul1 = inv_m * m;
assert_equal_epsilon(mul0, mat2(1.0f));
assert_equal_epsilon(mul1, mat2(1.0f));
assert_equal(transpose(transpose(m)), m);
}
static void test_mat3()
{
mat3 m(vec3(2.0f, 0.5f, -3.0f), vec3(8.0f, 4.0f, 0.25f), vec3(-20.0f, 5.0f, 1.0f));
mat3 inv_m = inverse(m);
mat3 mul0 = m * inv_m;
mat3 mul1 = inv_m * m;
assert_equal_epsilon(mul0, mat3(1.0f));
assert_equal_epsilon(mul1, mat3(1.0f));
assert_equal(transpose(transpose(m)), m);
}
static void test_mat4()
{
mat4 m(vec4(2.0f, 0.5f, -3.0f, 1.0f), vec4(8.0f, 4.0f, 0.25f, 8.0f), vec4(8.0f, -20.0f, 5.0f, 1.0f), vec4(0.0f, 1.0f, 2.0f, 3.0f));
mat4 inv_m = inverse(m);
mat4 mul0 = m * inv_m;
mat4 mul1 = inv_m * m;
assert_equal_epsilon(mul0, mat4(1.0f));
assert_equal_epsilon(mul1, mat4(1.0f));
assert_equal(transpose(transpose(m)), m);
}
static void test_quat()
{
// X
{
quat q = angleAxis(half_pi<float>(), vec3(0.0f, 0.0f, 1.0f));
vec3 y = q * vec3(1.0f, 0.0f, 0.0f);
assert_equal_epsilon(y, vec3(0.0f, 1.0f, 0.0f));
quat half_q = angleAxis(0.5f * half_pi<float>(), vec3(0.0f, 0.0f, 1.0f));
q = half_q * half_q;
y = q * vec3(1.0f, 0.0f, 0.0f);
assert_equal_epsilon(y, vec3(0.0f, 1.0f, 0.0f));
}
// Y
{
quat q = angleAxis(half_pi<float>(), vec3(0.0f, 1.0f, 0.0f));
vec3 y = q * vec3(1.0f, 0.0f, 0.0f);
assert_equal_epsilon(y, vec3(0.0f, 0.0f, -1.0f));
quat half_q = angleAxis(0.5f * half_pi<float>(), vec3(0.0f, 1.0f, 0.0f));
q = half_q * half_q;
y = q * vec3(1.0f, 0.0f, 0.0f);
assert_equal_epsilon(y, vec3(0.0f, 0.0f, -1.0f));
}
assert_equal_epsilon(mat3_cast(quat(1.0f, 0.0f, 0.0f, 0.0f)), mat3(1.0f));
assert_equal_epsilon(mat4_cast(quat(1.0f, 0.0f, 0.0f, 0.0f)), mat4(1.0f));
}
static void test_decompose()
{
vec3 scaling(4.0f, -3.0f, 2.0f);
quat rotate = angleAxis(0.543f, vec3(0.1f, 0.2f, -0.9f));
vec3 trans(5.0f, 4.0f, 2.0f);
mat4 original = translate(trans) * mat4_cast(rotate) * scale(scaling);
vec3 s, t;
quat r;
decompose(original, s, r, t);
mat4 reconstructed = translate(t) * mat4_cast(r) * scale(s);
assert_equal_epsilon(original, reconstructed);
}
static void test_transpose()
{
mat4 original(vec4(1, 2, 3, 4), vec4(5, 6, 7, 8), vec4(9, 10, 11, 12), vec4(13, 14, 15, 16));
mat4 transposed = transpose(original);
mat_affine aff{original};
const mat4 expected_transposed(
vec4(1, 5, 9, 13),
vec4(2, 6, 10, 14),
vec4(3, 7, 11, 15),
vec4(4, 8, 12, 16));
mat_affine expected_aff;
for (int i = 0; i < 3; i++)
expected_aff[i] = expected_transposed[i];
assert_equal_epsilon(expected_transposed, transposed, 0.0f);
assert_equal_epsilon(expected_aff, aff, 0.0f);
}
static void test_affine()
{
mat4 a(vec4(1, 2, 3, 0), vec4(5, 6, 7, 0),
vec4(9, 10, 11, 0), vec4(13, 14, 15, 1));
mat4 b(vec4(17, 18, 19, 0), vec4(21, 22, 23, 0),
vec4(25, 26, 27, 0), vec4(29, 30, 31, 1));
mat4 c = a * b;
mat_affine aff_c(c);
mat_affine aff_a(a);
mat_affine aff_b(b);
mat_affine res;
Granite::SIMD::mul(res, aff_a, aff_b);
assert_equal_epsilon(res, aff_c, 0.0f);
}
static void test_affine_mul()
{
mat4 a(vec4(1, 2, 3, 0), vec4(5, 6, 7, 0),
vec4(9, 10, 11, 0), vec4(13, 14, 15, 1));
vec4 b(1, 2, 3, 1);
mat_affine aff_a(a);
vec4 ref = a * b;
vec4 res4, res_affine;
Granite::SIMD::mul(res4, a, b);
Granite::SIMD::mul(res_affine, aff_a, b);
assert_equal_epsilon(ref, res4, 0.0f);
assert_equal_epsilon(ref, res_affine, 0.0f);
}
static void test_aabb_transform()
{
mat4 m(vec4(1, 2, 3, 0), vec4(5, 6, 7, 0),
vec4(9, 10, 11, 0), vec4(13, 14, 15, 1));
Granite::AABB aabb{vec3(-1, -2, -4), vec3(3, 7, 4)};
Granite::AABB reference_aabb, affine_aabb;
Granite::SIMD::transform_aabb(reference_aabb, aabb, m);
Granite::SIMD::transform_aabb(affine_aabb, aabb, mat_affine(m));
assert_equal_epsilon(reference_aabb.get_maximum4(), affine_aabb.get_maximum4(), 0.0f);
assert_equal_epsilon(reference_aabb.get_minimum4(), affine_aabb.get_minimum4(), 0.0f);
}
int main()
{
test_mat2();
test_mat3();
test_mat4();
test_quat();
test_decompose();
test_transpose();
test_affine();
test_affine_mul();
test_aabb_transform();
}
@@ -0,0 +1,196 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "math.hpp"
#include "image.hpp"
#include "lights/light_info.hpp"
#include "limits.hpp"
namespace Granite
{
class LightClusterer;
class VolumetricFog;
class VolumetricDiffuseLightManager;
enum { NumShadowCascades = 4 };
struct RenderParameters
{
mat4 projection;
mat4 view;
mat4 view_projection;
mat4 inv_projection;
mat4 inv_view;
mat4 inv_view_projection;
mat4 local_view_projection;
mat4 inv_local_view_projection;
mat4 unjittered_view_projection;
mat4 unjittered_inv_view_projection;
mat4 unjittered_prev_view_projection;
alignas(16) vec3 camera_position;
alignas(16) vec3 camera_front;
alignas(16) vec3 camera_right;
alignas(16) vec3 camera_up;
float z_near;
float z_far;
};
struct ResolutionParameters
{
alignas(8) vec2 resolution;
alignas(8) vec2 inv_resolution;
};
struct VolumetricFogParameters
{
float slice_z_log2_scale;
};
struct FogParameters
{
alignas(16) vec3 color;
float falloff;
};
struct DirectionalParameters
{
alignas(16) vec3 color;
alignas(16) vec3 direction;
};
struct ShadowParameters
{
alignas(16) mat4 transforms[NumShadowCascades];
float cascade_log_bias;
};
struct ClustererParametersBindless
{
alignas(16) mat4 transform;
alignas(16) vec4 clip_scale;
alignas(16) vec3 camera_base;
alignas(16) vec3 camera_front;
alignas(8) vec2 xy_scale;
alignas(8) ivec2 resolution_xy;
alignas(8) vec2 inv_resolution_xy;
uint32_t num_lights;
uint32_t num_lights_32;
uint32_t num_decals;
uint32_t num_decals_32;
uint32_t decals_texture_offset;
uint32_t z_max_index;
float z_scale;
};
struct DiffuseVolumeParameters
{
mat_affine world_to_texture;
vec4 world_lo;
vec4 world_hi;
float lo_tex_coord_x;
float hi_tex_coord_x;
float guard_band_factor;
float guard_band_sharpen;
};
#define CLUSTERER_MAX_VOLUMES 128
struct ClustererParametersVolumetric
{
alignas(16) muglm::vec3 sun_direction;
uint32_t bindless_index_offset;
alignas(16) muglm::vec3 sun_color;
uint32_t num_volumes;
alignas(16) DiffuseVolumeParameters volumes[CLUSTERER_MAX_VOLUMES];
};
struct FogRegionParameters
{
mat_affine world_to_texture;
vec4 world_lo;
vec4 world_hi;
};
#define CLUSTERER_MAX_FOG_REGIONS 128
struct ClustererParametersFogRegions
{
uint32_t bindless_index_offset;
uint32_t num_regions;
alignas(16) FogRegionParameters regions[CLUSTERER_MAX_FOG_REGIONS];
};
#define CLUSTERER_MAX_LIGHTS_BINDLESS 4096
#define CLUSTERER_MAX_DECALS_BINDLESS 4096
#define CLUSTERER_MAX_LIGHTS_GLOBAL 32
struct BindlessDecalTransform
{
mat_affine world_to_texture;
};
struct ClustererBindlessTransforms
{
PositionalFragmentInfo lights[CLUSTERER_MAX_LIGHTS_BINDLESS];
mat4 shadow[CLUSTERER_MAX_LIGHTS_BINDLESS];
mat_affine model[CLUSTERER_MAX_LIGHTS_BINDLESS];
uint32_t type_mask[CLUSTERER_MAX_LIGHTS_BINDLESS / 32];
BindlessDecalTransform decals[CLUSTERER_MAX_DECALS_BINDLESS];
};
struct ClustererGlobalTransforms
{
alignas(16) PositionalFragmentInfo lights[CLUSTERER_MAX_LIGHTS_GLOBAL];
alignas(16) mat4 shadow[CLUSTERER_MAX_LIGHTS_GLOBAL];
alignas(16) uint32_t type_mask[CLUSTERER_MAX_LIGHTS_GLOBAL / 32];
uint32_t descriptor_offset;
uint32_t num_lights;
};
static_assert(sizeof(ClustererGlobalTransforms) <= Vulkan::VULKAN_MAX_UBO_SIZE, "Global transforms is too large.");
struct CombinedRenderParameters
{
alignas(16) FogParameters fog;
alignas(16) ShadowParameters shadow;
alignas(16) VolumetricFogParameters volumetric_fog;
alignas(16) DirectionalParameters directional;
alignas(16) ResolutionParameters resolution;
};
static_assert(sizeof(CombinedRenderParameters) <= Vulkan::VULKAN_MAX_UBO_SIZE, "CombinedRenderParameters cannot fit in min-spec.");
struct LightingParameters
{
FogParameters fog = {};
DirectionalParameters directional;
ShadowParameters shadow;
Vulkan::ImageView *shadows = nullptr;
Vulkan::ImageView *ambient_occlusion = nullptr;
const LightClusterer *cluster = nullptr;
const VolumetricFog *volumetric_fog = nullptr;
const VolumetricDiffuseLightManager *volumetric_diffuse = nullptr;
};
}
@@ -0,0 +1,602 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "math.hpp"
#include "aabb.hpp"
#include "simd_headers.hpp"
#include "muglm/matrix_helper.hpp"
namespace Granite
{
namespace SIMD
{
static inline bool frustum_cull(const AABB &aabb, const vec4 *planes)
{
#if defined(__SSE3__)
__m128 lo = _mm_loadu_ps(aabb.get_minimum4().data);
__m128 hi = _mm_loadu_ps(aabb.get_maximum4().data);
#define COMPUTE_PLANE(i) \
__m128 p##i = _mm_loadu_ps(planes[i].data); \
__m128 mask##i = _mm_cmpgt_ps(p##i, _mm_setzero_ps()); \
__m128 major_axis##i = _mm_or_ps(_mm_and_ps(mask##i, hi), _mm_andnot_ps(mask##i, lo)); \
__m128 dotted##i = _mm_mul_ps(p##i, major_axis##i)
COMPUTE_PLANE(0);
COMPUTE_PLANE(1);
COMPUTE_PLANE(2);
COMPUTE_PLANE(3);
COMPUTE_PLANE(4);
COMPUTE_PLANE(5);
__m128 merged01 = _mm_hadd_ps(dotted0, dotted1);
__m128 merged23 = _mm_hadd_ps(dotted2, dotted3);
__m128 merged45 = _mm_hadd_ps(dotted4, dotted5);
__m128 merged0123 = _mm_hadd_ps(merged01, merged23);
merged45 = _mm_hadd_ps(merged45, merged45);
__m128 merged = _mm_or_ps(merged0123, merged45);
// Sets bit if the sign bit is set.
int mask = _mm_movemask_ps(merged);
return mask == 0;
#elif defined(__ARM_NEON)
float32x4_t lo = vld1q_f32(aabb.get_minimum4().data);
float32x4_t hi = vld1q_f32(aabb.get_maximum4().data);
#define COMPUTE_PLANE(i) \
float32x4_t p##i = vld1q_f32(planes[i].data); \
uint32x4_t mask##i = vcgtq_f32(p##i, vdupq_n_f32(0.0f)); \
float32x4_t major_axis##i = vbslq_f32(mask##i, hi, lo); \
float32x4_t dotted##i = vmulq_f32(p##i, major_axis##i)
COMPUTE_PLANE(0);
COMPUTE_PLANE(1);
COMPUTE_PLANE(2);
COMPUTE_PLANE(3);
COMPUTE_PLANE(4);
COMPUTE_PLANE(5);
#if defined(__aarch64__)
float32x4_t merged01 = vpaddq_f32(dotted0, dotted1);
float32x4_t merged23 = vpaddq_f32(dotted2, dotted3);
float32x4_t merged45 = vpaddq_f32(dotted4, dotted5);
float32x4_t merged0123 = vpaddq_f32(merged01, merged23);
merged45 = vpaddq_f32(merged45, merged45);
float32x4_t merged = vminq_f32(merged0123, merged45);
float32x2_t merged_half = vmin_f32(vget_low_f32(merged), vget_high_f32(merged));
merged_half = vpmin_f32(merged_half, merged_half);
return vget_lane_f32(merged_half, 0) >= 0.0f;
#else
float32x2_t merged0 = vpadd_f32(vget_low_f32(dotted0), vget_high_f32(dotted0));
float32x2_t merged1 = vpadd_f32(vget_low_f32(dotted1), vget_high_f32(dotted1));
float32x2_t merged2 = vpadd_f32(vget_low_f32(dotted2), vget_high_f32(dotted2));
float32x2_t merged3 = vpadd_f32(vget_low_f32(dotted3), vget_high_f32(dotted3));
float32x2_t merged4 = vpadd_f32(vget_low_f32(dotted4), vget_high_f32(dotted4));
float32x2_t merged5 = vpadd_f32(vget_low_f32(dotted5), vget_high_f32(dotted5));
float32x2_t merged01 = vpadd_f32(merged0, merged1);
float32x2_t merged23 = vpadd_f32(merged2, merged3);
float32x2_t merged45 = vpadd_f32(merged4, merged5);
float32x2_t merged = vmin_f32(merged01, merged23);
merged = vmin_f32(merged, merged45);
float32x2_t merged_half = vpmin_f32(merged, merged);
return vget_lane_f32(merged_half, 0) >= 0.0f;
#endif
#else
#error "Implement me."
#endif
}
static inline void mul(vec4 &c, const mat_affine &a, const vec4 &b)
{
#if defined(__SSE4_1__)
__m128 a0 = _mm_loadu_ps(a[0].data);
__m128 a1 = _mm_loadu_ps(a[1].data);
__m128 a2 = _mm_loadu_ps(a[2].data);
__m128 b0 = _mm_loadu_ps(b.data);
__m128 r0 = _mm_dp_ps(a0, b0, 0xf1);
__m128 r1 = _mm_dp_ps(a1, b0, 0xf2);
__m128 r2 = _mm_dp_ps(a2, b0, 0xf4);
__m128 r = _mm_or_ps(_mm_or_ps(r0, r1), r2);
r = _mm_insert_ps(r, _mm_set_ss(1.0f), 0x30);
_mm_storeu_ps(c.data, r);
#elif defined(__SSE__)
__m128 a0 = _mm_loadu_ps(a[0].data);
__m128 a1 = _mm_loadu_ps(a[1].data);
__m128 a2 = _mm_loadu_ps(a[2].data);
__m128 a3 = _mm_set_ps(1, 0, 0, 0);
_MM_TRANSPOSE4_PS(a0, a1, a2, a3);
__m128 b0 = _mm_loadu_ps(b.data);
__m128 b00 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(0, 0, 0, 0));
__m128 b01 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(1, 1, 1, 1));
__m128 b02 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(2, 2, 2, 2));
__m128 b03 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(3, 3, 3, 3));
__m128 col0 = _mm_mul_ps(a0, b00);
col0 = _mm_add_ps(col0, _mm_mul_ps(a1, b01));
col0 = _mm_add_ps(col0, _mm_mul_ps(a2, b02));
col0 = _mm_add_ps(col0, _mm_mul_ps(a3, b03));
_mm_storeu_ps(c.data, col0);
#elif defined(__aarch64__)
alignas(16) static const float a3_data[] = { 0, 0, 0, 1 };
float32x4_t a0 = vld1q_f32(a[0].data);
float32x4_t a1 = vld1q_f32(a[1].data);
float32x4_t a2 = vld1q_f32(a[2].data);
float32x4_t a3 = vld1q_f32(a3_data);
// From sse2neon.h
float64x2_t r0 = (float64x2_t)vtrn1q_f32(a0, a1);
float64x2_t r1 = (float64x2_t)vtrn2q_f32(a0, a1);
float64x2_t r2 = (float64x2_t)vtrn1q_f32(a2, a3);
float64x2_t r3 = (float64x2_t)vtrn2q_f32(a2, a3);
a0 = (float32x4_t)vtrn1q_f64(r0, r2);
a1 = (float32x4_t)vtrn1q_f64(r1, r3);
a2 = (float32x4_t)vtrn2q_f64(r0, r2);
a3 = (float32x4_t)vtrn2q_f64(r1, r3);
float32x4_t b0 = vld1q_f32(b.data);
float32x4_t col0 = vmulq_n_f32(a0, vgetq_lane_f32(b0, 0));
col0 = vmlaq_n_f32(col0, a1, vgetq_lane_f32(b0, 1));
col0 = vmlaq_n_f32(col0, a2, vgetq_lane_f32(b0, 2));
col0 = vmlaq_n_f32(col0, a3, vgetq_lane_f32(b0, 3));
vst1q_f32(c.data, col0);
#else
c = transpose(mat4(a[0], a[1], a[2], vec4(0, 0, 0, 1))) * b;
#endif
}
static inline void mul(vec4 &c, const mat4 &a, const vec4 &b)
{
#if defined(__SSE__)
__m128 a0 = _mm_loadu_ps(a[0].data);
__m128 a1 = _mm_loadu_ps(a[1].data);
__m128 a2 = _mm_loadu_ps(a[2].data);
__m128 a3 = _mm_loadu_ps(a[3].data);
__m128 b0 = _mm_loadu_ps(b.data);
__m128 b00 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(0, 0, 0, 0));
__m128 b01 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(1, 1, 1, 1));
__m128 b02 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(2, 2, 2, 2));
__m128 b03 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(3, 3, 3, 3));
__m128 col0 = _mm_mul_ps(a0, b00);
col0 = _mm_add_ps(col0, _mm_mul_ps(a1, b01));
col0 = _mm_add_ps(col0, _mm_mul_ps(a2, b02));
col0 = _mm_add_ps(col0, _mm_mul_ps(a3, b03));
_mm_storeu_ps(c.data, col0);
#elif defined(__ARM_NEON)
float32x4_t a0 = vld1q_f32(a[0].data);
float32x4_t a1 = vld1q_f32(a[1].data);
float32x4_t a2 = vld1q_f32(a[2].data);
float32x4_t a3 = vld1q_f32(a[3].data);
float32x4_t b0 = vld1q_f32(b.data);
float32x4_t col0 = vmulq_n_f32(a0, vgetq_lane_f32(b0, 0));
col0 = vmlaq_n_f32(col0, a1, vgetq_lane_f32(b0, 1));
col0 = vmlaq_n_f32(col0, a2, vgetq_lane_f32(b0, 2));
col0 = vmlaq_n_f32(col0, a3, vgetq_lane_f32(b0, 3));
vst1q_f32(c.data, col0);
#else
c = a * b;
#endif
}
static inline void mul(mat_affine &c, const mat_affine &a, const mat_affine &b)
{
#if defined(__SSE__)
// Swap the arguments to allow treating the multiplication as column-major.
__m128 a0 = _mm_loadu_ps(b[0].data);
__m128 a1 = _mm_loadu_ps(b[1].data);
__m128 a2 = _mm_loadu_ps(b[2].data);
__m128 b0 = _mm_loadu_ps(a[0].data);
__m128 b1 = _mm_loadu_ps(a[1].data);
__m128 b2 = _mm_loadu_ps(a[2].data);
const __m128 a3 = _mm_set_ps(1, 0, 0, 0);
__m128 b00 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(0, 0, 0, 0));
__m128 b01 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(1, 1, 1, 1));
__m128 b02 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(2, 2, 2, 2));
__m128 b03 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(3, 3, 3, 3));
__m128 col0 = _mm_mul_ps(a0, b00);
col0 = _mm_add_ps(col0, _mm_mul_ps(a1, b01));
col0 = _mm_add_ps(col0, _mm_mul_ps(a2, b02));
col0 = _mm_add_ps(col0, _mm_mul_ps(a3, b03));
__m128 b10 = _mm_shuffle_ps(b1, b1, _MM_SHUFFLE(0, 0, 0, 0));
__m128 b11 = _mm_shuffle_ps(b1, b1, _MM_SHUFFLE(1, 1, 1, 1));
__m128 b12 = _mm_shuffle_ps(b1, b1, _MM_SHUFFLE(2, 2, 2, 2));
__m128 b13 = _mm_shuffle_ps(b1, b1, _MM_SHUFFLE(3, 3, 3, 3));
__m128 col1 = _mm_mul_ps(a0, b10);
col1 = _mm_add_ps(col1, _mm_mul_ps(a1, b11));
col1 = _mm_add_ps(col1, _mm_mul_ps(a2, b12));
col1 = _mm_add_ps(col1, _mm_mul_ps(a3, b13));
__m128 b20 = _mm_shuffle_ps(b2, b2, _MM_SHUFFLE(0, 0, 0, 0));
__m128 b21 = _mm_shuffle_ps(b2, b2, _MM_SHUFFLE(1, 1, 1, 1));
__m128 b22 = _mm_shuffle_ps(b2, b2, _MM_SHUFFLE(2, 2, 2, 2));
__m128 b23 = _mm_shuffle_ps(b2, b2, _MM_SHUFFLE(3, 3, 3, 3));
__m128 col2 = _mm_mul_ps(a0, b20);
col2 = _mm_add_ps(col2, _mm_mul_ps(a1, b21));
col2 = _mm_add_ps(col2, _mm_mul_ps(a2, b22));
col2 = _mm_add_ps(col2, _mm_mul_ps(a3, b23));
_mm_storeu_ps(c[0].data, col0);
_mm_storeu_ps(c[1].data, col1);
_mm_storeu_ps(c[2].data, col2);
#elif defined(__ARM_NEON)
alignas(16) static const float a3_data[] = { 0, 0, 0, 1 };
float32x4_t a0 = vld1q_f32(b[0].data);
float32x4_t a1 = vld1q_f32(b[1].data);
float32x4_t a2 = vld1q_f32(b[2].data);
float32x4_t a3 = vld1q_f32(a3_data);
float32x4_t b0 = vld1q_f32(a[0].data);
float32x4_t b1 = vld1q_f32(a[1].data);
float32x4_t b2 = vld1q_f32(a[2].data);
float32x4_t col0 = vmulq_n_f32(a0, vgetq_lane_f32(b0, 0));
float32x4_t col1 = vmulq_n_f32(a0, vgetq_lane_f32(b1, 0));
float32x4_t col2 = vmulq_n_f32(a0, vgetq_lane_f32(b2, 0));
col0 = vmlaq_n_f32(col0, a1, vgetq_lane_f32(b0, 1));
col1 = vmlaq_n_f32(col1, a1, vgetq_lane_f32(b1, 1));
col2 = vmlaq_n_f32(col2, a1, vgetq_lane_f32(b2, 1));
col0 = vmlaq_n_f32(col0, a2, vgetq_lane_f32(b0, 2));
col1 = vmlaq_n_f32(col1, a2, vgetq_lane_f32(b1, 2));
col2 = vmlaq_n_f32(col2, a2, vgetq_lane_f32(b2, 2));
col0 = vmlaq_n_f32(col0, a3, vgetq_lane_f32(b0, 3));
col1 = vmlaq_n_f32(col1, a3, vgetq_lane_f32(b1, 3));
col2 = vmlaq_n_f32(col2, a3, vgetq_lane_f32(b2, 3));
vst1q_f32(c[0].data, col0);
vst1q_f32(c[1].data, col1);
vst1q_f32(c[2].data, col2);
#else
mat4 a4(a[0], a[1], a[2], vec4(0.0f, 0.0f, 0.0f, 1.0f));
mat4 b4(b[0], b[1], b[2], vec4(0.0f, 0.0f, 0.0f, 1.0f));
mat4 c4 = b4 * a4;
for (int i = 0; i < 3; i++)
c[i] = c4[i];
#endif
}
static inline void mul(mat4 &c, const mat4 &a, const mat4 &b)
{
#if defined(__SSE__)
__m128 a0 = _mm_loadu_ps(a[0].data);
__m128 a1 = _mm_loadu_ps(a[1].data);
__m128 a2 = _mm_loadu_ps(a[2].data);
__m128 a3 = _mm_loadu_ps(a[3].data);
__m128 b0 = _mm_loadu_ps(b[0].data);
__m128 b1 = _mm_loadu_ps(b[1].data);
__m128 b2 = _mm_loadu_ps(b[2].data);
__m128 b3 = _mm_loadu_ps(b[3].data);
__m128 b00 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(0, 0, 0, 0));
__m128 b01 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(1, 1, 1, 1));
__m128 b02 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(2, 2, 2, 2));
__m128 b03 = _mm_shuffle_ps(b0, b0, _MM_SHUFFLE(3, 3, 3, 3));
__m128 col0 = _mm_mul_ps(a0, b00);
col0 = _mm_add_ps(col0, _mm_mul_ps(a1, b01));
col0 = _mm_add_ps(col0, _mm_mul_ps(a2, b02));
col0 = _mm_add_ps(col0, _mm_mul_ps(a3, b03));
__m128 b10 = _mm_shuffle_ps(b1, b1, _MM_SHUFFLE(0, 0, 0, 0));
__m128 b11 = _mm_shuffle_ps(b1, b1, _MM_SHUFFLE(1, 1, 1, 1));
__m128 b12 = _mm_shuffle_ps(b1, b1, _MM_SHUFFLE(2, 2, 2, 2));
__m128 b13 = _mm_shuffle_ps(b1, b1, _MM_SHUFFLE(3, 3, 3, 3));
__m128 col1 = _mm_mul_ps(a0, b10);
col1 = _mm_add_ps(col1, _mm_mul_ps(a1, b11));
col1 = _mm_add_ps(col1, _mm_mul_ps(a2, b12));
col1 = _mm_add_ps(col1, _mm_mul_ps(a3, b13));
__m128 b20 = _mm_shuffle_ps(b2, b2, _MM_SHUFFLE(0, 0, 0, 0));
__m128 b21 = _mm_shuffle_ps(b2, b2, _MM_SHUFFLE(1, 1, 1, 1));
__m128 b22 = _mm_shuffle_ps(b2, b2, _MM_SHUFFLE(2, 2, 2, 2));
__m128 b23 = _mm_shuffle_ps(b2, b2, _MM_SHUFFLE(3, 3, 3, 3));
__m128 col2 = _mm_mul_ps(a0, b20);
col2 = _mm_add_ps(col2, _mm_mul_ps(a1, b21));
col2 = _mm_add_ps(col2, _mm_mul_ps(a2, b22));
col2 = _mm_add_ps(col2, _mm_mul_ps(a3, b23));
__m128 b30 = _mm_shuffle_ps(b3, b3, _MM_SHUFFLE(0, 0, 0, 0));
__m128 b31 = _mm_shuffle_ps(b3, b3, _MM_SHUFFLE(1, 1, 1, 1));
__m128 b32 = _mm_shuffle_ps(b3, b3, _MM_SHUFFLE(2, 2, 2, 2));
__m128 b33 = _mm_shuffle_ps(b3, b3, _MM_SHUFFLE(3, 3, 3, 3));
__m128 col3 = _mm_mul_ps(a0, b30);
col3 = _mm_add_ps(col3, _mm_mul_ps(a1, b31));
col3 = _mm_add_ps(col3, _mm_mul_ps(a2, b32));
col3 = _mm_add_ps(col3, _mm_mul_ps(a3, b33));
_mm_storeu_ps(c[0].data, col0);
_mm_storeu_ps(c[1].data, col1);
_mm_storeu_ps(c[2].data, col2);
_mm_storeu_ps(c[3].data, col3);
#elif defined(__ARM_NEON)
float32x4_t a0 = vld1q_f32(a[0].data);
float32x4_t a1 = vld1q_f32(a[1].data);
float32x4_t a2 = vld1q_f32(a[2].data);
float32x4_t a3 = vld1q_f32(a[3].data);
float32x4_t b0 = vld1q_f32(b[0].data);
float32x4_t b1 = vld1q_f32(b[1].data);
float32x4_t b2 = vld1q_f32(b[2].data);
float32x4_t b3 = vld1q_f32(b[3].data);
float32x4_t col0 = vmulq_n_f32(a0, vgetq_lane_f32(b0, 0));
float32x4_t col1 = vmulq_n_f32(a0, vgetq_lane_f32(b1, 0));
float32x4_t col2 = vmulq_n_f32(a0, vgetq_lane_f32(b2, 0));
float32x4_t col3 = vmulq_n_f32(a0, vgetq_lane_f32(b3, 0));
col0 = vmlaq_n_f32(col0, a1, vgetq_lane_f32(b0, 1));
col1 = vmlaq_n_f32(col1, a1, vgetq_lane_f32(b1, 1));
col2 = vmlaq_n_f32(col2, a1, vgetq_lane_f32(b2, 1));
col3 = vmlaq_n_f32(col3, a1, vgetq_lane_f32(b3, 1));
col0 = vmlaq_n_f32(col0, a2, vgetq_lane_f32(b0, 2));
col1 = vmlaq_n_f32(col1, a2, vgetq_lane_f32(b1, 2));
col2 = vmlaq_n_f32(col2, a2, vgetq_lane_f32(b2, 2));
col3 = vmlaq_n_f32(col3, a2, vgetq_lane_f32(b3, 2));
col0 = vmlaq_n_f32(col0, a3, vgetq_lane_f32(b0, 3));
col1 = vmlaq_n_f32(col1, a3, vgetq_lane_f32(b1, 3));
col2 = vmlaq_n_f32(col2, a3, vgetq_lane_f32(b2, 3));
col3 = vmlaq_n_f32(col3, a3, vgetq_lane_f32(b3, 3));
vst1q_f32(c[0].data, col0);
vst1q_f32(c[1].data, col1);
vst1q_f32(c[2].data, col2);
vst1q_f32(c[3].data, col3);
#else
c = a * b;
#endif
}
static inline void transform_aabb(AABB &output, const AABB &aabb, const mat_affine &m)
{
#if defined(__SSE__)
__m128 lo = _mm_loadu_ps(aabb.get_minimum4().data);
__m128 hi = _mm_loadu_ps(aabb.get_maximum4().data);
__m128 m0 = _mm_loadu_ps(m[0].data);
__m128 m1 = _mm_loadu_ps(m[1].data);
__m128 m2 = _mm_loadu_ps(m[2].data);
__m128 m3 = _mm_set_ps(1, 0, 0, 0);
_MM_TRANSPOSE4_PS(m0, m1, m2, m3);
__m128 m0_pos = _mm_cmpgt_ps(m0, _mm_setzero_ps());
__m128 m1_pos = _mm_cmpgt_ps(m1, _mm_setzero_ps());
__m128 m2_pos = _mm_cmpgt_ps(m2, _mm_setzero_ps());
__m128 hi0 = _mm_shuffle_ps(hi, hi, _MM_SHUFFLE(0, 0, 0, 0));
__m128 hi1 = _mm_shuffle_ps(hi, hi, _MM_SHUFFLE(1, 1, 1, 1));
__m128 hi2 = _mm_shuffle_ps(hi, hi, _MM_SHUFFLE(2, 2, 2, 2));
__m128 lo0 = _mm_shuffle_ps(lo, lo, _MM_SHUFFLE(0, 0, 0, 0));
__m128 lo1 = _mm_shuffle_ps(lo, lo, _MM_SHUFFLE(1, 1, 1, 1));
__m128 lo2 = _mm_shuffle_ps(lo, lo, _MM_SHUFFLE(2, 2, 2, 2));
__m128 hi_result = m3;
hi_result = _mm_add_ps(hi_result, _mm_mul_ps(m0, _mm_or_ps(_mm_and_ps(m0_pos, hi0), _mm_andnot_ps(m0_pos, lo0))));
hi_result = _mm_add_ps(hi_result, _mm_mul_ps(m1, _mm_or_ps(_mm_and_ps(m1_pos, hi1), _mm_andnot_ps(m1_pos, lo1))));
hi_result = _mm_add_ps(hi_result, _mm_mul_ps(m2, _mm_or_ps(_mm_and_ps(m2_pos, hi2), _mm_andnot_ps(m2_pos, lo2))));
__m128 lo_result = m3;
lo_result = _mm_add_ps(lo_result, _mm_mul_ps(m0, _mm_or_ps(_mm_andnot_ps(m0_pos, hi0), _mm_and_ps(m0_pos, lo0))));
lo_result = _mm_add_ps(lo_result, _mm_mul_ps(m1, _mm_or_ps(_mm_andnot_ps(m1_pos, hi1), _mm_and_ps(m1_pos, lo1))));
lo_result = _mm_add_ps(lo_result, _mm_mul_ps(m2, _mm_or_ps(_mm_andnot_ps(m2_pos, hi2), _mm_and_ps(m2_pos, lo2))));
_mm_storeu_ps(output.get_minimum4().data, lo_result);
_mm_storeu_ps(output.get_maximum4().data, hi_result);
#elif defined(__aarch64__)
alignas(16) static const float m3_data[] = { 0, 0, 0, 1 };
float32x4_t lo = vld1q_f32(aabb.get_minimum4().data);
float32x4_t hi = vld1q_f32(aabb.get_maximum4().data);
float32x4_t m0 = vld1q_f32(m[0].data);
float32x4_t m1 = vld1q_f32(m[1].data);
float32x4_t m2 = vld1q_f32(m[2].data);
float32x4_t m3 = vld1q_f32(m3_data);
// From sse2neon.h
float64x2_t r0 = (float64x2_t)vtrn1q_f32(m0, m1);
float64x2_t r1 = (float64x2_t)vtrn2q_f32(m0, m1);
float64x2_t r2 = (float64x2_t)vtrn1q_f32(m2, m3);
float64x2_t r3 = (float64x2_t)vtrn2q_f32(m2, m3);
m0 = (float32x4_t)vtrn1q_f64(r0, r2);
m1 = (float32x4_t)vtrn1q_f64(r1, r3);
m2 = (float32x4_t)vtrn2q_f64(r0, r2);
m3 = (float32x4_t)vtrn2q_f64(r1, r3);
uint32x4_t m0_pos = vcgtq_f32(m0, vdupq_n_f32(0.0f));
uint32x4_t m1_pos = vcgtq_f32(m1, vdupq_n_f32(0.0f));
uint32x4_t m2_pos = vcgtq_f32(m2, vdupq_n_f32(0.0f));
float32x4_t lo0 = vdupq_lane_f32(vget_low_f32(lo), 0);
float32x4_t lo1 = vdupq_lane_f32(vget_low_f32(lo), 1);
float32x4_t lo2 = vdupq_lane_f32(vget_high_f32(lo), 0);
float32x4_t hi0 = vdupq_lane_f32(vget_low_f32(hi), 0);
float32x4_t hi1 = vdupq_lane_f32(vget_low_f32(hi), 1);
float32x4_t hi2 = vdupq_lane_f32(vget_high_f32(hi), 0);
float32x4_t hi_result = m3;
hi_result = vmlaq_f32(hi_result, m0, vbslq_f32(m0_pos, hi0, lo0));
hi_result = vmlaq_f32(hi_result, m1, vbslq_f32(m1_pos, hi1, lo1));
hi_result = vmlaq_f32(hi_result, m2, vbslq_f32(m2_pos, hi2, lo2));
float32x4_t lo_result = m3;
lo_result = vmlaq_f32(lo_result, m0, vbslq_f32(m0_pos, lo0, hi0));
lo_result = vmlaq_f32(lo_result, m1, vbslq_f32(m1_pos, lo1, hi1));
lo_result = vmlaq_f32(lo_result, m2, vbslq_f32(m2_pos, lo2, hi2));
vst1q_f32(output.get_minimum4().data, lo_result);
vst1q_f32(output.get_maximum4().data, hi_result);
#else
output = aabb.transform(transpose(mat4(m[0], m[1], m[2], vec4(0, 0, 0, 1))));
#endif
}
static inline void transform_aabb(AABB &output, const AABB &aabb, const mat4 &m)
{
#if defined(__SSE__)
__m128 lo = _mm_loadu_ps(aabb.get_minimum4().data);
__m128 hi = _mm_loadu_ps(aabb.get_maximum4().data);
__m128 m0 = _mm_loadu_ps(m[0].data);
__m128 m1 = _mm_loadu_ps(m[1].data);
__m128 m2 = _mm_loadu_ps(m[2].data);
__m128 m3 = _mm_loadu_ps(m[3].data);
__m128 m0_pos = _mm_cmpgt_ps(m0, _mm_setzero_ps());
__m128 m1_pos = _mm_cmpgt_ps(m1, _mm_setzero_ps());
__m128 m2_pos = _mm_cmpgt_ps(m2, _mm_setzero_ps());
__m128 hi0 = _mm_shuffle_ps(hi, hi, _MM_SHUFFLE(0, 0, 0, 0));
__m128 hi1 = _mm_shuffle_ps(hi, hi, _MM_SHUFFLE(1, 1, 1, 1));
__m128 hi2 = _mm_shuffle_ps(hi, hi, _MM_SHUFFLE(2, 2, 2, 2));
__m128 lo0 = _mm_shuffle_ps(lo, lo, _MM_SHUFFLE(0, 0, 0, 0));
__m128 lo1 = _mm_shuffle_ps(lo, lo, _MM_SHUFFLE(1, 1, 1, 1));
__m128 lo2 = _mm_shuffle_ps(lo, lo, _MM_SHUFFLE(2, 2, 2, 2));
__m128 hi_result = m3;
hi_result = _mm_add_ps(hi_result, _mm_mul_ps(m0, _mm_or_ps(_mm_and_ps(m0_pos, hi0), _mm_andnot_ps(m0_pos, lo0))));
hi_result = _mm_add_ps(hi_result, _mm_mul_ps(m1, _mm_or_ps(_mm_and_ps(m1_pos, hi1), _mm_andnot_ps(m1_pos, lo1))));
hi_result = _mm_add_ps(hi_result, _mm_mul_ps(m2, _mm_or_ps(_mm_and_ps(m2_pos, hi2), _mm_andnot_ps(m2_pos, lo2))));
__m128 lo_result = m3;
lo_result = _mm_add_ps(lo_result, _mm_mul_ps(m0, _mm_or_ps(_mm_andnot_ps(m0_pos, hi0), _mm_and_ps(m0_pos, lo0))));
lo_result = _mm_add_ps(lo_result, _mm_mul_ps(m1, _mm_or_ps(_mm_andnot_ps(m1_pos, hi1), _mm_and_ps(m1_pos, lo1))));
lo_result = _mm_add_ps(lo_result, _mm_mul_ps(m2, _mm_or_ps(_mm_andnot_ps(m2_pos, hi2), _mm_and_ps(m2_pos, lo2))));
_mm_storeu_ps(output.get_minimum4().data, lo_result);
_mm_storeu_ps(output.get_maximum4().data, hi_result);
#elif defined(__ARM_NEON)
float32x4_t lo = vld1q_f32(aabb.get_minimum4().data);
float32x4_t hi = vld1q_f32(aabb.get_maximum4().data);
float32x4_t m0 = vld1q_f32(m[0].data);
float32x4_t m1 = vld1q_f32(m[1].data);
float32x4_t m2 = vld1q_f32(m[2].data);
float32x4_t m3 = vld1q_f32(m[3].data);
uint32x4_t m0_pos = vcgtq_f32(m0, vdupq_n_f32(0.0f));
uint32x4_t m1_pos = vcgtq_f32(m1, vdupq_n_f32(0.0f));
uint32x4_t m2_pos = vcgtq_f32(m2, vdupq_n_f32(0.0f));
float32x4_t lo0 = vdupq_lane_f32(vget_low_f32(lo), 0);
float32x4_t lo1 = vdupq_lane_f32(vget_low_f32(lo), 1);
float32x4_t lo2 = vdupq_lane_f32(vget_high_f32(lo), 0);
float32x4_t hi0 = vdupq_lane_f32(vget_low_f32(hi), 0);
float32x4_t hi1 = vdupq_lane_f32(vget_low_f32(hi), 1);
float32x4_t hi2 = vdupq_lane_f32(vget_high_f32(hi), 0);
float32x4_t hi_result = m3;
hi_result = vmlaq_f32(hi_result, m0, vbslq_f32(m0_pos, hi0, lo0));
hi_result = vmlaq_f32(hi_result, m1, vbslq_f32(m1_pos, hi1, lo1));
hi_result = vmlaq_f32(hi_result, m2, vbslq_f32(m2_pos, hi2, lo2));
float32x4_t lo_result = m3;
lo_result = vmlaq_f32(lo_result, m0, vbslq_f32(m0_pos, lo0, hi0));
lo_result = vmlaq_f32(lo_result, m1, vbslq_f32(m1_pos, lo1, hi1));
lo_result = vmlaq_f32(lo_result, m2, vbslq_f32(m2_pos, lo2, hi2));
vst1q_f32(output.get_minimum4().data, lo_result);
vst1q_f32(output.get_maximum4().data, hi_result);
#else
output = aabb.transform(m);
#endif
}
template <typename T>
static inline void transform_and_expand_aabb(AABB &expandee, const AABB &aabb, const T &m)
{
alignas(16) AABB tmp;
transform_aabb(tmp, aabb, m);
#if defined(__SSE__)
__m128 lo = _mm_min_ps(_mm_load_ps(tmp.get_minimum4().data), _mm_loadu_ps(expandee.get_minimum4().data));
__m128 hi = _mm_max_ps(_mm_load_ps(tmp.get_maximum4().data), _mm_loadu_ps(expandee.get_maximum4().data));
_mm_storeu_ps(expandee.get_minimum4().data, lo);
_mm_storeu_ps(expandee.get_maximum4().data, hi);
#elif defined(__ARM_NEON)
float32x4_t lo = vminq_f32(vld1q_f32(tmp.get_minimum4().data), vld1q_f32(expandee.get_minimum4().data));
float32x4_t hi = vmaxq_f32(vld1q_f32(tmp.get_maximum4().data), vld1q_f32(expandee.get_maximum4().data));
vst1q_f32(expandee.get_minimum4().data, lo);
vst1q_f32(expandee.get_maximum4().data, hi);
#else
auto &output_min = expandee.get_minimum4();
auto &output_max = expandee.get_maximum4();
output_min = min<vec4>(output_min, tmp.get_minimum4());
output_max = max<vec4>(output_max, tmp.get_maximum4());
#endif
}
static inline void convert_quaternion_with_scale(vec4 *cols, const quat &q, const vec3 &scale)
{
#if defined(__SSE3__)
__m128 quat = _mm_loadu_ps(q.as_vec4().data);
#define SHUF(x, y, z) _mm_shuffle_ps(quat, quat, _MM_SHUFFLE(z, y, x, 3))
__m128 q_yy_xz_xy = _mm_mul_ps(SHUF(1, 0, 0), SHUF(1, 2, 1));
__m128 q_zz_wy_wz = _mm_mul_ps(SHUF(2, 3, 3), SHUF(2, 1, 2));
__m128 col0 = _mm_mul_ps(_mm_set_ps(+2.0f, +2.0f, -2.0f, 0.0f), _mm_addsub_ps(q_yy_xz_xy, q_zz_wy_wz));
col0 = _mm_shuffle_ps(col0, col0, _MM_SHUFFLE(0, 2, 3, 1));
col0 = _mm_add_ps(col0, _mm_set_ss(1.0f));
col0 = _mm_mul_ps(col0, _mm_set1_ps(scale.x));
_mm_storeu_ps(cols[0].data, col0);
__m128 q_xx_xy_yz = _mm_mul_ps(SHUF(0, 0, 1), SHUF(0, 1, 2));
__m128 q_zz_wz_wx = _mm_mul_ps(SHUF(2, 3, 3), SHUF(2, 2, 0));
__m128 col1 = _mm_mul_ps(_mm_set_ps(2.0f, 2.0f, -2.0f, 0.0f), _mm_addsub_ps(q_xx_xy_yz, q_zz_wz_wx));
col1 = _mm_shuffle_ps(col1, col1, _MM_SHUFFLE(0, 3, 1, 2));
col1 = _mm_add_ps(col1, _mm_set_ps(0.0f, 0.0f, 1.0f, 0.0f));
col1 = _mm_mul_ps(col1, _mm_set1_ps(scale.y));
_mm_storeu_ps(cols[1].data, col1);
__m128 q_xz_yz_xx = _mm_mul_ps(SHUF(0, 1, 0), SHUF(2, 2, 0));
__m128 q_wy_wx_yy = _mm_mul_ps(SHUF(3, 3, 1), SHUF(1, 0, 1));
__m128 col2 = _mm_mul_ps(_mm_set_ps(-2.0f, 2.0f, 2.0f, 0.0f), _mm_addsub_ps(q_xz_yz_xx, q_wy_wx_yy));
col2 = _mm_shuffle_ps(col2, col2, _MM_SHUFFLE(0, 3, 2, 1));
col2 = _mm_add_ps(col2, _mm_set_ps(0.0f, 1.0f, 0.0f, 0.0f));
col2 = _mm_mul_ps(col2, _mm_set1_ps(scale.z));
_mm_storeu_ps(cols[2].data, col2);
#undef SHUF
#else
mat3 m = muglm::mat3_cast(q);
cols[0] = vec4(m[0] * scale.x, 0.0f);
cols[1] = vec4(m[1] * scale.y, 0.0f);
cols[2] = vec4(m[2] * scale.z, 0.0f);
#endif
}
}
}
@@ -0,0 +1,48 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#ifdef _MSC_VER
#include <intrin.h>
#if defined(_INCLUDED_PMM) && !defined(__SSE3__)
#define __SSE3__ 1
#endif
#if !defined(__SSE__)
#define __SSE__ 1
#endif
#if defined(_INCLUDED_IMM) && !defined(__AVX__)
#define __AVX__ 1
#endif
#elif defined(__AVX__)
#include <immintrin.h>
#elif defined(__SSE4_1__)
#include <smmintrin.h>
#elif defined(__SSE3__)
#include <pmmintrin.h>
#elif defined(__SSE__)
#include <xmmintrin.h>
#elif defined(__ARM_NEON)
#include <arm_neon.h>
#endif
@@ -0,0 +1,371 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "transforms.hpp"
#include "aabb.hpp"
#include "simd.hpp"
#include "muglm/matrix_helper.hpp"
#include <assert.h>
namespace Granite
{
bool compute_plane_reflection(mat4 &projection, mat4 &view, vec3 camera_pos, vec3 center, vec3 normal, vec3 look_up,
float radius_up, float radius_other, float &z_near, float z_far)
{
normal = normalize(normal);
// Reflect the camera position from the plane.
float over_plane = dot(normal, camera_pos - center);
if (over_plane <= 0.0f)
return false;
camera_pos -= 2.0f * over_plane * normal;
// The look direction is up through the plane direction.
// This way we avoid skewed near and far planes (i.e. oblique).
// Make sure look_up is perpendicular to normal.
vec3 look_pos_x = normalize(cross(normal, look_up));
look_up = normalize(cross(look_pos_x, normal));
view = mat4_cast(look_at(normal, look_up)) * translate(-camera_pos);
float dist_x = dot(look_pos_x, center - camera_pos);
float left = dist_x - radius_other;
float right = dist_x + radius_other;
float dist_y = dot(look_up, center - camera_pos);
float bottom = dist_y - radius_up;
float top = dist_y + radius_up;
z_near = over_plane;
projection = frustum(left, right, bottom, top, over_plane, z_far);
if (z_near >= z_far)
return false;
return true;
}
bool compute_plane_refraction(mat4 &projection, mat4 &view, vec3 camera_pos, vec3 center, vec3 normal, vec3 look_up,
float radius_up, float radius_other, float &z_near, float z_far)
{
normal = normalize(normal);
// Reflect the camera position from the plane.
float over_plane = dot(normal, camera_pos - center);
if (over_plane <= 0.0f)
return false;
normal = -normal;
// The look direction is up through the plane direction.
// This way we avoid skewed near and far planes (i.e. oblique).
// Make sure look_up is perpendicular to normal.
vec3 look_pos_x = normalize(cross(normal, look_up));
look_up = normalize(cross(look_pos_x, normal));
view = mat4_cast(look_at(normal, look_up)) * translate(-camera_pos);
float dist_x = dot(look_pos_x, center - camera_pos);
float left = dist_x - radius_other;
float right = dist_x + radius_other;
float dist_y = dot(look_up, center - camera_pos);
float bottom = dist_y - radius_up;
float top = dist_y + radius_up;
z_near = over_plane;
projection = frustum(left, right, bottom, top, over_plane, z_far);
if (z_near >= z_far)
return false;
return true;
}
void compute_model_transform(mat_affine &world, vec3 s, quat rot, vec3 trans, const mat_affine &parent)
{
// TODO: Make this more affine friendly.
mat4 model;
model[3] = vec4(trans, 1.0f);
SIMD::convert_quaternion_with_scale(&model[0], rot, s);
SIMD::mul(world, parent, mat_affine(model));
}
void compute_normal_transform(mat4 &normal, const mat4 &world)
{
normal = mat4(transpose(inverse(mat3(world))));
}
void compute_normal_transform(mat_affine &normal, const mat_affine &world)
{
// Can be done better, but not important unless it gets used a lot.
normal = mat_affine(mat4(transpose(inverse(world.to_mat3()))));
}
quat rotate_vector(vec3 from, vec3 to)
{
from = normalize(from);
to = normalize(to);
float cos_angle = dot(from, to);
if (abs(cos_angle) > 0.9999f)
{
if (cos_angle > 0.9999f)
return quat(1.0f, 0.0f, 0.0f, 0.0f);
else
{
vec3 rotation = cross(vec3(1.0f, 0.0f, 0.0f), from);
if (dot(rotation, rotation) > 0.001f)
rotation = normalize(rotation);
else
rotation = normalize(cross(vec3(0.0f, 1.0f, 0.0f), from));
return quat(0.0f, rotation);
}
}
vec3 rotation = normalize(cross(from, to));
vec3 half_vector = normalize(from + to);
float cos_half_range = clamp(dot(half_vector, from), 0.0f, 1.0f);
float sin_half_angle = sqrtf(1.0f - cos_half_range * cos_half_range);
return quat(cos_half_range, rotation * sin_half_angle);
}
quat rotate_vector_axis(vec3 from, vec3 to, vec3 axis)
{
axis = normalize(axis);
from = normalize(cross(axis, from));
to = normalize(cross(axis, to));
if (dot(to, from) < -0.9999f)
return quat(0.0f, axis);
// Rotate CCW or CW, we only find the angle of rotation below.
float quat_sign = sign(dot(axis, cross(from, to)));
vec3 half_vector = normalize(from + to);
float cos_half_range = clamp(dot(half_vector, from), 0.0f, 1.0f);
float sin_half_angle = quat_sign * sqrtf(1.0f - cos_half_range * cos_half_range);
return quat(cos_half_range, axis * sin_half_angle);
}
quat look_at(vec3 direction, vec3 up)
{
static const vec3 z(0.0f, 0.0f, -1.0f);
static const vec3 y(0.0f, 1.0f, 0.0f);
direction = normalize(direction);
vec3 right = cross(direction, up);
vec3 actual_up = cross(right, direction);
quat look_transform = rotate_vector(direction, z);
quat up_transform = rotate_vector_axis(look_transform * actual_up, y, z);
return up_transform * look_transform;
}
quat look_at_arbitrary_up(vec3 direction)
{
return rotate_vector(normalize(direction), vec3(0.0f, 0.0f, -1.0f));
}
mat4 projection(float fovy, float aspect, float znear, float zfar)
{
return perspective(fovy, aspect, znear, zfar);
}
mat4 ortho(const AABB &aabb)
{
vec3 min = aabb.get_minimum();
vec3 max = aabb.get_maximum();
// Flip Z for RH, ortho zNear/zFar is LH style.
std::swap(max.z, min.z);
max.z = -max.z;
min.z = -min.z;
return muglm::ortho(min.x, max.x, min.y, max.y, min.z, max.z);
}
void compute_cube_render_transform(vec3 center, unsigned face, mat4 &proj, mat4 &view, float znear, float zfar)
{
static const vec3 dirs[6] = {
vec3(1.0f, 0.0f, 0.0f),
vec3(-1.0f, 0.0f, 0.0f),
vec3(0.0f, 1.0f, 0.0f),
vec3(0.0f, -1.0f, 0.0f),
vec3(0.0f, 0.0f, 1.0f),
vec3(0.0f, 0.0f, -1.0f),
};
static const vec3 ups[6] = {
vec3(0.0f, 1.0f, 0.0f),
vec3(0.0f, 1.0f, 0.0f),
vec3(0.0f, 0.0f, -1.0f),
vec3(0.0f, 0.0f, +1.0f),
vec3(0.0f, 1.0f, 0.0f),
vec3(0.0f, 1.0f, 0.0f),
};
view = mat4_cast(look_at(dirs[face], ups[face])) * translate(-center);
proj = scale(vec3(-1.0f, 1.0f, 1.0f)) * projection(0.5f * pi<float>(), 1.0f, znear, zfar);
}
vec3 PositionalSampler::sample(unsigned index, float l) const
{
if (l == 0.0f)
return values[index];
else if (l == 1.0f)
return values[index + 1];
assert(index + 1 < values.size());
return mix(values[index], values[index + 1], l);
}
template <typename T>
static T compute_cubic_spline(const std::vector<T> &values, unsigned index, float t, float dt)
{
assert(3 * index + 4 < values.size());
T p0 = values[3 * index + 1];
T p1 = values[3 * index + 4];
// For t == 0.0f, the result must be exactly on the point as specified by glTF.
if (t == 0.0f)
return p0;
else if (t == 1.0f)
return p1;
T m0 = dt * values[3 * index + 2];
T m1 = dt * values[3 * index + 3];
float t2 = t * t;
float t3 = t2 * t;
return (2.0f * t3 - 3.0f * t2 + 1.0f) * p0 +
(t3 - 2.0f * t2 + t) * m0 +
(-2.0f * t3 + 3.0f * t2) * p1 +
(t3 - t2) * m1;
}
vec3 PositionalSampler::sample_spline(unsigned index, float t, float dt) const
{
return compute_cubic_spline(values, index, t, dt);
}
quat SphericalSampler::sample(unsigned index, float l) const
{
if (l == 0.0f)
return quat(values[index]);
else if (l == 1.0f)
return quat(values[index + 1]);
assert(index + 1 < values.size());
return slerp(quat(values[index]), quat(values[index + 1]), l);
}
quat SphericalSampler::sample_spline(unsigned index, float t, float dt) const
{
// CUBICSPLINE for quaternion is defined as simple vec4 interpolation with normalization.
return normalize(quat(compute_cubic_spline(values, index, t, dt)));
}
// See math/docs/squad.md for more detail and derivation.
quat SphericalSampler::sample_squad(unsigned index, float l) const
{
assert(3 * index + 4 < values.size());
if (l == 0.0f)
return quat(values[3 * index + 1]);
else if (l == 1.0f)
return quat(values[3 * index + 4]);
quat q0 = quat(values[3 * index + 1]);
quat cp0 = quat(values[3 * index + 2]);
quat cp1 = quat(values[3 * index + 3]);
quat q1 = quat(values[3 * index + 4]);
return slerp_no_invert(slerp_no_invert(q0, q1, l), slerp_no_invert(cp0, cp1, l), 2.0f * l * (1.0f - l));
}
quat compute_inner_control_point(const quat &q, const vec3 &delta)
{
return q * quat_exp(-delta);
}
vec3 compute_inner_control_point_delta(const quat &q0, const quat &q1, const quat &q2,
float dt0, float dt1)
{
// This is almost gibberish, as this is just copy-pastaed from various implementations
// found on the interwebs.
// From studying it in greater detail,
// the basic gist is that quaternion log and exp are used to
// decompose what should be a series of multiplications (quat rotations) into additions, since
// ln(a * b) = ln(a) + ln(b), and exp(ln(a)) = a.
// ln(q) means encoding a vec3 where the length encodes theta, and direction encodes direction.
// Summing ln(a) + ln(b) will therefore "add" the addition together, similar to how one
// would add torque vectors in physics. The exp must then re-encode the vector-magnitude encoding
// back to normal quaternion form.
// In this domain we can average rotations, and go back again to a normal quaternion with exp.
// inv_q1 * q2 and inv_q1 * q0 both do some form of "differential" of the rotations.
// q12 and q10 estimate first derivative at the control points.
// q12 and q10 have opposing signs,
// so the sum of the logs is therefore seen as instantaneous acceleration at the q1.
// quat_log() breaks down if q.w goes negative it seems, so that explains some shenanigans
// where some docs say that this only works for "normal" interpolation scenarios.
// Probably more than good enough for us though.
// Weigh the deltas so that they compute absolute velocity and acceleration.
// Rescale back to spline time domain after.
quat inv_q1 = conjugate(q1);
quat delta_k = inv_q1 * q2; // q2 - q1
quat delta_k_minus1 = inv_q1 * q0; // q0 - q1 = -(q1 - q0)
vec3 delta_k_log = quat_log(delta_k);
vec3 delta_k_minus1_log = quat_log(delta_k_minus1);
// We sample velocity at the center of the segment when taking the difference.
// Future sample is at t = +1/2 dt
// Past sample is at t = -1/2 dt
float segment_time = 0.5f * (dt0 + dt1);
vec3 absolute_accel = (delta_k_log / dt1 + delta_k_minus1_log / dt0) / segment_time;
vec3 delta = (0.25f * dt1 * dt1) * absolute_accel;
return delta;
}
// From https://mina86.com/2019/srgb-xyz-matrix/
static vec3 convert_primary(const vec2 &xy)
{
float X = xy.x / xy.y;
float Y = 1.0f;
float Z = (1.0f - xy.x - xy.y) / xy.y;
return vec3(X, Y, Z);
}
mat3 compute_xyz_matrix(const Primaries &primaries)
{
vec3 red = convert_primary(primaries.red);
vec3 green = convert_primary(primaries.green);
vec3 blue = convert_primary(primaries.blue);
vec3 white = convert_primary(primaries.white_point);
vec3 component_scale = inverse(mat3(red, green, blue)) * white;
return mat3(red * component_scale.x, green * component_scale.y, blue * component_scale.z);
}
}
@@ -0,0 +1,85 @@
/* Copyright (c) 2017-2026 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "math.hpp"
#include <vector>
namespace Granite
{
class AABB;
bool compute_plane_reflection(mat4 &projection, mat4 &view, vec3 camera_pos, vec3 center, vec3 normal, vec3 look_up,
float radius_up, float radius_other, float &z_near, float z_far);
bool compute_plane_refraction(mat4 &projection, mat4 &view, vec3 camera_pos, vec3 center, vec3 normal, vec3 look_up,
float radius_up, float radius_other, float &z_near, float z_far);
void compute_model_transform(mat_affine &world, vec3 scale, quat rotation, vec3 translation, const mat_affine &parent);
void compute_normal_transform(mat4 &normal, const mat4 &world);
void compute_normal_transform(mat_affine &normal, const mat_affine &world);
quat rotate_vector(vec3 from, vec3 to);
quat look_at(vec3 direction, vec3 up);
quat look_at_arbitrary_up(vec3 direction);
quat rotate_vector_axis(vec3 from, vec3 to, vec3 axis);
mat4 projection(float fovy, float aspect, float znear, float zfar);
mat4 ortho(const AABB &aabb);
void compute_cube_render_transform(vec3 center, unsigned face, mat4 &projection, mat4 &view, float znear, float zfar);
struct PositionalSampler
{
std::vector<vec3> values;
vec3 sample(unsigned index, float l) const;
vec3 sample_spline(unsigned index, float l, float dt) const;
};
struct SphericalSampler
{
std::vector<vec4> values;
quat sample(unsigned index, float l) const;
quat sample_spline(unsigned index, float l, float dt) const;
quat sample_squad(unsigned index, float l) const;
};
// Compute control points for q1.
// dt0 is delta time between q0 and q1.
// dt1 is delta time between q1 and q2.
vec3 compute_inner_control_point_delta(const quat &q0, const quat &q1, const quat &q2,
float dt0, float dt1);
quat compute_inner_control_point(const quat &q, const vec3 &delta);
struct Primaries
{
vec2 red, green, blue, white_point;
};
mat3 compute_xyz_matrix(const Primaries &primaries);
}