diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 5ffd0f8dec..6590b5936a 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -544,14 +544,20 @@ void SkeletonModel::computeBoundingShape() { totalExtents.addPoint(glm::vec3(0.0f)); int numStates = _rig->getJointStateCount(); for (int i = 0; i < numStates; i++) { - // compute the default transform of this joint const JointState& state = _rig->getJointState(i); - // Each joint contributes a sphere at its position - glm::vec3 axis(state.getBoneRadius()); - glm::vec3 jointPosition = state.getPosition(); - totalExtents.addPoint(jointPosition + axis); - totalExtents.addPoint(jointPosition - axis); + const glm::mat4& jointTransform = state.getTransform(); + float scale = extractUniformScale(jointTransform); + + // Each joint contributes a capsule defined by FBXJoint.shapeInfo. + // For totalExtents we use the capsule endpoints expanded by the radius. + const FBXJointShapeInfo& shapeInfo = geometry.joints.at(i).shapeInfo; + for (int j = 0; j < shapeInfo.points.size(); ++j) { + glm::vec3 transformedPoint = extractTranslation(jointTransform * glm::translate(shapeInfo.points[j])); + vec3 radius(scale * shapeInfo.radius); + totalExtents.addPoint(transformedPoint + radius); + totalExtents.addPoint(transformedPoint - radius); + } } // compute bounding shape parameters diff --git a/libraries/animation/src/JointState.cpp b/libraries/animation/src/JointState.cpp index edea3b462d..7493e95084 100644 --- a/libraries/animation/src/JointState.cpp +++ b/libraries/animation/src/JointState.cpp @@ -41,7 +41,7 @@ void JointState::copyState(const JointState& other) { // DO NOT copy _constraint _name = other._name; _isFree = other._isFree; - _boneRadius = other._boneRadius; +// _boneRadius = other._boneRadius; _parentIndex = other._parentIndex; _defaultRotation = other._defaultRotation; _inverseDefaultRotation = other._inverseDefaultRotation; @@ -58,7 +58,7 @@ JointState::JointState(const FBXJoint& joint) { _rotationInConstrainedFrame = joint.rotation; _name = joint.name; _isFree = joint.isFree; - _boneRadius = joint.boneRadius; +// _boneRadius = joint.boneRadius; _parentIndex = joint.parentIndex; _translation = joint.translation; _defaultRotation = joint.rotation; diff --git a/libraries/animation/src/JointState.h b/libraries/animation/src/JointState.h index 4f45661eb2..426603bde1 100644 --- a/libraries/animation/src/JointState.h +++ b/libraries/animation/src/JointState.h @@ -118,7 +118,7 @@ public: const glm::quat& getDefaultRotation() const { return _defaultRotation; } const glm::quat& getInverseDefaultRotation() const { return _inverseDefaultRotation; } const QString& getName() const { return _name; } - float getBoneRadius() const { return _boneRadius; } +// float getBoneRadius() const { return _boneRadius; } bool getIsFree() const { return _isFree; } float getAnimationPriority() const { return _animationPriority; } void setAnimationPriority(float priority) { _animationPriority = priority; } @@ -149,7 +149,7 @@ private: QString _name; int _parentIndex; bool _isFree; - float _boneRadius; +// float _boneRadius; glm::vec3 _rotationMin; glm::vec3 _rotationMax; glm::quat _preRotation; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 35390a8e44..5410859340 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1257,21 +1257,7 @@ QString getString(const QVariant& value) { return list.isEmpty() ? value.toString() : list.at(0).toString(); } -class JointShapeInfo { -public: - JointShapeInfo() : numVertices(0), - sumVertexWeights(0.0f), sumWeightedRadii(0.0f), numVertexWeights(0), - boneBegin(0.0f), averageRadius(0.0f) { - } - - // NOTE: the points here are in the "joint frame" which has the "jointEnd" at the origin - int numVertices; // num vertices from contributing meshes - float sumVertexWeights; // sum of all vertex weights - float sumWeightedRadii; // sum of weighted vertices - int numVertexWeights; // num vertices that contributed to sums - glm::vec3 boneBegin; // parent joint location (in joint frame) - float averageRadius; -}; +typedef std::vector ShapeVertices; class AnimationCurve { public: @@ -2282,22 +2268,21 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping joint.postTransform = model.postTransform; joint.rotationMin = model.rotationMin; joint.rotationMax = model.rotationMax; - glm::quat combinedRotation = model.preRotation * model.rotation * model.postRotation; + glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; if (joint.parentIndex == -1) { - joint.transform = geometry.offset * glm::translate(model.translation) * model.preTransform * - glm::mat4_cast(combinedRotation) * model.postTransform; + joint.transform = geometry.offset * glm::translate(joint.translation) * joint.preTransform * + glm::mat4_cast(combinedRotation) * joint.postTransform; joint.inverseDefaultRotation = glm::inverse(combinedRotation); - joint.distanceToParent = 0.0f; + joint.distanceToParent = 0.0f; } else { const FBXJoint& parentJoint = geometry.joints.at(joint.parentIndex); - joint.transform = parentJoint.transform * glm::translate(model.translation) * - model.preTransform * glm::mat4_cast(combinedRotation) * model.postTransform; + joint.transform = parentJoint.transform * glm::translate(joint.translation) * + joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; joint.inverseDefaultRotation = glm::inverse(combinedRotation) * parentJoint.inverseDefaultRotation; joint.distanceToParent = glm::distance(extractTranslation(parentJoint.transform), extractTranslation(joint.transform)); } - joint.boneRadius = 0.0f; joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = model.name; @@ -2326,9 +2311,10 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping zCurve.values.isEmpty() ? defaultValues.z : zCurve.values.at(i % zCurve.values.size())))); } } - // for each joint we allocate a JointShapeInfo in which we'll store collision shape info - QVector jointShapeInfos; - jointShapeInfos.resize(geometry.joints.size()); + + // NOTE: shapeVertices are in joint-frame + QVector shapeVertices; + shapeVertices.resize(geometry.joints.size()); // find our special joints geometry.leftEyeJointIndex = modelIDs.indexOf(jointEyeLeftID); @@ -2585,8 +2571,10 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping boneDirection /= boneLength; } } - float radiusScale = extractUniformScale(joint.transform * fbxCluster.inverseBindMatrix); - JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex]; + + float clusterScale = extractUniformScale(fbxCluster.inverseBindMatrix); + glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; + ShapeVertices& points = shapeVertices[jointIndex]; float totalWeight = 0.0f; for (int j = 0; j < cluster.indices.size(); j++) { @@ -2595,18 +2583,13 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping totalWeight += weight; for (QMultiHash::const_iterator it = extracted.newIndices.constFind(oldIndex); it != extracted.newIndices.end() && it.key() == oldIndex; it++) { - // expand the bone radius for vertices with at least 1/4 weight + + // remember vertices with at least 1/4 weight const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; if (weight > EXPANSION_WEIGHT_THRESHOLD) { - const glm::vec3& vertex = extracted.mesh.vertices.at(it.value()); - float proj = glm::dot(boneDirection, boneEnd - vertex); - float radiusWeight = (proj < 0.0f || proj > boneLength) ? 0.5f * weight : weight; - - jointShapeInfo.sumVertexWeights += radiusWeight; - jointShapeInfo.sumWeightedRadii += radiusWeight * radiusScale * glm::distance(vertex, boneEnd - boneDirection * proj); - ++jointShapeInfo.numVertexWeights; - - ++jointShapeInfo.numVertices; + // transform to joint-frame and save for later + const glm::mat4 vertexTransform = meshToJoint * glm::translate(extracted.mesh.vertices.at(it.value())); + points.push_back(extractTranslation(vertexTransform) * clusterScale); } // look for an unused slot in the weights vector @@ -2649,54 +2632,16 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping // this is a single-mesh joint int jointIndex = maxJointIndex; FBXJoint& joint = geometry.joints[jointIndex]; - JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex]; - glm::mat4 transformJointToMesh = inverseModelTransform * joint.bindTransform; - glm::vec3 boneEnd = extractTranslation(transformJointToMesh); - glm::vec3 boneBegin = boneEnd; - - glm::vec3 boneDirection; - float boneLength = 0.0f; - if (joint.parentIndex != -1) { - boneBegin = extractTranslation(inverseModelTransform * geometry.joints[joint.parentIndex].bindTransform); - boneDirection = boneEnd - boneBegin; - boneLength = glm::length(boneDirection); - if (boneLength > EPSILON) { - boneDirection /= boneLength; - } - } - float radiusScale = extractUniformScale(joint.transform * firstFBXCluster.inverseBindMatrix); - - // compute average vertex - glm::vec3 averageVertex(0.0f); + // transform cluster vertices to joint-frame and save for later + float clusterScale = extractUniformScale(firstFBXCluster.inverseBindMatrix); + glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; + ShapeVertices& points = shapeVertices[jointIndex]; foreach (const glm::vec3& vertex, extracted.mesh.vertices) { - float proj = glm::dot(boneDirection, boneEnd - vertex); - float radiusWeight = (proj < 0.0f || proj > boneLength) ? 0.5f : 1.0f; - jointShapeInfo.sumVertexWeights += radiusWeight; - jointShapeInfo.sumWeightedRadii += radiusWeight * radiusScale * glm::distance(vertex, boneEnd - boneDirection * proj); - ++jointShapeInfo.numVertexWeights; - averageVertex += vertex; + const glm::mat4 vertexTransform = meshToJoint * glm::translate(vertex); + points.push_back(extractTranslation(vertexTransform) * clusterScale); } - // compute joint's radius - int numVertices = extracted.mesh.vertices.size(); - jointShapeInfo.numVertices = numVertices; - if (numVertices > 0) { - // compute average radius - averageVertex /= (float)jointShapeInfo.numVertices; - float averageRadius = 0.0f; - foreach (const glm::vec3& vertex, extracted.mesh.vertices) { - averageRadius += glm::distance(vertex, averageVertex); - } - averageRadius *= radiusScale / (float)jointShapeInfo.numVertices; - - // final radius is minimum of average and weighted - float weightedRadius = jointShapeInfo.sumWeightedRadii / jointShapeInfo.sumVertexWeights; - jointShapeInfo.averageRadius = glm::min(weightedRadius, averageRadius); - } - - // clear sumVertexWeights (this flags it as a single-mesh joint for later) - jointShapeInfo.sumVertexWeights = 0.0f; } extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex); @@ -2721,24 +2666,59 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping glm::vec3 defaultCapsuleAxis(0.0f, 1.0f, 0.0f); for (int i = 0; i < geometry.joints.size(); ++i) { FBXJoint& joint = geometry.joints[i]; - JointShapeInfo& jointShapeInfo = jointShapeInfos[i]; - if (joint.parentIndex == -1) { - jointShapeInfo.boneBegin = glm::vec3(0.0f); + // NOTE: points are in joint-frame + // compute average point + ShapeVertices& points = shapeVertices[i]; + glm::vec3 avgPoint = glm::vec3(0.0f); + for (uint32_t j = 0; j < points.size(); ++j) { + avgPoint += points[j]; + } + avgPoint /= (float)points.size(); + + // compute axis from begin to avgPoint + glm::vec3 begin(0.0f); + glm::vec3 end = avgPoint; + glm::vec3 axis = end - begin; + float axisLength = glm::length(axis); + if (axisLength > EPSILON) { + axis /= axisLength; } else { - const FBXJoint& parentJoint = geometry.joints[joint.parentIndex]; - glm::quat inverseRotation = glm::inverse(extractRotation(joint.transform)); - jointShapeInfo.boneBegin = inverseRotation * (extractTranslation(parentJoint.transform) - extractTranslation(joint.transform)); + axis = glm::vec3(0.0f); } - if (jointShapeInfo.sumVertexWeights > 0.0f) { - // mutiple meshes contributed to the bone radius and now that all - // contributing meshes are done we can finally compute the boneRadius - joint.boneRadius = jointShapeInfo.sumWeightedRadii / jointShapeInfo.sumVertexWeights; - } else { - // single-mesh joint - joint.boneRadius = jointShapeInfo.averageRadius; + // measure average cylindrical radius + float avgRadius = 0.0f; + if (points.size() > 0) { + float minProjection = FLT_MAX; + float maxProjection = -FLT_MIN; + for (uint32_t j = 0; j < points.size(); ++j) { + glm::vec3 offset = points[j] - avgPoint; + float projection = glm::dot(offset, axis); + maxProjection = glm::max(maxProjection, projection); + minProjection = glm::min(minProjection, projection); + avgRadius += glm::length(offset - projection * axis); + } + avgRadius /= (float)points.size(); + + // compute endpoints of capsule in joint-frame + glm::vec3 capsuleBegin = avgPoint; + glm::vec3 capsuleEnd = avgPoint; + if (maxProjection - minProjection < 2.0f * avgRadius) { + // the mesh-as-cylinder approximation is too short to collide as a capsule + // so we'll collapse it to a sphere (although that isn't a very good approximation) + capsuleBegin = avgPoint + 0.5f * (maxProjection + minProjection) * axis; + capsuleEnd = capsuleBegin; + } else { + capsuleBegin = avgPoint + (minProjection + avgRadius) * axis; + capsuleEnd = avgPoint + (maxProjection - avgRadius) * axis; + } + + // save points for later + joint.shapeInfo.points.push_back(capsuleBegin); + joint.shapeInfo.points.push_back(capsuleEnd); } + joint.shapeInfo.radius = avgRadius; } geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString()); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 158b5581c6..cff22676c8 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -55,15 +55,21 @@ public: QVector normals; }; +struct FBXJointShapeInfo { + // same units and frame as FBXJoint.translation + QVector points; + float radius; +}; + /// A single joint (transformation node) extracted from an FBX document. class FBXJoint { public: - bool isFree; + FBXJointShapeInfo shapeInfo; QVector freeLineage; + bool isFree; int parentIndex; float distanceToParent; - float boneRadius; // http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/SDKRef/a00209.html diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 3eff3bdec5..b4fa000d47 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -444,7 +444,6 @@ FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, geometry.joints[0].isFree = false; geometry.joints[0].parentIndex = -1; geometry.joints[0].distanceToParent = 0; - geometry.joints[0].boneRadius = 0; geometry.joints[0].translation = glm::vec3(0, 0, 0); geometry.joints[0].rotationMin = glm::vec3(0, 0, 0); geometry.joints[0].rotationMax = glm::vec3(0, 0, 0); @@ -620,7 +619,6 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << " freeLineage" << joint.freeLineage; qCDebug(modelformat) << " parentIndex" << joint.parentIndex; qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; - qCDebug(modelformat) << " boneRadius" << joint.boneRadius; qCDebug(modelformat) << " translation" << joint.translation; qCDebug(modelformat) << " preTransform" << joint.preTransform; qCDebug(modelformat) << " preRotation" << joint.preRotation;