首页 > 解决方案 > 递归函数调用中的数据成员更新问题

问题描述

我可能没有看到明显的。我有一个对象层次结构,并希望对层次结构中的所有几何对象应用变换。基类Node如下:

class Node
{
    Matrix4 node_matrix;

    // ... other stuff
}

它包含一个基线矩阵 node_matrix,由程序修改:

for (size_t a = 0; a < level.objects.size(); ++a)
{
     ApplyTransform(level.objects[a].node_matrix);
     level.objects[a].Update(level.objects[a].node_matrix);
     // ... do more stuff
}

objects[a] 是一个 GameObject 和根节点,它的矩阵被 ApplyTransform() 修改。然后,我使用下面类中定义的递归函数 Update() 在根节点上调用 update 方法:

class GameObject : public Node
{
public:
    GameObject *parent;
    std::vector<GameObject*> children;
    Matrix4 current_node_matrix;
    GeometryNode *geometry;
       
    //Default ctor
    GameObject() : parent(nullptr) {}
    
    //Copy ctor and assignment
    GameObject(const GameObject&) = default;
    GameObject& operator=(const GameObject&) = default;
    
    //Move ctor and assignment
    GameObject(GameObject &&) = default;
    GameObject& operator=(GameObject &&) = default;
    
    //Default dtor
    ~GameObject() = default;
    
    void Update(Matrix4 nm)
    {
        if (parent != nullptr)
        {
            current_node_matrix = nm * node_matrix;
        }
        else
        {
            current_node_matrix = nm;
        }
          
        for (size_t i = 0; i < children.size(); ++i)
        {
            children[i]->Update(current_node_matrix);
        }
    }
};

这很好,但我认为我可以通过简单地编写来避免将矩阵传递给 Update() 方法;

void GameObject::Update()
{
     if (parent != nullptr)
     {
         current_node_matrix = parent->current_node_matrix * node_matrix;
     }
     else
     {
         current_node_matrix = node_matrix;
     }
          
     for (size_t i = 0; i < children.size(); ++i)
     {
         children[i]->Update();
     }
}

我验证了 current_node_matrix 在第一次调用(根节点)中确实按预期进行了修改,但是在使用时

parent->current_node_matrix

在递归调用中,孩子看到原始(未修改)矩阵。我验证了很多事情,只是不明白为什么 current_node_matrix 在父对象中正确更新但对子对象不可见(父指针指向正确的对象,我也检查过)。

我可能在房间里看不到大象。有什么想法吗?

我在下面添加了一个功能齐全的可重现示例。这与代码中的作用基本相同,并且以相同的方式失败:调用 Update1 有效,而调用 Update2 失败。

提前致谢,

克里斯

最小可重复的例子

#include <iostream>
#include <vector>

enum class Matrix4Type {identity};

class Matrix4
{
public:
    
    float M[4][4]{0};
    
    //Default ctor
    Matrix4() = default;
    
    Matrix4(Matrix4Type);
    
    //Copy ctor and assignment
    Matrix4(const Matrix4&) = default;
    
    Matrix4& operator=(const Matrix4&) = default;
    
    //Move ctor and assignment
    Matrix4(Matrix4&&) = default;
    Matrix4& operator=(Matrix4&&) = default;
    
    friend Matrix4 operator*(const Matrix4& mult1, const Matrix4& mult2);
    Matrix4 operator*(const float scale);
    
    friend std::ostream& operator<<(std::ostream &os, const Matrix4 &m);
    
};

Matrix4::Matrix4(Matrix4Type mtype)
{
    switch (mtype)
    {
        case Matrix4Type::identity:
        {
            M[0][0] = 1.0f;
            M[1][1] = 1.0f;
            M[2][2] = 1.0f;
            M[3][3] = 1.0f;
            break;
        }
    }
}

Matrix4 operator*(const Matrix4& mult1, const Matrix4& mult2)
{
    Matrix4 retmat;
    
    for (uint8_t i = 0; i < 4; ++i)
    {
        for (uint8_t j = 0; j < 4; ++j)
        {
            for (uint8_t k = 0; k < 4; ++k)
            {
                retmat.M[i][j] += mult1.M[i][k] * mult2.M[k][j];
            }
        }
     }
    
    return retmat;
}

Matrix4 Matrix4::operator*(const float scale)
{
    Matrix4 retmat = *this;
    
    for (uint8_t i = 0; i < 4; ++i)
    {
        for (uint8_t j = 0; j < 4; ++j)
        {
            retmat.M[i][j] *= scale;
        }
    }
    
    return retmat;
}

std::ostream& operator<<(std::ostream &os, const Matrix4 &m)
{
    for (int i = 0; i < 4; ++i)
    {
        for (int j = 0; j < 4; ++j)
        {
            os << m.M[i][j] << " ";
        }
        os << std::endl;
    }
    
    os << "-------" << std::endl;
    return os;
}


class Node
{
public:
    Matrix4 node_matrix;
    
    //Default ctor
    Node() = default;
    
    //Custom ctor
    Node(const Matrix4 &m) : node_matrix(m) { }
    
    //Copy ctor and assignment
    Node(const Node &pt) = default;
    Node& operator=(const Node &pt) = default;
    
    //Move ctor and assignment
    Node(Node &&mv) = default;
    Node& operator=(Node &&mv) = default;
    
    //Default dtor
    ~Node() = default;
    
    
    inline void ApplyTransform(const Matrix4 &trans) noexcept
    {
        node_matrix = node_matrix * trans;
    }
    
};


class GameObject : public Node
{
public:
    GameObject *parent;
    std::vector<GameObject*> children;
    Matrix4 current_node_matrix;
    
    //Default ctor
    GameObject() : parent(nullptr) {}
    
    //Copy ctor and assignment
    GameObject(const GameObject&) = default;
    GameObject& operator=(const GameObject&) = default;
    
    //Move ctor and assignment
    GameObject(GameObject &&) = default;
    GameObject& operator=(GameObject &&) = default;
    
    //Default ctor
    ~GameObject() = default;
    
    void Update1(Matrix4 nm)
    {
        if (parent != nullptr)
        {
            std::cout << "Parent" << std::endl << nm << std::endl;
            current_node_matrix = nm * node_matrix;
        }
        else
        {
            std::cout << "Root" << std::endl;
            current_node_matrix = nm;
        }
        
        std::cout << current_node_matrix << std::endl;
        
        for (size_t i = 0; i < children.size(); ++i)
        {
            children[i]->Update1(current_node_matrix);
        }
    }
    
    void Update2()
    {
        if (parent != nullptr)
        {
            std::cout << "Parent" << std::endl << parent->current_node_matrix << std::endl;
            current_node_matrix = parent->current_node_matrix * node_matrix;
        }
        else
        {
            std::cout << "Root" << std::endl;
            current_node_matrix = node_matrix;
        }
        
        std::cout << current_node_matrix << std::endl;
        
        for (size_t i = 0; i < children.size(); ++i)
        {
            children[i]->Update2();
        }
    }
    
    void AddChild(GameObject *toadd)
    {
        toadd->parent = this;
        children.push_back(toadd);
    }
    
    void SetCoordinateSystem(const Matrix4&m)
    {
        node_matrix = m;
    
    }
};


int main(int argc, const char * argv[])
{

    using std::cout;
    using std::endl;
    
    std::vector<GameObject> objects;
    GameObject base1, child1, child2, second_child1;
    
    Matrix4 temp1 = Matrix4(Matrix4Type::identity);
    Matrix4 temp2 = Matrix4(Matrix4Type::identity) * 2.0f;
    
    base1.SetCoordinateSystem(temp1);
    child1.SetCoordinateSystem(temp1);
    child2.SetCoordinateSystem(temp1);
    second_child1.SetCoordinateSystem(temp1);
    
    base1.AddChild(&child1);
    base1.AddChild(&child2);
    child2.AddChild(&second_child1);
    
    objects.push_back(base1);
    
    objects[0].ApplyTransform(temp2);
    
    objects[0].Update1(objects[0].node_matrix);
    //objects[0].Update2();
    
    return 0;
}

标签: c++recursiongame-engine

解决方案


我已经查看了您的最小可复制示例。 Update2()很好。

问题出在vector<GameObject> objects. 当你这样做时objects.push_back(base1),而不是持有base1你所期望的行为,会objects复制一个新对象base1,然后包含新对象。

要使用矢量容器,我建议您要么

  1. makeobjects包含指向游戏对象的指针 ( vector<GameObject*> objects)
  2. 将游戏对象包含在objects.

推荐阅读