Предположим, у нас есть 3D-сетка с текстурными координатами для каждой вершины, поэтому, если я сделаю ее развернутой, я получу что-то вроде этого (игнорируя красный квадрат):
Сейчас я пытаюсь найти подходящий алгоритм для уникальной идентификации этих областей с использованием вершинных UV и сохранения атрибута с этим уникальным значением id. Идея состоит в том, чтобы использовать это значение в качестве индекса для таблицы цветов и получить что-то вроде этого (сделано вручную):
Я попытался перебрать каждую вершину и найти «несвязанные» треугольники, сравнивающие координаты текстуры, но порядок индексов сетки кажется не связанным с тем, как расположены UV, или я не применяю правильную формулу. У меня нет сомнений относительно того, как сохранить и передать это значение в шейдер или что-то еще, сомневаюсь, как узнать «область», к которой принадлежит вершина, или, в конечном счете, пиксель.
Благодарю.
ОБНОВИТЬ:
Данные, используемые для визуализации сетки, представляют собой список вершин (GL_VERTEX_BUFFER) плюс список индексов (GL_ELEMENT_ARRAY). Сетка отображается как GL_TRIANGLES, и каждая вершина является структурой, подобной этой:
struct Vertex
{
float x, y, z;
float nx, ny, nz;
float tcx, tcy;
float regionId; //the attribute I want to fill
};
struct MPUVRegionVertex
{
float x, y;
int faceId, regionId;
};
ОБНОВЛЕНИЕ 2: Я создал новый массив вершин MPUVRegionVertex, с элементом для каждого индекса (не для каждой уникальной вершины). После ответа @ CsabaBálint я получил следующий код:
MPUVRegionVertex* uvVertexData = new MPUVRegionVertex[indexCount];
for(int ic = 0; ic < indexCount / 3; ic++)
{
for(int vc = 0; vc < 3; vc++)
{
uvVertexData[3*ic+vc].x = vertexData[indexData[3*ic+vc]].tcx;
uvVertexData[3*ic+vc].y = vertexData[indexData[3*ic+vc]].tcy;
uvVertexData[3*ic+vc].faceId = ic;
}
}
std::vector<std::forward_list<int> > graph(indexCount);
for(int t1=0;t1 < indexCount; ++t1)
{
for(int t2 = t1 + 1; t2 < indexCount; ++t2)
{
if (uvVertexData[t1].faceId == uvVertexData[t2].faceId)
{
graph[t1].push_front(t2);
graph[t2].push_front(t1);
}
}
}
std::forward_list<int> stack;
std::vector<int> component(indexCount);
std::set<int> notvisited;
for(int nv = 0; nv < indexCount; nv++)
{
notvisited.insert(nv);
}int k = 0;
while(notvisited.size() > 0)
{
stack.push_front(*notvisited.begin());
notvisited.erase(notvisited.begin());
while(!stack.empty())
{
//SOMETHING WRONG HERE
int temp = stack.front();
notvisited.erase(temp);
stack.pop_front();
component[temp] = k;
stack.merge(graph[temp]);
graph[temp].clear();
}
k++;
}
Результатом является разное k каждые три индекса, это означает, что k ++ вызывается для каждого нового треугольника, поэтому я что-то упускаю в алгоритме: S.
component[0]=0
component[1]=0
component[2]=0
component[3]=1
component[4]=1
component[5]=1
component[6]=2
component[7]=2
component[8]=2
component[9]=3
...
component[1778]=592
component[1779]=593
component[1780]=593
component[1781]=593
Некоторая информация о сетке:
Size of shape[0].indices: 1782
shape[0].positions: 1242
shape[0].texcoords: 828
shape[0].normals: 1242
ОБНОВЛЕНИЕ 3
Для получения дополнительной информации, есть только одна UV-координата для каждой вершины.
Отчисления / Правила до сих пор:
Если я правильно понял, это правильная информация для реализации нерекурсивного обхода графа. Мне нужно перебрать и сохранить как связанную, так и несвязанную вершину, все связанные вершины будут частью текущей области, все не будет проверяться снова с уже подключенными, первая итерация сохраняет первые вершины треугольника, вторая — все вершины треугольники, которые «касаются» первого треугольника, продолжаются до тех пор, пока итерация не даст новой связанной вершины (оптимизация здесь, если мы проверяем только список вершин, добавленный в последней итерации), новая добавленная вершина означает, что пришло время увеличивать regionId и начните снова с первой несвязанной вершины.
Я постараюсь реализовать поиск, следуя этому проекту.
Сейчас я пытаюсь найти подходящий алгоритм для уникальной идентификации этих областей с использованием вершинных UV и сохранения атрибута с этим уникальным значением id.
Создание графика
Сделайте идентификаторы для вершин и граней (пронумеруйте их). Но убедитесь, что одни и те же вершины получают одинаковые идентификаторы, сравните их по УФ или положению.
Создать вектор: std::vector<int> vertexToFace;
vertexToFace[i]==j
означает, что i-я вершина находится на лице j.
Тогда две вершины являются соседями, если они находятся на одной грани.
Затем создайте std::vector<std::forward_list<int> > graph;
Сохраните вершины как векторный индекс и добавьте соседей. (O (n ^ 2) сложность)
Чтобы сделать это, вы должны взять i-ую вершину, и для каждого j вам нужно проверить погоду, они на одном лице. Немного оптимизированная версия:
for(int i=0; i<n; ++i) for(int j=i+1; j <n ++j)
if (vertexToFace[i] == vertexToFace[j])
{
graph[i].push_front(j);
graph[j].push_front(i);
}
Это O (n ^ 2), но его легко реализовать. Тяжелее, но быстрее требуется еще один вектор: std::vector<std::array<int,3>> faceToVertex;
Таким образом, из i-й вершины вы можете получить доступ к ее соседям в постоянное время. В любом случае, мы построили график, в котором мы ищем подключенные компоненты, что легко с поиск в глубину.
Реализация алгоритма связанных компонентов
Чтобы реализовать это, вы должны сделать еще один вектор: std::vector<bool> visited(n,false);
, и еще один std::vector<int> component(n)
, Решение вашей проблемы будет в этом последнем.
Алгоритм прост, начать с вершины 0 и установить visited[0] = true;
а также component[0]=0;
, Затем для каждого невидимого соседа сделайте точно так же, как и для соседа i (некоторый элемент forward_list) if (!visited[i]) component[i] = 0;
затем сделайте то же самое. Он останавливается, когда все элементы компонента становятся посещенными. Итак, вам нужно найти не посещенный элемент и повторить вышеописанное, но знать, что вы делаете компонент 1 и так далее. Пример:
int l, k=0;
while(l!=n)
{
l=0;
while(visited[l]) l++;
fill_from(graph, visited, component, l, k);
++k;
}
Я думаю, вы поняли, так что: (псевдокод)
void fill_from(graph, visited, component, l, k)
{
if(visited[l]) return;
component[l] = k;
for(auto &i : graph[l])
fill_from(graph,visited,component,i,k);
}
Тогда мы закончили с задачей, но это еще не самое быстрое решение.
Более быстрый алгоритм
Чтобы получить еще быстрее, мы должны избавиться от рекурсии, и нам не нужен график впоследствии, используйте std::forward_list<int>
для стека. Вставьте первую вершину в стек. Затем вытолкните одну вершину, установите ее компонент на k. От себя все его соседи в стек, затем удалите соседей. Другими словами, добавьте список соседей в стек (очень быстрая операция). Повторяйте до тех пор, пока стек не станет пустым.
Таким образом, мы не будем делать бесконечный цикл, потому что если мы вернемся к той же вершине, у нее не будет соседей, и мы уже посетили их. Поэтому нет необходимости в посещаемом векторе. Мы можем установить элемент вектора компонента несколько раз, но всегда на одно и то же значение, так зачем проверять его?
Однако, если у нас нет посещенного вектора, тогда будет сложнее искать другую вершину, которую мы не посетили. Хотя мы могли бы искать некоторую вершину в графе, у которой все еще есть соседи, есть лучшее решение.
Создайте std::set<int> notvisited();
Для тех точек, которые еще не посещены. Сначала он должен содержать все идентификаторы вершин, а затем каждый раз, когда мы устанавливаем идентификатор компонента, мы пытаемся удалить вершину из notvisited
задавать. Мы повторяем получение вершины из набора и запускаем алгоритм fill_from (), пока набор не станет пустым, в то же время у нас будут все идентификаторы компонентов.
ОБНОВЛЕНИЕ: Использование обновленной информации о том, как хранится сетка.
Если у вас нет равных элементов в «списке вершин» (зачем вам), то индекс вершины — это ее позиция в массиве. Таким образом, идентификаторы для вершин сделаны.
Идентификаторы для треугольников или граней находятся в «списке индексов», позвольте мне назвать этот массив int listOfIndices[];
для j-й грани связанные с ним вершины listOfIndices[3*j + 0]
, listOfIndices[3*j + 1]
а также listOfIndices[3*j + 2]
, Чтобы сделать первый вектор, вам нужно сделать следующее:
std::vector<int> vertexToFace(num_of_verteces); //previously n
for(int j = 0; j < num_of_faces; ++j)
{
vertexToFace[listOfIndices[3*j + 0]]=j;
vertexToFace[listOfIndices[3*j + 1]]=j;
vertexToFace[listOfIndices[3*j + 2]]=j;
}
(Алгоритм построения обратной зависимости); Обратите внимание, что в этом случае вам даже не нужен другой faceToVertex
массив, потому что у вас уже есть (listOfIndices
), вы просто должны индексировать его по-разному (делить на 3 каждый раз).
Хорошо, следуя ранее упомянутым пунктам и благодаря Csaba Bálint за начальные подсказки, я нашел решение:
MPUVRegionVertex* uvVertexData = new MPUVRegionVertex[indexCount];
for(int ic = 0; ic < indexCount / 3; ic++)
{
for(int vc = 0; vc < 3; vc++)
{
uvVertexData[3*ic+vc].x = vertexData[indexData[3*ic+vc]].tcx;
uvVertexData[3*ic+vc].y = vertexData[indexData[3*ic+vc]].tcy;
uvVertexData[3*ic+vc].faceId = ic;
}
}
std::set<int> notAssigned;
for(int nv = 0; nv < indexCount; nv++)
{
notAssigned.insert(nv);
}
std::forward_list<int> addedInLastIterationElements;
int currentRegion = 0;
//while there are not assigned vertex
while (notAssigned.size() > 0)
{
//the first not assigned element simulate that was "added" to the current region in the last check
addedInLastIterationElements.push_front(*notAssigned.begin());
//this first has the new current region as it's regionId
uvVertexData[*notAssigned.begin()].regionId = currentRegion;
//and becomes assigned
notAssigned.erase(notAssigned.begin());
do
{
//will store the elements added in the next iteration
std::forward_list<int> newRegionElements;
//iterate not assigned elements
for(int currentElement : notAssigned)
{
//iterate elements added to the current region in the last iteration
for(int currentRegionElement : addedInLastIterationElements)
{
//compare if this vertex belongs to the same region of some of the recently added
if((uvVertexData[currentElement].x == uvVertexData[currentRegionElement].x &&
uvVertexData[currentElement].y == uvVertexData[currentRegionElement].y) ||
(uvVertexData[currentElement].faceId == uvVertexData[currentRegionElement].faceId))
{
//store as an element added this iteration
newRegionElements.push_front(currentElement);
//store the current region
uvVertexData[currentElement].regionId = currentRegion;
}
}
}
//remove new elements from the notAssigned list
for(int assigned : newRegionElements)
{
notAssigned.erase(assigned);
}
//replace the elements added the previous iteration for the last iteration
addedInLastIterationElements = newRegionElements;
//prepare for new elements
newRegionElements.clear();
}
//if there is no match in the last iteration, means all remaining vertex belongs to another region
while(!addedInLastIterationElements.empty());
//next region
currentRegion++;
}
Если рендеринг regionId сгенерирован с помощью таблицы цветов, я получаю желаемый результат (обратите внимание, что цвета варьируются только в 0.1 на канал, но здесь есть 10 разных цветов):
Конечно, алгоритм может быть оптимизирован, но он работает за разумное время и использует память, так что пока все в порядке.