@Quark_Hell
C++ программист

Как разделить mesh на отдельные сегменты?

Есть у меня такой вот мешь:
63bc1e7c8c5be805848145.png

В блендере это 1 мешь. Задача разделить его на не связанные друг с другом части. Таких в итоге, в данном случае, получается 7. Значит на выходе должно быть 7 мешей.

Для этого я написал вот такой код:

inline bool Collision::IsNeighbor(Triangle* current, Triangle* neighbor) {
	//current Vert 1
	if (current->Vert1 == neighbor->Vert1) {
		return true;
	}
	else if (current->Vert1 == neighbor->Vert2)
	{
		return true;
	}
	else if (current->Vert1 == neighbor->Vert3)
	{
		return true;
	}
	//current Vert 2
	else if (current->Vert2 == neighbor->Vert1)
	{
		return true;
	}
	else if (current->Vert2 == neighbor->Vert2)
	{
		return true;
	}
	else if (current->Vert2 == neighbor->Vert3)
	{
		return true;
	}
	//current Vert 3
	else if (current->Vert3 == neighbor->Vert1)
	{
		return true;
	}
	else if (current->Vert3 == neighbor->Vert2)
	{
		return true;
	}
	else if (current->Vert3 == neighbor->Vert3)
	{
		return true;
	}
	return false;
}


inline bool Collision::SeparateCollider() {
	Triangle currentTriangle;
	Triangle neighborTriangle;

	size_t countOfPrimitives;
	while (Collision::GeneralCollider->Points.size() != 0)
	{
		Collision::PrimitivesCollider.push_back(new Collider);
		countOfPrimitives = Collision::PrimitivesCollider.size() - 1;

		currentTriangle.Vert1 = Collision::GeneralCollider->Points[0];
		currentTriangle.Vert2 = Collision::GeneralCollider->Points[1];
		currentTriangle.Vert3 = Collision::GeneralCollider->Points[2];

		Collision::PrimitivesCollider[countOfPrimitives]->Points.push_back(currentTriangle.Vert1);
		Collision::PrimitivesCollider[countOfPrimitives]->Points.push_back(currentTriangle.Vert2);
		Collision::PrimitivesCollider[countOfPrimitives]->Points.push_back(currentTriangle.Vert3);

		//Erease current triangle from general buffer
		Collision::GeneralCollider->Points.erase(Collision::GeneralCollider->Points.begin(), Collision::GeneralCollider->Points.begin() + 3);

		size_t it = 3;
		do
		{
			for (size_t jt = 0; jt < Collision::GeneralCollider->Points.size(); jt += 3)
			{
				//Definition neighbor triangle
				neighborTriangle.Vert1 = Collision::GeneralCollider->Points[jt];
				neighborTriangle.Vert2 = Collision::GeneralCollider->Points[jt + 1];
				neighborTriangle.Vert3 = Collision::GeneralCollider->Points[jt + 2];

				//Check is neighbor triangle really neighbor for current triangle
				if (IsNeighbor(&currentTriangle, &neighborTriangle)) {
					Collision::PrimitivesCollider[countOfPrimitives]->Points.push_back(neighborTriangle.Vert1);
					Collision::PrimitivesCollider[countOfPrimitives]->Points.push_back(neighborTriangle.Vert2);
					Collision::PrimitivesCollider[countOfPrimitives]->Points.push_back(neighborTriangle.Vert3);

					//Erease neighbor triangle from general buffer
					Collision::GeneralCollider->Points.erase(Collision::GeneralCollider->Points.begin() + jt, Collision::GeneralCollider->Points.begin() + jt + 3);

          //Set neighbor triangle as current triangle
					currentTriangle.Vert1 = neighborTriangle.Vert1;
					currentTriangle.Vert2 = neighborTriangle.Vert2;
					currentTriangle.Vert3 = neighborTriangle.Vert3;

					it += 3;
					jt = -3;
				}
			}

			it -= 3;

			if (it != 0) {
        //Add last triangle of part
				currentTriangle.Vert1 = Collision::PrimitivesCollider[countOfPrimitives]->Points[it - 3];
				currentTriangle.Vert2 = Collision::PrimitivesCollider[countOfPrimitives]->Points[it - 2];
				currentTriangle.Vert3 = Collision::PrimitivesCollider[countOfPrimitives]->Points[it - 1];
			}

		} while (it != 0);
	}

	return true;
}


В целом как он работает:
Он берёт первый треугольник из общего буффера и ищет все возможные пути от него до тех пор, пока не переберёт все. После считается, что один несвязанные мешь найден. И так повторяет пока общий буффер не будет полностью опустошён.

По моим тестам этот код работает, но проблема в том, что он работает слишком медленно. Если на входе будет мешь, состоящий из сотни тысяч вершин, то его разделение займёт пару минут! В то время как разделение этого же меша в блендере соответсвующей функцией происходит моментально.

Вопрос, как можно ускорить данный алгоритм?
  • Вопрос задан
  • 220 просмотров
Решения вопроса 1
wataru
@wataru Куратор тега C++
Разработчик на С++, экс-олимпиадник.
Тут вам нужен обход в ширину и чуть-чуть структур данных, чтобы граф построить.

Чтобы не копировать кучу раз данные туда-сюда создайте один массив из треугольников. Потом заведите еще один массив int-ов для пометок, к какому мешу каждый из треугольников принадлежит. Вы его заполните и потом можно за один проход разложить треугольники по новым мешам.

Еще заведите массив списков длинной сколько у вас точек. Пройдитесь по каждому треугольнику и засуньте его номер в 3 списка для каждой из его вершин.

Ну, а дальше, Breadth-First-Search запускаете. Пройдитесь циклом по всем треугольникам, если он еще не помечен, запускаете BFS от него. Помечайте его новым номером, помещайте его номер в очередь, и циклом пока очередь не пуста, извлекаете из нее элемент. Смотрите 3 списка для трех вершин. Если треугольник оттуда еще не помечен, помечаете его текущим номером меша, кладете в очередь.

Еще для ускорения можно после просмотра списка треугольников для вершины отчищать его.

Альтернативный вариант - завести hash_map из пары вершин в номер треугольника. Пройдитесь по треугольникам, и для каждого ребра, если оно еще не помечено, кладите номер треугольника в map. Иначе текущий и второй треугольник связаны - добавьте каждый из них в список инцидентности для второго. В таком варианте у каждого труегольника будет три ребра в соседей по сторонам.

Только перед обращением к мапе точки сортируйте.
Ответ написан
Пригласить эксперта
Ответы на вопрос 2
firedragon
@firedragon
Не джун-мидл-сеньор, а трус-балбес-бывалый.
Как мне кажется блендер читерит, посмотрите сколько фрагментов будет у фразы
"ой" - если 2 то используются шрифтовые данные если же 3 то нет
Ответ написан
@Umpiro
На всякий случай, Blender уже умеет выделять связанные вершины, с помощью функции bpy.ops.mesh.select_linked().
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы