VariantConverter for Vector3D

EDIT: Here is a more refined implementation later in this thread: VariantConverter for Vector3D

Hello all, I want to use juce::Vector3D as a property in my ValueTree. I also want to be able to access/modify the Vector3D representation in the ValueTree via a CachedValue wrapper.

To do this, I need to write a juce::VariantConverter for juce::Vector3D that converts between var and Vector3D. I simply need to store the public members (x, y, and z) of the Vector3D into some var format and be able to read that format back into a var.

So far, I have the following implementation. Does anyone have suggestions for this implementation or notice any mistakes? I may be being too crazy with my constexpr here haha. Thanks!

template <typename T>
struct VariantConverter<Vector3D<T>>
{
    static Vector3D<T> fromVar (const var& v)
    {
        MemoryBlock * block = v.getBinaryData();
        jassert (block->getSize() == MEMORY_BLOCK_SIZE);
        T * start = static_cast<T*> (block->getData());
        return { *getX (start), *getY (start), *getZ (start) };
    }
    
    static var toVar (const Vector3D<T> & t)
    {
        MemoryBlock block { MEMORY_BLOCK_SIZE };
        T * start = static_cast<T*> (block.getData())
        *getX (start) = t.x;
        *getY (start) = t.y;
        *getZ (start) = t.z;
        return { std::move (block) };
    }
    
    static constexpr size_t MEMORY_BLOCK_SIZE = sizeof (T) * 3;
    static constexpr size_t X_OFFSET = 0;
    static constexpr size_t Y_OFFSET = sizeof (T);
    static constexpr size_t Z_OFFSET = sizeof (T) * 2;
    
    static constexpr T * getX (T * start) { return start + X_OFFSET; }
    static constexpr T * getY (T * start) { return start + Y_OFFSET; }
    static constexpr T * getZ (T * start) { return start + Z_OFFSET; }
};

I implemented a similar template class to store different structures in MemoryBlock saved as var in ValueTree. In my case it’s a kind of array, so I can operate on its elements via typical operators, comparators, UndoableActions etc. It works ok.

1 Like

After further investigation, here’s a better implementation. @matkatmusic offered me some help on discord by suggesting using Array instead of raw pointers. I also realized var has Array related methods so I have landed on this implementation (using C++ 17 syntax):

template <typename T>
struct VariantConverter<Vector3D<T>>
{
    static Vector3D<T> fromVar (const var& v)
    {
        if (auto * arr = v.getArray();
            arr != nullptr && arr->size() == NUM_VECTOR_ELEMENTS)
            return { v[X_INDEX], v[Y_INDEX], v[Z_INDEX] };
        return {};
    }
    
    static var toVar (const Vector3D<T> & t)
    {
        Array<T, DummyCriticalSection, NUM_VECTOR_ELEMENTS> arr;
        arr.set (X_INDEX, t.x);
        arr.set (Y_INDEX, t.y);
        arr.set (Z_INDEX, t.z);
        return { std::move (arr) };
    }
    
    static constexpr size_t NUM_VECTOR_ELEMENTS = 3;
    static constexpr size_t X_INDEX = 0;
    static constexpr size_t Y_INDEX = 1;
    static constexpr size_t Z_INDEX = 2;
};

Please see my reply in discord.

arr[X_INDEX] = t.x; doesn’t work like you expect it to.

1 Like

Haha yes, juce::Array misuse on my part. I will edit the above code to fix that, thanks again!

I suggest replacing arr.getReference(..) = ..; with arr.set(idx, val);

1 Like

Do you suggest Array::set() because it checks the index for out of bounds? My instinct was to use Array::getReference() to eliminate a pass-by-value parameter copy to the newValue parameter of set(), although this optimization is probably negligible for types like float. I think I wouldn’t need to worry about gaining the bonus of out-of-bounds checking contained in set() due to the initialization of Array with templated size. But, I do see that Array::set is more concise.

This is probably unnecessary optimization on my part.

Yes.

I suggest it because it’s easier to read and reason about what you’re trying to do.

arr.set(idx, val);
arr.getReference(idx) = val;

Which one would you rather look at, if you’d never seen the code before?

I use getReference in contexts like this;

auto& ref = arr.getReference(i);
//do something with ref
1 Like

Awesome, I agree with that. I’ll modify the above code to fit this! Thanks for all the help and suggestions.

1 Like