// // Hand.cpp // interface/src/avatar // // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include #include #include #include "Application.h" #include "Avatar.h" #include "Hand.h" #include "Menu.h" #include "Util.h" #include "renderer/ProgramObject.h" using namespace std; const float FINGERTIP_COLLISION_RADIUS = 0.01f; const float PALM_COLLISION_RADIUS = 0.03f; Hand::Hand(Avatar* owningAvatar) : HandData((AvatarData*)owningAvatar), _owningAvatar(owningAvatar) { } void Hand::simulate(float deltaTime, bool isMine) { if (isMine) { // Iterate hand controllers, take actions as needed for (size_t i = 0; i < getNumPalms(); ++i) { PalmData& palm = getPalms()[i]; palm.setLastControllerButtons(palm.getControllerButtons()); } } } // We create a static CollisionList that is recycled for each collision test. const float MAX_COLLISIONS_PER_AVATAR = 32; static CollisionList handCollisions(MAX_COLLISIONS_PER_AVATAR); void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) { if (!avatar || avatar == _owningAvatar) { // don't collide hands against ourself (that is done elsewhere) return; } const SkeletonModel& skeletonModel = _owningAvatar->getSkeletonModel(); int jointIndices[2]; jointIndices[0] = skeletonModel.getLeftHandJointIndex(); jointIndices[1] = skeletonModel.getRightHandJointIndex(); for (size_t i = 0; i < 2; i++) { int jointIndex = jointIndices[i]; if (jointIndex < 0) { continue; } handCollisions.clear(); QVector shapes; skeletonModel.getHandShapes(jointIndex, shapes); if (avatar->findCollisions(shapes, handCollisions)) { glm::vec3 totalPenetration(0.f); glm::vec3 averageContactPoint; for (int j = 0; j < handCollisions.size(); ++j) { CollisionInfo* collision = handCollisions.getCollision(j); totalPenetration += collision->_penetration; averageContactPoint += collision->_contactPoint; } if (isMyHand) { // our hand against other avatar // TODO: resolve this penetration when we don't think the other avatar will yield //palm.addToPenetration(averagePenetration); } else { // someone else's hand against MyAvatar // TODO: submit collision info to MyAvatar which should lean accordingly averageContactPoint /= (float)handCollisions.size(); avatar->applyCollision(averageContactPoint, totalPenetration); CollisionInfo collision; collision._penetration = totalPenetration; collision._contactPoint = averageContactPoint; emit avatar->collisionWithAvatar(avatar->getSessionUUID(), _owningAvatar->getSessionUUID(), collision); } } } } void Hand::collideAgainstOurself() { if (!Menu::getInstance()->isOptionChecked(MenuOption::HandsCollideWithSelf)) { return; } int leftPalmIndex, rightPalmIndex; getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex); float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale(); const Model& skeletonModel = _owningAvatar->getSkeletonModel(); for (int i = 0; i < int(getNumPalms()); i++) { PalmData& palm = getPalms()[i]; if (!palm.isActive()) { continue; } // ignoring everything below the parent of the parent of the last free joint int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex( skeletonModel.getLastFreeJointIndex((int(i) == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() : (int(i) == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1))); handCollisions.clear(); if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions, skipIndex)) { glm::vec3 totalPenetration; for (int j = 0; j < handCollisions.size(); ++j) { CollisionInfo* collision = handCollisions.getCollision(j); totalPenetration = addPenetrations(totalPenetration, collision->_penetration); } // resolve penetration palm.addToPenetration(totalPenetration); } } } void Hand::resolvePenetrations() { for (size_t i = 0; i < getNumPalms(); ++i) { PalmData& palm = getPalms()[i]; palm.resolvePenetrations(); } } void Hand::render(bool isMine, Model::RenderMode renderMode) { if (renderMode != Model::SHADOW_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { // draw a green sphere at hand joint location, which is actually near the wrist) for (size_t i = 0; i < getNumPalms(); i++) { PalmData& palm = getPalms()[i]; if (!palm.isActive()) { continue; } glm::vec3 position = palm.getPosition(); glPushMatrix(); glTranslatef(position.x, position.y, position.z); glColor3f(0.0f, 1.0f, 0.0f); glutSolidSphere(PALM_COLLISION_RADIUS * _owningAvatar->getScale(), 10, 10); glPopMatrix(); } } if (renderMode != Model::SHADOW_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHands)) { renderHandTargets(isMine); } glEnable(GL_DEPTH_TEST); glEnable(GL_RESCALE_NORMAL); } void Hand::renderHandTargets(bool isMine) { const float alpha = 1.0f; const glm::vec3 handColor(1.0, 0.0, 0.0); // Color the hand targets red to be different than skin glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); if (isMine && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHandTargets)) { for (size_t i = 0; i < getNumPalms(); ++i) { PalmData& palm = getPalms()[i]; if (!palm.isActive()) { continue; } glm::vec3 targetPosition; palm.getBallHoldPosition(targetPosition); glPushMatrix(); glTranslatef(targetPosition.x, targetPosition.y, targetPosition.z); const float collisionRadius = 0.05f; glColor4f(0.5f,0.5f,0.5f, alpha); glutWireSphere(collisionRadius, 10.f, 10.f); glPopMatrix(); } } const float PALM_BALL_RADIUS = 0.03f; const float PALM_DISK_RADIUS = 0.06f; const float PALM_DISK_THICKNESS = 0.01f; const float PALM_FINGER_ROD_RADIUS = 0.003f; // Draw the palm ball and disk for (size_t i = 0; i < getNumPalms(); ++i) { PalmData& palm = getPalms()[i]; if (palm.isActive()) { for (size_t f = 0; f < palm.getNumFingers(); ++f) { FingerData& finger = palm.getFingers()[f]; if (finger.isActive()) { glColor4f(handColor.r, handColor.g, handColor.b, alpha); glm::vec3 tip = finger.getTipPosition(); glm::vec3 root = finger.getRootPosition(); Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS); // Render sphere at palm/finger root glm::vec3 palmNormal = root + palm.getNormal() * PALM_DISK_THICKNESS; Avatar::renderJointConnectingCone(root, palmNormal, PALM_DISK_RADIUS, 0.0f); glPushMatrix(); glTranslatef(root.x, root.y, root.z); glutSolidSphere(PALM_BALL_RADIUS, 20.0f, 20.0f); glPopMatrix(); } } } } /* // Draw the hand paddles int MAX_NUM_PADDLES = 2; // one for left and one for right glColor4f(handColor.r, handColor.g, handColor.b, 0.3f); for (int i = 0; i < MAX_NUM_PADDLES; i++) { const PalmData* palm = getPalm(i); if (palm) { // compute finger axis glm::vec3 fingerAxis(0.f); for (size_t f = 0; f < palm->getNumFingers(); ++f) { const FingerData& finger = (palm->getFingers())[f]; if (finger.isActive()) { glm::vec3 fingerTip = finger.getTipPosition(); glm::vec3 fingerRoot = finger.getRootPosition(); fingerAxis = glm::normalize(fingerTip - fingerRoot); break; } } // compute paddle position glm::vec3 handPosition; if (i == SIXENSE_CONTROLLER_ID_LEFT_HAND) { _owningAvatar->getSkeletonModel().getLeftHandPosition(handPosition); } else if (i == SIXENSE_CONTROLLER_ID_RIGHT_HAND) { _owningAvatar->getSkeletonModel().getRightHandPosition(handPosition); } glm::vec3 tip = handPosition + HAND_PADDLE_OFFSET * fingerAxis; glm::vec3 root = tip + palm->getNormal() * HAND_PADDLE_THICKNESS; // render a very shallow cone as the paddle Avatar::renderJointConnectingCone(root, tip, HAND_PADDLE_RADIUS, 0.f); } } */ glDepthMask(GL_TRUE); glEnable(GL_DEPTH_TEST); glPopMatrix(); }