mirror of
https://github.com/overte-org/overte.git
synced 2025-04-29 16:02:43 +02:00
720 lines
26 KiB
C++
720 lines
26 KiB
C++
//
|
|
// Avatar.cpp
|
|
// interface
|
|
//
|
|
// Created by Philip Rosedale on 9/11/12.
|
|
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
|
|
|
#include <vector>
|
|
|
|
#include <glm/glm.hpp>
|
|
#include <glm/gtx/quaternion.hpp>
|
|
#include <glm/gtx/vector_angle.hpp>
|
|
|
|
#include <NodeList.h>
|
|
#include <PacketHeaders.h>
|
|
#include <SharedUtil.h>
|
|
|
|
#include <GeometryUtil.h>
|
|
|
|
#include "Application.h"
|
|
#include "Avatar.h"
|
|
#include "Hand.h"
|
|
#include "Head.h"
|
|
#include "Menu.h"
|
|
#include "Physics.h"
|
|
#include "world.h"
|
|
#include "devices/OculusManager.h"
|
|
#include "renderer/TextureCache.h"
|
|
#include "ui/TextRenderer.h"
|
|
|
|
using namespace std;
|
|
|
|
const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f);
|
|
const float YAW_MAG = 500.0f;
|
|
const float MY_HAND_HOLDING_PULL = 0.2f;
|
|
const float YOUR_HAND_HOLDING_PULL = 1.0f;
|
|
const float BODY_SPRING_DEFAULT_TIGHTNESS = 1000.0f;
|
|
const float BODY_SPRING_FORCE = 300.0f;
|
|
const float BODY_SPRING_DECAY = 16.0f;
|
|
const float COLLISION_RADIUS_SCALAR = 1.2f; // pertains to avatar-to-avatar collisions
|
|
const float COLLISION_BODY_FORCE = 30.0f; // pertains to avatar-to-avatar collisions
|
|
const float HEAD_ROTATION_SCALE = 0.70f;
|
|
const float HEAD_ROLL_SCALE = 0.40f;
|
|
const float HEAD_MAX_PITCH = 45;
|
|
const float HEAD_MIN_PITCH = -45;
|
|
const float HEAD_MAX_YAW = 85;
|
|
const float HEAD_MIN_YAW = -85;
|
|
const float AVATAR_BRAKING_STRENGTH = 40.0f;
|
|
const float MOUSE_RAY_TOUCH_RANGE = 0.01f;
|
|
const float FLOATING_HEIGHT = 0.13f;
|
|
const bool USING_HEAD_LEAN = false;
|
|
const float LEAN_SENSITIVITY = 0.15f;
|
|
const float LEAN_MAX = 0.45f;
|
|
const float LEAN_AVERAGING = 10.0f;
|
|
const float HEAD_RATE_MAX = 50.f;
|
|
const int NUM_BODY_CONE_SIDES = 9;
|
|
const float CHAT_MESSAGE_SCALE = 0.0015f;
|
|
const float CHAT_MESSAGE_HEIGHT = 0.1f;
|
|
const float DISPLAYNAME_FADE_TIME = 0.5f;
|
|
const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME);
|
|
const float DISPLAYNAME_ALPHA = 0.95f;
|
|
const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f;
|
|
|
|
Avatar::Avatar() :
|
|
AvatarData(),
|
|
_skeletonModel(this),
|
|
_bodyYawDelta(0.0f),
|
|
_mode(AVATAR_MODE_STANDING),
|
|
_velocity(0.0f, 0.0f, 0.0f),
|
|
_thrust(0.0f, 0.0f, 0.0f),
|
|
_speed(0.0f),
|
|
_leanScale(0.5f),
|
|
_scale(1.0f),
|
|
_worldUpDirection(DEFAULT_UP_DIRECTION),
|
|
_mouseRayOrigin(0.0f, 0.0f, 0.0f),
|
|
_mouseRayDirection(0.0f, 0.0f, 0.0f),
|
|
_moving(false),
|
|
_owningAvatarMixer(),
|
|
_collisionFlags(0),
|
|
_initialized(false)
|
|
{
|
|
// we may have been created in the network thread, but we live in the main thread
|
|
moveToThread(Application::getInstance()->thread());
|
|
|
|
// give the pointer to our head to inherited _headData variable from AvatarData
|
|
_headData = static_cast<HeadData*>(new Head(this));
|
|
_handData = static_cast<HandData*>(new Hand(this));
|
|
}
|
|
|
|
Avatar::~Avatar() {
|
|
}
|
|
|
|
void Avatar::init() {
|
|
getHead()->init();
|
|
getHand()->init();
|
|
_skeletonModel.init();
|
|
_initialized = true;
|
|
}
|
|
|
|
glm::vec3 Avatar::getChestPosition() const {
|
|
// for now, let's just assume that the "chest" is halfway between the root and the neck
|
|
glm::vec3 neckPosition;
|
|
return _skeletonModel.getNeckPosition(neckPosition) ? (_position + neckPosition) * 0.5f : _position;
|
|
}
|
|
|
|
glm::quat Avatar::getWorldAlignedOrientation () const {
|
|
return computeRotationFromBodyToWorldUp() * getOrientation();
|
|
}
|
|
|
|
float Avatar::getLODDistance() const {
|
|
return glm::distance(Application::getInstance()->getCamera()->getPosition(), _position) / _scale;
|
|
}
|
|
|
|
void Avatar::simulate(float deltaTime) {
|
|
if (_scale != _targetScale) {
|
|
setScale(_targetScale);
|
|
}
|
|
|
|
// copy velocity so we can use it later for acceleration
|
|
glm::vec3 oldVelocity = getVelocity();
|
|
|
|
getHand()->simulate(deltaTime, false);
|
|
_skeletonModel.setLODDistance(getLODDistance());
|
|
_skeletonModel.simulate(deltaTime);
|
|
Head* head = getHead();
|
|
glm::vec3 headPosition;
|
|
if (!_skeletonModel.getHeadPosition(headPosition)) {
|
|
headPosition = _position;
|
|
}
|
|
head->setPosition(headPosition);
|
|
head->setScale(_scale);
|
|
getHead()->simulate(deltaTime, false);
|
|
|
|
// use speed and angular velocity to determine walking vs. standing
|
|
if (_speed + fabs(_bodyYawDelta) > 0.2) {
|
|
_mode = AVATAR_MODE_WALKING;
|
|
} else {
|
|
_mode = AVATAR_MODE_INTERACTING;
|
|
}
|
|
|
|
// update position by velocity, and subtract the change added earlier for gravity
|
|
_position += _velocity * deltaTime;
|
|
|
|
// Zero thrust out now that we've added it to velocity in this frame
|
|
_thrust = glm::vec3(0, 0, 0);
|
|
|
|
// update animation for display name fade in/out
|
|
if ( _displayNameTargetAlpha != _displayNameAlpha) {
|
|
// the alpha function is
|
|
// Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt)
|
|
// Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt)
|
|
// factor^(dt) = coef
|
|
float coef = pow(DISPLAYNAME_FADE_FACTOR, deltaTime);
|
|
if (_displayNameTargetAlpha < _displayNameAlpha) {
|
|
// Fading out
|
|
_displayNameAlpha *= coef;
|
|
} else {
|
|
// Fading in
|
|
_displayNameAlpha = 1 - (1 - _displayNameAlpha) * coef;
|
|
}
|
|
_displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01? _displayNameTargetAlpha : _displayNameAlpha;
|
|
}
|
|
}
|
|
|
|
void Avatar::setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction) {
|
|
_mouseRayOrigin = origin;
|
|
_mouseRayDirection = direction;
|
|
}
|
|
|
|
enum TextRendererType {
|
|
CHAT,
|
|
DISPLAYNAME
|
|
};
|
|
|
|
static TextRenderer* textRenderer(TextRendererType type) {
|
|
static TextRenderer* chatRenderer = new TextRenderer(SANS_FONT_FAMILY, 24, -1, false, TextRenderer::SHADOW_EFFECT);
|
|
static TextRenderer* displayNameRenderer = new TextRenderer(SANS_FONT_FAMILY, 12, -1, false, TextRenderer::NO_EFFECT);
|
|
|
|
switch(type) {
|
|
case CHAT:
|
|
return chatRenderer;
|
|
case DISPLAYNAME:
|
|
return displayNameRenderer;
|
|
}
|
|
|
|
return displayNameRenderer;
|
|
}
|
|
|
|
void Avatar::render() {
|
|
glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition();
|
|
float lengthToTarget = glm::length(toTarget);
|
|
|
|
{
|
|
// glow when moving in the distance
|
|
|
|
const float GLOW_DISTANCE = 5.0f;
|
|
Glower glower(_moving && lengthToTarget > GLOW_DISTANCE ? 1.0f : 0.0f);
|
|
|
|
// render body
|
|
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) {
|
|
_skeletonModel.renderCollisionProxies(0.7f);
|
|
}
|
|
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionProxies)) {
|
|
getHead()->getFaceModel().renderCollisionProxies(0.7f);
|
|
}
|
|
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
|
|
renderBody();
|
|
}
|
|
|
|
// render voice intensity sphere for avatars that are farther away
|
|
const float MAX_SPHERE_ANGLE = 10.f;
|
|
const float MIN_SPHERE_ANGLE = 1.f;
|
|
const float MIN_SPHERE_SIZE = 0.01;
|
|
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.f;
|
|
float angle = abs(angleBetween(toTarget + delta, toTarget - delta));
|
|
float sphereRadius = getHead()->getAverageLoudness() * SPHERE_LOUDNESS_SCALING;
|
|
|
|
if ((sphereRadius > MIN_SPHERE_SIZE) && (angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) {
|
|
glColor4f(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.f - 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(lengthToTarget < DISPLAYNAME_DISTANCE);
|
|
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::angle(chatRotation), chatAxis.x, chatAxis.y, chatAxis.z);
|
|
|
|
|
|
glColor3f(0, 0.8f, 0);
|
|
glRotatef(180, 0, 1, 0);
|
|
glRotatef(180, 0, 0, 1);
|
|
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, 1, 0);
|
|
textRenderer(CHAT)->draw(width / 2.0f - lastWidth, 0, _chatMessage.c_str() + lastIndex);
|
|
}
|
|
glEnable(GL_LIGHTING);
|
|
glDepthMask(true);
|
|
|
|
glPopMatrix();
|
|
}
|
|
}
|
|
|
|
glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
|
|
glm::quat orientation = getOrientation();
|
|
glm::vec3 currentUp = orientation * IDENTITY_UP;
|
|
float angle = glm::degrees(acosf(glm::clamp(glm::dot(currentUp, _worldUpDirection), -1.0f, 1.0f)));
|
|
if (angle < EPSILON) {
|
|
return glm::quat();
|
|
}
|
|
glm::vec3 axis;
|
|
if (angle > 179.99f) { // 180 degree rotation; must use another axis
|
|
axis = orientation * IDENTITY_RIGHT;
|
|
} else {
|
|
axis = glm::normalize(glm::cross(currentUp, _worldUpDirection));
|
|
}
|
|
return glm::angleAxis(angle * proportion, axis);
|
|
}
|
|
|
|
void Avatar::renderBody() {
|
|
const float BILLBOARD_DISTANCE = 40.0f;
|
|
if (!_billboard.isEmpty() && getLODDistance() >= BILLBOARD_DISTANCE) {
|
|
renderBillboard();
|
|
return;
|
|
}
|
|
_skeletonModel.render(1.0f);
|
|
getHead()->render(1.0f);
|
|
getHand()->render(false);
|
|
}
|
|
|
|
void Avatar::renderBillboard() {
|
|
if (!_billboardTexture) {
|
|
QImage image = QImage::fromData(_billboard).convertToFormat(QImage::Format_ARGB32);
|
|
|
|
_billboardTexture.reset(new Texture());
|
|
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1,
|
|
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
} else {
|
|
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
|
|
}
|
|
|
|
glEnable(GL_ALPHA_TEST);
|
|
glAlphaFunc(GL_GREATER, 0.5f);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
glDisable(GL_LIGHTING);
|
|
|
|
glPushMatrix();
|
|
glTranslatef(_position.x, _position.y, _position.z);
|
|
|
|
// rotate about vertical to face the camera
|
|
glm::quat rotation = getOrientation();
|
|
glm::vec3 cameraVector = glm::inverse(rotation) * (Application::getInstance()->getCamera()->getPosition() - _position);
|
|
rotation = rotation * glm::angleAxis(glm::degrees(atan2f(-cameraVector.x, -cameraVector.z)), 0.0f, 1.0f, 0.0f);
|
|
glm::vec3 axis = glm::axis(rotation);
|
|
glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z);
|
|
|
|
// compute the size from the billboard camera parameters and scale
|
|
float size = _scale * BILLBOARD_DISTANCE * tanf(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f));
|
|
glScalef(size, size, size);
|
|
|
|
glColor3f(1.0f, 1.0f, 1.0f);
|
|
|
|
glBegin(GL_QUADS);
|
|
glTexCoord2f(0.0f, 0.0f);
|
|
glVertex2f(-1.0f, -1.0f);
|
|
glTexCoord2f(1.0f, 0.0f);
|
|
glVertex2f(1.0f, -1.0f);
|
|
glTexCoord2f(1.0f, 1.0f);
|
|
glVertex2f(1.0f, 1.0f);
|
|
glTexCoord2f(0.0f, 1.0f);
|
|
glVertex2f(-1.0f, 1.0f);
|
|
glEnd();
|
|
|
|
glPopMatrix();
|
|
|
|
glDisable(GL_TEXTURE_2D);
|
|
glEnable(GL_LIGHTING);
|
|
glDisable(GL_ALPHA_TEST);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
|
|
void Avatar::renderDisplayName() {
|
|
|
|
if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) {
|
|
return;
|
|
}
|
|
|
|
glDisable(GL_LIGHTING);
|
|
|
|
glPushMatrix();
|
|
glm::vec3 textPosition;
|
|
getSkeletonModel().getNeckPosition(textPosition);
|
|
textPosition += getBodyUpDirection() * getHeadHeight() * 1.1f;
|
|
|
|
glTranslatef(textPosition.x, textPosition.y, textPosition.z);
|
|
|
|
// we need "always facing camera": we must remove the camera rotation from the stack
|
|
glm::quat rotation = Application::getInstance()->getCamera()->getRotation();
|
|
glm::vec3 axis = glm::axis(rotation);
|
|
glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z);
|
|
|
|
// We need to compute the scale factor such as the text remains with fixed size respect to window coordinates
|
|
// We project a unit vector and check the difference in screen coordinates, to check which is the
|
|
// correction scale needed
|
|
// save the matrices for later scale correction factor
|
|
glm::dmat4 modelViewMatrix;
|
|
glm::dmat4 projectionMatrix;
|
|
GLint viewportMatrix[4];
|
|
Application::getInstance()->getModelViewMatrix(&modelViewMatrix);
|
|
Application::getInstance()->getProjectionMatrix(&projectionMatrix);
|
|
glGetIntegerv(GL_VIEWPORT, viewportMatrix);
|
|
GLdouble result0[3], result1[3];
|
|
|
|
glm::dvec3 upVector(modelViewMatrix[1]);
|
|
|
|
glm::dvec3 testPoint0 = glm::dvec3(textPosition);
|
|
glm::dvec3 testPoint1 = glm::dvec3(textPosition) + upVector;
|
|
|
|
bool success;
|
|
success = gluProject(testPoint0.x, testPoint0.y, testPoint0.z,
|
|
(GLdouble*)&modelViewMatrix, (GLdouble*)&projectionMatrix, viewportMatrix,
|
|
&result0[0], &result0[1], &result0[2]);
|
|
success = success &&
|
|
gluProject(testPoint1.x, testPoint1.y, testPoint1.z,
|
|
(GLdouble*)&modelViewMatrix, (GLdouble*)&projectionMatrix, viewportMatrix,
|
|
&result1[0], &result1[1], &result1[2]);
|
|
|
|
if (success) {
|
|
double textWindowHeight = abs(result1[1] - result0[1]);
|
|
float scaleFactor = (textWindowHeight > EPSILON) ? 1.0f / textWindowHeight : 1.0f;
|
|
glScalef(scaleFactor, scaleFactor, 1.0);
|
|
|
|
glScalef(1.0f, -1.0f, 1.0f); // TextRenderer::draw paints the text upside down in y axis
|
|
|
|
int text_x = -_displayNameBoundingRect.width() / 2;
|
|
int text_y = -_displayNameBoundingRect.height() / 2;
|
|
|
|
// draw a gray background
|
|
int left = text_x + _displayNameBoundingRect.x();
|
|
int right = left + _displayNameBoundingRect.width();
|
|
int bottom = text_y + _displayNameBoundingRect.y();
|
|
int top = bottom + _displayNameBoundingRect.height();
|
|
const int border = 8;
|
|
bottom -= border;
|
|
left -= border;
|
|
top += border;
|
|
right += border;
|
|
|
|
// We are drawing coplanar textures with depth: need the polygon offset
|
|
glEnable(GL_POLYGON_OFFSET_FILL);
|
|
glPolygonOffset(1.0f, 1.0f);
|
|
|
|
glColor4f(0.2f, 0.2f, 0.2f, _displayNameAlpha * DISPLAYNAME_BACKGROUND_ALPHA / DISPLAYNAME_ALPHA);
|
|
renderBevelCornersRect(left, bottom, right - left, top - bottom, 3);
|
|
|
|
glColor4f(0.93f, 0.93f, 0.93f, _displayNameAlpha);
|
|
QByteArray ba = _displayName.toLocal8Bit();
|
|
const char* text = ba.data();
|
|
|
|
glDisable(GL_POLYGON_OFFSET_FILL);
|
|
textRenderer(DISPLAYNAME)->draw(text_x, text_y, text);
|
|
|
|
|
|
}
|
|
|
|
glPopMatrix();
|
|
|
|
glEnable(GL_LIGHTING);
|
|
}
|
|
|
|
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 (getHead()->getFaceModel().findRayIntersection(origin, direction, modelDistance)) {
|
|
minDistance = qMin(minDistance, modelDistance);
|
|
}
|
|
if (minDistance < FLT_MAX) {
|
|
distance = minDistance;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
|
CollisionList& collisions, int skeletonSkipIndex) {
|
|
// Temporarily disabling collisions against the skeleton because the collision proxies up
|
|
// near the neck are bad and prevent the hand from hitting the face.
|
|
//return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions, 1.0f, skeletonSkipIndex);
|
|
return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
|
|
}
|
|
|
|
bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
|
|
if (_collisionFlags & COLLISION_GROUP_PARTICLES) {
|
|
return false;
|
|
}
|
|
bool collided = false;
|
|
// first do the hand collisions
|
|
const HandData* handData = getHandData();
|
|
if (handData) {
|
|
for (int i = 0; i < NUM_HANDS; i++) {
|
|
const PalmData* palm = handData->getPalm(i);
|
|
if (palm && palm->hasPaddle()) {
|
|
// create a disk collision proxy where the hand is
|
|
glm::vec3 fingerAxis(0.f);
|
|
for (size_t f = 0; f < palm->getNumFingers(); ++f) {
|
|
const FingerData& finger = (palm->getFingers())[f];
|
|
if (finger.isActive()) {
|
|
// compute finger axis
|
|
glm::vec3 fingerTip = finger.getTipPosition();
|
|
glm::vec3 fingerRoot = finger.getRootPosition();
|
|
fingerAxis = glm::normalize(fingerTip - fingerRoot);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int jointIndex = -1;
|
|
glm::vec3 handPosition;
|
|
if (i == 0) {
|
|
_skeletonModel.getLeftHandPosition(handPosition);
|
|
jointIndex = _skeletonModel.getLeftHandJointIndex();
|
|
}
|
|
else {
|
|
_skeletonModel.getRightHandPosition(handPosition);
|
|
jointIndex = _skeletonModel.getRightHandJointIndex();
|
|
}
|
|
glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis;
|
|
glm::vec3 diskNormal = palm->getNormal();
|
|
const float DISK_THICKNESS = 0.08f;
|
|
|
|
// collide against the disk
|
|
glm::vec3 penetration;
|
|
if (findSphereDiskPenetration(particleCenter, particleRadius,
|
|
diskCenter, HAND_PADDLE_RADIUS, DISK_THICKNESS, diskNormal,
|
|
penetration)) {
|
|
CollisionInfo* collision = collisions.getNewCollision();
|
|
if (collision) {
|
|
collision->_type = PADDLE_HAND_COLLISION;
|
|
collision->_flags = jointIndex;
|
|
collision->_penetration = penetration;
|
|
collision->_addedVelocity = palm->getVelocity();
|
|
collided = true;
|
|
} else {
|
|
// collisions are full, so we might as well bail now
|
|
return collided;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// then collide against the models
|
|
int preNumCollisions = collisions.size();
|
|
if (_skeletonModel.findSphereCollisions(particleCenter, particleRadius, collisions)) {
|
|
// the Model doesn't have velocity info, so we have to set it for each new collision
|
|
int postNumCollisions = collisions.size();
|
|
for (int i = preNumCollisions; i < postNumCollisions; ++i) {
|
|
CollisionInfo* collision = collisions.getCollision(i);
|
|
collision->_penetration /= (float)(TREE_SCALE);
|
|
collision->_addedVelocity = getVelocity();
|
|
}
|
|
collided = true;
|
|
}
|
|
return collided;
|
|
}
|
|
|
|
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
|
|
AvatarData::setFaceModelURL(faceModelURL);
|
|
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst");
|
|
getHead()->getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL);
|
|
}
|
|
|
|
void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
|
|
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
|
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fst");
|
|
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL);
|
|
}
|
|
|
|
void Avatar::setDisplayName(const QString& displayName) {
|
|
AvatarData::setDisplayName(displayName);
|
|
_displayNameBoundingRect = textRenderer(DISPLAYNAME)->metrics().tightBoundingRect(displayName);
|
|
}
|
|
|
|
void Avatar::setBillboard(const QByteArray& billboard) {
|
|
AvatarData::setBillboard(billboard);
|
|
|
|
// clear out any existing billboard texture
|
|
_billboardTexture.reset();
|
|
}
|
|
|
|
int Avatar::parseData(const QByteArray& packet) {
|
|
// change in position implies movement
|
|
glm::vec3 oldPosition = _position;
|
|
|
|
int bytesRead = AvatarData::parseData(packet);
|
|
|
|
const float MOVE_DISTANCE_THRESHOLD = 0.001f;
|
|
_moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD;
|
|
|
|
return bytesRead;
|
|
}
|
|
|
|
// render a makeshift cone section that serves as a body part connecting joint spheres
|
|
void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2) {
|
|
|
|
glBegin(GL_TRIANGLES);
|
|
|
|
glm::vec3 axis = position2 - position1;
|
|
float length = glm::length(axis);
|
|
|
|
if (length > 0.0f) {
|
|
|
|
axis /= length;
|
|
|
|
glm::vec3 perpSin = glm::vec3(1.0f, 0.0f, 0.0f);
|
|
glm::vec3 perpCos = glm::normalize(glm::cross(axis, perpSin));
|
|
perpSin = glm::cross(perpCos, axis);
|
|
|
|
float anglea = 0.0;
|
|
float angleb = 0.0;
|
|
|
|
for (int i = 0; i < NUM_BODY_CONE_SIDES; i ++) {
|
|
|
|
// the rectangles that comprise the sides of the cone section are
|
|
// referenced by "a" and "b" in one dimension, and "1", and "2" in the other dimension.
|
|
anglea = angleb;
|
|
angleb = ((float)(i+1) / (float)NUM_BODY_CONE_SIDES) * PIf * 2.0f;
|
|
|
|
float sa = sinf(anglea);
|
|
float sb = sinf(angleb);
|
|
float ca = cosf(anglea);
|
|
float cb = cosf(angleb);
|
|
|
|
glm::vec3 p1a = position1 + perpSin * sa * radius1 + perpCos * ca * radius1;
|
|
glm::vec3 p1b = position1 + perpSin * sb * radius1 + perpCos * cb * radius1;
|
|
glm::vec3 p2a = position2 + perpSin * sa * radius2 + perpCos * ca * radius2;
|
|
glm::vec3 p2b = position2 + perpSin * sb * radius2 + perpCos * cb * radius2;
|
|
|
|
glVertex3f(p1a.x, p1a.y, p1a.z);
|
|
glVertex3f(p1b.x, p1b.y, p1b.z);
|
|
glVertex3f(p2a.x, p2a.y, p2a.z);
|
|
glVertex3f(p1b.x, p1b.y, p1b.z);
|
|
glVertex3f(p2a.x, p2a.y, p2a.z);
|
|
glVertex3f(p2b.x, p2b.y, p2b.z);
|
|
}
|
|
}
|
|
|
|
glEnd();
|
|
}
|
|
|
|
void Avatar::updateCollisionFlags() {
|
|
_collisionFlags = 0;
|
|
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithEnvironment)) {
|
|
_collisionFlags |= COLLISION_GROUP_ENVIRONMENT;
|
|
}
|
|
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithAvatars)) {
|
|
_collisionFlags |= COLLISION_GROUP_AVATARS;
|
|
}
|
|
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithVoxels)) {
|
|
_collisionFlags |= COLLISION_GROUP_VOXELS;
|
|
}
|
|
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) {
|
|
_collisionFlags |= COLLISION_GROUP_PARTICLES;
|
|
}
|
|
}
|
|
|
|
void Avatar::setScale(float scale) {
|
|
_scale = scale;
|
|
|
|
if (_targetScale * (1.f - RESCALING_TOLERANCE) < _scale &&
|
|
_scale < _targetScale * (1.f + RESCALING_TOLERANCE)) {
|
|
_scale = _targetScale;
|
|
}
|
|
}
|
|
|
|
float Avatar::getSkeletonHeight() const {
|
|
Extents extents = _skeletonModel.getBindExtents();
|
|
return extents.maximum.y - extents.minimum.y;
|
|
}
|
|
|
|
float Avatar::getHeadHeight() const {
|
|
Extents extents = getHead()->getFaceModel().getBindExtents();
|
|
return extents.maximum.y - extents.minimum.y;
|
|
}
|
|
|
|
bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
|
|
if (!collision._data || collision._type != MODEL_COLLISION) {
|
|
return false;
|
|
}
|
|
Model* model = static_cast<Model*>(collision._data);
|
|
int jointIndex = collision._flags;
|
|
|
|
if (model == &(_skeletonModel) && jointIndex != -1) {
|
|
// collision response of skeleton is temporarily disabled
|
|
return false;
|
|
//return _skeletonModel.collisionHitsMoveableJoint(collision);
|
|
}
|
|
if (model == &(getHead()->getFaceModel())) {
|
|
// ATM we always handle MODEL_COLLISIONS against the face.
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Avatar::applyCollision(CollisionInfo& collision) {
|
|
if (!collision._data || collision._type != MODEL_COLLISION) {
|
|
return;
|
|
}
|
|
// TODO: make skeleton also respond to collisions
|
|
Model* model = static_cast<Model*>(collision._data);
|
|
if (model == &(getHead()->getFaceModel())) {
|
|
getHead()->applyCollision(collision);
|
|
}
|
|
}
|
|
|
|
float Avatar::getPelvisFloatingHeight() const {
|
|
return -_skeletonModel.getBindExtents().minimum.y;
|
|
}
|
|
|
|
float Avatar::getPelvisToHeadLength() const {
|
|
return glm::distance(_position, getHead()->getPosition());
|
|
}
|
|
|
|
void Avatar::setShowDisplayName(bool showDisplayName) {
|
|
// For myAvatar, the alpha update is not done (called in simulate for other avatars)
|
|
if (Application::getInstance()->getAvatar() == this) {
|
|
if (showDisplayName) {
|
|
_displayNameAlpha = DISPLAYNAME_ALPHA;
|
|
} else {
|
|
_displayNameAlpha = 0.0f;
|
|
}
|
|
}
|
|
|
|
if (showDisplayName) {
|
|
_displayNameTargetAlpha = DISPLAYNAME_ALPHA;
|
|
} else {
|
|
_displayNameTargetAlpha = 0.0f;
|
|
}
|
|
|
|
}
|
|
|