Merge pull request #3078 from PhilipRosedale/master

Improved toy ball, JS calls for palm position, concertCamera.js
This commit is contained in:
Clément Brisset 2014-06-26 11:03:48 -07:00
commit 463ac9cc74
17 changed files with 469 additions and 50 deletions

67
examples/concertCamera.js Normal file
View file

@ -0,0 +1,67 @@
//
// concertCamera.js
//
// Created by Philip Rosedale on June 24, 2014
// Copyright 2014 High Fidelity, Inc.
//
// Move a camera through a series of pre-set locations by pressing number keys
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var oldMode;
var avatarPosition;
var cameraNumber = 0;
var freeCamera = false;
var cameraLocations = [ {x: 7972.2, y: 241.6, z: 7304.1}, {x: 7973.0, y: 241.6, z: 7304.1}, {x: 7975.5, y: 241.6, z: 7304.1}, {x: 7972.3, y: 241.6, z: 7303.3}, {x: 7971.4, y: 241.6, z: 7304.3}, {x: 7973.5, y: 240.6, z: 7302.5} ];
var cameraLookAts = [ {x: 7971.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241., z: 7304.1} ];
function saveCameraState() {
oldMode = Camera.getMode();
avatarPosition = MyAvatar.position;
Camera.setModeShiftPeriod(0.0);
Camera.setMode("independent");
}
function restoreCameraState() {
Camera.stopLooking();
Camera.setMode(oldMode);
}
function update(deltaTime) {
if (freeCamera) {
var delta = Vec3.subtract(MyAvatar.position, avatarPosition);
if (Vec3.length(delta) > 0.05) {
cameraNumber = 0;
freeCamera = false;
restoreCameraState();
}
}
}
function keyPressEvent(event) {
var choice = parseInt(event.text);
if ((choice > 0) && (choice <= cameraLocations.length)) {
print("camera " + choice);
if (!freeCamera) {
saveCameraState();
freeCamera = true;
}
Camera.setMode("independent");
Camera.setPosition(cameraLocations[choice - 1]);
Camera.keepLookingAt(cameraLookAts[choice - 1]);
}
if (event.text == "0") {
// Show camera location in log
var cameraLocation = Camera.getPosition();
print(cameraLocation.x + ", " + cameraLocation.y + ", " + cameraLocation.z);
}
}
Script.update.connect(update);
Controller.keyPressEvent.connect(keyPressEvent);

View file

@ -0,0 +1,38 @@
//
// inWorldTestTone.js
//
//
// Created by Philip Rosedale on 5/29/14.
// Copyright 2014 High Fidelity, Inc.
//
// This example script plays a test tone that is useful for debugging audio dropout. 220Hz test tone played at the domain origin.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var sound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/220Sine.wav");
var soundPlaying = false;
function update(deltaTime) {
if (!Audio.isInjectorPlaying(soundPlaying)) {
var options = new AudioInjectionOptions();
options.position = { x:0, y:0, z:0 };
options.volume = 1.0;
options.loop = true;
soundPlaying = Audio.playSound(sound, options);
print("Started sound loop");
}
}
function scriptEnding() {
if (Audio.isInjectorPlaying(soundPlaying)) {
Audio.stopInjector(soundPlaying);
print("Stopped sound loop");
}
}
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);

View file

@ -195,6 +195,8 @@ function keyReleaseEvent(event) {
}
}
function mousePressEvent(event) {
if (alt && !isActive) {
mouseLastX = event.x;

View file

@ -19,7 +19,7 @@ var buttonHeight = 46;
var buttonPadding = 10;
var buttonPositionX = windowDimensions.x - buttonPadding - buttonWidth;
var buttonPositionY = (windowDimensions.y - buttonHeight) / 2 ;
var buttonPositionY = (windowDimensions.y - buttonHeight) / 2 - (buttonHeight + buttonPadding);
var sitDownButton = Overlays.addOverlay("image", {
x: buttonPositionX, y: buttonPositionY, width: buttonWidth, height: buttonHeight,
@ -49,13 +49,7 @@ var pose = [
{joint:"RightFoot", rotation: {x:30, y:15.0, z:0.0}},
{joint:"LeftUpLeg", rotation: {x:100.0, y:-15.0, z:0.0}},
{joint:"LeftLeg", rotation: {x:-130.0, y:-15.0, z:0.0}},
{joint:"LeftFoot", rotation: {x:30, y:15.0, z:0.0}},
{joint:"Spine2", rotation: {x:20, y:0.0, z:0.0}},
{joint:"RightShoulder", rotation: {x:0.0, y:40.0, z:0.0}},
{joint:"LeftShoulder", rotation: {x:0.0, y:-40.0, z:0.0}}
{joint:"LeftFoot", rotation: {x:30, y:15.0, z:0.0}}
];
var startPoseAndTransition = [];

View file

@ -9,8 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var rightHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/RightHandAnim.fbx";
var leftHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/LeftHandAnim.fbx";
var rightHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/RightHandAnimPhilip.fbx";
var leftHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/LeftHandAnimPhilip.fbx";
var LEFT = 0;
var RIGHT = 1;

View file

@ -26,14 +26,21 @@ var RIGHT_TIP = 3;
var RIGHT_BUTTON_FWD = 11;
var RIGHT_BUTTON_3 = 9;
var BALL_RADIUS = 0.08;
var GRAVITY_STRENGTH = 0.5;
var HELD_COLOR = { red: 240, green: 0, blue: 0 };
var THROWN_COLOR = { red: 128, green: 0, blue: 0 };
var leftBallAlreadyInHand = false;
var rightBallAlreadyInHand = false;
var leftHandParticle;
var rightHandParticle;
var throwSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw");
var newSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw");
var catchSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw");
var targetRadius = 0.25;
var throwSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Switches%20and%20sliders/slider%20-%20whoosh1.raw");
var targetRadius = 1.0;
var wantDebugging = false;
@ -44,31 +51,19 @@ function debugPrint(message) {
}
function getBallHoldPosition(whichSide) {
var normal;
var tipPosition;
if (whichSide == LEFT_PALM) {
normal = Controller.getSpatialControlNormal(LEFT_PALM);
tipPosition = Controller.getSpatialControlPosition(LEFT_TIP);
position = MyAvatar.getLeftPalmPosition();
} else {
normal = Controller.getSpatialControlNormal(RIGHT_PALM);
tipPosition = Controller.getSpatialControlPosition(RIGHT_TIP);
position = MyAvatar.getRightPalmPosition();
}
var BALL_FORWARD_OFFSET = 0.08; // put the ball a bit forward of fingers
position = { x: BALL_FORWARD_OFFSET * normal.x,
y: BALL_FORWARD_OFFSET * normal.y,
z: BALL_FORWARD_OFFSET * normal.z };
position.x += tipPosition.x;
position.y += tipPosition.y;
position.z += tipPosition.z;
return position;
}
function checkControllerSide(whichSide) {
var BUTTON_FWD;
var BUTTON_3;
var TRIGGER;
var palmPosition;
var ballAlreadyInHand;
var handMessage;
@ -76,18 +71,20 @@ function checkControllerSide(whichSide) {
if (whichSide == LEFT_PALM) {
BUTTON_FWD = LEFT_BUTTON_FWD;
BUTTON_3 = LEFT_BUTTON_3;
TRIGGER = 0;
palmPosition = Controller.getSpatialControlPosition(LEFT_PALM);
ballAlreadyInHand = leftBallAlreadyInHand;
handMessage = "LEFT";
} else {
BUTTON_FWD = RIGHT_BUTTON_FWD;
BUTTON_3 = RIGHT_BUTTON_3;
TRIGGER = 1;
palmPosition = Controller.getSpatialControlPosition(RIGHT_PALM);
ballAlreadyInHand = rightBallAlreadyInHand;
handMessage = "RIGHT";
}
var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3));
var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3) || (Controller.getTriggerValue(TRIGGER) > 0.5));
// If I don't currently have a ball in my hand, then try to catch closest one
if (!ballAlreadyInHand && grabButtonPressed) {
@ -107,8 +104,11 @@ function checkControllerSide(whichSide) {
var ballPosition = getBallHoldPosition(whichSide);
var properties = { position: { x: ballPosition.x,
y: ballPosition.y,
z: ballPosition.z },
velocity : { x: 0, y: 0, z: 0}, inHand: true };
z: ballPosition.z },
color: HELD_COLOR,
velocity : { x: 0, y: 0, z: 0},
lifetime : 600,
inHand: true };
Particles.editParticle(closestParticle, properties);
var options = new AudioInjectionOptions();
@ -127,7 +127,7 @@ function checkControllerSide(whichSide) {
//}
// If '3' is pressed, and not holding a ball, make a new one
if (Controller.isButtonPressed(BUTTON_3) && !ballAlreadyInHand) {
if (grabButtonPressed && !ballAlreadyInHand) {
var ballPosition = getBallHoldPosition(whichSide);
var properties = { position: { x: ballPosition.x,
y: ballPosition.y,
@ -135,11 +135,11 @@ function checkControllerSide(whichSide) {
velocity: { x: 0, y: 0, z: 0},
gravity: { x: 0, y: 0, z: 0},
inHand: true,
radius: 0.05,
radius: BALL_RADIUS,
damping: 0.999,
color: { red: 255, green: 0, blue: 0 },
color: HELD_COLOR,
lifetime: 10 // 10 seconds - same as default, not needed but here as an example
lifetime: 600 // 10 seconds - same as default, not needed but here as an example
};
newParticle = Particles.addParticle(properties);
@ -155,7 +155,7 @@ function checkControllerSide(whichSide) {
var options = new AudioInjectionOptions();
options.position = ballPosition;
options.volume = 1.0;
Audio.playSound(catchSound, options);
Audio.playSound(newSound, options);
return; // exit early
}
@ -188,7 +188,9 @@ function checkControllerSide(whichSide) {
y: tipVelocity.y * THROWN_VELOCITY_SCALING,
z: tipVelocity.z * THROWN_VELOCITY_SCALING } ,
inHand: false,
gravity: { x: 0, y: -2, z: 0},
color: THROWN_COLOR,
lifetime: 10,
gravity: { x: 0, y: -GRAVITY_STRENGTH, z: 0},
};
Particles.editParticle(handParticle, properties);

View file

@ -134,7 +134,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_nodeThread(new QThread(this)),
_datagramProcessor(),
_frameCount(0),
_fps(120.0f),
_fps(60.0f),
_justStarted(true),
_voxelImporter(NULL),
_importSucceded(false),
@ -553,7 +553,7 @@ void Application::initializeGL() {
}
// update before the first render
update(0.0f);
update(1.f / _fps);
InfoView::showFirstTime();
}

View file

@ -317,8 +317,6 @@ void CameraScriptableObject::setMode(const QString& mode) {
}
if (currentMode != targetMode) {
_camera->setMode(targetMode);
const float DEFAULT_MODE_SHIFT_PERIOD = 0.5f; // half second
_camera->setModeShiftPeriod(DEFAULT_MODE_SHIFT_PERIOD);
}
}

View file

@ -42,9 +42,8 @@ public:
void setTargetPosition(const glm::vec3& t);
void setTightness(float t) { _tightness = t; }
void setTargetRotation(const glm::quat& rotation);
void setMode(CameraMode m);
void setModeShiftPeriod(float r);
void setMode(CameraMode m);
void setFieldOfView(float f);
void setAspectRatio(float a);
void setNearClip(float n);
@ -130,6 +129,7 @@ public:
public slots:
QString getMode() const;
void setMode(const QString& mode);
void setModeShiftPeriod(float r) {_camera->setModeShiftPeriod(r); }
void setPosition(const glm::vec3& value) { _camera->setTargetPosition(value);}
glm::vec3 getPosition() const { return _camera->getPosition(); }

View file

@ -346,6 +346,7 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::StringHair, 0, false);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true);
addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));

View file

@ -326,6 +326,7 @@ namespace MenuOption {
const QString Bandwidth = "Bandwidth Display";
const QString BandwidthDetails = "Bandwidth Details";
const QString BuckyBalls = "Bucky Balls";
const QString StringHair = "String Hair";
const QString CascadedShadows = "Cascaded";
const QString Chat = "Chat...";
const QString ChatCircling = "Chat Circling";

View file

@ -68,6 +68,7 @@ void printVector(glm::vec3 vec) {
qDebug("%4.2f, %4.2f, %4.2f", vec.x, vec.y, vec.z);
}
// Return the azimuth angle (in radians) between two points.
float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos) {
return atan2(head_pos.x - source_pos.x, head_pos.z - source_pos.z);

View file

@ -48,6 +48,10 @@ Avatar::Avatar() :
_skeletonModel(this),
_bodyYawDelta(0.0f),
_velocity(0.0f, 0.0f, 0.0f),
_lastVelocity(0.0f, 0.0f, 0.0f),
_acceleration(0.0f, 0.0f, 0.0f),
_angularVelocity(0.0f, 0.0f, 0.0f),
_lastOrientation(),
_leanScale(0.5f),
_scale(1.0f),
_worldUpDirection(DEFAULT_UP_DIRECTION),
@ -76,6 +80,7 @@ void Avatar::init() {
_skeletonModel.init();
_initialized = true;
_shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE);
initializeHair();
}
glm::vec3 Avatar::getChestPosition() const {
@ -134,10 +139,15 @@ void Avatar::simulate(float deltaTime) {
head->setPosition(headPosition);
head->setScale(_scale);
head->simulate(deltaTime, false, _shouldRenderBillboard);
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
simulateHair(deltaTime);
}
}
// update position by velocity, and subtract the change added earlier for gravity
_position += _velocity * deltaTime;
updateAcceleration(deltaTime);
// update animation for display name fade in/out
if ( _displayNameTargetAlpha != _displayNameAlpha) {
@ -157,6 +167,17 @@ void Avatar::simulate(float deltaTime) {
}
}
void Avatar::updateAcceleration(float deltaTime) {
// Linear Component of Acceleration
_acceleration = (_velocity - _lastVelocity) * (1.f / deltaTime);
_lastVelocity = _velocity;
// Angular Component of Acceleration
glm::quat orientation = getOrientation();
glm::quat delta = glm::inverse(_lastOrientation) * orientation;
_angularVelocity = safeEulerAngles(delta) * (1.f / deltaTime);
_lastOrientation = getOrientation();
}
void Avatar::setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction) {
_mouseRayOrigin = origin;
_mouseRayDirection = direction;
@ -357,6 +378,232 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) {
getHand()->render(false, modelRenderMode);
}
getHead()->render(1.0f, modelRenderMode);
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
renderHair();
}
}
//
// 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 link = 0; link < HAIR_MAX_CONSTRAINTS; link++) {
_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link] = -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);
}
}
}
qDebug() << "Initialize Hair";
}
bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const {

View file

@ -32,6 +32,10 @@ static const float RESCALING_TOLERANCE = .02f;
extern const float CHAT_MESSAGE_SCALE;
extern const float CHAT_MESSAGE_HEIGHT;
const int HAIR_STRANDS = 150; // Number of strands of hair
const int HAIR_LINKS = 10; // Number of links in a hair strand
const int HAIR_MAX_CONSTRAINTS = 2; // Hair verlet is connected to at most how many others
enum DriveKeys {
FWD = 0,
BACK,
@ -145,6 +149,9 @@ public:
Q_INVOKABLE glm::quat getJointCombinedRotation(int index) const;
Q_INVOKABLE glm::quat getJointCombinedRotation(const QString& name) const;
glm::vec3 getAcceleration() const { return _acceleration; }
glm::vec3 getAngularVelocity() const { return _angularVelocity; }
public slots:
void updateCollisionGroups();
@ -156,6 +163,10 @@ protected:
QVector<Model*> _attachmentModels;
float _bodyYawDelta;
glm::vec3 _velocity;
glm::vec3 _lastVelocity;
glm::vec3 _acceleration;
glm::vec3 _angularVelocity;
glm::quat _lastOrientation;
float _leanScale;
float _scale;
glm::vec3 _worldUpDirection;
@ -172,6 +183,7 @@ protected:
glm::vec3 getBodyFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
void setScale(float scale);
void updateAcceleration(float deltaTime);
float getSkeletonHeight() const;
float getHeadHeight() const;
@ -187,6 +199,18 @@ 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:
@ -198,6 +222,7 @@ private:
void renderBillboard();
float getBillboardSize() const;
};
#endif // hifi_Avatar_h

View file

@ -194,6 +194,13 @@ void MyAvatar::simulate(float deltaTime) {
head->setScale(_scale);
head->simulate(deltaTime, true);
}
{
PerformanceTimer perfTimer("MyAvatar::simulate/hair Simulate");
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
simulateHair(deltaTime);
}
}
{
PerformanceTimer perfTimer("MyAvatar::simulate/ragdoll");
@ -368,6 +375,7 @@ void MyAvatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
if (!_shouldRender) {
return; // exit early
}
Avatar::render(cameraPosition, renderMode);
// don't display IK constraints in shadow mode
@ -420,6 +428,25 @@ void MyAvatar::renderHeadMouse(int screenWidth, int screenHeight) const {
}
}
const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f);
glm::vec3 MyAvatar::getLeftPalmPosition() {
glm::vec3 leftHandPosition;
getSkeletonModel().getLeftHandPosition(leftHandPosition);
glm::quat leftRotation;
getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation);
leftHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(leftRotation);
return leftHandPosition;
}
glm::vec3 MyAvatar::getRightPalmPosition() {
glm::vec3 rightHandPosition;
getSkeletonModel().getRightHandPosition(rightHandPosition);
glm::quat rightRotation;
getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation);
rightHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(rightRotation);
return rightHandPosition;
}
void MyAvatar::setLocalGravity(glm::vec3 gravity) {
_motionBehaviors |= AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
// Environmental and Local gravities are incompatible. Since Local is being set here
@ -831,6 +858,9 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
// Render head so long as the camera isn't inside it
if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) {
getHead()->render(1.0f, modelRenderMode);
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
renderHair();
}
}
getHand()->render(true, modelRenderMode);
}
@ -881,11 +911,19 @@ void MyAvatar::updateOrientation(float deltaTime) {
float yaw, pitch, roll;
OculusManager::getEulerAngles(yaw, pitch, roll);
// ... so they need to be converted to degrees before we do math...
yaw *= DEGREES_PER_RADIAN;
pitch *= DEGREES_PER_RADIAN;
roll *= DEGREES_PER_RADIAN;
// Record the angular velocity
Head* head = getHead();
head->setBaseYaw(yaw * DEGREES_PER_RADIAN);
head->setBasePitch(pitch * DEGREES_PER_RADIAN);
head->setBaseRoll(roll * DEGREES_PER_RADIAN);
glm::vec3 angularVelocity(yaw - head->getBaseYaw(), pitch - head->getBasePitch(), roll - head->getBaseRoll());
head->setAngularVelocity(angularVelocity);
head->setBaseYaw(yaw);
head->setBasePitch(pitch);
head->setBaseRoll(roll);
}
// update the euler angles
@ -974,6 +1012,7 @@ void MyAvatar::updatePosition(float deltaTime) {
} else {
_position += _velocity * deltaTime;
}
updateAcceleration(deltaTime);
}
// update moving flag based on speed

View file

@ -139,7 +139,10 @@ public slots:
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
void updateMotionBehaviorsFromMenu();
glm::vec3 getLeftPalmPosition();
glm::vec3 getRightPalmPosition();
signals:
void transformChanged();

View file

@ -39,9 +39,10 @@ inline float min(float a, float b) {
ApplicationOverlay::ApplicationOverlay() :
_framebufferObject(NULL),
_textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE),
_crosshairTexture(0),
_alpha(1.0f),
_active(true) {
_active(true),
_crosshairTexture(0)
{
memset(_reticleActive, 0, sizeof(_reticleActive));
memset(_magActive, 0, sizeof(_reticleActive));