Porting EggOctree to C++

Good day !

I’m currently trying to port the EggOctree tool (Egg Octree) to C++. I think I was able to put everything together correctly for the most part, however I get a segmentation fault when using the triangulate_polygons. I haven’t been able to make sense of it.

I have some code to reproduce the issue:

#include <panda3d/eggData.h>
#include <panda3d/eggGroupNode.h>
#include <panda3d/eggGroup.h>
#include <panda3d/eggPolygon.h>
#include <boost/concept_check.hpp>

#define MAX_3(a, b, c) (a > b && a > c ? a : (b > a && b > c ? b : c))

#define until(condition)  while (!(condition))
#define unless(condition) if (!(condition))

struct Egg2EggOctree
{
  struct Cell
  {
    LPoint3f         min, max;
    PT(EggGroupNode) group;
  };

  typedef std::vector<std::vector<std::vector<Cell> > > Cells;
public:
  static void Build(const std::string& source_path, const std::string& dest, LPoint3f cell_size, bool collide = false)
  {
    Egg2EggOctree(source_path, dest, cell_size, collide);
  }

private:
  Egg2EggOctree(const std::string& source_path, const std::string& dest, LPoint3f cell_size, bool collide)
  {
    std::cout << "[EggOctree] Processing '" << source_path << "'. Output: '" << dest << '\'' << std::endl;
    PT(EggData)       egg        = new EggData();
    PT(EggData)       egg_target = new EggData();
    PT(EggGroupNode)  source;
    PT(EggVertexPool) vertex_pool;
    LPoint3f          minBB, maxBB;
    int               n_vertexes;

    std::cout << ":: Loading egg file" << std::endl;
    egg->read(source_path);
    std::cout << "> Done" << std::endl;
    egg->get_first_child();
    source      = reinterpret_cast<EggGroupNode*>(egg->get_next_child()); // the source node must contain a VertexPool and a list of Polygons
    if (source->get_class_type() != egg->get_next_child()->get_type())
    {
      std::cout << "Oh shit ! It's not an EggGroupNode" << std::endl;
      return ;
    }
    egg_target->set_coordinate_system(egg->get_coordinate_system());
    std::cout << ":: Triangulating polygons" << std::endl;

    // The following line crashes:
    source->triangulate_polygons(EggGroupNode::T_polygon | EggGroupNode::T_convex);

    std::cout << "> Done" << std::endl;
    vertex_pool = reinterpret_cast<EggVertexPool*>(source->get_first_child());
    n_vertexes  = vertex_pool->get_highest_index();
    for (int i = 0 ; i < n_vertexes ; ++i)
    {
      PT(EggVertex) vertex     = vertex_pool->get_vertex(i);
      LVertexd      vertex_pos = vertex->get_pos3();

      // Set the X, Y or Z components
      #define SET_XYZ(coord) \
        if (vertex_pos.get_##coord() < minBB.get_##coord()) \
        { minBB.set_##coord(vertex_pos.get_##coord()); }
      SET_XYZ(x)
      SET_XYZ(y)
      SET_XYZ(z)
      #undef  SET_XYZ
    }
    {
      LPoint3f bbox_size = maxBB - minBB;

      ncx       = ceil(bbox_size.get_x());
      ncy       = ceil(bbox_size.get_y());
      ncz       = ceil(bbox_size.get_z());
      depth     = MAX_3(ncx, ncy, ncz);
      cells.resize(ncx);
      for (int x = 0 ; x < ncx ; ++x)
      {
        cells[x].resize(ncy);
        for (int y = 0 ; y < ncy ; ++y)
        {
          cells[x][y].resize(ncz);
          for (int z = 0 ; z < ncz ; ++z)
          {
            Cell              cell;
            std::stringstream group_name;

            cell.min = LPoint3f(minBB.get_x() + x       * cell_size.get_x(),
                                minBB.get_y() + y       * cell_size.get_y(),
                                minBB.get_z() + z       * cell_size.get_z());
            cell.max = LPoint3f(minBB.get_x() + (x + 1) * cell_size.get_x(),
                                minBB.get_y() + (y + 1) * cell_size.get_y(),
                                minBB.get_z() + (z + 1) * cell_size.get_z());
            group_name << "leaf_" << x << '_' << y << '_' << z;
            cell.group = new EggGroupNode(group_name.str());
            cells[x][y][z] = cell;
          }
        }
      }

      if (collide)
      {
        for (int x = 0 ; x < ncx ; ++x)
        {
          for (int y = 0 ; y < ncy ; ++y)
          {
            for (int z = 0 ; z < ncz ; ++z)
            {
              // TODO Find out what should be happening here.
              // cells[x][y][z].group->add_object_type("barrier");
            }
          }
        }

        // Iterate on the <Polygon>s (should be triangles)
        PT(EggNode) node = source->get_next_child();
        until (node.is_null())
        {
          if (node->is_of_type(EggPolygon::get_class_type()))
          {
            PT(EggPolygon) poly = reinterpret_cast<EggPolygon*>(node.p());

            //
            // Get the triangle center
            //
            LVertexd poly_center(0, 0, 0);

            for (unsigned short i = 0 ; i < 3 ; ++i)
            {
              LVertexd pos3 = poly->get_vertex(i)->get_pos3();

              poly_center += pos3;
            }
            poly_center /= 3.f;

            //
            // Add the triangle to the corresponding cell group
            //
            #define INIT_CXYZ(coord) floor(poly_center.get_##coord() - minBB.get_##coord()) / cell_size.get_##coord()
            int cx = INIT_CXYZ(x);
            int cy = INIT_CXYZ(y);
            int cz = INIT_CXYZ(z);
            #undef  INIT_CXYZ
            cells[cx][cy][cz].group->add_child(poly);
          }
          node = source->get_next_child();
        }
        egg_target->add_child(vertex_pool);
        n_leaves = Recur(egg_target.p(), 0, 0, 0, 0);
        std::cout << n_leaves << " leaves added" << std::endl;
      }
    }
    std::cout << ":: Generating output file" << std::endl;
    egg_target->write_egg(dest);
    std::cout << "> Done" << std::endl;
  }

  template<typename T>
  int Recur(T* node, int depth, int x, int y, int z)
  {
    if (depth < this->depth)
    {
      PT(EggGroup) nnode    = new EggGroup("");
      int          delt     = pow(2, this->depth - depth - 1);
      int          n_childs = 0;
      
      n_childs += Recur(nnode.p(), depth + 1, x, y, z);
      n_childs += Recur(nnode.p(), depth + 1, x + delt, y, z);
      n_childs += Recur(nnode.p(), depth + 1, x, y + delt, z);
      n_childs += Recur(nnode.p(), depth + 1, x + delt, y + delt, z);
      n_childs += Recur(nnode.p(), depth + 1, x, y, z + delt);
      n_childs += Recur(nnode.p(), depth + 1, x + delt, y, z + delt);
      n_childs += Recur(nnode.p(), depth + 1, x, y + delt, z + delt);
      n_childs += Recur(nnode.p(), depth + 1, x + delt, y + delt, z + delt);
      if (n_childs > 0)
        node->add_child(nnode);
      return (n_childs);
    }
    {
      // We are in a leaf
      if (x < ncx && y < ncy && z < ncz)
      {
        node->add_child(cells[x][y][z].group);
        return (1);
      }
      else
        return (0);
    }
  }
  
  Cells cells;
  int   n_leaves;
  int   depth;
  int   ncx, ncy, ncz;
};

int main(int argc, char** argv)
{
  if (argc == 4 && std::string(argv[1]) == "--egg-octree")
  {
    Egg2EggOctree::Build(argv[2], argv[3], LPoint3f(5, 5, 5), true);
    return (0);
  }
  return (1);
}

I figured maybe the type check was wrong, but as far as I know it’s supposed to be an EggGroupNode and I checked for that type (unless I did that check wrong).
What else could possibly be responsible ?

On another matter, I also haven’t been able to find what the addObjectType line was doing in the Python script, so I didn’t find any way to do it in C++. If anyone has a clue what it does it might prove useful !

Thanks !

The correct way to cast the pointer is as follows:

    source      = DCAST(EggGroupNode, egg->get_next_child());

“source” will be set to NULL if the type could not be cast.

Although in your case, you may want to use this instead:

    DCAST_INTO_V(source, egg->get_next_child());

which automatically outputs an error and returns from the function if the typecast was invalid. (the V stands for ‘void’, there is also a DCAST_INTO_R that takes an additional parameter in order to specify a return value).