nfrechette/rtm

Examples for Graphics

Closed this issue · 9 comments

Hi! Thanks for this library, seems pretty cool!

I just thought I'd drop in some feedback as I'm taking a stab at using this. I'm attempting to use your library for a renderer I'm making that supports both D3D11 and OpenGL. I was previously using DirectXMath, but thought I'd take a stab at building for WASM! DirectXMath doesn't do this, but it seems that RTM should. Unfortunately, I'm not super great at math. I can do basic matrix transforms, and know enough to be aware of row-major vs. column-major differences, but I get a bit lost when it comes to the more complicated bits.

The first snag I hit when trying to use your library was creating a perspective and lookat matrix for my camera setup! These aren't provided in RTM as far as I can tell, presumably because there's a lot of different ways to do this, RH vs. LH and all that. Unfortunately, perspective and lookat matrices are not the sort of thing I'm capable of making myself, so I referenced the DirectXMath implementations, and did my best to recreate those with RTM. And uhh, something's not right. I can't tell which one or why, but something's definitely not working.

Then I'm also creating a model->world matrix, which seems to be much easier. I dug through some of the tests you've got, and came up with what I believe is the correct bit of code:
matrix4x4f world = matrix_cast( matrix_from_qvv(...) );
The 3x4 to 4x4 matrix thing is a bit odd, but I need it as a 4x4, since that's what my shaders use. I can't tell if this works, since my camera matrices don't work. But I suppose in theory something could be broken here too!

But anyhow, after all this, all I really want is to suggest adding an example! And perhaps some camera matrix functions. A short documentary example of how to use RTM to do basic graphics operations like the Model Space -> View Space -> Projection Space flow would go a -long- way for someone like me! I was hoping to see some sample code in the Getting Started section, but I think that was just build information.

I hope some of this is helpful, insightful, or at least mildly interesting! Keep up the awesome work :)

Hello and thanks for the feedback!

The documentation at the moment is a bit thin. Aside from examples missing, the documentation isn't browsable (you have to look at the headers and function definitions). That being said, RTM should have most of what you need for graphics as well. It's just missing some utility functions to create perspective matrices, etc. You should be able to pick up tutorials for DirectX and they should map fairly well.

matrix3x4f represents an affine matrix. Affine matrices contain rotation/translation/scale. It is 3x4 because to represent this, the last row is always 0,0,0,1. RTM uses a 4 vectors underneath the hood (so a pseudo 4x4) but it leaves the last row as undefined since it isn't needed.

You can use a 3x4 for model matrices and you can cast it to a 4x4 but the last row won't be provided for you, you'll have to explicitly set it to 0,0,0,1 (using vector_set_w()). It might otherwise contain garbage in that last row which might mess things up.

You can use a 4x4 matrix to represent camera projection matrices.

Affine matrices are a subset of 4x4 matrices and RTM represents them with a distinct type. The cast function is present for inter-op but you have to handle the missing bits manually (for now). Most tutorials will use 4x4 matrices for everything and not distinguish between the two.

Now that I think about it, the matrix cast should fill in the last column for you automatically and I think we can consider this a real bug.

I agree that adding a camera_utils.h file would make sense. Let's leave this issue open for this task and I'll create a new issue (#93) for the 3x4 -> 4x4 matrix cast. I'll fix the cast this week in develop but I don't think I'll have time anytime soon to create this new file. I'll leave it as help wanted for now and I'll get to it sooner or later when I integrate proper browsable documentation.

RTM absolutely works in the browser. The Animation Compression Library uses it here through an NPM module. Note that it doesn't support the experimental WASM SIMD stuff yet. But I have plans to support it later on once it has stabilized a bit more.

@nfrechette @maluoi Hey, I've written up some extensions for RTM that may be useful for game/graphics apps as part of my game engine's WIP vectormath library (which I just finished moving from DXM to RTM for double precision support and speed - have to say I'm really enjoying using RTM!). There's not a crazy amount there right now, but it seemed pertinent to this issue, so I figured I'd share in case anyone else finds them useful. They're mostly based on implementations from either DirectXMath or GLM, just ported to RTM.

The extensions are somewhat documented here, though not particularly well at the moment. It includes some things like look_at matrix construction, projection matrix construction, affine transformation matrix construction (based on qvv), etc. There are also some benchmarks here if anyone is interested in the performance of RTM vs other libraries (including my own, which is again largely just a convenience-centric wrapper for the subset/superset of RTM that I need).

I haven't written tests for the RTM extension code directly, but it's being indirectly tested through the tests for my math types. And full disclosure on that - many of the tests compare the results against DXM for convenience, so they are somewhat reliant on the robustness of DXM's tests.

That looks awesome @shadowndacorner ! Thank you for sharing :)

Glad to hear that you're enjoying RTM. The next minor release should come out in November with incremental improvements to the prior release.

Float64 types are well supported although they haven't been optimized as much yet, especially on ARM. I've made a note to add the camera utils (next year) and I'll make another note to give some love to the float64 types as well. I haven't needed them yet in my work but I do my best to maintain feature parity everywhere. With your permission, I'll inspire from your extensions when I do so.

Feel free to reach out with any feedback :)

@nfrechette Permission granted on my part haha. The one thing I'd point out before basing your implementations on mine is that I'm not sure if there are any licensing oddities due to my implementation following the structure of the DXM/GLM implementations pretty closely in most cases. I was planning to just include the DXM/GLM licenses with my game alongside the RTM license to be extra safe, but that may well be overkill as none of the DXM/GLM sources are used directly (outside of the tests).

There are ofc only so many ways to do these operations and I'd be pretty surprised if anyone got upset about it, but that's not the most robust approach to licensing :P

As for float64 support, I've only been testing on x86-64 Windows and Linux, but performance has been great as far as I can tell (as demonstrated by the benchmarks, where some of RTM's float64 ops outperform equivalent float32 ops of other libraries, with the disclaimer that they are ofc artificial benchmarks). I think there have been a couple of things that didn't seem to be supported 1:1 between 32 and 64 bit float ops, but the only one that I'm immediately finding a workaround for in my code is the following, though it could also easily be me just missing something obvious...

    RTM_DISABLE_SECURITY_COOKIE_CHECK
    RTM_FORCE_INLINE mask4f vector_in_bounds(const vector4f& input,
        const vector4f& bounds_min, const vector4f& bounds_max)
    {
        return vector_and(vector_less_equal(input, bounds_max),
            vector_greater_equal(input, bounds_min));
    }

    RTM_DISABLE_SECURITY_COOKIE_CHECK
    RTM_FORCE_INLINE mask4d vector_in_bounds(const vector4d& input,
        const vector4d& bounds_min, const vector4d& bounds_max)
    {
        using vector_t = vector4d;
        using mask_t = mask4d;

        mask_t less_mask = vector_less_equal(input, bounds_max);
        mask_t greater_mask = vector_greater_equal(input, bounds_min);

        // TODO: Optimize this once it's properly implemented in RTM -
        // vectorized implementation did not work for doubles
        return mask_set(mask_get_x(less_mask) & mask_get_x(greater_mask),
            mask_get_y(less_mask) & mask_get_y(greater_mask),
            mask_get_z(less_mask) & mask_get_z(greater_mask),
            mask_get_w(less_mask) & mask_get_w(greater_mask));
    }

I'll try to open another issue when I have time with a few other points of feedback I have. Off the top of my head, it'd be nice to have vector_get/set_component(index) matrix_get/set_component(row, col), matrix_set_axis and vector_normalize4 calls, but those are pretty easy to work around. And like I said before, overall I'm really enjoying RTM! Thanks for all of your work on the library 👍

Thank you for the feedback!

Note that vector_less_equal returns a mask and you must thus use mask_and and friends to manipulate it. With SSE2 and x64, the returned type is an alias of the same underlying type as vector4f, both are typedefs to __m128. That's why the code compiles and appears to work for floats but fails for doubles. With doubles, we are forced to have a struct to wrap all 4 components. I typically find these bugs by compiling without SIMD. There, everything is its own type as you'd expect and these aliasing errors pop up.

vector_normalize4 doesn't exist because I adopted the convention that operations that operate on the whole width do not have a suffix. e.g. vector_add and not vector_add4. I figured it would otherwise be redundant everywhere if I wanted to be consistent in the naming. As such, you can use vector_normalize to do what you want.

I do have vector_get_component(vector4f_arg0 input, mix4 component) that takes an enum instead of an index for type safety. However, there currently isn't a setter with the same signature. I'll add that to my todo list.

Similarly, there is matrix_get_axis but no setter. It is also missing a per component getter/setter. I'll add those to my todo list as well.

Ah, it looks like mask_add and vector_get_component were added after 2.15.0, so updating to the latest develop commit resolved that. I can't seem to find vector_normalize, though - would it be available through a header other than vector4f.h/vector4d.h?

It seems you are right, it is indeed missing with vector4. It is however present for quaternions and so the implementation should be a simple copy/paste. I'll add it to my todo list as well. I should be able to knock most of these off reasonably quickly in the next few months. Going forward, I'm aiming to publish releases a bit faster, once or twice a year maybe.

The biggest thing missing is still browsable documentation but I haven't had the chance to look into that yet.

I've been taking a look at this and thinking it over. There is more than meets the eye to just 'camera utils'. By adding these, I have to make an executive decision for the math lib with that frame of reference I am using and supporting: left/right handed, and which axis is up/forward/cross/translation.

I've personally always hated how GLM and DirectXMath handle this. They make a choice, and you have to live with it and convert to/from everywhere. It makes it really hard to write open source software that is engine agnostic because if the host runtime uses a different convention, they have to convert everywhere, add wrappers, etc. It just adds a lot of friction when integrating 3rd party code. I also don't want to claim which one is best or assume anything about the host runtime.

To that end, I intend to try something new with RTM: I will let the host runtime decide at compile time through a macro, similar to how asserts are handled. This way, only functions that deal with building a frame of reference will need to deal with it, all other code should be able to work as-is through helpers like matrix_get_axis_forward etc. This should make it possible to write code that will work regardless of the frame of reference. No need to duplicate functions for right/left handed, etc. As such, a single matrix_look_at function can be provided that will take a template argument, something along the lines of:

template<class coord = RTM_DEFAULT_COORDINATE_SYSTEM>
matrix3x4f look_at(vector4f position, vector4f direction, vector4f up)

Callers can just use loop_at(foo, bar, up) to use the default coordinate system (the one specified by the host runtime) or they can explicitly provide one (e.g rtm::coord:maya) for import/export purposes.

I haven't seen anything quite like this used out there, so it'll be experimental in a way. Hopefully it pans out without too many complications. Let me know what you think! I'll get started on it tonight and if it becomes too much of a pain to work with, I'll revert to having duplicate right/left handed versions and I'll pick the same default as DirectXMath as that's somewhat convenient for me.

After tinkering with it, it seems like too much work for this minor release. I've created issue #206 to track this research idea and put it in the backlog for now.

I'll keep it simple for this release and only implement the minimal set to be useful.