Allow arbitrary number of skinning weights.

Controlled through command line parameter. Defaults to 4 skinning weights.
This commit is contained in:
Henrik Halen 2019-06-26 16:32:42 -07:00 committed by K. S. Ernest (iFire) Lee
parent 37f992321e
commit c2d16f080d
11 changed files with 298 additions and 127 deletions

View File

@ -152,11 +152,24 @@ int main(int argc, char* argv[]) {
gltfOptions.useBlendShapeTangents, gltfOptions.useBlendShapeTangents,
"Include blend shape tangents, if reported present by the FBX SDK."); "Include blend shape tangents, if reported present by the FBX SDK.");
app.add_option(
"--normalize-weights",
gltfOptions.normalizeSkinningWeights,
"Normalize skinning weights.",
true);
app.add_option(
"--skinning-weights",
gltfOptions.maxSkinningWeights,
"The number of joint influences per vertex.",
true)
->check(CLI::Range(0, 512));
app.add_option( app.add_option(
"-k,--keep-attribute", "-k,--keep-attribute",
[&](std::vector<std::string> attributes) -> bool { [&](std::vector<std::string> attributes) -> bool {
gltfOptions.keepAttribs = gltfOptions.keepAttribs =
RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
for (std::string attribute : attributes) { for (std::string attribute : attributes) {
if (attribute == "position") { if (attribute == "position") {
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION; gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION;
@ -216,7 +229,7 @@ int main(int argc, char* argv[]) {
app.add_option( app.add_option(
"--draco-bits-for-normals", "--draco-bits-for-normals",
gltfOptions.draco.quantBitsNormal, gltfOptions.draco.quantBitsNormal,
"How many bits to quantize nornals to.", "How many bits to quantize normals to.",
true) true)
->check(CLI::Range(1, 32)) ->check(CLI::Range(1, 32))
->group("Draco"); ->group("Draco");
@ -334,7 +347,7 @@ int main(int argc, char* argv[]) {
if (!texturesTransforms.empty()) { if (!texturesTransforms.empty()) {
raw.TransformTextures(texturesTransforms); raw.TransformTextures(texturesTransforms);
} }
raw.Condense(); raw.Condense(gltfOptions.maxSkinningWeights, gltfOptions.normalizeSkinningWeights);
raw.TransformGeometry(gltfOptions.computeNormals); raw.TransformGeometry(gltfOptions.computeNormals);
std::ofstream outStream; // note: auto-flushes in destructor std::ofstream outStream; // note: auto-flushes in destructor

View File

@ -112,17 +112,21 @@ struct GltfOptions {
/** Whether to include lights through the KHR_punctual_lights extension. */ /** Whether to include lights through the KHR_punctual_lights extension. */
bool useKHRLightsPunctual{true}; bool useKHRLightsPunctual{true};
/** Whether to include blend shape normals, if present according to the SDK. */ /** Whether to include blend shape normals, if present according to the SDK. */
bool useBlendShapeNormals{false}; bool useBlendShapeNormals { false };
/** Whether to include blend shape tangents, if present according to the SDK. */ /** Whether to include blend shape tangents, if present according to the SDK. */
bool useBlendShapeTangents{false}; bool useBlendShapeTangents { false };
/** When to compute vertex normals from geometry. */ /** Whether to normalized skinning weights. */
ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN; bool normalizeSkinningWeights { true };
/** When to use 32-bit indices. */ /** Maximum number of bone influences per vertex. */
UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO; int maxSkinningWeights { 4 };
/** Select baked animation framerate. */ /** When to compute vertex normals from geometry. */
AnimationFramerateOptions animationFramerate = AnimationFramerateOptions::BAKE24; ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN;
/** When to use 32-bit indices. */
/** Temporary directory used by FBX SDK. */ UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO;
std::string fbxTempDir; /** Select baked animation framerate. */
AnimationFramerateOptions animationFramerate = AnimationFramerateOptions::BAKE24;
/** Temporary directory used by FBX SDK. */
std::string fbxTempDir;
}; };

View File

@ -88,6 +88,30 @@ static RawMaterialType GetMaterialType(
return skinned ? RAW_MATERIAL_TYPE_SKINNED_OPAQUE : RAW_MATERIAL_TYPE_OPAQUE; return skinned ? RAW_MATERIAL_TYPE_SKINNED_OPAQUE : RAW_MATERIAL_TYPE_OPAQUE;
} }
static void calcMinMax(
RawSurface& rawSurface,
const FbxSkinningAccess& skinning,
const FbxVector4& globalPosition,
const std::vector<RawVertexSkinningInfo>& indicesAndWeights) {
for (int i = 0; i < indicesAndWeights.size(); i++) {
if (indicesAndWeights[i].jointWeight > 0.0f) {
const FbxVector4 localPosition =
skinning.GetJointInverseGlobalTransforms(indicesAndWeights[i].jointIndex)
.MultNormalize(globalPosition);
Vec3f& mins = rawSurface.jointGeometryMins[indicesAndWeights[i].jointIndex];
mins[0] = std::min(mins[0], (float)localPosition[0]);
mins[1] = std::min(mins[1], (float)localPosition[1]);
mins[2] = std::min(mins[2], (float)localPosition[2]);
Vec3f& maxs = rawSurface.jointGeometryMaxs[indicesAndWeights[i].jointIndex];
maxs[0] = std::max(maxs[0], (float)localPosition[0]);
maxs[1] = std::max(maxs[1], (float)localPosition[1]);
maxs[2] = std::max(maxs[2], (float)localPosition[2]);
}
}
}
static void ReadMesh( static void ReadMesh(
RawModel& raw, RawModel& raw,
FbxScene* pScene, FbxScene* pScene,
@ -177,10 +201,6 @@ static void ReadMesh(
if (uvLayer1.LayerPresent()) { if (uvLayer1.LayerPresent()) {
raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_UV1); raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_UV1);
} }
if (skinning.IsSkinned()) {
raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS);
raw.AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_JOINT_INDICES);
}
RawSurface& rawSurface = raw.GetSurface(rawSurfaceIndex); RawSurface& rawSurface = raw.GetSurface(rawSurfaceIndex);
@ -361,8 +381,15 @@ static void ReadMesh(
vertex.uv0[1] = (float)fbxUV0[1]; vertex.uv0[1] = (float)fbxUV0[1];
vertex.uv1[0] = (float)fbxUV1[0]; vertex.uv1[0] = (float)fbxUV1[0];
vertex.uv1[1] = (float)fbxUV1[1]; vertex.uv1[1] = (float)fbxUV1[1];
vertex.jointIndices = skinning.GetVertexIndices(controlPointIndex); if (skinning.IsSkinned()) {
vertex.jointWeights = skinning.GetVertexWeights(controlPointIndex); const std::vector<FbxVertexSkinningInfo> skinningInfo =
skinning.GetVertexSkinningInfo(controlPointIndex);
for (int skinningIndex = 0; skinningIndex < skinningInfo.size(); skinningIndex++) {
const FbxVertexSkinningInfo& sourceSkinningInfo = skinningInfo[skinningIndex];
vertex.skinningInfo.push_back(
RawVertexSkinningInfo{sourceSkinningInfo.jointId, sourceSkinningInfo.weight});
}
}
vertex.polarityUv0 = false; vertex.polarityUv0 = false;
// flag this triangle as transparent if any of its corner vertices substantially deviates from // flag this triangle as transparent if any of its corner vertices substantially deviates from
@ -406,38 +433,13 @@ static void ReadMesh(
} }
if (skinning.IsSkinned()) { if (skinning.IsSkinned()) {
const int jointIndices[FbxSkinningAccess::MAX_WEIGHTS] = {vertex.jointIndices[0], FbxMatrix skinningMatrix = FbxMatrix() * 0.0;
vertex.jointIndices[1], for (int j = 0; j < vertex.skinningInfo.size(); j++)
vertex.jointIndices[2], skinningMatrix += skinning.GetJointSkinningTransform(vertex.skinningInfo[j].jointIndex) *
vertex.jointIndices[3]}; vertex.skinningInfo[j].jointWeight;
const float jointWeights[FbxSkinningAccess::MAX_WEIGHTS] = {vertex.jointWeights[0],
vertex.jointWeights[1],
vertex.jointWeights[2],
vertex.jointWeights[3]};
const FbxMatrix skinningMatrix =
skinning.GetJointSkinningTransform(jointIndices[0]) * jointWeights[0] +
skinning.GetJointSkinningTransform(jointIndices[1]) * jointWeights[1] +
skinning.GetJointSkinningTransform(jointIndices[2]) * jointWeights[2] +
skinning.GetJointSkinningTransform(jointIndices[3]) * jointWeights[3];
const FbxVector4 globalPosition = skinningMatrix.MultNormalize(fbxPosition); const FbxVector4 globalPosition = skinningMatrix.MultNormalize(fbxPosition);
for (int i = 0; i < FbxSkinningAccess::MAX_WEIGHTS; i++) { calcMinMax(rawSurface, skinning, globalPosition, vertex.skinningInfo);
if (jointWeights[i] > 0.0f) {
const FbxVector4 localPosition =
skinning.GetJointInverseGlobalTransforms(jointIndices[i])
.MultNormalize(globalPosition);
Vec3f& mins = rawSurface.jointGeometryMins[jointIndices[i]];
mins[0] = std::min(mins[0], (float)localPosition[0]);
mins[1] = std::min(mins[1], (float)localPosition[1]);
mins[2] = std::min(mins[2], (float)localPosition[2]);
Vec3f& maxs = rawSurface.jointGeometryMaxs[jointIndices[i]];
maxs[0] = std::max(maxs[0], (float)localPosition[0]);
maxs[1] = std::max(maxs[1], (float)localPosition[1]);
maxs[2] = std::max(maxs[2], (float)localPosition[2]);
}
}
} }
} }

View File

@ -19,8 +19,7 @@ FbxSkinningAccess::FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, Fbx
continue; continue;
} }
int controlPointCount = pMesh->GetControlPointsCount(); int controlPointCount = pMesh->GetControlPointsCount();
vertexJointIndices.resize(controlPointCount, Vec4i(0, 0, 0, 0)); vertexSkinning.resize(controlPointCount);
vertexJointWeights.resize(controlPointCount, Vec4f(0.0f, 0.0f, 0.0f, 0.0f));
for (int clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) { for (int clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) {
FbxCluster* cluster = skin->GetCluster(clusterIndex); FbxCluster* cluster = skin->GetCluster(clusterIndex);
@ -56,31 +55,18 @@ FbxSkinningAccess::FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, Fbx
if (clusterIndices[i] < 0 || clusterIndices[i] >= controlPointCount) { if (clusterIndices[i] < 0 || clusterIndices[i] >= controlPointCount) {
continue; continue;
} }
if (clusterWeights[i] <= vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1]) { if (clusterWeights[i] <= 0.0) {
continue; continue;
} }
vertexJointIndices[clusterIndices[i]][MAX_WEIGHTS - 1] = clusterIndex;
vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1] = (float)clusterWeights[i]; vertexSkinning[clusterIndices[i]].push_back(FbxVertexSkinningInfo{(int) clusterIndex, (float)clusterWeights[i]});
for (int j = MAX_WEIGHTS - 1; j > 0; j--) {
if (vertexJointWeights[clusterIndices[i]][j - 1] >=
vertexJointWeights[clusterIndices[i]][j]) {
break;
}
std::swap(
vertexJointIndices[clusterIndices[i]][j - 1],
vertexJointIndices[clusterIndices[i]][j]);
std::swap(
vertexJointWeights[clusterIndices[i]][j - 1],
vertexJointWeights[clusterIndices[i]][j]);
}
} }
} }
for (int i = 0; i < controlPointCount; i++) {
const float weightSumRcp = 1.0 / for (int i = 0; i < vertexSkinning.size(); i++)
(vertexJointWeights[i][0] + vertexJointWeights[i][1] + vertexJointWeights[i][2] + maxBoneInfluences = std::max((int) vertexSkinning[i].size(), maxBoneInfluences);
vertexJointWeights[i][3]);
vertexJointWeights[i] *= weightSumRcp;
}
} }
} }

View File

@ -18,16 +18,22 @@
#include "FBX2glTF.h" #include "FBX2glTF.h"
struct FbxVertexSkinningInfo {
int jointId;
float weight;
};
class FbxSkinningAccess { class FbxSkinningAccess {
public: public:
static const int MAX_WEIGHTS = 4;
FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, FbxNode* pNode); FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, FbxNode* pNode);
bool IsSkinned() const { bool IsSkinned() const {
return (vertexJointWeights.size() > 0); return (vertexSkinning.size() > 0);
} }
int GetNodeCount() const { int GetNodeCount() const {
return (int)jointNodes.size(); return (int)jointNodes.size();
} }
@ -56,24 +62,18 @@ class FbxSkinningAccess {
const FbxAMatrix& GetInverseBindMatrix(const int jointIndex) const { const FbxAMatrix& GetInverseBindMatrix(const int jointIndex) const {
return inverseBindMatrices[jointIndex]; return inverseBindMatrices[jointIndex];
} }
const Vec4i GetVertexIndices(const int controlPointIndex) const { const std::vector<FbxVertexSkinningInfo> GetVertexSkinningInfo(const int controlPointIndex) const {
return (!vertexJointIndices.empty()) ? vertexJointIndices[controlPointIndex] return vertexSkinning[controlPointIndex];
: Vec4i(0, 0, 0, 0); }
}
const Vec4f GetVertexWeights(const int controlPointIndex) const {
return (!vertexJointWeights.empty()) ? vertexJointWeights[controlPointIndex]
: Vec4f(0, 0, 0, 0);
}
private: private:
int rootIndex; int rootIndex;
std::vector<uint64_t> jointIds; int maxBoneInfluences;
std::vector<long> jointIds;
std::vector<FbxNode*> jointNodes; std::vector<FbxNode*> jointNodes;
std::vector<FbxMatrix> jointSkinningTransforms; std::vector<FbxMatrix> jointSkinningTransforms;
std::vector<FbxMatrix> jointInverseGlobalTransforms; std::vector<FbxMatrix> jointInverseGlobalTransforms;
std::vector<FbxAMatrix> inverseBindMatrices; std::vector<FbxAMatrix> inverseBindMatrices;
std::vector<Vec4i> vertexJointIndices; std::vector<std::vector<FbxVertexSkinningInfo>> vertexSkinning;
std::vector<Vec4f> vertexJointWeights;
}; };

View File

@ -122,6 +122,31 @@ class GltfModel {
return accessor; return accessor;
}; };
template <class T>
std::shared_ptr<AccessorData> AddAttributeArrayToPrimitive(
BufferData& buffer,
const RawModel& surfaceModel,
PrimitiveData& primitive,
const AttributeArrayDefinition<T>& attrDef) {
// copy attribute data into vector
std::vector<T> attribArr;
surfaceModel.GetArrayAttributeArray<T>(attribArr, attrDef.rawAttributeIx, attrDef.arrayOffset);
std::shared_ptr<AccessorData> accessor;
if (attrDef.dracoComponentType != draco::DT_INVALID && primitive.dracoMesh != nullptr) {
primitive.AddDracoArrayAttrib(attrDef, attribArr);
accessor = accessors.hold(new AccessorData(attrDef.glType));
accessor->count = attribArr.size();
}
else {
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER);
accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr, std::string(""));
}
primitive.AddAttrib(attrDef.gltfName, *accessor);
return accessor;
};
template <class T> template <class T>
void serializeHolder(json& glTFJson, std::string key, const Holder<T> holder) { void serializeHolder(json& glTFJson, std::string key, const Holder<T> holder) {
if (!holder.ptrs.empty()) { if (!holder.ptrs.empty()) {

View File

@ -496,8 +496,8 @@ ModelData* Raw2Gltf(
} }
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_TANGENT) != 0) { if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_TANGENT) != 0) {
const AttributeDefinition<Vec4f> ATTR_TANGENT("TANGENT", &RawVertex::tangent, GLT_VEC4F); const AttributeDefinition<Vec4f> ATTR_TANGENT("TANGENT", &RawVertex::tangent, GLT_VEC4F);
const auto _ = gltf->AddAttributeToPrimitive<Vec4f>( const auto _ =
buffer, surfaceModel, *primitive, ATTR_TANGENT); gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_TANGENT);
} }
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) != 0) { if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) != 0) {
const AttributeDefinition<Vec4f> ATTR_COLOR( const AttributeDefinition<Vec4f> ATTR_COLOR(
@ -530,24 +530,30 @@ ModelData* Raw2Gltf(
buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1); buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1);
} }
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) != 0) { if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) != 0) {
const AttributeDefinition<Vec4i> ATTR_JOINTS( for (int i = 0; i < surfaceModel.GetGlobalWeightCount(); i += 4) {
"JOINTS_0", const AttributeArrayDefinition<Vec4i> ATTR_JOINTS(
&RawVertex::jointIndices, std::string("JOINTS_") + std::to_string(i / 4),
GLT_VEC4I, &RawVertex::jointIndices,
draco::GeometryAttribute::GENERIC, GLT_VEC4I,
draco::DT_UINT16); draco::GeometryAttribute::GENERIC,
const auto _ = draco::DT_UINT16,
gltf->AddAttributeToPrimitive<Vec4i>(buffer, surfaceModel, *primitive, ATTR_JOINTS); i / 4);
const auto _ = gltf->AddAttributeArrayToPrimitive<Vec4i>(
buffer, surfaceModel, *primitive, ATTR_JOINTS);
}
} }
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) != 0) { if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) != 0) {
const AttributeDefinition<Vec4f> ATTR_WEIGHTS( for (int i = 0; i < surfaceModel.GetGlobalWeightCount(); i += 4) {
"WEIGHTS_0", const AttributeArrayDefinition<Vec4f> ATTR_WEIGHTS(
&RawVertex::jointWeights, std::string("WEIGHTS_") + std::to_string(i / 4),
GLT_VEC4F, &RawVertex::jointWeights,
draco::GeometryAttribute::GENERIC, GLT_VEC4F,
draco::DT_FLOAT32); draco::GeometryAttribute::GENERIC,
const auto _ = draco::DT_FLOAT32,
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_WEIGHTS); i / 4);
const auto _ = gltf->AddAttributeArrayToPrimitive<Vec4f>(
buffer, surfaceModel, *primitive, ATTR_WEIGHTS);
}
} }
// each channel present in the mesh always ends up a target in the primitive // each channel present in the mesh always ends up a target in the primitive
@ -632,7 +638,8 @@ ModelData* Raw2Gltf(
draco::Status status = encoder.EncodeMeshToBuffer(*primitive->dracoMesh, &dracoBuffer); draco::Status status = encoder.EncodeMeshToBuffer(*primitive->dracoMesh, &dracoBuffer);
assert(status.code() == draco::Status::OK); assert(status.code() == draco::Status::OK);
auto view = gltf->AddRawBufferView(buffer, dracoBuffer.data(), to_uint32(dracoBuffer.size())); auto view =
gltf->AddRawBufferView(buffer, dracoBuffer.data(), to_uint32(dracoBuffer.size()));
primitive->NoteDracoBuffer(*view); primitive->NoteDracoBuffer(*view);
} }
mesh->AddPrimitive(primitive); mesh->AddPrimitive(primitive);

View File

@ -155,6 +155,30 @@ struct AttributeDefinition {
dracoComponentType(draco::DataType::DT_INVALID) {} dracoComponentType(draco::DataType::DT_INVALID) {}
}; };
template <class T>
struct AttributeArrayDefinition {
const std::string gltfName;
const std::vector<T> RawVertex::*rawAttributeIx;
const GLType glType;
const int arrayOffset;
const draco::GeometryAttribute::Type dracoAttribute;
const draco::DataType dracoComponentType;
AttributeArrayDefinition(
const std::string gltfName,
const std::vector<T> RawVertex::*rawAttributeIx,
const GLType& _glType,
const draco::GeometryAttribute::Type dracoAttribute,
const draco::DataType dracoComponentType,
const int arrayOffset)
: gltfName(gltfName),
rawAttributeIx(rawAttributeIx),
glType(_glType),
dracoAttribute(dracoAttribute),
dracoComponentType(dracoComponentType),
arrayOffset(arrayOffset) {}
};
struct AccessorData; struct AccessorData;
struct AnimationData; struct AnimationData;
struct BufferData; struct BufferData;

View File

@ -61,6 +61,32 @@ struct PrimitiveData {
dracoAttributes[attribute.gltfName] = dracoAttId; dracoAttributes[attribute.gltfName] = dracoAttId;
} }
template <class T>
void AddDracoArrayAttrib(const AttributeArrayDefinition<T> attribute, const std::vector<T>& attribArr) {
draco::PointAttribute att;
int8_t componentCount = attribute.glType.count;
att.Init(
attribute.dracoAttribute,
nullptr,
componentCount,
attribute.dracoComponentType,
false,
componentCount * draco::DataTypeLength(attribute.dracoComponentType),
0);
const int dracoAttId = dracoMesh->AddAttribute(att, true, attribArr.size());
draco::PointAttribute* attPtr = dracoMesh->attribute(dracoAttId);
std::vector<uint8_t> buf(sizeof(T));
for (uint32_t ii = 0; ii < attribArr.size(); ii++) {
uint8_t* ptr = &buf[0];
attribute.glType.write(ptr, attribArr[ii]);
attPtr->SetAttributeValue(attPtr->mapped_index(draco::PointIndex(ii)), ptr);
}
dracoAttributes[attribute.gltfName] = dracoAttId;
}
void NoteDracoBuffer(const BufferViewData& data); void NoteDracoBuffer(const BufferViewData& data);
const int indices; const int indices;

View File

@ -34,8 +34,9 @@ size_t VertexHasher::operator()(const RawVertex& v) const {
bool RawVertex::operator==(const RawVertex& other) const { bool RawVertex::operator==(const RawVertex& other) const {
return (position == other.position) && (normal == other.normal) && (tangent == other.tangent) && return (position == other.position) && (normal == other.normal) && (tangent == other.tangent) &&
(binormal == other.binormal) && (color == other.color) && (uv0 == other.uv0) && (binormal == other.binormal) && (color == other.color) && (uv0 == other.uv0) &&
(uv1 == other.uv1) && (jointIndices == other.jointIndices) && (uv1 == other.uv1) &&
(jointWeights == other.jointWeights) && (polarityUv0 == other.polarityUv0) && (jointWeights == other.jointWeights) && (jointIndices == other.jointIndices) &&
(polarityUv0 == other.polarityUv0) &&
(blendSurfaceIx == other.blendSurfaceIx) && (blends == other.blends); (blendSurfaceIx == other.blendSurfaceIx) && (blends == other.blends);
} }
@ -63,16 +64,13 @@ size_t RawVertex::Difference(const RawVertex& other) const {
attributes |= RAW_VERTEX_ATTRIBUTE_UV1; attributes |= RAW_VERTEX_ATTRIBUTE_UV1;
} }
// Always need both or neither. // Always need both or neither.
if (jointIndices != other.jointIndices) { if (jointIndices != other.jointIndices || jointWeights != other.jointWeights) {
attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
}
if (jointWeights != other.jointWeights) {
attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
} }
return attributes; return attributes;
} }
RawModel::RawModel() : vertexAttributes(0) {} RawModel::RawModel() : vertexAttributes(0){}
void RawModel::AddVertexAttribute(const RawVertexAttribute attrib) { void RawModel::AddVertexAttribute(const RawVertexAttribute attrib) {
vertexAttributes |= attrib; vertexAttributes |= attrib;
@ -335,7 +333,7 @@ int RawModel::AddNode(const long id, const char* name, const long parentId) {
return (int)nodes.size() - 1; return (int)nodes.size() - 1;
} }
void RawModel::Condense() { void RawModel::Condense(const int maxSkinningWeights, const bool normalizeWeights) {
// Only keep surfaces that are referenced by one or more triangles. // Only keep surfaces that are referenced by one or more triangles.
{ {
std::vector<RawSurface> oldSurfaces = surfaces; std::vector<RawSurface> oldSurfaces = surfaces;
@ -405,6 +403,53 @@ void RawModel::Condense() {
} }
} }
} }
{
globalMaxWeights = 0;
for (auto& vertex: vertices) {
// Sort from largest to smallest weight.
std::sort(vertex.skinningInfo.begin(), vertex.skinningInfo.end(), std::greater<RawVertexSkinningInfo>());
// Reduce to fit the requirements.
if (maxSkinningWeights < vertex.skinningInfo.size())
vertex.skinningInfo.resize(maxSkinningWeights);
globalMaxWeights = std::max(globalMaxWeights, (int) vertex.skinningInfo.size());
// Normalize weights if requested.
if (normalizeWeights) {
float weightSum = 0;
for (auto& jointWeight : vertex.skinningInfo)
weightSum += jointWeight.jointWeight;
const float weightSumRcp = 1.0 / weightSum;
for (auto& jointWeight : vertex.skinningInfo)
jointWeight.jointWeight *= weightSumRcp;
}
}
if (globalMaxWeights > 0) {
AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_JOINT_INDICES);
AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS);
}
assert(globalMaxWeights >= 0);
// Copy to gltf friendly structure
for (auto& vertex : vertices) {
vertex.jointIndices.reserve(globalMaxWeights);
vertex.jointWeights.reserve(globalMaxWeights);
for (int i = 0; i < globalMaxWeights; i += 4) { // ensure every vertex has the same amount of weights
Vec4f weights{0.0};
Vec4i jointIds{0,0,0,0};
for (int j = i; j < i + 4 && j < vertex.skinningInfo.size(); j++) {
weights[j - i] = vertex.skinningInfo[j].jointWeight;
jointIds[j - i] = vertex.skinningInfo[j].jointIndex;
}
vertex.jointIndices.push_back(jointIds);
vertex.jointWeights.push_back(weights);
}
}
}
} }
void RawModel::TransformGeometry(ComputeNormalsOption normals) { void RawModel::TransformGeometry(ComputeNormalsOption normals) {
@ -554,6 +599,7 @@ void RawModel::CreateMaterialModels(
surfaces[sortedTriangles[i - 1].surfaceIndex].discrete))) { surfaces[sortedTriangles[i - 1].surfaceIndex].discrete))) {
materialModels.resize(materialModels.size() + 1); materialModels.resize(materialModels.size() + 1);
model = &materialModels[materialModels.size() - 1]; model = &materialModels[materialModels.size() - 1];
model->globalMaxWeights = globalMaxWeights;
} }
// FIXME: will have to unlink from the nodes, transform both surfaces into a // FIXME: will have to unlink from the nodes, transform both surfaces into a
@ -622,11 +668,11 @@ void RawModel::CreateMaterialModels(
if ((keep & RAW_VERTEX_ATTRIBUTE_UV1) == 0) { if ((keep & RAW_VERTEX_ATTRIBUTE_UV1) == 0) {
vertex.uv1 = defaultVertex.uv1; vertex.uv1 = defaultVertex.uv1;
} }
if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) == 0) { if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) == 0) {
vertex.jointIndices = defaultVertex.jointIndices; vertex.jointIndices = defaultVertex.jointIndices;
} }
if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) == 0) { if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) == 0) {
vertex.jointWeights = defaultVertex.jointWeights; vertex.jointWeights = defaultVertex.jointWeights;
} }
} }

View File

@ -22,7 +22,7 @@ enum RawVertexAttribute {
RAW_VERTEX_ATTRIBUTE_COLOR = 1 << 4, RAW_VERTEX_ATTRIBUTE_COLOR = 1 << 4,
RAW_VERTEX_ATTRIBUTE_UV0 = 1 << 5, RAW_VERTEX_ATTRIBUTE_UV0 = 1 << 5,
RAW_VERTEX_ATTRIBUTE_UV1 = 1 << 6, RAW_VERTEX_ATTRIBUTE_UV1 = 1 << 6,
RAW_VERTEX_ATTRIBUTE_JOINT_INDICES = 1 << 7, RAW_VERTEX_ATTRIBUTE_JOINT_INDICES = 1 << 7,
RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS = 1 << 8, RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS = 1 << 8,
RAW_VERTEX_ATTRIBUTE_AUTO = 1 << 31 RAW_VERTEX_ATTRIBUTE_AUTO = 1 << 31
@ -38,6 +38,16 @@ struct RawBlendVertex {
} }
}; };
struct RawVertexSkinningInfo
{
int jointIndex;
float jointWeight;
bool operator>(const RawVertexSkinningInfo& rjw) const {
return jointWeight > rjw.jointWeight;
}
};
struct RawVertex { struct RawVertex {
Vec3f position{0.0f}; Vec3f position{0.0f};
Vec3f normal{0.0f}; Vec3f normal{0.0f};
@ -46,8 +56,10 @@ struct RawVertex {
Vec4f color{0.0f}; Vec4f color{0.0f};
Vec2f uv0{0.0f}; Vec2f uv0{0.0f};
Vec2f uv1{0.0f}; Vec2f uv1{0.0f};
Vec4i jointIndices{0, 0, 0, 0}; std::vector<Vec4i> jointIndices;
Vec4f jointWeights{0.0f}; std::vector<Vec4f> jointWeights;
std::vector<RawVertexSkinningInfo> skinningInfo;
// end of members that directly correspond to vertex attributes // end of members that directly correspond to vertex attributes
// if this vertex participates in a blend shape setup, the surfaceIx of its dedicated mesh; // if this vertex participates in a blend shape setup, the surfaceIx of its dedicated mesh;
@ -347,6 +359,7 @@ struct RawNode {
class RawModel { class RawModel {
public: public:
RawModel(); RawModel();
// Add geometry. // Add geometry.
@ -407,7 +420,7 @@ class RawModel {
// Remove unused vertices, textures or materials after removing vertex attributes, textures, // Remove unused vertices, textures or materials after removing vertex attributes, textures,
// materials or surfaces. // materials or surfaces.
void Condense(); void Condense(const int maxSkinningWeights, const bool normalizeWeights);
void TransformGeometry(ComputeNormalsOption); void TransformGeometry(ComputeNormalsOption);
@ -424,6 +437,11 @@ class RawModel {
int GetVertexCount() const { int GetVertexCount() const {
return (int)vertices.size(); return (int)vertices.size();
} }
int GetGlobalWeightCount() const{
return globalMaxWeights;
}
const RawVertex& GetVertex(const int index) const { const RawVertex& GetVertex(const int index) const {
return vertices[index]; return vertices[index];
} }
@ -506,6 +524,14 @@ class RawModel {
void GetAttributeArray(std::vector<_attrib_type_>& out, const _attrib_type_ RawVertex::*ptr) void GetAttributeArray(std::vector<_attrib_type_>& out, const _attrib_type_ RawVertex::*ptr)
const; const;
// Create individual attribute arrays, with the source as an array.
// Returns true if the vertices store the particular attribute.
template <typename _attrib_type_>
void GetArrayAttributeArray(std::vector<_attrib_type_>& out,
const std::vector<_attrib_type_> RawVertex::*ptr,
const int arrayOffset)
const;
// Create an array with a raw model for each material. // Create an array with a raw model for each material.
// Multiple surfaces with the same material will turn into a single model. // Multiple surfaces with the same material will turn into a single model.
// However, surfaces that are marked as 'discrete' will turn into separate models. // However, surfaces that are marked as 'discrete' will turn into separate models.
@ -520,6 +546,7 @@ class RawModel {
long rootNodeId; long rootNodeId;
int vertexAttributes; int vertexAttributes;
int globalMaxWeights;
std::unordered_map<RawVertex, int, VertexHasher> vertexHash; std::unordered_map<RawVertex, int, VertexHasher> vertexHash;
std::vector<RawVertex> vertices; std::vector<RawVertex> vertices;
std::vector<RawTriangle> triangles; std::vector<RawTriangle> triangles;
@ -541,3 +568,14 @@ void RawModel::GetAttributeArray(
out[i] = vertices[i].*ptr; out[i] = vertices[i].*ptr;
} }
} }
template <typename _attrib_type_>
void RawModel::GetArrayAttributeArray(
std::vector<_attrib_type_>& out,
const std::vector<_attrib_type_> RawVertex::*ptr,
const int arrayOffset) const {
out.resize(vertices.size());
for (size_t i = 0; i < vertices.size(); i++) {
out[i] = (vertices[i].*ptr)[arrayOffset];
}
}