void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { // simple frustum check float boundingRadius = getBillboardSize(); ViewFrustum* frustum = (renderMode == Avatar::SHADOW_RENDER_MODE) ? Application::getInstance()->getShadowViewFrustum() : Application::getInstance()->getViewFrustum(); if (frustum->sphereInFrustum(_position, boundingRadius) == ViewFrustum::OUTSIDE) { return; } glm::vec3 toTarget = cameraPosition - Application::getInstance()->getAvatar()->getPosition(); float distanceToTarget = glm::length(toTarget); { // glow when moving far away const float GLOW_DISTANCE = 20.0f; const float GLOW_MAX_LOUDNESS = 2500.0f; const float MAX_GLOW = 0.5f; float GLOW_FROM_AVERAGE_LOUDNESS = ((this == Application::getInstance()->getAvatar()) ? 0.0f : MAX_GLOW * getHeadData()->getAudioLoudness() / GLOW_MAX_LOUDNESS); if (!Menu::getInstance()->isOptionChecked(MenuOption::GlowWhenSpeaking)) { GLOW_FROM_AVERAGE_LOUDNESS = 0.0f; } float glowLevel = _moving && distanceToTarget > GLOW_DISTANCE && renderMode == NORMAL_RENDER_MODE ? 1.0f : GLOW_FROM_AVERAGE_LOUDNESS; // render body 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(); getHead()->getFaceModel().renderJointCollisionShapes(0.7f); } } if (renderMode != SHADOW_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) { if (shouldRenderHead(cameraPosition, renderMode)) { getHead()->getFaceModel().updateShapePositions(); 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(); } // quick check before falling into the code below: // (a 10 degree breadth of an almost 2 meter avatar kicks in at about 12m) const float MIN_VOICE_SPHERE_DISTANCE = 12.0f; if (distanceToTarget > MIN_VOICE_SPHERE_DISTANCE) { // render voice intensity sphere for avatars that are farther away const float MAX_SPHERE_ANGLE = 10.0f * RADIANS_PER_DEGREE; const float MIN_SPHERE_ANGLE = 1.0f * RADIANS_PER_DEGREE; const float MIN_SPHERE_SIZE = 0.01f; const float SPHERE_LOUDNESS_SCALING = 0.0005f; const float SPHERE_COLOR[] = { 0.5f, 0.8f, 0.8f }; float height = getSkeletonHeight(); glm::vec3 delta = height * (getHead()->getCameraOrientation() * IDENTITY_UP) / 2.0f; float angle = abs(angleBetween(toTarget + delta, toTarget - delta)); float sphereRadius = getHead()->getAverageLoudness() * SPHERE_LOUDNESS_SCALING; if (renderMode == NORMAL_RENDER_MODE && (sphereRadius > MIN_SPHERE_SIZE) && (angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) { glColor4f(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.0f - angle / MAX_SPHERE_ANGLE); glPushMatrix(); glTranslatef(_position.x, _position.y, _position.z); glScalef(height, height, height); glutSolidSphere(sphereRadius, 15, 15); glPopMatrix(); } } } const float DISPLAYNAME_DISTANCE = 10.0f; setShowDisplayName(renderMode == NORMAL_RENDER_MODE && distanceToTarget < DISPLAYNAME_DISTANCE); if (renderMode != NORMAL_RENDER_MODE || (isMyAvatar() && Application::getInstance()->getCamera()->getMode() == CAMERA_MODE_FIRST_PERSON)) { return; } renderDisplayName(); if (!_chatMessage.empty()) { int width = 0; int lastWidth = 0; for (string::iterator it = _chatMessage.begin(); it != _chatMessage.end(); it++) { width += (lastWidth = textRenderer(CHAT)->computeWidth(*it)); } glPushMatrix(); glm::vec3 chatPosition = getHead()->getEyePosition() + getBodyUpDirection() * CHAT_MESSAGE_HEIGHT * _scale; glTranslatef(chatPosition.x, chatPosition.y, chatPosition.z); glm::quat chatRotation = Application::getInstance()->getCamera()->getRotation(); glm::vec3 chatAxis = glm::axis(chatRotation); glRotatef(glm::degrees(glm::angle(chatRotation)), chatAxis.x, chatAxis.y, chatAxis.z); glColor3f(0.0f, 0.8f, 0.0f); glRotatef(180.0f, 0.0f, 1.0f, 0.0f); glRotatef(180.0f, 0.0f, 0.0f, 1.0f); glScalef(_scale * CHAT_MESSAGE_SCALE, _scale * CHAT_MESSAGE_SCALE, 1.0f); glDisable(GL_LIGHTING); glDepthMask(false); if (_keyState == NO_KEY_DOWN) { textRenderer(CHAT)->draw(-width / 2.0f, 0, _chatMessage.c_str()); } else { // rather than using substr and allocating a new string, just replace the last // character with a null, then restore it int lastIndex = _chatMessage.size() - 1; char lastChar = _chatMessage[lastIndex]; _chatMessage[lastIndex] = '\0'; textRenderer(CHAT)->draw(-width / 2.0f, 0, _chatMessage.c_str()); _chatMessage[lastIndex] = lastChar; glColor3f(0.0f, 1.0f, 0.0f); textRenderer(CHAT)->draw(width / 2.0f - lastWidth, 0, _chatMessage.c_str() + lastIndex); } glEnable(GL_LIGHTING); glDepthMask(true); glPopMatrix(); } }
void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode, bool postLighting) { if (_referential) { _referential->update(); } if (postLighting && glm::distance(Application::getInstance()->getAvatar()->getPosition(), _position) < 10.0f) { auto geometryCache = DependencyManager::get<GeometryCache>(); // render pointing lasers glm::vec3 laserColor = glm::vec3(1.0f, 0.0f, 1.0f); float laserLength = 50.0f; glm::vec3 position; glm::quat rotation; bool havePosition, haveRotation; if (_handState & LEFT_HAND_POINTING_FLAG) { if (_handState & IS_FINGER_POINTING_FLAG) { int leftIndexTip = getJointIndex("LeftHandIndex4"); int leftIndexTipJoint = getJointIndex("LeftHandIndex3"); havePosition = _skeletonModel.getJointPositionInWorldFrame(leftIndexTip, position); haveRotation = _skeletonModel.getJointRotationInWorldFrame(leftIndexTipJoint, rotation); } else { int leftHand = _skeletonModel.getLeftHandJointIndex(); havePosition = _skeletonModel.getJointPositionInWorldFrame(leftHand, position); haveRotation = _skeletonModel.getJointRotationInWorldFrame(leftHand, rotation); } if (havePosition && haveRotation) { glPushMatrix(); { glTranslatef(position.x, position.y, position.z); float angle = glm::degrees(glm::angle(rotation)); glm::vec3 axis = glm::axis(rotation); glRotatef(angle, axis.x, axis.y, axis.z); glColor3f(laserColor.x, laserColor.y, laserColor.z); geometryCache->renderLine(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f)); } glPopMatrix(); } } if (_handState & RIGHT_HAND_POINTING_FLAG) { if (_handState & IS_FINGER_POINTING_FLAG) { int rightIndexTip = getJointIndex("RightHandIndex4"); int rightIndexTipJoint = getJointIndex("RightHandIndex3"); havePosition = _skeletonModel.getJointPositionInWorldFrame(rightIndexTip, position); haveRotation = _skeletonModel.getJointRotationInWorldFrame(rightIndexTipJoint, rotation); } else { int rightHand = _skeletonModel.getRightHandJointIndex(); havePosition = _skeletonModel.getJointPositionInWorldFrame(rightHand, position); haveRotation = _skeletonModel.getJointRotationInWorldFrame(rightHand, rotation); } if (havePosition && haveRotation) { glPushMatrix(); { glTranslatef(position.x, position.y, position.z); float angle = glm::degrees(glm::angle(rotation)); glm::vec3 axis = glm::axis(rotation); glRotatef(angle, axis.x, axis.y, axis.z); glColor3f(laserColor.x, laserColor.y, laserColor.z); geometryCache->renderLine(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f)); } glPopMatrix(); } } } // simple frustum check float boundingRadius = getBillboardSize(); ViewFrustum* frustum = (renderMode == Avatar::SHADOW_RENDER_MODE) ? Application::getInstance()->getShadowViewFrustum() : Application::getInstance()->getViewFrustum(); if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) { return; } glm::vec3 toTarget = cameraPosition - getPosition(); float distanceToTarget = glm::length(toTarget); { // glow when moving far away const float GLOW_DISTANCE = 20.0f; const float GLOW_MAX_LOUDNESS = 2500.0f; const float MAX_GLOW = 0.5f; float GLOW_FROM_AVERAGE_LOUDNESS = ((this == Application::getInstance()->getAvatar()) ? 0.0f : MAX_GLOW * getHeadData()->getAudioLoudness() / GLOW_MAX_LOUDNESS); if (!Menu::getInstance()->isOptionChecked(MenuOption::GlowWhenSpeaking)) { GLOW_FROM_AVERAGE_LOUDNESS = 0.0f; } float glowLevel = _moving && distanceToTarget > GLOW_DISTANCE && renderMode == NORMAL_RENDER_MODE ? 1.0f : GLOW_FROM_AVERAGE_LOUDNESS; // render body if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { renderBody(renderMode, postLighting, glowLevel); } if (!postLighting && renderMode != SHADOW_RENDER_MODE) { // add local lights const float BASE_LIGHT_DISTANCE = 2.0f; const float LIGHT_EXPONENT = 1.0f; const float LIGHT_CUTOFF = glm::radians(80.0f); float distance = BASE_LIGHT_DISTANCE * _scale; glm::vec3 position = glm::mix(_skeletonModel.getTranslation(), getHead()->getFaceModel().getTranslation(), 0.9f); glm::quat orientation = getOrientation(); foreach (const AvatarManager::LocalLight& light, Application::getInstance()->getAvatarManager().getLocalLights()) { glm::vec3 direction = orientation * light.direction; DependencyManager::get<DeferredLightingEffect>()->addSpotLight(position - direction * distance, distance * 2.0f, glm::vec3(), light.color, light.color, 1.0f, 0.5f, 0.0f, direction, LIGHT_EXPONENT, LIGHT_CUTOFF); } } if (postLighting) { bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes); bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes); bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes); if (renderSkeleton) { _skeletonModel.renderJointCollisionShapes(0.7f); } if (renderHead && shouldRenderHead(cameraPosition, renderMode)) { getHead()->getFaceModel().renderJointCollisionShapes(0.7f); } if (renderBounding && shouldRenderHead(cameraPosition, renderMode)) { _skeletonModel.renderBoundingCollisionShapes(0.7f); } // If this is the avatar being looked at, render a little ball above their head if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::RenderFocusIndicator)) { const float LOOK_AT_INDICATOR_RADIUS = 0.03f; const float LOOK_AT_INDICATOR_OFFSET = 0.22f; const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.75f }; glPushMatrix(); glColor4fv(LOOK_AT_INDICATOR_COLOR); if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) { glTranslatef(_position.x, getDisplayNamePosition().y, _position.z); } else { glTranslatef(_position.x, getDisplayNamePosition().y + LOOK_AT_INDICATOR_OFFSET, _position.z); } DependencyManager::get<GeometryCache>()->renderSphere(LOOK_AT_INDICATOR_RADIUS, 15, 15); glPopMatrix(); } } // quick check before falling into the code below: // (a 10 degree breadth of an almost 2 meter avatar kicks in at about 12m) const float MIN_VOICE_SPHERE_DISTANCE = 12.0f; if (postLighting && Menu::getInstance()->isOptionChecked(MenuOption::BlueSpeechSphere) && distanceToTarget > MIN_VOICE_SPHERE_DISTANCE) { // render voice intensity sphere for avatars that are farther away const float MAX_SPHERE_ANGLE = 10.0f * RADIANS_PER_DEGREE; const float MIN_SPHERE_ANGLE = 0.5f * RADIANS_PER_DEGREE; const float MIN_SPHERE_SIZE = 0.01f; const float SPHERE_LOUDNESS_SCALING = 0.0005f; const float SPHERE_COLOR[] = { 0.5f, 0.8f, 0.8f }; float height = getSkeletonHeight(); glm::vec3 delta = height * (getHead()->getCameraOrientation() * IDENTITY_UP) / 2.0f; float angle = abs(angleBetween(toTarget + delta, toTarget - delta)); float sphereRadius = getHead()->getAverageLoudness() * SPHERE_LOUDNESS_SCALING; if (renderMode == NORMAL_RENDER_MODE && (sphereRadius > MIN_SPHERE_SIZE) && (angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) { glColor4f(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.0f - angle / MAX_SPHERE_ANGLE); glPushMatrix(); glTranslatef(_position.x, _position.y, _position.z); glScalef(height, height, height); DependencyManager::get<GeometryCache>()->renderSphere(sphereRadius, 15, 15); glPopMatrix(); } } } const float DISPLAYNAME_DISTANCE = 20.0f; setShowDisplayName(renderMode == NORMAL_RENDER_MODE && distanceToTarget < DISPLAYNAME_DISTANCE); if (!postLighting || renderMode != NORMAL_RENDER_MODE || (isMyAvatar() && Application::getInstance()->getCamera()->getMode() == CAMERA_MODE_FIRST_PERSON)) { return; } renderDisplayName(); }