mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
Moved hair to separate class
This commit is contained in:
parent
21e7833938
commit
b86890033c
5 changed files with 262 additions and 264 deletions
|
@ -15,21 +15,212 @@
|
|||
#include "Util.h"
|
||||
#include "world.h"
|
||||
|
||||
const float HAIR_DAMPING = 0.99f;
|
||||
const float CONSTRAINT_RELAXATION = 10.0f;
|
||||
const glm::vec3 HAIR_GRAVITY(0.0f, -0.007f, 0.0f);
|
||||
const float HAIR_ACCELERATION_COUPLING = 0.025f;
|
||||
const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.10f;
|
||||
const float HAIR_MAX_LINEAR_ACCELERATION = 4.0f;
|
||||
const float HAIR_STIFFNESS = 0.0000f;
|
||||
const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f);
|
||||
const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f);
|
||||
const glm::vec3 WIND_DIRECTION(0.5f, -1.0f, 0.0f);
|
||||
const float MAX_WIND_STRENGTH = 0.02f;
|
||||
|
||||
Hair::Hair() {
|
||||
qDebug() << "Creating Hair";
|
||||
|
||||
Hair::Hair(int strands,
|
||||
int links,
|
||||
float radius,
|
||||
float linkLength,
|
||||
float hairThickness) :
|
||||
_acceleration(0.0f),
|
||||
_angularVelocity(0.0f),
|
||||
_gravity(0.0f)
|
||||
{
|
||||
_strands = strands;
|
||||
_links = links;
|
||||
_radius = radius;
|
||||
_linkLength = linkLength;
|
||||
_hairThickness = hairThickness;
|
||||
_hairPosition = new glm::vec3[_strands * _links];
|
||||
_hairOriginalPosition = new glm::vec3[_strands * _links];
|
||||
_hairLastPosition = new glm::vec3[_strands * _links];
|
||||
_hairQuadDelta = new glm::vec3[_strands * _links];
|
||||
_hairNormals = new glm::vec3[_strands * _links];
|
||||
_hairColors = new glm::vec3[_strands * _links];
|
||||
_hairIsMoveable = new int[_strands * _links];
|
||||
_hairConstraints = new int[_strands * _links * HAIR_CONSTRAINTS]; // Hair can link to two others
|
||||
const float FACE_WIDTH = PI / 4.0f;
|
||||
glm::vec3 thisVertex;
|
||||
for (int strand = 0; strand < _strands; strand++) {
|
||||
float strandAngle = randFloat() * PI;
|
||||
float azimuth = FACE_WIDTH / 2.0f + (randFloat() * (2.0 * PI - FACE_WIDTH));
|
||||
float elevation = PI_OVER_TWO - (randFloat() * 0.75 * PI);
|
||||
glm::vec3 thisStrand(sinf(azimuth) * cosf(elevation), sinf(elevation), -cosf(azimuth) * cosf(elevation));
|
||||
thisStrand *= _radius;
|
||||
|
||||
for (int link = 0; link < _links; link++) {
|
||||
int vertexIndex = strand * _links + link;
|
||||
// Clear constraints
|
||||
for (int link2 = 0; link2 < HAIR_CONSTRAINTS; link2++) {
|
||||
_hairConstraints[vertexIndex * HAIR_CONSTRAINTS + link2] = -1;
|
||||
}
|
||||
if (vertexIndex % _links == 0) {
|
||||
// start of strand
|
||||
thisVertex = thisStrand;
|
||||
} else {
|
||||
thisVertex+= glm::normalize(thisStrand) * _linkLength;
|
||||
// Set constraints to vertex before and maybe vertex after in strand
|
||||
_hairConstraints[vertexIndex * HAIR_CONSTRAINTS] = vertexIndex - 1;
|
||||
if (link < (_links - 1)) {
|
||||
_hairConstraints[vertexIndex * HAIR_CONSTRAINTS + 1] = vertexIndex + 1;
|
||||
}
|
||||
}
|
||||
_hairPosition[vertexIndex] = thisVertex;
|
||||
_hairLastPosition[vertexIndex] = _hairPosition[vertexIndex];
|
||||
_hairOriginalPosition[vertexIndex] = _hairPosition[vertexIndex];
|
||||
|
||||
_hairQuadDelta[vertexIndex] = glm::vec3(cos(strandAngle) * _hairThickness, 0.f, sin(strandAngle) * _hairThickness);
|
||||
_hairQuadDelta[vertexIndex] *= 1.f - (link / _links);
|
||||
_hairNormals[vertexIndex] = glm::normalize(randVector());
|
||||
if (randFloat() < elevation / PI_OVER_TWO) {
|
||||
_hairColors[vertexIndex] = HAIR_COLOR1 * ((float)(link + 1) / (float)_links);
|
||||
} else {
|
||||
_hairColors[vertexIndex] = HAIR_COLOR2 * ((float)(link + 1) / (float)_links);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Hair::simulate(float deltaTime) {
|
||||
deltaTime = glm::clamp(deltaTime, 0.0f, 1.0f / 30.0f);
|
||||
glm::vec3 acceleration = _acceleration;
|
||||
if (glm::length(acceleration) > HAIR_MAX_LINEAR_ACCELERATION) {
|
||||
acceleration = glm::normalize(acceleration) * HAIR_MAX_LINEAR_ACCELERATION;
|
||||
}
|
||||
|
||||
float windIntensity = randFloat() * MAX_WIND_STRENGTH;
|
||||
|
||||
for (int strand = 0; strand < _strands; strand++) {
|
||||
for (int link = 0; link < _links; link++) {
|
||||
int vertexIndex = strand * _links + link;
|
||||
if (vertexIndex % _links == 0) {
|
||||
// Base Joint - no integration
|
||||
} else {
|
||||
//
|
||||
// Vertlet Integration
|
||||
//
|
||||
// Add velocity from last position, with damping
|
||||
glm::vec3 thisPosition = _hairPosition[vertexIndex];
|
||||
glm::vec3 diff = thisPosition - _hairLastPosition[vertexIndex];
|
||||
_hairPosition[vertexIndex] += diff * HAIR_DAMPING;
|
||||
// Resolve collision with head sphere
|
||||
if (glm::length(_hairPosition[vertexIndex]) < _radius) {
|
||||
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex]) *
|
||||
(_radius - glm::length(_hairPosition[vertexIndex]));
|
||||
}
|
||||
// Resolve collision with hands
|
||||
/*
|
||||
if (glm::length(_hairPosition[vertexIndex] - leftHandPosition) < FINGER_RADIUS) {
|
||||
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - leftHandPosition) *
|
||||
(FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - leftHandPosition));
|
||||
}
|
||||
if (glm::length(_hairPosition[vertexIndex] - rightHandPosition) < FINGER_RADIUS) {
|
||||
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - rightHandPosition) *
|
||||
(FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - rightHandPosition));
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
// Add a little gravity
|
||||
_hairPosition[vertexIndex] += HAIR_GRAVITY * deltaTime;
|
||||
|
||||
// Add linear acceleration of the avatar body
|
||||
_hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime;
|
||||
|
||||
// Add stiffness (like hair care products do)
|
||||
_hairPosition[vertexIndex] += (_hairOriginalPosition[vertexIndex] - _hairPosition[vertexIndex])
|
||||
* powf(1.f - link / _links, 2.f) * HAIR_STIFFNESS;
|
||||
|
||||
// Add some wind
|
||||
glm::vec3 wind = WIND_DIRECTION * windIntensity;
|
||||
_hairPosition[vertexIndex] += wind * deltaTime;
|
||||
|
||||
const float ANGULAR_VELOCITY_MIN = 0.001f;
|
||||
// Add angular acceleration of the avatar body
|
||||
if (glm::length(_angularVelocity) > ANGULAR_VELOCITY_MIN) {
|
||||
glm::vec3 yawVector = _hairPosition[vertexIndex];
|
||||
yawVector.y = 0.f;
|
||||
if (glm::length(yawVector) > EPSILON) {
|
||||
float radius = glm::length(yawVector);
|
||||
yawVector = glm::normalize(yawVector);
|
||||
float angle = atan2f(yawVector.x, -yawVector.z) + PI;
|
||||
glm::vec3 delta = glm::vec3(-1.f, 0.f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 1, 0));
|
||||
_hairPosition[vertexIndex] -= delta * radius * _angularVelocity.y * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
|
||||
}
|
||||
glm::vec3 pitchVector = _hairPosition[vertexIndex];
|
||||
pitchVector.x = 0.f;
|
||||
if (glm::length(pitchVector) > EPSILON) {
|
||||
float radius = glm::length(pitchVector);
|
||||
pitchVector = glm::normalize(pitchVector);
|
||||
float angle = atan2f(pitchVector.y, -pitchVector.z) + PI;
|
||||
glm::vec3 delta = glm::vec3(0.0f, 1.0f, 0.f) * glm::angleAxis(angle, glm::vec3(1, 0, 0));
|
||||
_hairPosition[vertexIndex] -= delta * radius * _angularVelocity.x * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
|
||||
}
|
||||
glm::vec3 rollVector = _hairPosition[vertexIndex];
|
||||
rollVector.z = 0.f;
|
||||
if (glm::length(rollVector) > EPSILON) {
|
||||
float radius = glm::length(rollVector);
|
||||
pitchVector = glm::normalize(rollVector);
|
||||
float angle = atan2f(rollVector.x, rollVector.y) + PI;
|
||||
glm::vec3 delta = glm::vec3(-1.0f, 0.0f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 0, 1));
|
||||
_hairPosition[vertexIndex] -= delta * radius * _angularVelocity.z * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate length constraints to other links
|
||||
for (int link = 0; link < HAIR_CONSTRAINTS; link++) {
|
||||
if (_hairConstraints[vertexIndex * HAIR_CONSTRAINTS + link] > -1) {
|
||||
// If there is a constraint, try to enforce it
|
||||
glm::vec3 vectorBetween = _hairPosition[_hairConstraints[vertexIndex * HAIR_CONSTRAINTS + link]] - _hairPosition[vertexIndex];
|
||||
_hairPosition[vertexIndex] += glm::normalize(vectorBetween) * (glm::length(vectorBetween) - _linkLength) * CONSTRAINT_RELAXATION * deltaTime;
|
||||
}
|
||||
}
|
||||
// Store start position for next vertlet pass
|
||||
_hairLastPosition[vertexIndex] = thisPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Hair::render() {
|
||||
//
|
||||
// Before calling this function, translate/rotate to the origin of the owning object
|
||||
glPushMatrix();
|
||||
glColor3f(1.0f, 1.0f, 0.0f);
|
||||
glutSolidSphere(1.0f, 15, 15);
|
||||
glPopMatrix();
|
||||
//
|
||||
glBegin(GL_QUADS);
|
||||
for (int strand = 0; strand < _strands; strand++) {
|
||||
for (int link = 0; link < _links - 1; link++) {
|
||||
int vertexIndex = strand * _links + link;
|
||||
glColor3fv(&_hairColors[vertexIndex].x);
|
||||
glNormal3fv(&_hairNormals[vertexIndex].x);
|
||||
glVertex3f(_hairPosition[vertexIndex].x - _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex].y - _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex].z - _hairQuadDelta[vertexIndex].z);
|
||||
glVertex3f(_hairPosition[vertexIndex].x + _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex].y + _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex].z + _hairQuadDelta[vertexIndex].z);
|
||||
|
||||
glVertex3f(_hairPosition[vertexIndex + 1].x + _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex + 1].y + _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex + 1].z + _hairQuadDelta[vertexIndex].z);
|
||||
glVertex3f(_hairPosition[vertexIndex + 1].x - _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex + 1].y - _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex + 1].z - _hairQuadDelta[vertexIndex].z);
|
||||
}
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -21,14 +21,45 @@
|
|||
#include "InterfaceConfig.h"
|
||||
#include "Util.h"
|
||||
|
||||
const int HAIR_CONSTRAINTS = 2;
|
||||
|
||||
const int DEFAULT_HAIR_STRANDS = 50;
|
||||
const int DEFAULT_HAIR_LINKS = 10;
|
||||
const float DEFAULT_HAIR_RADIUS = 0.25;
|
||||
const float DEFAULT_HAIR_LINK_LENGTH = 0.05;
|
||||
const float DEFAULT_HAIR_THICKNESS = 0.015;
|
||||
|
||||
class Hair {
|
||||
public:
|
||||
Hair();
|
||||
Hair(int strands = DEFAULT_HAIR_STRANDS,
|
||||
int links = DEFAULT_HAIR_LINKS,
|
||||
float radius = DEFAULT_HAIR_RADIUS,
|
||||
float linkLength = DEFAULT_HAIR_LINK_LENGTH,
|
||||
float hairThickness = DEFAULT_HAIR_THICKNESS);
|
||||
void simulate(float deltaTime);
|
||||
void render();
|
||||
void setAcceleration(const glm::vec3& acceleration) { _acceleration = acceleration; }
|
||||
void setAngularVelocity(const glm::vec3& angularVelocity) { _angularVelocity = angularVelocity; }
|
||||
void setGravity(const glm::vec3& gravity) { _gravity = gravity; }
|
||||
|
||||
private:
|
||||
int _strands;
|
||||
int _links;
|
||||
float _linkLength;
|
||||
float _hairThickness;
|
||||
float _radius;
|
||||
glm::vec3* _hairPosition;
|
||||
glm::vec3* _hairOriginalPosition;
|
||||
glm::vec3* _hairLastPosition;
|
||||
glm::vec3* _hairQuadDelta;
|
||||
glm::vec3* _hairNormals;
|
||||
glm::vec3* _hairColors;
|
||||
int* _hairIsMoveable;
|
||||
int* _hairConstraints;
|
||||
glm::vec3 _acceleration;
|
||||
glm::vec3 _angularVelocity;
|
||||
glm::vec3 _gravity;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f;
|
|||
|
||||
Avatar::Avatar() :
|
||||
AvatarData(),
|
||||
_hair(),
|
||||
_skeletonModel(this),
|
||||
_bodyYawDelta(0.0f),
|
||||
_velocity(0.0f, 0.0f, 0.0f),
|
||||
|
@ -60,9 +61,9 @@ Avatar::Avatar() :
|
|||
_mouseRayDirection(0.0f, 0.0f, 0.0f),
|
||||
_moving(false),
|
||||
_collisionGroups(0),
|
||||
_numLocalLights(1),
|
||||
_initialized(false),
|
||||
_shouldRenderBillboard(true),
|
||||
_numLocalLights(1)
|
||||
_shouldRenderBillboard(true)
|
||||
{
|
||||
// we may have been created in the network thread, but we live in the main thread
|
||||
moveToThread(Application::getInstance()->thread());
|
||||
|
@ -82,8 +83,7 @@ void Avatar::init() {
|
|||
_skeletonModel.init();
|
||||
_initialized = true;
|
||||
_shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE);
|
||||
initializeHair();
|
||||
|
||||
|
||||
for (int i = 0; i < MAX_LOCAL_LIGHTS; i++) {
|
||||
_localLightColors[i] = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
_localLightDirections[i] = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
|
@ -168,13 +168,11 @@ void Avatar::simulate(float deltaTime) {
|
|||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
|
||||
PerformanceTimer perfTimer("hair");
|
||||
simulateHair(deltaTime);
|
||||
_hair.setAcceleration(getAcceleration() * getHead()->getFinalOrientationInWorldFrame());
|
||||
_hair.setAngularVelocity(getAngularVelocity() + getHead()->getAngularVelocity() * getHead()->getFinalOrientationInWorldFrame());
|
||||
_hair.setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()) * getHead()->getFinalOrientationInWorldFrame());
|
||||
_hair.simulate(deltaTime);
|
||||
}
|
||||
|
||||
foreach (Hair* hair, _hairs) {
|
||||
hair->simulate(deltaTime);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// update position by velocity, and subtract the change added earlier for gravity
|
||||
|
@ -426,233 +424,17 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) {
|
|||
getHand()->render(false, modelRenderMode);
|
||||
}
|
||||
getHead()->render(1.0f, modelRenderMode);
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
|
||||
renderHair();
|
||||
foreach (Hair* hair, _hairs) {
|
||||
hair->render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Constants for the Hair Simulation
|
||||
//
|
||||
|
||||
const float HAIR_LENGTH = 0.2f;
|
||||
const float HAIR_LINK_LENGTH = HAIR_LENGTH / HAIR_LINKS;
|
||||
const float HAIR_DAMPING = 0.99f;
|
||||
const float HEAD_RADIUS = 0.21f;
|
||||
const float CONSTRAINT_RELAXATION = 10.0f;
|
||||
const glm::vec3 HAIR_GRAVITY(0.0f, -0.007f, 0.0f);
|
||||
const float HAIR_ACCELERATION_COUPLING = 0.025f;
|
||||
const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.10f;
|
||||
const float HAIR_MAX_LINEAR_ACCELERATION = 4.0f;
|
||||
const float HAIR_THICKNESS = 0.015f;
|
||||
const float HAIR_STIFFNESS = 0.0000f;
|
||||
const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f);
|
||||
const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f);
|
||||
const glm::vec3 WIND_DIRECTION(0.5f, -1.0f, 0.0f);
|
||||
const float MAX_WIND_STRENGTH = 0.02f;
|
||||
const float FINGER_LENGTH = 0.25f;
|
||||
const float FINGER_RADIUS = 0.10f;
|
||||
|
||||
void Avatar::renderHair() {
|
||||
//
|
||||
// Render the avatar's moveable hair
|
||||
//
|
||||
|
||||
glm::vec3 headPosition = getHead()->getPosition();
|
||||
glPushMatrix();
|
||||
glTranslatef(headPosition.x, headPosition.y, headPosition.z);
|
||||
const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
for (int strand = 0; strand < HAIR_STRANDS; strand++) {
|
||||
for (int link = 0; link < HAIR_LINKS - 1; link++) {
|
||||
int vertexIndex = strand * HAIR_LINKS + link;
|
||||
glColor3fv(&_hairColors[vertexIndex].x);
|
||||
glNormal3fv(&_hairNormals[vertexIndex].x);
|
||||
glVertex3f(_hairPosition[vertexIndex].x - _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex].y - _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex].z - _hairQuadDelta[vertexIndex].z);
|
||||
glVertex3f(_hairPosition[vertexIndex].x + _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex].y + _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex].z + _hairQuadDelta[vertexIndex].z);
|
||||
|
||||
glVertex3f(_hairPosition[vertexIndex + 1].x + _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex + 1].y + _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex + 1].z + _hairQuadDelta[vertexIndex].z);
|
||||
glVertex3f(_hairPosition[vertexIndex + 1].x - _hairQuadDelta[vertexIndex].x,
|
||||
_hairPosition[vertexIndex + 1].y - _hairQuadDelta[vertexIndex].y,
|
||||
_hairPosition[vertexIndex + 1].z - _hairQuadDelta[vertexIndex].z);
|
||||
}
|
||||
}
|
||||
glEnd();
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
}
|
||||
|
||||
void Avatar::simulateHair(float deltaTime) {
|
||||
|
||||
deltaTime = glm::clamp(deltaTime, 0.0f, 1.0f / 30.0f);
|
||||
glm::vec3 acceleration = getAcceleration();
|
||||
if (glm::length(acceleration) > HAIR_MAX_LINEAR_ACCELERATION) {
|
||||
acceleration = glm::normalize(acceleration) * HAIR_MAX_LINEAR_ACCELERATION;
|
||||
}
|
||||
const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
|
||||
acceleration = acceleration * rotation;
|
||||
glm::vec3 angularVelocity = getAngularVelocity() + getHead()->getAngularVelocity();
|
||||
|
||||
// Get hand positions to allow touching hair
|
||||
glm::vec3 leftHandPosition, rightHandPosition;
|
||||
getSkeletonModel().getLeftHandPosition(leftHandPosition);
|
||||
getSkeletonModel().getRightHandPosition(rightHandPosition);
|
||||
leftHandPosition -= getHead()->getPosition();
|
||||
rightHandPosition -= getHead()->getPosition();
|
||||
glm::quat leftRotation, rightRotation;
|
||||
getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation);
|
||||
getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation);
|
||||
leftHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.0f) * glm::inverse(leftRotation);
|
||||
rightHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.0f) * glm::inverse(rightRotation);
|
||||
leftHandPosition = leftHandPosition * rotation;
|
||||
rightHandPosition = rightHandPosition * rotation;
|
||||
|
||||
float windIntensity = randFloat() * MAX_WIND_STRENGTH;
|
||||
|
||||
for (int strand = 0; strand < HAIR_STRANDS; strand++) {
|
||||
for (int link = 0; link < HAIR_LINKS; link++) {
|
||||
int vertexIndex = strand * HAIR_LINKS + link;
|
||||
if (vertexIndex % HAIR_LINKS == 0) {
|
||||
// Base Joint - no integration
|
||||
} else {
|
||||
//
|
||||
// Vertlet Integration
|
||||
//
|
||||
// Add velocity from last position, with damping
|
||||
glm::vec3 thisPosition = _hairPosition[vertexIndex];
|
||||
glm::vec3 diff = thisPosition - _hairLastPosition[vertexIndex];
|
||||
_hairPosition[vertexIndex] += diff * HAIR_DAMPING;
|
||||
// Resolve collision with head sphere
|
||||
if (glm::length(_hairPosition[vertexIndex]) < HEAD_RADIUS) {
|
||||
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex]) *
|
||||
(HEAD_RADIUS - glm::length(_hairPosition[vertexIndex]));
|
||||
}
|
||||
// Resolve collision with hands
|
||||
if (glm::length(_hairPosition[vertexIndex] - leftHandPosition) < FINGER_RADIUS) {
|
||||
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - leftHandPosition) *
|
||||
(FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - leftHandPosition));
|
||||
}
|
||||
if (glm::length(_hairPosition[vertexIndex] - rightHandPosition) < FINGER_RADIUS) {
|
||||
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - rightHandPosition) *
|
||||
(FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - rightHandPosition));
|
||||
}
|
||||
|
||||
|
||||
// Add a little gravity
|
||||
_hairPosition[vertexIndex] += HAIR_GRAVITY * rotation * deltaTime;
|
||||
|
||||
// Add linear acceleration of the avatar body
|
||||
_hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime;
|
||||
|
||||
// Add stiffness (like hair care products do)
|
||||
_hairPosition[vertexIndex] += (_hairOriginalPosition[vertexIndex] - _hairPosition[vertexIndex])
|
||||
* powf(1.f - link / HAIR_LINKS, 2.f) * HAIR_STIFFNESS;
|
||||
|
||||
// Add some wind
|
||||
glm::vec3 wind = WIND_DIRECTION * windIntensity;
|
||||
_hairPosition[vertexIndex] += wind * deltaTime;
|
||||
|
||||
const float ANGULAR_VELOCITY_MIN = 0.001f;
|
||||
// Add angular acceleration of the avatar body
|
||||
if (glm::length(angularVelocity) > ANGULAR_VELOCITY_MIN) {
|
||||
glm::vec3 yawVector = _hairPosition[vertexIndex];
|
||||
yawVector.y = 0.f;
|
||||
if (glm::length(yawVector) > EPSILON) {
|
||||
float radius = glm::length(yawVector);
|
||||
yawVector = glm::normalize(yawVector);
|
||||
float angle = atan2f(yawVector.x, -yawVector.z) + PI;
|
||||
glm::vec3 delta = glm::vec3(-1.f, 0.f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 1, 0));
|
||||
_hairPosition[vertexIndex] -= delta * radius * angularVelocity.y * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
|
||||
}
|
||||
glm::vec3 pitchVector = _hairPosition[vertexIndex];
|
||||
pitchVector.x = 0.f;
|
||||
if (glm::length(pitchVector) > EPSILON) {
|
||||
float radius = glm::length(pitchVector);
|
||||
pitchVector = glm::normalize(pitchVector);
|
||||
float angle = atan2f(pitchVector.y, -pitchVector.z) + PI;
|
||||
glm::vec3 delta = glm::vec3(0.0f, 1.0f, 0.f) * glm::angleAxis(angle, glm::vec3(1, 0, 0));
|
||||
_hairPosition[vertexIndex] -= delta * radius * angularVelocity.x * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
|
||||
}
|
||||
glm::vec3 rollVector = _hairPosition[vertexIndex];
|
||||
rollVector.z = 0.f;
|
||||
if (glm::length(rollVector) > EPSILON) {
|
||||
float radius = glm::length(rollVector);
|
||||
pitchVector = glm::normalize(rollVector);
|
||||
float angle = atan2f(rollVector.x, rollVector.y) + PI;
|
||||
glm::vec3 delta = glm::vec3(-1.0f, 0.0f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 0, 1));
|
||||
_hairPosition[vertexIndex] -= delta * radius * angularVelocity.z * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate length constraints to other links
|
||||
for (int link = 0; link < HAIR_MAX_CONSTRAINTS; link++) {
|
||||
if (_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link] > -1) {
|
||||
// If there is a constraint, try to enforce it
|
||||
glm::vec3 vectorBetween = _hairPosition[_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link]] - _hairPosition[vertexIndex];
|
||||
_hairPosition[vertexIndex] += glm::normalize(vectorBetween) * (glm::length(vectorBetween) - HAIR_LINK_LENGTH) * CONSTRAINT_RELAXATION * deltaTime;
|
||||
}
|
||||
}
|
||||
// Store start position for next vertlet pass
|
||||
_hairLastPosition[vertexIndex] = thisPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Avatar::initializeHair() {
|
||||
const float FACE_WIDTH = PI / 4.0f;
|
||||
glm::vec3 thisVertex;
|
||||
for (int strand = 0; strand < HAIR_STRANDS; strand++) {
|
||||
float strandAngle = randFloat() * PI;
|
||||
float azimuth = FACE_WIDTH / 2.0f + (randFloat() * (2.0 * PI - FACE_WIDTH));
|
||||
float elevation = PI_OVER_TWO - (randFloat() * 0.75 * PI);
|
||||
glm::vec3 thisStrand(sinf(azimuth) * cosf(elevation), sinf(elevation), -cosf(azimuth) * cosf(elevation));
|
||||
thisStrand *= HEAD_RADIUS;
|
||||
|
||||
for (int link = 0; link < HAIR_LINKS; link++) {
|
||||
int vertexIndex = strand * HAIR_LINKS + link;
|
||||
// Clear constraints
|
||||
for (int link2 = 0; link2 < HAIR_MAX_CONSTRAINTS; link2++) {
|
||||
_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link2] = -1;
|
||||
}
|
||||
if (vertexIndex % HAIR_LINKS == 0) {
|
||||
// start of strand
|
||||
thisVertex = thisStrand;
|
||||
} else {
|
||||
thisVertex+= glm::normalize(thisStrand) * HAIR_LINK_LENGTH;
|
||||
// Set constraints to vertex before and maybe vertex after in strand
|
||||
_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS] = vertexIndex - 1;
|
||||
if (link < (HAIR_LINKS - 1)) {
|
||||
_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + 1] = vertexIndex + 1;
|
||||
}
|
||||
}
|
||||
_hairPosition[vertexIndex] = thisVertex;
|
||||
_hairLastPosition[vertexIndex] = _hairPosition[vertexIndex];
|
||||
_hairOriginalPosition[vertexIndex] = _hairPosition[vertexIndex];
|
||||
|
||||
_hairQuadDelta[vertexIndex] = glm::vec3(cos(strandAngle) * HAIR_THICKNESS, 0.f, sin(strandAngle) * HAIR_THICKNESS);
|
||||
_hairQuadDelta[vertexIndex] *= 1.f - (link / HAIR_LINKS);
|
||||
_hairNormals[vertexIndex] = glm::normalize(randVector());
|
||||
if (randFloat() < elevation / PI_OVER_TWO) {
|
||||
_hairColors[vertexIndex] = HAIR_COLOR1 * ((float)(link + 1) / (float)HAIR_LINKS);
|
||||
} else {
|
||||
_hairColors[vertexIndex] = HAIR_COLOR2 * ((float)(link + 1) / (float)HAIR_LINKS);
|
||||
}
|
||||
|
||||
}
|
||||
// Render Hair
|
||||
glPushMatrix();
|
||||
glm::vec3 headPosition = getHead()->getPosition();
|
||||
glTranslatef(headPosition.x, headPosition.y, headPosition.z);
|
||||
const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||
_hair.render();
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -164,7 +164,7 @@ signals:
|
|||
void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision);
|
||||
|
||||
protected:
|
||||
QVector<Hair*> _hairs;
|
||||
Hair _hair;
|
||||
SkeletonModel _skeletonModel;
|
||||
QVector<Model*> _attachmentModels;
|
||||
float _bodyYawDelta;
|
||||
|
@ -210,18 +210,6 @@ protected:
|
|||
virtual void renderAttachments(RenderMode renderMode);
|
||||
|
||||
virtual void updateJointMappings();
|
||||
|
||||
glm::vec3 _hairPosition[HAIR_STRANDS * HAIR_LINKS];
|
||||
glm::vec3 _hairOriginalPosition[HAIR_STRANDS * HAIR_LINKS];
|
||||
glm::vec3 _hairLastPosition[HAIR_STRANDS * HAIR_LINKS];
|
||||
glm::vec3 _hairQuadDelta[HAIR_STRANDS * HAIR_LINKS];
|
||||
glm::vec3 _hairNormals[HAIR_STRANDS * HAIR_LINKS];
|
||||
glm::vec3 _hairColors[HAIR_STRANDS * HAIR_LINKS];
|
||||
int _hairIsMoveable[HAIR_STRANDS * HAIR_LINKS];
|
||||
int _hairConstraints[HAIR_STRANDS * HAIR_LINKS * 2]; // Hair can link to two others
|
||||
void renderHair();
|
||||
void simulateHair(float deltaTime);
|
||||
void initializeHair();
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -185,10 +185,10 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
{
|
||||
PerformanceTimer perfTimer("hair");
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
|
||||
simulateHair(deltaTime);
|
||||
foreach (Hair* hair, _hairs) {
|
||||
hair->simulate(deltaTime);
|
||||
}
|
||||
_hair.setAcceleration(getAcceleration() * getHead()->getFinalOrientationInWorldFrame());
|
||||
_hair.setAngularVelocity(getAngularVelocity() + getHead()->getAngularVelocity() * getHead()->getFinalOrientationInWorldFrame());
|
||||
_hair.setGravity(getGravity() * getHead()->getFinalOrientationInWorldFrame());
|
||||
_hair.simulate(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -884,11 +884,17 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
|
|||
const glm::vec3 cameraPos = camera->getPosition() + (camera->getRotation() * glm::vec3(0.0f, 0.0f, 1.0f)) * camera->getDistance();
|
||||
if (shouldRenderHead(cameraPos, renderMode)) {
|
||||
getHead()->render(1.0f, modelRenderMode);
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
|
||||
renderHair();
|
||||
foreach (Hair* hair, _hairs) {
|
||||
hair->render();
|
||||
}
|
||||
// Render Hair
|
||||
glPushMatrix();
|
||||
glm::vec3 headPosition = getHead()->getPosition();
|
||||
glTranslatef(headPosition.x, headPosition.y, headPosition.z);
|
||||
const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||
_hair.render();
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
getHand()->render(true, modelRenderMode);
|
||||
|
|
Loading…
Reference in a new issue