static int32_t kdBinGetNodes(const kdTree &tree, const kdNode *node, u::vector<kdBinPlane> &planes, u::vector<kdBinNode> &nodes, u::vector<kdBinLeaf> &leafs) { if (node->isLeaf()) return kdBinInsertLeaf(node, leafs); // We only care about the distance and axis type for the plane. kdBinPlane binPlane; binPlane.d = node->m_splitPlane.d; for (size_t i = 0; i < 3; i++) { if (m::abs(node->m_splitPlane.n[i]) > m::kEpsilon) { binPlane.type = i; break; } } planes.push_back(binPlane); const size_t planeIndex = planes.size() - 1; const size_t nodeIndex = nodes.size(); kdBinNode binNode; binNode.plane = planeIndex; binNode.sphereRadius = node->m_sphereRadius; binNode.sphereOrigin = node->m_sphereOrigin; nodes.push_back(binNode); nodes[nodeIndex].children[0] = kdBinGetNodes(tree, node->m_front, planes, nodes, leafs); nodes[nodeIndex].children[1] = kdBinGetNodes(tree, node->m_back, planes, nodes, leafs); return nodeIndex; }
static uint32_t kdBinAddTexture(u::vector<kdBinTexture> &textures, const u::string &texturePath) { uint32_t index = 0; for (const auto &it : textures) { if (it.name == texturePath) return index; index++; } kdBinTexture texture; // Truncate the string if it doesn't fit const size_t length = u::min(sizeof texture.name - 1, texturePath.size()); memcpy(texture.name, (const void *)texturePath.c_str(), length); texture.name[length] = '\0'; textures.push_back(texture); return textures.size() - 1; }
static void kdBinCreateTangents(u::vector<kdBinVertex> &vertices, const u::vector<kdBinTriangle> &triangles) { // Computing Tangent Space Basis Vectors for an Arbitrary Mesh (Lengyel’s Method) // Section 7.8 (or in Section 6.8 of the second edition). const size_t vertexCount = vertices.size(); const size_t triangleCount = triangles.size(); u::vector<m::vec3> normals(vertexCount); u::vector<m::vec3> tangents(vertexCount); u::vector<m::vec3> bitangents(vertexCount); m::vec3 normal; m::vec3 tangent; m::vec3 bitangent; for (size_t i = 0; i < triangleCount; i++) { kdBinCalculateTangent(triangles, vertices, i, normal, tangent, bitangent); const size_t x = triangles[i].v[0]; const size_t y = triangles[i].v[1]; const size_t z = triangles[i].v[2]; normals[x] += normal; normals[y] += normal; normals[z] += normal; tangents[x] += tangent; tangents[y] += tangent; tangents[z] += tangent; bitangents[x] += bitangent; bitangents[y] += bitangent; bitangents[z] += bitangent; } for (size_t i = 0; i < vertexCount; i++) { // Gram-Schmidt orthogonalize // http://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process vertices[i].normal = normals[i].normalized(); const m::vec3 &n = vertices[i].normal; m::vec3 t = tangents[i]; m::vec3 tangent = (t - n * (n * t)).normalized(); if (!tangent.isNormalized()) { // Couldn't calculate vertex tangent for vertex, so we fill it in along // the x axis. tangent = m::vec3::xAxis; t = tangent; } // bitangents are only stored by handness in the W component (-1.0f or 1.0f). vertices[i].tangent = m::vec4(tangent, (((n ^ t) * bitangents[i]) < 0.0f) ? -1.0f : 1.0f); } }
bool write(const u::vector<unsigned char> &data, const u::string &file, const char *mode) { auto fp = u::fopen(file, mode); if (!fp) return false; if (fwrite(&data[0], data.size(), 1, fp.get()) != 1) return false; return true; }
void kdNode::split(const kdTree *tree, const u::vector<int> &tris, m::axis axis, u::vector<int> &frontList, u::vector<int> &backList, u::vector<int> &splitList, m::plane &plane) const { const size_t triangleCount = tris.size(); plane = findSplittingPlane(tree, tris, axis); for (size_t i = 0; i < triangleCount; i++) { switch (tree->testTriangle(tris[i], plane)) { case kPolyPlaneCoplanar: case kPolyPlaneSplit: splitList.push_back(tris[i]); break; case kPolyPlaneFront: frontList.push_back(tris[i]); break; case kPolyPlaneBack: backList.push_back(tris[i]); break; } } }
bool kdNode::calculateSphere(const kdTree *tree, const u::vector<int> &tris) { const size_t triangleCount = tris.size(); m::vec3 min; m::vec3 max; for (size_t i = 0; i < triangleCount; i++) { const int index = tris[i]; for (size_t j = 0; j < 3; j++) { const size_t vertexIndex = tree->m_triangles[index].m_vertices[j]; const m::vec3 *v = &tree->m_vertices[vertexIndex]; if (v->x < min.x) min.x = v->x; if (v->y < min.y) min.y = v->y; if (v->z < min.z) min.z = v->z; if (v->x > max.x) max.x = v->x; if (v->y > max.y) max.y = v->y; if (v->z > max.z) max.z = v->z; } } const m::vec3 mid = (max - min) * 0.5f; m_sphereOrigin = min + mid; m_sphereRadius = mid.abs(); return m_sphereRadius <= kdTree::kMaxTraceDistance; }
m::plane kdNode::findSplittingPlane(const kdTree *tree, const u::vector<int> &tris, m::axis axis) const { const size_t triangleCount = tris.size(); // every vertex component is stored depending on `axis' axis in the following // vector. The vector gets sorted and the median is chosen as the splitting // plane. u::vector<float> coords(triangleCount * 3); // 3 vertices for a triangle size_t k = 0; for (size_t i = 0; i < triangleCount; i++) { for (size_t j = 0; j < 3; j++) { const int index = tree->m_triangles[tris[i]].m_vertices[j]; const m::vec3 &vec = tree->m_vertices[index]; coords[k++] = vec[axis]; } } // sort coordinates for a L1 median estimation, this keeps us rather // robust against vertex outliers. u::sort(coords.begin(), coords.end(), [](float a, float b) { return a < b; }); const float split = coords[coords.size() / 2]; // median like const m::vec3 point(m::vec3::getAxis(axis) * split); const m::vec3 normal(m::vec3::getAxis(axis)); return m::plane(point, normal); }
static void kdSerialize(u::vector<unsigned char> &buffer, const T *data, size_t size) { const unsigned char *const beg = (const unsigned char *const)data; const unsigned char *const end = ((const unsigned char *const)data) + size; buffer.insert(buffer.end(), beg, end); }
static int32_t kdBinInsertLeaf(const kdNode *leaf, u::vector<kdBinLeaf> &leafs) { kdBinLeaf binLeaf; binLeaf.triangles.insert(binLeaf.triangles.begin(), leaf->m_triangles.begin(), leaf->m_triangles.end()); leafs.push_back(binLeaf); return -(int32_t)leafs.size(); // leaf indices are stored with negative index }
kdNode::kdNode(kdTree *tree, const u::vector<int> &tris, size_t recursionDepth) : m_front(nullptr) , m_back(nullptr) , m_sphereRadius(0.0f) { const size_t triangleCount = tris.size(); if (recursionDepth > tree->m_depth) tree->m_depth = recursionDepth; if (recursionDepth > kdTree::kMaxRecursionDepth) return; tree->m_nodeCount++; if (!calculateSphere(tree, tris)) { u::Log::err("[world] => level geometry is too large: collision detection and rendering may not work"); return; } u::vector<int> fx, fy, fz; // front u::vector<int> bx, by, bz; // back u::vector<int> sx, sy, sz; // split u::vector<int> *const frontList[3] = { &fx, &fy, &fz }; u::vector<int> *const backList[3] = { &bx, &by, &bz }; u::vector<int> *const splitList[3] = { &sx, &sy, &sz }; m::plane plane[3]; float ratio[3]; size_t best = recursionDepth % 3; // find a plane which gives a good balanced node for (size_t i = 0; i < 3; i++) { split(tree, tris, m::axis(i), *frontList[i], *backList[i], *splitList[i], plane[i]); const size_t fsize = (*frontList[i]).size(); const size_t bsize = (*backList[i]).size(); if (fsize > bsize) ratio[i] = (float)bsize / (float)fsize; else ratio[i] = (float)fsize / (float)bsize; } float bestRatio = 0.0f; for (size_t i = 0; i < 3; i++) { if (ratio[i] > bestRatio) { best = i; bestRatio = ratio[i]; } } m_splitPlane = plane[best]; // when there isn't many triangles left, create a leaf. In doing so we can // continue to create further subdivisions. if (frontList[best]->size() == 0 || backList[best]->size() == 0 || triangleCount <= kdTree::kMaxTrianglesPerLeaf) { // create subspace with `triangleCount` polygons m_triangles.insert(m_triangles.begin(), tris.begin(), tris.end()); tree->m_leafCount++; return; } // insert the split triangles on both sides of the plane. frontList[best]->insert(frontList[best]->end(), splitList[best]->begin(), splitList[best]->end()); backList[best]->insert(backList[best]->end(), splitList[best]->begin(), splitList[best]->end()); // recurse m_front = new kdNode(tree, *frontList[best], recursionDepth + 1); m_back = new kdNode(tree, *backList[best], recursionDepth + 1); }