From 58d460da0927c9b7a2f2012619e047fb32943c59 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Wed, 25 Jun 2014 15:14:17 -0700 Subject: [PATCH] option to turn on/off string hair --- examples/concertCamera.js | 67 ++++++++++++++++++++++++++++ interface/src/Menu.cpp | 1 + interface/src/Menu.h | 1 + interface/src/avatar/Avatar.cpp | 72 ++++++++++++------------------- interface/src/avatar/Avatar.h | 6 +-- interface/src/avatar/MyAvatar.cpp | 12 +++++- 6 files changed, 110 insertions(+), 49 deletions(-) create mode 100644 examples/concertCamera.js diff --git a/examples/concertCamera.js b/examples/concertCamera.js new file mode 100644 index 0000000000..0d26fd8ae0 --- /dev/null +++ b/examples/concertCamera.js @@ -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.0, y: 241.6, z: 7304.1}, {x: 7973.7, 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} ]; +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} ]; + +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); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 7d4c7d109d..c0c798799a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -345,6 +345,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())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 5b479773b6..3665f5838f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -319,6 +319,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"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index ee8e7266cb..5a294bb2a5 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -140,7 +140,9 @@ void Avatar::simulate(float deltaTime) { head->setScale(_scale); head->simulate(deltaTime, false, _shouldRenderBillboard); - simulateHair(deltaTime); + if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { + simulateHair(deltaTime); + } } // update position by velocity, and subtract the change added earlier for gravity @@ -376,26 +378,32 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) { getHand()->render(false, modelRenderMode); } getHead()->render(1.0f, modelRenderMode); - renderHair(); + 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.18f; -const float CONSTRAINT_RELAXATION = 20.0f; -const glm::vec3 HAIR_GRAVITY(0.f, -0.015f, 0.f); +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.f; -const float HAIR_THICKNESS = 0.020f; -const float HAIR_STIFFNESS = 0.0006f; +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.f); -const float MAX_WIND_STRENGTH = 0.01f; -const float FINGER_LENGTH = 0.25; -const float FINGER_RADIUS = 0.10; +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() { // @@ -403,26 +411,6 @@ void Avatar::renderHair() { // glm::vec3 headPosition = getHead()->getPosition(); - /* - glm::vec3 leftHandPosition, rightHandPosition; - getSkeletonModel().getLeftHandPosition(leftHandPosition); - getSkeletonModel().getRightHandPosition(rightHandPosition); - glm::quat leftRotation, rightRotation; - getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); - rightHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.f) * glm::inverse(rightRotation); - - glPushMatrix(); - glTranslatef(leftHandPosition.x, leftHandPosition.y, leftHandPosition.z); - glColor4f(1.0f, 0.0f, 0.0f, 0.5f); - glutSolidSphere(FINGER_RADIUS, 20, 20); - glPopMatrix(); - glPushMatrix(); - glTranslatef(rightHandPosition.x, rightHandPosition.y, rightHandPosition.z); - glColor4f(1.0f, 0.0f, 0.0f, 0.5f); - glutSolidSphere(FINGER_RADIUS, 20, 20); - glPopMatrix(); - */ - glPushMatrix(); glTranslatef(headPosition.x, headPosition.y, headPosition.z); const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame(); @@ -458,7 +446,7 @@ void Avatar::renderHair() { void Avatar::simulateHair(float deltaTime) { - deltaTime = glm::clamp(deltaTime, 0.f, 1.f / 30.f); + 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; @@ -476,8 +464,8 @@ void Avatar::simulateHair(float deltaTime) { glm::quat leftRotation, rightRotation; getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation); getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); - leftHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.f) * glm::inverse(leftRotation); - rightHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.f) * glm::inverse(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; @@ -499,9 +487,9 @@ void Avatar::simulateHair(float deltaTime) { // Resolve collision with head sphere if (glm::length(_hairPosition[vertexIndex]) < HEAD_RADIUS) { _hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex]) * - (HEAD_RADIUS - glm::length(_hairPosition[vertexIndex])); // * COLLISION_RELAXATION * deltaTime; + (HEAD_RADIUS - glm::length(_hairPosition[vertexIndex])); } - // Collide with hands + // 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)); @@ -518,7 +506,7 @@ void Avatar::simulateHair(float deltaTime) { // Add linear acceleration of the avatar body _hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime; - // Add stiffness (product) + // Add stiffness (like hair care products do) _hairPosition[vertexIndex] += (_hairOriginalPosition[vertexIndex] - _hairPosition[vertexIndex]) * powf(1.f - link / HAIR_LINKS, 2.f) * HAIR_STIFFNESS; @@ -580,13 +568,8 @@ void Avatar::initializeHair() { 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); - /*if ((azimuth > FACE_WIDTH) || (azimuth < -FACE_WIDTH)) { - elevation = randFloat() * PI_OVER_TWO; - } else { - elevation = (PI_OVER_TWO / 2.f) + randFloat() * (PI_OVER_TWO / 2.f); - }*/ glm::vec3 thisStrand(sinf(azimuth) * cosf(elevation), sinf(elevation), -cosf(azimuth) * cosf(elevation)); - thisStrand *= HEAD_RADIUS + 0.01f; + thisStrand *= HEAD_RADIUS; for (int link = 0; link < HAIR_LINKS; link++) { int vertexIndex = strand * HAIR_LINKS + link; @@ -610,6 +593,7 @@ void Avatar::initializeHair() { _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); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 40f92b468c..f20db1019d 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -32,9 +32,9 @@ static const float RESCALING_TOLERANCE = .02f; extern const float CHAT_MESSAGE_SCALE; extern const float CHAT_MESSAGE_HEIGHT; -const int HAIR_STRANDS = 200; // Number of strands of hair -const int HAIR_LINKS = 10; // Number of links in a hair strand -const int HAIR_MAX_CONSTRAINTS = 2; +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, diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5d6b381296..b853f3ff8f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -195,7 +195,12 @@ void MyAvatar::simulate(float deltaTime) { head->simulate(deltaTime, true); } - simulateHair(deltaTime); + { + PerformanceTimer perfTimer("MyAvatar::simulate/hair Simulate"); + if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { + simulateHair(deltaTime); + } + } { PerformanceTimer perfTimer("MyAvatar::simulate/ragdoll"); @@ -370,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 @@ -852,7 +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); - renderHair(); + if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { + renderHair(); + } } getHand()->render(true, modelRenderMode); }