Skip to content

Cameraz - #995

Open
AnastaZIuk wants to merge 173 commits into
masterfrom
cameraz
Open

Cameraz#995
AnastaZIuk wants to merge 173 commits into
masterfrom
cameraz

Conversation

@AnastaZIuk

@AnastaZIuk AnastaZIuk commented Feb 2, 2026

Copy link
Copy Markdown
Member

Introduces the new camera stack as Nabla::ext::Cameras.

…s - we were never be able to do ourMatrixT = glmMatrixT and its important since a lot of utils create glmMatrixT actually
…atrix xD remove the = operator glm -> to ours
…H (ambiguity dependent type issues), reference #760
… were missing an equivalent of E_KEY_CODE. Update examples_tests submodule
Comment on lines +78 to +80
virtual void updateKeyboardMapping(const std::function<void(keyboard_to_virtual_events_t&)>& mapKeys) = 0;
virtual void updateMouseMapping(const std::function<void(mouse_to_virtual_events_t&)>& mapKeys) = 0;
virtual void updateImguizmoMapping(const std::function<void(imguizmo_to_virtual_events_t&)>& mapKeys) = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't get the std::functions, this is virtual, but the implementation just calls the function?!
and calling the function is simply a lambda. this is super unnecessary, the members are public.

why would you want to modify them through a std::function? just access the member, assign it, clear it.

I'd get rid of the whole updateXXXMapping through function and modify the data directly

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

actually we could drop callbacks

i started with private maps and used callbacks to edit them

/// @brief Virtual event emitted by this binding.
gimbal_event_t event = {};
/// @brief Per-binding gain applied when raw input is converted into one virtual-event magnitude.
double magnitudeScale = DefaultMagnitudeScale;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

float is enough

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

float may be enough, more precision alone is not a reason to keep double

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ok, what's a good reason to keep a multiplier double here?
I usually don't care about camera classes being larger btw, there are very few of them. but since this is going into map of all possible inputs, i care a bit

/// @brief Per-binding gain applied when raw input is converted into one virtual-event magnitude.
double magnitudeScale = DefaultMagnitudeScale;
/// @brief Runtime latch used by held keyboard and mouse-button bindings.
bool active = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

CHashInfo is two unrelated things in one struct. same for magnitude inside the event

  • get rid of sanitize
  • get rid of useless std::functions accessors that is equivalent to returning map&

then I would look for a solution like this:

struct SBinding {                 // pure config — what lives in every stored layout
    uint8_t eventIndex;           // index into CVirtualGimbalEvent::VirtualEventsTypeTable
    float   magnitudeScale;                 // sensitivity; float is ample for a multiplier
};                                // = 8 bytes

Binding layout would just become a struct holding three maps and that's it ;)

State of active could be:

std::bitset<EKC_COUNT>  m_heldKeys;      //  16 B
std::bitset<EMC_COUNT>  m_heldButtons;   //   8 B
std::array<double, CVirtualGimbalEvent::EventsCount> m_magnitude;  // 144 B
                                         // = 168 B, fixed

also active+magnitude should not be something that "InputBinding" should care. that's why I suggest moving it up the hierarchy when used in IGimbalInputProcessor

this will not only be a net reduction in memory usage, but cleans things up a lot better. and you have data with different frequncies living seperately and in a better place

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

we can separate config and state and improve memory usage

/// Relative mouse movement, mouse scroll, and ImGuizmo deltas emit
/// `abs(rawDelta) * magnitudeScale` per bound axis. The result is written into
/// `CVirtualGimbalEvent::magnitude`.
class IGimbalInputProcessor : public CGimbalBindingLayoutStorage

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

InputProcessor needs to store map of the bindings from input to events.
and it makes 0 sense that a "processor" inherits from a "BindingLayout"

@Erfan-Ahmadi Erfan-Ahmadi Sep 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you already needed it somewhere else and decided to store it as member function.
but for some reason this inherits?

// in PlanarProjection
ui::CGimbalBindingLayoutStorage m_inputBinding

based on https://github.com/Devsh-Graphics-Programming/Nabla/pull/995/changes#r3933703955 this inheritance should be changed to just storage of the static "layout" (the maps) + dynamic "states" (the active status + magnitude)
also interface inheriting from impl was a bit sketchy

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

inheritance lets the processor be used as IGimbalBindingLayout by the same binding editor.

composition works too, but callers would need access to its layout member

using inv_concatenated_matrix_t = std::optional<hlsl::float64_t4x4>;

/// @brief One concrete linear projection matrix together with cached inverse metadata.
struct CProjection : public IProjection

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't get the choice to have ILinearProjection::CProjection inherit from IProjection and not the ILinearProjection itself
I would assume the convention IXXXProjection inherits from IProjection.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

the wrapper has multiple projections, while CProjection represents one.

we can change the names, but making the wrapper implement IProjection still needs a choice of which projection project(...) uses

/// `MinDistance` prevents zero-distance target-relative states.
/// `DefaultMaxDistance` is unbounded. Individual cameras and tools may apply
/// their own finite limits on top of it.
struct SCameraTargetRelativeTraits final

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"Traits" is usually something that get specialized via template on different classes
this header looks more like a "limit" to me.
naming convention aside.

  • why do we need to hardcode clamp distance to target by 0.1f?
  • why are values here float and the ones in SCameraToolingThresholds below in double?
  • I think this MinDistance value should be the same as whatever the projection paired with the camera uses as zNear plane.
  • but again, I don't get the reason of existance for MinDistance and why we need to clamp some distance between a costexpr Min and Max that's baked into the library

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

one camera can have multiple projections with independent clipping parameters, so which zNear would it use?

distance to the target is a different setting

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

my problem is not what it should be equal to. my problem is why is that a thing that is hardcoded with 0.1f.
it seems mostly like a UX thing, but breaks when user wants to work in small ranges.
it seems to be mostly for preventing the distances to targets becoming 0 and introducing div by 0 issues.

also I don't see any reason to clamp against infinity.
What i suggest is that if you want to have min/max distance, let the user provide it to you when building the camera classes, don't decide that for them

/// the minimum representable `float` value.
static inline constexpr float MinDistance = 1e-1f;
/// @brief Default upper bound for target-relative distance when no camera-specific cap is requested.
static inline constexpr float DefaultMaxDistance = std::numeric_limits<float>::infinity();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

again, this is float, and from what I've seen you're clamping/comparing doubles with this.
float infinity is not double infinity

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is not true

float infinity stays infinity when converted to double. see C++ [conv.fpprom]/1.

A prvalue of type float can be converted to a prvalue of type double. The value is unchanged.

Godbolt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

interesting. this i learned today.
but again. let's be consistent with our types and limits

};

/// @brief Comparison thresholds used by helper layers outside the runtime camera interface.
struct SCameraToolingThresholds final

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the limits in these files need to clarify:

  • Numerical guards — "will this divide by zero?"
  • Policy floors — "should we let the user do this?" A UX decision
    • e.g. MinDistance, which I think is useless why would we need to clamp a positive number to 0.1 and +inf? it's not guarding a div and it seems to be soley UX related.
  • Comparison tolerances — "are these two states the same?" and the epsilons relate to the floating point representation used. and their values need to be justified, their usages need to be justified as well. what are we protecting against?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is not true

computeDollyFov divides by max(distance, MinDistance), so it does guard a division

using 0.1 as the minimum is a separate choice

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ok but why 0.1 as hardcoded choice? MinDistance is being used multiple places for multiple reasons.
here is used for 0 guard div but it's used somewhere else for something else.

// This file is part of the "Nabla Engine".
// For conditions of distribution and use, see copyright notice in nabla.h

#ifndef _C_CAMERA_TRAITS_HPP_

@Erfan-Ahmadi Erfan-Ahmadi Sep 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

CCameraMathUtils has similar limits structs, maybe they should live in the same place.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

we can put them together, but keep the different kinds of limits clear

}

template<typename Vec, typename E = double>
static inline bool isOrthoBase(const Vec& x, const Vec& y, const Vec& z, const E epsilon = 1e-6)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the problem here is that's it's hardcoded for fp32 but templated on float type. you could get away with 1e-13 for doubles here
also, instead of "length" which has a sqrt involved. you could just abs(dot(V,V)-1.0) < eps

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

casting the input axes to double does not restore precision already lost in float32.

using the same epsilon for the squared length check also changes which vectors pass

hlsl::matrix<precision_t, 3, 3> m_orthonormal;

/// @brief Counter that increments for each performed manipulation, resets with each begin() call
size_t m_counter = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why reset every frame?
Isn't the point of the counter to avoid doing things everyframe and the manipulation to persist across frames? it's stored as uint64_t/size_t which means every frame there can be 2^64-1 manipulations?! even uint16 seems enough to me (~65K) for every frame.

Oh no! you're just doing &=bool(m_gimbal.getManipulationCounter()); everywhere, it is literally just 1 bit/bool needed but somehow we have counter and isManipulating, both are useless and replacable by a bool at this stage.

I'm going to go an step further, remove the begin/end and counter reset everyframe and the weird bool below. store a 64-bit counter and increment on every edit. higher level code could store a gimbal manipulation counter value next to their computed view matrix and recalc when counters mismatch.
also some functions appear to be missing manipulation_counter++ like setScale

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

view updates are already conditional, the counter tells us whether something changed while isManipulating tells us whether begin/end is open.

a counter that never resets can work too, but scale does not change the rigid view matrix

size_t m_counter = {};

/// @brief Tracks whether gimbal is currently in manipulation mode
bool m_isManipulating = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is just for assert(m_isManipulating) it seems. need to remove

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

it is also returned by isManipulating(), not just used in asserts.

after begin() the flag is true and the counter is zero, so they tell us different things

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

begin/end and isManipulating need to be replaced with global uint64 counter

Comment on lines +111 to +117
if constexpr (AllowedEvents & CVirtualGimbalEvent::ScaleXInc)
if (event.type == CVirtualGimbalEvent::ScaleXInc)
impulse.dVirtualScale.x *= static_cast<precision_t>(event.magnitude);

if constexpr (AllowedEvents & CVirtualGimbalEvent::ScaleXDec)
if (event.type == CVirtualGimbalEvent::ScaleXDec)
impulse.dVirtualScale.x *= static_cast<precision_t>(event.magnitude);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • you don't need scale for camera gimbal
  • this class is not supposed to be general model matrix construction that handles scale, this class is all about cameras and virtual events affecting cameras.
  • also I looked and there is literally 0 difference between CVirtualGimbalEvent::ScaleXInc and CVirtualGimbalEvent::ScaleXDec. you're treating them as the same thing everywhere
  • also no camera allows scaling gimball event as it makes 0 sense

we need to nuke the scale event completely and remove m_scale from here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

other objects and scale were explicitly part of the design, so this is not limited to cameras.

the input distinguishes factors below and above 1, although both events use multiplication in IGimbal

@Erfan-Ahmadi Erfan-Ahmadi Sep 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ok got it, you wrote it specifically for all types of objects. then you need to stop using hlsl::CCameraMathUtilities and hlsl::camera_vector_t and anything with camera in it's name.

also void transform function doesn't even take impulse.dVirtualScale into account. we may want to fix that as well (not sure if intentional?)

Comment on lines +205 to +221
template<typename T>
static inline constexpr camera_vector_t<T, 3> getCameraWorldRight()
{
return camera_vector_t<T, 3>(T(1), T(0), T(0));
}

template<typename T>
static inline constexpr camera_vector_t<T, 3> getCameraWorldUp()
{
return camera_vector_t<T, 3>(T(0), T(1), T(0));
}

template<typename T>
static inline constexpr camera_vector_t<T, 3> getCameraWorldForward()
{
return camera_vector_t<T, 3>(T(0), T(0), T(1));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

very bad names and types.
In linear algebra it's just simply standard basis X Y Z, has nothing to do with the camera.
you transform these vectors using quaternions to put into matrix columns and construct a matrix that transforms these standard basis to the new ones.

need to rename to something that makes more sense or get rid of the functions alltogether. writing it directly makes more sense and is more readable

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

"Has nothing to do with the camera" is not true

we model the camera with the gimbal's orthonormal frame and these helpers define its canonical right, up and forward axes.

being standard basis vectors does not make them unrelated to cameras

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

doesn't matter that much. I'd rename them to CameraStandardBasisX.
when I see World in the name hints at the units being in worldspace. while these are just the standard basis of a camera in camera space. the camera's up vector is always 0,1,0. that's why it's local and not world

/// The class exists mainly as a convenient instantiable type when no additional
/// camera-specific state or manipulation policy is required on top of `IGimbal`.
template<typename T = hlsl::float64_t>
class CGeneralPurposeGimbal : public IGimbal<T>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

unused class and adds 0 value to parent class

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

not being used inside Nabla alone does not make it unnecessary, but this wrapper is only a convenience type

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ok not used, but what value do they add to IGimball? they don't even specialize a template param. they are identical at this point. did you have any plans to add any feature to this class?

}

/// @brief Apply a prebuilt rigid reference transform and an accumulated impulse in one step.
inline void transform(const CReferenceTransform& reference, const VirtualImpulse& impulse)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this function is not used anywhere. just keeping notes for future

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

the ICamera::CGimbal::transform wrapper calls it.

it applies translation and rotation relative to a reference frame, lack of a current caller alone does not make that operation unnecessary

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

mostly hinting that a function is untested. and needs to be reviewed with more care later on or with unit tests

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

btw, any reason this does not take scale of the "impulse" into account?

Comment on lines +336 to +339
if constexpr (std::is_same_v<T, float>)
return makeQuaternionFromBasisImpl(canonicalRight, canonicalUp, canonicalForward);
else
return makeQuaternionFromBasisImpl(canonicalRight, canonicalUp, canonicalForward);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

these are the exact same, no need to specialize with if constexpr

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yeah my bad, no need for the if constexpr

{
setOrientation(reference.orientation * hlsl::CCameraMathUtilities::makeQuaternionFromEulerRadiansYXZ(impulse.dVirtualRotation));
setPosition(
hlsl::float64_t3(reference.frame[3]) +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

templated on T but using float64_t3 directly here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

doesn't matter, this will all change to float64 soon

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this uses float64 despite being templated on T, we should make that consistent

if(dRadians)
m_counter++;

const auto dRotation = hlsl::CCameraMathUtilities::makeQuaternionFromAxisAngle(axis, static_cast<precision_t>(dRadians));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

float for radians/angle is enough precision. so this is fine.
although that function needs to go into actual hlsl lib

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

we can move the math helper to hlsl


/// @brief Accumulates one frame of virtual events into a translation/rotation/scale impulse.
template <uint32_t AllowedEvents>
VirtualImpulse accumulate(std::span<const CVirtualGimbalEvent> virtualEvents, const vector_t<3u>& gRightOverride, const vector_t<3u>& gUpOverride, const vector_t<3u>& gForwardOverride)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

3 params are not used at all.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yeah these three params are not used, could be another leftover

Comment on lines +245 to +247
m_viewMatrix[0u] = hlsl::float64_t4(gRight, -hlsl::dot(gRight, position));
m_viewMatrix[1u] = hlsl::float64_t4(gUp, -hlsl::dot(gUp, position));
m_viewMatrix[2u] = hlsl::float64_t4(gForward, -hlsl::dot(gForward, position));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

make sure to mention that the inverse of an orthonormal matrix is it's transpose, so we caan put columns into rows.
also it's really confusing because you're treating m_orthonormal (where those getX/Y/ZAxis come from) as a column-major order matrix treating mat[0] as the first column but here you're doing it properly with nabla conventions (hlsl::matrix is row matrix)

and the conventions aren't clear, the when I look at some functions, some treat it as column-major some as row-major

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

here the basis stores the rotated axes as rows, which is already the transpose of the orientation used with column vectors. updateView uses those rows and their negative dot products with position, so this view construction is correct.

i think we can document that convention

@Erfan-Ahmadi Erfan-Ahmadi Sep 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we need to fix up the code and anywhere in the ext that uses matrix first.
either store 3 seperate orthonormal column vectors if you want to strore columns. otherwise you'll risk bugs and somebody touching up the code later and assuming the matrices are safe to mul with, not knowing sometimes it's transposed and sometimes not.

for example we cannot move something like getQuaternionBasisMatrix to the hlsl library because it clashes with nabla's convention of row major matrices.

Comment on lines +638 to +658
template<typename T>
static inline camera_vector_t<T, 3> projectWorldVectorToLocalBasis(
const camera_vector_t<T, 3>& worldVector,
const camera_vector_t<T, 3>& right,
const camera_vector_t<T, 3>& up,
const camera_vector_t<T, 3>& forward)
{
const camera_matrix_t<T, 3, 3> basis { right, up, forward };
return hlsl::mul(hlsl::transpose(basis), worldVector);
}

template<typename T>
static inline camera_vector_t<T, 3> transformLocalVectorToWorldBasis(
const camera_vector_t<T, 3>& localVector,
const camera_vector_t<T, 3>& right,
const camera_vector_t<T, 3>& up,
const camera_vector_t<T, 3>& forward)
{
const camera_matrix_t<T, 3, 3> basis { right, up, forward };
return hlsl::mul(basis, localVector);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is wrong and swapped because hlsl::matrix and mul treats matrix as row major matrix.
I'm 90% sure this is causing a bug with the CSphericalTargetCamera::applyPlanarTargetTranslation using this function

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yeah these two are swapped, local to world needs the transpose when the axes are stored as rows

this also affects planar target movement

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

btw instead of mul(transpose(m), v) you can do mul(v, m)

Comment on lines +272 to +273
const TRS operator()() const
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • this function is unused and is a bit funny, cause it's the same function but when you pass hlsl::matrix<T, 4u, 4u> it treats as row-major but otherwise column major and the function is never used btw.

@Erfan-Ahmadi

Copy link
Copy Markdown
Contributor

the convention in your camera ext is super unclear:

vector_t<3u> m_scale = { 1.f, 1.f , 1.f };

/// @brief Orthonormal basis reconstructed from the current orientation.
hlsl::matrix<precision_t, 3, 3> m_orthonormal;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if you want to keep this. then you can do it as seperate column vectors, because it looks like this matrix is only used for storage and not any matrix operations. and you're storing your columns in this matrx's rows and any mul using this matrix would break immediately.

}

template<typename T>
inline math::quaternion<T> matrix_to_quaternion_cast(NBL_CONST_REF_ARG(matrix<T,3,3>) m)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why are we considering the trasnpose matrices for matrix_to_quaternion cast? why are we doing this 4 times 4 different ways and scoring them?

Comment on lines +755 to +762
static inline camera_matrix_t<T, 3, 3> getQuaternionBasisMatrix(const camera_quaternion_t<T>& orientation)
{
const auto normalizedOrientation = normalizeQuaternion(orientation);
return camera_matrix_t<T, 3, 3>(
normalizedOrientation.transformVector(getCameraWorldRight<T>(), true),
normalizedOrientation.transformVector(getCameraWorldUp<T>(), true),
normalizedOrientation.transformVector(getCameraWorldForward<T>(), true));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why not use __constructMatrix? already available in hlsl.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

probably because first this PR got started and THEN the quaternions got cleaned up by Przemog

}

template<typename VecA, typename VecB, typename T>
static inline bool nearlyEqualVec3(const VecA& a, const VecB& b, const T epsilon)

@Erfan-Ahmadi Erfan-Ahmadi Sep 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we have nbl::hlsl::RelativeApproxCompareHelper that should even work for matrices. this is redundant

}

template<typename T>
static inline bool nearlyEqualScalar(const T a, const T b, const T epsilon)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we have nbl::hlsl::RelativeApproxCompareHelper that should even work for matrices. this is redundant

@Erfan-Ahmadi

Erfan-Ahmadi commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

not included in this PR but needs to be fixed:

https://github.com/Devsh-Graphics-Programming/Nabla/blame/89cfa0b58c4d719c3dd3a245f6b46f73c6caa638/include/nbl/builtin/hlsl/testing/relative_approx_compare.hlsl#L25

compare with 0.0 or any other subnormal even with a generous tolerance with return false. that's causing some quaternion stuff to be nan when they shouldn't

e.g here:

orthogonal = orthogonal && testing::relativeApproxCompare(hlsl::dot(m[i], m[(i+1)%N]), scalar_t(0.0), 1e-4);

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.

3 participants