// // Hand.cpp // interface // // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. #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 int TOY_BALL_HAND = 1; Hand::Hand(Avatar* owningAvatar) : HandData((AvatarData*)owningAvatar), _owningAvatar(owningAvatar), _renderAlpha(1.0), _ballColor(0.0, 0.0, 0.4), _collisionCenter(0,0,0), _collisionAge(0), _collisionDuration(0), _toyBallPosition(0), _toyBallVelocity(0), _toyBallInHand(false), _pitchUpdate(0) { } void Hand::init() { // Different colors for my hand and others' hands if (_owningAvatar && _owningAvatar->getOwningNode() == NULL) { _ballColor = glm::vec3(0.0, 0.4, 0.0); } else { _ballColor = glm::vec3(0.0, 0.0, 0.4); } } void Hand::reset() { } void Hand::simulate(float deltaTime, bool isMine) { if (_collisionAge > 0.f) { _collisionAge += deltaTime; } const glm::vec3 leapHandsOffsetFromFace(0.0, -0.2, -0.3); // place the hand in front of the face where we can see it Head& head = _owningAvatar->getHead(); _baseOrientation = _owningAvatar->getOrientation(); _basePosition = head.calculateAverageEyePosition() + _baseOrientation * leapHandsOffsetFromFace * head.getScale(); if (isMine) { updateCollisions(); } calculateGeometry(); if (isMine) { const float FINGERTIP_VOXEL_SIZE = 0.0125; // Iterate hand controllers, take actions as needed for (size_t i = 0; i < getNumPalms(); ++i) { PalmData& palm = getPalms()[i]; if (palm.isActive()) { FingerData& finger = palm.getFingers()[0]; // Sixense has only one finger glm::vec3 fingerTipPosition = finger.getTipPosition(); // Toy ball game if (palm.getSixenseID() == TOY_BALL_HAND) { if (palm.getControllerButtons() & BUTTON_FWD) { // If grabbing toy ball, add forces to it if (!_toyBallInHand) { // Test for whether close enough to catch and catch bool isCaught = true; if (isCaught) { _toyBallInHand = true; } } if (_toyBallInHand) { // Ball is in hand _toyBallPosition = fingerTipPosition; _toyBallVelocity = glm::vec3(0); } } else { // If toy ball just released, add velocity to it! if (_toyBallInHand) { _toyBallInHand = false; glm::vec3 handVelocity = palm.getRawVelocity(); glm::vec3 fingerTipVelocity = palm.getTipVelocity(); glm::quat avatarRotation = _owningAvatar->getOrientation(); //printVector(avatarRotation * handVelocity); _toyBallVelocity += avatarRotation * fingerTipVelocity; } } // Simulate toy ball const glm::vec3 TOYBALL_GRAVITY (0, -1, 0); _toyBallPosition += _toyBallVelocity * deltaTime; if (!_toyBallInHand) { _toyBallVelocity += TOYBALL_GRAVITY * deltaTime; } _toyBallVelocity *= 0.99f; } if (palm.getControllerButtons() & BUTTON_1) { if (glm::length(fingerTipPosition - _lastFingerAddVoxel) > (FINGERTIP_VOXEL_SIZE / 2.f)) { QColor paintColor = Menu::getInstance()->getActionForOption(MenuOption::VoxelPaintColor)->data().value(); Application::getInstance()->makeVoxel(fingerTipPosition, FINGERTIP_VOXEL_SIZE, paintColor.red(), paintColor.green(), paintColor.blue(), true); _lastFingerAddVoxel = fingerTipPosition; } } else if (palm.getControllerButtons() & BUTTON_2) { if (glm::length(fingerTipPosition - _lastFingerDeleteVoxel) > (FINGERTIP_VOXEL_SIZE / 2.f)) { Application::getInstance()->removeVoxel(fingerTipPosition, FINGERTIP_VOXEL_SIZE); _lastFingerDeleteVoxel = fingerTipPosition; } } // Check if the finger is intersecting with a voxel in the client voxel tree VoxelTreeElement* fingerNode = Application::getInstance()->getVoxels()->getVoxelEnclosing( glm::vec3(fingerTipPosition / (float)TREE_SCALE)); if (fingerNode) { if (!palm.getIsCollidingWithVoxel()) { // Collision has just started palm.setIsCollidingWithVoxel(true); handleVoxelCollision(&palm, fingerTipPosition, fingerNode, deltaTime); // Set highlight voxel VoxelDetail voxel; glm::vec3 pos = fingerNode->getCorner(); voxel.x = pos.x; voxel.y = pos.y; voxel.z = pos.z; voxel.s = fingerNode->getScale(); voxel.red = fingerNode->getColor()[0]; voxel.green = fingerNode->getColor()[1]; voxel.blue = fingerNode->getColor()[2]; Application::getInstance()->setHighlightVoxel(voxel); Application::getInstance()->setIsHighlightVoxel(true); } } else { if (palm.getIsCollidingWithVoxel()) { // Collision has just ended palm.setIsCollidingWithVoxel(false); Application::getInstance()->setIsHighlightVoxel(false); } } } } } } const float PALM_COLLISION_RADIUS = 0.03f; void Hand::updateCollisions() { // use position to obtain the left and right palm indices int leftPalmIndex, rightPalmIndex; getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex); // check for collisions for (int i = 0; i < getNumPalms(); i++) { PalmData& palm = getPalms()[i]; if (!palm.isActive()) { continue; } float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale(); glm::vec3 totalPenetration; // check other avatars NodeList* nodeList = NodeList::getInstance(); for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { if (node->getLinkedData() && node->getType() == NODE_TYPE_AGENT) { Avatar* otherAvatar = (Avatar*)node->getLinkedData(); glm::vec3 avatarPenetration; if (otherAvatar->findSpherePenetration(palm.getPosition(), scaledPalmRadius, avatarPenetration)) { totalPenetration = addPenetrations(totalPenetration, avatarPenetration); } } } // and the current avatar (ignoring everything below the parent of the parent of the last free joint) glm::vec3 owningPenetration; const Model& skeletonModel = _owningAvatar->getSkeletonModel(); int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex( skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() : (i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1))); if (_owningAvatar->findSpherePenetration(palm.getPosition(), scaledPalmRadius, owningPenetration, skipIndex)) { totalPenetration = addPenetrations(totalPenetration, owningPenetration); } // un-penetrate palm.addToPosition(-totalPenetration); } } void Hand::handleVoxelCollision(PalmData* palm, const glm::vec3& fingerTipPosition, VoxelTreeElement* voxel, float deltaTime) { // Collision between finger and a voxel plays sound const float LOWEST_FREQUENCY = 100.f; const float HERTZ_PER_RGB = 3.f; const float DECAY_PER_SAMPLE = 0.0005f; const float DURATION_MAX = 2.0f; const float MIN_VOLUME = 0.1f; float volume = MIN_VOLUME + glm::clamp(glm::length(palm->getRawVelocity()), 0.f, (1.f - MIN_VOLUME)); float duration = volume; _collisionCenter = fingerTipPosition; _collisionAge = deltaTime; _collisionDuration = duration; int voxelBrightness = voxel->getColor()[0] + voxel->getColor()[1] + voxel->getColor()[2]; float frequency = LOWEST_FREQUENCY + (voxelBrightness * HERTZ_PER_RGB); Application::getInstance()->getAudio()->startDrumSound(volume, frequency, DURATION_MAX, DECAY_PER_SAMPLE); } void Hand::calculateGeometry() { // generate finger tip balls.... _leapFingerTipBalls.clear(); 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()) { const float standardBallRadius = 0.010f; _leapFingerTipBalls.resize(_leapFingerTipBalls.size() + 1); HandBall& ball = _leapFingerTipBalls.back(); ball.rotation = _baseOrientation; ball.position = finger.getTipPosition(); ball.radius = standardBallRadius; ball.touchForce = 0.0; ball.isCollidable = true; ball.isColliding = false; } } } } // generate finger root balls.... _leapFingerRootBalls.clear(); 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()) { const float standardBallRadius = 0.005f; _leapFingerRootBalls.resize(_leapFingerRootBalls.size() + 1); HandBall& ball = _leapFingerRootBalls.back(); ball.rotation = _baseOrientation; ball.position = finger.getRootPosition(); ball.radius = standardBallRadius; ball.touchForce = 0.0; ball.isCollidable = true; ball.isColliding = false; } } } } } void Hand::render( bool isMine) { _renderAlpha = 1.0; if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) { for (int 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 (Menu::getInstance()->isOptionChecked(MenuOption::DisplayLeapHands)) { renderLeapHands(); } // Render toy ball if (isMine) { glPushMatrix(); const float TOY_BALL_RADIUS = 0.05f; glColor3f(1, 0, 0); glTranslatef(_toyBallPosition.x, _toyBallPosition.y, _toyBallPosition.z); glutSolidSphere(TOY_BALL_RADIUS, 10, 10); glPopMatrix(); } if (isMine) { // If hand/voxel collision has happened, render a little expanding sphere if (_collisionAge > 0.f) { float opacity = glm::clamp(1.f - (_collisionAge / _collisionDuration), 0.f, 1.f); glColor4f(1, 0, 0, 0.5 * opacity); glPushMatrix(); glTranslatef(_collisionCenter.x, _collisionCenter.y, _collisionCenter.z); glutSolidSphere(_collisionAge * 0.25f, 20, 20); glPopMatrix(); if (_collisionAge > _collisionDuration) { _collisionAge = 0.f; } } } glEnable(GL_DEPTH_TEST); glEnable(GL_RESCALE_NORMAL); } void Hand::renderLeapHands() { const float alpha = 1.0f; //const glm::vec3 handColor = _ballColor; const glm::vec3 handColor(1.0, 0.84, 0.66); // use the skin color glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); glPushMatrix(); // Draw the leap balls for (size_t i = 0; i < _leapFingerTipBalls.size(); i++) { if (alpha > 0.0f) { if (_leapFingerTipBalls[i].isColliding) { glColor4f(handColor.r, 0, 0, alpha); } else { glColor4f(handColor.r, handColor.g, handColor.b, alpha); } glPushMatrix(); glTranslatef(_leapFingerTipBalls[i].position.x, _leapFingerTipBalls[i].position.y, _leapFingerTipBalls[i].position.z); glutSolidSphere(_leapFingerTipBalls[i].radius, 20.0f, 20.0f); glPopMatrix(); } } // Draw the finger root cones 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, 0.5); glm::vec3 tip = finger.getTipPosition(); glm::vec3 root = finger.getRootPosition(); Avatar::renderJointConnectingCone(root, tip, 0.001, 0.003); } } } } // Draw the palms for (size_t i = 0; i < getNumPalms(); ++i) { PalmData& palm = getPalms()[i]; if (palm.isActive()) { const float palmThickness = 0.02f; glColor4f(handColor.r, handColor.g, handColor.b, 0.25); glm::vec3 tip = palm.getPosition(); glm::vec3 root = palm.getPosition() + palm.getNormal() * palmThickness; Avatar::renderJointConnectingCone(root, tip, 0.05, 0.03); } } glDepthMask(GL_TRUE); glEnable(GL_DEPTH_TEST); glPopMatrix(); } void Hand::setLeapHands(const std::vector& handPositions, const std::vector& handNormals) { for (size_t i = 0; i < getNumPalms(); ++i) { PalmData& palm = getPalms()[i]; if (i < handPositions.size()) { palm.setActive(true); palm.setRawPosition(handPositions[i]); palm.setRawNormal(handNormals[i]); } else { palm.setActive(false); } } }