From 46405874457840396802179c5f6c87926af2e7cc Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 15 May 2014 12:28:55 -0700 Subject: [PATCH 1/3] more correct model bounding capsule --- interface/src/avatar/Avatar.cpp | 60 +++++---- interface/src/renderer/Model.cpp | 213 ++++++++++++++++++++----------- interface/src/renderer/Model.h | 4 + 3 files changed, 173 insertions(+), 104 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 41dc50b1fa..7dec933b5a 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -214,37 +214,39 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { renderBody(renderMode, glowLevel); } - if (renderMode != SHADOW_RENDER_MODE && - Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { - _skeletonModel.updateShapePositions(); - _skeletonModel.renderJointCollisionShapes(0.7f); - } - if (renderMode != SHADOW_RENDER_MODE && - Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) { - if (shouldRenderHead(cameraPosition, renderMode)) { - getHead()->getFaceModel().updateShapePositions(); + if (renderMode != SHADOW_RENDER_MODE) { + bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes); + bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes); + bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes); + if (renderSkeleton || renderHead || renderBounding) { + updateShapePositions(); + } + + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { + _skeletonModel.renderJointCollisionShapes(0.7f); + } + + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes) + && shouldRenderHead(cameraPosition, renderMode)) { getHead()->getFaceModel().renderJointCollisionShapes(0.7f); } - } - if (renderMode != SHADOW_RENDER_MODE && - Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) { - if (shouldRenderHead(cameraPosition, renderMode)) { - getHead()->getFaceModel().updateShapePositions(); + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes) + && shouldRenderHead(cameraPosition, renderMode)) { getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f); - _skeletonModel.updateShapePositions(); _skeletonModel.renderBoundingCollisionShapes(0.7f); } - } - // If this is the avatar being looked at, render a little ball above their head - if (renderMode != SHADOW_RENDER_MODE &&_isLookAtTarget) { - const float LOOK_AT_INDICATOR_RADIUS = 0.03f; - const float LOOK_AT_INDICATOR_HEIGHT = 0.60f; - const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.5f }; - glPushMatrix(); - glColor4fv(LOOK_AT_INDICATOR_COLOR); - glTranslatef(_position.x, _position.y + (getSkeletonHeight() * LOOK_AT_INDICATOR_HEIGHT), _position.z); - glutSolidSphere(LOOK_AT_INDICATOR_RADIUS, 15, 15); - glPopMatrix(); + + // If this is the avatar being looked at, render a little ball above their head + if (_isLookAtTarget) { + const float LOOK_AT_INDICATOR_RADIUS = 0.03f; + const float LOOK_AT_INDICATOR_HEIGHT = 0.60f; + const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.5f }; + glPushMatrix(); + glColor4fv(LOOK_AT_INDICATOR_COLOR); + glTranslatef(_position.x, _position.y + (getSkeletonHeight() * LOOK_AT_INDICATOR_HEIGHT), _position.z); + glutSolidSphere(LOOK_AT_INDICATOR_RADIUS, 15, 15); + glPopMatrix(); + } } // quick check before falling into the code below: @@ -585,6 +587,12 @@ void Avatar::updateShapePositions() { _skeletonModel.updateShapePositions(); Model& headModel = getHead()->getFaceModel(); headModel.updateShapePositions(); + /* KEEP FOR DEBUG: use this in rather than code above to see shapes + * in their default positions where the bounding shape is computed. + _skeletonModel.resetShapePositions(); + Model& headModel = getHead()->getFaceModel(); + headModel.resetShapePositions(); + */ } bool Avatar::findCollisions(const QVector& shapes, CollisionList& collisions) { diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index e1bfd21bba..8a0286f63c 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -39,6 +39,7 @@ Model::Model(QObject* parent) : _scaledToFit(false), _snapModelToCenter(false), _snappedToCenter(false), + _rootIndex(-1), _shapesAreDirty(true), _boundingRadius(0.f), _boundingShape(), @@ -138,6 +139,7 @@ QVector Model::createJointStates(const FBXGeometry& geometry) const FBXJoint& joint = geometry.joints[i]; int parentIndex = joint.parentIndex; if (parentIndex == -1) { + _rootIndex = i; glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset); glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform * @@ -586,66 +588,20 @@ void Model::clearShapes() { void Model::rebuildShapes() { clearShapes(); - if (!_geometry) { + if (!_geometry || _rootIndex == -1) { return; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); - if (geometry.joints.isEmpty()) { return; } - int numJoints = geometry.joints.size(); - QVector transforms; - transforms.fill(glm::mat4(), numJoints); - QVector combinedRotations; - combinedRotations.fill(glm::quat(), numJoints); - QVector shapeIsSet; - shapeIsSet.fill(false, numJoints); - int rootIndex = 0; - + // We create the shapes with proper dimensions, but we set their transforms later. float uniformScale = extractUniformScale(_scale); - int numShapesSet = 0; - int lastNumShapesSet = -1; - while (numShapesSet < numJoints && numShapesSet != lastNumShapesSet) { - lastNumShapesSet = numShapesSet; - for (int i = 0; i < numJoints; ++i) { - if (shapeIsSet[i]) { - continue; - } - const FBXJoint& joint = geometry.joints[i]; - int parentIndex = joint.parentIndex; - if (parentIndex == -1) { - rootIndex = i; - glm::mat4 baseTransform = glm::mat4_cast(_rotation) * uniformScale * glm::translate(_offset); - glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; - transforms[i] = baseTransform * geometry.offset * glm::translate(joint.translation) * joint.preTransform * - glm::mat4_cast(combinedRotation) * joint.postTransform; - combinedRotations[i] = _rotation * combinedRotation; - ++numShapesSet; - shapeIsSet[i] = true; - } else if (shapeIsSet[parentIndex]) { - glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; - transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) * joint.preTransform * - glm::mat4_cast(combinedRotation) * joint.postTransform; - combinedRotations[i] = combinedRotations[parentIndex] * combinedRotation; - ++numShapesSet; - shapeIsSet[i] = true; - } - } - } - - // joint shapes - Extents totalExtents; - totalExtents.reset(); for (int i = 0; i < _jointStates.size(); i++) { const FBXJoint& joint = geometry.joints[i]; - glm::vec3 worldPosition = extractTranslation(transforms[i]); - Extents shapeExtents; - shapeExtents.reset(); - float radius = uniformScale * joint.boneRadius; float halfHeight = 0.5f * uniformScale * joint.distanceToParent; Shape::Type type = joint.shapeType; @@ -655,47 +611,148 @@ void Model::rebuildShapes() { } if (type == Shape::CAPSULE_SHAPE) { CapsuleShape* capsule = new CapsuleShape(radius, halfHeight); - capsule->setPosition(worldPosition); - capsule->setRotation(combinedRotations[i] * joint.shapeRotation); _jointShapes.push_back(capsule); - - // add the two furthest surface points of the capsule - glm::vec3 axis; - capsule->computeNormalizedAxis(axis); - axis = halfHeight * axis + glm::vec3(radius); - shapeExtents.addPoint(worldPosition + axis); - shapeExtents.addPoint(worldPosition - axis); - - totalExtents.addExtents(shapeExtents); } else if (type == Shape::SPHERE_SHAPE) { - SphereShape* sphere = new SphereShape(radius, worldPosition); + SphereShape* sphere = new SphereShape(radius, glm::vec3(0.0f)); _jointShapes.push_back(sphere); - - glm::vec3 axis = glm::vec3(radius); - shapeExtents.addPoint(worldPosition + axis); - shapeExtents.addPoint(worldPosition - axis); - totalExtents.addExtents(shapeExtents); } else { // this shape type is not handled and the joint shouldn't collide, // however we must have a shape for each joint, // so we make a bogus sphere with zero radius. // TODO: implement collision groups for more control over what collides with what - SphereShape* sphere = new SphereShape(0.f, worldPosition); + SphereShape* sphere = new SphereShape(0.f, glm::vec3(0.0f)); _jointShapes.push_back(sphere); } } - // bounding shape - // NOTE: we assume that the longest side of totalExtents is the yAxis + // This method moves the shapes to their default positions in Model frame + // which is where we compute the bounding shape's parameters. + computeBoundingShape(geometry); + + // finally sync shapes to joint positions + _shapesAreDirty = true; + updateShapePositions(); +} + +void Model::computeBoundingShape(const FBXGeometry& geometry) { + // compute default joint transforms and rotations + // (in local frame, ignoring Model translation and rotation) + int numJoints = geometry.joints.size(); + QVector transforms; + transforms.fill(glm::mat4(), numJoints); + QVector finalRotations; + finalRotations.fill(glm::quat(), numJoints); + + QVector shapeIsSet; + shapeIsSet.fill(false, numJoints); + int numShapesSet = 0; + int lastNumShapesSet = -1; + glm::vec3 rootOffset(0.0f); + while (numShapesSet < numJoints && numShapesSet != lastNumShapesSet) { + lastNumShapesSet = numShapesSet; + for (int i = 0; i < numJoints; i++) { + const FBXJoint& joint = geometry.joints.at(i); + int parentIndex = joint.parentIndex; + + if (parentIndex == -1) { + glm::mat4 baseTransform = glm::scale(_scale) * glm::translate(_offset); + glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; + transforms[i] = baseTransform * geometry.offset * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; + rootOffset = extractTranslation(transforms[i]); + finalRotations[i] = combinedRotation; + ++numShapesSet; + shapeIsSet[i] = true; + } else if (shapeIsSet[parentIndex]) { + glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; + transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; + finalRotations[i] = finalRotations[parentIndex] * combinedRotation; + ++numShapesSet; + shapeIsSet[i] = true; + } + } + } + + // sync shapes to joints + _boundingRadius = 0.0f; + float uniformScale = extractUniformScale(_scale); + for (int i = 0; i < _jointShapes.size(); i++) { + const FBXJoint& joint = geometry.joints[i]; + glm::vec3 jointToShapeOffset = uniformScale * (finalRotations[i] * joint.shapePosition); + glm::vec3 localPosition = extractTranslation(transforms[i]) + jointToShapeOffset- rootOffset; + Shape* shape = _jointShapes[i]; + shape->setPosition(localPosition); + shape->setRotation(finalRotations[i] * joint.shapeRotation); + float distance = glm::length(localPosition) + shape->getBoundingRadius(); + if (distance > _boundingRadius) { + _boundingRadius = distance; + } + } + + // compute bounding box + Extents totalExtents; + totalExtents.reset(); + for (int i = 0; i < _jointShapes.size(); i++) { + Extents shapeExtents; + shapeExtents.reset(); + + Shape* shape = _jointShapes[i]; + glm::vec3 localPosition = shape->getPosition(); + int type = shape->getType(); + if (type == Shape::CAPSULE_SHAPE) { + // add the two furthest surface points of the capsule + CapsuleShape* capsule = static_cast(shape); + glm::vec3 axis; + capsule->computeNormalizedAxis(axis); + float radius = capsule->getRadius(); + float halfHeight = capsule->getHalfHeight(); + axis = halfHeight * axis + glm::vec3(radius); + + shapeExtents.addPoint(localPosition + axis); + shapeExtents.addPoint(localPosition - axis); + totalExtents.addExtents(shapeExtents); + } else if (type == Shape::SPHERE_SHAPE) { + float radius = shape->getBoundingRadius(); + glm::vec3 axis = glm::vec3(radius); + shapeExtents.addPoint(localPosition + axis); + shapeExtents.addPoint(localPosition - axis); + totalExtents.addExtents(shapeExtents); + } + } + + // compute bounding shape parameters + // NOTE: we assume that the longest side of totalExtents is the yAxis... glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum; - // the radius is half the RMS of the X and Z sides: + // ... and assume the radius is half the RMS of the X and Z sides: float capsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); _boundingShape.setRadius(capsuleRadius); _boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius); + _boundingShapeLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum); +} - glm::quat inverseRotation = glm::inverse(_rotation); - glm::vec3 rootPosition = extractTranslation(transforms[rootIndex]); - _boundingShapeLocalOffset = inverseRotation * (0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition); +void Model::resetShapePositions() { + // DEBUG method. + // Moves shapes to the joint default locations for debug visibility into + // how the bounding shape is computed. + + if (!_geometry || _rootIndex == -1) { + // geometry or joints have not yet been created + return; + } + + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + if (geometry.joints.isEmpty() || _jointShapes.size() != geometry.joints.size()) { + return; + } + + // The shapes are moved to their default positions in computeBoundingShape(). + computeBoundingShape(geometry); + + // Then we move them into world frame for rendering at the Model's location. + for (int i = 0; i < _jointShapes.size(); i++) { + Shape* shape = _jointShapes[i]; + shape->setPosition(_translation + _rotation * shape->getPosition()); + shape->setRotation(_rotation * shape->getRotation()); + } _boundingShape.setPosition(_translation + _rotation * _boundingShapeLocalOffset); _boundingShape.setRotation(_rotation); } @@ -711,17 +768,17 @@ void Model::updateShapePositions() { // shape position and rotation need to be in world-frame glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition); glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation; - _jointShapes[i]->setPosition(worldPosition); - _jointShapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); - float distance2 = glm::distance2(worldPosition, _translation); - if (distance2 > _boundingRadius) { - _boundingRadius = distance2; + Shape* shape = _jointShapes[i]; + shape->setPosition(worldPosition); + shape->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); + float distance = glm::distance(worldPosition, _translation) + shape->getBoundingRadius(); + if (distance > _boundingRadius) { + _boundingRadius = distance; } if (joint.parentIndex == -1) { rootPosition = worldPosition; } } - _boundingRadius = sqrtf(_boundingRadius); _shapesAreDirty = false; _boundingShape.setPosition(rootPosition + _rotation * _boundingShapeLocalOffset); _boundingShape.setRotation(_rotation); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 1a469c8122..d80b2f13fa 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -185,6 +185,7 @@ public: void clearShapes(); void rebuildShapes(); + void resetShapePositions(); void updateShapePositions(); void renderJointCollisionShapes(float alpha); void renderBoundingCollisionShapes(float alpha); @@ -232,6 +233,7 @@ protected: bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space bool _snappedToCenter; /// are we currently snapped to center + int _rootIndex; class JointState { public: @@ -291,6 +293,8 @@ protected: void applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain = true); + void computeBoundingShape(const FBXGeometry& geometry); + private: void applyNextGeometry(); From 49a7f8c9108e8b9a12a58ee3b42d89be931f2975 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 15 May 2014 12:42:38 -0700 Subject: [PATCH 2/3] formatting some lines that are too long --- interface/src/renderer/Model.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 6cb62dbcfd..90ae9e5e46 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -674,14 +674,16 @@ void Model::computeBoundingShape(const FBXGeometry& geometry) { if (parentIndex == -1) { glm::mat4 baseTransform = glm::scale(_scale) * glm::translate(_offset); glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; - transforms[i] = baseTransform * geometry.offset * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; + transforms[i] = baseTransform * geometry.offset * glm::translate(joint.translation) + * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; rootOffset = extractTranslation(transforms[i]); finalRotations[i] = combinedRotation; ++numShapesSet; shapeIsSet[i] = true; } else if (shapeIsSet[parentIndex]) { glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; - transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; + transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) + * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; finalRotations[i] = finalRotations[parentIndex] * combinedRotation; ++numShapesSet; shapeIsSet[i] = true; From 337bc8b9472accafb34cfdf4e42779f5f23478a5 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 16 May 2014 08:28:36 -0700 Subject: [PATCH 3/3] reuse boolean results rather than recomputing them --- interface/src/avatar/Avatar.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 3ebc9f016f..0a3411f858 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -222,16 +222,14 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { updateShapePositions(); } - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { + if (renderSkeleton) { _skeletonModel.renderJointCollisionShapes(0.7f); } - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes) - && shouldRenderHead(cameraPosition, renderMode)) { + if (renderHead && shouldRenderHead(cameraPosition, renderMode)) { getHead()->getFaceModel().renderJointCollisionShapes(0.7f); } - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes) - && shouldRenderHead(cameraPosition, renderMode)) { + if (renderBounding && shouldRenderHead(cameraPosition, renderMode)) { getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f); _skeletonModel.renderBoundingCollisionShapes(0.7f); }