diff --git a/code/AssetLib/AC/ACLoader.cpp b/code/AssetLib/AC/ACLoader.cpp index 0db5ab80a..e261db462 100644 --- a/code/AssetLib/AC/ACLoader.cpp +++ b/code/AssetLib/AC/ACLoader.cpp @@ -75,6 +75,8 @@ static constexpr aiImporterDesc desc = { "ac acc ac3d" }; +static constexpr auto ACDoubleSidedFlag = 0x20; + // ------------------------------------------------------------------------------------------------ // skip to the next token inline const char *AcSkipToNextToken(const char *buffer, const char *end) { @@ -129,6 +131,30 @@ inline const char *TAcCheckedLoadFloatArray(const char *buffer, const char *end, return buffer; } +// ------------------------------------------------------------------------------------------------ +// Reverses vertex indices in a face. +static void flipWindingOrder(aiFace &f) { + std::reverse(f.mIndices, f.mIndices + f.mNumIndices); +} + +// ------------------------------------------------------------------------------------------------ +// Duplicates a face and inverts it. Also duplicates all vertices (so the new face gets its own +// set of normals and isn’t smoothed against the original). +static void buildBacksideOfFace(const aiFace &origFace, aiFace *&outFaces, aiVector3D *&outVertices, const aiVector3D *allVertices, + aiVector3D *&outUV, const aiVector3D *allUV, unsigned &curIdx) { + auto &newFace = *outFaces++; + newFace = origFace; + flipWindingOrder(newFace); + for (unsigned f = 0; f < newFace.mNumIndices; ++f) { + *outVertices++ = allVertices[newFace.mIndices[f]]; + if (outUV) { + *outUV = allUV[newFace.mIndices[f]]; + outUV++; + } + newFace.mIndices[f] = curIdx++; + } +} + // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer AC3DImporter::AC3DImporter() : @@ -451,6 +477,8 @@ aiNode *AC3DImporter::ConvertObjectSection(Object &object, if ((*it).entries.empty()) { ASSIMP_LOG_WARN("AC3D: surface has zero vertex references"); } + const bool isDoubleSided = ACDoubleSidedFlag == (it->flags & ACDoubleSidedFlag); + const int doubleSidedFactor = isDoubleSided ? 2 : 1; // validate all vertex indices to make sure we won't crash here for (it2 = (*it).entries.begin(), @@ -480,8 +508,8 @@ aiNode *AC3DImporter::ConvertObjectSection(Object &object, // triangle strip case Surface::TriangleStrip: - needMat[idx].first += (unsigned int)(*it).entries.size() - 2; - needMat[idx].second += ((unsigned int)(*it).entries.size() - 2) * 3; + needMat[idx].first += static_cast(it->entries.size() - 2) * doubleSidedFactor; + needMat[idx].second += static_cast(it->entries.size() - 2) * 3 * doubleSidedFactor; break; default: @@ -494,8 +522,8 @@ aiNode *AC3DImporter::ConvertObjectSection(Object &object, case Surface::Polygon: // the number of faces increments by one, the number // of vertices by surface.numref. - needMat[idx].first++; - needMat[idx].second += (unsigned int)(*it).entries.size(); + needMat[idx].first += doubleSidedFactor; + needMat[idx].second += static_cast(it->entries.size()) * doubleSidedFactor; }; } unsigned int *pip = node->mMeshes = new unsigned int[node->mNumMeshes]; @@ -545,6 +573,7 @@ aiNode *AC3DImporter::ConvertObjectSection(Object &object, for (it = object.surfaces.begin(); it != end; ++it) { if (mat == (*it).mat) { const Surface &src = *it; + const bool isDoubleSided = ACDoubleSidedFlag == (src.flags & ACDoubleSidedFlag); // closed polygon uint8_t type = (*it).GetType(); @@ -570,6 +599,8 @@ aiNode *AC3DImporter::ConvertObjectSection(Object &object, ++uv; } } + if(isDoubleSided) // Need a backface? + buildBacksideOfFace(faces[-1], faces, vertices, mesh->mVertices, uv, mesh->mTextureCoords[0], cur); } } else if (type == Surface::TriangleStrip) { for (unsigned int i = 0; i < (unsigned int)src.entries.size() - 2; ++i) { @@ -619,6 +650,8 @@ aiNode *AC3DImporter::ConvertObjectSection(Object &object, uv->y = entry3.second.y; ++uv; } + if(isDoubleSided) // Need a backface? + buildBacksideOfFace(faces[-1], faces, vertices, mesh->mVertices, uv, mesh->mTextureCoords[0], cur); } } else { diff --git a/test/models/AC/doubleSidedFace.ac b/test/models/AC/doubleSidedFace.ac new file mode 100644 index 000000000..620ef22e9 --- /dev/null +++ b/test/models/AC/doubleSidedFace.ac @@ -0,0 +1,21 @@ +AC3Db +MATERIAL "" rgb 1 1 1 amb 0.2 0.2 0.2 emis 0 0 0 spec 0.5 0.5 0.5 shi 10 trans 0 +OBJECT world +kids 1 +OBJECT poly +name "rect" +loc 1 0.5 0 +numvert 4 +-1 0.5 0 +1 0.5 0 +1 -0.5 0 +-1 -0.5 0 +numsurf 1 +SURF 0x20 +mat 0 +refs 4 +3 0 0 +2 1 0 +1 1 1 +0 0 1 +kids 0 diff --git a/test/unit/utACImportExport.cpp b/test/unit/utACImportExport.cpp index c3c0e4506..f8f582a29 100644 --- a/test/unit/utACImportExport.cpp +++ b/test/unit/utACImportExport.cpp @@ -141,3 +141,14 @@ TEST(utACImportExport, testFormatDetection) { const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/AC/TestFormatDetection", aiProcess_ValidateDataStructure); ASSERT_NE(nullptr, scene); } + +TEST(utACImportExport, importDobuleSidedFaces) { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/AC/doubleSidedFace.ac", aiProcess_ValidateDataStructure); + ASSERT_NE(nullptr, scene); + // The scene contains one double-sided, rectangular AC surface. It should resolve to two quads (front + back) with eight + // vertices (one per side to guarantee proper normal vectors). + ASSERT_EQ(scene->mNumMeshes, 1u); + ASSERT_EQ(scene->mMeshes[0]->mNumFaces, 2u); + ASSERT_EQ(scene->mMeshes[0]->mNumVertices, 8u); +}