|
@@ -225,12 +225,12 @@ MeshXml *OgreXmlSerializer::ImportMesh(XmlParser *parser) {
|
|
|
}
|
|
|
|
|
|
void OgreXmlSerializer::ReadMesh(MeshXml *mesh) {
|
|
|
- XmlNode *root = mParser->getRootNode();
|
|
|
- if (nullptr == root || std::string(nnMesh)!=root->name()) {
|
|
|
- throw DeadlyImportError("Root node is <" + std::string(root->name()) + "> expecting <mesh>");
|
|
|
+ XmlNode root = mParser->getRootNode();
|
|
|
+ if (nullptr == root || std::string(nnMesh) != root.name()) {
|
|
|
+ throw DeadlyImportError("Root node is <" + std::string(root.name()) + "> expecting <mesh>");
|
|
|
}
|
|
|
|
|
|
- for (XmlNode currentNode : root->children()) {
|
|
|
+ for (XmlNode currentNode : root.children()) {
|
|
|
const std::string currentName = currentNode.name();
|
|
|
if (currentName == nnSharedGeometry) {
|
|
|
mesh->sharedVertexData = new VertexDataXml();
|
|
@@ -242,43 +242,8 @@ void OgreXmlSerializer::ReadMesh(MeshXml *mesh) {
|
|
|
} else if (currentName == nnSkeletonLink) {
|
|
|
}
|
|
|
}
|
|
|
- /*if (NextNode() != nnMesh) {
|
|
|
- }*/
|
|
|
|
|
|
ASSIMP_LOG_VERBOSE_DEBUG("Reading Mesh");
|
|
|
-
|
|
|
- //NextNode();
|
|
|
-
|
|
|
- // Root level nodes
|
|
|
- /*while (m_currentNodeName == nnSharedGeometry ||
|
|
|
- m_currentNodeName == nnSubMeshes ||
|
|
|
- m_currentNodeName == nnSkeletonLink ||
|
|
|
- m_currentNodeName == nnBoneAssignments ||
|
|
|
- m_currentNodeName == nnLOD ||
|
|
|
- m_currentNodeName == nnSubMeshNames ||
|
|
|
- m_currentNodeName == nnExtremes ||
|
|
|
- m_currentNodeName == nnPoses ||
|
|
|
- m_currentNodeName == nnAnimations) {
|
|
|
- if (m_currentNodeName == nnSharedGeometry) {
|
|
|
- mesh->sharedVertexData = new VertexDataXml();
|
|
|
- ReadGeometry(mesh->sharedVertexData);
|
|
|
- } else if (m_currentNodeName == nnSubMeshes) {
|
|
|
- NextNode();
|
|
|
- while (m_currentNodeName == nnSubMesh) {
|
|
|
- ReadSubMesh(mesh);
|
|
|
- }
|
|
|
- } else if (m_currentNodeName == nnBoneAssignments) {
|
|
|
- ReadBoneAssignments(mesh->sharedVertexData);
|
|
|
- } else if (m_currentNodeName == nnSkeletonLink) {
|
|
|
- mesh->skeletonRef = ReadAttribute<std::string>("name");
|
|
|
- ASSIMP_LOG_VERBOSE_DEBUG_F("Read skeleton link ", mesh->skeletonRef);
|
|
|
- NextNode();
|
|
|
- }
|
|
|
- // Assimp incompatible/ignored nodes
|
|
|
- else {
|
|
|
- SkipCurrentNode();
|
|
|
- }
|
|
|
- }*/
|
|
|
}
|
|
|
|
|
|
void OgreXmlSerializer::ReadGeometry(XmlNode &node, VertexDataXml *dest) {
|
|
@@ -291,10 +256,6 @@ void OgreXmlSerializer::ReadGeometry(XmlNode &node, VertexDataXml *dest) {
|
|
|
ReadGeometryVertexBuffer(currentNode, dest);
|
|
|
}
|
|
|
}
|
|
|
- //NextNode();
|
|
|
- /*while (m_currentNodeName == nnVertexBuffer) {
|
|
|
- ReadGeometryVertexBuffer(dest);
|
|
|
- }*/
|
|
|
}
|
|
|
|
|
|
void OgreXmlSerializer::ReadGeometryVertexBuffer(XmlNode &node, VertexDataXml *dest) {
|
|
@@ -328,11 +289,6 @@ void OgreXmlSerializer::ReadGeometryVertexBuffer(XmlNode &node, VertexDataXml *d
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /*bool warnBinormal = true;
|
|
|
- bool warnColorDiffuse = true;
|
|
|
- bool warnColorSpecular = true;*/
|
|
|
-
|
|
|
- //NextNode();
|
|
|
for (XmlNode currentNode : node.children()) {
|
|
|
const std::string ¤tName = currentNode.name();
|
|
|
if (positions && currentName == nnPosition) {
|
|
@@ -363,84 +319,6 @@ void OgreXmlSerializer::ReadGeometryVertexBuffer(XmlNode &node, VertexDataXml *d
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /*while (m_currentNodeName == nnVertex ||
|
|
|
- m_currentNodeName == nnPosition ||
|
|
|
- m_currentNodeName == nnNormal ||
|
|
|
- m_currentNodeName == nnTangent ||
|
|
|
- m_currentNodeName == nnBinormal ||
|
|
|
- m_currentNodeName == nnTexCoord ||
|
|
|
- m_currentNodeName == nnColorDiffuse ||
|
|
|
- m_currentNodeName == nnColorSpecular) {
|
|
|
- if (m_currentNodeName == nnVertex) {
|
|
|
- NextNode();
|
|
|
- }
|
|
|
-
|
|
|
- /// @todo Implement nnBinormal, nnColorDiffuse and nnColorSpecular
|
|
|
-
|
|
|
- if (positions && m_currentNodeName == nnPosition) {
|
|
|
- aiVector3D pos;
|
|
|
- pos.x = ReadAttribute<float>(anX);
|
|
|
- pos.y = ReadAttribute<float>(anY);
|
|
|
- pos.z = ReadAttribute<float>(anZ);
|
|
|
- dest->positions.push_back(pos);
|
|
|
- } else if (normals && m_currentNodeName == nnNormal) {
|
|
|
- aiVector3D normal;
|
|
|
- normal.x = ReadAttribute<float>(anX);
|
|
|
- normal.y = ReadAttribute<float>(anY);
|
|
|
- normal.z = ReadAttribute<float>(anZ);
|
|
|
- dest->normals.push_back(normal);
|
|
|
- } else if (tangents && m_currentNodeName == nnTangent) {
|
|
|
- aiVector3D tangent;
|
|
|
- tangent.x = ReadAttribute<float>(anX);
|
|
|
- tangent.y = ReadAttribute<float>(anY);
|
|
|
- tangent.z = ReadAttribute<float>(anZ);
|
|
|
- dest->tangents.push_back(tangent);
|
|
|
- } else if (uvs > 0 && m_currentNodeName == nnTexCoord) {
|
|
|
- for (auto &curUvs : dest->uvs) {
|
|
|
- if (m_currentNodeName != nnTexCoord) {
|
|
|
- throw DeadlyImportError("Vertex buffer declared more UVs than can be found in a vertex");
|
|
|
- }
|
|
|
-
|
|
|
- aiVector3D uv;
|
|
|
- uv.x = ReadAttribute<float>("u");
|
|
|
- uv.y = (ReadAttribute<float>("v") * -1) + 1; // Flip UV from Ogre to Assimp form
|
|
|
- curUvs.push_back(uv);
|
|
|
-
|
|
|
- NextNode();
|
|
|
- }
|
|
|
- // Continue main loop as above already read next node
|
|
|
- continue;
|
|
|
- } else {
|
|
|
- /// @todo Remove this stuff once implemented. We only want to log warnings once per element.
|
|
|
- bool warn = true;
|
|
|
- if (m_currentNodeName == nnBinormal) {
|
|
|
- if (warnBinormal) {
|
|
|
- warnBinormal = false;
|
|
|
- } else {
|
|
|
- warn = false;
|
|
|
- }
|
|
|
- } else if (m_currentNodeName == nnColorDiffuse) {
|
|
|
- if (warnColorDiffuse) {
|
|
|
- warnColorDiffuse = false;
|
|
|
- } else {
|
|
|
- warn = false;
|
|
|
- }
|
|
|
- } else if (m_currentNodeName == nnColorSpecular) {
|
|
|
- if (warnColorSpecular) {
|
|
|
- warnColorSpecular = false;
|
|
|
- } else {
|
|
|
- warn = false;
|
|
|
- }
|
|
|
- }
|
|
|
- if (warn) {
|
|
|
- ASSIMP_LOG_WARN_F("Vertex buffer attribute read not implemented for element: ", m_currentNodeName);
|
|
|
- }
|
|
|
- }*/
|
|
|
-
|
|
|
- // Advance
|
|
|
- //NextNode();
|
|
|
- //}
|
|
|
-
|
|
|
// Sanity checks
|
|
|
if (dest->positions.size() != dest->count) {
|
|
|
throw DeadlyImportError(Formatter::format() << "Read only " << dest->positions.size() << " positions when should have read " << dest->count);
|
|
@@ -525,57 +403,6 @@ void OgreXmlSerializer::ReadSubMesh(XmlNode &node, MeshXml *mesh) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /*NextNode();
|
|
|
- while (m_currentNodeName == nnFaces ||
|
|
|
- m_currentNodeName == nnGeometry ||
|
|
|
- m_currentNodeName == nnTextures ||
|
|
|
- m_currentNodeName == nnBoneAssignments) {
|
|
|
- if (m_currentNodeName == nnFaces) {
|
|
|
- submesh->indexData->faceCount = ReadAttribute<uint32_t>(anCount);
|
|
|
- submesh->indexData->faces.reserve(submesh->indexData->faceCount);
|
|
|
-
|
|
|
- NextNode();
|
|
|
- while (m_currentNodeName == nnFace) {
|
|
|
- aiFace face;
|
|
|
- face.mNumIndices = 3;
|
|
|
- face.mIndices = new unsigned int[3];
|
|
|
- face.mIndices[0] = ReadAttribute<uint32_t>(anV1);
|
|
|
- face.mIndices[1] = ReadAttribute<uint32_t>(anV2);
|
|
|
- face.mIndices[2] = ReadAttribute<uint32_t>(anV3);
|
|
|
-
|
|
|
- /// @todo Support quads if Ogre even supports them in XML (I'm not sure but I doubt it)
|
|
|
- if (!quadWarned && HasAttribute(anV4)) {
|
|
|
- ASSIMP_LOG_WARN("Submesh <face> has quads with <v4>, only triangles are supported at the moment!");
|
|
|
- quadWarned = true;
|
|
|
- }
|
|
|
-
|
|
|
- submesh->indexData->faces.push_back(face);
|
|
|
-
|
|
|
- // Advance
|
|
|
- NextNode();
|
|
|
- }
|
|
|
-
|
|
|
- if (submesh->indexData->faces.size() == submesh->indexData->faceCount) {
|
|
|
- ASSIMP_LOG_VERBOSE_DEBUG_F(" - Faces ", submesh->indexData->faceCount);
|
|
|
- } else {
|
|
|
- throw DeadlyImportError(Formatter::format() << "Read only " << submesh->indexData->faces.size() << " faces when should have read " << submesh->indexData->faceCount);
|
|
|
- }
|
|
|
- } else if (m_currentNodeName == nnGeometry) {
|
|
|
- if (submesh->usesSharedVertexData) {
|
|
|
- throw DeadlyImportError("Found <geometry> in <submesh> when use shared geometry is true. Invalid mesh file.");
|
|
|
- }
|
|
|
-
|
|
|
- submesh->vertexData = new VertexDataXml();
|
|
|
- ReadGeometry(submesh->vertexData);
|
|
|
- } else if (m_currentNodeName == nnBoneAssignments) {
|
|
|
- ReadBoneAssignments(submesh->vertexData);
|
|
|
- }
|
|
|
- // Assimp incompatible/ignored nodes
|
|
|
- else {
|
|
|
- SkipCurrentNode();
|
|
|
- }
|
|
|
- }*/
|
|
|
-
|
|
|
submesh->index = static_cast<unsigned int>(mesh->subMeshes.size());
|
|
|
mesh->subMeshes.push_back(submesh);
|
|
|
}
|
|
@@ -603,19 +430,6 @@ void OgreXmlSerializer::ReadBoneAssignments(XmlNode &node, VertexDataXml *dest)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /*NextNode();
|
|
|
- while (m_currentNodeName == nnVertexBoneAssignment) {
|
|
|
- VertexBoneAssignment ba;
|
|
|
- ba.vertexIndex = ReadAttribute<uint32_t>(anVertexIndex);
|
|
|
- ba.boneIndex = ReadAttribute<uint16_t>(anBoneIndex);
|
|
|
- ba.weight = ReadAttribute<float>(anWeight);
|
|
|
-
|
|
|
- dest->boneAssignments.push_back(ba);
|
|
|
- influencedVertices.insert(ba.vertexIndex);
|
|
|
-
|
|
|
- NextNode();
|
|
|
- }*/
|
|
|
-
|
|
|
/** Normalize bone weights.
|
|
|
Some exporters won't care if the sum of all bone weights
|
|
|
for a single vertex equals 1 or not, so validate here. */
|
|
@@ -662,8 +476,8 @@ bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *me
|
|
|
|
|
|
Skeleton *skeleton = new Skeleton();
|
|
|
OgreXmlSerializer serializer(xmlParser.get());
|
|
|
- XmlNode *root = xmlParser->getRootNode();
|
|
|
- serializer.ReadSkeleton(*root, skeleton);
|
|
|
+ XmlNode root = xmlParser->getRootNode();
|
|
|
+ serializer.ReadSkeleton(root, skeleton);
|
|
|
mesh->skeleton = skeleton;
|
|
|
return true;
|
|
|
}
|
|
@@ -680,12 +494,9 @@ bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, Mesh *mesh)
|
|
|
|
|
|
Skeleton *skeleton = new Skeleton();
|
|
|
OgreXmlSerializer serializer(xmlParser.get());
|
|
|
- XmlNode *root = xmlParser->getRootNode();
|
|
|
- if (nullptr == root) {
|
|
|
- return false;
|
|
|
- }
|
|
|
+ XmlNode root = xmlParser->getRootNode();
|
|
|
|
|
|
- serializer.ReadSkeleton(*root, skeleton);
|
|
|
+ serializer.ReadSkeleton(root, skeleton);
|
|
|
mesh->skeleton = skeleton;
|
|
|
|
|
|
return true;
|
|
@@ -736,22 +547,6 @@ void OgreXmlSerializer::ReadSkeleton(XmlNode &node, Skeleton *skeleton) {
|
|
|
ReadAnimations(currentNode, skeleton);
|
|
|
}
|
|
|
}
|
|
|
- /*NextNode();
|
|
|
-
|
|
|
- // Root level nodes
|
|
|
- while (m_currentNodeName == nnBones ||
|
|
|
- m_currentNodeName == nnBoneHierarchy ||
|
|
|
- m_currentNodeName == nnAnimations ||
|
|
|
- m_currentNodeName == nnAnimationLinks) {
|
|
|
- if (m_currentNodeName == nnBones)
|
|
|
- ReadBones(skeleton);
|
|
|
- else if (m_currentNodeName == nnBoneHierarchy)
|
|
|
- ReadBoneHierarchy(skeleton);
|
|
|
- else if (m_currentNodeName == nnAnimations)
|
|
|
- ReadAnimations(skeleton);
|
|
|
- else
|
|
|
- SkipCurrentNode();
|
|
|
- }*/
|
|
|
}
|
|
|
|
|
|
void OgreXmlSerializer::ReadAnimations(XmlNode &node, Skeleton *skeleton) {
|
|
@@ -778,22 +573,6 @@ void OgreXmlSerializer::ReadAnimations(XmlNode &node, Skeleton *skeleton) {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-/* NextNode();
|
|
|
- while (m_currentNodeName == nnAnimation) {
|
|
|
- Animation *anim = new Animation(skeleton);
|
|
|
- anim->name = ReadAttribute<std::string>("name");
|
|
|
- anim->length = ReadAttribute<float>("length");
|
|
|
-
|
|
|
- if (NextNode() != nnTracks) {
|
|
|
- throw DeadlyImportError(Formatter::format() << "No <tracks> found in <animation> " << anim->name);
|
|
|
- }
|
|
|
-
|
|
|
- ReadAnimationTracks(anim);
|
|
|
- skeleton->animations.push_back(anim);
|
|
|
-
|
|
|
- ASSIMP_LOG_VERBOSE_DEBUG_F(" ", anim->name, " (", anim->length, " sec, ", anim->tracks.size(), " tracks)");
|
|
|
- }*/
|
|
|
}
|
|
|
|
|
|
void OgreXmlSerializer::ReadAnimationTracks(XmlNode &node, Animation *dest) {
|
|
@@ -815,20 +594,6 @@ void OgreXmlSerializer::ReadAnimationTracks(XmlNode &node, Animation *dest) {
|
|
|
|
|
|
}
|
|
|
}
|
|
|
- /*NextNode();
|
|
|
- while (m_currentNodeName == nnTrack) {
|
|
|
- VertexAnimationTrack track;
|
|
|
- track.type = VertexAnimationTrack::VAT_TRANSFORM;
|
|
|
- track.boneName = ReadAttribute<std::string>("bone");
|
|
|
-
|
|
|
- if (NextNode() != nnKeyFrames) {
|
|
|
- throw DeadlyImportError(Formatter::format() << "No <keyframes> found in <track> " << dest->name);
|
|
|
- }
|
|
|
-
|
|
|
- ReadAnimationKeyFrames(dest, &track);
|
|
|
-
|
|
|
- dest->tracks.push_back(track);
|
|
|
- }*/
|
|
|
}
|
|
|
|
|
|
void OgreXmlSerializer::ReadAnimationKeyFrames(XmlNode &node, Animation *anim, VertexAnimationTrack *dest) {
|
|
@@ -873,46 +638,6 @@ void OgreXmlSerializer::ReadAnimationKeyFrames(XmlNode &node, Animation *anim, V
|
|
|
}
|
|
|
dest->transformKeyFrames.push_back(keyframe);
|
|
|
}
|
|
|
- /*NextNode();
|
|
|
- while (m_currentNodeName == nnKeyFrame) {
|
|
|
- TransformKeyFrame keyframe;
|
|
|
- keyframe.timePos = ReadAttribute<float>("time");
|
|
|
-
|
|
|
- NextNode();
|
|
|
- while (m_currentNodeName == nnTranslate || m_currentNodeName == nnRotate || m_currentNodeName == nnScale) {
|
|
|
- if (m_currentNodeName == nnTranslate) {
|
|
|
- keyframe.position.x = ReadAttribute<float>(anX);
|
|
|
- keyframe.position.y = ReadAttribute<float>(anY);
|
|
|
- keyframe.position.z = ReadAttribute<float>(anZ);
|
|
|
- } else if (m_currentNodeName == nnRotate) {
|
|
|
- float angle = ReadAttribute<float>("angle");
|
|
|
-
|
|
|
- if (NextNode() != nnAxis) {
|
|
|
- throw DeadlyImportError("No axis specified for keyframe rotation in animation " + anim->name);
|
|
|
- }
|
|
|
-
|
|
|
- aiVector3D axis;
|
|
|
- axis.x = ReadAttribute<float>(anX);
|
|
|
- axis.y = ReadAttribute<float>(anY);
|
|
|
- axis.z = ReadAttribute<float>(anZ);
|
|
|
- if (axis.Equal(zeroVec)) {
|
|
|
- axis.x = 1.0f;
|
|
|
- if (angle != 0) {
|
|
|
- ASSIMP_LOG_WARN_F("Found invalid a key frame with a zero rotation axis in animation: ", anim->name);
|
|
|
- }
|
|
|
- }
|
|
|
- keyframe.rotation = aiQuaternion(axis, angle);
|
|
|
- } else if (m_currentNodeName == nnScale) {
|
|
|
- keyframe.scale.x = ReadAttribute<float>(anX);
|
|
|
- keyframe.scale.y = ReadAttribute<float>(anY);
|
|
|
- keyframe.scale.z = ReadAttribute<float>(anZ);
|
|
|
- }
|
|
|
-
|
|
|
- NextNode();
|
|
|
- }
|
|
|
-
|
|
|
- dest->transformKeyFrames.push_back(keyframe);
|
|
|
- }*/
|
|
|
}
|
|
|
|
|
|
void OgreXmlSerializer::ReadBoneHierarchy(XmlNode &node, Skeleton *skeleton) {
|
|
@@ -937,20 +662,6 @@ void OgreXmlSerializer::ReadBoneHierarchy(XmlNode &node, Skeleton *skeleton) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
- /*while (NextNode() == nnBoneParent) {
|
|
|
- const std::string name = ReadAttribute<std::string>("bone");
|
|
|
- const std::string parentName = ReadAttribute<std::string>("parent");
|
|
|
-
|
|
|
- Bone *bone = skeleton->BoneByName(name);
|
|
|
- Bone *parent = skeleton->BoneByName(parentName);
|
|
|
-
|
|
|
- if (bone && parent)
|
|
|
- parent->AddChild(bone);
|
|
|
- else
|
|
|
- throw DeadlyImportError("Failed to find bones for parenting: Child " + name + " for parent " + parentName);
|
|
|
- }*/
|
|
|
-
|
|
|
// Calculate bone matrices for root bones. Recursively calculates their children.
|
|
|
for (size_t i = 0, len = skeleton->bones.size(); i < len; ++i) {
|
|
|
Bone *bone = skeleton->bones[i];
|
|
@@ -1014,54 +725,6 @@ void OgreXmlSerializer::ReadBones(XmlNode &node, Skeleton *skeleton) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /*NextNode();
|
|
|
- while (m_currentNodeName == nnBone) {
|
|
|
- Bone *bone = new Bone();
|
|
|
- bone->id = ReadAttribute<uint16_t>("id");
|
|
|
- bone->name = ReadAttribute<std::string>("name");
|
|
|
-
|
|
|
- NextNode();
|
|
|
- while (m_currentNodeName == nnPosition ||
|
|
|
- m_currentNodeName == nnRotation ||
|
|
|
- m_currentNodeName == nnScale) {
|
|
|
- if (m_currentNodeName == nnPosition) {
|
|
|
- bone->position.x = ReadAttribute<float>(anX);
|
|
|
- bone->position.y = ReadAttribute<float>(anY);
|
|
|
- bone->position.z = ReadAttribute<float>(anZ);
|
|
|
- } else if (m_currentNodeName == nnRotation) {
|
|
|
- float angle = ReadAttribute<float>("angle");
|
|
|
-
|
|
|
- if (NextNode() != nnAxis) {
|
|
|
- throw DeadlyImportError(Formatter::format() << "No axis specified for bone rotation in bone " << bone->id);
|
|
|
- }
|
|
|
-
|
|
|
- aiVector3D axis;
|
|
|
- axis.x = ReadAttribute<float>(anX);
|
|
|
- axis.y = ReadAttribute<float>(anY);
|
|
|
- axis.z = ReadAttribute<float>(anZ);
|
|
|
-
|
|
|
- bone->rotation = aiQuaternion(axis, angle);
|
|
|
- } else if (m_currentNodeName == nnScale) {
|
|
|
- /// @todo Implement taking scale into account in matrix/pose calculations!
|
|
|
- if (HasAttribute("factor")) {
|
|
|
- float factor = ReadAttribute<float>("factor");
|
|
|
- bone->scale.Set(factor, factor, factor);
|
|
|
- } else {
|
|
|
- if (HasAttribute(anX))
|
|
|
- bone->scale.x = ReadAttribute<float>(anX);
|
|
|
- if (HasAttribute(anY))
|
|
|
- bone->scale.y = ReadAttribute<float>(anY);
|
|
|
- if (HasAttribute(anZ))
|
|
|
- bone->scale.z = ReadAttribute<float>(anZ);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- NextNode();
|
|
|
- }
|
|
|
-
|
|
|
- skeleton->bones.push_back(bone);
|
|
|
- }*/
|
|
|
-
|
|
|
// Order bones by Id
|
|
|
std::sort(skeleton->bones.begin(), skeleton->bones.end(), BoneCompare);
|
|
|
|