Skip to content

Conversation

@keptsecret
Copy link
Contributor

No description provided.

assert(hlsl::length(start.data) == scalar_type(1.0));
assert(hlsl::length(end.data) == scalar_type(1.0));
// TODO: benchmark uint sign flip vs just *sign(totalPseudoAngle)
const data_type adjEnd = ieee754::flipSignIfRHSNegative<data_type>(end.data, hlsl::promote<data_type>(totalPseudoAngle));
Copy link
Member

@devshgraphicsprogramming devshgraphicsprogramming Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, the promotion makes this expensive, you need to change the signature to

template <typename T, typename U=T>
NBL_CONSTEXPR_FUNC T flipSignIfRHSNegative(T val, U flip)

and then add a partial specialization of flipSignIfRHSNegative_helper<Vectorial,typename vertor_traits<Vectorial>::scalar_type>

which does

		using traits_v = hlsl::vector_traits<Vectorial>;
		using scalar_t = typename traits_v::scalar_type;

		using AsUint = typename unsigned_integer_of_size<sizeof(FloatingPoint)>::type;
		const AsUint signFlipBit = ieee754::traits<scalar_t>::signMask & ieee754::impl::bitCastToUintType(flip);
		
		Vectorial output;
		array_get<Vectorial, scalar_t> getter_v;
		for (uint32_t i = 0; i < traits_v::Dimension; ++i)
		    setter(output,i, ieee754::impl::bitCastToUintType(getter_v(val,i))^signFlipBit );

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw the comment about benchmark is no longer applicable, I realized that when totalPseudoAngle = 0 you have two different quaternions you can still interpolate between by a fraction which when normalized will produce a valid quaternion

(there's two 4D directions which are orthogonal, the resulting quaternion's data member is interpolated between them on shortest path / within a plane spanned by them, then after normalization its like interpolating on a 4D sphere great circle)

So we can't make the adjEnd = 0 in that case and code needs to stay as is, and all the above explanation needs to go in the comment instead of the TODO.

Btw there's a bit of a funny situation where we interpolate towards a different adjEnd when it dips slightly more than 90 degrees away from start in 4D space, thats correct but I don't yet have an understanding of why. My intuition tells me its because quaternions can represent 720 degree rotations and not just 360 due to half-angles being used in the definition, so its something to make the final rotation be performed "along the shortest path"

Comment on lines +306 to +312
this_t inverse() NBL_CONST_MEMBER_FUNC
{
this_t retval;
retval.data.xyz = -data.xyz;
retval.data.w = data.w;
return retval;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does HLSL not like the unary minus operator ?

Copy link
Contributor Author

@keptsecret keptsecret Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the unary operator, isn't it? Only the scalar component of the inverse isn't negated for a quaternion.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean to have a this_t operator-() NBL_CONST_METHOD { instead of inverse()

even though HLSL can't overload resolve these operators for a -q call, you can still call them as q.operator-()

Comment on lines 325 to 341
static inline math::truncated_quaternion<T> __call(const math::truncated_quaternion<T> q)
{
assert(hlsl::length(q.data) == scalar_type(1.0));

assert(testing::relativeApproxCompare(hlsl::length(q.data), scalar_type(1.0), scalar_type(1e-4)));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong check, truncated should never be 1.0 (thats kinda the point, because it reconstructs w = 1-length(xyz), I have no clue how your tests passed in a Debug build (where asserts work)

Anyway I think that it makes no sense to have a normalize specialization for truncated quaternion. so can just delete the struct

Comment on lines 55 to 56
template<typename U=vector3_type, typename F=scalar_type NBL_FUNC_REQUIRES(is_same_v<vector3_type,U> && is_same_v<typename vector_traits<U>::scalar_type,F>)
static this_t create(const U axis, const F angle, const F uniformScale = F(1.0))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just get rid of the F template argument, it will be much easier

{
static inline math::truncated_quaternion<T> cast(const math::quaternion<T> q)
{
assert(testing::relativeApproxCompare(hlsl::length(q.data), scalar_type(1.0), scalar_type(1e-4)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will not compile on debug, your scalar_type is not defined in this scope

you can try this snippet to see

#define _NBL_DEBUG 1
#include "nbl/builtin/hlsl/math/quaternions.hlsl"

using namespace nbl::hlsl;

[shader("compute")]
[numthreads(1, 1, 1)]
void main(uint3 tid : SV_DispatchThreadID)
{
    impl::static_cast_helper<
        math::truncated_quaternion<float>,
        math::quaternion<float>
    >::cast(math::quaternion<float>::create());
}

Comment on lines +369 to +371
t.data.x = t.data.x;
t.data.y = t.data.y;
t.data.z = t.data.z;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should be copying from q and now we self assign and return garbage

NBL_UNROLL for (uint16_t i = 0; i < N; i++)
dots[i] = hlsl::dot(m[i], m_T[i]);

scalar_t uniformScaleSq = hlsl::dot(m[0], m_T[0]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not scale^2, you want dot of the same axis dot(m[i], m[i]) (or dot(m_T[i], m_T[i])) while making the test. Counterexample

#include "nbl/builtin/hlsl/math/quaternions.hlsl"
using namespace nbl::hlsl;

// take 90 degress rotation matrix
// it's true that R^T * R = I => s^2 = 1
static const float3x3 R = float3x3(
    0, -1, 0,
    1,  0, 0,
    0,  0, 1
);

RWByteAddressBuffer outBuf : register(u0);

[shader("compute")]
[numthreads(1, 1, 1)]
void main(uint3 tid : SV_DispatchThreadID)
{
    math::quaternion<float> q = math::quaternion<float>::create(R, true);

    // but dot between column and row will make s = -1, it will go to sqrt and give you NaN!
    outBuf.Store(0, asuint(q.data.w)); // ctrl + f for "0x1.8p+128" in dissasembly
}

For an orthogonal matrix $Q$:

$$ Q^T Q = I $$

with uniform scale $s$:

$$ M = sQ \Rightarrow M^T M = (sQ)^T(sQ) = s^2 I $$

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants