【问题标题】:How to achieve vector swizzling in C++?如何在 C++ 中实现向量混合?
【发布时间】:2019-01-09 11:42:56
【问题描述】:
struct vec2
{
    union
    {
        struct { float x, y; };
        struct { float r, g; };
        struct { float s, t; };
    };
    vec2() {}
    vec2(float a, float b) : x(a), y(b) {}
};
struct vec3
{
    union
    {
        struct { float x, y, z; };
        struct { float r, g, b; };
        struct { float s, t, p; };
        // Here is the problem with g++.
        struct { vec2 xy; float z; };
        struct { float x; vec2 yz; };
    };
    vec3() {}
    vec3(float a, float b, float c) : x(a), y(b), z(c) {}
};

上面的代码在 Visual Studio 中按预期编译和工作,所以我可以像使用它一样

vec3 v1(1.f, 2.f, 3.f);
vec2 v2 = v1.yz; // (2, 3)

不是在 g++ (MinGW) 中。

src/main.cpp:22:23: error: member 'vec2 vec3::<unnamed union>::<unnamed struct>::xy' with constructor not allowed in anonymous aggregate
src/main.cpp:22:33: error: redeclaration of 'float vec3::<unnamed union>::<unnamed struct>::z'
src/main.cpp:18:30: note: previous declaration 'float vec3::<unnamed union>::<unnamed struct>::z'
src/main.cpp:23:32: error: member 'vec2 vec3::<unnamed union>::<unnamed struct>::yz' with constructor not allowed in anonymous aggregate
src/main.cpp:23:24: error: redeclaration of 'float vec3::<unnamed union>::<unnamed struct>::x'
src/main.cpp:18:24: note: previous declaration 'float vec3::<unnamed union>::<unnamed struct>::x'

我想我一开始就不应该那样做。有什么想法吗?

编辑:在阅读了很多文章并探索了开源项目之后,我开始了解矢量混合应该是什么样的,并在下面发布了解决方案,但仍在等待更好的答案。 p>

编辑 2:所有vec* 成员只能从父级访问,就像 GLM 库一样.

【问题讨论】:

    标签: c++ visual-studio g++ glsl mingw


    【解决方案1】:

    首先,匿名结构是a feature from C11,是not allowed by C++,所以它不支持带有构造函数的类成员(不是C结构)。要编写可移植的 C++ 代码,您应该避免使用匿名结构:

    struct vec2 // use C++ style struct declaration
    {
    // struct is public by default
        union
        {
            struct { float x, y; } xy; // add member name, 
            struct { float r, g; } rg; // now the declaration declares a member 
            struct { float s, t; } st; // instead of an anonymous struct
        };
        vec2() {}
        vec2(float a, float b) : xy{a, b} {}
                              // ^^^^^^^^ also change the initialization
    };
    
    struct vec3
    {
    public:
        union
        {
            struct { float x, y, z; } xyz;     //
            struct { float r, g, b; } rgb;     //
            struct { float s, t, p; } stp;     // add member name
            struct { vec2 xy; float z; } vecz; //
            struct { float x; vec2 yz; } xvec; //
        };
        vec3() {}
        vec3(float a, float b, float c) : xyz{a, b, c} {}
                                       // ^^^^^^^^ also change the initialization
    };
    

    现在代码可以在 GCC 下编译,但这还不够。在带有-pedantic-errors 的Clang 下,您将获得several errors

    error: anonymous types declared in an anonymous union are an extension [-Werror,-Wnested-anon-types]
    

    这是因为您不能在匿名联合中声明嵌套类型,因此您还应该将这些结构定义移到联合之外:

    struct vec2
    {
        struct XY { float x, y; };
        struct RG { float r, g; };
        struct ST { float s, t; };
        union
        {
            XY xy; 
            RG rg; 
            ST st; 
        };
        vec2() {}
        vec2(float a, float b) : xy{a, b} {}
    };
    
    struct vec3
    {
        struct XYZ { float x, y, z; };     
        struct RGB { float r, g, b; };     
        struct STP { float s, t, p; };     
        struct VECZ { vec2 xy; float z; }; 
        struct XVEC { float x; vec2 yz; }; 
        union
        {
            XYZ xyz;     
            RGB rgb;     
            STP stp;     
            VECZ vecz; 
            XVEC xvec; 
        };
        vec3() {}
        vec3(float a, float b, float c) : xyz{a, b, c} {}
    };
    

    虽然此解决方案有效,但您只能通过例如v.xy.x 而不是简单的v.x 访问成员。此外,将vec2 与两个float 混叠会导致未定义的行为。我认为没有标准的解决方案可以完美地实现矢量混合。

    对于非标准解决方案,可以使用没有构造函数的代理类而不是vec2 来使编译器工作。 GLM library 也使用了这个想法。 OP 已经发布了answer 作为这个想法的完整实现。

    【讨论】:

    • 我应该让他们匿名以按预期工作。它不适用于本文中提供的示例。问题仅在于构造函数,而不是整个变量。其实我对C++17也不是很了解,std::variant是怎么做到的呢?
    【解决方案2】:

    嗯,我自己找到了仅使用 C++ 标准的解决方案。
    没有命令行,也没有使用编译器特定代码。

    所以这是我的新的简单的实现

    template<unsigned int I>
    struct scalar_swizzle
    {
        float v[1];
        float &operator=(const float x)
        {
            v[I] = x;
            return v[I];
        }
        operator float() const
        {
            return v[I];
        }
        float operator++(int)
        {
            return v[I]++;
        }
        float operator++()
        {
            return ++v[I];
        }
        float operator--(int)
        {
            return v[I]--;
        }
        float operator--()
        {
            return --v[I];
        }
    };
    // We use a vec_type in a template instead of forward declartions to prevent erros in some compilers.
    template<typename vec_type, unsigned int A, unsigned int B>
    struct vec2_swizzle
    {
        float d[2];
        vec_type operator=(const vec_type& vec)
        {
            return vec_type(d[A] = vec.x, d[B] = vec.y);
        }
        operator vec_type()
        {
            return vec_type(d[A], d[B]);
        }
    };
    struct vec2
    {
        union
        {
            float d[2];
            scalar_swizzle<0> x, r, s;
            scalar_swizzle<1> y, g, t;
            vec2_swizzle<vec2, 0, 0> xx;
            vec2_swizzle<vec2, 1, 1> yy;
        };
        vec2() {}
        vec2(float all)
        {
            x = y = all;
        }
        vec2(float a, float b)
        {
            x = a;
            y = b;
        }
    };
    /* Debugging */
    inline std::ostream& operator<<(std::ostream &os, vec2 vec)
    {
        os << "(" << vec.x << ", " << vec.y << ")";
        return os;
    }
    template<typename vec_type, unsigned int A, unsigned int B, unsigned int C>
    struct vec3_swizzle
    {
        float d[3];
        vec_type operator=(const vec_type& vec)
        {
            return vec_type(d[A] = vec.x, d[B] = vec.y, d[C] = vec.z);
        }
        operator vec_type()
        {
            return vec_type(d[A], d[B], d[C]);
        }
    };
    struct vec3
    {
        union
        {
            float d[3];
            scalar_swizzle<0> x, r, s;
            scalar_swizzle<1> y, g, t;
            scalar_swizzle<2> z, b, p;
            vec2_swizzle<vec2, 0, 1> xy;
            vec2_swizzle<vec2, 1, 2> yz;
            vec3_swizzle<vec3, 0, 1, 2> xyz;
            vec3_swizzle<vec3, 2, 1, 0> zyx;
        };
        vec3() {}
        vec3(float all)
        {
            x = y = z = all;
        }
        vec3(float a, float b, float c)
        {
            x = a;
            y = b;
            z = c;
        }
    };
    /* Debugging */
    inline std::ostream& operator<<(std::ostream &os, vec3 vec)
    {
        os << "(" << vec.x << ", " << vec.y << ", " << vec.z << ")";
        return os;
    }
    

    当然,您可以添加/创建更多 swizzling。现在做一个小测试。

    int main()
    {
        vec3 v0(10, 20, 30);
        std::cout << v0.zyx << std::endl;
        vec2 c(-5, -5);
        v0.xy = c;
        vec2 v1(v0.yz);
        std::cout << v0 << std::endl;
        std::cout << v1 << std::endl;
        vec3 v(50, 60, 70);
        vec2 d = v.yz;
        std::cout << d << std::endl;
        float f = d.x * d.y;
        std::cout << f << std::endl;
    
        return 0;
    }
    

    输出:

    (30, 20, 10)
    (-5, -5, 30)
    (-5, 30)
    (60, 70)
    4200
    

    如果您不像我在 gcc 中那样使用 IDE,您可以使用 std::cout 打印用于调试的向量。

    【讨论】:

    【解决方案3】:

    至于“匿名聚合中不允许使用构造函数的成员”,是由于编译器运行符合旧标准,因为as of C++11, unions can have members with non-trivial constructors(您定义了自己的构造函数,所以它不是琐碎,有关此的详细信息可以找到here)。在您的 g++ 编译器的参数中添加 -std=c++11,这个错误可能会消失。

    接下来。可以可能使其编译您的代码的 g++ 的唯一标志是 -fms-extensions-fvisibility-ms-compat。匿名结构是 Microsoft 添加到其编译器的 非标准 扩展。抱歉,现在我无法测试它,但我认为这样可以解决问题。

    现在还有一些额外内容。

    1. 与 C 不同,您不应该在 C++ 中 typedef 结构 - 如果您命名了您的结构,您可以使用该名称作为类型来引用它们。
    2. 结构体默认是公开的,这里不需要public。但是,默认情况下,类是私有的。
    3. 如果您的意图只是能够在 C++ 中使用 GLSL 数学,GLM 就是这样做的方法。如果你想自己学习如何做,可以参考他们的源代码(不过模板相当繁重)。
    4. 其他 g++ 选项可以在 here 找到。

    希望这至少对您有所帮助。

    【讨论】:

    • 我已经有了完整的 GLSL 数学库,它比带有 extras 的 GLM 更快、更好,而且更像 GLSL,我在结构和类定义之间切换以调试问题所以我不需要每次都输入public:忘记删除它。顺便说一句,这些命令行都不起作用。 GLM 本身不支持该功能。使我的库更像 GLSL 的原因是它具有此功能,因此我必须为所有编译器修复它,或者为了跨平台兼容性而完全按照 GLM 所做的那样放手……还有其他解决方案吗?
    • 这里 (github.com/g-truc/glm/blob/master/manual.md#section2) 他们说如果 GLM_FORCE_SWIZZLE 在任何其他 GLM 文件之前定义,GLM 将支持 swizzling,我真的记得它对我有用,但我只在 MSVC 上使用它。它有一些怪癖,所有这些都在我提供的链接中详细说明。关于问题,对不起,如果这些标志没有帮助,我现在真的无能为力。
    • 哇!我不知道它可以做到。此外,它在没有任何标志的任何编译器中都能正常编译......我将尝试查看源代码并了解它是如何工作的。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-07
    • 2020-10-07
    • 1970-01-01
    • 1970-01-01
    • 2020-11-29
    • 2021-01-31
    • 1970-01-01
    相关资源
    最近更新 更多