/* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "GeomReindex.h" #include "FCollada.h" #include "FCDocument/FCDEntity.h" #include "FCDocument/FCDGeometryMesh.h" #include "FCDocument/FCDGeometryPolygons.h" #include "FCDocument/FCDGeometryPolygonsInput.h" #include "FCDocument/FCDGeometrySource.h" #include "FCDocument/FCDSkinController.h" #include #include #include #include typedef std::pair uv_pair_type; struct VertexData { VertexData(const float* pos, const float* norm, const std::vector &uvs, const std::vector& weights) : x(pos[0]), y(pos[1]), z(pos[2]), nx(norm[0]), ny(norm[1]), nz(norm[2]), uvs(uvs), weights(weights) { } float x, y, z; float nx, ny, nz; std::vector uvs; std::vector weights; }; bool similar(float a, float b) { return (fabsf(a - b) < 0.000001f); } bool operator==(const FCDJointWeightPair& a, const FCDJointWeightPair& b) { return (a.jointIndex == b.jointIndex && similar(a.weight, b.weight)); } bool operator<(const FCDJointWeightPair& a, const FCDJointWeightPair& b) { // Sort by decreasing weight, then by increasing joint ID if (a.weight > b.weight) return true; else if (a.weight < b.weight) return false; else if (a.jointIndex < b.jointIndex) return true; else return false; } bool operator==(const uv_pair_type& a, const uv_pair_type& b) { return similar(a.first, b.first) && similar(a.second, b.second); } bool operator==(const VertexData& a, const VertexData& b) { return (similar(a.x, b.x) && similar(a.y, b.y) && similar(a.z, b.z) && similar(a.nx, b.nx) && similar(a.ny, b.ny) && similar(a.nz, b.nz) && (a.uvs == b.uvs) && (a.weights == b.weights)); } bool operator<(const VertexData& a, const VertexData& b) { #define CMP(f) if (a.f < b.f) return true; if (a.f > b.f) return false CMP(x); CMP(y); CMP(z); CMP(nx); CMP(ny); CMP(nz); CMP(uvs); CMP(weights); #undef CMP return false; } template struct InserterWithoutDuplicates { InserterWithoutDuplicates(std::vector& vec) : vec(vec) { } size_t add(const T& val) { typename std::map::iterator it = btree.find(val); if (it != btree.end()) return it->second; size_t idx = vec.size(); vec.push_back(val); btree.insert(std::make_pair(val, idx)); return idx; } std::vector& vec; std::map btree; // for faster lookups (so we can build a duplicate-free list in O(n log n) instead of O(n^2)) private: InserterWithoutDuplicates& operator=(const InserterWithoutDuplicates&); }; void CanonicaliseWeights(std::vector& weights) { // Convert weight-lists into a standard format, so simple vector equality // can be used to determine equivalence std::sort(weights.begin(), weights.end()); } void ReindexGeometry(FCDGeometryPolygons* polys, FCDSkinController* skin) { // Given geometry with: // positions, normals, texcoords, bone blends // each with their own data array and index array, change it to // have a single optimised index array shared by all vertexes. FCDGeometryPolygonsInput* inputPosition = polys->FindInput(FUDaeGeometryInput::POSITION); FCDGeometryPolygonsInput* inputNormal = polys->FindInput(FUDaeGeometryInput::NORMAL); FCDGeometryPolygonsInput* inputTexcoord = polys->FindInput(FUDaeGeometryInput::TEXCOORD); size_t numVertices = polys->GetFaceVertexCount(); assert(inputPosition->GetIndexCount() == numVertices); assert(inputNormal ->GetIndexCount() == numVertices); assert(inputTexcoord->GetIndexCount() == numVertices); const uint32* indicesPosition = inputPosition->GetIndices(); const uint32* indicesNormal = inputNormal->GetIndices(); const uint32* indicesTexcoord = inputTexcoord->GetIndices(); assert(indicesPosition); assert(indicesNormal); assert(indicesTexcoord); // TODO - should be optional, because textureless meshes aren't unreasonable FCDGeometrySourceList texcoordSources; polys->GetParent()->FindSourcesByType(FUDaeGeometryInput::TEXCOORD, texcoordSources); FCDGeometrySource* sourcePosition = inputPosition->GetSource(); FCDGeometrySource* sourceNormal = inputNormal ->GetSource(); const float* dataPosition = sourcePosition->GetData(); const float* dataNormal = sourceNormal ->GetData(); if (skin) { #ifndef NDEBUG size_t numVertexPositions = sourcePosition->GetDataCount() / sourcePosition->GetStride(); assert(skin->GetInfluenceCount() == numVertexPositions); #endif } uint32 stridePosition = sourcePosition->GetStride(); uint32 strideNormal = sourceNormal ->GetStride(); std::vector indicesCombined; std::vector vertexes; InserterWithoutDuplicates inserter(vertexes); for (size_t i = 0; i < numVertices; ++i) { std::vector weights; if (skin) { FCDSkinControllerVertex* influences = skin->GetVertexInfluence(indicesPosition[i]); assert(influences != NULL); for (size_t j = 0; j < influences->GetPairCount(); ++j) { FCDJointWeightPair* pair = influences->GetPair(j); assert(pair != NULL); weights.push_back(*pair); } CanonicaliseWeights(weights); } std::vector uvs; for (size_t set = 0; set < texcoordSources.size(); ++set) { const float* dataTexcoord = texcoordSources[set]->GetData(); uint32 strideTexcoord = texcoordSources[set]->GetStride(); uv_pair_type p; p.first = dataTexcoord[indicesTexcoord[i]*strideTexcoord]; p.second = dataTexcoord[indicesTexcoord[i]*strideTexcoord + 1]; uvs.push_back(p); } VertexData vtx ( &dataPosition[indicesPosition[i]*stridePosition], &dataNormal [indicesNormal [i]*strideNormal], uvs, weights ); size_t idx = inserter.add(vtx); indicesCombined.push_back((uint32)idx); } // TODO: rearrange indicesCombined (and rearrange vertexes to match) to use // the vertex cache efficiently // ( etc) FloatList newDataPosition; FloatList newDataNormal; FloatList newDataTexcoord; std::vector > newWeightedMatches; for (size_t i = 0; i < vertexes.size(); ++i) { newDataPosition.push_back(vertexes[i].x); newDataPosition.push_back(vertexes[i].y); newDataPosition.push_back(vertexes[i].z); newDataNormal .push_back(vertexes[i].nx); newDataNormal .push_back(vertexes[i].ny); newDataNormal .push_back(vertexes[i].nz); newWeightedMatches.push_back(vertexes[i].weights); } // (Slightly wasteful to duplicate this array so many times, but FCollada // doesn't seem to support multiple inputs with the same source data) inputPosition->SetIndices(&indicesCombined.front(), indicesCombined.size()); inputNormal ->SetIndices(&indicesCombined.front(), indicesCombined.size()); inputTexcoord->SetIndices(&indicesCombined.front(), indicesCombined.size()); for (size_t set = 0; set < texcoordSources.size(); ++set) { newDataTexcoord.clear(); for (size_t i = 0; i < vertexes.size(); ++i) { newDataTexcoord.push_back(vertexes[i].uvs[set].first); newDataTexcoord.push_back(vertexes[i].uvs[set].second); } texcoordSources[set]->SetData(newDataTexcoord, 2); } sourcePosition->SetData(newDataPosition, 3); sourceNormal ->SetData(newDataNormal, 3); if (skin) { skin->SetInfluenceCount(newWeightedMatches.size()); for (size_t i = 0; i < newWeightedMatches.size(); ++i) { skin->GetVertexInfluence(i)->SetPairCount(0); for (size_t j = 0; j < newWeightedMatches[i].size(); ++j) skin->GetVertexInfluence(i)->AddPair(newWeightedMatches[i][j].jointIndex, newWeightedMatches[i][j].weight); } } }