Merge pull request #1452 from ey6es/master

First cut at orbit behavior (plus ray/capsule intersection test for avatar picking).
This commit is contained in:
Philip Rosedale 2014-01-03 20:02:11 -08:00
commit 607d8332e3
11 changed files with 170 additions and 22 deletions

View file

@ -1105,9 +1105,24 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
}
if (activeWindow() == _window) {
int deltaX = event->x() - _mouseX;
int deltaY = event->y() - _mouseY;
_mouseX = event->x();
_mouseY = event->y();
// orbit behavior
if (_mousePressed && !Menu::getInstance()->isVoxelModeActionChecked()) {
if (_lookatTargetAvatar) {
_myAvatar.orbit(_lookatTargetAvatar->getPosition(), deltaX, deltaY);
return;
}
if (_isHoverVoxel) {
_myAvatar.orbit(glm::vec3(_hoverVoxel.x, _hoverVoxel.y, _hoverVoxel.z) * (float)TREE_SCALE, deltaX, deltaY);
return;
}
}
// detect drag
glm::vec3 mouseVoxelPos(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z);
if (!_justEditedVoxel && mouseVoxelPos != _lastMouseVoxelPos) {
@ -1151,7 +1166,8 @@ void Application::mousePressEvent(QMouseEvent* event) {
}
if (!_palette.isActive() && (!_isHoverVoxel || _lookatTargetAvatar)) {
_pieMenu.mousePressEvent(_mouseX, _mouseY);
// disable for now
// _pieMenu.mousePressEvent(_mouseX, _mouseY);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode) && _pasteMode) {
@ -1952,7 +1968,9 @@ void Application::updateLookatTargetAvatar(const glm::vec3& mouseRayOrigin, cons
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateLookatTargetAvatar()");
_lookatTargetAvatar = findLookatTargetAvatar(mouseRayOrigin, mouseRayDirection, eyePosition, DEFAULT_NODE_ID_REF);
if (!_mousePressed) {
_lookatTargetAvatar = findLookatTargetAvatar(mouseRayOrigin, mouseRayDirection, eyePosition, DEFAULT_NODE_ID_REF);
}
}
Avatar* Application::findLookatTargetAvatar(const glm::vec3& mouseRayOrigin, const glm::vec3& mouseRayDirection,
@ -1961,17 +1979,15 @@ Avatar* Application::findLookatTargetAvatar(const glm::vec3& mouseRayOrigin, con
NodeList* nodeList = NodeList::getInstance();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) {
Avatar* avatar = (Avatar *) node->getLinkedData();
glm::vec3 headPosition = avatar->getHead().getPosition();
Avatar* avatar = (Avatar*)node->getLinkedData();
float distance;
if (rayIntersectsSphere(mouseRayOrigin, mouseRayDirection, headPosition,
HEAD_SPHERE_RADIUS * avatar->getHead().getScale(), distance)) {
if (avatar->findRayIntersection(mouseRayOrigin, mouseRayDirection, distance)) {
// rescale to compensate for head embiggening
eyePosition = (avatar->getHead().calculateAverageEyePosition() - avatar->getHead().getScalePivot()) *
(avatar->getScale() / avatar->getHead().getScale()) + avatar->getHead().getScalePivot();
_lookatIndicatorScale = avatar->getHead().getScale();
_lookatOtherPosition = headPosition;
_lookatOtherPosition = avatar->getHead().getPosition();
nodeUUID = avatar->getOwningNode()->getUUID();
return avatar;
}
@ -2213,7 +2229,7 @@ void Application::updateHoverVoxels(float deltaTime, glm::vec3& mouseRayOrigin,
glm::vec4 oldVoxel(_hoverVoxel.x, _hoverVoxel.y, _hoverVoxel.z, _hoverVoxel.s);
// only do this work if MAKE_SOUND_ON_VOXEL_HOVER or MAKE_SOUND_ON_VOXEL_CLICK is enabled,
// and make sure the tree is not already busy... because otherwise you'll have to wait.
if (!_voxels.treeIsBusy()) {
if (!(_voxels.treeIsBusy() || _mousePressed)) {
{
PerformanceWarning warn(showWarnings, "Application::updateHoverVoxels() _voxels.findRayIntersection()");
_isHoverVoxel = _voxels.findRayIntersection(mouseRayOrigin, mouseRayDirection, _hoverVoxel, distance, face);

View file

@ -190,10 +190,6 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) {
// update avatar skeleton
_skeleton.update(deltaTime, getOrientation(), _position);
// if this is not my avatar, then hand position comes from transmitted data
_skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position = _handPosition;
_hand.simulate(deltaTime, false);
_skeletonModel.simulate(deltaTime);
_head.setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
@ -340,8 +336,24 @@ void Avatar::getSkinColors(glm::vec3& lighter, glm::vec3& darker) {
}
}
bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
float minDistance = FLT_MAX;
float modelDistance;
if (_skeletonModel.findRayIntersection(origin, direction, modelDistance)) {
minDistance = qMin(minDistance, modelDistance);
}
if (_head.getFaceModel().findRayIntersection(origin, direction, modelDistance)) {
minDistance = qMin(minDistance, modelDistance);
}
if (minDistance < FLT_MAX) {
distance = minDistance;
return true;
}
return false;
}
bool Avatar::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
glm::vec3& penetration, int skeletonSkipIndex) {
glm::vec3& penetration, int skeletonSkipIndex) const {
bool didPenetrate = false;
glm::vec3 totalPenetration;
glm::vec3 skeletonPenetration;

View file

@ -160,6 +160,8 @@ public:
void getSkinColors(glm::vec3& lighter, glm::vec3& darker);
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
/// Checks for penetration between the described sphere and the avatar.
/// \param penetratorCenter the center of the penetration test sphere
/// \param penetratorRadius the radius of the penetration test sphere
@ -167,7 +169,7 @@ public:
/// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
/// \return whether or not the sphere penetrated
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
glm::vec3& penetration, int skeletonSkipIndex = -1);
glm::vec3& penetration, int skeletonSkipIndex = -1) const;
virtual int parseData(unsigned char* sourceBuffer, int numBytes);

View file

@ -72,6 +72,7 @@ public:
VideoFace& getVideoFace() { return _videoFace; }
FaceModel& getFaceModel() { return _faceModel; }
const FaceModel& getFaceModel() const { return _faceModel; }
const bool getReturnToCenter() const { return _returnHeadToCenter; } // Do you want head to try to return to center (depends on interface detected)
float getAverageLoudness() const { return _averageLoudness; }

View file

@ -542,6 +542,16 @@ void MyAvatar::loadData(QSettings* settings) {
settings->endGroup();
}
void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
glm::vec3 vector = getPosition() - position;
glm::quat orientation = getOrientation();
glm::vec3 up = orientation * IDENTITY_UP;
const float ANGULAR_SCALE = 0.5f;
glm::quat rotation = glm::angleAxis(deltaX * -ANGULAR_SCALE, up);
const float LINEAR_SCALE = 0.01f;
setPosition(position + rotation * vector + up * (deltaY * LINEAR_SCALE * _scale));
setOrientation(rotation * orientation);
}
float MyAvatar::getAbsoluteHeadYaw() const {
return glm::yaw(_head.getOrientation());
@ -757,15 +767,11 @@ void MyAvatar::updateHandMovementAndTouching(float deltaTime, bool enableHandMov
glm::vec3 farVector = _mouseRayOrigin + pointDirection * (float)TREE_SCALE - shoulderPosition;
const float ARM_RETRACTION = 0.75f;
float retractedLength = _skeletonModel.getRightArmLength() * ARM_RETRACTION;
_skeleton.joint[AVATAR_JOINT_RIGHT_FINGERTIPS].position = shoulderPosition +
glm::normalize(farVector) * retractedLength;
setHandPosition(shoulderPosition + glm::normalize(farVector) * retractedLength);
pointing = true;
}
}
//Set right hand position and state to be transmitted, and also tell AvatarTouch about it
setHandPosition(_skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position);
if (_mousePressed) {
_handState = HAND_STATE_GRASPING;
} else if (pointing) {
@ -919,8 +925,9 @@ void MyAvatar::updateChatCircle(float deltaTime) {
// remove members whose accumulated circles are too far away to influence us
const float CIRCUMFERENCE_PER_MEMBER = 0.5f;
const float CIRCLE_INFLUENCE_SCALE = 2.0f;
const float MIN_RADIUS = 0.3f;
for (int i = sortedAvatars.size() - 1; i >= 0; i--) {
float radius = (CIRCUMFERENCE_PER_MEMBER * (i + 2)) / PI_TIMES_TWO;
float radius = qMax(MIN_RADIUS, (CIRCUMFERENCE_PER_MEMBER * (i + 2)) / PI_TIMES_TWO);
if (glm::distance(_position, sortedAvatars[i].accumulatedCenter) > radius * CIRCLE_INFLUENCE_SCALE) {
sortedAvatars.remove(i);
} else {
@ -931,7 +938,7 @@ void MyAvatar::updateChatCircle(float deltaTime) {
return;
}
center = sortedAvatars.last().accumulatedCenter;
float radius = (CIRCUMFERENCE_PER_MEMBER * (sortedAvatars.size() + 1)) / PI_TIMES_TWO;
float radius = qMax(MIN_RADIUS, (CIRCUMFERENCE_PER_MEMBER * (sortedAvatars.size() + 1)) / PI_TIMES_TWO);
// compute the average up vector
glm::vec3 up = getWorldAlignedOrientation() * IDENTITY_UP;

View file

@ -69,6 +69,8 @@ public:
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
glm::vec3 getThrust() { return _thrust; };
void orbit(const glm::vec3& position, int deltaX, int deltaY);
private:
bool _mousePressed;
float _bodyPitchDelta;

View file

@ -546,6 +546,35 @@ glm::vec4 Model::computeAverageColor() const {
return _geometry ? _geometry->computeAverageColor() : glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
}
bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
const glm::vec3 relativeOrigin = origin - _translation;
const FBXGeometry& geometry = _geometry->getFBXGeometry();
float minDistance = FLT_MAX;
float radiusScale = extractUniformScale(_scale);
for (int i = 0; i < _jointStates.size(); i++) {
const FBXJoint& joint = geometry.joints[i];
glm::vec3 end = extractTranslation(_jointStates[i].transform);
float endRadius = joint.boneRadius * radiusScale;
glm::vec3 start = end;
float startRadius = joint.boneRadius * radiusScale;
if (joint.parentIndex != -1) {
start = extractTranslation(_jointStates[joint.parentIndex].transform);
startRadius = geometry.joints[joint.parentIndex].boneRadius * radiusScale;
}
// for now, use average of start and end radii
float capsuleDistance;
if (findRayCapsuleIntersection(relativeOrigin, direction, start, end,
(startRadius + endRadius) / 2.0f, capsuleDistance)) {
minDistance = qMin(minDistance, capsuleDistance);
}
}
if (minDistance < FLT_MAX) {
distance = minDistance;
return true;
}
return false;
}
bool Model::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
glm::vec3& penetration, float boneScale, int skipIndex) const {
const glm::vec3 relativeCenter = penetratorCenter - _translation;

View file

@ -125,6 +125,8 @@ public:
/// Returns the average color of all meshes in the geometry.
glm::vec4 computeAverageColor() const;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
glm::vec3& penetration, float boneScale = 1.0f, int skipIndex = -1) const;

View file

@ -115,7 +115,7 @@ public:
/// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
/// \return whether or not the sphere penetrated
virtual bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
glm::vec3& penetration, int skeletonSkipIndex = -1) { return false; }
glm::vec3& penetration, int skeletonSkipIndex = -1) const { return false; }
protected:
QUuid _uuid;

View file

@ -154,6 +154,77 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3&
newPenetration - (currentDirection * directionalComponent);
}
bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction,
const glm::vec3& center, float radius, float& distance) {
glm::vec3 relativeOrigin = origin - center;
float c = glm::dot(relativeOrigin, relativeOrigin) - radius * radius;
if (c < 0.0f) {
distance = 0.0f;
return true; // starts inside the sphere
}
float b = glm::dot(direction, relativeOrigin);
float radicand = b * b - c;
if (radicand < 0.0f) {
return false; // doesn't hit the sphere
}
float t = -b - sqrtf(radicand);
if (t < 0.0f) {
return false; // doesn't hit the sphere
}
distance = t;
return true;
}
bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction,
const glm::vec3& start, const glm::vec3& end, float radius, float& distance) {
if (start == end) {
return findRaySphereIntersection(origin, direction, start, radius, distance); // handle degenerate case
}
glm::vec3 relativeOrigin = origin - start;
glm::vec3 relativeEnd = end - start;
float capsuleLength = glm::length(relativeEnd);
relativeEnd /= capsuleLength;
float originProjection = glm::dot(relativeEnd, relativeOrigin);
glm::vec3 constant = relativeOrigin - relativeEnd * originProjection;
float c = glm::dot(constant, constant) - radius * radius;
if (c < 0.0f) { // starts inside cylinder
if (originProjection < 0.0f) { // below start
return findRaySphereIntersection(origin, direction, start, radius, distance);
} else if (originProjection > capsuleLength) { // above end
return findRaySphereIntersection(origin, direction, end, radius, distance);
} else { // between start and end
distance = 0.0f;
return true;
}
}
glm::vec3 coefficient = direction - relativeEnd * glm::dot(relativeEnd, direction);
float a = glm::dot(coefficient, coefficient);
if (a == 0.0f) {
return false; // parallel to enclosing cylinder
}
float b = 2.0f * glm::dot(constant, coefficient);
float radicand = b * b - 4.0f * a * c;
if (radicand < 0.0f) {
return false; // doesn't hit the enclosing cylinder
}
float t = (-b - sqrtf(radicand)) / (2.0f * a);
if (t < 0.0f) {
return false; // doesn't hit the enclosing cylinder
}
glm::vec3 intersection = relativeOrigin + direction * t;
float intersectionProjection = glm::dot(relativeEnd, intersection);
if (intersectionProjection < 0.0f) { // below start
return findRaySphereIntersection(origin, direction, start, radius, distance);
} else if (intersectionProjection > capsuleLength) { // above end
return findRaySphereIntersection(origin, direction, end, radius, distance);
}
distance = t; // between start and end
return true;
}
// Do line segments (r1p1.x, r1p1.y)--(r1p2.x, r1p2.y) and (r2p1.x, r2p1.y)--(r2p2.x, r2p2.y) intersect?
// from: http://ptspts.blogspot.com/2010/06/how-to-determine-if-two-line-segments.html
bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2) {

View file

@ -49,6 +49,12 @@ bool findCapsulePlanePenetration(const glm::vec3& penetratorStart, const glm::ve
glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& newPenetration);
bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction,
const glm::vec3& center, float radius, float& distance);
bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction,
const glm::vec3& start, const glm::vec3& end, float radius, float& distance);
bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2);
bool isOnSegment(float xi, float yi, float xj, float yj, float xk, float yk);
int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk);