forked from 0ad/0ad
bb
157c6af18e
Avoid cases of filenames Update years in terms and other legal(ish) documents Don't update years in license headers, since change is not meaningful Will add linter rule in seperate commit Happy recompiling everyone! Original Patch By: Nescio Comment By: Gallaecio Differential Revision: D2620 This was SVN commit r27786.
722 lines
24 KiB
C++
722 lines
24 KiB
C++
/* Copyright (C) 2012 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "PMDConvert.h"
|
|
#include "CommonConvert.h"
|
|
|
|
#include "FCollada.h"
|
|
#include "FCDocument/FCDAsset.h"
|
|
#include "FCDocument/FCDocument.h"
|
|
#include "FCDocument/FCDocumentTools.h"
|
|
#include "FCDocument/FCDController.h"
|
|
#include "FCDocument/FCDControllerInstance.h"
|
|
#include "FCDocument/FCDGeometry.h"
|
|
#include "FCDocument/FCDGeometryMesh.h"
|
|
#include "FCDocument/FCDGeometryPolygons.h"
|
|
#include "FCDocument/FCDGeometryPolygonsInput.h"
|
|
#include "FCDocument/FCDGeometryPolygonsTools.h"
|
|
#include "FCDocument/FCDGeometrySource.h"
|
|
#include "FCDocument/FCDSceneNode.h"
|
|
#include "FCDocument/FCDSkinController.h"
|
|
|
|
#include "StdSkeletons.h"
|
|
#include "Decompose.h"
|
|
#include "Maths.h"
|
|
#include "GeomReindex.h"
|
|
|
|
#include <cassert>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
const size_t maxInfluences = 4;
|
|
struct VertexBlend
|
|
{
|
|
uint8 bones[maxInfluences];
|
|
float weights[maxInfluences];
|
|
};
|
|
VertexBlend defaultInfluences = { { 0xFF, 0xFF, 0xFF, 0xFF }, { 0, 0, 0, 0 } };
|
|
|
|
struct PropPoint
|
|
{
|
|
std::string name;
|
|
float translation[3];
|
|
float orientation[4];
|
|
uint8 bone;
|
|
};
|
|
|
|
// Based on FMVector3::Normalize, but that function uses a static member
|
|
// FMVector3::XAxis which causes irritating linker errors. Rather than trying
|
|
// to make that XAxis work in a cross-platform way, just reimplement Normalize:
|
|
static FMVector3 FMVector3_Normalize(const FMVector3& vec)
|
|
{
|
|
float l = vec.Length();
|
|
if (l > 0.0f)
|
|
return FMVector3(vec.x/l, vec.y/l, vec.z/l);
|
|
else
|
|
return FMVector3(1.0f, 0.0f, 0.0f);
|
|
}
|
|
|
|
static void AddStaticPropPoints(std::vector<PropPoint> &propPoints, const FMMatrix44& upAxisTransform, FCDSceneNode* node)
|
|
{
|
|
if (node->GetName().find("prop-") == 0 || node->GetName().find("prop_") == 0)
|
|
{
|
|
// Strip off the "prop-" from the name
|
|
std::string propPointName (node->GetName().substr(5));
|
|
|
|
Log(LOG_INFO, "Adding prop point %s", propPointName.c_str());
|
|
|
|
// CalculateWorldTransform applies transformations recursively for all parents of this node
|
|
// upAxisTransform transforms this node to right-handed Z_UP coordinates
|
|
|
|
FMMatrix44 transform = upAxisTransform * node->CalculateWorldTransform();
|
|
|
|
HMatrix matrix;
|
|
memcpy(matrix, transform.Transposed().m, sizeof(matrix));
|
|
|
|
AffineParts parts;
|
|
decomp_affine(matrix, &parts);
|
|
|
|
// Add prop point in game coordinates
|
|
|
|
PropPoint p = {
|
|
propPointName,
|
|
|
|
// Flip translation across the x-axis by swapping y and z
|
|
{ parts.t.x, parts.t.z, parts.t.y },
|
|
|
|
// To convert the quaternions: imagine you're using the axis/angle
|
|
// representation, then swap the y,z basis vectors and change the
|
|
// direction of rotation by negating the angle ( => negating sin(angle)
|
|
// => negating x,y,z => changing (x,y,z,w) to (-x,-z,-y,w)
|
|
// but then (-x,-z,-y,w) == (x,z,y,-w) so do that instead)
|
|
{ parts.q.x, parts.q.z, parts.q.y, -parts.q.w },
|
|
|
|
0xff
|
|
};
|
|
propPoints.push_back(p);
|
|
}
|
|
|
|
// Search children for prop points
|
|
for (size_t i = 0; i < node->GetChildrenCount(); ++i)
|
|
AddStaticPropPoints(propPoints, upAxisTransform, node->GetChild(i));
|
|
}
|
|
|
|
class PMDConvert
|
|
{
|
|
public:
|
|
/**
|
|
* Converts a COLLADA XML document into the PMD mesh format.
|
|
*
|
|
* @param input XML document to parse
|
|
* @param output callback for writing the PMD data; called lots of times
|
|
* with small strings
|
|
* @param xmlErrors output - errors reported by the XML parser
|
|
* @throws ColladaException on failure
|
|
*/
|
|
static void ColladaToPMD(const char* input, OutputCB& output, std::string& xmlErrors)
|
|
{
|
|
CommonConvert converter(input, xmlErrors);
|
|
|
|
if (converter.GetInstance().GetEntity()->GetType() == FCDEntity::GEOMETRY)
|
|
{
|
|
Log(LOG_INFO, "Found static geometry");
|
|
|
|
FCDGeometryPolygons* polys = GetPolysFromGeometry((FCDGeometry*)converter.GetInstance().GetEntity());
|
|
|
|
// Convert the geometry into a suitable form for the game
|
|
ReindexGeometry(polys);
|
|
|
|
std::vector<VertexBlend> boneWeights; // unused
|
|
std::vector<BoneTransform> boneTransforms; // unused
|
|
std::vector<PropPoint> propPoints;
|
|
|
|
// Get the raw vertex data
|
|
|
|
FCDGeometryPolygonsInput* inputPosition = polys->FindInput(FUDaeGeometryInput::POSITION);
|
|
FCDGeometryPolygonsInput* inputNormal = polys->FindInput(FUDaeGeometryInput::NORMAL);
|
|
|
|
const uint32* indicesCombined = inputPosition->GetIndices();
|
|
size_t indicesCombinedCount = inputPosition->GetIndexCount();
|
|
// (ReindexGeometry guarantees position/normal/texcoord have the same indexes)
|
|
|
|
FCDGeometrySource* sourcePosition = inputPosition->GetSource();
|
|
FCDGeometrySource* sourceNormal = inputNormal ->GetSource();
|
|
|
|
FCDGeometrySourceList texcoordSources;
|
|
polys->GetParent()->FindSourcesByType(FUDaeGeometryInput::TEXCOORD, texcoordSources);
|
|
|
|
float* dataPosition = sourcePosition->GetData();
|
|
float* dataNormal = sourceNormal ->GetData();
|
|
size_t vertexCount = sourcePosition->GetDataCount() / 3;
|
|
assert(sourcePosition->GetDataCount() == vertexCount*3);
|
|
assert(sourceNormal ->GetDataCount() == vertexCount*3);
|
|
|
|
std::vector<float*> dataTexcoords;
|
|
for (size_t i = 0; i < texcoordSources.size(); ++i)
|
|
{
|
|
dataTexcoords.push_back(texcoordSources[i]->GetData());
|
|
}
|
|
|
|
// Transform mesh coordinate system to game coordinates
|
|
// (doesn't modify prop points)
|
|
|
|
TransformStaticModel(dataPosition, dataNormal, vertexCount, converter.GetEntityTransform(), converter.IsYUp());
|
|
|
|
// Add static prop points
|
|
// which are empty child nodes of the main parent
|
|
|
|
// Default prop points are already given in game coordinates
|
|
AddDefaultPropPoints(propPoints);
|
|
|
|
// Calculate transform to convert from COLLADA-defined up_axis to Z-up because
|
|
// it's relatively straightforward to convert that to game coordinates
|
|
FMMatrix44 upAxisTransform = FMMatrix44_Identity;
|
|
if (converter.IsYUp())
|
|
{
|
|
// Prop points are rotated -90 degrees about the X-axis, reverse that rotation
|
|
// (do this once now because it's easier than messing with quaternions later)
|
|
upAxisTransform = FMMatrix44::XAxisRotationMatrix(1.57f);
|
|
}
|
|
|
|
AddStaticPropPoints(propPoints, upAxisTransform, converter.GetInstance().GetParent());
|
|
|
|
WritePMD(output, indicesCombined, indicesCombinedCount, dataPosition, dataNormal, dataTexcoords, vertexCount, boneWeights, boneTransforms, propPoints);
|
|
}
|
|
else if (converter.GetInstance().GetType() == FCDEntityInstance::CONTROLLER)
|
|
{
|
|
Log(LOG_INFO, "Found skinned geometry");
|
|
|
|
FCDControllerInstance& controllerInstance = static_cast<FCDControllerInstance&>(converter.GetInstance());
|
|
|
|
// (NB: GetType is deprecated and should be replaced with HasType,
|
|
// except that has irritating linker errors when using a DLL, so don't
|
|
// bother)
|
|
|
|
assert(converter.GetInstance().GetEntity()->GetType() == FCDEntity::CONTROLLER); // assume this is always true?
|
|
FCDController* controller = static_cast<FCDController*>(converter.GetInstance().GetEntity());
|
|
|
|
FCDSkinController* skin = controller->GetSkinController();
|
|
REQUIRE(skin != NULL, "is skin controller");
|
|
|
|
FixSkeletonRoots(controllerInstance);
|
|
|
|
// Data for joints is stored in two places - avoid overflows by limiting
|
|
// to the minimum of the two sizes, and warn if they're different (which
|
|
// happens in practice for slightly-broken meshes)
|
|
size_t jointCount = std::min(skin->GetJointCount(), controllerInstance.GetJointCount());
|
|
if (skin->GetJointCount() != controllerInstance.GetJointCount())
|
|
{
|
|
Log(LOG_WARNING, "Mismatched bone counts (skin has %d, skeleton has %d)",
|
|
skin->GetJointCount(), controllerInstance.GetJointCount());
|
|
for (size_t i = 0; i < skin->GetJointCount(); ++i)
|
|
Log(LOG_INFO, "Skin joint %d: %s", i, skin->GetJoint(i)->GetId().c_str());
|
|
for (size_t i = 0; i < controllerInstance.GetJointCount(); ++i)
|
|
Log(LOG_INFO, "Skeleton joint %d: %s", i, controllerInstance.GetJoint(i)->GetName().c_str());
|
|
}
|
|
|
|
// Get the skinned mesh for this entity
|
|
FCDGeometry* baseGeometry = controller->GetBaseGeometry();
|
|
REQUIRE(baseGeometry != NULL, "controller has base geometry");
|
|
FCDGeometryPolygons* polys = GetPolysFromGeometry(baseGeometry);
|
|
|
|
// Make sure it doesn't use more bones per vertex than the game can handle
|
|
SkinReduceInfluences(skin, maxInfluences, 0.001f);
|
|
|
|
// Convert the geometry into a suitable form for the game
|
|
ReindexGeometry(polys, skin);
|
|
|
|
const Skeleton& skeleton = FindSkeleton(controllerInstance);
|
|
|
|
// Convert the bone influences into VertexBlend structures for the PMD:
|
|
|
|
bool hasComplainedAboutNonexistentJoints = false; // because we want to emit a warning only once
|
|
|
|
std::vector<VertexBlend> boneWeights; // one per vertex
|
|
|
|
const FCDSkinControllerVertex* vertexInfluences = skin->GetVertexInfluences();
|
|
for (size_t i = 0; i < skin->GetInfluenceCount(); ++i)
|
|
{
|
|
VertexBlend influences = defaultInfluences;
|
|
|
|
assert(vertexInfluences[i].GetPairCount() <= maxInfluences);
|
|
// guaranteed by ReduceInfluences; necessary for avoiding
|
|
// out-of-bounds writes to the VertexBlend
|
|
|
|
if (vertexInfluences[i].GetPairCount() == 0)
|
|
{
|
|
// Blender exports some models with vertices that have no influences,
|
|
// which I've not found details about in the COLLADA spec, however,
|
|
// it seems to work OK to treat these vertices the same as if they
|
|
// were only influenced by the bind-shape matrix (see comment below),
|
|
// so we use the same special case here.
|
|
influences.bones[0] = (uint8)jointCount;
|
|
influences.weights[0] = 1.0f;
|
|
}
|
|
|
|
for (size_t j = 0; j < vertexInfluences[i].GetPairCount(); ++j)
|
|
{
|
|
if (vertexInfluences[i].GetPair(j)->jointIndex == -1)
|
|
{
|
|
// This is a special case we must handle, according to the COLLADA spec:
|
|
// "An index of -1 into the array of joints refers to the bind shape"
|
|
//
|
|
// which basically means when skinning the vertex it's relative to the
|
|
// bind-shape transform instead of an animated bone. Since our skinning
|
|
// is in world space, we will have already applied the bind-shape transform,
|
|
// so we don't have to worry about that, though we DO have to apply the
|
|
// world space transform of the model for the indicated vertex.
|
|
//
|
|
// To indicate this special case, we use a bone ID set to the total number
|
|
// of bones in the model, which will have a special "bone matrix" reserved
|
|
// that contains the world space transform of the model during skinning.
|
|
// (see http://trac.wildfiregames.com/ticket/1012)
|
|
influences.bones[j] = (uint8)jointCount;
|
|
influences.weights[j] = vertexInfluences[i].GetPair(j)->weight;
|
|
}
|
|
else
|
|
{
|
|
// Check for less than 254 joints because we store them in a u8,
|
|
// 0xFF is a reserved value (no influence), and we reserve one slot
|
|
// for the above special case.
|
|
uint32 jointIdx = vertexInfluences[i].GetPair(j)->jointIndex;
|
|
REQUIRE(jointIdx < 0xFE, "sensible number of joints (<254)");
|
|
|
|
// Find the joint on the skeleton, after checking it really exists
|
|
FCDSceneNode* joint = NULL;
|
|
if (jointIdx < controllerInstance.GetJointCount())
|
|
joint = controllerInstance.GetJoint(jointIdx);
|
|
|
|
// Complain on error
|
|
if (! joint)
|
|
{
|
|
if (! hasComplainedAboutNonexistentJoints)
|
|
{
|
|
Log(LOG_WARNING, "Vertexes influenced by nonexistent joint");
|
|
hasComplainedAboutNonexistentJoints = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Store into the VertexBlend
|
|
int boneId = skeleton.GetBoneID(joint->GetName().c_str());
|
|
if (boneId < 0)
|
|
{
|
|
// The relevant joint does exist, but it's not a recognised
|
|
// bone in our chosen skeleton structure
|
|
Log(LOG_ERROR, "Vertex influenced by unrecognised bone '%s'", joint->GetName().c_str());
|
|
continue;
|
|
}
|
|
|
|
influences.bones[j] = (uint8)boneId;
|
|
influences.weights[j] = vertexInfluences[i].GetPair(j)->weight;
|
|
}
|
|
}
|
|
|
|
boneWeights.push_back(influences);
|
|
}
|
|
|
|
// Convert the bind pose into BoneTransform structures for the PMD:
|
|
|
|
BoneTransform boneDefault = { { 0, 0, 0 }, { 0, 0, 0, 1 } }; // identity transform
|
|
std::vector<BoneTransform> boneTransforms (skeleton.GetBoneCount(), boneDefault);
|
|
|
|
for (size_t i = 0; i < jointCount; ++i)
|
|
{
|
|
FCDSceneNode* joint = controllerInstance.GetJoint(i);
|
|
|
|
int boneId = skeleton.GetRealBoneID(joint->GetName().c_str());
|
|
if (boneId < 0)
|
|
{
|
|
// unrecognised joint - it's probably just a prop point
|
|
// or something, so ignore it
|
|
continue;
|
|
}
|
|
|
|
FMMatrix44 bindPose = skin->GetJoint(i)->GetBindPoseInverse().Inverted();
|
|
|
|
HMatrix matrix;
|
|
memcpy(matrix, bindPose.Transposed().m, sizeof(matrix));
|
|
// set matrix = bindPose^T, to match what decomp_affine wants
|
|
|
|
AffineParts parts;
|
|
decomp_affine(matrix, &parts);
|
|
|
|
BoneTransform b = {
|
|
{ parts.t.x, parts.t.y, parts.t.z },
|
|
{ parts.q.x, parts.q.y, parts.q.z, parts.q.w }
|
|
};
|
|
|
|
boneTransforms[boneId] = b;
|
|
}
|
|
|
|
// Construct the list of prop points.
|
|
// Currently takes all objects that are directly attached to a
|
|
// standard bone, and whose name begins with "prop-" or "prop_".
|
|
|
|
std::vector<PropPoint> propPoints;
|
|
AddDefaultPropPoints(propPoints);
|
|
|
|
for (size_t i = 0; i < jointCount; ++i)
|
|
{
|
|
FCDSceneNode* joint = controllerInstance.GetJoint(i);
|
|
|
|
int boneId = skeleton.GetBoneID(joint->GetName().c_str());
|
|
if (boneId < 0)
|
|
{
|
|
// unrecognised joint name - ignore, same as before
|
|
continue;
|
|
}
|
|
|
|
// Check all the objects attached to this bone
|
|
for (size_t j = 0; j < joint->GetChildrenCount(); ++j)
|
|
{
|
|
FCDSceneNode* child = joint->GetChild(j);
|
|
if (child->GetName().find("prop-") != 0 && child->GetName().find("prop_") != 0)
|
|
{
|
|
// doesn't begin with "prop-", so skip it
|
|
continue;
|
|
}
|
|
// Strip off the "prop-" from the name
|
|
std::string propPointName (child->GetName().substr(5));
|
|
|
|
Log(LOG_INFO, "Adding prop point %s", propPointName.c_str());
|
|
|
|
// Get translation and orientation of local transform
|
|
|
|
FMMatrix44 localTransform = child->ToMatrix();
|
|
|
|
HMatrix matrix;
|
|
memcpy(matrix, localTransform.Transposed().m, sizeof(matrix));
|
|
|
|
AffineParts parts;
|
|
decomp_affine(matrix, &parts);
|
|
|
|
// Add prop point to list
|
|
|
|
PropPoint p = {
|
|
propPointName,
|
|
{ parts.t.x, parts.t.y, parts.t.z },
|
|
{ parts.q.x, parts.q.y, parts.q.z, parts.q.w },
|
|
(uint8)boneId
|
|
};
|
|
propPoints.push_back(p);
|
|
}
|
|
}
|
|
|
|
// Get the raw vertex data
|
|
|
|
FCDGeometryPolygonsInput* inputPosition = polys->FindInput(FUDaeGeometryInput::POSITION);
|
|
FCDGeometryPolygonsInput* inputNormal = polys->FindInput(FUDaeGeometryInput::NORMAL);
|
|
|
|
const uint32* indicesCombined = inputPosition->GetIndices();
|
|
size_t indicesCombinedCount = inputPosition->GetIndexCount();
|
|
// (ReindexGeometry guarantees position/normal/texcoord have the same indexes)
|
|
|
|
FCDGeometrySource* sourcePosition = inputPosition->GetSource();
|
|
FCDGeometrySource* sourceNormal = inputNormal ->GetSource();
|
|
|
|
FCDGeometrySourceList texcoordSources;
|
|
polys->GetParent()->FindSourcesByType(FUDaeGeometryInput::TEXCOORD, texcoordSources);
|
|
|
|
float* dataPosition = sourcePosition->GetData();
|
|
float* dataNormal = sourceNormal ->GetData();
|
|
size_t vertexCount = sourcePosition->GetDataCount() / 3;
|
|
assert(sourcePosition->GetDataCount() == vertexCount*3);
|
|
assert(sourceNormal ->GetDataCount() == vertexCount*3);
|
|
|
|
std::vector<float*> dataTexcoords;
|
|
for (size_t i = 0; i < texcoordSources.size(); ++i)
|
|
{
|
|
dataTexcoords.push_back(texcoordSources[i]->GetData());
|
|
}
|
|
|
|
// Transform model coordinate system to game coordinates
|
|
|
|
TransformSkinnedModel(dataPosition, dataNormal, vertexCount, boneTransforms, propPoints,
|
|
converter.GetEntityTransform(), skin->GetBindShapeTransform(),
|
|
converter.IsYUp(), converter.IsXSI());
|
|
|
|
WritePMD(output, indicesCombined, indicesCombinedCount, dataPosition, dataNormal, dataTexcoords, vertexCount, boneWeights, boneTransforms, propPoints);
|
|
}
|
|
else
|
|
{
|
|
throw ColladaException("Unrecognised object type");
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Adds the default "root" prop-point.
|
|
*/
|
|
static void AddDefaultPropPoints(std::vector<PropPoint>& propPoints)
|
|
{
|
|
PropPoint root;
|
|
root.name = "root";
|
|
root.translation[0] = root.translation[1] = root.translation[2] = 0.0f;
|
|
root.orientation[0] = root.orientation[1] = root.orientation[2] = 0.0f;
|
|
root.orientation[3] = 1.0f;
|
|
root.bone = 0xFF;
|
|
propPoints.push_back(root);
|
|
}
|
|
|
|
/**
|
|
* Writes the model data in the PMD format.
|
|
*/
|
|
static void WritePMD(OutputCB& output,
|
|
const uint32* indices, size_t indexCount,
|
|
const float* position, const float* normal,
|
|
const std::vector<float*>& texcoords,
|
|
size_t vertexCount,
|
|
const std::vector<VertexBlend>& boneWeights, const std::vector<BoneTransform>& boneTransforms,
|
|
const std::vector<PropPoint>& propPoints)
|
|
{
|
|
static const VertexBlend noBlend = { { 0xFF, 0xFF, 0xFF, 0xFF }, { 0, 0, 0, 0 } };
|
|
|
|
size_t faceCount = indexCount/3;
|
|
size_t boneCount = boneTransforms.size();
|
|
if (boneCount)
|
|
assert(boneWeights.size() == vertexCount);
|
|
|
|
size_t propPointsSize = 0; // can't calculate this statically, so loop over all the prop points
|
|
for (size_t i = 0; i < propPoints.size(); ++i)
|
|
{
|
|
propPointsSize += 4 + propPoints[i].name.length();
|
|
propPointsSize += 3*4 + 4*4 + 1;
|
|
}
|
|
|
|
output("PSMD", 4); // magic number
|
|
write(output, (uint32)4); // version number
|
|
write(output, (uint32)(
|
|
// for UVs, we add one uint32 (i.e. 4 bytes) per model that gives the number of
|
|
// texcoord sets in the model, plus 2 floats per new UV
|
|
// pair per vertex (i.e. 8 bytes * number of pairs * vertex count)
|
|
4 + 11*4*vertexCount + 4 + 8*texcoords.size()*vertexCount + // vertices
|
|
4 + 6*faceCount + // faces
|
|
4 + 7*4*boneCount + // bones
|
|
4 + propPointsSize // props
|
|
)); // data size
|
|
|
|
// Vertex data
|
|
write<uint32>(output, (uint32)vertexCount);
|
|
write<uint32>(output, (uint32)texcoords.size()); // UV pairs per vertex
|
|
for (size_t i = 0; i < vertexCount; ++i)
|
|
{
|
|
output((char*)&position[i*3], 12);
|
|
output((char*)&normal [i*3], 12);
|
|
|
|
for (size_t s = 0; s < texcoords.size(); ++s)
|
|
{
|
|
output((char*)&texcoords[s][i*2], 8);
|
|
}
|
|
|
|
if (boneCount)
|
|
write(output, boneWeights[i]);
|
|
else
|
|
write(output, noBlend);
|
|
}
|
|
|
|
// Face data
|
|
write(output, (uint32)faceCount);
|
|
for (size_t i = 0; i < indexCount; ++i)
|
|
{
|
|
write(output, (uint16)indices[i]);
|
|
}
|
|
|
|
// Bones data
|
|
write(output, (uint32)boneCount);
|
|
for (size_t i = 0; i < boneCount; ++i)
|
|
{
|
|
output((char*)&boneTransforms[i], 7*4);
|
|
}
|
|
|
|
// Prop points data
|
|
write(output, (uint32)propPoints.size());
|
|
for (size_t i = 0; i < propPoints.size(); ++i)
|
|
{
|
|
uint32 nameLen = (uint32)propPoints[i].name.length();
|
|
write(output, nameLen);
|
|
output(propPoints[i].name.c_str(), nameLen);
|
|
write(output, propPoints[i].translation);
|
|
write(output, propPoints[i].orientation);
|
|
write(output, propPoints[i].bone);
|
|
}
|
|
}
|
|
|
|
static FCDGeometryPolygons* GetPolysFromGeometry(FCDGeometry* geom)
|
|
{
|
|
REQUIRE(geom->IsMesh(), "geometry is mesh");
|
|
FCDGeometryMesh* mesh = geom->GetMesh();
|
|
|
|
if (! mesh->IsTriangles())
|
|
FCDGeometryPolygonsTools::Triangulate(mesh);
|
|
|
|
REQUIRE(mesh->IsTriangles(), "mesh is made of triangles");
|
|
REQUIRE(mesh->GetPolygonsCount() == 1, "mesh has single set of polygons");
|
|
FCDGeometryPolygons* polys = mesh->GetPolygons(0);
|
|
REQUIRE(polys->FindInput(FUDaeGeometryInput::POSITION) != NULL, "mesh has vertex positions");
|
|
REQUIRE(polys->FindInput(FUDaeGeometryInput::NORMAL) != NULL, "mesh has vertex normals");
|
|
REQUIRE(polys->FindInput(FUDaeGeometryInput::TEXCOORD) != NULL, "mesh has vertex tex coords");
|
|
return polys;
|
|
}
|
|
|
|
/**
|
|
* Applies world-space transform to vertex data and transforms Collada's right-handed
|
|
* Y-up / Z-up coordinates to the game's left-handed Y-up coordinate system
|
|
*
|
|
* TODO: Maybe we should use FCDocumentTools::StandardizeUpAxisAndLength in addition
|
|
* to this, so we'd only have one up-axis case to worry about, but it doesn't seem to
|
|
* correctly adjust the prop points in Y_UP models.
|
|
*/
|
|
static void TransformStaticModel(float* position, float* normal, size_t vertexCount,
|
|
const FMMatrix44& transform, bool yUp)
|
|
{
|
|
for (size_t i = 0; i < vertexCount; ++i)
|
|
{
|
|
FMVector3 pos (&position[i*3], 0);
|
|
FMVector3 norm (&normal[i*3], 0);
|
|
|
|
// Apply the scene-node transforms
|
|
pos = transform.TransformCoordinate(pos);
|
|
norm = FMVector3_Normalize(transform.TransformVector(norm));
|
|
|
|
// Convert from right-handed Y_UP or Z_UP to the game's coordinate system (left-handed Y-up)
|
|
|
|
if (yUp)
|
|
{
|
|
pos.z = -pos.z;
|
|
norm.z = -norm.z;
|
|
}
|
|
else
|
|
{
|
|
std::swap(pos.y, pos.z);
|
|
std::swap(norm.y, norm.z);
|
|
}
|
|
|
|
// Copy back to array
|
|
|
|
position[i*3] = pos.x;
|
|
position[i*3+1] = pos.y;
|
|
position[i*3+2] = pos.z;
|
|
|
|
normal[i*3] = norm.x;
|
|
normal[i*3+1] = norm.y;
|
|
normal[i*3+2] = norm.z;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies world-space transform to vertex data and transforms Collada's right-handed
|
|
* Y-up / Z-up coordinates to the game's left-handed Y-up coordinate system
|
|
*
|
|
* TODO: Maybe we should use FCDocumentTools::StandardizeUpAxisAndLength in addition
|
|
* to this, so we'd only have one up-axis case to worry about, but it doesn't seem to
|
|
* correctly adjust the prop points in Y_UP models.
|
|
*/
|
|
static void TransformSkinnedModel(float* position, float* normal, size_t vertexCount,
|
|
std::vector<BoneTransform>& bones, std::vector<PropPoint>& propPoints,
|
|
const FMMatrix44& transform, const FMMatrix44& bindTransform, bool yUp, bool isXSI)
|
|
{
|
|
FMMatrix44 scaledTransform; // for vertexes
|
|
FMMatrix44 scaleMatrix; // for bones
|
|
|
|
// HACK: see comment in PSAConvert::TransformVertices
|
|
if (isXSI)
|
|
{
|
|
scaleMatrix = DecomposeToScaleMatrix(transform);
|
|
scaledTransform = DecomposeToScaleMatrix(bindTransform) * transform;
|
|
}
|
|
else
|
|
{
|
|
scaleMatrix = FMMatrix44_Identity;
|
|
scaledTransform = bindTransform;
|
|
}
|
|
|
|
// Update the vertex positions and normals
|
|
for (size_t i = 0; i < vertexCount; ++i)
|
|
{
|
|
FMVector3 pos (&position[i*3], 0);
|
|
FMVector3 norm (&normal[i*3], 0);
|
|
|
|
// Apply the scene-node transforms
|
|
pos = scaledTransform.TransformCoordinate(pos);
|
|
norm = FMVector3_Normalize(scaledTransform.TransformVector(norm));
|
|
|
|
// Convert from right-handed Y_UP or Z_UP to the game's coordinate system (left-handed Y-up)
|
|
|
|
if (yUp)
|
|
{
|
|
pos.z = -pos.z;
|
|
norm.z = -norm.z;
|
|
}
|
|
else
|
|
{
|
|
std::swap(pos.y, pos.z);
|
|
std::swap(norm.y, norm.z);
|
|
}
|
|
|
|
// and copy back into the original array
|
|
|
|
position[i*3] = pos.x;
|
|
position[i*3+1] = pos.y;
|
|
position[i*3+2] = pos.z;
|
|
|
|
normal[i*3] = norm.x;
|
|
normal[i*3+1] = norm.y;
|
|
normal[i*3+2] = norm.z;
|
|
}
|
|
|
|
TransformBones(bones, scaleMatrix, yUp);
|
|
|
|
// And do the same for prop points
|
|
for (size_t i = 0; i < propPoints.size(); ++i)
|
|
{
|
|
if (yUp)
|
|
{
|
|
propPoints[i].translation[0] = -propPoints[i].translation[0];
|
|
propPoints[i].orientation[0] = -propPoints[i].orientation[0];
|
|
propPoints[i].orientation[3] = -propPoints[i].orientation[3];
|
|
}
|
|
else
|
|
{
|
|
// Flip translation across the x-axis by swapping y and z
|
|
std::swap(propPoints[i].translation[1], propPoints[i].translation[2]);
|
|
|
|
// To convert the quaternions: imagine you're using the axis/angle
|
|
// representation, then swap the y,z basis vectors and change the
|
|
// direction of rotation by negating the angle ( => negating sin(angle)
|
|
// => negating x,y,z => changing (x,y,z,w) to (-x,-z,-y,w)
|
|
// but then (-x,-z,-y,w) == (x,z,y,-w) so do that instead)
|
|
std::swap(propPoints[i].orientation[1], propPoints[i].orientation[2]);
|
|
propPoints[i].orientation[3] = -propPoints[i].orientation[3];
|
|
}
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
|
|
// The above stuff is just in a class since I don't like having to bother
|
|
// with forward declarations of functions - but provide the plain function
|
|
// interface here:
|
|
|
|
void ColladaToPMD(const char* input, OutputCB& output, std::string& xmlErrors)
|
|
{
|
|
PMDConvert::ColladaToPMD(input, output, xmlErrors);
|
|
}
|