3

我试图为 Mesh 类进行通用编程。(我需要 CPU 转换来变形网格,这就是为什么这不是在 GPU 中完成的,以防你想问)

AMesh<T>包含 3d 形状的顶点,以及面连接(哪些顶点连接在一起形成三角形)。

这里T是顶点的类型,一些类型的模型有 PNCT 类型(位置、法线、颜色、texcoord)而其他的只是 PC(位置、颜色)。

struct VertexPNCT {
    Vector3 pos, normal ;
    Vector4 color ;
    Vector2 tex ;
} ;

struct VertexPC {
    Vector3 pos ;
    Vector4 color ;
} ;

当然,所有顶点都有一个位置!

但这是我的问题。

Mesh<T>必须实现一个transform方法。当然,每个顶点格式都有一个位置,如果它的命名一致(.pos),那么该方法可以正常工作:

.pos由于每个顶点肯定都有一个位置,如果我总是在结构中调用该成员,那么我可以向模板类Vertex*添加一个方法:.transformMesh<T>

void transform( Matrix4& mat )
{
    each vertex
      vertex.pos = mat * vertex.pos ; // transform it
}

现在这是搞砸了。 如果顶点格式中有法线,那么法线也必须进行转换(通过“法线矩阵”)。现在我的模板中有一个“if 语句”?我必须.transform为每种顶点类型编写一个模板特化方法(基本上分为两类,有法线的和没有法线的)?

我在这里滥用了模板吗?错过了一些“编程船”?

这是我真正想做的合乎逻辑的事情:

void transform( Matrix4& mat )
{
    each vertex in vertices
    {
      vertex.pos = mat * vertex.pos ; // transform it
      #ifdef vertex.normal
      vertex.normal = mat * vertex.normal ; // transform normal as well*
      #endif
    }
}

* 假设转换没有尺度(那么你不需要使用“正常矩阵”)

4

3 回答 3

3

如果您可以编写一个层次结构,Vertex其中 base 仅保存位置(和.pos成员),aVertexWNormal保存.normal然后其余部分从那里继承,那么您只需添加非模板化重载并让编译器处理:

void transform( Matrix4& m, VertexBase& v ) {
   // transform only pos
}
void transform( Matrix4& m, VertexWNormal& v ) {
   transform(m,static_cast<VertexBase&>(v));
   // transform normal here
}
void tranform( Matrix4& m ) {
   foreach vertex:
       transform(m,vertex);
}

当然,这对你的设计是否有意义取决于你没有展示的很多东西,这很难说。

于 2012-10-03T18:07:42.617 回答
2

如果您只有两种类型的顶点,我建议您按照 David 上面描述的方式进行操作:只需创建两个进行转换的函数,并根据顶点类型使用重载调用它们。这也适用于更多的顶点类型,但是每次添加新的顶点类型时,都需要再次重载该函数。对于此处描述的简单功能,这可能没问题,但如果该功能实际上更复杂,它可能会变得烦人。

部分修复是创建一个特征类,它告诉特定顶点类型是否具有普通成员。可以设置默认值,使其在大多数情况下都是正确的,并且可以选择基于特征的合适函数。您仍然会提供两个版本的代码,但对于每个额外的顶点类型,所有需要的是定义特征:

template <typename> struct VertexHasNormal { enum { value = false }; };
template <> struct VertexHasNormal<VertexPNCT> { enum { value = true }; };

template <typename V, template T, template S>
void transform(V& vertices, Matrix4& m, T S::*member) {
    for (auto& v: vertices) {
        v.*member = mat * v.*member;
    }
}

template <bool, typename T>
struct Transform {
    template <typename V>
    void call(V& vertices, Matrix4& m) {
        transform(vertices, m, &T::pos);
    }
};
template <typename T>
struct Transform<bool, T> {
    template <typename V>
    void call(V& vertices, Matrix4& m) {
        transform(vertices, m, &T::pos);
        transform(vertices, m, &T::normal);
    }
};

template <typename T>
void Mash<T>::transform(Matrix4& m) {
    Transform<VertexHasNormal::value>::call(this->vertices, m);
}

函数模板transform()是对一系列顶点进行实际变换的函数。我将其分解是因为我认为可以分解但不需要分解它有点可爱。它也不需要使用指向成员的指针auto,等等。Transform类型只是使用的辅助类型,因为函数模板不能部分特化。它专门用于顶点类型是否具有normal成员的特征。最后,Mash<T>::transform()只是分派到专门功能的适当版本。

唯一需要做的是normal在定义另一个具有成员的顶点时添加新的特征特化。这可能是不可取的。在这种情况下,可以确定结构是否具有normal使用类型特征调用的可访问数据成员。但是,我不认为我可以从头顶输入这个。实现它的基本思想是利用替换失败不是错误(“SFINAE”)这一事实,并且如果测试类型确实具有必要的成员,则可以设置事物以在两个潜在成员之间创建歧义。有一个 Boost 组件可以做到这一点,但如果您需要自己创建它,大约需要 10 行代码。

于 2012-10-03T18:54:27.817 回答
1

您可能想要创建结构类并从两个基类派生它们,一个是正常的,一个是没有的。然后,您可以使用模板专业化在这两个基类之间进行选择(而不是所有顶点类)。

于 2012-10-03T18:04:11.803 回答