#include #include #include #include #include #include "util.h" #include "version.h" QString valueOfKeyInMapOrEmpty(const std::map &map, const QString &key) { auto it = map.find(key); if (it == map.end()) return QString(); return it->second; } bool isTrueValueString(const QString &str) { return "true" == str || "True" == str || "1" == str; } bool isFloatEqual(float a, float b) { return fabs(a - b) <= 0.000001; } void qNormalizeAngle(int &angle) { while (angle < 0) angle += 360 * 16; while (angle > 360 * 16) angle -= 360 * 16; } // https://en.wikibooks.org/wiki/Cg_Programming/Unity/Hermite_Curves QVector3D pointInHermiteCurve(float t, QVector3D p0, QVector3D m0, QVector3D p1, QVector3D m1) { return (2.0f * t * t * t - 3.0f * t * t + 1.0f) * p0 + (t * t * t - 2.0f * t * t + t) * m0 + (-2.0f * t * t * t + 3.0f * t * t) * p1 + (t * t * t - t * t) * m1; } float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D planeNormal) { float degrees = acos(QVector3D::dotProduct(a, b)) * 180.0 / M_PI; QVector3D direct = QVector3D::crossProduct(a, b); if (QVector3D::dotProduct(direct, planeNormal) < 0) return 360 - degrees; return degrees; } QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal) { const auto verticalOffset = QVector3D::dotProduct(line, planeNormal) * planeNormal; return line - verticalOffset; } QVector3D projectPointOnLine(const QVector3D &point, const QVector3D &linePointA, const QVector3D &linePointB) { auto aToPoint = point - linePointA; auto aToB = linePointB - linePointA; return linePointA + QVector3D::dotProduct(aToPoint, aToB) / QVector3D::dotProduct(aToB, aToB) * aToB; } QString unifiedWindowTitle(const QString &text) { return text + QObject::tr(" - ") + APP_NAME; } // Sam Hocevar's answer // https://gamedev.stackexchange.com/questions/98246/quaternion-slerp-and-lerp-implementation-with-overshoot QQuaternion quaternionOvershootSlerp(const QQuaternion &q0, const QQuaternion &q1, float t) { // If t is too large, divide it by two recursively if (t > 1.0) { auto tmp = quaternionOvershootSlerp(q0, q1, t / 2); return tmp * q0.inverted() * tmp; } // It’s easier to handle negative t this way if (t < 0.0) return quaternionOvershootSlerp(q1, q0, 1.0 - t); return QQuaternion::slerp(q0, q1, t); } float radianBetweenVectors(const QVector3D &first, const QVector3D &second) { return std::acos(QVector3D::dotProduct(first.normalized(), second.normalized())); }; float degreesBetweenVectors(const QVector3D &first, const QVector3D &second) { return radianBetweenVectors(first, second) * 180.0 / M_PI; } float areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c) { auto ab = b - a; auto ac = c - a; return 0.5 * QVector3D::crossProduct(ab, ac).length(); } QQuaternion eulerAnglesToQuaternion(double pitch, double yaw, double roll) { return QQuaternion::fromEulerAngles(pitch, yaw, roll); } void quaternionToEulerAngles(const QQuaternion &q, double *pitch, double *yaw, double *roll) { auto eulerAngles = q.toEulerAngles(); *pitch = eulerAngles.x(); *yaw = eulerAngles.y(); *roll = eulerAngles.z(); } QVector3D polygonNormal(const std::vector &vertices, const std::vector &polygon) { QVector3D normal; for (size_t i = 0; i < polygon.size(); ++i) { auto j = (i + 1) % polygon.size(); auto k = (i + 2) % polygon.size(); const auto &enter = vertices[polygon[i]]; const auto &cone = vertices[polygon[j]]; const auto &leave = vertices[polygon[k]]; normal += QVector3D::normal(enter, cone, leave); } return normal.normalized(); } bool pointInTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &p) { auto u = b - a; auto v = c - a; auto w = p - a; auto vXw = QVector3D::crossProduct(v, w); auto vXu = QVector3D::crossProduct(v, u); if (QVector3D::dotProduct(vXw, vXu) < 0.0) { return false; } auto uXw = QVector3D::crossProduct(u, w); auto uXv = QVector3D::crossProduct(u, v); if (QVector3D::dotProduct(uXw, uXv) < 0.0) { return false; } auto denom = uXv.length(); auto r = vXw.length() / denom; auto t = uXw.length() / denom; return r + t <= 1.0; } void angleSmooth(const std::vector &vertices, const std::vector> &triangles, const std::vector &triangleNormals, float thresholdAngleDegrees, std::vector &triangleVertexNormals) { std::vector>> triangleVertexNormalsMapByIndices(vertices.size()); std::vector angleAreaWeightedNormals; for (size_t triangleIndex = 0; triangleIndex < triangles.size(); ++triangleIndex) { const auto &sourceTriangle = triangles[triangleIndex]; if (sourceTriangle.size() != 3) { qDebug() << "Encounter non triangle"; continue; } const auto &v1 = vertices[sourceTriangle[0]]; const auto &v2 = vertices[sourceTriangle[1]]; const auto &v3 = vertices[sourceTriangle[2]]; float area = areaOfTriangle(v1, v2, v3); float angles[] = {degreesBetweenVectors(v2-v1, v3-v1), degreesBetweenVectors(v1-v2, v3-v2), degreesBetweenVectors(v1-v3, v2-v3)}; for (int i = 0; i < 3; ++i) { if (sourceTriangle[i] >= vertices.size()) { qDebug() << "Invalid vertex index" << sourceTriangle[i] << "vertices size" << vertices.size(); continue; } triangleVertexNormalsMapByIndices[sourceTriangle[i]].push_back({triangleIndex, angleAreaWeightedNormals.size()}); angleAreaWeightedNormals.push_back(triangleNormals[triangleIndex] * area * angles[i]); } } triangleVertexNormals = angleAreaWeightedNormals; std::map, float> degreesBetweenFacesMap; for (size_t vertexIndex = 0; vertexIndex < vertices.size(); ++vertexIndex) { const auto &triangleVertices = triangleVertexNormalsMapByIndices[vertexIndex]; for (const auto &triangleVertex: triangleVertices) { for (const auto &otherTriangleVertex: triangleVertices) { if (triangleVertex.first == otherTriangleVertex.first) continue; float degrees = 0; auto findDegreesResult = degreesBetweenFacesMap.find({triangleVertex.first, otherTriangleVertex.first}); if (findDegreesResult == degreesBetweenFacesMap.end()) { degrees = degreesBetweenVectors(triangleNormals[triangleVertex.first], triangleNormals[otherTriangleVertex.first]); degreesBetweenFacesMap.insert({{triangleVertex.first, otherTriangleVertex.first}, degrees}); degreesBetweenFacesMap.insert({{otherTriangleVertex.first, triangleVertex.first}, degrees}); } else { degrees = findDegreesResult->second; } if (degrees > thresholdAngleDegrees) { continue; } triangleVertexNormals[triangleVertex.second] += angleAreaWeightedNormals[otherTriangleVertex.second]; } } } for (auto &item: triangleVertexNormals) item.normalize(); } void recoverQuads(const std::vector &vertices, const std::vector> &triangles, const std::set> &sharedQuadEdges, std::vector> &triangleAndQuads) { std::vector verticesPositionKeys; for (const auto &position: vertices) { verticesPositionKeys.push_back(PositionKey(position)); } std::map, std::pair> triangleEdgeMap; for (size_t i = 0; i < triangles.size(); i++) { const auto &faceIndices = triangles[i]; if (faceIndices.size() == 3) { triangleEdgeMap[std::make_pair(faceIndices[0], faceIndices[1])] = std::make_pair(i, faceIndices[2]); triangleEdgeMap[std::make_pair(faceIndices[1], faceIndices[2])] = std::make_pair(i, faceIndices[0]); triangleEdgeMap[std::make_pair(faceIndices[2], faceIndices[0])] = std::make_pair(i, faceIndices[1]); } } std::unordered_set unionedFaces; std::vector> newUnionedFaceIndices; for (const auto &edge: triangleEdgeMap) { if (unionedFaces.find(edge.second.first) != unionedFaces.end()) continue; auto pair = std::make_pair(verticesPositionKeys[edge.first.first], verticesPositionKeys[edge.first.second]); if (sharedQuadEdges.find(pair) != sharedQuadEdges.end()) { auto oppositeEdge = triangleEdgeMap.find(std::make_pair(edge.first.second, edge.first.first)); if (oppositeEdge == triangleEdgeMap.end()) { //qDebug() << "Find opposite edge failed"; } else { if (unionedFaces.find(oppositeEdge->second.first) == unionedFaces.end()) { unionedFaces.insert(edge.second.first); unionedFaces.insert(oppositeEdge->second.first); std::vector indices; indices.push_back(edge.second.second); indices.push_back(edge.first.first); indices.push_back(oppositeEdge->second.second); indices.push_back(edge.first.second); triangleAndQuads.push_back(indices); } } } } for (size_t i = 0; i < triangles.size(); i++) { if (unionedFaces.find(i) == unionedFaces.end()) { triangleAndQuads.push_back(triangles[i]); } } } size_t weldSeam(const std::vector &sourceVertices, const std::vector> &sourceTriangles, float allowedSmallestDistance, const std::set &excludePositions, std::vector &destVertices, std::vector> &destTriangles) { std::unordered_set excludeVertices; for (size_t i = 0; i < sourceVertices.size(); ++i) { if (excludePositions.find(sourceVertices[i]) != excludePositions.end()) excludeVertices.insert(i); } float squareOfAllowedSmallestDistance = allowedSmallestDistance * allowedSmallestDistance; std::map weldVertexToMap; std::unordered_set weldTargetVertices; std::unordered_set processedFaces; std::map, std::pair> triangleEdgeMap; std::unordered_map vertexAdjFaceCountMap; for (int i = 0; i < (int)sourceTriangles.size(); i++) { const auto &faceIndices = sourceTriangles[i]; if (faceIndices.size() == 3) { vertexAdjFaceCountMap[faceIndices[0]]++; vertexAdjFaceCountMap[faceIndices[1]]++; vertexAdjFaceCountMap[faceIndices[2]]++; triangleEdgeMap[std::make_pair(faceIndices[0], faceIndices[1])] = std::make_pair(i, faceIndices[2]); triangleEdgeMap[std::make_pair(faceIndices[1], faceIndices[2])] = std::make_pair(i, faceIndices[0]); triangleEdgeMap[std::make_pair(faceIndices[2], faceIndices[0])] = std::make_pair(i, faceIndices[1]); } } for (int i = 0; i < (int)sourceTriangles.size(); i++) { if (processedFaces.find(i) != processedFaces.end()) continue; const auto &faceIndices = sourceTriangles[i]; if (faceIndices.size() == 3) { bool indicesSeamCheck[3] = { excludeVertices.find(faceIndices[0]) == excludeVertices.end(), excludeVertices.find(faceIndices[1]) == excludeVertices.end(), excludeVertices.find(faceIndices[2]) == excludeVertices.end() }; for (int j = 0; j < 3; j++) { int next = (j + 1) % 3; int nextNext = (j + 2) % 3; if (indicesSeamCheck[j] && indicesSeamCheck[next]) { std::pair edge = std::make_pair(faceIndices[j], faceIndices[next]); int thirdVertexIndex = faceIndices[nextNext]; if ((sourceVertices[edge.first] - sourceVertices[edge.second]).lengthSquared() < squareOfAllowedSmallestDistance) { auto oppositeEdge = std::make_pair(edge.second, edge.first); auto findOppositeFace = triangleEdgeMap.find(oppositeEdge); if (findOppositeFace == triangleEdgeMap.end()) { //qDebug() << "Find opposite edge failed"; continue; } int oppositeFaceIndex = findOppositeFace->second.first; if (((sourceVertices[edge.first] - sourceVertices[thirdVertexIndex]).lengthSquared() < (sourceVertices[edge.second] - sourceVertices[thirdVertexIndex]).lengthSquared()) && vertexAdjFaceCountMap[edge.second] <= 4 && weldVertexToMap.find(edge.second) == weldVertexToMap.end()) { weldVertexToMap[edge.second] = edge.first; weldTargetVertices.insert(edge.first); processedFaces.insert(i); processedFaces.insert(oppositeFaceIndex); break; } else if (vertexAdjFaceCountMap[edge.first] <= 4 && weldVertexToMap.find(edge.first) == weldVertexToMap.end()) { weldVertexToMap[edge.first] = edge.second; weldTargetVertices.insert(edge.second); processedFaces.insert(i); processedFaces.insert(oppositeFaceIndex); break; } } } } } } int weldedCount = 0; int faceCountAfterWeld = 0; std::map oldToNewVerticesMap; for (int i = 0; i < (int)sourceTriangles.size(); i++) { const auto &faceIndices = sourceTriangles[i]; std::vector mappedFaceIndices; bool errored = false; for (const auto &index: faceIndices) { int finalIndex = index; int mapTimes = 0; while (mapTimes < 500) { auto findMapResult = weldVertexToMap.find(finalIndex); if (findMapResult == weldVertexToMap.end()) break; finalIndex = findMapResult->second; mapTimes++; } if (mapTimes >= 500) { qDebug() << "Map too much times"; errored = true; break; } mappedFaceIndices.push_back(finalIndex); } if (errored || mappedFaceIndices.size() < 3) continue; bool welded = false; for (decltype(mappedFaceIndices.size()) j = 0; j < mappedFaceIndices.size(); j++) { int next = (j + 1) % 3; if (mappedFaceIndices[j] == mappedFaceIndices[next]) { welded = true; break; } } if (welded) { weldedCount++; continue; } faceCountAfterWeld++; std::vector newFace; for (const auto &index: mappedFaceIndices) { auto findMap = oldToNewVerticesMap.find(index); if (findMap == oldToNewVerticesMap.end()) { size_t newIndex = destVertices.size(); newFace.push_back(newIndex); destVertices.push_back(sourceVertices[index]); oldToNewVerticesMap.insert({index, newIndex}); } else { newFace.push_back(findMap->second); } } destTriangles.push_back(newFace); } return weldedCount; } bool isManifold(const std::vector> &faces) { std::set> halfEdges; for (const auto &face: faces) { for (size_t i = 0; i < face.size(); ++i) { size_t j = (i + 1) % face.size(); auto insertResult = halfEdges.insert({face[i], face[j]}); if (!insertResult.second) return false; } } for (const auto &it: halfEdges) { if (halfEdges.find({it.second, it.first}) == halfEdges.end()) return false; } return true; } void trim(std::vector *vertices, bool normalize) { float xLow = std::numeric_limits::max(); float xHigh = std::numeric_limits::lowest(); float yLow = std::numeric_limits::max(); float yHigh = std::numeric_limits::lowest(); float zLow = std::numeric_limits::max(); float zHigh = std::numeric_limits::lowest(); for (const auto &position: *vertices) { if (position.x() < xLow) xLow = position.x(); else if (position.x() > xHigh) xHigh = position.x(); if (position.y() < yLow) yLow = position.y(); else if (position.y() > yHigh) yHigh = position.y(); if (position.z() < zLow) zLow = position.z(); else if (position.z() > zHigh) zHigh = position.z(); } float xMiddle = (xHigh + xLow) * 0.5; float yMiddle = (yHigh + yLow) * 0.5; float zMiddle = (zHigh + zLow) * 0.5; if (normalize) { float xSize = xHigh - xLow; float ySize = yHigh - yLow; float zSize = zHigh - zLow; float longSize = ySize; if (xSize > longSize) longSize = xSize; if (zSize > longSize) longSize = zSize; if (qFuzzyIsNull(longSize)) longSize = 0.000001; for (auto &position: *vertices) { position.setX((position.x() - xMiddle) / longSize); position.setY((position.y() - yMiddle) / longSize); position.setZ((position.z() - zMiddle) / longSize); } } else { for (auto &position: *vertices) { position.setX((position.x() - xMiddle)); position.setY((position.y() - yMiddle)); position.setZ((position.z() - zMiddle)); } } } void chamferFace2D(std::vector *face) { auto oldFace = *face; face->clear(); for (size_t i = 0; i < oldFace.size(); ++i) { size_t j = (i + 1) % oldFace.size(); face->push_back(oldFace[i] * 0.8 + oldFace[j] * 0.2); face->push_back(oldFace[i] * 0.2 + oldFace[j] * 0.8); } } void subdivideFace2D(std::vector *face) { auto oldFace = *face; face->resize(oldFace.size() * 2); for (size_t i = 0, n = 0; i < oldFace.size(); ++i) { size_t h = (i + oldFace.size() - 1) % oldFace.size(); size_t j = (i + 1) % oldFace.size(); (*face)[n++] = oldFace[h] * 0.125 + oldFace[i] * 0.75 + oldFace[j] * 0.125; (*face)[n++] = (oldFace[i] + oldFace[j]) * 0.5; } } QVector3D choosenBaseAxis(const QVector3D &layoutDirection) { const std::vector axisList = { QVector3D(1, 0, 0), QVector3D(0, 1, 0), QVector3D(0, 0, 1), }; std::vector> dots; for (size_t i = 0; i < axisList.size(); ++i) { dots.push_back(std::make_pair(qAbs(QVector3D::dotProduct(layoutDirection, axisList[i])), i)); } return axisList[std::min_element(dots.begin(), dots.end(), [](const std::pair &first, const std::pair &second) { return first.first < second.first; })->second]; } void saveAsObj(const char *filename, const std::vector &vertices, const std::vector> &faces) { QFile file(filename); if (!file.open(QIODevice::WriteOnly)) return; QTextStream stream(&file); for (std::vector::const_iterator it = vertices.begin() ; it != vertices.end(); ++it) { stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl; } for (std::vector>::const_iterator it = faces.begin() ; it != faces.end(); ++it) { stream << "f"; for (std::vector::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) { stream << " " << (1 + *subIt); } stream << endl; } } bool intersectSegmentAndPlane(const QVector3D &segmentPoint0, const QVector3D &segmentPoint1, const QVector3D &pointOnPlane, const QVector3D &planeNormal, QVector3D *intersection) { auto u = segmentPoint1 - segmentPoint0; auto w = segmentPoint0 - pointOnPlane; auto d = QVector3D::dotProduct(planeNormal, u); auto n = QVector3D::dotProduct(-planeNormal, w); if (qAbs(d) < 0.00000001) return false; auto s = n / d; if (s < 0 || s > 1 || qIsNaN(s) || qIsInf(s)) return false; if (nullptr != intersection) *intersection = segmentPoint0 + s * u; return true; } bool intersectSegmentAndTriangle(const QVector3D &segmentPoint0, const QVector3D &segmentPoint1, const std::vector &triangle, const QVector3D &triangleNormal, QVector3D *intersection) { QVector3D possibleIntersection; if (!intersectSegmentAndPlane(segmentPoint0, segmentPoint1, triangle[0], triangleNormal, &possibleIntersection)) { return false; } auto ray = (segmentPoint0 - segmentPoint1).normalized(); std::vector normals; for (size_t i = 0; i < 3; ++i) { size_t j = (i + 1) % 3; normals.push_back(QVector3D::normal(possibleIntersection, triangle[i], triangle[j])); } if (QVector3D::dotProduct(normals[0], ray) <= 0) return false; if (QVector3D::dotProduct(normals[0], normals[1]) <= 0) return false; if (QVector3D::dotProduct(normals[0], normals[2]) <= 0) return false; if (nullptr != intersection) *intersection = possibleIntersection; return true; } bool intersectRayAndPolyhedron(const QVector3D &rayNear, const QVector3D &rayFar, const std::vector &vertices, const std::vector> &triangles, const std::vector &triangleNormals, QVector3D *intersection, size_t *intersectedTriangleIndex) { bool foundPosition = false; auto ray = (rayNear - rayFar).normalized(); float minDistance2 = std::numeric_limits::max(); for (size_t i = 0; i < triangles.size(); ++i) { const auto &triangleIndices = triangles[i]; std::vector triangle = { vertices[triangleIndices[0]], vertices[triangleIndices[1]], vertices[triangleIndices[2]], }; const auto &triangleNormal = triangleNormals[i]; if (QVector3D::dotProduct(triangleNormal, ray) <= 0) continue; QVector3D point; if (intersectSegmentAndTriangle(rayNear, rayFar, triangle, triangleNormal, &point)) { float distance2 = (point - rayNear).lengthSquared(); if (distance2 < minDistance2) { if (nullptr != intersection) *intersection = point; if (nullptr != intersectedTriangleIndex) *intersectedTriangleIndex = i; minDistance2 = distance2; foundPosition = true; } } } return foundPosition; } QVector3D barycentricCoordinates(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &point) { auto invertedAreaOfAbc = 1.0 / areaOfTriangle(a, b, c); auto areaOfPbc = areaOfTriangle(point, b, c); auto areaOfPca = areaOfTriangle(point, c, a); auto alpha = areaOfPbc * invertedAreaOfAbc; auto beta = areaOfPca * invertedAreaOfAbc; return QVector3D(alpha, beta, 1.0 - alpha - beta); }