Предоставление нескольких повторяющихся интерфейсов

#c #loops

#c #циклы

Вопрос:

Scene Содержит список Shape .

Каждый Shape содержит:

  • Его std::vector of Vertex (с нормалью поверхности, texcoord, позицией с 3 пробелами)
  • Его std::vector of Triangle (связанные вершины, для пересечения сетки)

Я хотел бы сделать Scene объект итеративным как в виде коллекции Vertex , так и в виде коллекции Triangle , не таким беспорядочным способом.

В настоящее время требуется обойти треугольники: (синтаксис C # здесь):

 foreach( Shape shape in Scene )
{
    foreach( Mesh mesh in shape.meshGroup.meshes )
    {
        foreach( Triangle tri in mesh.tris )
        {
            // work with tri
        }
    }
}
  

Тройная вложенность for не очень приятна для просмотра, и, конечно, синтаксис C намного хуже, используя либо счетчики i, j и k, либо используя ::iterator s..

Для доступа к каждому Vertex :

 foreach( Shape shape in Scene )
{
    foreach( Mesh mesh in shape.meshGroup.meshes )
    {
        foreach( Vertex v in mesh.verts )
        {
            // work with v
        }
    }
}
  

Поскольку для начала обходить все треугольники / вершины дорого, каков наилучший способ сделать это? (Предположим, что необходимо использовать каждый Triangle / Vertex , поэтому нет необходимости в алгоритмах пространственного разделения и т.д.)

Вы можете использовать любые функции C 0x (с поддержкой VS-2010), включая lambda.

Комментарии:

1. Это проблема, потому что вам нужно повторять одни и те же циклы в нескольких местах вашего кода?

2. Вы бы сделали for (Vertex amp; v : shape.vertices()) /*...*/ , где Shape::vertices возвращает диапазон. Аналогично для треугольников. Как вы реализуете Shape::vertices — это совершенно другой вопрос.

3. В качестве дополнения, я уже некоторое время скучаю yield по C .

4. Не стали бы вы массово пересчитывать вершины, поскольку каждая вершина может принадлежать произвольно многим треугольникам?

5. @KerrekSB Краткий ответ: Нет :). Подробности: verts Массив — это в основном массив вершин, используемый для рисования. Он может быть проиндексирован, что означает, что вершины не будут повторяться (существует отдельный массив с именем indices , который указывает порядок рисования.) tris Список является вспомогательным, используется только для таких вещей, как пересечение сетки.

Ответ №1:

Вы могли бы создавать функции, которые выполняют итерацию по всем элементам и вызывают объект функции:

 template <typename F>
inline void for_each_vertex(Sceneamp; scene,F f)
{
  for (Shapeamp; shape : scene) {
    for (Meshamp; mesh : shape.meshGroup.meshes) {
      for (Vertexamp; vertex : mesh.verts) {
        f(vertex);
      }
    }
  }
}

template <typename F>
inline void for_each_triangle(Sceneamp; scene,F f)
{
  for (Shapeamp; shape : scene) {
    for (Meshamp; mesh : shape.meshGroup.meshes) {
      for (Triangleamp; triangle : mesh.tris) {
        f(triangle);
      }
    }
  }
}
  

Теперь вы можете сделать

 for_each_vertex(scene,[](Vertexamp; vertex)(/* work with vertex */});
for_each_triangle(scene,[](Triangleamp; triangle)(/* work with triangle */});
  

Это должно иметь ту же производительность, что и ваш исходный код, но вам не придется писать одну и ту же структуру цикла несколько раз в вашей программе.

Комментарии:

1. Однако обратите внимание, что эта конструкция инвертирует логику итерации и делает ее немного менее полезной (например, это не позволяет вам использовать какой-либо из стандартных алгоритмов диапазона).

2. Я думал об этом, но не приведет ли лямбда к огромным издержкам производительности (более 1 000 000 с лишним вершин)?

3. Ах да, вы встроили это, хм, позвольте мне протестировать это

Ответ №2:

Что я обычно делаю, чтобы выделить отдельные «логические контейнеры» в большой (мульти-) контейнерный класс:

Я определяю метод ‘getMeshRange() , getShapeRange()` и т.д.

Если вы используете библиотеку boost range, вы могли бы вернуть boost::iterator_range<const Shape*> , boost::sub_range<std::vector<Vertex> > и т.д.

На практике, IIRC, чтобы удовлетворить концепции диапазона, вы можете просто вернуть std::pair<const_iterator, const_iterator> .

Диапазон может использоваться следующим образом:

 for (It it = std::begin(X.getMeshRange()); it!= std::end(X.getMeshRange());   it)
{ 
    // use *it
}
  

или в C 0x

 for (autoamp; mesh : X.getMeshRange())
{
    // use mesh
}
  

С помощью алгоритма Boost Range вы можете делать все, что могли бы с «обычным» контейнером:

 const Shapeamp; shape = *std::min_element(X.getShapeRange());
  

Комментарии:

1. Я думаю, что вопрос больше о том, как связать все контейнеры вершин, чтобы получить один повторяющийся диапазон.

2. Ну, на самом деле было бы полезно разделить повторяющиеся диапазоны. Я не хочу использовать Boost.

3. @bobobobo Помните: вам не обязательно использовать boost. Пара итераторов отлично моделирует диапазон ( for (const Tamp; i: make_pair(it1, it2)) будет хорошо работать на c 0x)

Ответ №3:

Если вся обработка выполняется во внутреннем цикле, вы можете создать пользовательский итератор, который выполняет итерации по всем трем уровням и создает (Shape*,Mesh*,Vertex*) тройки, обрабатывая логику для перехода, например, к следующему, Mesh как только все Vertex элементы будут повторены для текущего Mesh .

Если вы хотите выполнить некоторую обработку в среднем или внешних циклах, вы могли бы добавить несколько перехватов или, например, вернуть (Shape*,NULL,NULL) тройку в первый раз через новый Shape .