fix avatar bounding capsule calculations

This commit is contained in:
Andrew Meadows 2015-09-08 15:01:51 -07:00
parent 90fe54e6d6
commit 281e4f21fc
6 changed files with 98 additions and 108 deletions

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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<glm::vec3> 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<JointShapeInfo> jointShapeInfos;
jointShapeInfos.resize(geometry.joints.size());
// NOTE: shapeVertices are in joint-frame
QVector<ShapeVertices> 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<int, int>::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());

View file

@ -55,15 +55,21 @@ public:
QVector<glm::vec3> normals;
};
struct FBXJointShapeInfo {
// same units and frame as FBXJoint.translation
QVector<glm::vec3> points;
float radius;
};
/// A single joint (transformation node) extracted from an FBX document.
class FBXJoint {
public:
bool isFree;
FBXJointShapeInfo shapeInfo;
QVector<int> freeLineage;
bool isFree;
int parentIndex;
float distanceToParent;
float boneRadius;
// http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/SDKRef/a00209.html

View file

@ -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;