// // 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; 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) { } 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) { // Create a voxel at fingertip if controller button is pressed const float FINGERTIP_VOXEL_SIZE = 0.0125; 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(); 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->getVelocity()), 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(); } 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; } } // If hand controller buttons pressed, render stuff as needed if (getPalms().size() > 0) { for (size_t i = 0; i < getPalms().size(); ++i) { PalmData& palm = getPalms()[i]; // If trigger pulled, thrust in that direction and draw beam const float MAX_THRUSTER_BEAM_LENGTH = 5.f; const float THRUSTER_MARKER_SIZE = 0.0125f; if (palm.getJoystickY() != 0.f) { FingerData& finger = palm.getFingers()[0]; if (finger.isActive()) { if (palm.getJoystickY() > 0.f) { glColor3f(0, 1, 0); } else { glColor3f(1, 0, 0); } glm::vec3 palmPosition = palm.getPosition(); glm::vec3 pointerPosition = palmPosition + glm::normalize(finger.getTipPosition() - palmPosition) * MAX_THRUSTER_BEAM_LENGTH; glPushMatrix(); glm::vec3 markerPosition = palmPosition + glm::normalize(finger.getTipPosition() - palmPosition) * MAX_THRUSTER_BEAM_LENGTH * (0.5f + palm.getJoystickY() / 2.f); glTranslatef(markerPosition.x, markerPosition.y, markerPosition.z); glutSolidSphere(THRUSTER_MARKER_SIZE, 10, 10); glPopMatrix(); glLineWidth(2.0); glBegin(GL_LINES); glVertex3f(palmPosition.x, palmPosition.y, palmPosition.z); glVertex3f(pointerPosition.x, pointerPosition.y, pointerPosition.z); glEnd(); } } } } } 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); } } }