1238 lines
48 KiB
C++
1238 lines
48 KiB
C++
//
|
|
// Copyright (c) 2008-2021 the Urho3D project.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
//
|
|
|
|
#include <Urho3D/Core/Context.h>
|
|
#include <Urho3D/Core/ProcessUtils.h>
|
|
#include <Urho3D/Core/StringUtils.h>
|
|
#include <Urho3D/Graphics/Tangent.h>
|
|
#include <Urho3D/IO/File.h>
|
|
#include <Urho3D/IO/FileSystem.h>
|
|
#include <Urho3D/Resource/XMLFile.h>
|
|
|
|
#include "OgreImporterUtils.h"
|
|
|
|
#ifdef WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include <Urho3D/DebugNew.h>
|
|
|
|
static const int VERTEX_CACHE_SIZE = 32;
|
|
|
|
SharedPtr<Context> context_(new Context());
|
|
SharedPtr<XMLFile> meshFile_(new XMLFile(context_));
|
|
SharedPtr<XMLFile> skelFile_(new XMLFile(context_));
|
|
Vector<ModelIndexBuffer> indexBuffers_;
|
|
Vector<ModelVertexBuffer> vertexBuffers_;
|
|
Vector<Vector<ModelSubGeometryLodLevel> > subGeometries_;
|
|
Vector<Vector3> subGeometryCenters_;
|
|
Vector<ModelBone> bones_;
|
|
Vector<ModelMorph> morphs_;
|
|
Vector<String> materialNames_;
|
|
BoundingBox boundingBox_;
|
|
unsigned maxBones_ = 64;
|
|
unsigned numSubMeshes_ = 0;
|
|
bool useOneBuffer_ = true;
|
|
|
|
int main(int argc, char** argv);
|
|
void Run(const Vector<String>& arguments);
|
|
void LoadSkeleton(const String& skeletonFileName);
|
|
void LoadMesh(const String& inputFileName, bool generateTangents, bool splitSubMeshes, bool exportMorphs);
|
|
void WriteOutput(const String& outputFileName, bool exportAnimations, bool rotationsOnly, bool saveMaterialList);
|
|
void OptimizeIndices(ModelSubGeometryLodLevel* subGeom, ModelVertexBuffer* vb, ModelIndexBuffer* ib);
|
|
void CalculateScore(ModelVertex& vertex);
|
|
String SanitateAssetName(const String& name);
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
Vector<String> arguments;
|
|
|
|
#ifdef WIN32
|
|
arguments = ParseArguments(GetCommandLineW());
|
|
#else
|
|
arguments = ParseArguments(argc, argv);
|
|
#endif
|
|
|
|
Run(arguments);
|
|
return 0;
|
|
}
|
|
|
|
void Run(const Vector<String>& arguments)
|
|
{
|
|
if (arguments.Size() < 2)
|
|
{
|
|
ErrorExit(
|
|
"Usage: OgreImporter <input file> <output file> [options]\n\n"
|
|
"Options:\n"
|
|
"-l Output a material list file\n"
|
|
"-na Do not output animations\n"
|
|
"-nm Do not output morphs\n"
|
|
"-r Output only rotations from animations\n"
|
|
"-s Split each submesh into own vertex buffer\n"
|
|
"-t Generate tangents\n"
|
|
"-mb <x> Maximum number of bones per submesh, default 64\n"
|
|
);
|
|
}
|
|
|
|
bool generateTangents = false;
|
|
bool splitSubMeshes = false;
|
|
bool exportAnimations = true;
|
|
bool exportMorphs = true;
|
|
bool rotationsOnly = false;
|
|
bool saveMaterialList = false;
|
|
|
|
if (arguments.Size() > 2)
|
|
{
|
|
for (unsigned i = 2; i < arguments.Size(); ++i)
|
|
{
|
|
if (arguments[i].Length() > 1 && arguments[i][0] == '-')
|
|
{
|
|
String argument = arguments[i].Substring(1).ToLower();
|
|
if (argument == "l")
|
|
saveMaterialList = true;
|
|
else if (argument == "r")
|
|
rotationsOnly = true;
|
|
else if (argument == "s")
|
|
splitSubMeshes = true;
|
|
else if (argument == "t")
|
|
generateTangents = true;
|
|
else if (argument.Length() == 2 && argument[0] == 'n')
|
|
{
|
|
switch (tolower(argument[1]))
|
|
{
|
|
case 'a':
|
|
exportAnimations = false;
|
|
break;
|
|
|
|
case 'm':
|
|
exportMorphs = false;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
else if (argument == "mb" && i < arguments.Size() - 1)
|
|
{
|
|
maxBones_ = ToUInt(arguments[i + 1]);
|
|
if (maxBones_ < 1)
|
|
maxBones_ = 1;
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LoadMesh(arguments[0], generateTangents, splitSubMeshes, exportMorphs);
|
|
WriteOutput(arguments[1], exportAnimations, rotationsOnly, saveMaterialList);
|
|
|
|
PrintLine("Finished");
|
|
}
|
|
|
|
void LoadSkeleton(const String& skeletonFileName)
|
|
{
|
|
// Process skeleton first (if found)
|
|
XMLElement skeletonRoot;
|
|
File skeletonFileSource(context_);
|
|
skeletonFileSource.Open(skeletonFileName);
|
|
if (!skelFile_->Load(skeletonFileSource))
|
|
PrintLine("Failed to load skeleton " + skeletonFileName);
|
|
skeletonRoot = skelFile_->GetRoot();
|
|
|
|
if (skeletonRoot)
|
|
{
|
|
XMLElement bonesRoot = skeletonRoot.GetChild("bones");
|
|
XMLElement bone = bonesRoot.GetChild("bone");
|
|
while (bone)
|
|
{
|
|
unsigned index = bone.GetInt("id");
|
|
String name = bone.GetAttribute("name");
|
|
if (index >= bones_.Size())
|
|
bones_.Resize(index + 1);
|
|
|
|
// Convert from right- to left-handed
|
|
XMLElement position = bone.GetChild("position");
|
|
float x = position.GetFloat("x");
|
|
float y = position.GetFloat("y");
|
|
float z = position.GetFloat("z");
|
|
Vector3 pos(x, y, -z);
|
|
|
|
XMLElement rotation = bone.GetChild("rotation");
|
|
XMLElement axis = rotation.GetChild("axis");
|
|
float angle = -rotation.GetFloat("angle") * M_RADTODEG;
|
|
x = axis.GetFloat("x");
|
|
y = axis.GetFloat("y");
|
|
z = axis.GetFloat("z");
|
|
Vector3 axisVec(x, y, -z);
|
|
Quaternion rot(angle, axisVec);
|
|
|
|
bones_[index].name_ = name;
|
|
bones_[index].parentIndex_ = index; // Fill in the correct parent later
|
|
bones_[index].bindPosition_ = pos;
|
|
bones_[index].bindRotation_ = rot;
|
|
bones_[index].bindScale_ = Vector3::ONE;
|
|
bones_[index].collisionMask_ = 0;
|
|
bones_[index].radius_ = 0.0f;
|
|
|
|
bone = bone.GetNext("bone");
|
|
}
|
|
|
|
// Go through the bone hierarchy
|
|
XMLElement boneHierarchy = skeletonRoot.GetChild("bonehierarchy");
|
|
XMLElement boneParent = boneHierarchy.GetChild("boneparent");
|
|
while (boneParent)
|
|
{
|
|
String bone = boneParent.GetAttribute("bone");
|
|
String parent = boneParent.GetAttribute("parent");
|
|
unsigned i = 0, j = 0;
|
|
for (i = 0; i < bones_.Size() && bones_[i].name_ != bone; ++i);
|
|
for (j = 0; j < bones_.Size() && bones_[j].name_ != parent; ++j);
|
|
|
|
if (i >= bones_.Size() || j >= bones_.Size())
|
|
ErrorExit("Found indeterminate parent bone assignment");
|
|
bones_[i].parentIndex_ = j;
|
|
|
|
boneParent = boneParent.GetNext("boneparent");
|
|
}
|
|
|
|
// Calculate bone derived positions
|
|
for (unsigned i = 0; i < bones_.Size(); ++i)
|
|
{
|
|
Vector3 derivedPosition = bones_[i].bindPosition_;
|
|
Quaternion derivedRotation = bones_[i].bindRotation_;
|
|
Vector3 derivedScale = bones_[i].bindScale_;
|
|
|
|
unsigned index = bones_[i].parentIndex_;
|
|
if (index != i)
|
|
{
|
|
for (;;)
|
|
{
|
|
derivedPosition = bones_[index].bindPosition_ + (bones_[index].bindRotation_ * (bones_[index].bindScale_ * derivedPosition));
|
|
derivedRotation = bones_[index].bindRotation_ * derivedRotation;
|
|
derivedScale = bones_[index].bindScale_ * derivedScale;
|
|
if (bones_[index].parentIndex_ != index)
|
|
index = bones_[index].parentIndex_;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
bones_[i].derivedPosition_ = derivedPosition;
|
|
bones_[i].derivedRotation_ = derivedRotation;
|
|
bones_[i].derivedScale_ = derivedScale;
|
|
bones_[i].worldTransform_ = Matrix3x4(derivedPosition, derivedRotation, derivedScale);
|
|
bones_[i].inverseWorldTransform_ = bones_[i].worldTransform_.Inverse();
|
|
}
|
|
|
|
PrintLine("Processed skeleton");
|
|
}
|
|
}
|
|
|
|
void LoadMesh(const String& inputFileName, bool generateTangents, bool splitSubMeshes, bool exportMorphs)
|
|
{
|
|
File meshFileSource(context_);
|
|
meshFileSource.Open(inputFileName);
|
|
if (!meshFile_->Load(meshFileSource))
|
|
ErrorExit("Could not load input file " + inputFileName);
|
|
|
|
XMLElement root = meshFile_->GetRoot("mesh");
|
|
XMLElement subMeshes = root.GetChild("submeshes");
|
|
XMLElement skeletonLink = root.GetChild("skeletonlink");
|
|
if (root.IsNull())
|
|
ErrorExit("Could not load input file " + inputFileName);
|
|
|
|
String skeletonName = skeletonLink.GetAttribute("name");
|
|
if (!skeletonName.Empty())
|
|
LoadSkeleton(GetPath(inputFileName) + GetFileName(skeletonName) + ".skeleton.xml");
|
|
|
|
// Check whether there's benefit of avoiding 32bit indices by splitting each submesh into own buffer
|
|
XMLElement subMesh = subMeshes.GetChild("submesh");
|
|
unsigned totalVertices = 0;
|
|
unsigned maxSubMeshVertices = 0;
|
|
while (subMesh)
|
|
{
|
|
materialNames_.Push(subMesh.GetAttribute("material"));
|
|
XMLElement geometry = subMesh.GetChild("geometry");
|
|
if (geometry)
|
|
{
|
|
unsigned vertices = geometry.GetInt("vertexcount");
|
|
totalVertices += vertices;
|
|
if (maxSubMeshVertices < vertices)
|
|
maxSubMeshVertices = vertices;
|
|
}
|
|
++numSubMeshes_;
|
|
|
|
subMesh = subMesh.GetNext("submesh");
|
|
}
|
|
|
|
XMLElement sharedGeometry = root.GetChild("sharedgeometry");
|
|
if (sharedGeometry)
|
|
{
|
|
unsigned vertices = sharedGeometry.GetInt("vertexcount");
|
|
totalVertices += vertices;
|
|
if (maxSubMeshVertices < vertices)
|
|
maxSubMeshVertices = vertices;
|
|
}
|
|
|
|
if (!sharedGeometry && (splitSubMeshes || (totalVertices > 65535 && maxSubMeshVertices <= 65535)))
|
|
{
|
|
useOneBuffer_ = false;
|
|
vertexBuffers_.Resize(numSubMeshes_);
|
|
indexBuffers_.Resize(numSubMeshes_);
|
|
}
|
|
else
|
|
{
|
|
vertexBuffers_.Resize(1);
|
|
indexBuffers_.Resize(1);
|
|
}
|
|
|
|
subMesh = subMeshes.GetChild("submesh");
|
|
unsigned indexStart = 0;
|
|
unsigned vertexStart = 0;
|
|
unsigned subMeshIndex = 0;
|
|
|
|
PODVector<unsigned> vertexStarts;
|
|
vertexStarts.Resize(numSubMeshes_);
|
|
|
|
while (subMesh)
|
|
{
|
|
XMLElement geometry = subMesh.GetChild("geometry");
|
|
XMLElement faces = subMesh.GetChild("faces");
|
|
|
|
// If no submesh vertexbuffer, process the shared geometry, but do it only once
|
|
unsigned vertices = 0;
|
|
if (!geometry)
|
|
{
|
|
vertexStart = 0;
|
|
if (!subMeshIndex)
|
|
geometry = root.GetChild("sharedgeometry");
|
|
}
|
|
|
|
if (geometry)
|
|
vertices = geometry.GetInt("vertexcount");
|
|
|
|
ModelSubGeometryLodLevel subGeometryLodLevel;
|
|
ModelVertexBuffer* vBuf;
|
|
ModelIndexBuffer* iBuf;
|
|
|
|
if (useOneBuffer_)
|
|
{
|
|
vBuf = &vertexBuffers_[0];
|
|
if (vertices)
|
|
vBuf->vertices_.Resize(vertexStart + vertices);
|
|
iBuf = &indexBuffers_[0];
|
|
|
|
subGeometryLodLevel.vertexBuffer_ = 0;
|
|
subGeometryLodLevel.indexBuffer_ = 0;
|
|
}
|
|
else
|
|
{
|
|
vertexStart = 0;
|
|
indexStart = 0;
|
|
|
|
vBuf = &vertexBuffers_[subMeshIndex];
|
|
vBuf->vertices_.Resize(vertices);
|
|
iBuf = &indexBuffers_[subMeshIndex];
|
|
|
|
subGeometryLodLevel.vertexBuffer_ = subMeshIndex;
|
|
subGeometryLodLevel.indexBuffer_ = subMeshIndex;
|
|
}
|
|
|
|
// Store the start vertex for later use
|
|
vertexStarts[subMeshIndex] = vertexStart;
|
|
|
|
// Ogre may have multiple buffers in one submesh. These will be merged into one
|
|
XMLElement bufferDef;
|
|
if (geometry)
|
|
bufferDef = geometry.GetChild("vertexbuffer");
|
|
|
|
while (bufferDef)
|
|
{
|
|
if (bufferDef.HasAttribute("positions"))
|
|
vBuf->elementMask_ |= MASK_POSITION;
|
|
if (bufferDef.HasAttribute("normals"))
|
|
vBuf->elementMask_ |= MASK_NORMAL;
|
|
if (bufferDef.HasAttribute("texture_coords"))
|
|
{
|
|
vBuf->elementMask_ |= MASK_TEXCOORD1;
|
|
if (bufferDef.GetInt("texture_coords") > 1)
|
|
vBuf->elementMask_ |= MASK_TEXCOORD2;
|
|
}
|
|
|
|
unsigned vertexNum = vertexStart;
|
|
if (vertices)
|
|
{
|
|
XMLElement vertex = bufferDef.GetChild("vertex");
|
|
while (vertex)
|
|
{
|
|
XMLElement position = vertex.GetChild("position");
|
|
if (position)
|
|
{
|
|
// Convert from right- to left-handed
|
|
float x = position.GetFloat("x");
|
|
float y = position.GetFloat("y");
|
|
float z = position.GetFloat("z");
|
|
Vector3 vec(x, y, -z);
|
|
|
|
vBuf->vertices_[vertexNum].position_ = vec;
|
|
boundingBox_.Merge(vec);
|
|
}
|
|
XMLElement normal = vertex.GetChild("normal");
|
|
if (normal)
|
|
{
|
|
// Convert from right- to left-handed
|
|
float x = normal.GetFloat("x");
|
|
float y = normal.GetFloat("y");
|
|
float z = normal.GetFloat("z");
|
|
Vector3 vec(x, y, -z);
|
|
|
|
vBuf->vertices_[vertexNum].normal_ = vec;
|
|
}
|
|
XMLElement uv = vertex.GetChild("texcoord");
|
|
if (uv)
|
|
{
|
|
float x = uv.GetFloat("u");
|
|
float y = uv.GetFloat("v");
|
|
Vector2 vec(x, y);
|
|
|
|
vBuf->vertices_[vertexNum].texCoord1_ = vec;
|
|
|
|
if (vBuf->elementMask_ & MASK_TEXCOORD2)
|
|
{
|
|
uv = uv.GetNext("texcoord");
|
|
if (uv)
|
|
{
|
|
float x = uv.GetFloat("u");
|
|
float y = uv.GetFloat("v");
|
|
Vector2 vec(x, y);
|
|
|
|
vBuf->vertices_[vertexNum].texCoord2_ = vec;
|
|
}
|
|
}
|
|
}
|
|
|
|
vertexNum++;
|
|
vertex = vertex.GetNext("vertex");
|
|
}
|
|
}
|
|
|
|
bufferDef = bufferDef.GetNext("vertexbuffer");
|
|
}
|
|
|
|
unsigned triangles = faces.GetInt("count");
|
|
unsigned indices = triangles * 3;
|
|
|
|
XMLElement triangle = faces.GetChild("face");
|
|
while (triangle)
|
|
{
|
|
unsigned v1 = triangle.GetInt("v1");
|
|
unsigned v2 = triangle.GetInt("v2");
|
|
unsigned v3 = triangle.GetInt("v3");
|
|
iBuf->indices_.Push(v3 + vertexStart);
|
|
iBuf->indices_.Push(v2 + vertexStart);
|
|
iBuf->indices_.Push(v1 + vertexStart);
|
|
triangle = triangle.GetNext("face");
|
|
}
|
|
|
|
subGeometryLodLevel.indexStart_ = indexStart;
|
|
subGeometryLodLevel.indexCount_ = indices;
|
|
if (vertexStart + vertices > 65535)
|
|
iBuf->indexSize_ = sizeof(unsigned);
|
|
|
|
XMLElement boneAssignments = subMesh.GetChild("boneassignments");
|
|
if (bones_.Size())
|
|
{
|
|
if (boneAssignments)
|
|
{
|
|
XMLElement boneAssignment = boneAssignments.GetChild("vertexboneassignment");
|
|
while (boneAssignment)
|
|
{
|
|
unsigned vertex = boneAssignment.GetInt("vertexindex") + vertexStart;
|
|
unsigned bone = boneAssignment.GetInt("boneindex");
|
|
float weight = boneAssignment.GetFloat("weight");
|
|
|
|
BoneWeightAssignment assign{static_cast<unsigned char>(bone), weight};
|
|
// Source data might have 0 weights. Disregard these
|
|
if (assign.weight_ > 0.0f)
|
|
{
|
|
subGeometryLodLevel.boneWeights_[vertex].Push(assign);
|
|
|
|
// Require skinning weight to be sufficiently large before vertex contributes to bone hitbox
|
|
if (assign.weight_ > 0.33f)
|
|
{
|
|
// Check distance of vertex from bone to get bone max. radius information
|
|
Vector3 bonePos = bones_[bone].derivedPosition_;
|
|
Vector3 vertexPos = vBuf->vertices_[vertex].position_;
|
|
float distance = (bonePos - vertexPos).Length();
|
|
if (distance > bones_[bone].radius_)
|
|
{
|
|
bones_[bone].collisionMask_ |= 1;
|
|
bones_[bone].radius_ = distance;
|
|
}
|
|
// Build the hitbox for the bone
|
|
bones_[bone].boundingBox_.Merge(bones_[bone].inverseWorldTransform_ * (vertexPos));
|
|
bones_[bone].collisionMask_ |= 2;
|
|
}
|
|
}
|
|
boneAssignment = boneAssignment.GetNext("vertexboneassignment");
|
|
}
|
|
}
|
|
|
|
if ((subGeometryLodLevel.boneWeights_.Size()) && bones_.Size())
|
|
{
|
|
vBuf->elementMask_ |= MASK_BLENDWEIGHTS | MASK_BLENDINDICES;
|
|
bool sorted = false;
|
|
|
|
// If amount of bones is larger than supported by HW skinning, must remap per submesh
|
|
if (bones_.Size() > maxBones_)
|
|
{
|
|
HashMap<unsigned, unsigned> usedBoneMap;
|
|
unsigned remapIndex = 0;
|
|
for (HashMap<unsigned, PODVector<BoneWeightAssignment> >::Iterator i =
|
|
subGeometryLodLevel.boneWeights_.Begin(); i != subGeometryLodLevel.boneWeights_.End(); ++i)
|
|
{
|
|
// Sort the bone assigns by weight
|
|
Sort(i->second_.Begin(), i->second_.End(), CompareWeights);
|
|
|
|
// Use only the first 4 weights
|
|
for (unsigned j = 0; j < i->second_.Size() && j < 4; ++j)
|
|
{
|
|
unsigned originalIndex = i->second_[j].boneIndex_;
|
|
if (!usedBoneMap.Contains(originalIndex))
|
|
{
|
|
usedBoneMap[originalIndex] = remapIndex;
|
|
remapIndex++;
|
|
}
|
|
i->second_[j].boneIndex_ = usedBoneMap[originalIndex];
|
|
}
|
|
}
|
|
|
|
// If still too many bones in one subgeometry, error
|
|
if (usedBoneMap.Size() > maxBones_)
|
|
ErrorExit("Too many bones (limit " + String(maxBones_) + ") in submesh " + String(subMeshIndex + 1));
|
|
|
|
// Write mapping of vertex buffer bone indices to original bone indices
|
|
subGeometryLodLevel.boneMapping_.Resize(usedBoneMap.Size());
|
|
for (HashMap<unsigned, unsigned>::Iterator j = usedBoneMap.Begin(); j != usedBoneMap.End(); ++j)
|
|
subGeometryLodLevel.boneMapping_[j->second_] = j->first_;
|
|
|
|
sorted = true;
|
|
}
|
|
|
|
for (HashMap<unsigned, PODVector<BoneWeightAssignment> >::Iterator i = subGeometryLodLevel.boneWeights_.Begin();
|
|
i != subGeometryLodLevel.boneWeights_.End(); ++i)
|
|
{
|
|
// Sort the bone assigns by weight, if not sorted yet in bone remapping pass
|
|
if (!sorted)
|
|
Sort(i->second_.Begin(), i->second_.End(), CompareWeights);
|
|
|
|
float totalWeight = 0.0f;
|
|
float normalizationFactor = 0.0f;
|
|
|
|
// Calculate normalization factor in case there are more than 4 blend weights, or they do not add up to 1
|
|
for (unsigned j = 0; j < i->second_.Size() && j < 4; ++j)
|
|
totalWeight += i->second_[j].weight_;
|
|
if (totalWeight > 0.0f)
|
|
normalizationFactor = 1.0f / totalWeight;
|
|
|
|
for (unsigned j = 0; j < i->second_.Size() && j < 4; ++j)
|
|
{
|
|
vBuf->vertices_[i->first_].blendIndices_[j] = i->second_[j].boneIndex_;
|
|
vBuf->vertices_[i->first_].blendWeights_[j] = i->second_[j].weight_ * normalizationFactor;
|
|
}
|
|
|
|
// If there are less than 4 blend weights, fill rest with zero
|
|
for (unsigned j = i->second_.Size(); j < 4; ++j)
|
|
{
|
|
vBuf->vertices_[i->first_].blendIndices_[j] = 0;
|
|
vBuf->vertices_[i->first_].blendWeights_[j] = 0.0f;
|
|
}
|
|
|
|
vBuf->vertices_[i->first_].hasBlendWeights_ = true;
|
|
}
|
|
}
|
|
}
|
|
else if (boneAssignments)
|
|
PrintLine("No skeleton loaded, skipping skinning information");
|
|
|
|
// Calculate center for the subgeometry
|
|
Vector3 center = Vector3::ZERO;
|
|
for (unsigned i = 0; i < iBuf->indices_.Size(); i += 3)
|
|
{
|
|
center += vBuf->vertices_[iBuf->indices_[i]].position_;
|
|
center += vBuf->vertices_[iBuf->indices_[i + 1]].position_;
|
|
center += vBuf->vertices_[iBuf->indices_[i + 2]].position_;
|
|
}
|
|
if (iBuf->indices_.Size())
|
|
center /= (float)iBuf->indices_.Size();
|
|
subGeometryCenters_.Push(center);
|
|
|
|
indexStart += indices;
|
|
vertexStart += vertices;
|
|
|
|
OptimizeIndices(&subGeometryLodLevel, vBuf, iBuf);
|
|
|
|
PrintLine("Processed submesh " + String(subMeshIndex + 1) + ": " + String(vertices) + " vertices " +
|
|
String(triangles) + " triangles");
|
|
Vector<ModelSubGeometryLodLevel> thisSubGeometry;
|
|
thisSubGeometry.Push(subGeometryLodLevel);
|
|
subGeometries_.Push(thisSubGeometry);
|
|
|
|
subMesh = subMesh.GetNext("submesh");
|
|
subMeshIndex++;
|
|
}
|
|
|
|
// Process LOD levels, if any
|
|
XMLElement lods = root.GetChild("levelofdetail");
|
|
if (lods)
|
|
{
|
|
try
|
|
{
|
|
// For now, support only generated LODs, where the vertices are the same
|
|
XMLElement lod = lods.GetChild("lodgenerated");
|
|
while (lod)
|
|
{
|
|
float distance = M_EPSILON;
|
|
if (lod.HasAttribute("fromdepthsquared"))
|
|
distance = sqrtf(lod.GetFloat("fromdepthsquared"));
|
|
if (lod.HasAttribute("value"))
|
|
distance = lod.GetFloat("value");
|
|
XMLElement lodSubMesh = lod.GetChild("lodfacelist");
|
|
while (lodSubMesh)
|
|
{
|
|
unsigned subMeshIndex = lodSubMesh.GetInt("submeshindex");
|
|
unsigned triangles = lodSubMesh.GetInt("numfaces");
|
|
|
|
ModelSubGeometryLodLevel newLodLevel;
|
|
ModelSubGeometryLodLevel& originalLodLevel = subGeometries_[subMeshIndex][0];
|
|
|
|
// Copy all initial values
|
|
newLodLevel = originalLodLevel;
|
|
|
|
ModelVertexBuffer* vBuf;
|
|
ModelIndexBuffer* iBuf;
|
|
|
|
if (useOneBuffer_)
|
|
{
|
|
vBuf = &vertexBuffers_[0];
|
|
iBuf = &indexBuffers_[0];
|
|
}
|
|
else
|
|
{
|
|
vBuf = &vertexBuffers_[subMeshIndex];
|
|
iBuf = &indexBuffers_[subMeshIndex];
|
|
}
|
|
|
|
unsigned indexStart = iBuf->indices_.Size();
|
|
unsigned indexCount = triangles * 3;
|
|
unsigned vertexStart = vertexStarts[subMeshIndex];
|
|
|
|
newLodLevel.distance_ = distance;
|
|
newLodLevel.indexStart_ = indexStart;
|
|
newLodLevel.indexCount_ = indexCount;
|
|
|
|
// Append indices to the original index buffer
|
|
XMLElement triangle = lodSubMesh.GetChild("face");
|
|
while (triangle)
|
|
{
|
|
unsigned v1 = triangle.GetInt("v1");
|
|
unsigned v2 = triangle.GetInt("v2");
|
|
unsigned v3 = triangle.GetInt("v3");
|
|
iBuf->indices_.Push(v3 + vertexStart);
|
|
iBuf->indices_.Push(v2 + vertexStart);
|
|
iBuf->indices_.Push(v1 + vertexStart);
|
|
triangle = triangle.GetNext("face");
|
|
}
|
|
|
|
OptimizeIndices(&newLodLevel, vBuf, iBuf);
|
|
|
|
subGeometries_[subMeshIndex].Push(newLodLevel);
|
|
PrintLine("Processed LOD level for submesh " + String(subMeshIndex + 1) + ": distance " + String(distance));
|
|
|
|
lodSubMesh = lodSubMesh.GetNext("lodfacelist");
|
|
}
|
|
lod = lod.GetNext("lodgenerated");
|
|
}
|
|
}
|
|
catch (...) {}
|
|
}
|
|
|
|
// Process poses/morphs
|
|
// First find out all pose definitions
|
|
if (exportMorphs)
|
|
{
|
|
try
|
|
{
|
|
Vector<XMLElement> poses;
|
|
XMLElement posesRoot = root.GetChild("poses");
|
|
if (posesRoot)
|
|
{
|
|
XMLElement pose = posesRoot.GetChild("pose");
|
|
while (pose)
|
|
{
|
|
poses.Push(pose);
|
|
pose = pose.GetNext("pose");
|
|
}
|
|
}
|
|
|
|
// Then process animations using the poses
|
|
XMLElement animsRoot = root.GetChild("animations");
|
|
if (animsRoot)
|
|
{
|
|
XMLElement anim = animsRoot.GetChild("animation");
|
|
while (anim)
|
|
{
|
|
String name = anim.GetAttribute("name");
|
|
float length = anim.GetFloat("length");
|
|
HashSet<unsigned> usedPoses;
|
|
XMLElement tracks = anim.GetChild("tracks");
|
|
if (tracks)
|
|
{
|
|
XMLElement track = tracks.GetChild("track");
|
|
while (track)
|
|
{
|
|
XMLElement keyframes = track.GetChild("keyframes");
|
|
if (keyframes)
|
|
{
|
|
XMLElement keyframe = keyframes.GetChild("keyframe");
|
|
while (keyframe)
|
|
{
|
|
float time = keyframe.GetFloat("time");
|
|
XMLElement poseref = keyframe.GetChild("poseref");
|
|
// Get only the end pose
|
|
if (poseref && time == length)
|
|
usedPoses.Insert(poseref.GetInt("poseindex"));
|
|
|
|
keyframe = keyframe.GetNext("keyframe");
|
|
}
|
|
}
|
|
track = track.GetNext("track");
|
|
}
|
|
}
|
|
|
|
if (usedPoses.Size())
|
|
{
|
|
ModelMorph newMorph;
|
|
newMorph.name_ = name;
|
|
|
|
if (useOneBuffer_)
|
|
newMorph.buffers_.Resize(1);
|
|
else
|
|
newMorph.buffers_.Resize(usedPoses.Size());
|
|
|
|
unsigned bufIndex = 0;
|
|
|
|
for (HashSet<unsigned>::Iterator i = usedPoses.Begin(); i != usedPoses.End(); ++i)
|
|
{
|
|
XMLElement pose = poses[*i];
|
|
unsigned targetSubMesh = pose.GetInt("index");
|
|
XMLElement poseOffset = pose.GetChild("poseoffset");
|
|
|
|
if (useOneBuffer_)
|
|
newMorph.buffers_[bufIndex].vertexBuffer_ = 0;
|
|
else
|
|
newMorph.buffers_[bufIndex].vertexBuffer_ = targetSubMesh;
|
|
|
|
newMorph.buffers_[bufIndex].elementMask_ = MASK_POSITION;
|
|
|
|
ModelVertexBuffer* vBuf = &vertexBuffers_[newMorph.buffers_[bufIndex].vertexBuffer_];
|
|
|
|
while (poseOffset)
|
|
{
|
|
// Convert from right- to left-handed
|
|
unsigned vertexIndex = poseOffset.GetInt("index") + vertexStarts[targetSubMesh];
|
|
float x = poseOffset.GetFloat("x");
|
|
float y = poseOffset.GetFloat("y");
|
|
float z = poseOffset.GetFloat("z");
|
|
Vector3 vec(x, y, -z);
|
|
|
|
if (vBuf->morphCount_ == 0)
|
|
{
|
|
vBuf->morphStart_ = vertexIndex;
|
|
vBuf->morphCount_ = 1;
|
|
}
|
|
else
|
|
{
|
|
unsigned first = vBuf->morphStart_;
|
|
unsigned last = first + vBuf->morphCount_ - 1;
|
|
if (vertexIndex < first)
|
|
first = vertexIndex;
|
|
if (vertexIndex > last)
|
|
last = vertexIndex;
|
|
vBuf->morphStart_ = first;
|
|
vBuf->morphCount_ = last - first + 1;
|
|
}
|
|
|
|
ModelVertex newVertex;
|
|
newVertex.position_ = vec;
|
|
newMorph.buffers_[bufIndex].vertices_.Push(MakePair(vertexIndex, newVertex));
|
|
poseOffset = poseOffset.GetNext("poseoffset");
|
|
}
|
|
|
|
if (!useOneBuffer_)
|
|
++bufIndex;
|
|
}
|
|
morphs_.Push(newMorph);
|
|
PrintLine("Processed morph " + name + " with " + String(usedPoses.Size()) + " sub-poses");
|
|
}
|
|
|
|
anim = anim.GetNext("animation");
|
|
}
|
|
}
|
|
}
|
|
catch (...) {}
|
|
}
|
|
|
|
// Check any of the buffers for vertices with missing blend weight assignments
|
|
for (unsigned i = 0; i < vertexBuffers_.Size(); ++i)
|
|
{
|
|
if (vertexBuffers_[i].elementMask_ & MASK_BLENDWEIGHTS)
|
|
{
|
|
for (unsigned j = 0; j < vertexBuffers_[i].vertices_.Size(); ++j)
|
|
if (!vertexBuffers_[i].vertices_[j].hasBlendWeights_)
|
|
ErrorExit("Found a vertex with missing skinning information");
|
|
}
|
|
}
|
|
|
|
// Tangent generation
|
|
if (generateTangents)
|
|
{
|
|
for (unsigned i = 0; i < subGeometries_.Size(); ++i)
|
|
{
|
|
for (unsigned j = 0; j < subGeometries_[i].Size(); ++j)
|
|
{
|
|
ModelVertexBuffer& vBuf = vertexBuffers_[subGeometries_[i][j].vertexBuffer_];
|
|
ModelIndexBuffer& iBuf = indexBuffers_[subGeometries_[i][j].indexBuffer_];
|
|
unsigned indexStart = subGeometries_[i][j].indexStart_;
|
|
unsigned indexCount = subGeometries_[i][j].indexCount_;
|
|
|
|
// If already has tangents, do not regenerate
|
|
if (vBuf.elementMask_ & MASK_TANGENT || vBuf.vertices_.Empty() || iBuf.indices_.Empty())
|
|
continue;
|
|
|
|
vBuf.elementMask_ |= MASK_TANGENT;
|
|
|
|
if ((vBuf.elementMask_ & (MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1)) != (MASK_POSITION | MASK_NORMAL |
|
|
MASK_TEXCOORD1))
|
|
ErrorExit("To generate tangents, positions normals and texcoords are required");
|
|
|
|
GenerateTangents(&vBuf.vertices_[0], sizeof(ModelVertex), &iBuf.indices_[0], sizeof(unsigned), indexStart,
|
|
indexCount, offsetof(ModelVertex, normal_), offsetof(ModelVertex, texCoord1_), offsetof(ModelVertex,
|
|
tangent_));
|
|
|
|
PrintLine("Generated tangents");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WriteOutput(const String& outputFileName, bool exportAnimations, bool rotationsOnly, bool saveMaterialList)
|
|
{
|
|
/// \todo Use save functions of Model & Animation classes
|
|
|
|
// Begin serialization
|
|
{
|
|
File dest(context_);
|
|
if (!dest.Open(outputFileName, FILE_WRITE))
|
|
ErrorExit("Could not open output file " + outputFileName);
|
|
|
|
// ID
|
|
dest.WriteFileID("UMD2");
|
|
|
|
// Vertexbuffers
|
|
dest.WriteUInt(vertexBuffers_.Size());
|
|
for (unsigned i = 0; i < vertexBuffers_.Size(); ++i)
|
|
vertexBuffers_[i].WriteData(dest);
|
|
|
|
// Indexbuffers
|
|
dest.WriteUInt(indexBuffers_.Size());
|
|
for (unsigned i = 0; i < indexBuffers_.Size(); ++i)
|
|
indexBuffers_[i].WriteData(dest);
|
|
|
|
// Subgeometries
|
|
dest.WriteUInt(subGeometries_.Size());
|
|
for (unsigned i = 0; i < subGeometries_.Size(); ++i)
|
|
{
|
|
// Write bone mapping info from the first LOD level. It does not change for further LODs
|
|
dest.WriteUInt(subGeometries_[i][0].boneMapping_.Size());
|
|
for (unsigned k = 0; k < subGeometries_[i][0].boneMapping_.Size(); ++k)
|
|
dest.WriteUInt(subGeometries_[i][0].boneMapping_[k]);
|
|
|
|
// Lod levels for this subgeometry
|
|
dest.WriteUInt(subGeometries_[i].Size());
|
|
for (unsigned j = 0; j < subGeometries_[i].Size(); ++j)
|
|
{
|
|
dest.WriteFloat(subGeometries_[i][j].distance_);
|
|
dest.WriteUInt((unsigned)subGeometries_[i][j].primitiveType_);
|
|
dest.WriteUInt(subGeometries_[i][j].vertexBuffer_);
|
|
dest.WriteUInt(subGeometries_[i][j].indexBuffer_);
|
|
dest.WriteUInt(subGeometries_[i][j].indexStart_);
|
|
dest.WriteUInt(subGeometries_[i][j].indexCount_);
|
|
}
|
|
}
|
|
|
|
// Morphs
|
|
dest.WriteUInt(morphs_.Size());
|
|
for (unsigned i = 0; i < morphs_.Size(); ++i)
|
|
morphs_[i].WriteData(dest);
|
|
|
|
// Skeleton
|
|
dest.WriteUInt(bones_.Size());
|
|
for (unsigned i = 0; i < bones_.Size(); ++i)
|
|
{
|
|
dest.WriteString(bones_[i].name_);
|
|
dest.WriteUInt(bones_[i].parentIndex_);
|
|
dest.WriteVector3(bones_[i].bindPosition_);
|
|
dest.WriteQuaternion(bones_[i].bindRotation_);
|
|
dest.WriteVector3(bones_[i].bindScale_);
|
|
|
|
Matrix3x4 offsetMatrix(bones_[i].derivedPosition_, bones_[i].derivedRotation_, bones_[i].derivedScale_);
|
|
offsetMatrix = offsetMatrix.Inverse();
|
|
dest.Write(offsetMatrix.Data(), sizeof(Matrix3x4));
|
|
|
|
dest.WriteUByte(bones_[i].collisionMask_);
|
|
if (bones_[i].collisionMask_ & 1u)
|
|
dest.WriteFloat(bones_[i].radius_);
|
|
if (bones_[i].collisionMask_ & 2u)
|
|
dest.WriteBoundingBox(bones_[i].boundingBox_);
|
|
}
|
|
|
|
// Bounding box
|
|
dest.WriteBoundingBox(boundingBox_);
|
|
|
|
// Geometry centers
|
|
for (unsigned i = 0; i < subGeometryCenters_.Size(); ++i)
|
|
dest.WriteVector3(subGeometryCenters_[i]);
|
|
}
|
|
|
|
if (saveMaterialList)
|
|
{
|
|
String materialListName = ReplaceExtension(outputFileName, ".txt");
|
|
File listFile(context_);
|
|
if (listFile.Open(materialListName, FILE_WRITE))
|
|
{
|
|
for (unsigned i = 0; i < materialNames_.Size(); ++i)
|
|
{
|
|
// Assume the materials will be located inside the standard Materials subdirectory
|
|
listFile.WriteLine("Materials/" + ReplaceExtension(SanitateAssetName(materialNames_[i]), ".xml"));
|
|
}
|
|
}
|
|
else
|
|
PrintLine("Warning: could not write material list file " + materialListName);
|
|
}
|
|
|
|
XMLElement skeletonRoot = skelFile_->GetRoot("skeleton");
|
|
if (skeletonRoot && exportAnimations)
|
|
{
|
|
// Go through animations
|
|
XMLElement animationsRoot = skeletonRoot.GetChild("animations");
|
|
if (animationsRoot)
|
|
{
|
|
XMLElement animation = animationsRoot.GetChild("animation");
|
|
while (animation)
|
|
{
|
|
ModelAnimation newAnimation;
|
|
newAnimation.name_ = animation.GetAttribute("name");
|
|
newAnimation.length_ = animation.GetFloat("length");
|
|
|
|
XMLElement tracksRoot = animation.GetChild("tracks");
|
|
XMLElement track = tracksRoot.GetChild("track");
|
|
while (track)
|
|
{
|
|
String trackName = track.GetAttribute("bone");
|
|
ModelBone* bone = nullptr;
|
|
for (unsigned i = 0; i < bones_.Size(); ++i)
|
|
{
|
|
if (bones_[i].name_ == trackName)
|
|
{
|
|
bone = &bones_[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!bone)
|
|
ErrorExit("Found animation track for unknown bone " + trackName);
|
|
|
|
AnimationTrack newAnimationTrack;
|
|
newAnimationTrack.name_ = trackName;
|
|
if (!rotationsOnly)
|
|
newAnimationTrack.channelMask_ = CHANNEL_POSITION | CHANNEL_ROTATION;
|
|
else
|
|
newAnimationTrack.channelMask_ = CHANNEL_ROTATION;
|
|
|
|
XMLElement keyFramesRoot = track.GetChild("keyframes");
|
|
XMLElement keyFrame = keyFramesRoot.GetChild("keyframe");
|
|
while (keyFrame)
|
|
{
|
|
AnimationKeyFrame newKeyFrame;
|
|
|
|
// Convert from right- to left-handed
|
|
XMLElement position = keyFrame.GetChild("translate");
|
|
float x = position.GetFloat("x");
|
|
float y = position.GetFloat("y");
|
|
float z = position.GetFloat("z");
|
|
Vector3 pos(x, y, -z);
|
|
|
|
XMLElement rotation = keyFrame.GetChild("rotate");
|
|
XMLElement axis = rotation.GetChild("axis");
|
|
float angle = -rotation.GetFloat("angle") * M_RADTODEG;
|
|
x = axis.GetFloat("x");
|
|
y = axis.GetFloat("y");
|
|
z = axis.GetFloat("z");
|
|
Vector3 axisVec(x, y, -z);
|
|
Quaternion rot(angle, axisVec);
|
|
|
|
// Transform from bind-pose relative into absolute
|
|
pos = bone->bindPosition_ + pos;
|
|
rot = bone->bindRotation_ * rot;
|
|
|
|
newKeyFrame.time_ = keyFrame.GetFloat("time");
|
|
newKeyFrame.position_ = pos;
|
|
newKeyFrame.rotation_ = rot;
|
|
|
|
newAnimationTrack.keyFrames_.Push(newKeyFrame);
|
|
keyFrame = keyFrame.GetNext("keyframe");
|
|
}
|
|
|
|
// Make sure keyframes are sorted from beginning to end
|
|
Sort(newAnimationTrack.keyFrames_.Begin(), newAnimationTrack.keyFrames_.End(), CompareKeyFrames);
|
|
|
|
// Do not add tracks with no keyframes
|
|
if (newAnimationTrack.keyFrames_.Size())
|
|
newAnimation.tracks_.Push(newAnimationTrack);
|
|
|
|
track = track.GetNext("track");
|
|
}
|
|
|
|
// Write each animation into a separate file
|
|
String animationFileName = outputFileName.Replaced(".mdl", "");
|
|
animationFileName += "_" + newAnimation.name_ + ".ani";
|
|
|
|
File dest(context_);
|
|
if (!dest.Open(animationFileName, FILE_WRITE))
|
|
ErrorExit("Could not open output file " + animationFileName);
|
|
|
|
dest.WriteFileID("UANI");
|
|
dest.WriteString(newAnimation.name_);
|
|
dest.WriteFloat(newAnimation.length_);
|
|
dest.WriteUInt(newAnimation.tracks_.Size());
|
|
for (unsigned i = 0; i < newAnimation.tracks_.Size(); ++i)
|
|
{
|
|
AnimationTrack& track = newAnimation.tracks_[i];
|
|
dest.WriteString(track.name_);
|
|
dest.WriteUByte(track.channelMask_);
|
|
dest.WriteUInt(track.keyFrames_.Size());
|
|
for (unsigned j = 0; j < track.keyFrames_.Size(); ++j)
|
|
{
|
|
AnimationKeyFrame& keyFrame = track.keyFrames_[j];
|
|
dest.WriteFloat(keyFrame.time_);
|
|
if (track.channelMask_ & CHANNEL_POSITION)
|
|
dest.WriteVector3(keyFrame.position_);
|
|
if (track.channelMask_ & CHANNEL_ROTATION)
|
|
dest.WriteQuaternion(keyFrame.rotation_);
|
|
if (track.channelMask_ & CHANNEL_SCALE)
|
|
dest.WriteVector3(keyFrame.scale_);
|
|
}
|
|
}
|
|
|
|
animation = animation.GetNext("animation");
|
|
PrintLine("Processed animation " + newAnimation.name_);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OptimizeIndices(ModelSubGeometryLodLevel* subGeom, ModelVertexBuffer* vb, ModelIndexBuffer* ib)
|
|
{
|
|
PODVector<Triangle> oldTriangles;
|
|
PODVector<Triangle> newTriangles;
|
|
|
|
if (subGeom->indexCount_ % 3)
|
|
{
|
|
PrintLine("Index count is not divisible by 3, skipping index optimization");
|
|
return;
|
|
}
|
|
|
|
for (unsigned i = 0; i < vb->vertices_.Size(); ++i)
|
|
{
|
|
vb->vertices_[i].useCount_ = 0;
|
|
vb->vertices_[i].cachePosition_ = -1;
|
|
}
|
|
|
|
for (unsigned i = subGeom->indexStart_; i < subGeom->indexStart_ + subGeom->indexCount_; i += 3)
|
|
{
|
|
Triangle triangle{ib->indices_[i], ib->indices_[i + 1], ib->indices_[i + 2]};
|
|
vb->vertices_[triangle.v0_].useCount_++;
|
|
vb->vertices_[triangle.v1_].useCount_++;
|
|
vb->vertices_[triangle.v2_].useCount_++;
|
|
oldTriangles.Push(triangle);
|
|
}
|
|
|
|
for (unsigned i = 0; i < vb->vertices_.Size(); ++i)
|
|
CalculateScore(vb->vertices_[i]);
|
|
|
|
PODVector<unsigned> vertexCache;
|
|
|
|
while (oldTriangles.Size())
|
|
{
|
|
unsigned bestTriangle = M_MAX_UNSIGNED;
|
|
float bestTriangleScore = -1.0f;
|
|
|
|
// Find the best triangle at this point
|
|
for (unsigned i = 0; i < oldTriangles.Size(); ++i)
|
|
{
|
|
Triangle& triangle = oldTriangles[i];
|
|
float triangleScore =
|
|
vb->vertices_[triangle.v0_].score_ +
|
|
vb->vertices_[triangle.v1_].score_ +
|
|
vb->vertices_[triangle.v2_].score_;
|
|
|
|
if (triangleScore > bestTriangleScore)
|
|
{
|
|
bestTriangle = i;
|
|
bestTriangleScore = triangleScore;
|
|
}
|
|
}
|
|
|
|
if (bestTriangle == M_MAX_UNSIGNED)
|
|
{
|
|
PrintLine("Could not find next triangle, aborting index optimization");
|
|
return;
|
|
}
|
|
|
|
// Add the best triangle
|
|
Triangle triangleCopy = oldTriangles[bestTriangle];
|
|
newTriangles.Push(triangleCopy);
|
|
oldTriangles.Erase(oldTriangles.Begin() + bestTriangle);
|
|
|
|
// Reduce the use count
|
|
vb->vertices_[triangleCopy.v0_].useCount_--;
|
|
vb->vertices_[triangleCopy.v1_].useCount_--;
|
|
vb->vertices_[triangleCopy.v2_].useCount_--;
|
|
|
|
// Model the LRU cache behaviour
|
|
// Erase the triangle vertices from the middle of the cache, if they were there
|
|
for (unsigned i = 0; i < vertexCache.Size(); ++i)
|
|
{
|
|
if ((vertexCache[i] == triangleCopy.v0_) ||
|
|
(vertexCache[i] == triangleCopy.v1_) ||
|
|
(vertexCache[i] == triangleCopy.v2_))
|
|
{
|
|
vertexCache.Erase(vertexCache.Begin() + i);
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// Then push them to the front
|
|
vertexCache.Insert(vertexCache.Begin(), triangleCopy.v0_);
|
|
vertexCache.Insert(vertexCache.Begin(), triangleCopy.v1_);
|
|
vertexCache.Insert(vertexCache.Begin(), triangleCopy.v2_);
|
|
|
|
// Update positions & scores of all vertices in the cache
|
|
// Give position -1 if vertex is going to be erased
|
|
for (unsigned i = 0; i < vertexCache.Size(); ++i)
|
|
{
|
|
ModelVertex& vertex = vb->vertices_[vertexCache[i]];
|
|
if (i >= VERTEX_CACHE_SIZE)
|
|
vertex.cachePosition_ = -1;
|
|
else
|
|
vertex.cachePosition_ = i;
|
|
CalculateScore(vertex);
|
|
}
|
|
|
|
// Finally erase the extra vertices
|
|
if (vertexCache.Size() > VERTEX_CACHE_SIZE)
|
|
vertexCache.Resize(VERTEX_CACHE_SIZE);
|
|
}
|
|
|
|
// Rewrite the index data now
|
|
unsigned i = subGeom->indexStart_;
|
|
for (unsigned j = 0; j < newTriangles.Size(); ++j)
|
|
{
|
|
ib->indices_[i++] = newTriangles[j].v0_;
|
|
ib->indices_[i++] = newTriangles[j].v1_;
|
|
ib->indices_[i++] = newTriangles[j].v2_;
|
|
}
|
|
}
|
|
|
|
void CalculateScore(ModelVertex& vertex)
|
|
{
|
|
// Linear-Speed Vertex Cache Optimisation by Tom Forsyth from
|
|
// http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
|
|
const float cacheDecayPower = 1.5f;
|
|
const float lastTriScore = 0.75f;
|
|
const float valenceBoostScale = 2.0f;
|
|
const float valenceBoostPower = 0.5f;
|
|
|
|
if (vertex.useCount_ == 0)
|
|
{
|
|
// No tri needs this vertex!
|
|
vertex.score_ = -1.0f;
|
|
return;
|
|
}
|
|
|
|
float score = 0.0f;
|
|
int cachePosition = vertex.cachePosition_;
|
|
if (cachePosition < 0)
|
|
{
|
|
// Vertex is not in FIFO cache - no score.
|
|
}
|
|
else
|
|
{
|
|
if (cachePosition < 3)
|
|
{
|
|
// This vertex was used in the last triangle,
|
|
// so it has a fixed score, whichever of the three
|
|
// it's in. Otherwise, you can get very different
|
|
// answers depending on whether you add
|
|
// the triangle 1,2,3 or 3,1,2 - which is silly.
|
|
score = lastTriScore;
|
|
}
|
|
else
|
|
{
|
|
// Points for being high in the cache.
|
|
const float scaler = 1.0f / (VERTEX_CACHE_SIZE - 3);
|
|
score = 1.0f - (cachePosition - 3) * scaler;
|
|
score = powf(score, cacheDecayPower);
|
|
}
|
|
}
|
|
|
|
// Bonus points for having a low number of tris still to
|
|
// use the vert, so we get rid of lone verts quickly.
|
|
float valenceBoost = powf((float)vertex.useCount_, -valenceBoostPower);
|
|
score += valenceBoostScale * valenceBoost;
|
|
vertex.score_ = score;
|
|
}
|
|
|
|
String SanitateAssetName(const String& name)
|
|
{
|
|
String fixedName = name;
|
|
fixedName.Replace("<", "");
|
|
fixedName.Replace(">", "");
|
|
fixedName.Replace("?", "");
|
|
fixedName.Replace("*", "");
|
|
fixedName.Replace(":", "");
|
|
fixedName.Replace("\"", "");
|
|
fixedName.Replace("/", "");
|
|
fixedName.Replace("\\", "");
|
|
fixedName.Replace("|", "");
|
|
|
|
return fixedName;
|
|
}
|