in src/esp/assets/PTexMeshData.cpp [132:275]
std::vector<PTexMeshData::MeshData> splitMesh(
const PTexMeshData::MeshData& mesh,
const float splitSize) {
std::vector<uint32_t> verts;
verts.resize(mesh.vbo.size());
auto Part1By2 = [](uint64_t x) {
x &= 0x1fffff; // mask off lower 21 bits
x = (x | (x << 32)) & 0x1f00000000ffff;
x = (x | (x << 16)) & 0x1f0000ff0000ff;
x = (x | (x << 8)) & 0x100f00f00f00f00f;
x = (x | (x << 4)) & 0x10c30c30c30c30c3;
x = (x | (x << 2)) & 0x1249249249249249;
return x;
};
auto EncodeMorton3 = [&Part1By2](const vec3i& v) {
return (Part1By2(v(2)) << 2) + (Part1By2(v(1)) << 1) + Part1By2(v(0));
};
box3f boundingBox;
for (size_t i = 0; i < mesh.vbo.size(); i++) {
boundingBox.extend(mesh.vbo[i].head<3>());
}
// calculate vertex grid position and code
#pragma omp parallel for
for (size_t i = 0; i < mesh.vbo.size(); i++) {
const vec3f p = mesh.vbo[i].head<3>();
vec3f pi = (p - boundingBox.min()) / splitSize;
verts[i] = EncodeMorton3(pi.cast<int>());
}
// data structure for sorting faces
struct SortFace {
uint32_t index[4];
uint32_t code;
// TODO:
// Do we need the originalFace anywhere?
// If not, remove it in the future PR
size_t originalFace;
};
// fill per-face data structures (including codes)
size_t numFaces = mesh.ibo.size() / 4;
std::vector<SortFace> faces;
faces.resize(numFaces);
#pragma omp parallel for
for (size_t i = 0; i < numFaces; i++) {
faces[i].originalFace = i;
faces[i].code = std::numeric_limits<uint32_t>::max();
for (int j = 0; j < 4; j++) {
faces[i].index[j] = mesh.ibo[i * 4 + j];
// face code is minimum of referenced vertices codes
faces[i].code = std::min(faces[i].code, verts[faces[i].index[j]]);
}
}
// sort faces by code
std::sort(faces.begin(), faces.end(),
[](const SortFace& f1, const SortFace& f2) -> bool {
return (f1.code < f2.code);
});
// find face chunk start indices
std::vector<uint32_t> chunkStart;
chunkStart.push_back(0);
uint32_t prevCode = faces[0].code;
for (size_t i = 1; i < faces.size(); i++) {
if (faces[i].code != prevCode) {
chunkStart.push_back(i);
prevCode = faces[i].code;
}
}
chunkStart.push_back(faces.size());
size_t numChunks = chunkStart.size() - 1;
// TODO:
// Do we need the maxFaces anywhere?
// If not, remove it in the future PR
size_t maxFaces = 0;
for (size_t i = 0; i < numChunks; i++) {
uint32_t chunkSize = chunkStart[i + 1] - chunkStart[i];
if (chunkSize > maxFaces)
maxFaces = chunkSize;
}
// create new mesh for each chunk of faces
std::vector<PTexMeshData::MeshData> subMeshes;
for (size_t i = 0; i < numChunks; i++) {
subMeshes.emplace_back();
}
#pragma omp parallel for
for (size_t i = 0; i < numChunks; i++) {
uint32_t chunkSize = chunkStart[i + 1] - chunkStart[i];
std::vector<uint32_t> refdVerts;
// it maps indices from original mesh to the new ones in the chunk
std::unordered_map<uint32_t, uint32_t> refdVertsMap;
subMeshes[i].ibo.resize(chunkSize * 4);
for (size_t j = 0; j < chunkSize; j++) {
size_t faceIdx = chunkStart[i] + j;
for (int k = 0; k < 4; k++) {
uint32_t vertIndex = faces[faceIdx].index[k];
uint32_t newIndex = 0;
auto it = refdVertsMap.find(vertIndex);
if (it == refdVertsMap.end()) {
// vertex not found, add
newIndex = refdVerts.size();
refdVerts.push_back(vertIndex);
refdVertsMap[vertIndex] = newIndex;
} else {
// found, use existing index
newIndex = it->second;
}
subMeshes[i].ibo[j * 4 + k] = newIndex;
}
}
computeTriangleMeshIndices(chunkSize, subMeshes[i]);
// add referenced vertices to submesh
subMeshes[i].vbo.resize(refdVerts.size());
subMeshes[i].nbo.resize(refdVerts.size());
for (size_t j = 0; j < refdVerts.size(); j++) {
uint32_t index = refdVerts[j];
subMeshes[i].vbo[j] = mesh.vbo[index];
subMeshes[i].nbo[j] = mesh.nbo[index];
// Careful:
// for Ptex mesh we never ever set the "cbo"
}
}
return subMeshes;
}